Math-PlanePath-129/0002755000175000017500000000000014001441531011701 5ustar ggggMath-PlanePath-129/COPYING0000644000175000017500000010437410641206144012750 0ustar gggg GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . Math-PlanePath-129/tools/0002755000175000017500000000000014001441522013041 5ustar ggggMath-PlanePath-129/tools/alternate-paper-dxdy.pl0000644000175000017500000000407012022542003017424 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl alternate-paper-dxdy.pl # use 5.010; use strict; # uncomment this to run the ### lines #use Smart::Comments; { my @pending_state; foreach my $rot (0,1,2,3) { foreach my $oddpos (0,1) { push @pending_state, make_state (bit => 0, lowerbit => 0, rot => $rot, oddpos => $oddpos, nextturn => 0); } } my $count = 0; my @seen_state; my $depth = 1; foreach my $state (@pending_state) { $seen_state[$state] = $depth; } while (@pending_state) { my @new_pending_state; foreach my $state (@pending_state) { $count++; ### consider state: $state foreach my $bit (0 .. 1) { my $next_state = $next_state[$state+$bit]; if (! $seen_state[$next_state]) { $seen_state[$next_state] = $depth; push @new_pending_state, $next_state; ### push: "$next_state depth $depth" } } $depth++; } @pending_state = @new_pending_state; } for (my $state = 0; $state < @next_state; $state += 2) { $seen_state[$state] ||= '-'; my $state_string = state_string($state); print "# used state $state depth $seen_state[$state] $state_string\n"; } print "used state count $count\n"; } exit 0; Math-PlanePath-129/tools/dragon-curve-table.pl0000644000175000017500000001463312021026530017062 0ustar gggg#!/usr/bin/perl -w # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl dragon-curve-table.pl # # Print the state tables used for DragonCurve n_to_xy(). use 5.010; use strict; use List::Util 'max'; # uncomment this to run the ### lines #use Smart::Comments; sub print_table { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length($_//'')} @$aref); foreach my $i (0 .. $#$aref) { printf "%*s", $entry_width, $aref->[$i]//'undef'; if ($i == $#$aref) { print ");\n"; } else { print ","; if (($i % 16) == 15 || ($entry_width >= 3 && ($i % 4) == 3)) { print "\n ".(" " x length($name)); } elsif (($i % 4) == 3) { print " "; } } } } my @next_state; my @digit_to_x; my @digit_to_y; my @digit_to_dxdy; sub make_state { my %param = @_; my $state = 0; $state <<= 1; $state |= delete $param{'rev'}; $state <<= 2; $state |= delete $param{'rot'}; $state <<= 2; $state |= delete $param{'digit'}; return $state; } sub state_string { my ($state) = @_; my $digit = $state & 3; $state >>= 2; my $rot = $state & 3; $state >>= 2; my $rev = $state & 1; $state >>= 1; return "rot=$rot rev=$rev (digit=$digit)"; } foreach my $rot (0 .. 3) { foreach my $rev (0, 1) { foreach my $digit (0, 1, 2, 3) { my $state = make_state (rot => $rot, rev => $rev, digit => $digit); my $new_rev; my $new_rot = $rot; my $x; my $y; if ($rev) { # # 2<--3 # ^ | # | v # 0<--1 * # if ($digit == 0) { $x = 0; $y = 0; $new_rev = 0; } elsif ($digit == 1) { $x = 1; $y = 0; $new_rev = 1; $new_rot++; } elsif ($digit == 2) { $x = 1; $y = 1; $new_rev = 0; } elsif ($digit == 3) { $x = 2; $y = 1; $new_rev = 1; $new_rot--; } } else { # # 0 3<--* # | ^ # v | # 1<--2 # if ($digit == 0) { $x = 0; $y = 0; $new_rev = 0; $new_rot--; } elsif ($digit == 1) { $x = 0; $y = -1; $new_rev = 1; } elsif ($digit == 2) { $x = 1; $y = -1; $new_rev = 0; $new_rot++; } elsif ($digit == 3) { $x = 1; $y = 0; $new_rev = 1; } } $new_rot &= 3; my $dx = 1; my $dy = 0; if ($rot & 2) { $x = -$x; $y = -$y; $dx = -$dx; $dy = -$dy; } if ($rot & 1) { ($x,$y) = (-$y,$x); # rotate +90 ($dx,$dy) = (-$dy,$dx); # rotate +90 } ### rot to: "$x, $y" my $next_dx = $x; my $next_dy = $y; $digit_to_x[$state] = $x; $digit_to_y[$state] = $y; if ($digit == 0) { $digit_to_dxdy[$state] = $dx; $digit_to_dxdy[$state+1] = $dy; } my $next_state = make_state (rot => $new_rot, rev => $new_rev, digit => 0); $next_state[$state] = $next_state; } } } ### @next_state ### next_state length: 4*(4*2*2 + 4*2) print "# next_state length ", scalar(@next_state), "\n"; print_table ("next_state", \@next_state); print_table ("digit_to_x", \@digit_to_x); print_table ("digit_to_y", \@digit_to_y); print_table ("digit_to_dxdy", \@digit_to_dxdy); print "\n"; # { # DIGIT: foreach my $digit (0 .. 3) { # foreach my $rot (0 .. 3) { # foreach my $rev (0 .. 1) { # if ($digit_to_x[make_state(rot => $rot, # rev => $rev, # digit => $digit)] # != $digit_to_dxdy[make_state(rot => $rot, # rev => $rev, # digit => 0)]) { # print "digit=$digit dx different at rot=$rot rev=$rev\n"; # next DIGIT; # } # } # } # print "digit=$digit digit_to_x[] is dx\n"; # } # } { my @pending_state = (0, 4, 8, 12); # in 4 arm directions my $count = 0; my @seen_state; my $depth = 1; foreach my $state (@pending_state) { $seen_state[$state] = $depth; } while (@pending_state) { my @new_pending_state; foreach my $state (@pending_state) { $count++; ### consider state: $state foreach my $digit (0 .. 1) { my $next_state = $next_state[$state+$digit]; if (! $seen_state[$next_state]) { $seen_state[$next_state] = $depth; push @new_pending_state, $next_state; ### push: "$next_state depth $depth" } } $depth++; } @pending_state = @new_pending_state; } for (my $state = 0; $state < @next_state; $state += 2) { $seen_state[$state] ||= '-'; my $state_string = state_string($state); print "# used state $state depth $seen_state[$state] $state_string\n"; } print "used state count $count\n"; } use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; foreach my $int (0 .. 16) { ### $int my @digits = digit_split_lowtohigh($int,4); my $len = 2 ** $#digits; my $state = (scalar(@digits) & 3) << 2; ### @digits ### $len ### initial state: $state.' '.state_string($state) my $x = 0; my $y = 0; foreach my $i (reverse 0 .. $#digits) { ### at: "i=$i len=$len digit=$digits[$i] state=$state ".state_string($state) $state += $digits[$i]; ### digit x: $digit_to_x[$state] ### digit y: $digit_to_y[$state] $x += $len * $digit_to_x[$state]; $y += $len * $digit_to_y[$state]; $state = $next_state[$state]; $len /= 2; } ### $x ### $y print "$int $x $y\n"; } exit 0; __END__ Math-PlanePath-129/tools/hilbert-spiral-table.pl0000644000175000017500000001627411666767377017455 0ustar gggg#!/usr/bin/perl -w # Copyright 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'max'; # uncomment this to run the ### lines #use Smart::Comments; sub print_table { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length($_//'')} @$aref); foreach my $i (0 .. $#$aref) { printf "%s", ($aref->[$i]//'undef'); if ($i == $#$aref) { print ");\n"; } else { print ","; if (($i % 16) == 15) { print "\n ".(" " x length($name)); } elsif (($i % 4) == 3) { print " "; } } } } sub print_table12 { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length($_//'')} @$aref); foreach my $i (0 .. $#$aref) { printf "%*s", $entry_width, ($aref->[$i]//'undef'); if ($i == $#$aref) { print ");\n"; } else { print ","; if (($i % 12) == 11) { print "\n ".(" " x length($name)); } elsif (($i % 4) == 3) { print " "; } } } } sub make_state { my ($rot, $transpose, $spiral) = @_; $transpose %= 2; $rot %= 2; $spiral %= 2; return 4*($rot + 2*$transpose + 4*$spiral); } my @next_state; my @digit_to_x; my @digit_to_y; my @xy_to_digit; my @min_digit; my @max_digit; foreach my $spiral (0,1) { foreach my $rot (0, 1) { foreach my $transpose (0, ($spiral ? () : (1))) { my $state = make_state ($rot, $transpose, $spiral); # range 0 [X,_] # range 1 [X,X] # range 2 [_,X] foreach my $xrange (0,1,2) { foreach my $yrange (0,1,2) { my $xr = $xrange; my $yr = $yrange; my $bits = $xr + 3*$yr; # before rot+transpose if ($rot) { $xr = 2-$xr; $yr = 2-$yr; } if ($transpose) { ($xr,$yr) = ($yr,$xr); } my ($min_digit, $max_digit); # 3--2 # | # 0--1 if ($xr == 0) { # 0 or 3 if ($yr == 0) { # x,y both low, 0 only $min_digit = 0; $max_digit = 0; } elsif ($yr == 1) { # y either, 0 or 3 $min_digit = 0; $max_digit = 3; } elsif ($yr == 2) { # y high, 3 only $min_digit = 3; $max_digit = 3; } } elsif ($xr == 1) { # x either, any 0,1,2,3 if ($yr == 0) { # y low, 0 or 1 $min_digit = 0; $max_digit = 1; } elsif ($yr == 1) { # y either, 0,1,2,3 $min_digit = 0; $max_digit = 3; } elsif ($yr == 2) { # y high, 2,3 only $min_digit = 2; $max_digit = 3; } } else { # x high, 1 or 2 if ($yr == 0) { # y low, 1 only $min_digit = 1; $max_digit = 1; } elsif ($yr == 1) { # y either, 1 or 2 $min_digit = 1; $max_digit = 2; } elsif ($yr == 2) { # y high, 2 only $min_digit = 2; $max_digit = 2; } } ### range store: $state+$bits my $key = 3*$state + $bits; if (defined $min_digit[$key]) { # die "oops min_digit[] already: state=$state bits=$bits value=$min_digit[$state+$bits], new=$min_digit"; } $min_digit[$key] = $min_digit; $max_digit[$key] = $max_digit; } } ### @min_digit foreach my $orig_digit (0, 1, 2, 3) { my $digit = $orig_digit; my $xo = 0; my $yo = 0; my $new_transpose = $transpose; my $new_rot = $rot; my $new_spiral; # 3--2 # | # 0--1 if ($digit == 0) { if ($spiral) { $new_spiral = 1; $new_rot ^= 1; } else { $new_transpose ^= 1; $new_spiral = 0; } } elsif ($digit == 1) { $xo = 1; $new_spiral = 0; } elsif ($digit == 2) { $xo = 1; $yo = 1; $new_spiral = 0; } elsif ($digit == 3) { $yo = 1; $new_transpose ^= 1; $new_rot ^= 1; $new_spiral = 0; } ### base: "$xo, $yo" if ($transpose) { ($xo,$yo) = ($yo,$xo); } ### transp to: "$xo, $yo" if ($rot) { $xo ^= 1; $yo ^= 1; } ### rot to: "$xo, $yo" $digit_to_x[$state+$orig_digit] = $xo; $digit_to_y[$state+$orig_digit] = $yo; $xy_to_digit[$state + $xo*2+$yo] = $orig_digit; my $next_state = make_state ($new_rot, $new_transpose, $new_spiral); $next_state[$state+$orig_digit] = $next_state; } } } } ### @next_state ### @digit_to_x ### @digit_to_y ### next_state length: 4*(4*2*2 + 4*2) ### next_state length: scalar(@next_state) print_table ("next_state", \@next_state); print_table ("digit_to_x", \@digit_to_x); print_table ("digit_to_y", \@digit_to_y); print_table ("xy_to_digit", \@xy_to_digit); print_table12 ("min_digit", \@min_digit); print_table12 ("max_digit", \@max_digit); my $spiral_rot_state = make_state (1, # rot 0, # transpose 1); # spiral print "# neg state $spiral_rot_state\n"; print "\n"; exit 0; __END__ my $x_cmp = $x_max + $len; my $y_cmp = $y_max + $len; my $digit = $min_digit[4*$min_state + ($x1 >= $x_cmp) + 2*($x2 >= $x_cmp) + ($y1 >= $y_cmp) + 2*($y2 >= $y_cmp)]; $min_state += $digit; $n_lo += $digit * $power; if ($digit_to_x[$min_state]) { $x_min += $len; } if ($digit_to_y[$min_state]) { $x_min += $len; } $min_state = $next_state[$min_state + $min_digit]; Math-PlanePath-129/tools/dekking-curve-table.pl0000644000175000017500000001543512021305065017230 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use List::Util 'max'; use Math::PlanePath::DekkingCentres; # uncomment this to run the ### lines #use Smart::Comments; sub print_table { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {defined $_ ? length : 5} @$aref); foreach my $i (0 .. $#$aref) { printf "%*s", $entry_width, $aref->[$i]//'undef'; if ($i == $#$aref) { print ");\n"; } else { print ","; if ($entry_width >= 2 && ($i % 25) == 4) { print " # ".($i-4); } if (($i % 25) == 24 || $entry_width >= 2 && ($i % 5) == 4) { print "\n ".(" " x length($name)); } elsif (($i % 5) == 4) { print " "; } } } } sub make_state { my ($rev, $rot) = @_; $rev %= 2; $rot %= 4; return 25*($rot + 4*$rev); } my @next_state; my @edge_dx; my @edge_dy; my @yx_to_digit; foreach my $rev (0, 1) { foreach my $rot (0, 1, 2, 3) { foreach my $orig_digit (0 .. 24) { my $digit = $orig_digit; if ($rev) { $digit = 25-$digit; } my $xo; my $yo; my $new_rot = $rot; my $new_rev = $rev; if ($digit == 0) { $xo = 0; $yo = 0; } elsif ($digit == 1) { $xo = 1; $yo = 0; } elsif ($digit == 2) { $xo = 2; $yo = 0; $new_rot = $rot - 1; $new_rev ^= 1; } elsif ($digit == 3) { $xo = 2; $yo = 1; $new_rev ^= 1; } elsif ($digit == 4) { $xo = 1; $yo = 1; $new_rot = $rot + 1; } elsif ($digit == 5) { $xo = 1; $yo = 2; } elsif ($digit == 6) { $xo = 2; $yo = 2; $new_rot = $rot - 1; $new_rev ^= 1; } elsif ($digit == 7) { $xo = 2; $yo = 3; $new_rev ^= 1; } elsif ($digit == 8) { $xo = 1; $yo = 3; $new_rot = $rot + 2; } elsif ($digit == 9) { $xo = 0; $yo = 3; $new_rot = $rot - 1; $new_rev ^= 1; } elsif ($digit == 10) { $xo = 0; $yo = 4; } elsif ($digit == 11) { $xo = 1; $yo = 4; } elsif ($digit == 12) { $xo = 2; $yo = 4; $new_rot = $rot + 2; $new_rev ^= 1; } elsif ($digit == 13) { $xo = 3; $yo = 4; $new_rot = $rot + 1; } elsif ($digit == 14) { $xo = 3; $yo = 5; $new_rot = $rot + 2; $new_rev ^= 1; } elsif ($digit == 15) { $xo = 4; $yo = 5; $new_rot = $rot - 1; } elsif ($digit == 16) { $xo = 4; $yo = 4; $new_rot = $rot - 1; } elsif ($digit == 17) { $xo = 4; $yo = 3; $new_rev ^= 1; } elsif ($digit == 18) { $xo = 3; $yo = 3; $new_rot = $rot - 1; } elsif ($digit == 19) { $xo = 3; $yo = 2; $new_rot = $rot + 1; $new_rev ^= 1; } elsif ($digit == 20) { $xo = 3; $yo = 1; $new_rot = $rot + 2; $new_rev ^= 1; } elsif ($digit == 21) { $xo = 4; $yo = 1; $new_rot = $rot + 1; } elsif ($digit == 22) { $xo = 4; $yo = 2; } elsif ($digit == 23) { $xo = 5; $yo = 2; $new_rot = $rot + 1; $new_rev ^= 1; } elsif ($digit == 24) { $xo = 5; $yo = 1; $new_rot = $rot + 1; $new_rev ^= 1; } elsif ($digit == 25) { $xo = 5; $yo = 0; $new_rot = $rot + 1; } else { die; } ### base: "$xo, $yo" my $state = make_state ($rev, $rot); my $shift_xo = $xo; my $shift_yo = $yo; if ($rot & 2) { $shift_xo = 5 - $shift_xo; $shift_yo = 5 - $shift_yo; } if ($rot & 1) { ($shift_xo,$shift_yo) = (5-$shift_yo,$shift_xo); } $yx_to_digit[$state + $shift_yo*5 + $shift_xo] = $orig_digit; # if ($rev) { # if (($rot % 4) == 0) { # } elsif (($rot % 4) == 1) { # $yo -= 1; # } elsif (($rot % 4) == 2) { # $yo -= 1; # $xo -= 1; # } elsif (($rot % 4) == 3) { # $xo -= 1; # } # } else { # if (($rot % 4) == 0) { # } elsif (($rot % 4) == 1) { # $yo -= 1; # } elsif (($rot % 4) == 2) { # $yo -= 1; # $xo -= 1; # } elsif (($rot % 4) == 3) { # $xo -= 1; # } # # $xo -= 1; # } if ($rot & 2) { $xo = 5 - $xo; $yo = 5 - $yo; } if ($rot & 1) { ($xo,$yo) = (5-$yo,$xo); } ### rot to: "$xo, $yo" $edge_dx[$state+$orig_digit] = $xo - $Math::PlanePath::DekkingCentres::_digit_to_x[$state+$orig_digit]; $edge_dy[$state+$orig_digit] = $yo - $Math::PlanePath::DekkingCentres::_digit_to_y[$state+$orig_digit]; my $next_state = make_state ($new_rev, $new_rot); $next_state[$state+$orig_digit] = $next_state; } } } print "# state length ",scalar(@next_state)," in each of 4 tables\n"; # print_table ("next_state", \@next_state); print_table ("edge_dx", \@edge_dx); print_table ("edge_dy", \@edge_dy); # print_table ("last_yx_to_digit", \@yx_to_digit); ### @next_state ### @edge_dx ### @edge_dy ### @yx_to_digit ### next_state length: scalar(@next_state) print "\n"; exit 0; Math-PlanePath-129/tools/beta-omega-table.pl0000644000175000017500000002703712161517122016500 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl beta-omega-table.pl # # Print the state tables used in BetaOmega.pm. # # This isn't a thing of beauty. A state incorporates the beta vs omega # shape and the orientation of that shape as 4 rotations by 90-degrees, a # transpose swapping X,Y, and a reversal for numbering points the opposite # way around. # # The reversal is only needed for the beta, as noted in the # Math::PlanePath::BetaOmega POD. For an omega the reverse is the same as # the forward. make_state() collapses a reverse omega down to corresponding # plain forward omega. # # State values are 0, 4, 8, etc. Having them 4 apart means a base 4 digit # from N in n_to_xy() can be added state+digit to make an index into the # tables. # # For @max_digit and @min_digit the input is instead 3*3=9 values, and in # those tables the index is "state*3 + input". 3*state puts states 12 # apart, which is more than the 9 input values needs, but 3*state is a # little less work in the code than say (state/4)*9 to change from 4-stride # to exactly 9-stride. # use 5.010; use strict; use List::Util 'max'; # uncomment this to run the ### lines #use Smart::Comments; sub print_table { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length($_//'')} @$aref); foreach my $i (0 .. $#$aref) { printf "%*s", $entry_width, $aref->[$i]//'undef'; if ($i == $#$aref) { print ");\n"; } else { print ","; if (($i % 16) == 15 || ($entry_width >= 3 && ($i % 4) == 3)) { print "\n ".(" " x length($name)); } elsif (($i % 4) == 3) { print " "; } } } } sub print_table12 { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length($_//'')} @$aref); foreach my $i (0 .. $#$aref) { printf "%*s", $entry_width, $aref->[$i]//'undef'; if ($i == $#$aref) { print ");\n"; } else { print ","; if (($i % 12) == 11) { print "\n ".(" " x length($name)); } elsif (($i % 4) == 3) { print " "; } } } } my @next_state; my @digit_to_x; my @digit_to_y; my @xy_to_digit; my @min_digit; my @max_digit; sub state_string { my ($state) = @_; my $digit = $state % 4; $state = int($state/4); my $transpose = $state % 2; $state = int($state/2); my $rot = $state % 4; $state = int($state/4); my $rev = $state % 2; $state = int($state/2); my $omega = $state % 2; $state = int($state/2); my $omega_str = ($omega ? 'omega' : 'beta'); return "$omega_str transpose=$transpose rot=$rot rev=$rev"; } sub make_state { my ($omega, $rev, $rot, $transpose, $digit) = @_; if ($omega && $rev) { $rev = 0; if ($transpose) { $rot--; } else { $rot++; } $transpose ^= 1; } $transpose %= 2; $rev %= 2; $rot %= 4; return $digit + 4*($transpose + 2*($rot + 4*($rev + 2*$omega))); } foreach my $omega (0, 1) { foreach my $rev (0, ($omega ? () : (1))) { foreach my $rot (0, 1, 2, 3) { foreach my $transpose (0, 1) { my $state = make_state ($omega, $rev, $rot, $transpose, 0); ### $state # range 0 [X,_] # range 1 [X,X] # range 2 [_,X] foreach my $xrange (0,1,2) { foreach my $yrange (0,1,2) { my $xr = $xrange; my $yr = $yrange; my $bits = $xr + 3*$yr; # before transpose etc if ($rot & 1) { ($xr,$yr) = ($yr,2-$xr); } if ($rot & 2) { $xr = 2-$xr; $yr = 2-$yr; } if ($transpose) { ($xr,$yr) = ($yr,$xr); } if ($rev) { # 2--1 # | | # 3 0 $xr = 2-$xr; } my ($min_digit, $max_digit); # 1--2 # | | # 0 3 if ($xr == 0) { # 0 or 1 only if ($yr == 0) { # x,y both low, 0 only $min_digit = 0; $max_digit = 0; } elsif ($yr == 1) { # y either, 0 or 1 $min_digit = 0; $max_digit = 1; } elsif ($yr == 2) { # y high, 1 only $min_digit = 1; $max_digit = 1; } } elsif ($xr == 1) { # x either, any 0,1,2,3 if ($yr == 0) { # y low, 0 or 3 $min_digit = 0; $max_digit = 3; } elsif ($yr == 1) { # y either, 0,1,2,3 $min_digit = 0; $max_digit = 3; } elsif ($yr == 2) { # y high, 1,2 only $min_digit = 1; $max_digit = 2; } } else { # x high, 2 or 3 if ($yr == 0) { # y low, 3 only $min_digit = 3; $max_digit = 3; } elsif ($yr == 1) { # y either, 2 or 3 $min_digit = 2; $max_digit = 3; } elsif ($yr == 2) { # y high, 2 only $min_digit = 2; $max_digit = 2; } } ### range store: $state+$bits my $key = 3*$state + $bits; if (defined $min_digit[$key]) { die "oops min_digit[] already: state=$state bits=$bits value=$min_digit[$state+$bits], new=$min_digit"; } $min_digit[$key] = $min_digit; $max_digit[$key] = $max_digit; } } ### @min_digit foreach my $orig_digit (0, 1, 2, 3) { my $digit = $orig_digit; if ($rev) { $digit = 3-$digit; } my $xo = 0; my $yo = 0; my $new_transpose = $transpose; my $new_rot = $rot; my $new_omega = 0; my $new_rev = $rev; if ($omega) { # 1---2 # | | # --0 3-- $new_omega = 0; if ($digit == 0) { $new_transpose = $transpose ^ 1; if ($transpose) { $new_rot = $rot + 1; } else { $new_rot = $rot - 1; } } elsif ($digit == 1) { $yo = 1; if ($transpose) { $new_rot = $rot - 1; } else { $new_rot = $rot + 1; } } elsif ($digit == 2) { $xo = 1; $yo = 1; $new_transpose = $transpose ^ 1; $new_rev ^= 1; } elsif ($digit == 3) { $xo = 1; $new_rot = $rot + 2; $new_rev ^= 1; } } else { # 1---2 # | | # --0 3 # | if ($digit == 0) { $new_transpose = $transpose ^ 1; if ($transpose) { $new_rot = $rot + 1; } else { $new_rot = $rot - 1; } } elsif ($digit == 1) { $yo = 1; if ($transpose) { $new_rot = $rot - 1; } else { $new_rot = $rot + 1; } } elsif ($digit == 2) { $xo = 1; $yo = 1; $new_transpose = $transpose ^ 1; $new_rev ^= 1; } elsif ($digit == 3) { $xo = 1; if ($transpose) { $new_rot = $rot + 1; } else { $new_rot = $rot - 1; } $new_omega = 1; } } ### base: "$xo, $yo" if ($transpose) { ($xo,$yo) = ($yo,$xo); } ### transp to: "$xo, $yo" if ($rot & 2) { $xo ^= 1; $yo ^= 1; } if ($rot & 1) { ($xo,$yo) = ($yo^1,$xo); } ### rot to: "$xo, $yo" $digit_to_x[$state+$orig_digit] = $xo; $digit_to_y[$state+$orig_digit] = $yo; $xy_to_digit[$state + $xo*2+$yo] = $orig_digit; my $next_state = make_state ($new_omega, $new_rev, $new_rot, $new_transpose, 0); $next_state[$state+$orig_digit] = $next_state; } } } } } ### @next_state ### @digit_to_x ### @digit_to_y ### next_state length: 4*(4*2*2 + 4*2) ### next_state length: scalar(@next_state) my $next_state_size = scalar(@next_state); my $state_count = $next_state_size/4; print "# next_state table has $next_state_size entries, is $state_count states\n"; print_table ("next_state", \@next_state); print_table ("digit_to_x", \@digit_to_x); print_table ("digit_to_y", \@digit_to_y); print_table ("xy_to_digit", \@xy_to_digit); print_table12 ("min_digit", \@min_digit); print_table12 ("max_digit", \@max_digit); my $invert_state = make_state (0, # omega 0, # rev 3, # rot 1, # transpose 0); # digit ### $invert_state print "\n"; { my @pending_state = (0); my $count = 0; my @seen_state; my $depth = 0; $seen_state[0] = $depth; while (@pending_state) { $depth++; my @new_pending_state; foreach my $state (@pending_state) { $count++; ### consider state: $state foreach my $digit (0 .. 3) { my $next_state = $next_state[$state+$digit]; if (! defined $seen_state[$next_state]) { $seen_state[$next_state] = $depth; push @new_pending_state, $next_state; ### push: "$next_state depth $depth" } } } @pending_state = @new_pending_state; } for (my $state = 0; $state < @next_state; $state += 4) { print "# used state $state depth $seen_state[$state]\n"; } print "used state count $count\n"; } { print "\n"; print "initial 0: ",state_string(0),"\n"; print "initial 28: ",state_string(28),"\n"; require Graph::Easy; my $g = Graph::Easy->new; for (my $state = 0; $state < scalar(@next_state); $state += 4) { my $next = $next_state[$state]; $g->add_edge("$state: ".state_string($state), "$next: ".state_string($next)); } print $g->as_ascii(); } exit 0; Math-PlanePath-129/tools/pythagorean-tree.pl0000644000175000017500000000407313655404750016676 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl pythagorean-tree.pl # # Print tree diagrams used in the Math::PlanePath::PythagoreanTree docs. # use 5.010; use strict; use Math::PlanePath::PythagoreanTree; foreach my $tree_type ('UAD','UArD','FB','UMT') { my $str = <<"HERE"; tree_type => "$tree_type" coordinates A,B ______________ 001 _____________ / | \\ 00002 00003 00004 / | \\ / | \\ / | \\ 0005 00006 00007 00008 00009 00010 00011 00012 00013 HERE my $path = Math::PlanePath::PythagoreanTree->new(tree_type => $tree_type, coordinates => 'AB'); $str =~ s{(\d+)} { my ($x,$y) = $path->n_to_xy($1); my $fieldlen = length($1); sprintf '%-*s', $fieldlen, "$x,$y"; }ge; print $str; } # Previous horizontal across. # # my $str = <<"HERE"; # tree_type => "$tree_type" # # +-> 00005 # +-> 00002 --+-> 00006 # | +-> 00007 # | # | +-> 00008 # 001 --+-> 00003 --+-> 00009 # | +-> 00010 # | # | +-> 00011 # +-> 00004 --+-> 00012 # +-> 00013 # # HERE Math-PlanePath-129/tools/peano-diagonal-samples.pl0000644000175000017500000000453213731641530017732 0ustar gggg#!/usr/bin/perl -w # Copyright 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl peano-diagonal-samples.pl # # Print some of the PeanoDiagonals samples. # use 5.010; use strict; use FindBin; use File::Spec; use List::Util 'max'; use Math::NumSeq::Fibbinary; use lib File::Spec->catdir($FindBin::Bin, File::Spec->updir, 'devel/lib'); use Math::PlanePath::PeanoDiagonals; # uncomment this to run the ### lines # use Smart::Comments; { my $path = Math::PlanePath::PeanoDiagonals->new; my $x_max = 9; foreach my $y (reverse 0 .. 9) { printf ' %3s | ', $y==0 ? "Y=0" : $y; foreach my $x (0 .. $x_max) { my @n_list = $path->xy_to_n_list($x,$y); my $width = ($x==0 ? 3 : 6); my $half = int($width/2); my $str = ''; if (@n_list == 0) { } elsif (@n_list == 1) { $str = sprintf "%d%*s", $n_list[0], $half, ''; } elsif (@n_list == 2) { $str = sprintf '%d,%-*d', $n_list[0], $half, $n_list[1]; } else { die; } ### $x ### $y ### $str if ($x < $x_max) { length($str) <= $width or die "length"; } printf '%*s', $width, $str; } print "\n"; } print " +", ('-' x (4+$x_max*6)), "\n"; print "\n"; } { my $path = Math::PlanePath::PeanoDiagonals->new (radix => 4); my %seen; my $x_max = 9; my $y_max = 8; foreach my $n (0 .. 4**6) { my ($x,$y) = $path->n_to_xy($n); next if $x > $x_max; next if $y > $y_max; push @{$seen{$x,$y}}, $n; } foreach my $y (reverse 0 .. $y_max) { foreach my $x (0 .. $x_max) { my $aref = $seen{$x,$y} || []; my $str = join(',',@$aref); printf ' %8s', $str; } print "\n"; } } exit 0; Math-PlanePath-129/tools/hilbert-curve-table.pl0000644000175000017500000001471312036160013017241 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'max'; # uncomment this to run the ### lines #use Smart::Comments; sub print_table { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length} @$aref); foreach my $i (0 .. $#$aref) { printf "%d", $aref->[$i]; if ($i == $#$aref) { print ");\n"; } else { print ","; if (($i % 16) == 15) { print "\n ".(" " x length($name)); } elsif (($i % 4) == 3) { print " "; } } } } sub print_table12 { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length($_//'')} @$aref); foreach my $i (0 .. $#$aref) { printf "%*s", $entry_width, $aref->[$i]//'undef'; if ($i == $#$aref) { print ");\n"; } else { print ","; if (($i % 12) == 11) { print "\n ".(" " x length($name)); } elsif (($i % 4) == 3) { print " "; } } } } sub make_state { my ($rot, $transpose) = @_; $transpose %= 2; $rot %= 2; return 4*($transpose + 2*$rot); } my @next_state; my @digit_to_x; my @digit_to_y; my @yx_to_digit; my @min_digit; my @max_digit; foreach my $rot (0, 1) { foreach my $transpose (0, 1) { my $state = make_state ($rot, $transpose); # range 0 [X,_] # range 1 [X,X] # range 2 [_,X] foreach my $xrange (0,1,2) { foreach my $yrange (0,1,2) { my $xr = $xrange; my $yr = $yrange; my $bits = $xr + 3*$yr; # before rot+transpose if ($rot) { $xr = 2-$xr; $yr = 2-$yr; } if ($transpose) { ($xr,$yr) = ($yr,$xr); } my ($min_digit, $max_digit); # 3--2 # | # 0--1 if ($xr == 0) { # 0 or 3 if ($yr == 0) { # x,y both low, 0 only $min_digit = 0; $max_digit = 0; } elsif ($yr == 1) { # y either, 0 or 3 $min_digit = 0; $max_digit = 3; } elsif ($yr == 2) { # y high, 3 only $min_digit = 3; $max_digit = 3; } } elsif ($xr == 1) { # x either, any 0,1,2,3 if ($yr == 0) { # y low, 0 or 1 $min_digit = 0; $max_digit = 1; } elsif ($yr == 1) { # y either, 0,1,2,3 $min_digit = 0; $max_digit = 3; } elsif ($yr == 2) { # y high, 2,3 only $min_digit = 2; $max_digit = 3; } } else { # x high, 1 or 2 if ($yr == 0) { # y low, 1 only $min_digit = 1; $max_digit = 1; } elsif ($yr == 1) { # y either, 1 or 2 $min_digit = 1; $max_digit = 2; } elsif ($yr == 2) { # y high, 2 only $min_digit = 2; $max_digit = 2; } } ### range store: $state+$bits my $key = 3*$state + $bits; if (defined $min_digit[$key]) { die "oops min_digit[] already: state=$state bits=$bits value=$min_digit[$state+$bits], new=$min_digit"; } $min_digit[$key] = $min_digit; $max_digit[$key] = $max_digit; } } ### @min_digit foreach my $orig_digit (0, 1, 2, 3) { my $digit = $orig_digit; my $xo = 0; my $yo = 0; my $new_transpose = $transpose; my $new_rot = $rot; # 3--2 # | # 0--1 if ($digit == 0) { $new_transpose ^= 1; } elsif ($digit == 1) { $xo = 1; } elsif ($digit == 2) { $xo = 1; $yo = 1; } elsif ($digit == 3) { $yo = 1; $new_transpose ^= 1; $new_rot ^= 1; } ### base: "$xo, $yo" if ($transpose) { ($xo,$yo) = ($yo,$xo); } ### transp to: "$xo, $yo" if ($rot) { $xo ^= 1; $yo ^= 1; } ### rot to: "$xo, $yo" $digit_to_x[$state+$orig_digit] = $xo; $digit_to_y[$state+$orig_digit] = $yo; $yx_to_digit[$state + 2*$yo + $xo] = $orig_digit; my $next_state = make_state ($new_rot, $new_transpose); $next_state[$state+$orig_digit] = $next_state; } } } ### @next_state ### @digit_to_x ### @digit_to_y ### next_state length: 4*(4*2*2 + 4*2) ### next_state length: scalar(@next_state) print_table ("next_state", \@next_state); print_table ("digit_to_x", \@digit_to_x); print_table ("digit_to_y", \@digit_to_y); print_table ("yx_to_digit", \@yx_to_digit); print_table12 ("min_digit", \@min_digit); print_table12 ("max_digit", \@max_digit); my $invert_state = make_state (1, # rot 1); # transpose ### $invert_state print "\n"; exit 0; __END__ my $x_cmp = $x_max + $len; my $y_cmp = $y_max + $len; my $digit = $min_digit[4*$min_state + ($x1 >= $x_cmp) + 2*($x2 >= $x_cmp) + ($y1 >= $y_cmp) + 2*($y2 >= $y_cmp)]; $min_state += $digit; $n_lo += $digit * $power; if ($digit_to_x[$min_state]) { $x_min += $len; } if ($digit_to_y[$min_state]) { $x_min += $len; } $min_state = $next_state[$min_state + $min_digit]; Math-PlanePath-129/tools/kochel-curve-table.pl0000644000175000017500000002026411666767323017104 0ustar gggg#!/usr/bin/perl -w # Copyright 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use List::Util 'min','max'; # uncomment this to run the ### lines #use Smart::Comments; sub print_table { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {defined && length} @$aref); foreach my $i (0 .. $#$aref) { printf "%*s", $entry_width, $aref->[$i]//'undef'; if ($i == $#$aref) { print "); # ",$i-8,"\n"; } else { print ","; if (($i % 9) == 8) { print " # ".($i-8); } if (($i % 9) == 8) { print "\n ".(" " x length($name)); } elsif (($i % 3) == 2) { print " "; } } } } sub print_table36 { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {defined && length} @$aref); foreach my $i (0 .. $#$aref) { printf "%*d", $entry_width, $aref->[$i]; if ($i == $#$aref) { print ");\n"; } else { print ","; if (($i % 36) == 5) { print " # ".($i-5); } if (($i % 6) == 5) { print "\n ".(" " x length($name)); } elsif (($i % 6) == 5) { print " "; } } } } sub make_state { my ($f, $rev, $rot) = @_; $rev %= 2; if ($f && $rev) { $rot += 2; $rev = 0; } $rot %= 4; return 9*($rot + 4*($rev + 2*$f)); } # x__ 0 # xx_ 1 # xxx 2 # _xx 3 # __x 4 # _x_ 5 my @r_to_cover = ([1,0,0], [1,1,0], [1,1,1], [0,1,1], [0,0,1], [0,1,0]); my @reverse_range = (4,3,2,1,0,5); my @min_digit; my @max_digit; my @next_state; my @digit_to_x; my @digit_to_y; my @xy_to_digit; foreach my $f (0, 1) { foreach my $rot (0, 1, 2, 3) { foreach my $rev (0, ($f ? () : (1))) { my $state = make_state ($f, $rev, $rot); foreach my $orig_digit (0 .. 8) { my $digit = $orig_digit; if ($rev) { $digit = 8-$digit; } my $xo; my $yo; my $new_rot = $rot; my $new_rev = $rev; my $new_f; if ($f) { if ($digit == 0) { $xo = 0; $yo = 0; $new_f = 0; $new_rev ^= 1; $new_rot = $rot - 1; } elsif ($digit == 1) { $xo = 0; $yo = 1; $new_f = 1; } elsif ($digit == 2) { $xo = 0; $yo = 2; $new_f = 0; $new_rot = $rot + 1; } elsif ($digit == 3) { $xo = 1; $yo = 2; $new_rot = $rot - 1; $new_f = 1; } elsif ($digit == 4) { $xo = 1; $yo = 1; $new_f = 1; $new_rot = $rot + 2; } elsif ($digit == 5) { $xo = 1; $yo = 0; $new_f = 1; $new_rot = $rot - 1; } elsif ($digit == 6) { $xo = 2; $yo = 0; $new_f = 0; $new_rot = $rot - 1; $new_rev ^= 1; } elsif ($digit == 7) { $xo = 2; $yo = 1; $new_f = 1; } elsif ($digit == 8) { $xo = 2; $yo = 2; $new_f = 0; $new_rot = $rot + 1; } else { die; } } else { if ($digit == 0) { $xo = 0; $yo = 0; $new_rev ^= 1; $new_f = 0; $new_rot = $rot - 1; } elsif ($digit == 1) { $xo = 0; $yo = 1; $new_f = 1; } elsif ($digit == 2) { $xo = 0; $yo = 2; $new_f = 0; $new_rot = $rot + 1; } elsif ($digit == 3) { $xo = 1; $yo = 2; $new_rot = $rot - 1; $new_f = 1; } elsif ($digit == 4) { $xo = 2; $yo = 2; $new_f = 0; } elsif ($digit == 5) { $xo = 2; $yo = 1; $new_f = 1; $new_rot = $rot + 2; } elsif ($digit == 6) { $xo = 1; $yo = 1; $new_f = 0; $new_rev ^= 1; } elsif ($digit == 7) { $xo = 1; $yo = 0; $new_f = 1; $new_rot = $rot - 1; } elsif ($digit == 8) { $xo = 2; $yo = 0; $new_f = 0; } else { die; } } ### base: "$xo, $yo" if ($rot & 2) { $xo = 2 - $xo; $yo = 2 - $yo; } if ($rot & 1) { ($xo,$yo) = (2-$yo,$xo); } ### rot to: "$xo, $yo" $digit_to_x[$state+$orig_digit] = $xo; $digit_to_y[$state+$orig_digit] = $yo; $xy_to_digit[$state + 3*$xo + $yo] = $orig_digit; my $next_state = make_state ($new_f, $new_rev, $new_rot); $next_state[$state+$orig_digit] = $next_state; } foreach my $xrange (0 .. 5) { foreach my $yrange (0 .. 5) { my $xr = $xrange; my $yr = $yrange; my $bits = $xr + 6*$yr; # before transpose etc my $key = 4*$state + $bits; ### assert: (4*$state % 36) == 0 my $min_digit = 8; my $max_digit = 0; foreach my $digit (0 .. 8) { my $x = $digit_to_x[$state + $digit]; my $y = $digit_to_y[$state + $digit]; next unless $r_to_cover[$xr]->[$x]; next unless $r_to_cover[$yr]->[$y]; $min_digit = min($digit,$min_digit); $max_digit = max($digit,$max_digit); } ### min/max: "state=$state 4*state=".(4*$state)." bits=$bits key=$key" if (defined $min_digit[$key]) { # die "oops min_digit[] already: state=$state bits=$bits value=$min_digit[$state+$bits], new=$min_digit"; } $min_digit[$key] = $min_digit; $max_digit[$key] = $max_digit; } } ### @min_digit } } } print_table ("next_state", \@next_state); print_table ("digit_to_x", \@digit_to_x); print_table ("digit_to_y", \@digit_to_y); print_table ("xy_to_digit", \@xy_to_digit); print_table36 ("min_digit", \@min_digit); print_table36 ("max_digit", \@max_digit); print "# state length ",scalar(@next_state)," in each of 4 tables\n\n"; print "# R reverse state ",make_state(0,1,-1),"\n"; ### @next_state ### @digit_to_x ### @digit_to_y ### @xy_to_digit ### next_state length: scalar(@next_state) { my @pending_state = (0); my $count = 0; my @seen_state; my $depth = 1; $seen_state[0] = $depth; while (@pending_state) { my $state = pop @pending_state; $count++; ### consider state: $state foreach my $digit (0 .. 8) { my $next_state = $next_state[$state+$digit]; if (! $seen_state[$next_state]) { $seen_state[$next_state] = $depth; push @pending_state, $next_state; ### push: "$next_state depth $depth" } } $depth++; } for (my $state = 0; $state < @next_state; $state += 9) { print "# used state $state depth $seen_state[$state]\n"; } print "used state count $count\n"; } print "\n"; exit 0; Math-PlanePath-129/tools/dragon-curve-dxdy.pl0000644000175000017500000001163412022543023016743 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl dragon-curve-dxdy.pl # # Print the state tables used for DragonCurve n_to_dxdy(). These are not # the same as the tables for n_to_xy() which are in dragon-curve-table.pl. use 5.010; use strict; use List::Util 'max'; # uncomment this to run the ### lines #use Smart::Comments; sub print_table { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length($_//'')} @$aref); foreach my $i (0 .. $#$aref) { printf "%*s", $entry_width, $aref->[$i]//'undef'; if ($i == $#$aref) { print ");\n"; } else { print ","; if (($i % 16) == 15 || ($entry_width >= 3 && ($i % 4) == 3)) { print "\n ".(" " x length($name)); } elsif (($i % 4) == 3) { print " "; } } } } my @next_state; my @state_to_dxdy; sub make_state { my %param = @_; my $state = 0; $state <<= 1; $state |= delete $param{'nextturn'}; # high $state <<= 2; $state |= delete $param{'rot'}; $state <<= 1; $state |= delete $param{'prevbit'}; $state <<= 1; $state |= delete $param{'digit'}; # low if (%param) { die; } return $state; } sub state_string { my ($state) = @_; my $digit = $state & 1; $state >>= 1; my $prevbit = $state & 1; $state >>= 1; my $rot = $state & 3; $state >>= 2; my $nextturn = $state & 1; $state >>= 1; return "rot=$rot prevbit=$prevbit (digit=$digit)"; } foreach my $nextturn (0, 1) { foreach my $rot (0, 1, 2, 3) { foreach my $prevbit (0, 1) { my $state = make_state (nextturn => $nextturn, rot => $rot, prevbit => $prevbit, digit => 0); ### $state foreach my $bit (0, 1) { my $new_nextturn = $nextturn; my $new_prevbit = $bit; my $new_rot = $rot; if ($bit != $prevbit) { # count 0<->1 transitions $new_rot++; $new_rot &= 3; } if ($bit == 0) { $new_nextturn = $prevbit; # bit above lowest 0 } my $dx = 1; my $dy = 0; if ($rot & 2) { $dx = -$dx; $dy = -$dy; } if ($rot & 1) { ($dx,$dy) = (-$dy,$dx); # rotate +90 } ### rot to: "$dx, $dy" my $next_dx = $dx; my $next_dy = $dy; if ($nextturn) { ($next_dx,$next_dy) = ($next_dy,-$next_dx); # right, rotate -90 } else { ($next_dx,$next_dy) = (-$next_dy,$next_dx); # left, rotate +90 } my $frac_dx = $next_dx - $dx; my $frac_dy = $next_dy - $dy; my $masked_state = $state & 0x1C; $state_to_dxdy[$masked_state] = $dx; $state_to_dxdy[$masked_state + 1] = $dy; $state_to_dxdy[$masked_state + 2] = $frac_dx; $state_to_dxdy[$masked_state + 3] = $frac_dy; my $next_state = make_state (nextturn => $new_nextturn, rot => $new_rot, prevbit => $new_prevbit, digit => 0); $next_state[$state+$bit] = $next_state; } } } } ### @next_state ### @state_to_dxdy ### next_state length: 4*(4*2*2 + 4*2) print "# next_state length ", scalar(@next_state), "\n"; print_table ("next_state", \@next_state); print_table ("state_to_dxdy", \@state_to_dxdy); print "\n"; { my @pending_state = (0, 4, 8, 12); # in 4 arm directions my $count = 0; my @seen_state; my $depth = 1; foreach my $state (@pending_state) { $seen_state[$state] = $depth; } while (@pending_state) { my @new_pending_state; foreach my $state (@pending_state) { $count++; ### consider state: $state foreach my $bit (0 .. 1) { my $next_state = $next_state[$state+$bit]; if (! $seen_state[$next_state]) { $seen_state[$next_state] = $depth; push @new_pending_state, $next_state; ### push: "$next_state depth $depth" } } $depth++; } @pending_state = @new_pending_state; } for (my $state = 0; $state < @next_state; $state += 2) { $seen_state[$state] ||= '-'; my $state_string = state_string($state); print "# used state $state depth $seen_state[$state] $state_string\n"; } print "used state count $count\n"; } exit 0; Math-PlanePath-129/tools/flowsnake-centres-table.pl0000644000175000017500000001373212063226253020131 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Not working # Usage: perl flowsnake-centres-table.pl # # Print the state tables used for Math:PlanePath::FlowsnakeCentres n_to_xy(). use 5.010; use strict; use List::Util 'max'; # uncomment this to run the ### lines #use Smart::Comments; sub print_table14 { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length($_//'')} @$aref); foreach my $i (0 .. $#$aref) { my $entry_str = $aref->[$i]//'undef'; if ($i == $#$aref) { $entry_str .= ");"; } else { $entry_str .= ","; } if ($i % 14 == 0 && $#$aref > 14) { printf "%-*s", $entry_width+1, $entry_str; } else { printf "%*s", $entry_width+1, $entry_str; } if ($i % 14 == 13) { print " # ",$i-13,",",$i-6,"\n"; if ($i != $#$aref) { print " ".(" " x length($name)); } } elsif ($i % 7 == 6) { print " "; } } } sub print_table12 { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length($_//'')} @$aref); foreach my $i (0 .. $#$aref) { my $entry_str = $aref->[$i]//'undef'; if ($i == $#$aref) { $entry_str .= ");"; } else { $entry_str .= ","; } if ($i % 12 == 0 && $#$aref > 12) { printf "%-*s", $entry_width+1, $entry_str; } else { printf "%*s", $entry_width+1, $entry_str; } if ($i % 12 == 11) { print "\n"; if ($i != $#$aref) { print " ".(" " x length($name)); } } elsif ($i % 6 == 5) { print " "; } } } my @next_state; my @digit_to_i; my @digit_to_j; my @state_to_di; my @state_to_dj; sub make_state { my %param = @_; my $state = 0; $state *= 6; $state += delete $param{'rot'}; # high $state *= 2; $state += delete $param{'rev'}; $state *= 7; $state += delete $param{'digit'}; # low if (%param) { die; } return $state; } sub state_string { my ($state) = @_; my $digit = $state % 7; $state = int($state/7); # low my $rev = $state % 2; $state = int($state/2); my $rot = $state % 6; $state = int($state/6); # high return "rot=$rot rev=$rev (digit=$digit)"; } foreach my $rev (0, 1) { foreach my $rot (0 .. 5) { foreach my $digit (0 .. 6) { my $state = make_state (rot => $rot, rev => $rev, digit => $digit); my $new_rev = $rev; my $new_rot = $rot; my $plain_digit = ($rev ? 6-$digit : $digit); my ($i, $j); if ($rev) { # # 0 5 # ^ ^ # / / \ # 1 4 6---- # \ \ # # 2-----3 if ($digit == 0) { $i = 0; $j = 0; $new_rev = 0; } elsif ($digit == 1) { $i = 1; $j = 0; $new_rev = 1; $new_rot += 1; } elsif ($digit == 2) { $i = 2; $j = -1; $new_rev = 1; } elsif ($digit == 3) { $i = 3; $j = -1; $new_rot += 1; $new_rev = 1; } elsif ($digit == 4) { $i = 3; $j = 0; $new_rot += 3; $new_rev = 0; } elsif ($digit == 5) { $i = 2; $j = 0; $new_rot += 2; $new_rev = 0; } elsif ($digit == 6) { $i = 1; $j = 1; $new_rev = 1; } } else { # 4-->5 # ^ \ # / v # 3-->2 6<---7 # \ # v # 0-->1 if ($digit == 0) { $i = 0; $j = 0; $new_rev = 0; } elsif ($digit == 1) { $i = 1; $j = 0; $new_rev = 1; $new_rot += 2; } elsif ($digit == 2) { $i = 0; $j = 1; $new_rev = 1; $new_rot += 3; } elsif ($digit == 3) { $i = -1; $j = 1; $new_rev = 0; $new_rot += 1; } elsif ($digit == 4) { $i = -1; $j = 2; $new_rev = 0; } elsif ($digit == 5) { $i = 0; $j = 2; $new_rev = 0; $new_rot -= 1; } elsif ($digit == 6) { $i = 1; $j = 1; $new_rev = 1; } } foreach (1 .. $rot) { ($i,$j) = (-$j, $i+$j); # rotate +60 } $new_rot %= 6; my $next_state = make_state (rot => $new_rot, rev => $new_rev, digit => 0); $next_state[$state] = $next_state; $digit_to_i[$state] = $i; $digit_to_j[$state] = $j; } my $state = make_state (rot => $rot, rev => $rev, digit => 0); my $di = 1; my $dj = 0; foreach (1 .. $rot) { ($di,$dj) = (-$dj, $di+$dj); # rotate +60 } $state_to_di[$state/7] = $di; $state_to_dj[$state/7] = $dj; } } ### @next_state ### @digit_to_dxdy ### next_state length: 4*(4*2*2 + 4*2) print "# next_state length ", scalar(@next_state), "\n"; print_table14 ("next_state", \@next_state); print_table14 ("digit_to_i", \@digit_to_i); print_table14 ("digit_to_j", \@digit_to_j); print_table12 ("state_to_di", \@state_to_di); print_table12 ("state_to_dj", \@state_to_dj); print "\n"; exit 0; Math-PlanePath-129/tools/flowsnake-table.pl0000644000175000017500000001712412704276401016471 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2016 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl flowsnake-table.pl # # Print the state tables used for Math:PlanePath::Flowsnake n_to_xy(). use 5.010; use strict; use List::Util 'max'; # uncomment this to run the ### lines #use Smart::Comments; sub print_table14 { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length($_//'')} @$aref); foreach my $i (0 .. $#$aref) { my $entry_str = $aref->[$i]//'undef'; if ($i == $#$aref) { $entry_str .= ");"; } else { $entry_str .= ","; } if ($i % 14 == 0 && $#$aref > 14) { printf "%-*s", $entry_width+1, $entry_str; } else { printf "%*s", $entry_width+1, $entry_str; } if ($i % 14 == 13) { print " # ",$i-13,",",$i-6,"\n"; if ($i != $#$aref) { print " ".(" " x length($name)); } } elsif ($i % 7 == 6) { print " "; } } } sub print_table12 { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length($_//'')} @$aref); foreach my $i (0 .. $#$aref) { my $entry_str = $aref->[$i]//'undef'; if ($i == $#$aref) { $entry_str .= ");"; } else { $entry_str .= ","; } if ($i % 12 == 0 && $#$aref > 12) { printf "%-*s", $entry_width+1, $entry_str; } else { printf "%*s", $entry_width+1, $entry_str; } if ($i % 12 == 11) { print "\n"; if ($i != $#$aref) { print " ".(" " x length($name)); } } elsif ($i % 6 == 5) { print " "; } } } my @next_state; my @digit_to_i; my @digit_to_j; my @state_to_di; my @state_to_dj; sub make_state { my %param = @_; my $state = 0; $state *= 6; $state += delete $param{'rot'}; # high $state *= 2; $state += delete $param{'rev'}; $state *= 7; $state += delete $param{'digit'}; # low if (%param) { die; } return $state; } sub state_string { my ($state) = @_; my $digit = $state % 7; $state = int($state/7); # low my $rev = $state % 2; $state = int($state/2); my $rot = $state % 6; $state = int($state/6); # high return "rot=$rot rev=$rev (digit=$digit)"; } foreach my $rev (0, 1) { foreach my $rot (0 .. 5) { foreach my $digit (0 .. 6) { my $state = make_state (rot => $rot, rev => $rev, digit => $digit); my $new_rev = $rev; my $new_rot = $rot; my ($i, $j); if ($rev == 0) { # 4-->5-->6 # ^ ^ # \ \ # 3-->2 7 # / # v # 0-->1 if ($digit == 0) { $i = 0; $j = 0; $new_rev = 0; } elsif ($digit == 1) { $i = 1; $j = 0; $new_rev = 1; $new_rot++; } elsif ($digit == 2) { $i = 1; $j = 1; $new_rev = 1; $new_rot += 3; } elsif ($digit == 3) { $i = 0; $j = 1; $new_rev = 0; $new_rot += 2; } elsif ($digit == 4) { $i = -1; $j = 2; $new_rev = 0; } elsif ($digit == 5) { $i = 0; $j = 2; $new_rev = 0; } elsif ($digit == 6) { $i = 1; $j = 2; $new_rev = 1; $new_rot += 5; } } else { # 6<---7 # ^ # / # 0 5<--4 # \ \ # v v # 1<--2<--3 if ($digit == 0) { $i = 0; $j = 0; $new_rev = 0; $new_rot -= 1; } elsif ($digit == 1) { $i = 1; $j = -1; $new_rev = 1; } elsif ($digit == 2) { $i = 2; $j = -1; $new_rev = 1; } elsif ($digit == 3) { $i = 3; $j = -1; $new_rev = 1; $new_rot += 2; } elsif ($digit == 4) { $i = 2; $j = 0; $new_rev = 0; $new_rot += 3; } elsif ($digit == 5) { $i = 1; $j = 0; $new_rev = 0; $new_rot += 1; } elsif ($digit == 6) { $i = 1; $j = 1; $new_rev = 1; } } foreach (1 .. $rot) { ($i,$j) = (-$j, $i+$j); # rotate +60 } $new_rot %= 6; my $next_state = make_state (rot => $new_rot, rev => $new_rev, digit => 0); $next_state[$state] = $next_state; $digit_to_i[$state] = $i; $digit_to_j[$state] = $j; } my $state = make_state (rot => $rot, rev => $rev, digit => 0); my $di = 1; my $dj = 0; foreach (1 .. $rot) { ($di,$dj) = (-$dj, $di+$dj); # rotate +60 } $state_to_di[$state/7] = $di; $state_to_dj[$state/7] = $dj; } } my @digit_to_next_di; my @digit_to_next_dj; my $end_i = 2; my $end_j = 1; my $state = 0; foreach my $rot (0 .. 5) { foreach my $rev (0, 1) { foreach my $digit (0 .. 5) { my $di; if ($digit < 5) { $di = $digit_to_i[$state + $digit + 2] } else { $di = $end_i; } $di -= $digit_to_i[$state + $digit + 1]; $digit_to_next_di[$state + $digit] = $di; my $dj; if ($digit < 5) { $dj = $digit_to_j[$state + $digit + 2]; } else { $dj = $end_j; } $dj -= $digit_to_j[$state + $digit + 1]; $digit_to_next_dj[$state + $digit] = $dj; if ($di == 0 && $dj == 0) { die "no delta at state=$state digit=$digit"; } if ($rev) { if ($digit == 0) { ($di,$dj) = ($di+$dj, -$di); # rotate -60 } elsif ($digit == 1) { ($di,$dj) = ($di+$dj, -$di); # rotate -60 } elsif ($digit == 2) { ($di,$dj) = ($di+$dj, -$di); # rotate -60 } elsif ($digit == 5) { ($di,$dj) = ($di+$dj, -$di); # rotate -60 } } else { if ($digit == 0) { ($di,$dj) = ($di+$dj, -$di); # rotate -60 } elsif ($digit == 1) { ($di,$dj) = ($di+$dj, -$di); # rotate -60 } elsif ($digit == 5) { ($di,$dj) = ($di+$dj, -$di); # rotate -60 } } $digit_to_next_di[$state + $digit + 84] = $di; $digit_to_next_dj[$state + $digit + 84] = $dj; } $state += 7; } ($end_i,$end_j) = (-$end_j, $end_i+$end_j); # rotate +60 } ### @next_state ### @digit_to_dxdy ### next_state length: 4*(4*2*2 + 4*2) print "# next_state length ", scalar(@next_state), "\n"; print_table14 ("next_state", \@next_state); print_table14 ("digit_to_i", \@digit_to_i); print_table14 ("digit_to_j", \@digit_to_j); print_table12 ("state_to_di", \@state_to_di); print_table12 ("state_to_dj", \@state_to_dj); print "\n"; print_table14 ("digit_to_next_di", \@digit_to_next_di); print "\n"; print_table14 ("digit_to_next_dj", \@digit_to_next_dj); print "\n"; exit 0; Math-PlanePath-129/tools/moore-spiral-table.pl0000644000175000017500000000663711713712763017125 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; # uncomment this to run the ### lines #use Smart::Comments; sub make_state { my ($rev, $rot) = @_; $rev %= 2; $rot %= 4; return 10*($rot + 4*$rev); } sub state_string { my ($state) = @_; my $digit = $state % 10; $state = int($state/10); my $rot = $state % 4; $state = int($state/4); my $rev = $state % 2; $state = int($state/2); return "rot=$rot rev=$rev" . ($digit ? " digit=$digit" : ""); } my @min_digit; my @max_digit; my @next_state; my @digit_to_x; my @digit_to_y; my @xy_to_digit; my @unrot_digit_to_x = (0,1,1, 0,-1,-2, -2,-2,-3, -3); my @unrot_digit_to_y = (0,0,1, 1, 1, 1, 0,-1,-1, 0); my @segment_to_rev = (0,0,0, 1,0,0, 1,1,1, 0); my @segment_to_dir = (0,1,2, 2,2,3, 3,2,1, 0); foreach my $rot (0, 1, 2, 3) { foreach my $rev (0, 1) { my $state = make_state ($rev, $rot); foreach my $digit (0 .. 9) { my $xo = $unrot_digit_to_x[$rev ? 9-$digit : $digit]; my $yo = $unrot_digit_to_y[$rev ? 9-$digit : $digit]; if ($rev) { $xo += 3 } my $new_rev = $rev ^ $segment_to_rev[$rev ? 8-$digit : $digit]; my $new_rot = $rot + $segment_to_dir[$rev ? 8-$digit : $digit]; if ($new_rev) { $new_rot += 0; } else { $new_rot += 2; } if ($rev) { $new_rot += 2; } else { $new_rot += 0; } if ($rot & 2) { $xo = - $xo; $yo = - $yo; } if ($rot & 1) { ($xo,$yo) = (-$yo,$xo); } ### rot to: "$xo, $yo" $digit_to_x[$state+$digit] = $xo; $digit_to_y[$state+$digit] = $yo; # $xy_to_digit[$state + 3*$xo + $yo] = $orig_digit; my $next_state = make_state ($new_rev, $new_rot); if ($digit == 9) { $next_state = undef; } $next_state[$state+$digit] = $next_state; } } } use List::Util 'min','max'; sub print_table { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {defined && length} @$aref); foreach my $i (0 .. $#$aref) { printf "%*s", $entry_width, $aref->[$i]//'undef'; if ($i == $#$aref) { print "); # ",$i-9,"\n"; } else { print ","; if (($i % 10) == 9) { print " # ".($i-9); } if (($i % 10) == 9) { print "\n ".(" " x length($name)); } elsif (($i % 3) == 2) { print " "; } } } } print_table ("next_state", \@next_state); print_table ("digit_to_x", \@digit_to_x); print_table ("digit_to_y", \@digit_to_y); # print_table ("xy_to_digit", \@xy_to_digit); # print_table36 ("min_digit", \@min_digit); # print_table36 ("max_digit", \@max_digit); print "# state length ",scalar(@next_state)," in each of 4 tables\n"; print "# rot2 state ",make_state(0,2),"\n"; exit 0; Math-PlanePath-129/tools/cellular-rule-limits.pl0000644000175000017500000010124712311703413017452 0ustar gggg#!/usr/bin/perl -w # Copyright 2013, 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use List::Util 'min', 'max'; use Math::PlanePath::CellularRule; # uncomment this to run the ### lines # use Smart::Comments; my %h; use Tie::IxHash; tie %h, 'Tie::IxHash'; foreach my $rule (# 141, 0 .. 255, ) { print "$rule\n"; my $path = Math::PlanePath::CellularRule->new(rule=>$rule); unless (ref $path eq 'Math::PlanePath::CellularRule') { ### skip subclass: ref $path next; } my @x; my @y; my @sumxy; my @diffxy; my $x_negative_at_n; my @dx; my @dy; my @dsumxy; my @ddiffxy; my $n_start = $path->n_start; foreach my $n ($n_start .. 200) { my ($x,$y) = $path->n_to_xy($n) or last; ### at: "n=$n xy=$x,$y" push @x, $x; push @y, $y; push @sumxy, $x+$y; push @diffxy, $x-$y; if ($x < 0 && ! defined $x_negative_at_n) { $x_negative_at_n = $n - $n_start; ### $x_negative_at_n } if (my ($dx,$dy) = $path->n_to_dxdy($n)) { push @dx, $dx; push @dy, $dy; push @dsumxy, $dx+$dy; push @ddiffxy, $dx-$dy; } } $h{'x_minimum'}->[$rule] = min(@x); $h{'x_maximum'}->[$rule] = max(@x); $h{'y_maximum'}->[$rule] = max(@y); ### $x_negative_at_n $h{'x_negative_at_n'}->[$rule] = $x_negative_at_n; $h{'dx_minimum'}->[$rule] = min(@dx); $h{'dx_maximum'}->[$rule] = max(@dx); $h{'dy_minimum'}->[$rule] = min(@dy); $h{'dy_maximum'}->[$rule] = max(@dy); $h{'absdx_minimum'}->[$rule] = min(map{abs}@dx); $h{'absdx_maximum'}->[$rule] = max(map{abs}@dx); $h{'absdy_minimum'}->[$rule] = min(map{abs}@dy); $h{'sumxy_minimum'}->[$rule] = min(@sumxy); $h{'sumxy_maximum'}->[$rule] = max(@sumxy); $h{'diffxy_minimum'}->[$rule] = min(@diffxy); $h{'diffxy_maximum'}->[$rule] = max(@diffxy); $h{'dsumxy_minimum'}->[$rule] = min(@dsumxy); $h{'dsumxy_maximum'}->[$rule] = max(@dsumxy); $h{'ddiffxy_minimum'}->[$rule] = min(@ddiffxy); $h{'ddiffxy_maximum'}->[$rule] = max(@ddiffxy); } foreach my $name (keys %h, # 'x_negative_at_n', ) { print " my \@${name} = (\n"; my $aref = $h{$name}; while (@$aref && ! defined $aref->[-1]) { pop @$aref; } my $row_rule; foreach my $rule (0 .. $#$aref) { if ($rule % 8 == 0) { print " "; $row_rule = $rule; } my $value = $aref->[$rule]; if (defined $value && $name ne 'x_negative_at_n' && ($value < -5 || $value > 5)) { $value = undef; } if (! defined $value) { $value = 'undef'; } printf " %5s,", $value; if ($rule % 8 == 7 || $rule == $#$aref) { print " # rule=$row_rule\n"; } } } exit 0; __END__ my @dx_minimum = ( undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -2, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -2, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -2, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -2, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, undef, undef, undef, undef, my @dy_maximum = ( undef, 2, undef, 1, undef, 1, undef, 2, undef, 2, undef, 1, undef, 1, 1, 1, undef, 1, undef, 2, undef, 2, 1, 2, undef, 1, undef, 1, 1, 1, 1, 2, undef, 2, undef, 1, undef, 1, undef, 1, undef, 2, undef, 1, undef, 1, 1, 1, undef, 1, undef, 1, undef, 1, undef, 2, undef, undef, undef, 1, undef, 1, 1, 2, undef, 2, undef, 1, undef, 1, 1, 1, undef, 2, undef, 1, undef, 1, 1, 1, undef, 1, undef, 1, 1, 1, 1, 2, undef, 1, undef, 1, 1, 1, 1, 2, undef, 2, undef, undef, undef, 1, undef, 1, undef, 2, undef, 1, undef, 1, 1, 1, undef, 1, undef, 1, 1, 1, 1, 2, undef, 1, undef, 1, 1, 1, 1, 2, undef, 2, undef, 1, undef, 1, undef, 1, undef, 2, undef, 1, undef, 1, 1, 1, undef, 1, undef, 1, undef, 1, 1, undef, undef, 1, undef, 1, 1, 1, 1, undef, undef, 2, undef, 1, undef, 1, undef, 1, undef, 2, undef, 1, undef, 1, 1, 1, undef, 1, undef, undef, undef, 1, 1, undef, undef, 1, undef, 1, 1, 1, undef, undef, undef, 2, undef, 1, undef, 1, 1, 1, undef, 2, undef, 1, undef, 1, undef, 1, undef, 1, undef, 1, 1, 1, 1, undef, undef, 1, undef, 1, undef, 1, undef, undef, undef, 2, undef, 1, undef, 1, 1, 1, undef, 2, undef, 1, undef, 1, undef, 1, undef, 1, undef, 1, 1, 1, undef, undef, undef, 1, undef, 1, undef, 1, my @absdy_minimum = ( undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, 0, 0, 0, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, undef, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, 0, 0, 0, 0, undef, 0, undef, 0, 0, 0, 0, 0, undef, 0, undef, undef, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, 0, 0, 0, 0, undef, 0, undef, 0, 0, 0, 0, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, undef, 0, 0, undef, undef, 0, undef, 0, 0, 0, 0, undef, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, undef, undef, 0, 0, undef, undef, 0, undef, 0, 0, 0, undef, undef, undef, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, 0, 0, 0, undef, undef, 0, undef, 0, undef, 0, undef, undef, undef, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, 0, 0, undef, undef, undef, 0, undef, 0, undef, 0, my @sum_maximum = ( 0, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, 1, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, 1, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, 1, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, 1, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, undef, undef, 0, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, my @diff_maximum = ( 0, 0, undef, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, 0, 0, 0, 0, 0, 0, undef, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, undef, undef, 0, undef, 0, 0, 0, 0, 0, undef, 0, undef, 0, 0, 0, 0, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, 0, 0, 0, 0, undef, 0, undef, 0, 0, 0, 0, 0, 0, 0, undef, undef, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, 0, 0, 0, 0, undef, 0, undef, 0, 0, 0, 0, 0, 0, 0, undef, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, undef, 0, 0, undef, undef, 0, undef, 0, 0, 0, 0, undef, 0, 0, undef, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, 0, 0, 0, undef, 0, undef, undef, undef, 0, 0, undef, undef, 0, undef, 0, 0, 0, undef, undef, 0, 0, undef, 0, undef, 0, 0, 0, 0, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, 0, 0, 0, undef, undef, 0, undef, 0, undef, 0, undef, undef, 0, 0, undef, 0, undef, 0, 0, 0, 0, 0, undef, 0, undef, 0, undef, 0, undef, 0, undef, 0, 0, 0, undef, undef, undef, 0, undef, 0, undef, 0, my @dsum_minimum = ( undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -1, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -1, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, 1, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, 1, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -1, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -1, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, 1, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, 1, undef, undef, undef, undef, undef, undef, undef, undef, undef, my @ddiffxy_minimum = ( undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -3, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -3, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -1, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -1, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -3, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -3, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -1, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, undef, -1, undef, undef, undef, undef, undef, undef, undef, undef, undef, Math-PlanePath-129/tools/terdragon-midpoint-offset.pl0000644000175000017500000000312713102216445020476 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2017 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use Math::PlanePath::TerdragonMidpoint; my $path = Math::PlanePath::TerdragonMidpoint->new (arms => 1); my @yx_to_dxdy; foreach my $n (0 .. 3**10) { my ($x,$y) = $path->n_to_xy($n); my $to_n = $n; if (($n % 3) == 0) { $to_n = $n + 1; } elsif (($n % 3) == 2) { $to_n = $n - 1; } my ($to_x,$to_y) = $path->n_to_xy($to_n); my $dx = $to_x - $x; my $dy = $to_y - $y; my $k = 2*(12*($y%12) + ($x%12)); $yx_to_dxdy[$k+0] = $dx; $yx_to_dxdy[$k+1] = $dy; } print_72(\@yx_to_dxdy); sub print_72 { my ($aref) = @_; print "("; for (my $i = 0; $i < @$aref; ) { my $v1 = $aref->[$i++] // 'undef'; my $v2 = $aref->[$i++] // 'undef'; my $str = "$v1,$v2"; if ($i != $#$aref) { $str .= ", " } my $width = (($i % 4) == 2 ? 6 : 6); printf "%-*s", $width, $str; if (($i % 12) == 0) { print "\n " } } print ");\n"; } exit 0; Math-PlanePath-129/tools/man-page-listing.pl0000644000175000017500000001353513736545562016566 0ustar gggg#!/usr/bin/perl -w # Copyright 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Generate HTML list of the online man-pages. use strict; use warnings; use sort 'stable'; use FindBin; use App::Upfiles; use File::Copy; use File::Slurp (); use File::Temp; use File::chdir; use File::stat (); use Module::Util; use HTML::Entities::Interpolate; use POSIX (); use Regexp::Tr; use Sort::Key::Natural; # uncomment this to run the ### lines # use Smart::Comments; my $webdir = "$ENV{HOME}/tux/web/math-planepath"; my $canonical_top = "https://user42.tuxfamily.org/math-planepath/"; my $out_filename = 'manpages.html'; my $author = "Kevin Ryde"; my $generator = $FindBin::Script; chdir $webdir or die; my $year = POSIX::strftime('%Y', localtime(time())); # $n is a file size in bytes. # Return a string which is a human-readable form, like "50 kbytes". sub bytes_to_human { my ($n) = @_; if ($n < 1_000) { return "$n bytes"; } if ($n < 10_000) { return sprintf "%.1f kbytes", $n/1000; } if ($n < 1_000_000) { return sprintf "%.0f kbytes", $n/1000; } if ($n < 10_000_000) { return sprintf "%.1f mbytes", $n/1_000_000; } return sprintf "%.0f mbytes", $n/1_000_000; } # $n is an integer like 12500. # Insert commas between thousands like "12,500". sub number_commas { my ($n) = @_; while ($n =~ s/(\d)(\d{3})(,|$)/$1,$2/) {}; return $n; } sub filename_to_module { my ($str) = @_; $str =~ s/\.html$//; $str =~ s/-/::/g; return $str; } CHECK { filename_to_module('Math-PlanePath.html') eq 'Math::PlanePath' or die; } my @libdirs = (File::Spec->catdir($FindBin::Bin, File::Spec->updir, 'lib'), File::Spec->catdir($FindBin::Bin, File::Spec->updir, File::Spec->updir, 'pt', 'lib')); sub module_to_description { my ($module) = @_; my $filename = Module::Util::find_installed($module, @libdirs) // die "$module not found under @libdirs"; my $str = File::Slurp::read_file($filename); $str =~ /=head1 NAME.*?-- (.*?)\n/s or die "$filename NAME not matched"; return $1; } sub filename_sort_key { my ($str) = @_; $str = filename_to_module($str); $str =~ s/NumSeq/ZNumSeq/; # sort last $str =~ s/PlanePath::Base/PlanePath::ZZZBase/; # sort last return Sort::Key::Natural::mkkey_natural($str); } my @filenames = File::Slurp::read_dir('.'); @filenames = grep {! -d} @filenames; @filenames = grep {/^[A-Z].*\.html$/} @filenames; @filenames =sort {filename_sort_key($a) cmp filename_sort_key($b)} @filenames; ### @filenames ### num filenames: scalar(@filenames) my $favicon = ''; if (-e "favicon.png") { $favicon = "\n"; } my $out = File::Temp->new; my $dateModified = POSIX::strftime('%Y-%m-%d', gmtime(time())); print $out <<"HERE"; PlanePath Man Pages $favicon

Math-PlanePath Man Pages (including Math-PlanePath-Toothpick)

(back to Math-PlanePath home page) HERE my $count = 0; my $total_bytes = 0; my $join = "\n

"; my $prev_type = ''; foreach my $filename (@filenames) { $filename =~ /(Math-.*?-(Base)?)?/; my $type = $&; if ($type ne $prev_type) { print $out "
\n"; $prev_type = $type; } my $bytes = -s $filename; $total_bytes += $bytes; my $st = File::stat::stat($filename); my $module = filename_to_module($filename); my $description = module_to_description($module); $module =~ s/^Math::PlanePath:://; # shorten for display print $out $join, " $Entitize{$module}\n", " -- $Entitize{$description}\n"; $join = '
'; $count++; # my $size = bytes_to_human(-s $filename); # (about $Entitize{$size}) } # my $size_str = bytes_to_human($total_bytes); # , about $size_str print $out <<"HERE";

Total $count modules.


This page Copyright $year $Entitize{$author}.

HERE close $out or die; my $old_content = File::Slurp::read_file($out_filename, {err_mode=>'quiet'}) // ''; my $new_content = File::Slurp::read_file($out->filename); foreach ($old_content, $new_content) { # compare sans mod dates s/^filename, $out_filename); } my $bytes = -s $out_filename // '[undef]'; print "$diff $out_filename $bytes bytes\n"; system 'weblint',$out_filename; exit 0; Math-PlanePath-129/tools/corner-replicate-table.pl0000644000175000017500000000705211660104640017730 0ustar gggg#!/usr/bin/perl -w # Copyright 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'max'; # uncomment this to run the ### lines #use Smart::Comments; # There's no states for CornerReplicate, just two tables of 9 values for # min/max digits. sub print_table { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length} @$aref); foreach my $i (0 .. $#$aref) { printf "%d", $aref->[$i]; if ($i == $#$aref) { print ");\n"; } else { print ","; if (($i % 16) == 15) { print "\n ".(" " x length($name)); } elsif (($i % 4) == 3) { print " "; } } } } sub print_table9 { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length($_//'')} @$aref); foreach my $i (0 .. $#$aref) { printf "%*s", $entry_width, $aref->[$i]//'undef'; if ($i == $#$aref) { print ");\n"; } else { print ","; if (($i % 9) == 8) { print "\n ".(" " x length($name)); } elsif (($i % 3) == 2) { print " "; } } } } my @min_digit; my @max_digit; # range 0 [X,_] # range 1 [X,X] # range 2 [_,X] foreach my $xrange (0,1,2) { foreach my $yrange (0,1,2) { my $xr = $xrange; my $yr = $yrange; my $key = $xr + 3*$yr; # before rot+transpose my ($min_digit, $max_digit); # 3--2 # | # 0--1 if ($xr == 0) { # 0 or 3 if ($yr == 0) { # x,y both low, 0 only $min_digit = 0; $max_digit = 0; } elsif ($yr == 1) { # y either, 0 or 3 $min_digit = 0; $max_digit = 3; } elsif ($yr == 2) { # y high, 3 only $min_digit = 3; $max_digit = 3; } } elsif ($xr == 1) { # x either, any 0,1,2,3 if ($yr == 0) { # y low, 0 or 1 $min_digit = 0; $max_digit = 1; } elsif ($yr == 1) { # y either, 0,1,2,3 $min_digit = 0; $max_digit = 3; } elsif ($yr == 2) { # y high, 2,3 only $min_digit = 2; $max_digit = 3; } } else { # x high, 1 or 2 if ($yr == 0) { # y low, 1 only $min_digit = 1; $max_digit = 1; } elsif ($yr == 1) { # y either, 1 or 2 $min_digit = 1; $max_digit = 2; } elsif ($yr == 2) { # y high, 2 only $min_digit = 2; $max_digit = 2; } } if (defined $min_digit[$key]) { die "oops min_digit[] already: key=$key value=$min_digit[$key], new=$min_digit"; } $min_digit[$key] = $min_digit; $max_digit[$key] = $max_digit; } } ### @min_digit print_table9 ("min_digit", \@min_digit); print_table9 ("max_digit", \@max_digit); print "\n"; exit 0; Math-PlanePath-129/tools/wythoff-array-zeck.pl0000644000175000017500000000374612113742706017154 0ustar gggg#!/usr/bin/perl -w # Copyright 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl wythoff-array-zeck.pl # # Print some of the Wythoff array with N values in Zeckendorf base. # use 5.010; use strict; use List::Util 'max'; use Math::NumSeq::Fibbinary; use Math::PlanePath::WythoffArray; my $class = 'Math::PlanePath::WythoffArray'; # $class = 'Math::PlanePath::WythoffDifference'; # $class = 'Math::PlanePath::WythoffPreliminaryTriangle'; my $width = 4; my $height = 9; eval "require $class"; my $path = $class->new; my $fib = Math::NumSeq::Fibbinary->new; my @z; my @colwidth; foreach my $x (0 .. $width) { foreach my $y (0 .. $height) { my $n = $path->xy_to_n ($x,$y); my $z = $n && $fib->ith($n); my $zb = $z && sprintf '%b', $z; # $zb = $n && sprintf '%d', $n; if (! defined $n) { $zb = ''; } $z[$x][$y] = $zb; $colwidth[$x] = max($colwidth[$x]||0, length($z[$x][$y])); } } my $ywidth = length($height); foreach my $y (reverse 0 .. $height) { printf "%*d |", $ywidth, $y; foreach my $x (0 .. $width) { my $value = $z[$x][$y] // ''; printf " %*s", $colwidth[$x], $z[$x][$y]; } print "\n"; } printf "%*s +-", $ywidth, ''; foreach my $x (0 .. $width) { print '-' x ($colwidth[$x]+1); } print "\n"; printf "%*s ", $ywidth, ''; foreach my $x (0 .. $width) { printf " %*s", $colwidth[$x], $x; } print "\n"; exit 0; Math-PlanePath-129/tools/dekking-centres-table.pl0000644000175000017500000001374512020130531017542 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'max'; # uncomment this to run the ### lines #use Smart::Comments; sub print_table { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length} @$aref); foreach my $i (0 .. $#$aref) { printf "%*d", $entry_width, $aref->[$i]; if ($i == $#$aref) { print ");\n"; } else { print ","; if ($entry_width >= 2 && ($i % 25) == 4) { print " # ".($i-4); } if (($i % 25) == 24 || $entry_width >= 2 && ($i % 5) == 4) { print "\n ".(" " x length($name)); } elsif (($i % 5) == 4) { print " "; } } } } sub make_state { my ($rev, $rot) = @_; $rev %= 2; $rot %= 4; return 25*($rot + 4*$rev); } my @next_state; my @digit_to_x; my @digit_to_y; my @yx_to_digit; foreach my $rev (0, 1) { foreach my $rot (0, 1, 2, 3) { foreach my $orig_digit (0 .. 24) { my $digit = $orig_digit; if ($rev) { $digit = 24-$digit; } my $xo; my $yo; my $new_rot = $rot; my $new_rev = $rev; if ($digit == 0) { $xo = 0; $yo = 0; } elsif ($digit == 1) { $xo = 1; $yo = 0; } elsif ($digit == 2) { $xo = 2; $yo = 0; $new_rot = $rot - 1; $new_rev ^= 1; } elsif ($digit == 3) { $xo = 1; $yo = 1; $new_rev ^= 1; } elsif ($digit == 4) { $xo = 0; $yo = 1; $new_rot = $rot + 1; } elsif ($digit == 5) { $xo = 1; $yo = 2; } elsif ($digit == 6) { $xo = 2; $yo = 2; $new_rot = $rot - 1; $new_rev ^= 1; } elsif ($digit == 7) { $xo = 1; $yo = 3; $new_rev ^= 1; } elsif ($digit == 8) { $xo = 0; $yo = 2; $new_rot = $rot + 2; } elsif ($digit == 9) { $xo = 0; $yo = 3; $new_rot = $rot - 1; $new_rev ^= 1; } elsif ($digit == 10) { $xo = 0; $yo = 4; } elsif ($digit == 11) { $xo = 1; $yo = 4; } elsif ($digit == 12) { $xo = 2; $yo = 3; $new_rot = $rot + 2; $new_rev ^= 1; } elsif ($digit == 13) { $xo = 2; $yo = 4; $new_rot = $rot + 1; } elsif ($digit == 14) { $xo = 3; $yo = 4; $new_rot = $rot + 2; $new_rev ^= 1; } elsif ($digit == 15) { $xo = 4; $yo = 4; $new_rot = $rot - 1; } elsif ($digit == 16) { $xo = 4; $yo = 3; $new_rot = $rot - 1; } elsif ($digit == 17) { $xo = 3; $yo = 3; $new_rev ^= 1; } elsif ($digit == 18) { $xo = 3; $yo = 2; $new_rot = $rot - 1; } elsif ($digit == 19) { $xo = 2; $yo = 1; $new_rot = $rot + 1; $new_rev ^= 1; } elsif ($digit == 20) { $xo = 3; $yo = 0; $new_rot = $rot + 2; $new_rev ^= 1; } elsif ($digit == 21) { $xo = 3; $yo = 1; $new_rot = $rot + 1; } elsif ($digit == 22) { $xo = 4; $yo = 2; } elsif ($digit == 23) { $xo = 4; $yo = 1; $new_rot = $rot + 1; $new_rev ^= 1; } elsif ($digit == 24) { $xo = 4; $yo = 0; $new_rot = $rot + 1; $new_rev ^= 1; } else { die; } ### base: "$xo, $yo" if ($rot & 2) { $xo = 4 - $xo; $yo = 4 - $yo; } if ($rot & 1) { ($xo,$yo) = (4-$yo,$xo); } ### rot to: "$xo, $yo" my $state = make_state ($rev, $rot); $digit_to_x[$state+$orig_digit] = $xo; $digit_to_y[$state+$orig_digit] = $yo; $yx_to_digit[$state + $yo*5+$xo] = $orig_digit; my $next_state = make_state ($new_rev, $new_rot); $next_state[$state+$orig_digit] = $next_state; } } } print "# state length ",scalar(@next_state)," in each of 4 tables\n"; print_table ("next_state", \@next_state); print_table ("digit_to_x", \@digit_to_x); print_table ("digit_to_y", \@digit_to_y); print_table ("yx_to_digit", \@yx_to_digit); ### @next_state ### @digit_to_x ### @digit_to_y ### @yx_to_digit ### next_state length: scalar(@next_state) { my @pending_state = (0); my $count = 0; my @seen_state; my $depth = 1; $seen_state[0] = $depth; while (@pending_state) { my $state = pop @pending_state; $count++; ### consider state: $state foreach my $digit (0 .. 24) { my $next_state = $next_state[$state+$digit]; if (! $seen_state[$next_state]) { $seen_state[$next_state] = $depth; push @pending_state, $next_state; ### push: "$next_state depth $depth" } } $depth++; } for (my $state = 0; $state < @next_state; $state += 25) { print "# used state $state depth $seen_state[$state]\n"; } print "used state count $count\n"; } print "\n"; exit 0; Math-PlanePath-129/tools/cinco-curve-table.pl0000644000175000017500000002023211665051545016714 0ustar gggg#!/usr/bin/perl -w # Copyright 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use List::Util 'min','max'; # uncomment this to run the ### lines #use Smart::Comments; sub min_maybe { return min(grep {defined} @_); } sub max_maybe { return max(grep {defined} @_); } sub print_table { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length($_//'undef')} @$aref); foreach my $i (0 .. $#$aref) { printf "%*s", $entry_width, $aref->[$i]//'undef'; if ($i == $#$aref) { print ");\n"; } else { print ","; if ($entry_width >= 2 && ($i % 25) == 4) { print " # ".($i-4); } if (($i % 25) == 24 || $entry_width >= 2 && ($i % 5) == 4) { print "\n ".(" " x length($name)); } elsif (($i % 5) == 4) { print " "; } } } } sub make_state { my ($transpose, $rot) = @_; $transpose %= 2; $rot %= 4; unless ($rot == 0 || $rot == 2) { die "bad rotation $rot"; } return 25*($rot/2 + 2*$transpose); } my @next_state; my @digit_to_x; my @digit_to_y; my @yx_to_digit; my @min_digit; my @max_digit; foreach my $transpose (0, 1) { foreach my $rot (0, 2) { my $state = make_state ($transpose, $rot); ### $state foreach my $orig_digit (0 .. 24) { my $digit = $orig_digit; # if ($rev) { # $digit = 24-$digit; # } my $xo; my $yo; my $new_rot = $rot; my $new_transpose = $transpose; my $inc_rot = 0; if ($digit == 0) { $xo = 0; $yo = 0; } elsif ($digit == 1) { $xo = 1; $yo = 0; } elsif ($digit == 2) { $xo = 2; $yo = 0; $new_transpose ^= 1; } elsif ($digit == 3) { $xo = 2; $yo = 1; $new_transpose ^= 1; } elsif ($digit == 4) { $xo = 2; $yo = 2; $new_transpose ^= 1; } elsif ($digit == 5) { $xo = 1; $yo = 2; $inc_rot = 2; $new_transpose ^= 1; } elsif ($digit == 6) { $xo = 1; $yo = 1; $inc_rot = 2; } elsif ($digit == 7) { $xo = 0; $yo = 1; $inc_rot = 2; } elsif ($digit == 8) { $xo = 0; $yo = 2; $new_transpose ^= 1; } elsif ($digit == 9) { $xo = 0; $yo = 3; $new_transpose ^= 1; } elsif ($digit == 10) { $xo = 0; $yo = 4; } elsif ($digit == 11) { $xo = 1; $yo = 4; } elsif ($digit == 12) { $xo = 1; $yo = 3; $inc_rot = 2; $new_transpose ^= 1; } elsif ($digit == 13) { $xo = 2; $yo = 3; $new_transpose ^= 1; } elsif ($digit == 14) { $xo = 2; $yo = 4; } elsif ($digit == 15) { $xo = 3; $yo = 4; } elsif ($digit == 16) { $xo = 4; $yo = 4; } elsif ($digit == 17) { $xo = 4; $yo = 3; $inc_rot = 2; } elsif ($digit == 18) { $xo = 3; $yo = 3; $inc_rot = 2; $new_transpose ^= 1; } elsif ($digit == 19) { $xo = 3; $yo = 2; $inc_rot = 2; $new_transpose ^= 1; } elsif ($digit == 20) { $xo = 4; $yo = 2; } elsif ($digit == 21) { $xo = 4; $yo = 1; $inc_rot = 2; } elsif ($digit == 22) { $xo = 3; $yo = 1; $inc_rot = 2; $new_transpose ^= 1; } elsif ($digit == 23) { $xo = 3; $yo = 0; $inc_rot = 2; $new_transpose ^= 1; } elsif ($digit == 24) { $xo = 4; $yo = 0; } else { die; } ### base: "$xo, $yo" if ($transpose) { ($xo,$yo) = ($yo,$xo); $inc_rot = - $inc_rot; } $new_rot = $rot + $inc_rot; if ($rot & 2) { $xo = 4 - $xo; $yo = 4 - $yo; } if ($rot & 1) { ($xo,$yo) = (4-$yo,$xo); } ### rot to: "$xo, $yo" $digit_to_x[$state+$orig_digit] = $xo; $digit_to_y[$state+$orig_digit] = $yo; $yx_to_digit[$state + $yo*5+$xo] = $orig_digit; my $next_state = make_state ($new_transpose, $new_rot); $next_state[$state+$orig_digit] = $next_state; } # N = (- 1/2 d^2 + 9/2 d) # = (- 1/2*$d**2 + 9/2*$d) # = ((9 - d)d/2 # (9-d)*d/2 # d=0 (9-0)*0/2 = 0 # d=1 (9-1)*1/2 - 1 = 8/2-1 = 3 # d=2 (9-2)*2/2 - 2 = 7-1 = 6 # d=4 (9-4)*4/2 = 5*4/2 = 10 # foreach my $x1pos (0 .. 4) { foreach my $x2pos ($x1pos .. 4) { my $xkey = (9-$x1pos)*$x1pos/2 + $x2pos; ### $xkey ### assert: $xkey >= 0 ### assert: $xkey < 15 foreach my $y1pos (0 .. 4) { foreach my $y2pos ($y1pos .. 4) { my $ykey = (9-$y1pos)*$y1pos/2 + $y2pos; ### $ykey ### assert: $ykey >= 0 ### assert: $ykey < 15 my $min_digit = undef; my $max_digit = undef; foreach my $digit (0 .. 24) { my $x = $digit_to_x[$digit]; my $y = $digit_to_y[$digit]; if ($rot & 2) { $x = 4 - $x; $y = 4 - $y; } if ($transpose) { ($x,$y) = ($y,$x); } next unless $x >= $x1pos; next unless $x <= $x2pos; next unless $y >= $y1pos; next unless $y <= $y2pos; $min_digit = min_maybe($digit,$min_digit); $max_digit = max_maybe($digit,$max_digit); } my $key = $state*9 + $xkey*15 + $ykey; ### $key if (defined $min_digit[$key]) { die "oops min_digit[] already: state=$state key=$key y1p=$y1pos y2p=$y2pos value=$min_digit[$key], new=$min_digit"; } $min_digit[$key] = $min_digit; $max_digit[$key] = $max_digit; } } ### @min_digit } } } } print_table ("next_state", \@next_state); print_table ("digit_to_x", \@digit_to_x); print_table ("digit_to_y", \@digit_to_y); print_table ("yx_to_digit", \@yx_to_digit); print_table ("min_digit", \@min_digit); print_table ("max_digit", \@max_digit); print "# state length ",scalar(@next_state)," in each of 4 tables\n\n"; ### @next_state ### @digit_to_x ### @digit_to_y ### @yx_to_digit ### next_state length: scalar(@next_state) { my @pending_state = (0); my $count = 0; my @seen_state; my $depth = 1; $seen_state[0] = $depth; while (@pending_state) { my $state = pop @pending_state; $count++; ### consider state: $state foreach my $digit (0 .. 24) { my $next_state = $next_state[$state+$digit]; if (! $seen_state[$next_state]) { $seen_state[$next_state] = $depth; push @pending_state, $next_state; ### push: "$next_state depth $depth" } } $depth++; } for (my $state = 0; $state < @next_state; $state += 25) { print "# used state $state depth ",$seen_state[$state]//'undef',"\n"; } print "used state count $count\n"; } print "\n"; exit 0; Math-PlanePath-129/tools/gallery.pl0000644000175000017500000014717413774320167015072 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl gallery.pl # # Create .png files as for the web page # http://user42.tuxfamily.org/math-planepath/gallery.html # Output is to $target_dir = "$ENV{HOME}/tux/web/math-planepath". # use 5.004; use strict; use warnings; use File::Compare (); use File::Copy; use File::Temp; use Image::Base::GD; use POSIX 'floor'; # uncomment this to run the ### lines # use Smart::Comments; my $target_dir = "$ENV{HOME}/tux/web/math-planepath"; my $tempfh = File::Temp->new (SUFFIX => '.png'); my $tempfile = $tempfh->filename; my $big_bytes = 0; my %seen_filename; foreach my $elem ( ['corner-alternating-small.png', 'math-image --path=CornerAlternating --lines --scale=4 --size=32'], ['corner-alternating-big.png', 'math-image --path=CornerAlternating --lines --scale=12 --size=200'], ['corner-alternating-wider4-big.png', 'math-image --path=CornerAlternating,wider=4 --lines --scale=12 --size=200'], ['corner-small.png', 'math-image --path=Corner --lines --scale=4 --size=32'], ['corner-big.png', 'math-image --path=Corner --lines --scale=12 --size=200'], ['corner-wider4-big.png', 'math-image --path=Corner,wider=4 --lines --scale=12 --size=200'], ['peano-diagonals-small.png', 'math-image --path=PeanoDiagonals --lines --scale=3 --size=32'], ['peano-diagonals-big.png', 'math-image --path=PeanoDiagonals --lines --scale=12 --size=192'], ['peano-diagonals-rounded-big.png', 'math-image --path=PeanoDiagonals --values=Lines,lines_type=rounded,midpoint_offset=0.4 --figure=point --scale=15 --size=192'], ['peano-small.png', 'math-image --path=PeanoCurve --lines --scale=3 --size=32'], ['peano-big.png', 'math-image --path=PeanoCurve --lines --scale=7 --size=192'], ['peano-radix7-big.png', 'math-image --path=PeanoCurve,radix=7 --values=Lines --scale=5 --size=192'], # ['knight-temporary.png', # 'math-image --path=KnightSpiral --lines --scale=20 --size=420 --figure=point'], ['knight-small.png', 'math-image --path=KnightSpiral --lines --scale=7 --size=32'], ['knight-big.png', 'math-image --path=KnightSpiral --lines --scale=11 --size=197'], ['alternate-terdragon-small.png', 'math-image --path=AlternateTerdragon --lines --scale=5 --size=32 --offset=-3,-7'], ['alternate-terdragon-big.png', 'math-image --path=AlternateTerdragon --lines --figure=point --scale=4 --size=200 --offset=0,-40'], ['hilbert-sides-small.png', 'math-image --path=HilbertSides --lines --scale=2 --size=32 --figure=point'], ['hilbert-sides-big.png', 'math-image --path=HilbertSides --lines --scale=4 --size=257 --figure=point'], ['hilbert-small.png', 'math-image --path=HilbertCurve --lines --scale=3 --size=32 --figure=point'], ['hilbert-big.png', 'math-image --path=HilbertCurve --lines --scale=7 --size=225 --figure=point'], ['hilbert-spiral-small.png', 'math-image --path=HilbertSpiral --lines --scale=3 --size=32 --figure=point'], ['hilbert-spiral-big.png', 'math-image --path=HilbertSpiral --lines --scale=7 --size=230 --figure=point'], ['dekking-curve-4arm-big.png', 'math-image --path=DekkingCurve,arms=4 --lines --scale=7 --size=181 --figure=point'], ['dekking-curve-big.png', 'math-image --path=DekkingCurve --lines --scale=7 --size=183 --figure=point'], ['dekking-curve-small.png', 'math-image --path=DekkingCurve --lines --scale=5 --size=32 --figure=point'], ['dekking-centres-small.png', 'math-image --path=DekkingCentres --lines --scale=6 --size=32 --figure=point'], ['dekking-centres-big.png', 'math-image --path=DekkingCentres --lines --scale=7 --size=176 --figure=point'], ['ulam-warburton-quarter-small.png', "math-image --path=UlamWarburtonQuarter --expression='i<50?i:0' --scale=2 --size=32"], ['ulam-warburton-quarter-octant.png', "math-image --path=UlamWarburtonQuarter,parts=octant --expression='i<132?i:0' --scale=4 --size=150"], ['ulam-warburton-quarter-octant-up.png', "math-image --path=UlamWarburtonQuarter,parts=octant_up --values=Lines --scale=2 --size=150 --figure=point"], ['ulam-warburton-quarter-big.png', "math-image --path=UlamWarburtonQuarter --expression='i<233?i:0' --scale=4 --size=150"], ['gcd-rationals-rows-big.png', "math-image --path=GcdRationals --expression='i<=68*67/2?i:0' --scale=2 --size=140x140"], ['gcd-rationals-diagonals-big.png', "math-image --path=GcdRationals,pairs_order=diagonals_down --expression='i<=47**2?i:0' --scale=2 --size=160x200"], ['gcd-rationals-small.png', 'math-image --path=GcdRationals --lines --scale=6 --size=32 --offset=-4,-4'], ['gcd-rationals-big.png', 'math-image --path=GcdRationals --lines --scale=15 --size=200'], ['gcd-rationals-reverse-big.png', 'math-image --path=GcdRationals,pairs_order=rows_reverse --lines --scale=15 --size=200'], ['wythoff-preliminary-triangle-small.png', 'math-image --path=WythoffPreliminaryTriangle --lines --scale=5 --size=32'], ['wythoff-preliminary-triangle-big.png', 'math-image --path=WythoffPreliminaryTriangle --lines --scale=12 --size=200'], ['wythoff-array-small.png', 'math-image --path=WythoffArray --lines --scale=8 --size=32'], ['wythoff-array-big.png', 'math-image --path=WythoffArray --lines --scale=16 --size=200'], ['pythagorean-tree-ltoh.png', 'math-image --path=PythagoreanTree,digit_order=LtoH --values=LinesTree --scale=2 --size=200'], ['pythagorean-tree-big.png', 'math-image --path=PythagoreanTree --values=LinesTree --scale=4 --size=200'], ['pythagorean-tree-uard-rows-pq.png', 'math-image --path=PythagoreanTree,tree_type=UArD,digit_order=LtoH,coordinates=PQ --lines --scale=14 --size=200 --figure=point'], ['pythagorean-tree-uard-rows.png', 'math-image --path=PythagoreanTree,tree_type=UArD,digit_order=LtoH --lines --scale=1 --size=200 --figure=point'], ['pythagorean-tree-umt-big.png', 'math-image --path=PythagoreanTree,tree_type=UMT --values=LinesTree --scale=4 --size=200'], ['pythagorean-tree-fb-big.png', 'math-image --path=PythagoreanTree,tree_type=FB --values=LinesTree --scale=4 --size=200'], ['pythagorean-points-sm-big.png', 'math-image --path=PythagoreanTree,coordinates=SM --all --scale=1 --size=150'], ['pythagorean-points-sc-big.png', 'math-image --path=PythagoreanTree,coordinates=SC --all --scale=1 --size=150'], ['pythagorean-points-mc-big.png', 'math-image --path=PythagoreanTree,coordinates=MC --all --scale=1 --size=150'], ['pythagorean-points-bc-big.png', 'math-image --path=PythagoreanTree,coordinates=BC --all --scale=1 --size=200'], ['pythagorean-points-ac-big.png', 'math-image --path=PythagoreanTree,coordinates=AC --all --scale=1 --size=200'], ['pythagorean-small.png', 'math-image --path=PythagoreanTree --values=LinesTree --scale=1 --size=32'], ['pythagorean-points-big.png', 'math-image --path=PythagoreanTree --all --scale=1 --size=200'], ['htree-big.png', 'math-image --path=HTree --values=LinesTree --scale=6 --size=196 --offset=2,2 --figure=point'], ['htree-small.png', 'math-image --path=HTree --values=LinesTree --scale=4 --size=32 --offset=2,2'], ['chan-tree-rows-ltoh.png', \&special_chan_rows, title => 'ChanTree,digit_order=LtoH rows' ], ['cfrac-digits-growth.png', "math-image --path=CfracDigits --expression='i<=3**7?i:0' --scale=1 --size=100x200"], ['cfrac-digits-small.png', 'math-image --path=CfracDigits --lines --scale=4 --size=32 --offset=-4,-8'], ['cfrac-digits-big.png', 'math-image --path=CfracDigits --lines --scale=10 --size=200'], ['cfrac-digits-radix3.png', 'math-image --path=CfracDigits,radix=3 --lines --scale=10 --size=200'], ['cfrac-digits-radix4.png', 'math-image --path=CfracDigits,radix=4 --lines --scale=10 --size=200'], ['chan-tree-lines.png', 'math-image --path=ChanTree --values=LinesTree --scale=12 --size=200'], ['chan-tree-small.png', 'math-image --path=ChanTree --all --scale=2 --size=32'], ['chan-tree-big.png', 'math-image --path=ChanTree --all --scale=3 --size=200'], ['chan-tree-k4.png', 'math-image --path=ChanTree,k=4 --all --scale=3 --size=200'], ['chan-tree-k5.png', 'math-image --path=ChanTree,k=5 --all --scale=3 --size=200'], ['toothpick-spiral-small.png', 'math-image --path=ToothpickSpiral --values=Lines --scale=5 --size=32 --figure=point'], ['toothpick-spiral-big.png', 'math-image --path=ToothpickSpiral --values=Lines --scale=9 --size=200x200'], ['toothpick-upist-small.png', 'math-image --path=ToothpickUpist --values=LinesTree --scale=4 --size=32 --figure=toothpick --offset=0,5'], ['toothpick-upist-big.png', 'math-image --path=ToothpickUpist --values=LinesTree --scale=5 --size=300x150 --figure=toothpick'], ['lcorner-tree-1.png', 'math-image --path=LCornerTree,parts=1 --values=LinesTree --scale=7 --size=99'], ['lcorner-tree-big.png', 'math-image --path=LCornerTree --values=LinesTree --scale=7 --size=199'], ['lcorner-tree-octant-up.png', 'math-image --path=LCornerTree,parts=octant_up --values=LinesTree --scale=7 --size=99 --figure=point'], ['lcorner-tree-octant-up+1.png', 'math-image --path=LCornerTree,parts=octant_up+1 --values=LinesTree --scale=7 --size=99 --figure=point'], ['lcorner-tree-wedge.png', 'math-image --path=LCornerTree,parts=wedge --values=LinesTree --scale=6 --size=200x95 --figure=point'], ['lcorner-tree-wedge+1.png', 'math-image --path=LCornerTree,parts=wedge+1 --values=LinesTree --scale=6 --size=200x95 --figure=point'], ['lcorner-tree-octant.png', 'math-image --path=LCornerTree,parts=octant --values=LinesTree --scale=7 --size=99 --figure=point'], ['lcorner-tree-octant+1.png', 'math-image --path=LCornerTree,parts=octant+1 --values=LinesTree --scale=7 --size=99 --figure=point'], ['lcorner-tree-diagonal.png', 'math-image --path=LCornerTree,parts=diagonal --values=LinesTree --scale=7 --size=99 --figure=point'], ['lcorner-tree-diagonal-1.png', 'math-image --path=LCornerTree,parts=diagonal-1 --values=LinesTree --scale=7 --size=99'], ['lcorner-tree-small.png', 'math-image --path=LCornerTree --values=LinesTree --scale=4 --size=32'], ['toothpick-tree-3.png', 'math-image --path=ToothpickTree,parts=3 --values=LinesTree --scale=6 --size=200 --figure=point'], ['toothpick-tree-octant.png', 'math-image --path=ToothpickTree,parts=octant --values=LinesTree --scale=6 --size=200 --figure=point'], ['toothpick-tree-wedge.png', 'math-image --path=ToothpickTree,parts=wedge --values=LinesTree --scale=6 --size=200x104 --figure=toothpick --offset=0,5'], ['toothpick-tree-small.png', 'math-image --path=ToothpickTree --values=LinesTree --scale=4 --size=32'], ['toothpick-tree-big.png', 'math-image --path=ToothpickTree --values=LinesTree --scale=6 --size=200'], ['toothpick-replicate-small.png', 'math-image --path=ToothpickReplicate --lines --scale=4 --size=32 --figure=toothpick'], ['toothpick-replicate-big.png', 'math-image --path=ToothpickReplicate --all --scale=6 --size=200 --figure=toothpick'], ['ulam-warburton-1.png', "math-image --path=UlamWarburton,parts=1 --values=LinesTree --figure=diamond --scale=8 --size=150"], ['ulam-warburton-2.png', "math-image --path=UlamWarburton,parts=2 --values=Lines --figure=point --scale=6 --size=360x130"], ['ulam-warburton-tree-big.png', "math-image --path=UlamWarburton --values=LinesTree --scale=7 --figure=point --size=150"], ['ulam-warburton-small.png', "math-image --path=UlamWarburton --expression='i<50?i:0' --scale=2 --size=32"], ['ulam-warburton-big.png', "math-image --path=UlamWarburton --expression='i<233?i:0' --scale=4 --size=150"], ['one-of-eight-wedge.png', 'math-image --path=OneOfEight,parts=wedge --all --scale=3 --size=200x99'], ['one-of-eight-1-nonleaf.png', 'math-image --path=OneOfEight,parts=1 --values=PlanePathCoord,planepath=\"OneOfEight,parts=1\",coordinate_type=IsNonLeaf --scale=3 --size=99'], ['one-of-eight-small.png', 'math-image --path=OneOfEight --values=LinesTree --scale=4 --size=32'], ['one-of-eight-big.png', 'math-image --path=OneOfEight --values=LinesTree --scale=6 --size=200'], ['one-of-eight-1.png', 'math-image --path=OneOfEight,parts=1 --all --scale=3 --size=99'], ['one-of-eight-octant.png', 'math-image --path=OneOfEight,parts=octant --all --scale=3 --size=99'], ['one-of-eight-3mid.png', 'math-image --path=OneOfEight,parts=3mid --all --scale=3 --size=99'], ['one-of-eight-3side.png', 'math-image --path=OneOfEight,parts=3side --all --scale=3 --size=99'], ['flowsnake-3arm-big.png', 'math-image --path=Flowsnake,arms=3 --lines --scale=6 --size=200 --figure=point'], ['flowsnake-small.png', 'math-image --path=Flowsnake --lines --scale=4 --size=32 --offset=-5,-13'], ['flowsnake-big.png', 'math-image --path=Flowsnake --lines --scale=8 --size=200 --offset=-20,-90'], ['flowsnake-centres-small.png', 'math-image --path=FlowsnakeCentres --lines --scale=4 --size=32 --offset=-5,-13'], ['flowsnake-centres-big.png', 'math-image --path=FlowsnakeCentres --lines --scale=8 --size=200 --offset=-20,-90'], ['rationals-tree-rows-sb.png', \&special_sb_rows, title => 'RationalsTree,tree_type=SB rows' ], ['rationals-tree-lines-ayt.png', 'math-image --path=RationalsTree,tree_type=AYT --values=LinesTree --scale=20 --size=200'], ['rationals-tree-lines-hcs.png', 'math-image --path=RationalsTree,tree_type=HCS --values=LinesTree --scale=20 --size=200'], ['rationals-tree-lines-l.png', 'math-image --path=RationalsTree,tree_type=L --values=LinesTree --scale=20 --size=200'], ['rationals-tree-small.png', 'math-image --path=RationalsTree --values=LinesTree --scale=8 --size=32 --offset=-8,-8'], ['rationals-tree-big.png', 'math-image --path=RationalsTree --all --scale=3 --size=200'], ['rationals-tree-lines-sb.png', 'math-image --path=RationalsTree,tree_type=SB --values=LinesTree --scale=20 --size=200'], ['rationals-tree-lines-cw.png', 'math-image --path=RationalsTree,tree_type=CW --values=LinesTree --scale=20 --size=200'], ['rationals-tree-lines-bird.png', 'math-image --path=RationalsTree,tree_type=Bird --values=LinesTree --scale=20 --size=200'], ['rationals-tree-lines-drib.png', 'math-image --path=RationalsTree,tree_type=Drib --values=LinesTree --scale=20 --size=200'], ['triangle-spiral-skewed-small.png', 'math-image --path=TriangleSpiralSkewed --lines --scale=3 --size=32'], ['triangle-spiral-skewed-big.png', 'math-image --path=TriangleSpiralSkewed --lines --scale=13 --size=150'], ['triangle-spiral-skewed-right-big.png', 'math-image --path=TriangleSpiralSkewed,skew=right --lines --scale=13 --size=150'], ['triangle-spiral-skewed-up-big.png', 'math-image --path=TriangleSpiralSkewed,skew=up --lines --scale=13 --size=150'], ['triangle-spiral-skewed-down-big.png', 'math-image --path=TriangleSpiralSkewed,skew=down --lines --scale=13 --size=150'], ['triangle-spiral-small.png', 'math-image --path=TriangleSpiral --lines --scale=3 --size=32'], ['triangle-spiral-big.png', 'math-image --path=TriangleSpiral --lines --scale=13 --size=300x150'], ['koch-curve-small.png', 'math-image --path=KochCurve --lines --scale=2 --size=32 --offset=0,8'], ['koch-curve-big.png', 'math-image --path=KochCurve --lines --scale=5 --size=250x100 --offset=0,5'], ['lcorner-replicate-small.png', 'math-image --path=LCornerReplicate --lines --scale=4 --size=32'], ['lcorner-replicate-big.png', 'math-image --path=LCornerReplicate --lines --scale=7 --size=200'], ['imaginaryhalf-small.png', 'math-image --path=ImaginaryHalf --lines --scale=7 --size=32'], ['imaginaryhalf-big.png', 'math-image --path=ImaginaryHalf --lines --scale=18 --size=200'], ['imaginaryhalf-radix5-big.png', 'math-image --path=ImaginaryHalf,radix=5 --lines --scale=18 --size=200'], ['imaginaryhalf-xxy-big.png', 'math-image --path=ImaginaryHalf,digit_order=XXY --lines --scale=10 --size=75'], ['imaginaryhalf-yxx-big.png', 'math-image --path=ImaginaryHalf,digit_order=YXX --lines --scale=10 --size=75'], ['imaginaryhalf-xnyx-big.png', 'math-image --path=ImaginaryHalf,digit_order=XnYX --lines --scale=10 --size=75'], ['imaginaryhalf-xnxy-big.png', 'math-image --path=ImaginaryHalf,digit_order=XnXY --lines --scale=10 --size=75'], ['imaginaryhalf-yxnx-big.png', 'math-image --path=ImaginaryHalf,digit_order=YXnX --lines --scale=10 --size=75'], ['imaginarybase-small.png', 'math-image --path=ImaginaryBase --lines --scale=7 --size=32'], ['imaginarybase-big.png', 'math-image --path=ImaginaryBase --lines --scale=18 --size=200'], ['imaginarybase-radix5-big.png', 'math-image --path=ImaginaryBase,radix=5 --lines --scale=18 --size=200'], ['h-indexing-small.png', 'math-image --path=HIndexing --scale=3 --size=32 --lines --figure=point'], ['h-indexing-big.png', 'math-image --path=HIndexing --lines --scale=5 --size=200 --figure=point'], ['sierpinski-curve-small.png', 'math-image --path=SierpinskiCurve,arms=2 --scale=3 --size=32 --lines --figure=point'], ['sierpinski-curve-big.png', 'math-image --path=SierpinskiCurve --lines --scale=3 --size=200 --figure=point'], ['sierpinski-curve-8arm-big.png', 'math-image --path=SierpinskiCurve,arms=8 --lines --scale=3 --size=200 --figure=point'], ['alternate-paper-midpoint-small.png', 'math-image --path=AlternatePaperMidpoint --lines --scale=3 --size=32'], ['alternate-paper-midpoint-big.png', 'math-image --path=AlternatePaperMidpoint --lines --figure=point --scale=4 --size=200'], ['alternate-paper-midpoint-8arm-big.png', 'math-image --path=AlternatePaperMidpoint,arms=8 --lines --figure=point --scale=4 --size=200'], ['sierpinski-curve-stair-small.png', 'math-image --path=SierpinskiCurveStair,arms=2 --scale=3 --size=32 --lines --figure=point'], ['sierpinski-curve-stair-big.png', 'math-image --path=SierpinskiCurveStair --lines --scale=5 --size=200 --figure=point'], ['sierpinski-curve-stair-8arm-big.png', 'math-image --path=SierpinskiCurveStair,arms=8 --lines --scale=5 --size=200 --figure=point'], ['alternate-paper-small.png', 'math-image --path=AlternatePaper --lines --scale=4 --size=32'], ['alternate-paper-big.png', 'math-image --path=AlternatePaper --lines --figure=point --scale=8 --size=200'], ['alternate-paper-rounded-big.png', 'math-image --path=AlternatePaper --values=Lines,lines_type=rounded,midpoint_offset=0.4 --figure=point --scale=16 --size=200'], ['pyramid-rows-small.png', 'math-image --path=PyramidRows --lines --scale=5 --size=32'], ['pyramid-rows-big.png', 'math-image --path=PyramidRows --lines --scale=15 --size=300x150'], ['pyramid-rows-right-big.png', 'math-image --path=PyramidRows,step=4,align=right --lines --scale=15 --size=300x150'], ['pyramid-rows-left-big.png', 'math-image --path=PyramidRows,step=1,align=left --lines --scale=15 --size=160x150 --offset=65,0'], ['sierpinski-triangle-small.png', 'math-image --path=SierpinskiTriangle --all --scale=2 --size=32'], ['sierpinski-triangle-big.png', 'math-image --path=SierpinskiTriangle --all --scale=3 --size=400x200'], ['sierpinski-triangle-right-big.png', 'math-image --path=SierpinskiTriangle,align=right --all --scale=3 --size=200x200'], ['sierpinski-triangle-left-big.png', 'math-image --path=SierpinskiTriangle,align=left --all --scale=3 --size=200x200 --offset=98,0'], ['sierpinski-triangle-diagonal-big.png', 'math-image --path=SierpinskiTriangle,align=diagonal --values=LinesTree --scale=4 --size=200x200'], ['sierpinski-arrowhead-centres-small.png', 'math-image --path=SierpinskiArrowheadCentres --lines --scale=2 --size=32'], ['sierpinski-arrowhead-centres-big.png', 'math-image --path=SierpinskiArrowheadCentres --lines --scale=3 --size=400x200'], ['sierpinski-arrowhead-centres-right-big.png', 'math-image --path=SierpinskiArrowheadCentres,align=right --lines --scale=4 --size=200x200'], ['sierpinski-arrowhead-centres-left-big.png', 'math-image --path=SierpinskiArrowheadCentres,align=left --lines --scale=4 --size=200x200 --offset=98,0'], ['sierpinski-arrowhead-centres-diagonal-big.png', 'math-image --path=SierpinskiArrowheadCentres,align=diagonal --lines --scale=5 --size=200x200 --figure=point'], ['sierpinski-arrowhead-small.png', 'math-image --path=SierpinskiArrowhead --lines --scale=2 --size=32'], ['sierpinski-arrowhead-big.png', 'math-image --path=SierpinskiArrowhead --lines --scale=3 --size=400x200'], ['sierpinski-arrowhead-right-big.png', 'math-image --path=SierpinskiArrowhead,align=right --lines --scale=4 --size=200x200'], ['sierpinski-arrowhead-left-big.png', 'math-image --path=SierpinskiArrowhead,align=left --lines --scale=4 --size=200x200 --offset=98,0'], ['sierpinski-arrowhead-diagonal-big.png', 'math-image --path=SierpinskiArrowhead,align=diagonal --lines --scale=5 --size=200x200 --figure=point'], ['wunderlich-meander-small.png', 'math-image --path=WunderlichMeander --lines --scale=4 --size=32 --figure=point'], ['wunderlich-meander-big.png', 'math-image --path=WunderlichMeander --lines --scale=7 --size=192 --figure=point'], ['cinco-small.png', 'math-image --path=CincoCurve --lines --scale=6 --size=32 --figure=point'], ['cinco-big.png', 'math-image --path=CincoCurve --lines --scale=7 --size=176 --figure=point'], ['power-array-small.png', 'math-image --path=PowerArray --lines --scale=8 --size=32'], ['power-array-big.png', 'math-image --path=PowerArray --lines --scale=16 --size=200'], ['power-array-radix5-big.png', 'math-image --path=PowerArray,radix=5 --lines --scale=16 --size=200'], ['complexminus-small.png', "math-image --path=ComplexMinus --expression='i<32?i:0' --scale=2 --size=32"], ['complexminus-big.png', "math-image --path=ComplexMinus --expression='i<1024?i:0' --scale=3 --size=200"], ['complexminus-r2-small.png', "math-image --path=ComplexMinus,realpart=2 --expression='i<125?i:0' --scale=2 --size=32"], ['complexminus-r2-big.png', "math-image --path=ComplexMinus,realpart=2 --expression='i<3125?i:0' --scale=1 --size=200"], ['pyramid-sides-small.png', 'math-image --path=PyramidSides --lines --scale=5 --size=32'], ['pyramid-sides-big.png', 'math-image --path=PyramidSides --lines --scale=15 --size=300x150'], ['triangular-hypot-small.png', 'math-image --path=TriangularHypot --lines --scale=4 --size=32'], ['triangular-hypot-big.png', 'math-image --path=TriangularHypot --lines --scale=15 --size=200x150'], ['triangular-hypot-odd-big.png', 'math-image --path=TriangularHypot,points=odd --lines --scale=15 --size=200x150'], ['triangular-hypot-all-big.png', 'math-image --path=TriangularHypot,points=all --lines --scale=15 --size=200x150'], ['triangular-hypot-hex-big.png', 'math-image --path=TriangularHypot,points=hex --lines --scale=15 --size=200x150'], ['triangular-hypot-hex-rotated-big.png', 'math-image --path=TriangularHypot,points=hex_rotated --lines --scale=15 --size=200x150'], ['triangular-hypot-hex-centred-big.png', 'math-image --path=TriangularHypot,points=hex_centred --lines --scale=15 --size=200x150'], ['greek-key-small.png', 'math-image --path=GreekKeySpiral --lines --scale=4 --size=32'], ['greek-key-big.png', 'math-image --path=GreekKeySpiral --lines --scale=8 --size=200'], ['greek-key-turns1-big.png', 'math-image --path=GreekKeySpiral,turns=1 --lines --scale=8 --figure=point --size=200'], ['greek-key-turns5-big.png', 'math-image --path=GreekKeySpiral,turns=5 --lines --scale=8 --figure=point --size=200'], ['c-curve-small.png', 'math-image --path=CCurve --lines --scale=3 --size=32 --offset=8,0'], ['c-curve-big.png', 'math-image --path=CCurve --lines --figure=point --scale=3 --size=250x250 --offset=20,-70'], ['diagonals-octant-small.png', 'math-image --path=DiagonalsOctant --lines --scale=6 --size=32'], ['diagonals-octant-big.png', 'math-image --path=DiagonalsOctant --lines --scale=15 --size=195'], ['diagonals-alternating-small.png', 'math-image --path=DiagonalsAlternating --lines --scale=6 --size=32'], ['diagonals-alternating-big.png', 'math-image --path=DiagonalsAlternating --lines --scale=15 --size=195'], ['diagonals-small.png', 'math-image --path=Diagonals --lines --scale=6 --size=32'], ['diagonals-big.png', 'math-image --path=Diagonals --lines --scale=15 --size=195'], ['terdragon-rounded-small.png', 'math-image --path=TerdragonRounded --lines --scale=2 --size=32 --offset=-5,-10'], ['terdragon-rounded-big.png', 'math-image --path=TerdragonRounded --lines --figure=point --scale=3 --size=200 --offset=65,-20'], ['terdragon-rounded-6arm-big.png', 'math-image --path=TerdragonRounded,arms=6 --lines --figure=point --scale=5 --size=200'], ['terdragon-small.png', 'math-image --path=TerdragonCurve --lines --scale=5 --size=32 --offset=-3,-7'], ['terdragon-big.png', 'math-image --path=TerdragonCurve --lines --figure=point --scale=4 --size=200 --offset=75,50'], # ['terdragon-6arm-big.png', # 'math-image --path=TerdragonCurve,arms=6 --lines --figure=point --scale=4 --size=200'], # ['terdragon-rounded-big.png', # 'math-image --path=TerdragonCurve --values=Lines,lines_type=rounded,midpoint_offset=.4 --figure=point --scale=16 --size=200 --offset=35,-30'], # ['terdragon-rounded-6arm-big.png', # 'math-image --path=TerdragonCurve,arms=6 --values=Lines,lines_type=rounded,midpoint_offset=.4 --figure=point --scale=10 --size=200'], ['terdragon-midpoint-6arm-big.png', 'math-image --path=TerdragonMidpoint,arms=6 --lines --figure=circle --scale=4 --size=200'], ['terdragon-midpoint-small.png', 'math-image --path=TerdragonMidpoint --lines --scale=2 --size=32 --offset=2,-9'], ['terdragon-midpoint-big.png', 'math-image --path=TerdragonMidpoint --lines --figure=circle --scale=8 --size=200 --offset=50,-50'], ['r5dragon-small.png', 'math-image --path=R5DragonCurve --lines --scale=4 --size=32 --offset=6,-5'], ['r5dragon-big.png', 'math-image --path=R5DragonCurve --lines --figure=point --scale=10 --size=200x200 --offset=20,45'], ['r5dragon-rounded-big.png', 'math-image --path=R5DragonCurve --values=Lines,lines_type=rounded,midpoint_offset=.6 --figure=point --scale=10 --size=200x200 --offset=20,45'], ['r5dragon-rounded-4arm-big.png', 'math-image --path=R5DragonCurve,arms=4 --values=Lines,lines_type=rounded,midpoint_offset=.6 --figure=point --scale=20 --size=200x200'], ['r5dragon-midpoint-small.png', 'math-image --path=R5DragonMidpoint --lines --scale=3 --size=32 --offset=3,-9'], ['r5dragon-midpoint-big.png', 'math-image --path=R5DragonMidpoint --lines --figure=point --scale=8 --size=200 --offset=65,-15'], ['r5dragon-midpoint-4arm-big.png', 'math-image --path=R5DragonMidpoint,arms=4 --lines --figure=point --scale=12 --size=200'], ['cubicbase-small.png', 'math-image --path=CubicBase --lines --scale=5 --size=32'], ['cubicbase-big.png', 'math-image --path=CubicBase --lines --scale=18 --size=200'], ['cubicbase-radix5-big.png', 'math-image --path=CubicBase,radix=5 --lines --scale=18 --size=200'], ['gray-code-small.png', 'math-image --path=GrayCode --lines --scale=6 --size=32'], ['gray-code-big.png', 'math-image --path=GrayCode --lines --scale=14 --size=226'], ['gray-code-radix4-big.png', 'math-image --path=GrayCode,radix=4 --lines --scale=14 --size=226'], ['zorder-small.png', 'math-image --path=ZOrderCurve --lines --scale=6 --size=32'], ['zorder-big.png', 'math-image --path=ZOrderCurve --lines --scale=14 --size=226'], ['zorder-radix5-big.png', 'math-image --path=ZOrderCurve,radix=5 --lines --scale=14 --size=226'], ['zorder-fibbinary.png', 'math-image --path=ZOrderCurve --values=Fibbinary --scale=1 --size=704x320'], ['wunderlich-serpentine-small.png', 'math-image --path=WunderlichSerpentine --lines --scale=4 --size=32'], ['wunderlich-serpentine-big.png', 'math-image --path=WunderlichSerpentine --lines --scale=7 --size=192'], ['wunderlich-serpentine-coil-big.png', 'math-image --path=WunderlichSerpentine,serpentine_type=coil --values=Lines --scale=7 --size=192'], ['wunderlich-serpentine-radix7-big.png', 'math-image --path=WunderlichSerpentine,radix=7 --values=Lines --scale=5 --size=192'], ['cretan-labyrinth-small.png', 'math-image --path=CretanLabyrinth --lines --scale=3 --size=32'], ['cretan-labyrinth-big.png', 'math-image --path=CretanLabyrinth --lines --scale=9 --size=185x195 --offset=5,0'], ['theodorus-small.png', 'math-image --path=TheodorusSpiral --lines --scale=3 --size=32'], ['theodorus-big.png', 'math-image --path=TheodorusSpiral --lines --scale=10 --size=200'], ['filled-rings-small.png', 'math-image --path=FilledRings --lines --scale=4 --size=32'], ['filled-rings-big.png', 'math-image --path=FilledRings --lines --scale=10 --size=200'], ['pixel-small.png', 'math-image --path=PixelRings --lines --scale=4 --size=32'], ['pixel-big.png', 'math-image --path=PixelRings --all --figure=circle --scale=10 --size=200', border => 1 ], ['pixel-lines-big.png', 'math-image --path=PixelRings --lines --scale=10 --size=200'], ['staircase-small.png', 'math-image --path=Staircase --lines --scale=4 --size=32'], ['staircase-big.png', 'math-image --path=Staircase --lines --scale=12 --size=200x200'], ['staircase-alternating-square-small.png', 'math-image --path=StaircaseAlternating,end_type=square --lines --scale=4 --size=32'], ['staircase-alternating-big.png', 'math-image --path=StaircaseAlternating --lines --scale=12 --size=200x200'], ['staircase-alternating-square-big.png', 'math-image --path=StaircaseAlternating,end_type=square --lines --scale=12 --size=200x200'], ['cellular-rule-30-small.png', 'math-image --path=CellularRule,rule=30 --all --scale=2 --size=32'], ['cellular-rule-30-big.png', 'math-image --path=CellularRule,rule=30 --all --scale=4 --size=300x150'], ['cellular-rule-73-big.png', 'math-image --path=CellularRule,rule=73 --all --scale=4 --size=300x150'], ['cellular-rule190-small.png', 'math-image --path=CellularRule190 --all --scale=3 --size=32'], ['cellular-rule190-big.png', 'math-image --path=CellularRule190 --all --scale=4 --size=300x150'], ['cellular-rule190-mirror-big.png', 'math-image --path=CellularRule190,mirror=1 --all --scale=4 --size=300x150'], ['cellular-rule54-small.png', 'math-image --path=CellularRule54 --all --scale=3 --size=32'], ['cellular-rule54-big.png', 'math-image --path=CellularRule54 --all --scale=4 --size=300x150'], ['complexplus-small.png', "math-image --path=ComplexPlus --all --scale=2 --size=32"], ['complexplus-big.png', "math-image --path=ComplexPlus --all --scale=3 --size=200", border => 1], ['complexplus-r2-small.png', "math-image --path=ComplexPlus,realpart=2 --all --scale=2 --size=32"], ['complexplus-r2-big.png', "math-image --path=ComplexPlus,realpart=2 --all --scale=1 --size=200", border => 1], ['digit-groups-small.png', "math-image --path=DigitGroups --expression='i<256?i:0' --scale=2 --size=32"], # --foreground=red ['digit-groups-big.png', "math-image --path=DigitGroups --expression='i<2048?i:0' --scale=3 --size=200", border => 1], ['digit-groups-radix5-big.png', "math-image --path=DigitGroups,radix=5 --expression='i<15625?i:0' --scale=3 --size=200", border => 1], ['l-tiling-small.png', 'math-image --path=LTiling --all --scale=2 --size=32' ], ['l-tiling-big.png', 'math-image --path=LTiling --all --scale=10 --size=200', border => 1 ], ['l-tiling-ends-big.png', 'math-image --path=LTiling,L_fill=ends --all --scale=10 --size=200', border => 1], ['l-tiling-all-big.png', 'math-image --path=LTiling,L_fill=all --lines --scale=10 --size=200'], ['dragon-rounded-small.png', 'math-image --path=DragonRounded --lines --scale=2 --size=32 --offset=6,-3'], ['dragon-rounded-big.png', 'math-image --path=DragonRounded --lines --figure=point --scale=3 --size=200 --offset=-20,0'], ['dragon-rounded-3arm-big.png', 'math-image --path=DragonRounded,arms=3 --lines --figure=point --scale=3 --size=200'], ['dragon-midpoint-small.png', 'math-image --path=DragonMidpoint --lines --scale=3 --size=32 --offset=7,-6'], ['dragon-midpoint-big.png', 'math-image --path=DragonMidpoint --lines --figure=point --scale=8 --size=200 --offset=-10,50'], ['dragon-midpoint-4arm-big.png', 'math-image --path=DragonMidpoint,arms=4 --lines --figure=point --scale=8 --size=200'], ['dragon-small.png', 'math-image --path=DragonCurve --lines --scale=4 --size=32 --offset=6,0'], ['dragon-big.png', 'math-image --path=DragonCurve --lines --figure=point --scale=8 --size=250x200 --offset=-55,0'], ['cellular-rule57-small.png', 'math-image --path=CellularRule57 --all --scale=3 --size=32'], ['cellular-rule57-big.png', 'math-image --path=CellularRule57 --all --scale=4 --size=300x150'], ['cellular-rule57-mirror-big.png', 'math-image --path=CellularRule57,mirror=1 --all --scale=4 --size=300x150'], ['quadric-islands-small.png', 'math-image --path=QuadricIslands --lines --scale=4 --size=32'], ['quadric-islands-big.png', 'math-image --path=QuadricIslands --lines --scale=2 --size=200'], ['quadric-curve-small.png', 'math-image --path=QuadricCurve --lines --scale=2 --size=32'], ['quadric-curve-big.png', 'math-image --path=QuadricCurve --lines --scale=4 --size=300x200'], ['divisible-columns-small.png', 'math-image --path=DivisibleColumns --all --scale=3 --size=32'], ['divisible-columns-big.png', 'math-image --path=DivisibleColumns --all --scale=3 --size=200'], ['divisible-columns-proper-big.png', 'math-image --path=DivisibleColumns,divisor_type=proper --all --scale=3 --size=400x200'], ['vogel-small.png', 'math-image --path=VogelFloret --all --scale=3 --size=32'], ['vogel-big.png', 'math-image --path=VogelFloret --all --scale=4 --size=200'], ['vogel-sqrt2-big.png', 'math-image --path=VogelFloret,rotation_type=sqrt2 --all --scale=4 --size=200'], ['vogel-sqrt5-big.png', 'math-image --path=VogelFloret,rotation_type=sqrt5 --all --scale=4 --size=200'], ['anvil-small.png', 'math-image --path=AnvilSpiral --lines --scale=4 --size=32'], ['anvil-big.png', 'math-image --path=AnvilSpiral --lines --scale=13 --size=200'], ['anvil-wider4-big.png', 'math-image --path=AnvilSpiral,wider=4 --lines --scale=13 --size=200'], ['octagram-small.png', 'math-image --path=OctagramSpiral --lines --scale=4 --size=32'], ['octagram-big.png', 'math-image --path=OctagramSpiral --lines --scale=13 --size=200'], ['complexrevolving-small.png', "math-image --path=ComplexRevolving --expression='i<64?i:0' --scale=2 --size=32"], ['complexrevolving-big.png', "math-image --path=ComplexRevolving --expression='i<4096?i:0' --scale=2 --size=200"], ['fractions-tree-small.png', 'math-image --path=FractionsTree --values=LinesTree --scale=8 --size=32 --offset=-8,-12'], ['fractions-tree-big.png', 'math-image --path=FractionsTree --all --scale=3 --size=200'], ['fractions-tree-lines-kepler.png', 'math-image --path=FractionsTree,tree_type=Kepler --values=LinesTree --scale=20 --size=200'], ['factor-rationals-small.png', 'math-image --path=FactorRationals --lines --scale=6 --size=32 --offset=-4,-4'], ['factor-rationals-big.png', 'math-image --path=FactorRationals --lines --scale=15 --size=200'], ['ar2w2-small.png', 'math-image --path=AR2W2Curve --lines --scale=4 --size=32 --figure=point'], ['ar2w2-a1-big.png', 'math-image --path=AR2W2Curve --lines --scale=7 --size=225 --figure=point'], ['ar2w2-d2-big.png', 'math-image --path=AR2W2Curve,start_shape=D2 --lines --scale=7 --size=113 --figure=point'], ['ar2w2-b2-big.png', 'math-image --path=AR2W2Curve,start_shape=B2 --lines --scale=7 --size=113 --figure=point'], ['ar2w2-b1rev-big.png', 'math-image --path=AR2W2Curve,start_shape=B1rev --lines --scale=7 --size=113 --figure=point'], ['ar2w2-d1rev-big.png', 'math-image --path=AR2W2Curve,start_shape=D1rev --lines --scale=7 --size=113 --figure=point'], ['ar2w2-a2rev-big.png', 'math-image --path=AR2W2Curve,start_shape=A2rev --lines --scale=7 --size=113 --figure=point'], ['diagonal-rationals-small.png', 'math-image --path=DiagonalRationals --lines --scale=4 --size=32'], ['diagonal-rationals-big.png', 'math-image --path=DiagonalRationals --lines --scale=10 --size=200'], ['coprime-columns-small.png', 'math-image --path=CoprimeColumns --all --scale=3 --size=32'], ['coprime-columns-big.png', 'math-image --path=CoprimeColumns --all --scale=3 --size=200'], ['kochel-small.png', 'math-image --path=KochelCurve --lines --scale=4 --size=32 --figure=point'], ['kochel-big.png', 'math-image --path=KochelCurve --lines --scale=7 --size=192 --figure=point'], ['beta-omega-small.png', 'math-image --path=BetaOmega --lines --scale=4 --size=32 --figure=point'], ['beta-omega-big.png', 'math-image --path=BetaOmega --lines --scale=7 --size=226 --figure=point'], ['mpeaks-small.png', 'math-image --path=MPeaks --lines --scale=4 --size=32'], ['mpeaks-big.png', 'math-image --path=MPeaks --lines --scale=13 --size=200x180'], ['hex-small.png', 'math-image --path=HexSpiral --lines --scale=3 --size=32'], ['hex-big.png', 'math-image --path=HexSpiral --lines --scale=13 --size=300x150'], ['hex-wider4-big.png', 'math-image --path=HexSpiral,wider=4 --lines --scale=13 --size=300x150'], ['hex-arms-small.png', 'math-image --path=HexArms --lines --scale=3 --size=32'], ['hex-arms-big.png', 'math-image --path=HexArms --lines --scale=10 --size=300x150'], ['hex-skewed-small.png', 'math-image --path=HexSpiralSkewed --lines --scale=3 --size=32'], ['hex-skewed-big.png', 'math-image --path=HexSpiralSkewed --lines --scale=13 --size=150'], ['hex-skewed-wider4-big.png', 'math-image --path=HexSpiralSkewed,wider=4 --lines --scale=13 --size=150'], ['fibonacci-word-fractal-small.png', 'math-image --path=FibonacciWordFractal --lines --scale=2 --size=32 --offset=2,2'], ['fibonacci-word-fractal-big.png', 'math-image --path=FibonacciWordFractal --lines --scale=2 --size=345x170'], ['corner-replicate-small.png', 'math-image --path=CornerReplicate --lines --scale=4 --size=32'], ['corner-replicate-big.png', 'math-image --path=CornerReplicate --lines --scale=10 --size=200'], ['aztec-diamond-rings-small.png', 'math-image --path=AztecDiamondRings --lines --scale=4 --size=32 --offset=3,3'], ['aztec-diamond-rings-big.png', 'math-image --path=AztecDiamondRings --lines --scale=13 --size=200x200'], ['diamond-spiral-small.png', 'math-image --path=DiamondSpiral --lines --scale=4 --size=32'], ['diamond-spiral-big.png', 'math-image --path=DiamondSpiral --lines --scale=13 --size=200x200'], ['square-replicate-small.png', 'math-image --path=SquareReplicate --lines --scale=4 --size=32'], ['square-replicate-big.png', 'math-image --path=SquareReplicate --lines --scale=10 --size=215'], ['gosper-replicate-small.png', # 7^2-1=48 "math-image --path=GosperReplicate --expression='i<48?i:0' --scale=2 --size=32"], ['gosper-replicate-big.png', # 7^4-1=16806 "math-image --path=GosperReplicate --expression='i<16806?i:0' --scale=1 --size=320x200"], ['gosper-side-small.png', 'math-image --path=GosperSide --lines --scale=3 --size=32 --offset=-13,-7'], ['gosper-side-big.png', 'math-image --path=GosperSide --lines --scale=1 --size=250x200 --offset=95,-95'], ['gosper-islands-small.png', 'math-image --path=GosperIslands --lines --scale=3 --size=32'], ['gosper-islands-big.png', 'math-image --path=GosperIslands --lines --scale=2 --size=250x200'], ['square-small.png', 'math-image --path=SquareSpiral --lines --scale=4 --size=32'], ['square-big.png', 'math-image --path=SquareSpiral --lines --scale=13 --size=200'], ['square-wider4-big.png', 'math-image --path=SquareSpiral,wider=4 --lines --scale=13 --size=253x200'], ['quintet-replicate-small.png', "math-image --path=QuintetReplicate --expression='i<125?i:0' --scale=2 --size=32"], ['quintet-replicate-big.png', "math-image --path=QuintetReplicate --expression='i<3125?i:0' --scale=2 --size=200"], ['quintet-curve-small.png', 'math-image --path=QuintetCurve --lines --scale=4 --size=32 --offset=-10,0 --figure=point'], ['quintet-curve-big.png', 'math-image --path=QuintetCurve --lines --scale=7 --size=200 --offset=-20,-70 --figure=point'], ['quintet-curve-4arm-big.png', 'math-image --path=QuintetCurve,arms=4 --lines --scale=7 --size=200 --figure=point'], ['quintet-centres-small.png', 'math-image --path=QuintetCentres --lines --scale=4 --size=32 --offset=-10,0 --figure=point'], ['quintet-centres-big.png', 'math-image --path=QuintetCentres --lines --scale=7 --size=200 --offset=-20,-70 --figure=point'], ['koch-squareflakes-inward-small.png', 'math-image --path=KochSquareflakes,inward=1 --lines --scale=2 --size=32'], ['koch-squareflakes-inward-big.png', 'math-image --path=KochSquareflakes,inward=1 --lines --scale=2 --size=150x150'], ['koch-squareflakes-small.png', 'math-image --path=KochSquareflakes --lines --scale=1 --size=32'], ['koch-squareflakes-big.png', 'math-image --path=KochSquareflakes --lines --scale=2 --size=150x150'], ['koch-snowflakes-small.png', 'math-image --path=KochSnowflakes --lines --scale=2 --size=32'], ['koch-snowflakes-big.png', 'math-image --path=KochSnowflakes --lines --scale=3 --size=200x150'], ['koch-peaks-small.png', 'math-image --path=KochPeaks --lines --scale=2 --size=32'], ['koch-peaks-big.png', 'math-image --path=KochPeaks --lines --scale=3 --size=200x100'], ['diamond-arms-small.png', 'math-image --path=DiamondArms --lines --scale=5 --size=32'], ['diamond-arms-big.png', 'math-image --path=DiamondArms --lines --scale=15 --size=150x150'], ['square-arms-small.png', 'math-image --path=SquareArms --lines --scale=3 --size=32'], ['square-arms-big.png', 'math-image --path=SquareArms --lines --scale=10 --size=150x150'], ['hept-skewed-small.png', 'math-image --path=HeptSpiralSkewed --lines --scale=4 --size=32'], ['hept-skewed-big.png', 'math-image --path=HeptSpiralSkewed --lines --scale=13 --size=200'], ['pent-small.png', 'math-image --path=PentSpiral --lines --scale=4 --size=32'], ['pent-big.png', 'math-image --path=PentSpiral --lines --scale=13 --size=200'], ['hypot-octant-small.png', 'math-image --path=HypotOctant --lines --scale=5 --size=32'], ['hypot-octant-big.png', 'math-image --path=HypotOctant --lines --scale=15 --size=200x150'], ['hypot-small.png', 'math-image --path=Hypot --lines --scale=6 --size=32'], ['hypot-big.png', 'math-image --path=Hypot --lines --scale=15 --size=200x150'], ['multiple-small.png', 'math-image --path=MultipleRings --lines --scale=4 --size=32'], ['multiple-big.png', 'math-image --path=MultipleRings --lines --scale=10 --size=200'], ['sacks-small.png', 'math-image --path=SacksSpiral --lines --scale=5 --size=32'], ['sacks-big.png', 'math-image --path=SacksSpiral --lines --scale=10 --size=200'], ['archimedean-small.png', 'math-image --path=ArchimedeanChords --lines --scale=5 --size=32'], ['archimedean-big.png', 'math-image --path=ArchimedeanChords --lines --scale=10 --size=200'], ) { my ($filename, $command, %option) = @$elem; if ($seen_filename{$filename}++) { die "Duplicate filename $filename"; } if (ref $command) { &$command ($tempfile); } else { $command .= " --png >$tempfile"; ### $command my $status = system $command; if ($status) { die "Exit $status"; } } if ($option{'border'}) { png_border($tempfile); } pngtextadd($tempfile, 'Author', 'Kevin Ryde'); pngtextadd($tempfile, 'Generator', 'Math-PlanePath tools/gallery.pl running math-image'); { my $title = $option{'title'}; if (! defined $title) { $command =~ /--path=([^ ]+)/ or die "Oops no --path in command: $command"; $title = $1; if ($command =~ /--values=(Fibbinary)/) { $title .= " $1"; } } pngtextadd ($tempfile, 'Title', $title); } system ("optipng -quiet -o2 $tempfile"); my $targetfile = "$target_dir/$filename"; if (File::Compare::compare($tempfile,$targetfile) == 0) { print "Unchanged $filename\n"; } else { print "Update $filename\n"; File::Copy::copy($tempfile,$targetfile); } if ($filename !~ /small/) { $big_bytes += -s $targetfile; } } foreach my $filename (<*.png>) { $filename =~ s{.*/}{}; if (! $seen_filename{$filename}) { print "leftover file: $filename\n"; } } my $gallery_html_filename = "$target_dir/gallery.html"; my $gallery_html_bytes = -s $gallery_html_filename; my $total_gallery_bytes = $big_bytes + $gallery_html_bytes; print "total gallery bytes $total_gallery_bytes ($gallery_html_bytes html, $big_bytes \"big\" images)\n"; exit 0; # draw a 1-pixel black border around the png image in $filename sub png_border { my ($filename) = @_; my $image = Image::Base::GD->new(-file => $filename); $image->rectangle (0,0, $image->get('-width') - 1, $image->get('-height') - 1, 'black'); $image->save; } # add text to the png image in $filename sub pngtextadd { my ($filename, $keyword, $value) = @_; system('pngtextadd', "--keyword=$keyword", "--text=$value", $tempfile) == 0 or die "system(pngtextadd)"; } sub special_chan_rows { my ($filename) = @_; my $scale = 8; my $width = 400; my $height = 200; my $margin = int($scale * .2); my $xhi = int($width/$scale) + 3; my $yhi = int($height/$scale) + 3; require Geometry::AffineTransform; my $affine = Geometry::AffineTransform->new; $affine->scale ($scale, -$scale); $affine->translate (-$scale+$margin, $height-1 - (-$scale+$margin)); { my ($x,$y) = $affine->transform (0,0); ### $x ### $y } require Image::Base::GD; my $image = Image::Base::GD->new (-width => $width, -height => $height); $image->rectangle (0,0, $width-1,$height-1, 'black'); require Math::PlanePath::ChanTree; my $path = Math::PlanePath::ChanTree->new (digit_order => 'LtoH', reduced => 0); foreach my $y (0 .. $yhi) { foreach my $x (0 .. $xhi) { my $n = $path->xy_to_n($x,$y) // next; next unless $path->tree_n_root($n) == 0; # first root only my $depth = $path->tree_n_to_depth($n); foreach my $n2 ($n + 1, $n - 1) { next unless $n2 >= 1; next unless $path->tree_n_to_depth($n2) == $depth; # within same depth next unless $path->tree_n_root($n2) == 0; # first root only my ($x2,$y2) = $path->n_to_xy($n2); my ($sx1,$sy1) = $affine->transform($x,$y); my ($sx2,$sy2) = $affine->transform($x2,$y2); _image_line_clipped ($image, $sx1,$sy1, $sx2,$sy2, $width,$height, 'white'); } } } $image->save($filename); } sub special_sb_rows { my ($filename) = @_; my $scale = 14; my $width = 200; my $height = 200; my $margin = int($scale * .2); my $xhi = int($width/$scale) + 3; my $yhi = int($height/$scale) + 3; require Geometry::AffineTransform; my $affine = Geometry::AffineTransform->new; $affine->scale ($scale, -$scale); $affine->translate (-$scale+$margin, $height-1 - (-$scale+$margin)); { my ($x,$y) = $affine->transform (0,0); ### $x ### $y } require Image::Base::GD; my $image = Image::Base::GD->new (-width => $width, -height => $height); $image->rectangle (0,0, $width-1,$height-1, 'black'); require Math::PlanePath::RationalsTree; my $path = Math::PlanePath::RationalsTree->new; foreach my $y (0 .. $yhi) { foreach my $x (0 .. $xhi) { my $n = $path->xy_to_n($x,$y) // next; my $depth = $path->tree_n_to_depth($n); foreach my $n2 ($n + 1, $n - 1) { next unless $n2 >= 1; next unless $path->tree_n_to_depth($n2) == $depth; my ($x2,$y2) = $path->n_to_xy($n2); my ($sx1,$sy1) = $affine->transform($x,$y); my ($sx2,$sy2) = $affine->transform($x2,$y2); _image_line_clipped ($image, $sx1,$sy1, $sx2,$sy2, $width,$height, 'white'); } } } $image->save($filename); } sub _image_line_clipped { my ($image, $x1,$y1, $x2,$y2, $width,$height, $colour) = @_; ### _image_line_clipped(): "$x1,$y1 $x2,$y2 ${width}x${height}" if (($x1,$y1, $x2,$y2) = line_clipper ($x1,$y1, $x2,$y2, $width,$height)) { ### clipped draw: "$x1,$y1 $x2,$y2" $image->line ($x1,$y1, $x2,$y2, $colour); return 1; } else { return 0; } } sub line_clipper { my ($x1,$y1, $x2,$y2, $width, $height) = @_; return if ($x1 < 0 && $x2 < 0) || ($x1 >= $width && $x2 >= $width) || ($y1 < 0 && $y2 < 0) || ($y1 >= $height && $y2 >= $height); my $x1new = $x1; my $y1new = $y1; my $x2new = $x2; my $y2new = $y2; my $xlen = ($x1 - $x2); my $ylen = ($y1 - $y2); if ($x1new < 0) { $x1new = 0; $y1new = floor (0.5 + ($y1 * (-$x2) + $y2 * ($x1)) / $xlen); ### x1 neg: "y1new to $x1new,$y1new" } elsif ($x1new >= $width) { $x1new = $width-1; $y1new = floor (0.5 + ($y1 * ($x1new-$x2) + $y2 * ($x1 - $x1new)) / $xlen); ### x1 big: "y1new to $x1new,$y1new" } if ($y1new < 0) { $y1new = 0; $x1new = floor (0.5 + ($x1 * (-$y2) + $x2 * ($y1)) / $ylen); ### y1 neg: "x1new to $x1new,$y1new left ".($y1new-$y2)." right ".($y1-$y1new) ### x1new to: $x1new } elsif ($y1new >= $height) { $y1new = $height-1; $x1new = floor (0.5 + ($x1 * ($y1new-$y2) + $x2 * ($y1 - $y1new)) / $ylen); ### y1 big: "x1new to $x1new,$y1new left ".($y1new-$y2)." right ".($y1-$y1new) } if ($x1new < 0 || $x1new >= $width) { ### x1new outside return; } if ($x2new < 0) { $x2new = 0; $y2new = floor (0.5 + ($y2 * ($x1) + $y1 * (-$x2)) / $xlen); ### x2 neg: "y2new to $x2new,$y2new" } elsif ($x2new >= $width) { $x2new = $width-1; $y2new = floor (0.5 + ($y2 * ($x1-$x2new) + $y1 * ($x2new-$x2)) / $xlen); ### x2 big: "y2new to $x2new,$y2new" } if ($y2new < 0) { $y2new = 0; $x2new = floor (0.5 + ($x2 * ($y1) + $x1 * (-$y2)) / $ylen); ### y2 neg: "x2new to $x2new,$y2new" } elsif ($y2new >= $height) { $y2new = $height-1; $x2new = floor (0.5 + ($x2 * ($y1-$y2new) + $x1 * ($y2new-$y2)) / $ylen); ### y2 big: "x2new $x2new,$y2new" } if ($x2new < 0 || $x2new >= $width) { ### x2new outside return; } return ($x1new,$y1new, $x2new,$y2new); } Math-PlanePath-129/tools/ar2w2-curve-table.pl0000644000175000017500000002763712161517106016566 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use List::Util 'min','max'; # uncomment this to run the ### lines #use Smart::Comments; sub min_maybe { return min(grep {defined} @_); } sub max_maybe { return max(grep {defined} @_); } my $table_total = 0; sub print_table { my ($name, $aref) = @_; $table_total += scalar(@$aref); print "my \@$name\n = ("; my $entry_width = max (map {defined $_ ? length : 0} @$aref); foreach my $i (0 .. $#$aref) { printf "%*s", $entry_width, $aref->[$i]//'undef'; if ($i == $#$aref) { print ");\n"; } else { print ","; if (($i % 16) == 15) { print "\n "; } elsif (($i % 4) == 3) { print " "; } } } } sub print_table12 { my ($name, $aref) = @_; $table_total += scalar(@$aref); print "my \@$name = ("; my $entry_width = max (map {length($_//'')} @$aref); foreach my $i (0 .. $#$aref) { printf "%*s", $entry_width, $aref->[$i]//'undef'; if ($i == $#$aref) { print ");\n"; } else { print ","; if (($i % 12) == 11) { my $state = ($i-11)/3; print " # 3* $state"; print "\n ".(" " x length($name)); } elsif (($i % 3) == 2) { print " "; } } } } sub make_state { my ($part, $rot, $rev) = @_; $rot %= 4; return 4*($rot + 4*($rev + 2*$part)); } my @part_name = ('A1','A2', 'B1','B2', 'C1','C2', 'D1','D2'); my @rev_name = ('','rev'); sub state_string { my ($state) = @_; my $digit = $state % 4; $state = int($state/4); my $rot = $state % 4; $state = int($state/4); my $rev = $state % 2; $state = int($state/2); my $part = $state; return "part=$part_name[$part]$rev_name[$rev] rot=$rot digit=$digit"; } my @next_state; my @digit_to_x; my @digit_to_y; my @yx_to_digit; my @min_digit; my @max_digit; use constant A1 => 0; use constant A2 => 1; use constant B1 => 2; use constant B2 => 3; use constant C1 => 4; use constant C2 => 5; use constant D1 => 6; use constant D2 => 7; foreach my $part (A1, A2, B1, B2, C1, C2, D1, D2) { foreach my $rot (0, 1, 2, 3) { foreach my $rev (0, 1) { my $state = make_state ($part, $rot, $rev); foreach my $orig_digit (0, 1, 2, 3) { my $digit = $orig_digit; if ($rev) { $digit = 3-$digit; } my $xo = 0; my $yo = 0; my $new_part = $part; my $new_rot = $rot; my $new_rev = $rev; if ($part == A1) { if ($digit == 0) { $new_part = D2; } elsif ($digit == 1) { $xo = 1; $new_part = B1; $new_rev ^= 1; $new_rot = $rot - 1; } elsif ($digit == 2) { $yo = 1; $new_part = C1; $new_rot = $rot + 1; } elsif ($digit == 3) { $xo = 1; $yo = 1; $new_part = B2; $new_rev ^= 1; $new_rot = $rot + 2; } } elsif ($part == A2) { if ($digit == 0) { $new_part = B1; $new_rev ^= 1; $new_rot = $rot - 1; } elsif ($digit == 1) { $yo = 1; $new_part = C2; } elsif ($digit == 2) { $xo = 1; $new_part = B2; $new_rev ^= 1; $new_rot = $rot + 2; } elsif ($digit == 3) { $xo = 1; $yo = 1; $new_part = D1; $new_rot = $rot + 1; } } elsif ($part == B1) { if ($digit == 0) { $new_part = D1; $new_rev ^= 1; $new_rot = $rot - 1; } elsif ($digit == 1) { $yo = 1; $new_part = C2; } elsif ($digit == 2) { $xo = 1; $yo = 1; $new_part = B1; } elsif ($digit == 3) { $xo = 1; $new_part = B2; $new_rev ^= 1; $new_rot = $rot + 1; } } elsif ($part == B2) { if ($digit == 0) { $new_part = B1; $new_rev ^= 1; $new_rot = $rot - 1; } elsif ($digit == 1) { $yo = 1; $new_part = B2; } elsif ($digit == 2) { $xo = 1; $yo = 1; $new_part = C1; } elsif ($digit == 3) { $xo = 1; $new_part = D2; $new_rev ^= 1; $new_rot = $rot + 1; } } elsif ($part == C1) { if ($digit == 0) { $new_part = A2; } elsif ($digit == 1) { $yo = 1; $new_part = B1; $new_rot = $rot + 1; } elsif ($digit == 2) { $xo = 1; $yo = 1; $new_part = A1; $new_rot = $rot - 1; } elsif ($digit == 3) { $xo = 1; $new_part = B2; $new_rev ^= 1; $new_rot = $rot + 1; } } elsif ($part == C2) { if ($digit == 0) { $new_part = B1; $new_rev ^= 1; $new_rot = $rot - 1; } elsif ($digit == 1) { $yo = 1; $new_part = A2; } elsif ($digit == 2) { $xo = 1; $yo = 1; $new_part = B2; $new_rot = $rot - 1; } elsif ($digit == 3) { $xo = 1; $new_part = A1; $new_rot = $rot - 1; } } elsif ($part == D1) { if ($digit == 0) { $new_part = D1; $new_rev ^= 1; $new_rot = $rot - 1; } elsif ($digit == 1) { $yo = 1; $new_part = A2; } elsif ($digit == 2) { $xo = 1; $yo = 1; $new_part = C2; $new_rot = $rot - 1; } elsif ($digit == 3) { $xo = 1; $new_part = A2; $new_rot = $rot - 1; } } elsif ($part == D2) { if ($digit == 0) { $new_part = A1; } elsif ($digit == 1) { $yo = 1; $new_part = C1; $new_rot = $rot + 1; } elsif ($digit == 2) { $xo = 1; $yo = 1; $new_part = A1; $new_rot = $rot - 1; } elsif ($digit == 3) { $xo = 1; $new_part = D2; $new_rev ^= 1; $new_rot = $rot + 1; } } else { die; } ### base: "$xo, $yo" if ($rot & 2) { $xo ^= 1; $yo ^= 1; } if ($rot & 1) { ($xo,$yo) = ($yo^1,$xo); } ### rot to: "$xo, $yo" $digit_to_x[$state+$orig_digit] = $xo; $digit_to_y[$state+$orig_digit] = $yo; $yx_to_digit[$state + $yo*2 + $xo] = $orig_digit; my $next_state = make_state ($new_part, $new_rot, $new_rev); $next_state[$state+$orig_digit] = $next_state; } foreach my $x1pos (0 .. 1) { foreach my $x2pos ($x1pos .. 1) { my $xr = ($x1pos ? 2 : $x2pos ? 1 : 0); ### $xr foreach my $y1pos (0 .. 1) { foreach my $y2pos ($y1pos .. 1) { my $yr = ($y1pos ? 6 : $y2pos ? 3 : 0); ### $yr my $min_digit = undef; my $max_digit = undef; foreach my $digit (0 .. 3) { my $x = $digit_to_x[$state+$digit]; my $y = $digit_to_y[$state+$digit]; next unless $x >= $x1pos; next unless $x <= $x2pos; next unless $y >= $y1pos; next unless $y <= $y2pos; $min_digit = min_maybe($digit,$min_digit); $max_digit = max_maybe($digit,$max_digit); } my $key = 3*$state + $xr + $yr; ### $key if (defined $min_digit[$key]) { die "oops min_digit[] already: state=$state key=$key y1p=$y1pos y2p=$y2pos value=$min_digit[$key], new=$min_digit"; } $min_digit[$key] = $min_digit; $max_digit[$key] = $max_digit; } } ### @min_digit } } } } } sub check_used { my @pending_state = @_; my $count = 0; my @seen_state; my $depth = 1; while (@pending_state) { my $state = pop @pending_state; $count++; ### consider state: $state foreach my $digit (0 .. 3) { my $next_state = $next_state[$state+$digit]; if (! $seen_state[$next_state]) { $seen_state[$next_state] = $depth; push @pending_state, $next_state; ### push: "$next_state depth $depth" } } $depth++; } for (my $state = 0; $state < @next_state; $state += 4) { if (! defined $seen_state[$state]) { $seen_state[$state] = 'none'; } my $str = state_string($state); print "# used state $state depth $seen_state[$state] $str\n"; } print "used state count $count\n"; } print_table ("next_state", \@next_state); print_table ("digit_to_x", \@digit_to_x); print_table ("digit_to_y", \@digit_to_y); print_table ("yx_to_digit", \@yx_to_digit); print_table12 ("min_digit", \@min_digit); print_table12 ("max_digit", \@max_digit); print "# state length ",scalar(@next_state)," in each of 4 tables\n"; print "# grand total $table_total\n"; print "\n"; { my %seen; my @pending; for (my $state = 0; $state < @next_state; $state += 4) { push @pending, $state; } while (@pending) { my $state = shift @pending; next if $seen{$state}++; next if $digit_to_x[$state] != 0 || $digit_to_y[$state] != 0; my $next = $next_state[$state]; if ($next_state[$next] == $state) { print "# cycle $state/$next ",state_string($state)," <-> ",state_string($next),"\n"; unshift @pending, $next; } } print "#\n"; } { my $a1 = make_state(A1,0,0); my $d2 = make_state(D2,0,0); my $d1rev = make_state(D1,3,1); my $a2rev = make_state(A2,2,1); my $b2 = make_state(B2,0,0); my $b1rev3 = make_state(B1,-1,1); my $b1rev = make_state(B1,0,1); my $b2_1 = make_state(B2,1,0); my $str = <<"HERE"; my %start_state = (A1 => [$a1, $d2], D2 => [$d2, $a1], B2 => [$b2, $b1rev3], B1rev => [$b1rev3, $b2], D1rev => [$d1rev, $a2rev], A2rev => [$a2rev, $d1rev], ); HERE print $str; my %start_state = eval "$str; %start_state"; foreach my $elem (values %start_state) { my ($s1, $s2) = @$elem; $next_state[$s1]==$s2 or die; $next_state[$s2]==$s1 or die; $digit_to_x[$s1]==0 or die "$s1 not at 0,0"; $digit_to_y[$s1]==0 or die; $digit_to_x[$s2]==0 or die; $digit_to_y[$s2]==0 or die; } } # print "# state A1=",make_state(A1,0,0),"\n"; # print "# state D2=",make_state(D2,0,0),"\n"; # print "# state D1=",make_state(D1,0,0),"\n"; # print "from A1/D2\n"; # check_used (make_state(A1,0,0), make_state(D2,0,0)); # print "from D1\n"; # check_used (make_state(D1,0,0)); { print "\n"; require Graph::Easy; my $g = Graph::Easy->new; for (my $state = 0; $state < scalar(@next_state); $state += 4) { my $next = $next_state[$state]; $g->add_edge("$state: ".state_string($state), "$next: ".state_string($next)); } print $g->as_ascii(); } exit 0; Math-PlanePath-129/tools/r5dragon-midpoint-offset.pl0000644000175000017500000000412213102216543020225 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2017 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use Math::PlanePath::R5DragonMidpoint; my $path = Math::PlanePath::R5DragonMidpoint->new (arms => 1); my @yx_to_digdxdy; foreach my $n (0 .. 5**10) { my ($x,$y) = $path->n_to_xy($n); my $digit = $n % 5; my $to_n = ($n-$digit)/5; my ($to_x,$to_y) = $path->n_to_xy($to_n); # (x+iy)*(1+2i) = x-2y + 2x+y ($to_x,$to_y) = ($to_x-2*$to_y, 2*$to_x+$to_y); my $dx = $to_x - $x; my $dy = $to_y - $y; my $k = 3*(10*($y%10) + ($x%10)); my $v0 = $digit; my $v1 = $dx; my $v2 = $dy; if (defined $yx_to_digdxdy[$k+0] && $yx_to_digdxdy[$k+0] != $v0) { die "diff v0 $yx_to_digdxdy[$k+0] $v0 k=$k n=$n"; } if (defined $yx_to_digdxdy[$k+1] && $yx_to_digdxdy[$k+1] != $v1) { die "diff v1 $yx_to_digdxdy[$k+1] $v1 k=$k n=$n"; } if (defined $yx_to_digdxdy[$k+2] && $yx_to_digdxdy[$k+2] != $v2) { die "diff v2 $yx_to_digdxdy[$k+2] $v2 k=$k n=$n"; } $yx_to_digdxdy[$k+0] = $v0; $yx_to_digdxdy[$k+1] = $v1; $yx_to_digdxdy[$k+2] = $v2; } print_table(\@yx_to_digdxdy); sub print_table { my ($aref) = @_; print "("; for (my $i = 0; $i < @$aref; ) { my $v0 = $aref->[$i++] // 'undef'; my $v1 = $aref->[$i++] // 'undef'; my $v2 = $aref->[$i++] // 'undef'; my $str = "$v0,$v1,$v2"; if ($i != $#$aref) { $str .= ", " } printf "%-9s", $str; if (($i % (3*5)) == 0) { print "\n " } } print ");\n"; } exit 0; Math-PlanePath-129/tools/wunderlich-meander-table.pl0000644000175000017500000001553511660132465020262 0ustar gggg#!/usr/bin/perl -w # Copyright 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min','max'; # uncomment this to run the ### lines #use Smart::Comments; sub print_table { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {defined && length} @$aref); foreach my $i (0 .. $#$aref) { printf "%*d", $entry_width, $aref->[$i]; if ($i == $#$aref) { print "); # ",$i-8,"\n"; } else { print ","; if (($i % 9) == 8) { print " # ".($i-8); } if (($i % 9) == 8) { print "\n ".(" " x length($name)); } elsif (($i % 3) == 2) { print " "; } } } } sub print_table36 { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {defined && length} @$aref); foreach my $i (0 .. $#$aref) { printf "%*d", $entry_width, $aref->[$i]; if ($i == $#$aref) { print ");\n"; } else { print ","; if (($i % 36) == 5) { print " # ".($i-5); } if (($i % 6) == 5) { print "\n ".(" " x length($name)); } elsif (($i % 6) == 5) { print " "; } } } } sub make_state { my ($transpose, $rot) = @_; $transpose %= 2; $rot %= 4; ($rot % 2) == 0 or die; $rot /= 2; return 9*($rot + 2*$transpose); } # x__ 0 # xx_ 1 # xxx 2 # _xx 3 # __x 4 # _x_ 5 my @r_to_cover = ([1,0,0], [1,1,0], [1,1,1], [0,1,1], [0,0,1], [0,1,0]); my @reverse_range = (4,3,2,1,0,5); my @next_state; my @digit_to_x; my @digit_to_y; my @xy_to_digit; my @min_digit; my @max_digit; # 8 5-- 4 # | | | # 7-- 6 3 # | # 0-- 1-- 2 # foreach my $transpose (0, 1) { foreach my $rot (0, 2) { my $state = make_state ($transpose, $rot); foreach my $orig_digit (0 .. 8) { my $digit = $orig_digit; my $xo; my $yo; my $new_rot = $rot; my $new_transpose = $transpose; if ($digit == 0) { $xo = 0; $yo = 0; $new_transpose ^= 1; } elsif ($digit == 1) { $xo = 1; $yo = 0; $new_transpose ^= 1; } elsif ($digit == 2) { $xo = 2; $yo = 0; } elsif ($digit == 3) { $xo = 2; $yo = 1; } elsif ($digit == 4) { $xo = 2; $yo = 2; } elsif ($digit == 5) { $xo = 1; $yo = 2; $new_rot = $rot + 2; } elsif ($digit == 6) { $xo = 1; $yo = 1; $new_transpose ^= 1; $new_rot = $rot + 2; } elsif ($digit == 7) { $xo = 0; $yo = 1; $new_transpose ^= 1; $new_rot = $rot + 2; } elsif ($digit == 8) { $xo = 0; $yo = 2; } else { die; } ### base: "$xo, $yo" if ($transpose) { ($xo,$yo) = ($yo,$xo); } if ($rot & 2) { $xo = 2 - $xo; $yo = 2 - $yo; } if ($rot & 1) { ($xo,$yo) = (2-$yo,$xo); } ### rot to: "$xo, $yo" $digit_to_x[$state+$orig_digit] = $xo; $digit_to_y[$state+$orig_digit] = $yo; $xy_to_digit[$state + 3*$xo + $yo] = $orig_digit; my $next_state = make_state ($new_transpose, $new_rot); $next_state[$state+$orig_digit] = $next_state; } foreach my $xrange (0 .. 5) { foreach my $yrange (0 .. 5) { my $xr = $xrange; my $yr = $yrange; my $bits = $xr + 6*$yr; # before transpose etc my $key = 4*$state + $bits; ### assert: (4*$state % 36) == 0 if ($rot & 1) { ($xr,$yr) = ($yr,$reverse_range[$xr]); } if ($rot & 2) { $xr = $reverse_range[$xr]; $yr = $reverse_range[$yr]; } if ($transpose) { ($xr,$yr) = ($yr,$xr); } # now xr,yr plain unrotated etc my $min_digit = 8; my $max_digit = 0; foreach my $digit (0 .. 8) { my $x = $digit_to_x[$digit]; my $y = $digit_to_y[$digit]; next unless $r_to_cover[$xr]->[$x]; next unless $r_to_cover[$yr]->[$y]; $min_digit = min($digit,$min_digit); $max_digit = max($digit,$max_digit); } ### min/max: "state=$state 4*state=".(4*$state)." bits=$bits key=$key" if (defined $min_digit[$key]) { die "oops min_digit[] already: state=$state bits=$bits value=$min_digit[$state+$bits], new=$min_digit"; } $min_digit[$key] = $min_digit; $max_digit[$key] = $max_digit; } } ### @min_digit } } print_table ("next_state", \@next_state); print_table ("digit_to_x", \@digit_to_x); print_table ("digit_to_y", \@digit_to_y); print_table ("xy_to_digit", \@xy_to_digit); print_table36 ("min_digit", \@min_digit); print_table36 ("max_digit", \@max_digit); print "# transpose state ",make_state(1,0),"\n"; print "# state length ",scalar(@next_state)," in each of 4 tables\n"; print "# min/max length ",scalar(@min_digit)," in each of 2 tables\n\n"; ### @next_state ### @digit_to_x ### @digit_to_y ### @xy_to_digit ### next_state length: scalar(@next_state) { my @pending_state = (0); my $count = 0; my @seen_state; my $depth = 1; $seen_state[0] = $depth; while (@pending_state) { my $state = pop @pending_state; $count++; ### consider state: $state foreach my $digit (0 .. 8) { my $next_state = $next_state[$state+$digit]; if (! $seen_state[$next_state]) { $seen_state[$next_state] = $depth; push @pending_state, $next_state; ### push: "$next_state depth $depth" } } $depth++; } for (my $state = 0; $state < @next_state; $state += 9) { print "# used state $state depth ".($seen_state[$state]||0)."\n"; } print "used state count $count\n"; } print "\n"; exit 0; Math-PlanePath-129/Changes0000644000175000017500000004063214001421040013170 0ustar ggggCopyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . Version 129, January 2021 - new CornerAlternating - t/number-fraction.t avoid trouble with Number::Fraction 3.0.3 Version 128, September 2020 - new PeanoDiagonals Version 127, August 2019 - Base-Digits.t more careful of UV overflow - KochSnowflakes.t beware rounding - PlanePathTurn new turn_type "NotStraight" Version 126, March 2018 - new AlternateTerdragon Version 125, December 2017 - GosperReplicate, QuintetReplicate new numbering_type "rotate" - SquareReplicate new numbering_type "rotate-4","rotate-8" Version 124, January 2017 - new n_to_n_list() Version 123, April 2016 - bigfloat.t compare results with ==, needed by BigFloat 1.999720, as reported by Petr Pisar RT#114014 - MultipleRings fix BigFloat output on BigRat input Version 122, January 2016 - tests fix sloppy condition exposed by recent Math::BigFloat Version 121, September 2015 - new methods xyxy_to_n_list(), xyxy_to_n_list_either(), turn_any_left(), turn_any_right(), turn_any_straight() Version 120, August 2015 - new HilbertSides - PlanePathTurn new turn_type "Straight" Version 119, May 2015 - fixes to most n_to_level() - Math::PlanePath::Base::Digits new round_up_pow() Version 118, February 2015 - new methods xyxy_to_n(), xyxy_to_n_either() - DekkingCurve new "arms" parameter, correction to level N range Version 117, September 2014 - new methods n_to_level(), level_to_n_range() - UlamWarburton,UlamWarburtonQuarter parameter parts=>octant,octant_up Version 116, June 2014 - new WythoffPreliminaryTriangle - new methods is_tree(), x_negative_at_n(), y_negative_at_n() Version 115, March 2014 - CoprimeColumns new parameter direction=down - MPeaks new parameter n_start - Math::PlanePath::Base::Generic new parameter_info_nstart0() Version 114, February 2014 - PlanePathDelta new delta_type=>"dRadius","dRSquared" - CCurve xy_to_n() by division instead of search Version 113, December 2013 - PythagoreanTree new tree_type="UArD", digit_order="LtoH" - PlanePathCoord new coordinate_type "MinAbs","MaxAbs" Version 112, December 2013 - PythagoreanTree new tree_type="UMT" Version 111, November 2013 - FactorRationals new factor_coding "odd/even","negabinary","revbinary" - new sumabsxy_minimum(), sumabsxy_maximum(), absdiffxy_minimum(), absdiffxy_maximum() Version 110, August 2013 - PlanePathTurn new turn_type "SLR","SRL" Version 109, August 2013 - TerdragonCurve correction to dx_minimum() - TerdragonMidpoint correction to dx_maximum() Version 108, July 2013 - new tree_n_to_subheight() - PlanePathCoord new coordinate_type "SubHeight" - tests skip some 64-bit perl 5.6.2 dodginess in "%" operator Version 107, July 2013 - PentSpiral,PentSpiralSkewed,HeptSpiralSkewed,OctagramSpiral, Staircase,StaircaseAlternating new parameter n_start - FilledRings fix parameter_info_array() missing n_start - StaircaseAlternating fix parameter_info_array() missing end_type Version 106, June 2013 - new methods tree_n_root(), tree_num_roots(), tree_root_n_list(), tree_depth_to_n_range(), tree_depth_to_width(), tree_num_children_list(), dsumxy_minimum(),dsumxy_maximum(), ddiffxy_minimum(),ddiffxy_maximum() - PyramidSpiral new parameter n_start - PlanePathCoord new coordinate_type "RootN" Version 105, June 2013 - PlanePathCoord new coordinate_type "NumSiblings" Version 104, May 2013 - new method n_to_radius() Version 103, May 2013 - UlamWarburton new parts=2,1 - PythagoreanTree new coordinates="SM","SC","MC" Version 102, April 2013 - new sumxy_minimum(),sumxy_maximum(), diffxy_minimum(),diffxy_maximum(), - PlanePathDelta new delta_type=>"dSumAbs" Version 101, April 2013 - MultipleRings fixes for ring_shape=polygon xy_to_n(), rect_to_n_range() - CellularRule,CellularRule54,CellularRule57,CellularRule190 new parameter n_start - DiagonalRationals new parameter direction=up Version 100, March 2013 - new absdx_minimum(),absdx_maximum(), absdy_minimum(),absdy_maximum(), dir_minimum_dxdy(),dir_maximum_dxdy() - AztecDiamondRings new parameter n_start - TriangleSpiralSkewed new parameter skew=right,up,down - WythoffArray new parameters x_start,y_start - PlanePathDelta new delta_type=>"dAbsDiff" Version 99, February 2013 - oops, correction to IntXY on negatives Version 98, February 2013 - CoprimeColumns,DiagonalRationals,DivisibleColumns new n_start parameter - PlanePathCoord new coordinate_type "IntXY" Version 97, January 2013 - new tree_num_children_minimum(), tree_num_children_maximum() Version 96, January 2013 - AnvilSpiral,HexSpiral,HexSpiralSkewed new n_start, which was in parameter_info but did nothing - FilledRings new n_start parameter Version 95, December 2012 - new tree_any_leaf() - PythagoreanTree new coordinates="AC" and "BC" Version 94, December 2012 - new rsquared_minimum(), rsquared_maximum() - PlanePathCoord new coordinate_type "IsLeaf","IsNonLeaf" - ImaginaryHalf new option "digit_order" - Math::PlanePath::Base::Generic new parameter_info_nstart1() Version 93, November 2012 - new xy_is_visited() - PlanePathCoord new coordinate_type "Min","Max","BitAnd","BitOr","BitXor" Version 92, October 2012 - new x_minimum(),x_maximum(), y_minimum(),y_maximum(), dx_minimum(),dx_maximum(), dy_minimum(),dy_maximum() Version 91, October 2012 - new tree_depth_to_n(), tree_depth_to_n_end() - RationalsTree new tree_type "HCS" - UlamWarburton,UlamWarburtonQuarter new "n_start" parameter - PlanePathN new line_type=>"Depth_start","Depth_end" - Math::PlanePath::Base::Digits new bit_split_lowtohigh() Version 90, October 2012 - new CfracDigits, ChanTree - tree_n_num_children() return undef when no such N - Diagonals new x_start,y_start parameters - PlanePathCoord new coordinate_type "GCD" Version 89, September 2012 - RationalsTree new tree_type=L Version 88, September 2012 - new DekkingCurve, DekkingCentres - new tree_n_to_depth() - PlanePathCoord new coordinate_type "Depth" - DiamondSpiral new "n_start" parameter Version 87, August 2012 - new tree_n_num_children() - PlanePathCoord new coordinate_type "NumChildren" - SierpinskiArrowhead,SierpinskiArrowheadCentres new parameter align=right,left,diagonal - Rows,Columns new "n_start" parameter - KnightSpiral,PentSpiral,SierpinskiCurve fixes for n_to_xy() on some fractional N Version 86, August 2012 - Diagonals,DiagonalsOctant,DiagonalsAlternating,PyramidRows,PyramidSides, Corner new "n_start" parameter Version 85, August 2012 - SquareSpiral new "n_start" parameter - PlanePathDelta new delta_type=>"AbsdX","AbsdY" Version 84, August 2012 - PyramidRows new "align" parameter Version 83, July 2012 - new n_to_dxdy() - SierpinskiTriangle new parameter align=right,left,diagonal - SierpinskiTriangle,TriangleSpiral,TriangleSpiralSkewed,Hypot new "n_start" parameter - PlanePathDelta new delta_type=>"dDiffYX" - PlanePathN new line_type=>"Diagonal_NW","Diagonal_SW","Diagonal_SE" - Math::PlanePath::Base::Digits new digit_join_lowtohigh() - new Math::PlanePath::Base::Generic round_nearest() Version 82, July 2012 - new tree_n_children(), tree_n_parent() - PlanePathDelta new delta_type=>"dDiffXY" - ImaginaryBase,ImaginaryHalf rect_to_n_range() exact - new Math::PlanePath::Base::Digits round_down_pow(), digit_split_lowtohigh(), parameter_info_array(), parameter_info_radix2() Version 81, July 2012 - TriangularHypot new points=hex,hex_rotated,hex_centred Version 80, July 2012 - new AlternatePaperMidpoint - AlternatePaper new "arms" - GreekKeySpiral new "turns" - ComplexPlus, Flowsnake, FlowsnakeCentres, TerdragonMidpoint, TerdragonRounded, R5DragonMidpoint fix for arms>1 fractional N Version 79, June 2012 - TriangularHypot new option points=odd,even Version 78, June 2012 - new WythoffArray, PowerArray - GcdRationals new option pairs_order - Hypot,HypotOctant new option points=odd,even - Diagonals new options direction=up,down Version 77, June 2012 - new DiagonalsOctant Version 76, May 2012 - tests allow for as_float() only in recent Math::BigRat Version 75, May 2012 - new CubicBase, CCurve, R5DragonCurve, R5DragonMidpoint, TerdragonRounded - MultipleRings new ring_shape=>"polygon" - PlanePathDelta new delta_type=>"dSum" - fix TheodorusSpiral n_to_rsquared() on fractional N Version 74, May 2012 - new ImaginaryBase - new method n_to_rsquared() - PlanePathN new line_type X_neg,Y_neg - fix ImaginaryBase xy_to_n() possible infloop on floating point rounding - fix TerdragonMidpoint xy_to_n() undef on points outside requested arms Version 73, April 2012 - new GrayCode, SierpinskiCurveStair, WunderlichSerpentine - fix GcdRationals xy_to_n() on BigInt - PlanePathCoord new coordinate_type "SumAbs","TRadius","TRSquared" Version 72, March 2012 - PlanePathTurn new turn_type "Right" Version 71, February 2012 - new FilledRings - misc fixes for Math::NumSeq::PlanePathCoord etc values_min etc Version 70, February 2012 - TheodorusSpiral fix n_to_xy() position saving - StaircaseAlternating new end_type=>"square" Version 69, February 2012 - new Math::NumSeq::PlanePathTurn - Math::NumSeq::PlanePathN new pred() Version 68, February 2012 - new xy_to_n_list() - new CretanLabyrinth Version 67, February 2012 - oops, DragonMidpoint,DragonRounded xy_to_n() exclude points on the arm one past what was requested - new CellularRule57 Version 66, February 2012 - new TerdragonMidpoint - DragonCurve,DragonMidpoint,DragonRounded,TerdragonCurve faster xy_to_n() Version 65, January 2012 - new parameter_info_hash(), n_frac_discontinuity() Version 64, January 2012 - new AnvilSpiral, AlternatePaper, ComplexPlus, TerdragonCurve Version 63, January 2012 - new class_x_negative() and class_y_negative() methods - new CellularRule, ComplexRevolving, Math::NumSeq::PlanePathN - Math::NumSeq::PlanePathCoord etc new planepath_object option Version 62, December 2011 - new FractionsTree Version 61, December 2011 - new FactorRationals Version 60, December 2011 - new GcdRationals Version 59, December 2011 - new AR2W2Curve Version 58, December 2011 - new DiagonalRationals, StaircaseAlternating, Math::NumSeq::PlanePathDelta Version 57, December 2011 - new HilbertSpiral - LTiling new L_fill "left" and "upper" Version 56, December 2011 - new CincoCurve, DiagonalsAlternating, LTiling Version 55, November 2011 - new KochelCurve, MPeaks - Flowsnake,QuintetCurve faster xy_to_n() Version 54, November 2011 - new WunderlichMeander - PlanePathCoord new coordinate_type "Product","DiffXY","DiffYX","AbsDiff" - BetaOmega,CellularRule190 exact rect_to_n_range() Version 53, November 2011 - new FibonacciWordFractal, Math::NumSeq::PlanePathCoord Version 52, November 2011 - new BetaOmega, CornerReplicate, DigitGroups, HIndexing Version 51, October 2011 - new CellularRule190 Version 50, October 2011 - DragonRounded fix xy_to_n() with arms=2,3,4 on innermost XY=0,1 - SierpinskiCurve fixes for rect_to_n_range() Version 49, October 2011 - new AztecDiamondRings, DivisibleColumns, SierpinskiCurve, UlamWarburtonQuarter - SierpinskiArrowheadCentres fix for n_to_xy() on fractional $n Version 48, October 2011 - new UlamWarburton Version 47, October 2011 - new SquareReplicate Version 46, September 2011 - new GosperReplicate Version 45, September 2011 - new QuintetCurve, QuintetCentres, QuintetReplicate Version 44, September 2011 - new ComplexMinus - RationalsTree new tree_type=Drib - Corner new wider parameter Version 43, September 2011 - new KochSquareflakes, RationalsTree - new parameter_info_array(), parameter_info_list() Version 42, September 2011 - new SierpinskiArrowheadCentres, SierpinskiTriangle Version 41, August 2011 - new QuadricCurve, QuadricIslands, ImaginaryBase Version 40, August 2011 - new DragonRounded, CellularRule54 - new arms_count() method - Flowsnake, FlowsnakeCentres new "arms" parameter Version 39, August 2011 - new DragonCurve, DragonMidpoint Version 38, August 2011 - new Flowsnake, FlowsnakeCentres Version 37, July 2011 - new SquareArms, DiamondArms, File Version 36, July 2011 - new HexArms - PeanoCurve new radix parameter Version 35, July 2011 - new GosperSide - fixes for experimental BigFloat support Version 34, July 2011 - ZOrderCurve new radix parameter Version 33, July 2011 - new GosperIslands Version 32, June 2011 - new SierpinskiArrowhead, CoprimeColumns Version 31, June 2011 - KochCurve fix for fractional N Version 31, June 2011 - PythagoreanTree avoid dubious hypot() on darwin 8.11.0 Version 30, May 2011 - new TriangularHypot, KochCurve, KochPeaks, KochSnowflakes Version 29, May 2011 - GreekKeySpiral rect_to_n_range() tighter $n_lo - tests more diagnostics on PythagoreanTree Version 28, May 2011 - PixelRings xy_to_n() fix some X==Y points should be undef Version 27, May 2011 - new GreekKeySpiral Version 26, May 2011 - new PythagoreanTree - Rows,Columns more care against width<=0 or height<=0 Version 25, May 2011 - tests fix neg zero for long double NV Version 24, May 2011 - tests fix OEIS file comparisons - MultipleRings xy_to_n() fix for x=-0,y=0 Version 23, April 2011 - new ArchimedeanChords - TheodorusSpiral rect_to_n_range() tighter $n_lo Version 22, March 2011 - new n_start() method - SacksSpiral rect_to_n_range() include N=0 Version 21, February 2011 - new Hypot, HypotOctant, OctagramSpiral - TheodorusSpiral, VogelFloret allow for xy_to_n() result bigger than IV (though that big is probably extremely slow) Version 20, February 2011 - fix Makefile.PL for perl 5.6.0 - tests avoid stringized "-0" from perl 5.6.x Version 19, January 2011 - new PixelRings Version 18, January 2011 - avoid some 5.12 warnings on infs Version 17, January 2011 - avoid some inf loops and div by zeros for n=infinity or x,y=infinity (handling of infinity is unspecified, but at least don't hang) - PyramidRows, PyramidSides exact rect_to_n_range() Version 16, January 2011 - new PeanoCurve, Staircase Version 15, January 2011 - MultipleRings fix xy_to_n() and rect_to_n_range() at 0,0 - Corners,Diagonals,MultipleRings tighter rect_to_n_range() Version 14, December 2010 - HilbertCurve exact rect_to_n_range() Version 13, December 2010 - new HilbertCurve, ZOrderCurve Version 12, October 2010 - oops, VogelFloret botched rect_to_n_range() Version 11, October 2010 - VogelFloret new rotation and radius parameters - SacksSpiral,VogelFloret tighter rect_to_n_range() when away from origin Version 10, October 2010 - fix MultipleRings xy_to_n() Version 9, September 2010 - HexSpiral and HexSpiralSkewed new "wider" parameter Version 8, September 2010 - tests fix stray 5.010 should be just 5.004 Version 7, August 2010 - new MultipleRings - VogelFloret xy_to_n() fix for positions away from exact N - Rows, Columns rect_to_n_range() tighter Version 6, August 2010 - new TheodorusSpiral Version 5, July 2010 - SquareSpiral new "wider" parameter Version 4, July 2010 - new PentSpiral, HeptSpiralSkewed - PyramidRows "step" parameter Version 3, July 2010 - new PyramidSpiral, TriangleSpiral, TriangleSpiralSkewed, PentSpiralSkewed Version 2, July 2010 - in Diagonals don't negative sqrt() if n=0 Version 1, July 2010 - the first version Math-PlanePath-129/xtools/0002755000175000017500000000000014001441522013231 5ustar ggggMath-PlanePath-129/xtools/my-wunused.sh0000755000175000017500000000313013433430641015710 0ustar gggg#!/bin/sh # my-wunused.sh -- run warnings::unused on dist files # Copyright 2009, 2010, 2011, 2012, 2013, 2015, 2019 Kevin Ryde # my-wunused.sh is shared by several distributions. # # my-wunused.sh 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, or (at your option) any later # version. # # my-wunused.sh 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 file. If not, see . set -e set -x EXE_FILES=`sed -n 's/^EXE_FILES = \(.*\)/\1/p' Makefile` TO_INST_PM=`find lib -name \*.pm` LINT_FILES="Makefile.PL $EXE_FILES $TO_INST_PM" if test -e "t/*.t"; then LINT_FILES="$LINT_FILES t/*.t" fi if test -e "xt/*.t"; then LINT_FILES="$LINT_FILES xt/*.t" fi for i in t xt examples devel; do if test -e "$i/*.pl"; then LINT_FILES="$LINT_FILES $i/*.pl" fi if test -e "$i/*.pm"; then LINT_FILES="$LINT_FILES $i/*.pm" fi done echo "$LINT_FILES" for i in $LINT_FILES; do # warnings::unused broken by perl 5.14, so use 5.10 for checks perl -I /usr/share/perl5 -Mwarnings::unused=-global -I lib -c $i # # full path name or else the "require" looks through @INC # echo "\"$i\"" # perl -e 'use Test::More tests=>1; use Test::Vars; Test::Vars::vars_ok($ARGV[0])' "`pwd`/$i" done Math-PlanePath-129/xtools/my-diff-prev.sh0000755000175000017500000000300611776230514016107 0ustar gggg#!/bin/sh # my-diff-prev.sh -- diff against previous version # Copyright 2009, 2010, 2011, 2012 Kevin Ryde # my-diff-prev.sh is shared by several distributions. # # my-diff-prev.sh 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, or (at your option) any later # version. # # my-diff-prev.sh 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 file. If not, see . set -e set -x DISTNAME=`sed -n 's/^DISTNAME = \(.*\)/\1/p' Makefile` if test -z "$DISTNAME"; then echo "DISTNAME not found" exit 1 fi VERSION=`sed -n 's/^VERSION = \(.*\)/\1/p' Makefile` if test -z "$VERSION"; then echo "VERSION not found" exit 1 fi case $VERSION in 3.*) PREV_VERSION=3.018000 ;; 1.*) PREV_VERSION=1.16 ;; *) PREV_VERSION="`expr $VERSION - 1`" ;; esac if test -z "$VERSION"; then echo "PREV_VERSION not established" exit 1 fi rm -rf diff.tmp mkdir -p diff.tmp (cd diff.tmp; tar xfz ../$DISTNAME-$PREV_VERSION.tar.gz tar xfz ../$DISTNAME-$VERSION.tar.gz diff -ur $DISTNAME-$PREV_VERSION \ $DISTNAME-$VERSION \ >tree.diff || true ) ${PAGER:-more} diff.tmp/tree.diff || true rm -rf diff.tmp exit 0 Math-PlanePath-129/xtools/my-tags.sh0000644000175000017500000000200311714065142015147 0ustar gggg#!/bin/sh # my-tags.sh -- make tags # Copyright 2009, 2010, 2011, 2012 Kevin Ryde # my-tags.sh is shared by several distributions. # # my-tags.sh 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, or (at your option) any later # version. # # my-tags.sh 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 file. If not, see . set -e set -x # in a hash-style multi-const this "use constant" pattern only picks up the # first constant, unfortunately, but it's better than nothing etags \ --regex='{perl}/use[ \t]+constant\(::defer\)?[ \t]+\({[ \t]*\)?\([A-Za-z_][^ \t=,;]+\)/\3/' \ `find lib -type f` Math-PlanePath-129/xtools/my-check-spelling.sh0000755000175000017500000000401613734320307017113 0ustar gggg#!/bin/sh # my-check-spelling.sh -- grep for spelling errors # Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # my-check-spelling.sh is shared by several distributions. # # my-check-spelling.sh 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, or (at your option) any later # version. # # my-check-spelling.sh 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 file. If not, see . set -e # set -x # | tee /dev/stdout # -name samp -prune \ # -o -name formats -prune \ # -o -name "*~" -prune \ # -o -name "*.tar.gz" -prune \ # -o -name "*.deb" -prune \ # -o # -o -name dist-deb -prune \ # | egrep -v '(Makefile|dist-deb)' \ # --colour=always if find . -name my-check-spelling.sh -prune \ -o -type f -print0 \ | xargs -0 egrep -nHi 'Hausdorf\b\bwich\b|simlar|roughtly|randomes|silbing|minmal|wiht|\bits the\b|\bint he\b|withtout|occured|exmaple|weiner|rigth|peroid|Manhatten|occuring|optino|recurrance|nineth|\bon on\b|\bto to\b|tranpose|adjustement|glpyh|rectanglar|availabe|grabing|cusor|refering|writeable|nineth|\bommitt?ed|omited|[$][rd]elf|requrie|noticable|continous|existant|explict|agument|destionation|\bthe the\b|\bfor for\b|\bare have\b|\bare are\b|\bwith with\b|\bin in\b|\b[tw]hen then\b|\bnote sure\b|\bnote yet\b|correspondance|sprial|wholely|satisif|\bteh\b|\btje\b|\btained\b|zip.com.au' then echo '(word)' exit 1 fi if find . -name my-check-spelling.sh -prune \ -o -name \*.gz -prune \ -o -type f -print0 \ | xargs -0 egrep -nH '\bov\b|\bTH[a-ce-z]' then echo '(ov or TH)' exit 1 fi exit 0 Math-PlanePath-129/xtools/my-kwalitee.sh0000755000175000017500000000214611775434756016053 0ustar gggg#!/bin/sh # my-kwalitee.sh -- run cpants_lint kwalitee checker # Copyright 2009, 2010, 2011, 2012 Kevin Ryde # my-kwalitee.sh is shared by several distributions. # # my-kwalitee.sh 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, or (at your option) any later # version. # # my-kwalitee.sh 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 file. If not, see . # Module::CPANTS::Analyse set -e set -x DISTVNAME=`sed -n 's/^DISTVNAME = \(.*\)/\1/p' Makefile` if test -z "$DISTVNAME"; then echo "DISTVNAME not found" exit 1 fi if [ -e ~/bin/my-gpg-agent-daemon ]; then eval `my-gpg-agent-daemon` echo "gpg-agent $GPG_AGENT_INFO" fi TGZ="$DISTVNAME.tar.gz" make "$TGZ" cpants_lint "$TGZ" Math-PlanePath-129/xtools/my-pc.sh0000755000175000017500000000324312666236402014633 0ustar gggg#!/bin/sh # my-pc.sh -- run cpants_lint kwalitee checker # Copyright 2009, 2010, 2011, 2012, 2013, 2016 Kevin Ryde # my-pc.sh is shared by several distributions. # # my-pc.sh 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, or (at your # option) any later version. # # my-pc.sh 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 file. If not, see . set -x # PERLRUNINST=`sed -n 's/^PERLRUNINST = \(.*\)/\1/p' Makefile` # if test -z "$PERLRUNINST"; then # echo "PERLRUNINST not found" # exit 1 # fi EXE_FILES=`sed -n 's/^EXE_FILES = \(.*\)/\1/p' Makefile` TO_INST_PM=`find lib -name \*.pm` LINT_FILES="Makefile.PL $EXE_FILES $TO_INST_PM" if test -e "t/*.t"; then LINT_FILES="$LINT_FILES t/*.t" fi if test -e "xt/*.t"; then LINT_FILES="$LINT_FILES xt/*.t" fi for i in t xt examples devel; do if test -e "$i/*.pl"; then LINT_FILES="$LINT_FILES $i/*.pl" fi if test -e "$i/*.pm"; then LINT_FILES="$LINT_FILES $i/*.pm" fi done # perl -e 'use Test::Vars; all_vars_ok()' # MyMakeMakerExtras_Pod_Coverage perl -e 'use Pod::Coverage package => $class' podlinkcheck -I lib `ls $LINT_FILES | grep -v '\.bash$$|\.desktop$$\.png$$|\.xpm$$'` podchecker -nowarnings `ls $LINT_FILES | grep -v '\.bash$$|\.desktop$$\.png$$|\.xpm$$'` perlcritic $LINT_FILES Math-PlanePath-129/xtools/my-deb.sh0000755000175000017500000001110713606474247014767 0ustar gggg#!/bin/sh # my-deb.sh -- make .deb # Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2017, 2018, 2019, 2020 Kevin Ryde # my-deb.sh is shared by several distributions. # # my-deb.sh 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, or (at your option) any later # version. # # my-deb.sh 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 file. If not, see . # warnings::unused broken by perl 5.14, so use 5.10 for checks set -e set -x DISTNAME=`sed -n 's/^DISTNAME = \(.*\)/\1/p' Makefile` if test -z "$DISTNAME"; then echo "DISTNAME not found" exit 1 fi echo "DISTNAME $DISTNAME" VERSION=`sed -n 's/^VERSION = \(.*\)/\1/p' Makefile` if test -z "$VERSION"; then echo "VERSION not found" exit 1 fi echo "VERSION $VERSION" DISTVNAME=`sed -n 's/^DISTVNAME = \(.*\)/\1/p' Makefile` if test -z "$DISTVNAME"; then echo "DISTVNAME not found" exit 1 fi DISTVNAME=`echo "$DISTVNAME" | sed "s/[$][(]VERSION[)]/$VERSION/"` DISTVNAME=`echo "$DISTVNAME" | sed "s/[$][(]DISTNAME[)]/$DISTNAME/"` echo "DISTVNAME $DISTVNAME" XS_FILES=`sed -n 's/^XS_FILES = \(.*\)/\1/p' Makefile` EXE_FILES=`sed -n 's/^EXE_FILES = \(.*\)/\1/p' Makefile` if test "$DISTNAME" = pngtextadd -o "$DISTNAME" = x2gpm then DPKG_ARCH=`dpkg --print-architecture` elif test -n "$XS_FILES" then DPKG_ARCH=`dpkg --print-architecture` else DPKG_ARCH=all fi echo "DPKG_ARCH $DPKG_ARCH" # programs named after the dist, libraries named with "lib" # gtk2-ex-splash and wx-perl-podbrowser programs are lib too though DEBNAME=`echo $DISTNAME | tr A-Z a-z` DEBNAME=`echo $DEBNAME | sed 's/app-//'` IS_PERL_LIB=0 if test -f Makefile.PL; then # Perl modules IS_PERL_LIB=1 fi if test -n "$EXE_FILES"; then # Perl programs IS_PERL_LIB=0 fi case $DISTNAME in # these have EXE_FILES programs but still named lib...-perl Gtk2-Ex-Splash|Wx-Perl-PodBrowser) IS_PERL_LIB=1 ;; esac if test $IS_PERL_LIB = 1; then DEBNAME="lib${DEBNAME}-perl" fi echo "DEBNAME $DEBNAME" DEBVNAME="${DEBNAME}_$VERSION-0.1" DEBFILE="${DEBVNAME}_$DPKG_ARCH.deb" echo "DEBVNAME $DEBVNAME" echo "DEBFILE $DEBFILE" # ExtUtils::MakeMaker 6.42 of perl 5.10.0 makes "$(DISTVNAME).tar.gz" depend # on "$(DISTVNAME)" distdir directory, which is always non-existent after a # successful dist build, so the .tar.gz is always rebuilt. # # So although the .deb depends on the .tar.gz don't express that here or it # rebuilds the .tar.gz every time. # # The right rule for the .tar.gz would be to depend on the files which go # into it of course ... # # DISPLAY is unset for making a deb since under fakeroot gtk stuff may try # to read config files like ~/.pangorc from root's home dir /root/.pangorc, # and that dir will be unreadable by ordinary users (normally), provoking # warnings and possible failures from nowarnings(). # test -f $DISTVNAME.tar.gz || make $DISTVNAME.tar.gz debver="`dpkg-parsechangelog -c1 | sed -n -r -e 's/^Version: (.*)-[0-9.]+$/\1/p'`" echo "debver $debver", want $VERSION test "$debver" = "$VERSION" rm -rf $DISTVNAME tar xfz $DISTVNAME.tar.gz unset DISPLAY; export DISPLAY cd $DISTVNAME if test -d examples; then if ! grep _examples debian/rules; then echo "examples directory not in debian/rules" fi fi dpkg-checkbuilddeps debian/control fakeroot debian/rules binary cd .. rm -rf $DISTVNAME #------------------------------------------------------------------------------ # source .dsc cp $DISTVNAME.tar.gz ${DEBNAME}_$VERSION.orig.tar.gz tar xfz ${DEBNAME}_$VERSION.orig.tar.gz if test "$DISTVNAME" != "$DEBNAME-$VERSION"; then mv -T $DISTVNAME $DEBNAME-$VERSION fi dpkg-source -b $DEBNAME-$VERSION ${DEBNAME}_$VERSION.orig.tar.gz rm -rf $DEBNAME-$VERSION #------------------------------------------------------------------------------ # lintian .deb and source lintian -I -i \ --suppress-tags new-package-should-close-itp-bug,desktop-entry-contains-encoding-key,command-in-menu-file-and-desktop-file,emacsen-common-without-dh-elpa,bugs-field-does-not-refer-to-debian-infrastructure \ ${DEBNAME}_${VERSION}*_$DPKG_ARCH.deb lintian -I -i \ --suppress-tags maintainer-upload-has-incorrect-version-number,changelog-should-mention-nmu,empty-debian-diff,debian-rules-uses-deprecated-makefile,testsuite-autopkgtest-missing *.dsc exit 0 Math-PlanePath-129/xtools/my-manifest.sh0000755000175000017500000000165211764227757016054 0ustar gggg#!/bin/sh # my-manifest.sh -- update MANIFEST file # Copyright 2009, 2010, 2011, 2012 Kevin Ryde # my-manifest.sh is shared by several distributions. # # my-manifest.sh 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, or (at your option) any later # version. # # my-manifest.sh 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 file. If not, see . set -e if [ -e MANIFEST ]; then mv MANIFEST MANIFEST.old || true fi touch SIGNATURE ( make manifest 2>&1; diff -u MANIFEST.old MANIFEST ) | ${PAGER:-more} Math-PlanePath-129/xtools/my-check-copyright-years.sh0000755000175000017500000000504513360263206020431 0ustar gggg#!/bin/sh # my-check-copyright-years.sh -- check copyright years in dist # Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2017, 2018 Kevin Ryde # my-check-copyright-years.sh is shared by several distributions. # # my-check-copyright-years.sh 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, or (at your # option) any later version. # # my-check-copyright-years.sh 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 file. If not, see . set -e # die on error set -x # echo # find files in the dist with mod times this year, but without this year in # the copyright line if test -z "$DISTVNAME"; then DISTVNAME=`sed -n 's/^DISTVNAME = \(.*\)/\1/p' Makefile` fi case $DISTVNAME in *\$*) DISTVNAME=`make echo-DISTVNAME` ;; esac if test -z "$DISTVNAME"; then echo "DISTVNAME not set and not in Makefile" exit 1 fi TARGZ="$DISTVNAME.tar.gz" if test -e "$TARGZ"; then :; else pwd echo "TARGZ $TARGZ not found" exit 1 fi MY_HIDE= year=`date +%Y` result=0 # files with dates $year tar tvfz $TARGZ \ | egrep "$year-|debian/copyright" \ | sed "s:^.*$DISTVNAME/::" \ | { while read i do # echo "consider $i" GREP=grep case $i in \ '' | */ \ | ppport.h \ | debian/changelog | debian/doc-base \ | debian/compat | debian/emacsen-compat | debian/source/format \ | debian/patches/*.diff \ | COPYING | MANIFEST* | SIGNATURE | META.yml | META.json \ | version.texi | */version.texi \ | *utf16* | examples/rs''s2lea''fnode.conf \ | */MathI''mage/ln2.gz | */MathI''mage/pi.gz \ | *.mo | *.locatedb* | t/samp.* \ | t/empty.dat | t/*.xpm | t/*.xbm | t/*.jpg | t/*.gif \ | t/*.g${MY_HIDE}d \ | tools/*-oeis-samples.gp \ | test-oeis-samples.gp \ | tools/configurations-gfs-generated.gp \ | devel/configurations-t-generated.gp \ | test-symbols.txt | test-funcs.txt \ | devel/minimal-domsets-max-even2.c \ | */_whizzy*) continue ;; *.gz) GREP=zgrep esac; \ if test -e "$srcdir/$i" then f="$srcdir/$i" else f="$i" fi if $GREP -q -e "Copyright.*$year" $f then :; else echo "$i:1: this file" grep Copyright $f result=1 fi done } exit $result Math-PlanePath-129/xt/0002755000175000017500000000000014001441522012334 5ustar ggggMath-PlanePath-129/xt/0-Test-ConsistentVersion.t0000644000175000017500000000225311655356324017313 0ustar gggg#!/usr/bin/perl -w # 0-Test-ConsistentVersion.t -- run Test::ConsistentVersion if available # Copyright 2011 Kevin Ryde # 0-Test-ConsistentVersion.t is shared by several distributions. # # 0-Test-ConsistentVersion.t 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, or (at your option) any # later version. # # 0-Test-ConsistentVersion.t 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 file. If not, see . use 5.004; use strict; use Test::More; eval { require Test::ConsistentVersion } or plan skip_all => "due to Test::ConsistentVersion not available -- $@"; Test::ConsistentVersion::check_consistent_versions (no_readme => 1, # no version number in my READMEs no_pod => 1, # no version number in my docs, at the moment ); # ! -e 'README'); exit 0; Math-PlanePath-129/xt/DragonCurve-hog.t0000644000175000017500000000733113601570017015524 0ustar gggg#!/usr/bin/perl -w # Copyright 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use Carp 'croak'; use File::Slurp; use FindBin; use Graph; use List::Util 'min', 'max'; $|=1; use Test; plan tests => 4; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::DragonCurve; use File::Spec; use lib File::Spec->catdir('devel','lib'); use MyGraphs; #------------------------------------------------------------------------------ sub BlobN { my ($k) = @_; if ($k < 4) { croak "No blob k=$k"; } my $ret = 7; foreach my $i (5 .. $k) { $ret = 2*$ret - (($i-1) % 4 < 2 ? 0 : 3); } return $ret; } sub make_graph { my ($level, $blob) = @_; my $path = Math::PlanePath::DragonCurve->new; my $graph = Graph->new (undirected => 1); my ($n_lo, $n_hi); if ($blob) { $n_lo = BlobN($level); $n_hi = BlobN($level+1) - 3; } else { ($n_lo, $n_hi) = $path->level_to_n_range($level); } foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); $graph->add_vertex("$x,$y"); } foreach my $n ($n_lo .. $n_hi-1) { my ($x,$y) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); $graph->add_edge("$x,$y", "$x2,$y2"); } return $graph; } { my %shown; { my $content = File::Slurp::read_file (File::Spec->catfile($FindBin::Bin, File::Spec->updir, 'lib','Math','PlanePath','DragonCurve.pm')); $content =~ /=head1 HOUSE OF GRAPHS.*?=head1/s or die; $content = $&; my $count = 0; my $type = ''; while ($content =~ /^( +(?\d+) +level=(?\d+)|And for just a (?blob))/mg) { if ($+{'blob'}) { $type = 'blob,'; } else { $count++; my $id = $+{'id'}; my $level = $+{'level'}; $shown{"${type}level=$level"} = $+{'id'}; } } ok ($type, 'blob,'); ok ($count, 15, 'HOG ID number of lines'); } ok (scalar(keys %shown), 15); ### %shown my $extras = 0; my $compared = 0; my $others = 0; my %seen; foreach my $blob (0, 1) { my $type = ($blob ? 'blob,' : ''); foreach my $level (($blob ? 4 : 0) .. 10) { my $graph = make_graph($level, $blob); last if $graph->vertices >= 256; my $g6_str = MyGraphs::Graph_to_graph6_str($graph); $g6_str = MyGraphs::graph6_str_to_canonical($g6_str); next if $seen{$g6_str}++; my $key = "${type}level=$level"; if (my $id = $shown{$key}) { MyGraphs::hog_compare($id, $g6_str); $compared++; } else { $others++; if (MyGraphs::hog_grep($g6_str)) { my $name = $graph->get_graph_attribute('name'); MyTestHelpers::diag ("HOG $key in HOG, not shown in POD"); MyTestHelpers::diag ($name); MyTestHelpers::diag ($g6_str); # MyGraphs::Graph_view($graph); $extras++; } } } } ok ($extras, 0); MyTestHelpers::diag ("POD HOG $compared compares, $others others"); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/0-Test-Synopsis.t0000755000175000017500000000176411655356314015453 0ustar gggg#!/usr/bin/perl -w # 0-Test-Synopsis.t -- run Test::Synopsis if available # Copyright 2009, 2010, 2011 Kevin Ryde # 0-Test-Synopsis.t is shared by several distributions. # # 0-Test-Synopsis.t 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, or (at your option) any later # version. # # 0-Test-Synopsis.t 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 file. If not, see . use 5.004; use strict; use Test::More; eval 'use Test::Synopsis; 1' or plan skip_all => "due to Test::Synopsis not available -- $@"; ## no critic (ProhibitCallsToUndeclaredSubs) all_synopsis_ok(); exit 0; Math-PlanePath-129/xt/oeis-duplicate.t0000644000175000017500000000307413244716276015455 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Check that OEIS A-number sequences implemented by PlanePath modules aren't # already supplied by the core NumSeq. # use 5.004; use strict; use Test; plan tests => 1; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::NumSeq::OEIS::Catalogue::Plugin::BuiltinTable; use Math::NumSeq::OEIS::Catalogue::Plugin::PlanePath; my %builtin_anums; foreach my $info (@{Math::NumSeq::OEIS::Catalogue::Plugin::BuiltinTable::info_arrayref()}) { $builtin_anums{$info->{'anum'}} = $info; } my $good = 1; my $count = 0; foreach my $info (@{Math::NumSeq::OEIS::Catalogue::Plugin::PlanePath::info_arrayref()}) { my $anum = $info->{'anum'}; if ($builtin_anums{$anum}) { MyTestHelpers::diag ("$anum already a NumSeq builtin"); $good = 0; } $count++; } ok ($good); MyTestHelpers::diag ("total $count PlanePath A-numbers"); exit 0; Math-PlanePath-129/xt/0-no-debug-left-on.t0000755000175000017500000000712313561713016015736 0ustar gggg#!/usr/bin/perl -w # 0-no-debug-left-on.t -- check no Smart::Comments left on # Copyright 2011, 2012, 2017, 2019 Kevin Ryde # 0-no-debug-left-on.t is shared by several distributions. # # 0-no-debug-left-on.t 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, or (at your option) any # later version. # # 0-no-debug-left-on.t 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 file. If not, see . # cf Test::NoSmartComments which uses Module::ScanDeps. require 5; use strict; Test::NoDebugLeftOn->Test_More(verbose => 0); exit 0; package Test::NoDebugLeftOn; use strict; use ExtUtils::Manifest; sub Test_More { my ($class, %options) = @_; require Test::More; Test::More::plan (tests => 1); Test::More::ok ($class->check (diag => \&Test::More::diag, %options)); 1; } sub check { my ($class, %options) = @_; my $diag = $options{'diag'}; if (! -e 'Makefile.PL') { &$diag ('skip, no Makefile.PL so not ExtUtils::MakeMaker'); return 1; } my $href = ExtUtils::Manifest::maniread(); my @files = keys %$href; my $good = 1; my @perl_files = grep {m{ ^lib/ |^(lib|examples|x?t)/.*\.(p[lm]|t)$ |^Makefile.PL$ |^[^/]+$ }x } @files; my $filename; foreach $filename (@perl_files) { if ($options{'verbose'}) { &$diag ("perl file ",$filename); } if (! open FH, "< $filename") { &$diag ("Oops, cannot open $filename: $!"); $good = 0; next; } while () { if (/^__END__/) { last; } # only a DEBUG=> non-zero number is bad, so an expression can copy a # debug from another package if (/(DEBUG\s*=>\s*[1-9][0-9]*)/ || /^[ \t]*((use|no) (Smart|Devel)::Comments)/ ) { print STDERR "\n$filename:$.: leftover: $_\n"; $good = 0; } # no "use lib ... devel", except in xt/*.t unless ($filename =~ /\bxt\b/) { if (/^[ \t]*(use lib\b.*devel.*)/) { print STDERR "\n$filename:$.: leftover: $_\n"; $good = 0; } } } if (! close FH) { &$diag ("Oops, error closing $filename: $!"); $good = 0; next; } } my @C_files = grep {m{ # toplevel or lib .c and .xs files ^[^/]*\.([ch]|xs)$ |^(lib|examples|x?t)/.*\.([ch]|xs)$ }x } @files; foreach $filename (@C_files) { if ($options{'verbose'}) { &$diag ("C/XS file ",$filename); } if (! open FH, "< $filename") { &$diag ("Oops, cannot open $filename: $!"); $good = 0; next; } while () { # #define DEBUG 1 # #define MY_DEBUG 1 if (/^#\s*define\s+(MY_)DEBUG\s+[1-9]/ ) { print STDERR "\n$filename:$.: leftover: $_\n"; $good = 0; } } if (! close FH) { &$diag ("Oops, error closing $filename: $!"); $good = 0; next; } } &$diag ("checked ",scalar(@perl_files)," perl files, ", scalar(@C_files)," C/XS files\n"); return $good; } Math-PlanePath-129/xt/TerdragonCurve-hog.t0000644000175000017500000000622013570401563016237 0ustar gggg#!/usr/bin/perl -w # Copyright 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use File::Slurp; use FindBin; use Graph; use List::Util 'min', 'max'; use Test; plan tests => 4; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::TerdragonCurve; use File::Spec; use lib File::Spec->catdir('devel','lib'); use MyGraphs; #------------------------------------------------------------------------------ sub make_graph { my ($level) = @_; my $path = Math::PlanePath::TerdragonCurve->new; my $graph = Graph->new (undirected => 1); my ($n_lo, $n_hi) = $path->level_to_n_range($level); foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); $graph->add_vertex("$x,$y"); } foreach my $n ($n_lo .. $n_hi-1) { my ($x,$y) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); $graph->add_edge("$x,$y", "$x2,$y2"); } return $graph; } { my %shown; { my $content = File::Slurp::read_file (File::Spec->catfile($FindBin::Bin, File::Spec->updir, 'lib','Math','PlanePath','TerdragonCurve.pm')); $content =~ /=head1 HOUSE OF GRAPHS.*?=head1/s or die; $content = $&; my $count = 0; while ($content =~ /^ +(?\d+) +level=(?\d+)/mg) { $count++; my $id = $+{'id'}; my $level = $+{'level'}; $shown{"level=$level"} = $+{'id'}; } ok ($count, 6, 'HOG ID number of lines'); } ok (scalar(keys %shown), 6); ### %shown my $extras = 0; my $compared = 0; my $others = 0; my %seen; # 3^6 == 729 foreach my $level (0 .. 6) { my $graph = make_graph($level); last if $graph->vertices >= 256; my $g6_str = MyGraphs::Graph_to_graph6_str($graph); $g6_str = MyGraphs::graph6_str_to_canonical($g6_str); next if $seen{$g6_str}++; my $key = "level=$level"; if (my $id = $shown{$key}) { MyGraphs::hog_compare($id, $g6_str); $compared++; } else { $others++; if (MyGraphs::hog_grep($g6_str)) { MyTestHelpers::diag ("HOG $key in HOG, not shown in POD"); my $name = $graph->get_graph_attribute('name'); MyTestHelpers::diag ($name); MyTestHelpers::diag ($g6_str); # MyGraphs::Graph_view($graph); $extras++; } } } ok ($extras, 0); ok ($others, 0); MyTestHelpers::diag ("POD HOG $compared compares, $others others"); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/AlternateTerdragon-hog.t0000644000175000017500000000623413570402012017065 0ustar gggg#!/usr/bin/perl -w # Copyright 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use File::Slurp; use FindBin; use Graph; use List::Util 'min', 'max'; use Test; plan tests => 4; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::AlternateTerdragon; use File::Spec; use lib File::Spec->catdir('devel','lib'); use MyGraphs; #------------------------------------------------------------------------------ sub make_graph { my ($level) = @_; my $path = Math::PlanePath::AlternateTerdragon->new; my $graph = Graph->new (undirected => 1); my ($n_lo, $n_hi) = $path->level_to_n_range($level); foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); $graph->add_vertex("$x,$y"); } foreach my $n ($n_lo .. $n_hi-1) { my ($x,$y) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); $graph->add_edge("$x,$y", "$x2,$y2"); } return $graph; } { my %shown; { my $content = File::Slurp::read_file (File::Spec->catfile($FindBin::Bin, File::Spec->updir, 'lib','Math','PlanePath','AlternateTerdragon.pm')); $content =~ /=head1 HOUSE OF GRAPHS.*?=head1/s or die; $content = $&; my $count = 0; while ($content =~ /^ +(?\d+) +level=(?\d+)/mg) { $count++; my $id = $+{'id'}; my $level = $+{'level'}; $shown{"level=$level"} = $+{'id'}; } ok ($count, 6, 'HOG ID number of lines'); } ok (scalar(keys %shown), 6); ### %shown my $extras = 0; my $compared = 0; my $others = 0; my %seen; # 3^6 == 729 foreach my $level (0 .. 6) { my $graph = make_graph($level); last if $graph->vertices >= 256; my $g6_str = MyGraphs::Graph_to_graph6_str($graph); $g6_str = MyGraphs::graph6_str_to_canonical($g6_str); next if $seen{$g6_str}++; my $key = "level=$level"; if (my $id = $shown{$key}) { MyGraphs::hog_compare($id, $g6_str); $compared++; } else { $others++; if (MyGraphs::hog_grep($g6_str)) { MyTestHelpers::diag ("HOG $key in HOG, not shown in POD"); my $name = $graph->get_graph_attribute('name'); MyTestHelpers::diag ($name); MyTestHelpers::diag ($g6_str); # MyGraphs::Graph_view($graph); $extras++; } } } ok ($extras, 0); ok ($others, 0); MyTestHelpers::diag ("POD HOG $compared compares, $others others"); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/PeanoDiagonals-seq.t0000644000175000017500000000352313731641652016213 0ustar gggg#!/usr/bin/perl -w # Copyright 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Carp 'croak'; use Test; plan tests => 1; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::NumSeq::PlanePathTurn; use File::Spec; use lib File::Spec->catdir('devel','lib'); use Math::PlanePath::PeanoDiagonals; #------------------------------------------------------------------------------ # Turn Sequence - per POD sub turn { my ($n, $radix) = @_; $n >= 1 or croak "turn is for n>=1"; my $v = $n; until ($v % $radix) { $v >= 1 or die; $n++; $v = int($v/$radix); } (-1)**$n; } { my $bad = 0; foreach my $radix (3,5,7) { my $path = Math::PlanePath::PeanoDiagonals->new (radix => $radix); my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); foreach my $n (1 .. $radix**6) { my ($seq_i, $seq_turn) = $seq->next; my $turn = turn($n,$radix); unless ($n == $seq_i) { $bad++; } unless ($turn == $seq_turn) { $bad++; } } } ok ($bad, 0); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/CCurve-hog.t0000644000175000017500000000617113570402317014477 0ustar gggg#!/usr/bin/perl -w # Copyright 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use File::Slurp; use FindBin; use Graph; use List::Util 'min', 'max'; use Test; plan tests => 4; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::CCurve; use File::Spec; use lib File::Spec->catdir('devel','lib'); use MyGraphs; #------------------------------------------------------------------------------ sub make_graph { my ($level) = @_; my $path = Math::PlanePath::CCurve->new; my $graph = Graph->new (undirected => 1); my ($n_lo, $n_hi) = $path->level_to_n_range($level); foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); $graph->add_vertex("$x,$y"); } foreach my $n ($n_lo .. $n_hi-1) { my ($x,$y) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); $graph->add_edge("$x,$y", "$x2,$y2"); } return $graph; } { my %shown; { my $content = File::Slurp::read_file (File::Spec->catfile($FindBin::Bin, File::Spec->updir, 'lib','Math','PlanePath','CCurve.pm')); $content =~ /=head1 HOUSE OF GRAPHS.*?=head1/s or die; $content = $&; my $count = 0; while ($content =~ /^ +(?\d+) +level=(?\d+)/mg) { $count++; my $id = $+{'id'}; my $level = $+{'level'}; $shown{"level=$level"} = $+{'id'}; } ok ($count, 9, 'HOG ID number of lines'); } ok (scalar(keys %shown), 9); ### %shown my $extras = 0; my $compared = 0; my $others = 0; my %seen; # 3^6 == 729 foreach my $level (0 .. 10) { my $graph = make_graph($level); last if $graph->vertices >= 256; my $g6_str = MyGraphs::Graph_to_graph6_str($graph); $g6_str = MyGraphs::graph6_str_to_canonical($g6_str); next if $seen{$g6_str}++; my $key = "level=$level"; if (my $id = $shown{$key}) { MyGraphs::hog_compare($id, $g6_str); $compared++; } else { $others++; if (MyGraphs::hog_grep($g6_str)) { MyTestHelpers::diag ("HOG $key in HOG, not shown in POD"); my $name = $graph->get_graph_attribute('name'); MyTestHelpers::diag ($name); MyTestHelpers::diag ($g6_str); # MyGraphs::Graph_view($graph); $extras++; } } } ok ($extras, 0); ok ($others, 0); MyTestHelpers::diag ("POD HOG $compared compares, $others others"); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/bigrat.t0000644000175000017500000005502013774320636014013 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Crib notes: # # In perl 5.8.4 "BigInt != BigRat" doesn't work, must have it other way # around as "BigRat != BigInt" so get the BigRat equality testing code. # Symptom is "uninitialized" warnings. # use 5.004; use strict; use Test; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments '###'; my $test_count = (tests => 486)[1]; plan tests => $test_count; if (! eval { require Math::BigRat; 1 }) { MyTestHelpers::diag ('skip due to Math::BigRat not available -- ',$@); foreach (1 .. $test_count) { skip ('due to no Math::BigRat', 1, 1); } exit 0; } MyTestHelpers::diag ('Math::BigRat version ', Math::BigRat->VERSION); if (! Math::BigRat->can('as_float')) { MyTestHelpers::diag ('skip due to Math::BigRat->as_float method not available'); foreach (1 .. $test_count) { skip ('due to no as_float()', 1, 1); } exit 0; } { my $f = Math::BigRat->new('-1/2'); my $int = int($f); if ($int == 0) { MyTestHelpers::diag ('BigRat int(-1/2)==0, good'); } else { MyTestHelpers::diag ("BigRat has int(-1/2) != 0 dodginess: value is '$int'"); } } require Math::BigInt; MyTestHelpers::diag ('Math::BigInt version ', Math::BigInt->VERSION); { my $n = Math::BigInt->new(2) ** 256; my $int = int($n); if (! ref $int) { MyTestHelpers::diag ('skip due to Math::BigInt no "int" operator'); foreach (1 .. $test_count) { skip ('due to no Math::BigInt int() operator', 1, 1); } exit 0; } } # doesn't help sqrt(), slows down blog() # # require Math::BigFloat; # Math::BigFloat->precision(-2000); # digits right of decimal point #------------------------------------------------------------------------------ # Diagonals { require Math::PlanePath::Diagonals; my $path = Math::PlanePath::Diagonals->new; { my $x = Math::BigRat->new(10); my $n = ($x+1)*($x+2)/2; # triangular numbers on Y=0 horizontal my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $x, 1, "got x=$got_x want $x"); ok ($got_y == 0, 1, "got y=$got_y want 0"); my $got_n = $path->xy_to_n($x,0); ok ($got_n == $n, 1); } { my $x = Math::BigRat->new(2) ** 256 - 1; my $n = ($x+1)*($x+2)/2; # triangular numbers on Y=0 horizontal my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $x, 1, "got x=$got_x want $x"); ok ($got_y == 0, 1, "got y=$got_y want 0"); my $got_n = $path->xy_to_n($x,0); ok ($got_n == $n, 1); } { my $x = Math::BigRat->new(2) ** 128 - 1; my $n = ($x+1)*($x+2)/2; # Y=0 horizontal my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $x, 1); ok ($got_y == 0, 1); my $got_n = $path->xy_to_n($x,0); ok ($got_n == $n, 1); } { my $y = Math::BigRat->new(2) ** 128 - 1; my $n = $y*($y+1)/2 + 1; # X=0 vertical my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == 0, 1); ok ($got_y == $y, 1); my $got_n = $path->xy_to_n(0,$y); ok ($got_n, $n); } { my $n = Math::BigRat->new(-1); my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, undef); ok ($got_y, undef); } { my $n = Math::BigRat->new(0.5); my ($got_x,$got_y) = $path->n_to_xy($n); ok (!! $got_x->isa('Math::BigRat'), 1); ok (!! $got_y->isa('Math::BigRat'), 1); ok ($got_x == -0.5, 1); ok ($got_y == 0.5, 1); } } #------------------------------------------------------------------------------ # MultipleRings { require Math::PlanePath::MultipleRings; my $width = 5; my $path = Math::PlanePath::MultipleRings->new (step => 6); { my $n = Math::BigRat->new(23); my ($got_x,$got_y) = $path->n_to_xy($n); ok (!! (ref $got_x && $got_x->isa('Math::BigFloat')), 1, "MultipleRings raise BigRat to BigFloat"); ok ($got_x > 0 && $got_x < 1, 1, "MultipleRings n_to_xy($n) got_x $got_x"); ok ($got_y > 2.5 && $got_y < 3.1, 1, "MultipleRings n_to_xy($n) got_y $got_y"); } } #------------------------------------------------------------------------------ # round_nearest() use Math::PlanePath::Base::Generic 'round_nearest'; ok (round_nearest(Math::BigRat->new('-7/4')) == -2, 1); ok (round_nearest(Math::BigRat->new('-3/2')) == -1, 1); ok (round_nearest(Math::BigRat->new('-5/4')) == -1, 1); ok (round_nearest(Math::BigRat->new('-3/4')) == -1, 1); ok (round_nearest(Math::BigRat->new('-1/2')) == 0, 1); ok (round_nearest(Math::BigRat->new('-1/4')) == 0, 1); ok (round_nearest(Math::BigRat->new('1/4')) == 0, 1); ok (round_nearest(Math::BigRat->new('5/4')) == 1, 1); ok (round_nearest(Math::BigRat->new('3/2')) == 2, 1); ok (round_nearest(Math::BigRat->new('7/4')) == 2, 1); ok (round_nearest(Math::BigRat->new('2')) == 2, 1); #------------------------------------------------------------------------------ # floor() use Math::PlanePath::Base::Generic 'floor'; ok (floor(Math::BigRat->new('-7/4')) == -2, 1); ok (floor(Math::BigRat->new('-3/2')) == -2, 1); ok (floor(Math::BigRat->new('-5/4')) == -2, 1); ok (floor(Math::BigRat->new('-3/4')) == -1, 1); ok (floor(Math::BigRat->new('-1/2')) == -1, 1); ok (floor(Math::BigRat->new('-1/4')) == -1, 1); ok (floor(Math::BigRat->new('1/4')) == 0, 1); ok (floor(Math::BigRat->new('3/4')) == 0, 1); ok (floor(Math::BigRat->new('5/4')) == 1, 1); ok (floor(Math::BigRat->new('3/2')) == 1, 1); ok (floor(Math::BigRat->new('7/4')) == 1, 1); ok (floor(Math::BigRat->new('2')) == 2, 1); #------------------------------------------------------------------------------ # CoprimeColumns { require Math::PlanePath::CoprimeColumns; my $path = Math::PlanePath::CoprimeColumns->new; { my $n = Math::BigRat->new('-2/3'); my @ret = $path->n_to_xy($n); ok (scalar(@ret), 0); } { my $n = Math::BigRat->new(0); my $want_x = 1; my $want_y = 1; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $want_x, 1, "got $got_x want $want_x"); ok ($got_y == $want_y); my $got_n = $path->xy_to_n($want_x,$want_y); ok ($got_n == 0, 1); } # pending int(-1/2)==0 dodginess # { # my $n = Math::BigRat->new('-1/3'); # my $want_x = 1; # my $want_y = Math::BigRat->new('1/3'); # # my ($got_x,$got_y) = $path->n_to_xy($n); # ok ($got_x == $want_x, 1, "got $got_x want $want_x"); # ok ($got_y == $want_y); # # my $got_n = $path->xy_to_n($want_x,$want_y); # ok ($got_n == 0, 1); # } { my $n = Math::BigRat->new('1/2'); my $want_x = 2; my $want_y = Math::BigRat->new('1/2'); my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $want_x, 1, "got $got_x want $want_x"); ok ($got_y == $want_y); my $got_n = $path->xy_to_n($want_x,$want_y); ok ($got_n == 1, 1); } } #------------------------------------------------------------------------------ # DiagonalRationals { require Math::PlanePath::DiagonalRationals; my $path = Math::PlanePath::DiagonalRationals->new; { my $n = Math::BigRat->new('1/3'); my @ret = $path->n_to_xy($n); ok (scalar(@ret), 0); } { my $n = Math::BigRat->new('1/2'); my $want_x = Math::BigRat->new('1/2'); my $want_y = Math::BigRat->new('3/2'); my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $want_x, 1, "DiagonalRationals n_to_xy() n=$n, got X=$got_x want X=$want_x"); ok ($got_y == $want_y, 1, "DiagonalRationals n_to_xy() n=$n, got Y=$got_y want Y=$want_y"); # my $got_n = $path->xy_to_n($want_x,$want_y); # ok (defined $got_n && $got_n == 1, 1, # 'DiagonalRationals xy_to_n($want_x,$want_y) from 1/2'); } { # # | 1+1/2 # | \ # | \ # Y=1 | 1 # | \ # | 1+1/3 # | \ # | 1+1/2-eps # | # +--------------- # ^ # X=1 my $n = Math::BigRat->new('4/3'); my $want_x = Math::BigRat->new('4/3'); my $want_y = Math::BigRat->new('2/3'); my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $want_x, 1, "DiagonalRationals n_to_xy() from 4/3, X got $got_x want $want_x"); ok ($got_y == $want_y, 1, "DiagonalRationals n_to_xy() from 4/3, Y got $got_y want $want_y"); my $got_n = $path->xy_to_n($want_x,$want_y); ok ($got_n == 1, 1, 'DiagonalRationals xy_to_n($want_x,$want_y) from 4/3'); } } #------------------------------------------------------------------------------ # Rows { require Math::PlanePath::Rows; my $width = 5; my $path = Math::PlanePath::Rows->new (width => $width); { my $y = Math::BigRat->new(2) ** 128; my $x = 4; my $n = $y*$width + $x + 1; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $x, 1, "got $got_x want $x"); ok ($got_y == $y); my $got_n = $path->xy_to_n($x,$y); ok ($got_n == $n, 1); } { my $n = Math::BigRat->new('4/3'); my ($got_x,$got_y) = $path->n_to_xy($n); ok ("$got_x", '1/3'); ok ($got_y == 0, 1); } { my $n = Math::BigRat->new('4/3') + 15; my ($got_x,$got_y) = $path->n_to_xy($n); ok ("$got_x", '1/3'); ok ($got_y == 3, 1); } { my $n = Math::BigRat->new('4/3') - 15; my ($got_x,$got_y) = $path->n_to_xy($n); ok ("$got_x", '1/3'); ok ($got_y == -3, 1); } } #------------------------------------------------------------------------------ # PeanoCurve require Math::PlanePath::PeanoCurve; { my $path = Math::PlanePath::PeanoCurve->new; require Math::BigRat; my $n = Math::BigRat->new(9) ** 128 + Math::BigRat->new('4/3'); my $want_x = Math::BigRat->new(3) ** 128 + Math::BigRat->new('4/3'); my $want_y = Math::BigRat->new(3) ** 128 - 1; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, $want_x); ok ($got_y, $want_y); } #------------------------------------------------------------------------------ # ZOrderCurve require Math::PlanePath::ZOrderCurve; { my $path = Math::PlanePath::ZOrderCurve->new; require Math::BigRat; my $n = Math::BigRat->new(4) ** 128 + Math::BigRat->new('1/3'); $n->isa('Math::BigRat') || die "Oops, n not a BigRat"; my $want_x = Math::BigRat->new(2) ** 128 + Math::BigRat->new('1/3'); my $want_y = 0; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, $want_x); ok ($got_y, $want_y); } #------------------------------------------------------------------------------ # round_down_pow() use Math::PlanePath::Base::Digits 'round_down_pow'; { my $orig = Math::BigRat->new(3) ** 128 + Math::BigRat->new('1/7'); my $n = Math::BigRat->new(3) ** 128 + Math::BigRat->new('1/7'); my ($pow,$exp) = round_down_pow($n,3); ok ($n, $orig); ok ($pow, Math::BigRat->new(3) ** 128); ok ($exp, 128); } { my $orig = Math::BigRat->new(3) ** 128; my $n = Math::BigRat->new(3) ** 128; my ($pow,$exp) = round_down_pow($n,3); ok ($n, $orig); ok ($pow, Math::BigRat->new(3) ** 128); ok ($exp, 128); } #------------------------------------------------------------------------------ my @modules = ( 'Corner', 'CornerAlternating', 'PyramidSides', 'HilbertSides', 'HilbertCurve', 'HilbertSpiral', 'WythoffPreliminaryTriangle', 'WythoffArray', 'PowerArray', 'PowerArray,radix=3', 'PowerArray,radix=4', 'AztecDiamondRings', # but not across ring end 'PyramidSpiral', 'CfracDigits,radix=1', 'CfracDigits', 'CfracDigits,radix=3', 'CfracDigits,radix=4', 'CfracDigits,radix=10', 'CfracDigits,radix=37', 'ChanTree', 'ChanTree,k=2', 'ChanTree,k=4', 'ChanTree,k=5', 'ChanTree,k=7', 'ChanTree,reduced=1', 'ChanTree,reduced=1,k=2', 'ChanTree,reduced=1,k=4', 'ChanTree,reduced=1,k=5', 'ChanTree,reduced=1,k=7', 'RationalsTree', 'RationalsTree,tree_type=L', 'RationalsTree,tree_type=HCS', 'FractionsTree', 'DekkingCurve', 'DekkingCentres', 'QuintetCurve', 'QuintetCurve,arms=2', 'QuintetCurve,arms=3', 'QuintetCurve,arms=4', 'QuintetCentres', 'QuintetCentres,arms=2', 'QuintetCentres,arms=3', 'QuintetCentres,arms=4', 'PyramidRows', 'PyramidRows,step=0', 'PyramidRows,step=1', 'PyramidRows,step=3', 'PyramidRows,step=37', 'PyramidRows,align=right', 'PyramidRows,align=right,step=0', 'PyramidRows,align=right,step=1', 'PyramidRows,align=right,step=3', 'PyramidRows,align=right,step=37', 'PyramidRows,align=left', 'PyramidRows,align=left,step=0', 'PyramidRows,align=left,step=1', 'PyramidRows,align=left,step=3', 'PyramidRows,align=left,step=37', 'GreekKeySpiral', 'GreekKeySpiral,turns=0', 'GreekKeySpiral,turns=1', 'GreekKeySpiral,turns=3', 'GreekKeySpiral,turns=4', 'GreekKeySpiral,turns=5', 'GreekKeySpiral,turns=6', 'GreekKeySpiral,turns=7', 'GreekKeySpiral,turns=8', 'GreekKeySpiral,turns=37', 'AlternatePaperMidpoint', 'AlternatePaperMidpoint,arms=2', 'AlternatePaperMidpoint,arms=3', 'AlternatePaperMidpoint,arms=4', 'AlternatePaperMidpoint,arms=5', 'AlternatePaperMidpoint,arms=6', 'AlternatePaperMidpoint,arms=7', 'AlternatePaperMidpoint,arms=8', 'AlternatePaper', 'AlternatePaper,arms=2', 'AlternatePaper,arms=3', 'AlternatePaper,arms=4', 'AlternatePaper,arms=5', 'AlternatePaper,arms=6', 'AlternatePaper,arms=7', 'AlternatePaper,arms=8', 'Diagonals', 'Diagonals,direction=up', 'DiagonalsOctant', 'DiagonalsOctant,direction=up', 'DiagonalsAlternating', 'AlternateTerdragon', 'AlternateTerdragon,arms=1', 'AlternateTerdragon,arms=2', 'AlternateTerdragon,arms=6', 'TerdragonMidpoint', 'TerdragonMidpoint,arms=1', 'TerdragonMidpoint,arms=2', 'TerdragonMidpoint,arms=6', 'TerdragonCurve', 'TerdragonCurve,arms=1', 'TerdragonCurve,arms=2', 'TerdragonCurve,arms=6', 'TerdragonRounded', 'TerdragonRounded,arms=1', 'TerdragonRounded,arms=2', 'TerdragonRounded,arms=6', 'CCurve', 'R5DragonMidpoint', 'R5DragonMidpoint,arms=2', 'R5DragonMidpoint,arms=3', 'R5DragonMidpoint,arms=4', 'R5DragonCurve', 'R5DragonCurve,arms=2', 'R5DragonCurve,arms=3', 'R5DragonCurve,arms=4', 'ImaginaryHalf', 'ImaginaryBase', 'CubicBase', 'GrayCode', 'WunderlichSerpentine', 'WunderlichSerpentine,serpentine_type=100_000_000', 'WunderlichSerpentine,serpentine_type=000_000_001', 'WunderlichSerpentine,radix=2', 'WunderlichSerpentine,radix=4', 'WunderlichSerpentine,radix=5,serpentine_type=coil', 'CretanLabyrinth', 'OctagramSpiral', 'AnvilSpiral', 'AnvilSpiral,wider=1', 'AnvilSpiral,wider=2', 'AnvilSpiral,wider=9', 'AnvilSpiral,wider=17', 'AR2W2Curve', 'AR2W2Curve,start_shape=D2', 'AR2W2Curve,start_shape=B2', 'AR2W2Curve,start_shape=B1rev', 'AR2W2Curve,start_shape=D1rev', 'AR2W2Curve,start_shape=A2rev', 'BetaOmega', 'KochelCurve', 'CincoCurve', 'LTiling', 'LTiling,L_fill=ends', 'LTiling,L_fill=all', 'MPeaks', # but not across gap 'WunderlichMeander', 'FibonacciWordFractal', # 'CornerReplicate', # not defined yet 'DigitGroups', 'PeanoCurve', 'PeanoDiagonals', 'ZOrderCurve', 'HIndexing', 'SierpinskiCurve', 'SierpinskiCurveStair', 'DiamondArms', 'SquareArms', 'HexArms', # 'UlamWarburton', # not really defined yet # 'UlamWarburtonQuarter', # not really defined yet 'CellularRule54', # but not across gap # 'CellularRule57', # but not across gap # 'CellularRule57,mirror=1', # but not across gap 'CellularRule190', # but not across gap 'CellularRule190,mirror=1', # but not across gap 'Rows', 'Columns', 'SquareSpiral', 'DiamondSpiral', 'PentSpiral', 'PentSpiralSkewed', 'HexSpiral', 'HexSpiralSkewed', 'HeptSpiralSkewed', 'TriangleSpiral', 'TriangleSpiralSkewed', 'TriangleSpiralSkewed,skew=right', 'TriangleSpiralSkewed,skew=up', 'TriangleSpiralSkewed,skew=down', # 'SacksSpiral', # sin/cos # 'TheodorusSpiral', # counting by N # 'ArchimedeanChords', # counting by N # 'VogelFloret', # sin/cos 'KnightSpiral', 'SierpinskiArrowheadCentres', 'SierpinskiArrowheadCentres,align=right', 'SierpinskiArrowheadCentres,align=left', 'SierpinskiArrowheadCentres,align=diagonal', 'SierpinskiArrowhead', 'SierpinskiArrowhead,align=right', 'SierpinskiArrowhead,align=left', 'SierpinskiArrowhead,align=diagonal', # 'SierpinskiTriangle', # fracs not really defined yet 'QuadricCurve', 'QuadricIslands', 'DragonRounded', 'DragonMidpoint', 'DragonCurve', 'KochSquareflakes', 'KochSnowflakes', 'KochCurve', 'KochPeaks', 'FlowsnakeCentres', 'GosperReplicate', 'GosperSide', 'GosperIslands', 'Flowsnake', # 'DivisibleColumns', # counting by N # 'DivisibleColumns,divisor_type=proper', # 'CoprimeColumns', # counting by N # 'DiagonalRationals',# counting by N # 'GcdRationals', # counting by N # 'GcdRationals,pairs_order=rows_reverse', # 'GcdRationals,pairs_order=diagonals_down', # 'GcdRationals,pairs_order=diagonals_up', # 'FactorRationals', # counting by N # 'TriangularHypot', # counting by N # 'TriangularHypot,points=odd', # 'TriangularHypot,points=all', # 'TriangularHypot,points=hex', # 'TriangularHypot,points=hex_rotated', # 'TriangularHypot,points=hex_centred', 'PythagoreanTree', # 'Hypot', # searching by N # 'HypotOctant', # searching by N # 'PixelRings', # searching by N # 'FilledRings', # searching by N # 'MultipleRings', # sin/cos, maybe 'QuintetReplicate', 'SquareReplicate', 'ComplexPlus', 'ComplexMinus', 'ComplexRevolving', # 'File', # not applicable 'Staircase', 'StaircaseAlternating', 'StaircaseAlternating,end_type=square', ); my @classes = map {"Math::PlanePath::$_"} @modules; sub module_parse { my ($mod) = @_; my ($class, @parameters) = split /,/, $mod; return ("Math::PlanePath::$class", map {/(.*?)=(.*)/ or die; ($1 => $2)} @parameters); } foreach my $module (@modules) { ### $module my ($class, %parameters) = module_parse($module); eval "require $class" or die; my $path = $class->new (width => 23, height => 17); my $arms = $path->arms_count; my $n = Math::BigRat->new(2) ** 256 + 3; if ($path->isa('Math::PlanePath::CellularRule190')) { $n += 1; # not across gap } my $frac = Math::BigRat->new('1/3'); my $n_frac = $frac + $n; my $orig = $n_frac->copy; my ($x1,$y1) = $path->n_to_xy($n); ### xy1: "$x1,$y1" my ($x2,$y2) = $path->n_to_xy($n+$arms); ### xy2: "$x2,$y2" my $dx = $x2 - $x1; my $dy = $y2 - $y1; ### dxy: "$dx, $dy" my $want_x = $frac * Math::BigRat->new ($dx) + $x1; my $want_y = $frac * Math::BigRat->new ($dy) + $y1; my ($x_frac,$y_frac) = $path->n_to_xy($n_frac); ### xy frac: "$x_frac, $y_frac" ok ("$x_frac", "$want_x", "$module arms=$arms X frac=$frac dxdy=$dx,$dy arms=$arms"); ok ("$y_frac", "$want_y", "$module arms=$arms Y frac=$frac dxdy=$dx,$dy arms=$arms"); } exit 0; Math-PlanePath-129/xt/pod-lists.t0000644000175000017500000001555712344544606014471 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Check that the supported fields described in each pod matches what the # code says. use 5.005; use strict; use FindBin; use ExtUtils::Manifest; use List::Util 'max'; use File::Spec; use Test::More; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; # new in 5.6, so unless got it separately with 5.005 eval { require Pod::Parser } or plan skip_all => "Pod::Parser not available -- $@"; plan tests => 6; my $toplevel_dir = File::Spec->catdir ($FindBin::Bin, File::Spec->updir); my $manifest_file = File::Spec->catfile ($toplevel_dir, 'MANIFEST'); my $manifest = ExtUtils::Manifest::maniread ($manifest_file); my @lib_modules = map {m{^lib/Math/PlanePath/([^/]+)\.pm$} ? $1 : ()} keys %$manifest; @lib_modules = sort @lib_modules; diag "module count ",scalar(@lib_modules); #------------------------------------------------------------------------------ { open FH, 'lib/Math/PlanePath.pm' or die $!; my $content = do { local $/; }; # slurp close FH or die; ### $content { $content =~ /=for my_pod see_also begin(.*)=for my_pod see_also end/s or die "see_also not matched"; my $see_also = $1; my @see_also; while ($see_also =~ /L]+)>/g) { push @see_also, $1; } @see_also = sort @see_also; my $s = join(', ',@see_also); my $l = join(', ',@lib_modules); is ($s, $l, 'PlanePath.pm pod SEE ALSO'); my $j = "$s\n$l"; $j =~ /^(.*)(.*)\n\1(.*)/ or die; my $sd = $2; my $ld = $3; if ($sd) { diag "see also: ",$sd; diag "library: ",$ld; } } { $content =~ /=for my_pod list begin(.*)=for my_pod list end/s or die "class list not matched"; my $list = $1; my @list; while ($list =~ /^ (\S+)/mg) { push @list, $1; } @list = sort @list; my $s = join(', ',@list); my $l = join(', ',@lib_modules); is ($s, $l, 'PlanePath.pm pod class list'); my $j = "$s\n$l"; $j =~ /^(.*)(.*)\n\1(.*)/ or die; my $sd = $2; my $ld = $3; if ($sd) { diag "list: ",$sd; diag "library: ",$ld; } } { $content =~ /=for my_pod step begin(.*)=for my_pod step end/s or die "base list not matched"; my $list = $1; $content =~ /=for my_pod base begin(.*)=for my_pod base end/s or die "step list not matched"; $list .= $1; # initialized to exceptions, no "step" in the pod my @list = ('File', 'Hypot', 'HypotOctant', 'TriangularHypot', 'VogelFloret', 'PythagoreanTree', 'RationalsTree', 'FractionsTree', 'ChanTree', 'FactorRationals', 'GcdRationals', 'CfracDigits', 'WythoffPreliminaryTriangle'); my %seen; while ($list =~ /([A-Z]\S+)/g) { my $elem = $1; next if $elem eq 'Base'; next if $elem eq 'Path'; next if $elem eq 'Step'; next if $elem eq 'Fibonacci'; next if $elem eq 'ToothpickSpiral'; # separate Math-PlanePath-Toothpick $elem =~ s/,//; next if $seen{$elem}++; push @list, $elem; } @list = sort @list; my $s = join(', ',@list); my $l = join(', ',@lib_modules); is ($s, $l, 'PlanePath.pm step/base pod lists'); my $j = "$s\n$l"; $j =~ /^(.*)(.*)\n\1(.*)/ or die; my $sd = $2; my $ld = $3; if ($sd) { diag "list: ",$sd; diag "library: ",$ld; } } } #------------------------------------------------------------------------------ foreach my $tfile ('xt/PlanePath-subclasses.t', 'xt/slow/NumSeq-PlanePathCoord.t', ) { open FH, $tfile or die "$tfile: $!"; my $content = do { local $/; }; # slurp close FH or die; ### $content { $content =~ /# module list begin(.*)module list end/s or die "module list not matched"; my $list = $1; my @list; my %seen; while ($list =~ /'([A-Z][^',]+)/ig) { next if $seen{$1}++; push @list, $1; } @list = sort @list; my $s = join(', ',@list); my $l = join(', ',@lib_modules); is ($s, $l, $tfile); my $j = "$s\n$l"; $j =~ /^(.*)(.*)\n\1(.*)/ or die; my $sd = $2; my $ld = $3; if ($sd) { diag "t list: ",$sd; diag "library: ",$ld; } } if ($tfile eq 't/PlanePath-subclasses.t') { $content =~ /# rect_to_n_range exact begin(.*)# rect_to_n_range exact /s or die "rect_to_n_range exact not matched"; my $list = $1; my %exact; while ($list =~ /^\s*'Math::PlanePath::([A-Z][^']+)/img) { $exact{$1} = 1; } my $good = 1; foreach my $module (@lib_modules) { next if $module eq 'Flowsnake'; # inherited next if $module eq 'QuintetCurve'; # inherited my $file = module_exact($module); my $t = $exact{$module} || 0; if ($file != $t) { diag "Math::PlanePath::$module file $file t $t"; $good = 0; } } ok ($good, "$tfile rect exact matches file comments"); sub module_exact { my ($module) = @_; my $filename = "lib/Math/PlanePath/$module.pm"; open FH, $filename or die $!; my $content = do { local $/; }; # slurp close FH or die; ### $content $content =~ /^# (not )?exact\n(sub rect_to_n_range |\*rect_to_n_range =)/m or die "$filename no exact comment"; return $1 ? 0 : 1; } } } #------------------------------------------------------------------------------ # numbers.pl { open FH, 'examples/numbers.pl' or die $!; my $content = do { local $/; }; # slurp close FH or die; ### $content { $content =~ /my \@all_classes = \((.*)# expand arg "all"/s or die "module list not matched"; my $list = $1; my @list = ('File'); my %seen; while ($list =~ /'([A-Z][^',]+)/ig) { next if $seen{$1}++; push @list, $1; } @list = sort @list; my $s = join(', ',@list); my $l = join(', ',@lib_modules); is ($s, $l, 'numbers.pl all_classes'); my $j = "$s\n$l"; $j =~ /^(.*)(.*)\n\1(.*)/ or die; my $sd = $2; my $ld = $3; if ($sd) { diag "numbers.pl list: ",$sd; diag "library: ",$ld; } } } exit 0; Math-PlanePath-129/xt/HIndexing-more.t0000644000175000017500000000567513475604735015376 0ustar gggg#!/usr/bin/perl -w # Copyright 2014, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 35; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::HIndexing; #------------------------------------------------------------------------------ # area sub points_to_area { my ($points) = @_; if (@$points < 3) { return 0; } require Math::Geometry::Planar; my $polygon = Math::Geometry::Planar->new; $polygon->points($points); return $polygon->area; } { my $path = Math::PlanePath::HIndexing->new; foreach my $level (0 .. 10) { my $a = $path->_UNDOCUMENTED__level_to_area($level); my $Y = $path->_UNDOCUMENTED__level_to_area_Y($level); my $up = $path->_UNDOCUMENTED__level_to_area_up($level); ok ($Y+$up, $a); } } { my $path = Math::PlanePath::HIndexing->new; foreach my $level (0 .. 7) { my $got_area = $path->_UNDOCUMENTED__level_to_area($level); my @points; my ($n_lo, $n_hi) = $path->level_to_n_range($level); my $y_max = 0; foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); push @points, [$x,$y]; if ($y > $y_max) { $y_max = $y; } } push @points, [0,$y_max]; my $want_area = points_to_area(\@points); ok ($got_area, $want_area); # print "$want_area, "; } } { my $path = Math::PlanePath::HIndexing->new; foreach my $level (0 .. 7) { my $got_area = $path->_UNDOCUMENTED__level_to_area_up($level); my @points; my ($n_lo, $n_hi) = $path->level_to_n_range($level); $n_lo = ($n_hi + 1)/2 - 1; foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); push @points, [$x,$y]; } my $want_area = points_to_area(\@points); ok ($got_area, $want_area); } } { my $path = Math::PlanePath::HIndexing->new; foreach my $level (0 .. 7) { my $got_area = $path->_UNDOCUMENTED__level_to_area_Y($level); my @points; my ($n_lo, $n_hi) = $path->level_to_n_range($level); $n_hi = ($n_hi + 1)/2 - 1; foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); push @points, [$x,$y]; } my $want_area = points_to_area(\@points); ok ($got_area, $want_area); } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/0-file-is-part-of.t0000644000175000017500000000622212536755447015605 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2015 Kevin Ryde # 0-file-is-part-of.t is shared by several distributions. # # 0-file-is-part-of.t 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, or (at your option) any # later version. # # 0-file-is-part-of.t 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 file. If not, see . require 5; use strict; use Test::More tests => 1; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } ok (Test::FileIsPartOfDist->check(verbose=>1), 'Test::FileIsPartOfDist'); exit 0; package Test::FileIsPartOfDist; BEGIN { require 5 } use strict; use ExtUtils::Manifest; use File::Slurp; # uncomment this to run the ### lines # use Smart::Comments; sub import { my $class = shift; my $arg; foreach $arg (@_) { if ($arg eq '-test') { require Test; Test::plan(tests=>1); is ($class->check, 1, 'Test::FileIsPartOfDist'); } } return 1; } sub new { my $class = shift; return bless { @_ }, $class; } sub check { my $class = shift; my $self = $class->new(@_); my $manifest = ExtUtils::Manifest::maniread(); if (! $manifest) { $self->diag("no MANIFEST perhaps"); return 0; } my @filenames = keys %$manifest; my $distname = $self->makefile_distname; if (! defined $distname) { $self->diag("Oops, DISTNAME not found in Makefile"); return 0; } if ($self->{'verbose'}) { $self->diag("DISTNAME $distname"); } my $good = 1; my $filename; foreach $filename (@filenames) { if (! $self->check_file_is_part_of($filename,$distname)) { $good = 0; } } return $good; } sub makefile_distname { my ($self) = @_; my $filename = "Makefile"; my $content = File::Slurp::read_file ($filename); if (! defined $content) { $self->diag("Cannot read $filename: $!"); return undef; } my $distname; if ($content =~ /^DISTNAME\s*=\s*([^#\n]*)/m) { $distname = $1; $distname =~ s/\s+$//; ### $distname if ($distname eq 'App-Chart') { $distname = 'Chart'; } # hack } return $distname; } sub check_file_is_part_of { my ($self, $filename, $distname) = @_; my $content = File::Slurp::read_file ($filename); if (! defined $content) { $self->diag("Cannot read $filename: $!"); return 0; } $content =~ /([T]his file is part of[^\n]*)/i or return 1; my $got = $1; if ($got =~ /[T]his file is part of \Q$distname\E\b/i) { return 1; } $self->diag("$filename: $got"); $self->diag("expected DISTNAME: $distname"); return 0; } sub diag { my $self = shift; my $func = $self->{'diag_func'} || eval { Test::More->can('diag') } || \&_diag; &$func(@_); } sub _diag { my $msg = join('', map {defined($_)?$_:'[undef]'} @_)."\n"; $msg =~ s/^/# /mg; print STDERR $msg; } Math-PlanePath-129/xt/oeis/0002755000175000017500000000000014001441522013273 5ustar ggggMath-PlanePath-129/xt/oeis/Corner-oeis.t0000644000175000017500000001516713775153516015701 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 11; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::Corner; #------------------------------------------------------------------------------ # A027709 -- unit squares figure boundary MyOEIS::compare_values (anum => 'A027709', func => sub { my ($count) = @_; my $path = Math::PlanePath::Corner->new; my @got = (0); for (my $n = $path->n_start; @got < $count; $n++) { push @got, $path->_NOTDOCUMENTED_n_to_figure_boundary($n); } return \@got; }); # A078633 -- grid sticks { my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); sub path_n_to_dsticks { my ($path, $n) = @_; my ($x,$y) = $path->n_to_xy($n); my $dsticks = 4; foreach my $i (0 .. $#dir4_to_dx) { my $an = $path->xy_to_n($x+$dir4_to_dx[$i], $y+$dir4_to_dy[$i]); $dsticks -= (defined $an && $an < $n); } return $dsticks; } } MyOEIS::compare_values (anum => 'A078633', func => sub { my ($count) = @_; my $path = Math::PlanePath::Corner->new; my @got; my $boundary = 0; for (my $n = $path->n_start; @got < $count; $n++) { $boundary += path_n_to_dsticks($path,$n); push @got, $boundary; } return \@got; }); #------------------------------------------------------------------------------ # A002061 -- N on X=Y diagonal, extra initial 1 MyOEIS::compare_values (anum => 'A002061', func => sub { my ($count) = @_; my @got = (1); my $path = Math::PlanePath::Corner->new; for (my $i = 0; @got < $count; $i++) { push @got, $path->xy_to_n ($i, $i); } return \@got; }); #------------------------------------------------------------------------------ # A060736 -- permutation, N by diagonals down MyOEIS::compare_values (anum => 'A060736', func => sub { my ($count) = @_; require Math::PlanePath::Diagonals; my $corner = Math::PlanePath::Corner->new; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'down'); my @got; for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal->n_to_xy($n); push @got, $corner->xy_to_n ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ # A064788 -- permutation, inverse of N by diagonals down MyOEIS::compare_values (anum => 'A064788', func => sub { my ($count) = @_; require Math::PlanePath::Diagonals; my $corner = Math::PlanePath::Corner->new; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'down'); my @got; for (my $n = $corner->n_start; @got < $count; $n++) { my ($x, $y) = $corner->n_to_xy($n); push @got, $diagonal->xy_to_n ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ # A060734 -- permutation, N by diagonals upwards MyOEIS::compare_values (anum => 'A060734', func => sub { my ($count) = @_; require Math::PlanePath::Diagonals; my $corner = Math::PlanePath::Corner->new; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up'); my @got; for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal->n_to_xy($n); push @got, $corner->xy_to_n ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ # A064790 -- permutation, inverse of N by diagonals upwards MyOEIS::compare_values (anum => 'A064790', func => sub { my ($count) = @_; require Math::PlanePath::Diagonals; my $corner = Math::PlanePath::Corner->new; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up'); my @got; for (my $n = $corner->n_start; @got < $count; $n++) { my ($x, $y) = $corner->n_to_xy($n); push @got, $diagonal->xy_to_n ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ # A004201 -- N for which Y<=X, half below diagonal MyOEIS::compare_values (anum => 'A004201', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::Corner->new; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); if ($x >= $y) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A020703 -- permutation transpose Y,X MyOEIS::compare_values (anum => 'A020703', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::Corner->new; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n ($y, $x); } return \@got; }); #------------------------------------------------------------------------------ # A053188 -- abs(X-Y), distance to next higher pronic, wider=1, extra 0 MyOEIS::compare_values (anum => 'A053188', func => sub { my ($count) = @_; my @got = (0); # extra initial 0 my $path = Math::PlanePath::Corner->new (wider => 1); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, abs($x-$y); } return \@got; }); #------------------------------------------------------------------------------ # Matthew P. Szudzik, "The Rosenberg-Strong Pairing Function", # arxiv 1706.04129. # Ref in A319514 coordinate pairs # # A. L. Rosenberg and H. R. Strong, "Addressing Arrays By Shells", IBM # Technical Disclosure Bulletin, 14(10):3026-3028, March 1972. # # max(x,y) as "shell number" of point x,y. # # GP-DEFINE r2(x,y) = max(x,y)^2 + max(x,y) + x - y; # matrix(10,10,x,y,x--;y--; r2(x,y)) #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/CornerReplicate-oeis.t0000644000175000017500000000532313244716266017521 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2015, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::Base::Digits 'bit_split_lowtohigh'; use Math::PlanePath::CornerReplicate; my $crep = Math::PlanePath::CornerReplicate->new; #------------------------------------------------------------------------------ # A139351 - HammingDist(X,Y) = count 1-bits at even bit positions in N MyOEIS::compare_values (name => 'HammingDist(X,Y)', anum => 'A139351', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x, $y) = $crep->n_to_xy($n); push @got, HammingDist($x,$y); } return \@got; }); sub HammingDist { my ($x,$y) = @_; my @xbits = bit_split_lowtohigh($x); my @ybits = bit_split_lowtohigh($y); my $ret = 0; while (@xbits || @ybits) { $ret += (shift @xbits ? 1 : 0) ^ (shift @ybits ? 1 : 0); } return $ret; } #------------------------------------------------------------------------------ # A048647 -- permutation N at transpose Y,X MyOEIS::compare_values (anum => 'A048647', func => sub { my ($count) = @_; my @got; for (my $n = $crep->n_start; @got < $count; $n++) { my ($x, $y) = $crep->n_to_xy ($n); ($x, $y) = ($y, $x); my $n = $crep->xy_to_n ($x, $y); push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ # A163241 -- flip base-4 digits 2,3 maps to ZOrderCurve MyOEIS::compare_values (anum => 'A163241', func => sub { my ($count) = @_; require Math::PlanePath::ZOrderCurve; my $zorder = Math::PlanePath::ZOrderCurve->new; my @got; for (my $n = $crep->n_start; @got < $count; $n++) { my ($x, $y) = $crep->n_to_xy ($n); my $n = $zorder->xy_to_n ($x, $y); push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/DigitGroups-oeis.t0000644000175000017500000000542713244716255016703 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::DigitGroups; #------------------------------------------------------------------------------ # parity_bitwise() vs path # X is low 0111..11 then Y above that, so (X^Y)&1 is # Parity = lowbit(N) ^ bit_above_lowest_zero(N) { my $path = Math::PlanePath::DigitGroups->new; my $bad = 0; foreach my $n (0 .. 0xFFFF) { my ($x, $y) = $path->n_to_xy ($n); my $path_value = ($x + $y) % 2; my $a_value = parity_bitwise($n); if ($path_value != $a_value) { MyTestHelpers::diag ("diff n=$n path=$path_value acalc=$a_value"); MyTestHelpers::diag (" xy=$x,$y"); last if ++$bad > 10; } } ok ($bad, 0, "parity_bitwise()"); } sub parity_bitwise { my ($n) = @_; return ($n & 1) ^ bit_above_lowest_zero($n); } sub bit_above_lowest_zero { my ($n) = @_; for (;;) { if (($n % 2) == 0) { last; } $n = int($n/2); } $n = int($n/2); return ($n % 2); } #------------------------------------------------------------------------------ # A084472 - X axis in binary, excluding 0 MyOEIS::compare_values (anum => 'A084472', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::DigitGroups->new; for (my $x = 1; @got < $count; $x++) { my $n = $path->xy_to_n ($x,0); push @got, to_binary($n); } return \@got; }); sub to_binary { my ($n) = @_; return ($n < 0 ? '-' : '') . sprintf('%b', abs($n)); } #------------------------------------------------------------------------------ # A060142 - X axis sorted MyOEIS::compare_values (anum => 'A060142', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::DigitGroups->new; for (my $x = 0; @got < 16 * $count; $x++) { push @got, $path->xy_to_n ($x,0); } @got = sort {$a<=>$b} @got; $#got = $count-1; return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/ComplexMinus-oeis.t0000644000175000017500000002755014001115011017036 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2016, 2018, 2019, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BaseCnv 'cnv'; use Math::BigInt try => 'GMP'; # for bignums in reverse-add steps use Test; plan tests => 27; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines # use Smart::Comments '###'; use Math::PlanePath::ComplexMinus; use Math::PlanePath::Diagonals; my $path = Math::PlanePath::ComplexMinus->new; # Cf catalogued NumSeq sequences # A318438 X coordinate # A318439 Y coordinate # A318479 norm #------------------------------------------------------------------------------ # A340566 - permutation N by diagonals +/- # in binary # A001057 alternating pos and neg 0, 1, -1, 2, -2, 3, -3, 4, -4, 5, -5 sub A001057 { my ($n) = @_; return ($n&1 ? ($n>>1)+1 : -($n>>1)); } MyOEIS::compare_values (anum => 'A001057', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { push @got, A001057($n); } return \@got; }); MyOEIS::compare_values (anum => 'A340566', func => sub { my ($count) = @_; my @got; my $diag = Math::PlanePath::Diagonals->new; for (my $n = $diag->n_start; @got < $count; $n++) { my ($x,$y) = $diag->n_to_xy($n); $x = A001057($x); $y = A001057($y); my $n = $path->xy_to_n($x,$y); push @got, cnv($n,10,2); } return \@got; }); #------------------------------------------------------------------------------ # A073791 - X axis X sorted by N, being base conversion 4 to -4 # X axis points (+ and -) in the order visited by the path MyOEIS::compare_values (anum => 'A073791', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($y==0) { push @got, $x; } } return \@got; }); # A320283 - Y axis Y sorted by N # Y axis points (+ and -) in the order visited by the path MyOEIS::compare_values (anum => 'A320283', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($x==0) { push @got, $y; } } return \@got; }); #------------------------------------------------------------------------------ # A256441 N on negative X axis, X<=0 MyOEIS::compare_values (anum => 'A256441', func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x++) { push @got, $path->xy_to_n (-$x,0); } return \@got; }); #------------------------------------------------------------------------------ # A066321 N on X axis, being the base i-1 positive reals MyOEIS::compare_values (anum => 'A066321', func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x++) { push @got, $path->xy_to_n ($x,0); } return \@got; }); # and 2*A066321 on North-West diagonal by one expansion MyOEIS::compare_values (anum => q{A066321}, func => sub { my ($count) = @_; my @got; for (my $i = 0; @got < $count; $i++) { push @got, $path->xy_to_n(-$i,$i) / 2; } return \@got; }); # A271472 - and in binary MyOEIS::compare_values (anum => 'A271472', func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x++) { push @got, sprintf '%b', $path->xy_to_n ($x,0); } return \@got; }); # A066323 - N on X axis, count 1 bits MyOEIS::compare_values (anum => 'A066323', func => sub { my ($count) = @_; my @got = (0); for (my $x = 1; @got < $count; $x++) { my $n = $path->xy_to_n ($x,0); push @got, count_1_bits($n); } return \@got; }); sub count_1_bits { my ($n) = @_; my $count = 0; while ($n) { $count += ($n & 1); $n >>= 1; } return $count; } #------------------------------------------------------------------------------ sub is_string_palindrome { my ($str) = @_; return $str eq reverse($str); } ok (!! is_string_palindrome('acbca'), 1); ok (! is_string_palindrome('aab'), 1); sub is_binary_palindrome { my ($n) = @_; return is_string_palindrome(sprintf '%b', $n); } ok (!! is_binary_palindrome(oct('0b1011101')), 1); ok (! is_binary_palindrome(oct('0b1011')), 1); sub binary_reverse { my ($n) = @_; $n = substr(Math::BigInt->new($n)->as_bin, 2); $n = reverse $n; return Math::BigInt->from_bin($n); } ### rev: binary_reverse(13)."" ok (binary_reverse(13) == 11, 1); sub reverse_add_step { my ($n) = @_; my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy (binary_reverse($n)); return $path->xy_to_n ($x1+$x2, $y1+$y2); } sub reverse_add_palindrome_steps { my ($n) = @_; ### reverse_add_palindrome_steps(): "$n" my %seen; my $count = 0; my $limit = ($n*0+1) << 50; while ($n < $limit && !$seen{$n}++) { ### at: "$n ".$n->as_bin if (is_binary_palindrome($n)) { ### palindrome, count: $count return $count; } $n = reverse_add_step($n); $count++; } return -1; } sub reverse_subtract_step { my ($n, $order) = @_; my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy (binary_reverse($n)); if ($order) { ($x1,$y1, $x2,$y2) = ($x2,$y2, $x1,$y1); } return $path->xy_to_n ($x1-$x2, $y1-$y2); } sub reverse_subtract_palindrome_steps { my ($n, $order) = @_; ### reverse_subtract_palindrome_steps(): "$n" my %seen; my $count = 0; my $limit = ($n*0+1) << 50; while ($n < $limit && !$seen{$n}++) { ### at: "$n ".$n->as_bin if ($n==0) { ### zero, count: $count return $count; } $n = reverse_subtract_step($n,$order); $count++; } return -1; } #------------------------------------------------------------------------------ # A011658 - repeat 0,0,0,1,1 is turn NotStraight # N= 1 2 3 4 5 ... MyOEIS::compare_values (anum => 'A011658', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'ComplexMinus,realpart=2', turn_type => 'NotStraight'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); { my @want = (1,0,0,0,1); my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'ComplexMinus,realpart=2', turn_type => 'NotStraight'); for (1 .. 10_000) { my ($i,$value) = $seq->next; $value == $want[$i%5] or die "oops $i"; } ok(1,1, 'Turn repeating'); } #------------------------------------------------------------------------------ # A193306 reverse-subtract steps to 0 (plain-rev) in base i-1 # A193307 reverse-subtract steps to 0 (rev-plain) in base i-1 MyOEIS::compare_values (anum => 'A193306', max_count => 30, # touch slow func => sub { my ($count) = @_; my @got; for (my $n = Math::BigInt->new(0); @got < $count; $n++) { push @got, reverse_subtract_palindrome_steps($n, 0); } return \@got; }); MyOEIS::compare_values (anum => 'A193307', max_count => 30, # touch slow func => sub { my ($count) = @_; my @got; for (my $n = Math::BigInt->new(0); @got < $count; $n++) { push @got, reverse_subtract_palindrome_steps($n, 1); } return \@got; }); #------------------------------------------------------------------------------ # A193241 reverse-add trajectory of binary 10110, in binary MyOEIS::compare_values (anum => 'A193241', func => sub { my ($count) = @_; my @got; my $n = Math::BigInt->new(20); while (@got < $count) { push @got, substr($n->as_bin, 2); $n = reverse_add_step($n); } return \@got; }); # A193240 reverse-add trajectory of binary 110, in binary MyOEIS::compare_values (anum => 'A193240', func => sub { my ($count) = @_; my @got; my $n = Math::BigInt->new(6); while (@got < $count) { push @got, substr($n->as_bin, 2); $n = reverse_add_step($n); } return \@got; }); # A193239 reverse-add steps to palindrome MyOEIS::compare_values (anum => 'A193239', func => sub { my ($count) = @_; my @got; for (my $n = Math::BigInt->new(0); @got < $count; $n++) { push @got, reverse_add_palindrome_steps($n); } return \@got; }); #------------------------------------------------------------------------------ # A137426 - dX/2 at N=2^(k+2)-1, for k>=0 MyOEIS::compare_values (anum => 'A137426', func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { my ($dx,$dy) = $path->n_to_dxdy (Math::BigInt->new(2)**($k+2) - 1); push @got, $dx/2; } return \@got; }); # A137426 - dY at N=2^k-1, for k>=0 MyOEIS::compare_values (anum => 'A137426', func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { my ($dx,$dy) = $path->n_to_dxdy (Math::BigInt->new(2)**$k - 1); push @got, $dy; } return \@got; }); # GP-Test my(k=0); 2^k-1 == 0 # GP-Test my(k=1); 2^k-1 == 1 # GP-Test my(k=2); 2^k-1 == 3 #------------------------------------------------------------------------------ # A052537 length A,B or C # A003476 total boundary length / 2 # A203175 boundary length MyOEIS::compare_values (anum => 'A203175', name => 'boundary length', func => sub { my ($count) = @_; my @got = (1,1,2); my $a = Math::BigInt->new(2); my $b = Math::BigInt->new(2); my $c = Math::BigInt->new(0); while (@got < $count) { push @got, ($a+$b+$c); ($a,$b,$c) = abc_step($a,$b,$c); } return \@got; }); MyOEIS::compare_values (anum => 'A003476', name => 'boundary length / 2', func => sub { my ($count) = @_; my @got = (1); my $a = Math::BigInt->new(2); my $b = Math::BigInt->new(2); my $c = Math::BigInt->new(0); while (@got < $count) { push @got, ($a+$b+$c)/2; ($a,$b,$c) = abc_step($a,$b,$c); } return \@got; }); MyOEIS::compare_values (anum => 'A052537', func => sub { my ($count) = @_; my @got = (1,0); for (my $i = 0; @got < $count; $i++) { my ($a,$b,$c) = abc_by_pow($i); push @got, $c; } return \@got; }); sub abc_step { my ($a,$b,$c) = @_; return ($a + 2*$c, $a, $b); } sub abc_by_pow { my ($k) = @_; my $zero = $k*0; my $r = 1; my $a = $zero + 2*$r; my $b = $zero + 2; my $c = $zero + 2*(1-$r); foreach (1 .. $k) { ($a,$b,$c) = ((2*$r-1)*$a + 0 + 2*$r*$c, ($r*$r-2*$r+2)*$a + 0 + ($r-1)*($r-1)*$c, 0 + $b); } return ($a,$b,$c); } #------------------------------------------------------------------------------ # A066322 - N on X axis, diffs at 16k+3,16k+4 MyOEIS::compare_values (anum => 'A066322', func => sub { my ($count) = @_; my @got; for (my $i = 0; @got < $count; $i++) { my $x = 16*$i+3; my $x_next = 16*$i+4; my $n = $path->xy_to_n ($x,0); my $n_next = $path->xy_to_n ($x_next,0); push @got, $n_next - $n; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/WythoffPreliminaryTriangle-oeis.t0000644000175000017500000000477312112610147021760 0ustar gggg#!/usr/bin/perl -w # Copyright 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'max'; use Test; plan tests => 46; use lib 't','xt'; use MyTestHelpers; MyTestHelpers::nowarnings(); use MyOEIS; use Math::PlanePath::WythoffPreliminaryTriangle; #------------------------------------------------------------------------------ # A165359 column 1 of left justified Wythoff, gives preliminary triangle Y MyOEIS::compare_values (anum => 'A165359', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffPreliminaryTriangle->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A165360 column 2 of left justified Wythoff, gives preliminary triangle X MyOEIS::compare_values (anum => 'A165360', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffPreliminaryTriangle->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); #------------------------------------------------------------------------------ # A166309 Preliminary Wythoff Triangle, N by rows MyOEIS::compare_values (anum => 'A166309', func => sub { my ($count) = @_; require Math::PlanePath::PyramidRows; my $path = Math::PlanePath::WythoffPreliminaryTriangle->new; my $rows = Math::PlanePath::PyramidRows->new (step=>1); my @got; for (my $r = $rows->n_start; @got < $count; $r++) { my ($x,$y) = $rows->n_to_xy($r); # by rows $y += 1; push @got, $path->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/UlamWarburtonQuarter-oeis.t0000644000175000017500000000375413244716252020607 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 6; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::UlamWarburtonQuarter; #------------------------------------------------------------------------------ # A079318 - (3^(count 1-bits) + 1)/2, width of octant row # extra initial 1 in A079318 foreach my $parts ('octant','octant_up') { MyOEIS::compare_values (anum => 'A079318', func => sub { my ($count) = @_; my $path = Math::PlanePath::UlamWarburtonQuarter->new(parts=>$parts); my @got = (1); for (my $depth = 0; @got < $count; $depth++) { push @got, $path->tree_depth_to_width($depth); } return \@got; }); } #------------------------------------------------------------------------------ # A147610 - 3^(count 1-bits), width of parts=1 row MyOEIS::compare_values (anum => 'A147610', func => sub { my ($count) = @_; my $path = Math::PlanePath::UlamWarburtonQuarter->new; my @got; for (my $depth = 0; @got < $count; $depth++) { push @got, $path->tree_depth_to_width($depth); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/AlternatePaperMidpoint-oeis.t0000644000175000017500000000356113774453712021057 0ustar gggg#!/usr/bin/perl -w # Copyright 2013, 2015, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt try => 'GMP'; use Math::PlanePath::AlternatePaperMidpoint; use Test; plan tests => 1; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; #------------------------------------------------------------------------------ # A334576 -- X coordinate # A334577 -- Y coordinate # checked through PlanePathCoord # my(g=OEIS_bfile_gf("A334576")); x(n) = polcoeff(g,n); # my(g=OEIS_bfile_gf("A334577")); y(n) = polcoeff(g,n); # plothraw(vector(3^7,n,n--; x(n)), \ # vector(3^7,n,n--; y(n)), 1+8+16+32) #------------------------------------------------------------------------------ # A016116 -- X/2 at N=2^k, starting k=1, being 2^floor(k/2) MyOEIS::compare_values (anum => 'A016116', max_count => 200, func => sub { my ($count) = @_; my $path = Math::PlanePath::AlternatePaperMidpoint->new; my @got; for (my $n = Math::BigInt->new(2); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); push @got, $x/2; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/HilbertSides-oeis.t0000644000175000017500000000523513616123602017010 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min', 'max'; use Test; plan tests => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::HilbertSides; my $path = Math::PlanePath::HilbertSides->new; #------------------------------------------------------------------------------ # A000975 count segments on X axis to level k # = 10101010 binary # 10101010, 101010101, 1010101010 MyOEIS::compare_values (anum => 'A000975', max_count => 14, # bit slow by bare search func => sub { my ($count) = @_; my @got; for (my $k=1; @got < $count; $k++) { my $segs = 0; foreach my $y (0 .. 2**$k-1) { $segs += defined $path->xyxy_to_n(0,$y, 0,$y+1); } push @got, $segs; } return \@got; }); # A005578 count segments on X axis to level k # = 101010...1011 binary MyOEIS::compare_values (anum => 'A005578', max_count => 14, # bit slow by bare search func => sub { my ($count) = @_; my @got; for (my $k=0; @got < $count; $k++) { my $segs = 0; foreach my $x (0 .. 2**$k-1) { $segs += defined $path->xyxy_to_n($x,0, $x+1,0); } push @got, $segs; } return \@got; }); #------------------------------------------------------------------------------ # A096268 - morphism turn 1=straight,0=not-straight # but OFFSET=0 is turn at N=1, so "next turn" MyOEIS::compare_values (anum => 'A096268', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'HilbertSides', turn_type => 'Straight'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/KochSnowflakes-oeis.t0000644000175000017500000000504512253200234017340 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 2; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::KochSnowflakes; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # A178789 - num acute angle turns, 4^n + 2 # A002446 - num obtuse angle turns, 2*4^n - 2 MyOEIS::compare_values (anum => 'A002446', max_value => 100_000, func => sub { my ($count) = @_; my @got; for (my $level = 0; @got < $count; $level++) { my ($acute, $obtuse) = count_angles_in_level($level); push @got, $obtuse; } return \@got; }); MyOEIS::compare_values (anum => 'A178789', max_value => 100_000, func => sub { my ($count) = @_; my @got; for (my $level = 0; @got < $count; $level++) { my ($acute, $obtuse) = count_angles_in_level($level); push @got, $acute; } return \@got; }); sub count_angles_in_level { my ($level) = @_; require Math::NumSeq::PlanePathTurn; my $path = Math::PlanePath::KochSnowflakes->new; my $n_level = 4**$level; my $n_end = 4**($level+1) - 1; my @x; my @y; foreach my $n ($n_level .. $n_end) { my ($x,$y) = $path->n_to_xy($n); push @x, $x; push @y, $y; } my $acute = 0; my $obtuse = 0; foreach my $i (0 .. $#x) { my $dx = $x[$i-1] - $x[$i-2]; my $dy = $y[$i-1] - $y[$i-2]; my $next_dx = $x[$i] - $x[$i-1]; my $next_dy = $y[$i] - $y[$i-1]; my $tturn6 = Math::NumSeq::PlanePathTurn::_turn_func_TTurn6($dx,$dy, $next_dx,$next_dy); ### $tturn6 if ($tturn6 == 2 || $tturn6 == 4) { $acute++; } else { $obtuse++; } } return ($acute, $obtuse); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/CornerAlternating-oeis.t0000644000175000017500000002156513775153516020071 0ustar gggg#!/usr/bin/perl -w # Copyright 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 10; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use FindBin; use lib "$FindBin::Bin/../.."; use Math::PlanePath::CornerAlternating; use Math::PlanePath::Diagonals; # abs(X-Y) with wider=1, different from plain Corner # not in OEIS: 0,0,1,2,1,0,1,2,1,0,1,2,3,4,3,2,1,0,1,2,3,4,3,2,1,0,1,2,3,4,5,6,5,4,3,2,1,0 # GP-DEFINE read("my-oeis.gp"); #------------------------------------------------------------------------------ # A220603 -- X+1 coordinate # # cf A319289, A319290 as 0-based X,Y MyOEIS::compare_values (anum => 'A220603', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::CornerAlternating->new; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x+1; } return \@got; }); # A220604 -- Y+1 coordinate MyOEIS::compare_values (anum => 'A220604', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::CornerAlternating->new; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y+1; } return \@got; }); # GP-DEFINE \\ following formulas by Boris Putievskiy in A220603, A220604 # GP-DEFINE A220603(n) = { # GP-DEFINE my(t=sqrtint(n-1)+1); # GP-DEFINE (t%2)*min(t, n- (t - 1)^2) + ((t+1)%2)*min(t, t^2 - n + 1) # GP-DEFINE } # GP-Test my(v=OEIS_samples("A220603")); /* OFFSET=1 */ \ # GP-Test vector(#v,n, A220603(n)) == v # # GP-DEFINE A220604(n) = { # GP-DEFINE my(t=sqrtint(n-1)+1); # GP-DEFINE (t%2)*min(t, t^2 - n + 1) + ((t+1)%2)*min(t, n - (t - 1)^2) # GP-DEFINE } # GP-Test my(v=OEIS_samples("A220604")); /* OFFSET=1 */ \ # GP-Test vector(#v,n, A220604(n)) == v # GP-DEFINE \\ following code by Peter Luschny in A319289, A319290 # GP-DEFINE A319289(n) = { # GP-DEFINE my(m=sqrtint(n), # GP-DEFINE x = m, # GP-DEFINE y = n - x^2); # GP-DEFINE if(x <= y, [x, y] = [2*x - y, x]); # GP-DEFINE if(m%2,y,x); # GP-DEFINE } # GP-Test my(v=OEIS_samples("A319289")); /* OFFSET=0 */ \ # GP-Test vector(#v,n,n--; A319289(n)) == v # # GP-DEFINE A319290(n) = { # GP-DEFINE my(m=sqrtint(n), # GP-DEFINE x = m, # GP-DEFINE y = n - x^2); # GP-DEFINE if(x <= y, [x, y] = [2*x - y, x]); # GP-DEFINE if(m%2,x,y); # GP-DEFINE } # GP-Test my(v=OEIS_samples("A319290")); /* OFFSET=0 */ \ # GP-Test vector(#v,n,n--; A319290(n)) == v # GP-Test vector(1000,n,n--; A319290(n)) == \ # GP-Test vector(1000,n,n--; A220603(n+1)-1) # GP-Test vector(1000,n,n--; A319289(n)) == \ # GP-Test vector(1000,n,n--; A220604(n+1)-1) #------------------------------------------------------------------------------ # A002061 -- N on X=Y diagonal, extra initial 1 MyOEIS::compare_values (anum => 'A002061', func => sub { my ($count) = @_; my @got = (1); my $path = Math::PlanePath::CornerAlternating->new; for (my $i = 0; @got < $count; $i++) { push @got, $path->xy_to_n ($i, $i); } return \@got; }); #------------------------------------------------------------------------------ # A319514 - coordinate pairs Y,X MyOEIS::compare_values (anum => 'A319514', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::CornerAlternating->new; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y,$x; } $#got = $count-1; return \@got; }); #------------------------------------------------------------------------------ # A027709 -- unit squares figure boundary, # same as plain Corner (until wider param) MyOEIS::compare_values (anum => 'A027709', func => sub { my ($count) = @_; my $path = Math::PlanePath::CornerAlternating->new; my @got = (0); for (my $n = $path->n_start; @got < $count; $n++) { push @got, $path->_NOTDOCUMENTED_n_to_figure_boundary($n); } return \@got; }); # A078633 -- grid sticks, same as plain Corner (until wider param) { my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); sub path_n_to_dsticks { my ($path, $n) = @_; my ($x,$y) = $path->n_to_xy($n); my $dsticks = 4; foreach my $i (0 .. $#dir4_to_dx) { my $an = $path->xy_to_n($x+$dir4_to_dx[$i], $y+$dir4_to_dy[$i]); $dsticks -= (defined $an && $an < $n); } return $dsticks; } } MyOEIS::compare_values (anum => 'A078633', func => sub { my ($count) = @_; my $path = Math::PlanePath::CornerAlternating->new; my @got; my $boundary = 0; for (my $n = $path->n_start; @got < $count; $n++) { $boundary += path_n_to_dsticks($path,$n); push @got, $boundary; } return \@got; }); #------------------------------------------------------------------------------ # A081344 -- "maze" permutation, N by diagonals { my $corner = Math::PlanePath::CornerAlternating->new; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up'); MyOEIS::compare_values (anum => 'A081344', func => sub { my ($count) = @_; my @got; for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x,$y) = $diagonal->n_to_xy($n); push @got, $corner->xy_to_n ($x,$y); } return \@got; }); # inverse MyOEIS::compare_values (anum => 'A194280', func => sub { my ($count) = @_; my @got; for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x,$y) = $corner->n_to_xy($n); push @got, $diagonal->xy_to_n ($x,$y); } return \@got; }); } { # with n_start = 0 my $corner = Math::PlanePath::CornerAlternating->new (n_start => 0); my $diagonal = Math::PlanePath::Diagonals->new (n_start => 0, direction => 'up'); MyOEIS::compare_values (anum => 'A220516', func => sub { my ($count) = @_; my @got; for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x,$y) = $diagonal->n_to_xy($n); push @got, $corner->xy_to_n ($x,$y); } return \@got; }); # inverse # not in OEIS: 0,1,4,2,5,8,12,7,3,6,11,17,24 } #------------------------------------------------------------------------------ # A093650 -- "maze" permutation, wider=1 N by diagonals # example in A093650 # 1 6 # | | but upwards anti-diagonals # 2 5 # | | # 3---4 # inverse # not in OEIS: 1,2,4,8,5,3,6,9,13,18,12,7,11,17,24 { my $corner = Math::PlanePath::CornerAlternating->new (wider => 1); my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up'); MyOEIS::compare_values (anum => 'A093650', func => sub { my ($count) = @_; my @got; for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x,$y) = $diagonal->n_to_xy($n); push @got, $corner->xy_to_n ($x,$y); } return \@got; }); } #------------------------------------------------------------------------------ # A081349 -- "maze" permutation, wider=2 N by diagonals { my $corner = Math::PlanePath::CornerAlternating->new (wider => 2); my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up'); MyOEIS::compare_values (anum => 'A081349', func => sub { my ($count) = @_; my @got; for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x,$y) = $diagonal->n_to_xy($n); push @got, $corner->xy_to_n ($x,$y); } return \@got; }); # inverse # not in OEIS: 1,2,4,7,12,8,5,3,6,9,13,18,24,17,11 } #------------------------------------------------------------------------------ # A020703 -- permutation transpose Y,X MyOEIS::compare_values (anum => 'A020703', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::CornerAlternating->new; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n ($y, $x); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/UlamWarburton-oeis.t0000644000175000017500000001624413717076103017240 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2015, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'sum'; use Test; plan tests => 12; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines # use Smart::Comments '###'; use Math::PlanePath::UlamWarburton; my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); #------------------------------------------------------------------------------ # A264768 surrounded # A264769 surrounded increment # A264039 num poisoned # A260490 num newly poisoned # all surrounded 4 MyOEIS::compare_values (anum => 'A264768', func => sub { my ($count) = @_; return poisoned($count, 0, 4); }); # newly surrounded 4 MyOEIS::compare_values (anum => 'A264769', func => sub { my ($count) = @_; return poisoned($count, 1, 4); }); # all poisoned MyOEIS::compare_values (anum => 'A264039', func => sub { my ($count) = @_; return poisoned($count, 0, 2); }); # newly poisoned MyOEIS::compare_values (anum => 'A260490', func => sub { my ($count) = @_; return poisoned($count, 1, 2); }); sub poisoned { my ($count, $newly, $target) = @_; my $path = Math::PlanePath::UlamWarburton->new; my @got = (0); my %seen; my %poisoned; my $prev = 0; for (my $depth = 0; @got < $count; $depth++) { foreach my $n ($path->tree_depth_to_n($depth) .. $path->tree_depth_to_n_end($depth)) { my ($x,$y) = $path->n_to_xy($n); $seen{"$x,$y"}++; foreach my $dir4 (0 .. $#dir4_to_dx) { my $x2 = $x + $dir4_to_dx[$dir4]; my $y2 = $y + $dir4_to_dy[$dir4]; $poisoned{"$x2,$y2"}++; } } my $total = sum(0, map {$poisoned{$_}>=$target && !$seen{$_}} keys %poisoned); push @got, $newly ? $total - $prev : $total; $prev = $total; } return \@got; } #------------------------------------------------------------------------------ # A255264 - count cells up to A048645(n) = bits with one or two 1-bits sub A048645_pred { my ($n) = @_; my $c = 0; for ( ; $n; $n>>=1) { $c += ($n&1); } return $c==1 || $c==2; } MyOEIS::compare_values (anum => 'A048645', max_count => 12, func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { if (A048645_pred($n)) { push @got, $n; } } return \@got; }); MyOEIS::compare_values (anum => 'A255264', max_count => 10, func => sub { my ($count) = @_; my $path = Math::PlanePath::UlamWarburton->new; my @got; for (my $depth = 1; @got < $count; $depth++) { next unless A048645_pred($depth); push @got, $path->tree_depth_to_n_end($depth-1); } return \@got; }); #------------------------------------------------------------------------------ # my @grid; # my $offset = 30; # my @n_start; # # my $prev = 0; # $grid[0+$offset][0+$offset] = 0; # foreach my $n (1 .. 300) { # my ($x,$y) = $path->n_to_xy($n); # my $l = $grid[$x+$offset-1][$y+$offset] # || $grid[$x+$offset+1][$y+$offset] # || $grid[$x+$offset][$y+$offset-1] # || $grid[$x+$offset][$y+$offset+1] # || 0; # if ($l != $prev) { # push @n_start, $n; # $prev = $l; # } # $grid[$x+$offset][$y+$offset] = $l+1; # } # ### @n_start # my @n_end = map {$_-1} @n_start; # ### @n_end # # my @levelcells = (1, map {$n_start[$_]-$n_start[$_-1]} 1 .. $#n_start); # ### @levelcells # foreach my $y (reverse -$offset .. $offset) { # foreach my $x (-$offset .. $offset) { # my $c = $grid[$x+$offset][$y+$offset]; # if (! defined $c) { $c = ' '; } # print $c; # } # print "\n"; # } #------------------------------------------------------------------------------ # A183060 - count total cells in half plane, including axes MyOEIS::compare_values (anum => 'A183060', func => sub { my ($count) = @_; my $path = Math::PlanePath::UlamWarburton->new (parts => '2', n_start => 0); my @got; for (my $depth = 0; @got < $count; $depth++) { push @got, $path->tree_depth_to_n($depth); } return \@got; }); # added cells MyOEIS::compare_values (anum => 'A183061', func => sub { my ($count) = @_; my $path = Math::PlanePath::UlamWarburton->new (parts => '2'); my @got = (0); for (my $depth = 0; @got < $count; $depth++) { push @got, $path->tree_depth_to_width($depth); } return \@got; }); #------------------------------------------------------------------------------ # A151922 - count total cells in first quadrant, incl X,Y axes MyOEIS::compare_values (anum => 'A151922', func => sub { my ($count) = @_; my $path = Math::PlanePath::UlamWarburton->new (parts => '1'); my @got; for (my $depth = 0; @got < $count; $depth++) { push @got, $path->tree_depth_to_n_end($depth); } return \@got; }); # added MyOEIS::compare_values (anum => 'A079314', func => sub { my ($count) = @_; my $path = Math::PlanePath::UlamWarburton->new (parts => '1'); my @got; for (my $depth = 0; @got < $count; $depth++) { push @got, $path->tree_depth_to_width($depth); } return \@got; }); MyOEIS::compare_values (anum => q{A151922}, func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::UlamWarburton->new; my $n = $path->n_start; my $total = 0; for (my $depth = 0; @got < $count; $depth++) { my $n_end = $path->tree_depth_to_n_end($depth); for ( ; $n <= $n_end; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($x >= 0 && $y >= 0) { $total++; } } push @got, $total; } return \@got; }); #------------------------------------------------------------------------------ # A079314 - count added cells in first quadrant, incl X,Y axes # is added(depth)/4 + 1, the +1 being for two axes # MyOEIS::compare_values (anum => 'A079314', func => sub { my ($count) = @_; my $path = Math::PlanePath::UlamWarburton->new; my @got; my $n = $path->n_start; for (my $depth = 0; @got < $count; $depth++) { my $n_end = $path->tree_depth_to_n_end($depth); my $added = 0; for ( ; $n <= $n_end; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($x >= 0 && $y >= 0) { $added++; } } push @got, $added; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/KnightSpiral-oeis.t0000644000175000017500000001413613464464673017047 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 8; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines #use Smart::Comments '###'; use Math::PlanePath::KnightSpiral; use Math::PlanePath::SquareSpiral; my $knight = Math::PlanePath::KnightSpiral->new; my $square = Math::PlanePath::SquareSpiral->new; #------------------------------------------------------------------------------ # KnightSpiral # LSR # 1,0,1,-1,1,-1,1,-1,0,1,-1,1,1,1,-1,1,1,-1,1,1,-1,1,1,1,-1,1,1,-1,1,1,-1,1,1,1,-1,1,1,-1,1,1,-1,1,1,1,-1,1,1,-1,1,0,1,-1,1,-1 # 18,25,66 # MyOEIS::compare_values # (anum => 'A227741', # func => sub { # my ($count) = @_; # $count = 5000; # my @got; # require Math::NumSeq::PlanePathTurn; # my $seq = Math::NumSeq::PlanePathTurn->new(planepath => 'KnightSpiral', # turn_type => 'LSR'); # while (@got < $count) { # my ($i, $value) = $seq->next; # push @got, $value; # if ($value == 0) { # print "$i,"; # } # } # return \@got; # }); # Straight ahead at step to next outer ring is squares. # South-East continuing around is others. # vector_modulo(v,i) = v[(i% #v)+1]; # S(k) = 4 * k^2 + vector_modulo([16, 12],k) * k + vector_modulo([18, 9],k) # vector(10,k,k--; S(k)) # vector(10,k,k--; S(2*k)) \\ A010006 # vector(10,k,k--; S(2*k+1)) \\ A016814 # poldisc(4 * k^2 + 16 * k + 18) # poldisc(4 * k^2 + 12 * k + 9) # factor(4 * k^2 + 12 * k + 9) # 4 * k^2 + 12 * k + 9 == (2*k+3)^2 # factor(4 * k^2 + 16 * k + 18) # factor(I*(4 * k^2 + 16 * k + 18)) # polroots(4 * k^2 + 16 * k + 18) #------------------------------------------------------------------------------ # # A306659 - X coordinate # MyOEIS::compare_values # (anum => 'A306659', # func => sub { # my ($count) = @_; # my @got; # for (my $n = 1; @got < $count; $n++) { # my ($x, $y) = $knight->n_to_xy ($n); # push @got, $x; # } # return \@got; # }); # # # A306660 - Y coordinate # MyOEIS::compare_values # (anum => 'A306660', # func => sub { # my ($count) = @_; # my @got; # for (my $n = 1; @got < $count; $n++) { # my ($x, $y) = $knight->n_to_xy ($n); # push @got, $y; # } # return \@got; # }); # Not yet, slightly different innermost ring. # # A306659 0, 2, 0, -2, -1, 1, 2, 1, -1, -2, 0, 2, 1, -1, -2, -1, 1, 0, -2, -1, # A306660 0, 1, 2, 1, -1, -2, 0, 2, 1, -1, -2, -1, 1, 2, 0, -2, -1, 1, 2, 0, -2, # # 1 . . # 2 # # Line plot of X=A306659, Y=A306660 # http://oeis.org/plot2a?name1=A306659&name2=A306660&tform1=untransformed&tform2=untransformed&shift=0&radiop1=xy&drawpoints=true&drawlines=true #------------------------------------------------------------------------------ # A068608 - N values in square spiral order, same first step MyOEIS::compare_values (anum => 'A068608', func => sub { my ($count) = @_; my @got; foreach my $n (1 .. $count) { my ($x, $y) = $knight->n_to_xy ($n); push @got, $square->xy_to_n ($x, $y); } return \@got; }); # A068609 - rotate 90 degrees MyOEIS::compare_values (anum => 'A068609', func => sub { my ($count) = @_; my @got; foreach my $n (1 .. $count) { my ($x, $y) = $knight->n_to_xy ($n); ### knight: "$n $x,$y" ($x, $y) = (-$y, $x); push @got, $square->xy_to_n ($x, $y); ### rotated: "$x,$y" ### is: "got[$#got] = $got[-1]" } return \@got; }); # A068610 - rotate 180 degrees MyOEIS::compare_values (anum => 'A068610', func => sub { my ($count) = @_; my @got; foreach my $n (1 .. $count) { my ($x, $y) = $knight->n_to_xy ($n); ($x, $y) = (-$x, -$y); push @got, $square->xy_to_n ($x, $y); } return \@got; }); # A068611 - rotate 270 degrees MyOEIS::compare_values (anum => 'A068611', func => sub { my ($count) = @_; my @got; foreach my $n (1 .. $count) { my ($x, $y) = $knight->n_to_xy ($n); ($x, $y) = ($y, -$x); push @got, $square->xy_to_n ($x, $y); } return \@got; }); # A068612 - rotate 180 degrees, opp direction, being X negated MyOEIS::compare_values (anum => 'A068612', func => sub { my ($count) = @_; my @got; foreach my $n (1 .. $count) { my ($x, $y) = $knight->n_to_xy ($n); $x = -$x; push @got, $square->xy_to_n ($x, $y); } return \@got; }); # A068613 - MyOEIS::compare_values (anum => 'A068613', func => sub { my ($count) = @_; my @got; foreach my $n (1 .. $count) { my ($x, $y) = $knight->n_to_xy ($n); ($x, $y) = (-$y, -$x); push @got, $square->xy_to_n ($x, $y); } return \@got; }); # A068614 - clockwise, Y negated MyOEIS::compare_values (anum => 'A068614', func => sub { my ($count) = @_; my @got; foreach my $n (1 .. $count) { my ($x, $y) = $knight->n_to_xy ($n); $y = -$y; push @got, $square->xy_to_n ($x, $y); } return \@got; }); # A068615 - transpose MyOEIS::compare_values (anum => 'A068615', func => sub { my ($count) = @_; my @got; foreach my $n (1 .. $count) { my ($x, $y) = $knight->n_to_xy ($n); ($y, $x) = ($x, $y); push @got, $square->xy_to_n ($x, $y); } return \@got; }); exit 0; Math-PlanePath-129/xt/oeis/DragonCurve-oeis.t0000644000175000017500000010161513717067116016656 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt try => 'GMP'; use Test; plan tests => 58; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::DragonCurve; my $dragon = Math::PlanePath::DragonCurve->new; sub is_square { my ($n) = @_; my $sqrt = int(sqrt($n)); return $n == $sqrt*$sqrt; } #------------------------------------------------------------------------------ # A332383 -- X coordinate MyOEIS::compare_values (anum => 'A332383', func => sub { my ($count) = @_; my $path = Math::PlanePath::DragonCurve->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); # A332384 -- Y coordinate MyOEIS::compare_values (anum => 'A332384', func => sub { my ($count) = @_; my $path = Math::PlanePath::DragonCurve->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A106840 -- N positions of turns L,L MyOEIS::compare_values (anum => 'A106840', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Left'); while (@got < $count) { my ($i, $value) = $seq->next; if ($value && $seq->ith($i+1)) { push @got, $i; } } return \@got; }); # A106841 -- N positions of turns L,L,L MyOEIS::compare_values (anum => 'A106841', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Left'); while (@got < $count) { my ($i, $value) = $seq->next; if ($value && $seq->ith($i+1) && $seq->ith($i+2)) { push @got, $i; } } return \@got; }); #------------------------------------------------------------------------------ # A119972 -- turn sequence * index n MyOEIS::compare_values (anum => 'A119972', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'LSR'); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $i * $value; } return \@got; }); #------------------------------------------------------------------------------ # A126937 -- points X,Y coded by SquareSpiral, starting N=0 MyOEIS::compare_values (anum => 'A126937', func => sub { my ($count) = @_; require Math::PlanePath::SquareSpiral; my $square = Math::PlanePath::SquareSpiral->new (n_start => 0); my @got; for (my $n = $dragon->n_start; @got < $count; $n++) { my ($x, $y) = $dragon->n_to_xy ($n); my $square_n = $square->xy_to_n ($x, -$y); push @got, $square_n; } return \@got; }); #------------------------------------------------------------------------------ # A091072 -- N positions of left turns MyOEIS::compare_values (anum => 'A091072', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Left'); while (@got < $count) { my ($i, $value) = $seq->next; if ($value) { push @got, $i; } } return \@got; }); #------------------------------------------------------------------------------ # A106837 -- N positions of turns R,R MyOEIS::compare_values (anum => 'A106837', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Right'); while (@got < $count) { my ($i, $value) = $seq->next; if ($value && $seq->ith($i+1)) { push @got, $i; } } return \@got; }); # A106838 -- N positions of turns R,R,R MyOEIS::compare_values (anum => 'A106838', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Right'); while (@got < $count) { my ($i, $value) = $seq->next; if ($value && $seq->ith($i+1) && $seq->ith($i+2)) { push @got, $i; } } return \@got; }); #------------------------------------------------------------------------------ # Skd num segments in directions to level k foreach my $elem ([ 'A038503', 0, [1] ], [ 'A038504', 1, [0] ], [ 'A038505', 2, [] ], [ 'A000749', 3, [0] ]) { my ($anum, $want_dir4, $initial) = @$elem; MyOEIS::compare_values (anum => $anum, max_count => 8, name => "dir=$want_dir4", func => sub { my ($count) = @_; my @got = @$initial; require Math::NumSeq::PlanePathDelta; my $seq = Math::NumSeq::PlanePathDelta->new(planepath_object=>$dragon, delta_type => 'Dir4'); my $target = 2; my $total = 0; while (@got < $count) { my ($i, $value) = $seq->next; if ($i == $target) { push @got, $total; $target *= 2; } $total += ($value == $want_dir4); } return \@got; }); } #------------------------------------------------------------------------------ # N with dir E,N,W,S require Math::NumSeq::PlanePathDelta; foreach my $elem ([ 'A043724', 0, 1], # A043724 doesn't include N=0 [ 'A043725', 1], [ 'A043726', 2], [ 'A043727', 3]) { my ($anum, $want_dir4, $skip) = @$elem; MyOEIS::compare_values (anum => $anum, name => "N of dir4=$want_dir4", func => sub { my ($count) = @_; my @got; my $seq = Math::NumSeq::PlanePathDelta->new(planepath_object=>$dragon, delta_type => 'Dir4'); foreach (1 .. $skip||0) { $seq->next; } while (@got < $count) { my ($n, $value) = $seq->next; if ($value == $want_dir4) { push @got, $n; } } return \@got; }); } #------------------------------------------------------------------------------ # A268411 - horizontals 2N direction 0=East, 1=West MyOEIS::compare_values (anum => 'A268411', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathDelta; my $seq = Math::NumSeq::PlanePathDelta->new(planepath_object=>$dragon, delta_type => 'Dir4'); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value/2; $seq->next; # skip odd N } return \@got; }); #------------------------------------------------------------------------------ # A227741 permutation of the integers, # each dir many integers in reverse order MyOEIS::compare_values (anum => 'A227741', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'LSR'); my $upto = 1; my $dir = 0; while (@got < $count) { my ($i, $value) = $seq->next; $dir += $value; push @got, reverse $upto .. $upto+$dir-1; $upto += $dir; } $#got = $count-1; return \@got; }); # FORMULA # A227742 permutation fixed point # # middle of each odd dir # turn +/-1 so dir alternately even,odd # so per Antti Karttunen A173318(2*(n-1)) + (1/2)*(1 + A005811(2n-1)) # MyOEIS::compare_values (anum => 'A227742', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'LSR'); my $upto = 1; my $dir = 0; while (@got < $count) { my ($i, $value) = $seq->next; $dir += $value; if ($dir %2) { push @got, $upto + ($dir-1)/2; } $upto += $dir; } $#got = $count-1; return \@got; }); #------------------------------------------------------------------------------ # A164910 - dragon 1 + cumulative turn +/-1, partial sums of that cumulative # partial sums A088748 # A001792 = (n+2)*2^(n-1) # a(4) = 8 = 4*2^1 # a(8) = 20 = 5*2^2 # a(16)= 48 = 6*2^3 # a(32)= 112 = 7*2^4 MyOEIS::compare_values (anum => 'A164910', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'LSR'); my $cumulative = 1; my $partial_sum = $cumulative; while (@got < $count) { push @got, $partial_sum; my ($i, $value) = $seq->next; $cumulative += $value; $partial_sum += $cumulative; } return \@got; }); # A173318 - dragon cumulative turn +/-1, partial sums of that cumulative MyOEIS::compare_values (anum => 'A173318', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'LSR'); my $cumulative = 0; my $partial_sum = $cumulative; while (@got < $count) { push @got, $partial_sum; my ($i, $value) = $seq->next; $cumulative += $value; $partial_sum += $cumulative; } return \@got; }); # A227744 squares among A173318 dragon dir cumulative MyOEIS::compare_values (anum => 'A227744', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'LSR'); my $cumulative = 0; my $partial_sum = $cumulative; while (@got < $count) { if (is_square($partial_sum)) { push @got, $partial_sum; } my ($i, $value) = $seq->next; $cumulative += $value; $partial_sum += $cumulative; } return \@got; }); # A227743 indexes of squares among A173318 dragon dir cumulative MyOEIS::compare_values (anum => 'A227743', func => sub { my ($count) = @_; my @got = (0); require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'LSR'); my $cumulative = 0; my $partial_sum = $cumulative; while (@got < $count) { my ($i, $value) = $seq->next; $cumulative += $value; $partial_sum += $cumulative; if (is_square($partial_sum)) { push @got, $i; } } return \@got; }); # A227745 sqrts of squares among A173318 dragon dir cumulative MyOEIS::compare_values (anum => 'A227745', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'LSR'); my $cumulative = 0; my $partial_sum = $cumulative; while (@got < $count) { if (is_square($partial_sum)) { push @got, sqrt($partial_sum); } my ($i, $value) = $seq->next; $cumulative += $value; $partial_sum += $cumulative; } return \@got; }); #------------------------------------------------------------------------------ # A005811 -- total rotation, count runs of bits in binary # MyOEIS::compare_values (anum => 'A005811', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'LSR'); my $cumulative = 0; while (@got < $count) { push @got, $cumulative; my ($i, $value) = $seq->next; $cumulative += $value; } return \@got; }); # A136004 total turn + 4 MyOEIS::compare_values (anum => 'A136004', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'LSR'); my $cumulative = 4; while (@got < $count) { push @got, $cumulative; my ($i, $value) = $seq->next; $cumulative += $value; } return \@got; }); # A037834 - dragon cumulative turn +/-1 # -1 + sum i=1 to n turn(n) # MyOEIS::compare_values (anum => 'A037834', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'LSR'); my $cumulative = -1; # sum - 1 while (@got < $count) { my ($i, $value) = $seq->next; $cumulative += $value; push @got, $cumulative; } return \@got; }); # A088748 - dragon cumulative turn +/-1 # 1 + sum i=1 to n turn(n) # MyOEIS::compare_values (anum => 'A088748', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'LSR'); my $cumulative = 1; # sum + 1 while (@got < $count) { push @got, $cumulative; my ($i, $value) = $seq->next; $cumulative += $value; } return \@got; }); #------------------------------------------------------------------------------ # A255070 - TurnsR num right turns 1 to N MyOEIS::compare_values (anum => 'A255070', func => sub { my ($count) = @_; my @got = (0); require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Right'); my $total = 0; while (@got < $count) { my ($i, $value) = $seq->next; $total += $value; push @got, $total; } return \@got; }); # A236840 - 2*TurnsR num right turns 1 to N MyOEIS::compare_values (anum => 'A236840', func => sub { my ($count) = @_; my @got = (0); require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Right'); my $total = 0; while (@got < $count) { my ($i, $value) = $seq->next; $total += $value; push @got, 2*$total; } return \@got; }); #------------------------------------------------------------------------------ # A090678 - N not start of a turn run, so where turn same as previous MyOEIS::compare_values (anum => 'A090678', func => sub { my ($count) = @_; my @got = (1,1); require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'LSR'); (undef, my $prev_value) = $seq->next; while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value == $prev_value ? 1 : 0; $prev_value = $value; } return \@got; }); #------------------------------------------------------------------------------ # A106836 - N steps between right turns # with a first term included so start $prev_i=0 MyOEIS::compare_values (anum => 'A106836', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Right'); my $prev_i = 0; while (@got < $count) { my ($i, $value) = $seq->next; next unless $value; if (defined $prev_i) { push @got, $i - $prev_i; } $prev_i = $i; } return \@got; }); # A088742 - N steps between left turns # with a first term included so start $prev_i=0 MyOEIS::compare_values (anum => 'A088742', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Left'); my $prev_i = 0; while (@got < $count) { my ($i, $value) = $seq->next; next unless $value; if (defined $prev_i) { push @got, $i - $prev_i; } $prev_i = $i; } return \@got; }); #------------------------------------------------------------------------------ # Ba2 boundary length of arms=2 around whole of level k # FIXME: Neither values nor diff are A052537 it seems, what was this mean to be? # * # | # 3 5---* 4 * *---*---* # | | | | | | | # o---2 o---* *---* o---* # len=4 k=2 len=8 k=3 len=14 # # MyOEIS::compare_values # (anum => 'A052537', # max_value => 100, # func => sub { # my ($count) = @_; # my @got; # my $path = Math::PlanePath::DragonCurve->new (arms => 2); # my $k = 0; # my $prev = MyOEIS::path_boundary_length ($path, 2*2**$k + 1); # for ($k++; @got < $count; $k++) { # my $len = MyOEIS::path_boundary_length ($path, 2*2**$k + 1); # my $diff = $len - $prev; # push @got, $diff; # $prev = $len; # } # return \@got; # }); #------------------------------------------------------------------------------ # A091067 -- N positions of right turns MyOEIS::compare_values (anum => 'A091067', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Right'); while (@got < $count) { my ($i, $value) = $seq->next; if ($value) { push @got, $i; } } return \@got; }); # A255068 -- N positions where next turn right MyOEIS::compare_values (anum => 'A255068', name => 'N where next turn right', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Right'); while (@got < $count) { my ($i, $value) = $seq->next; if ($value) { push @got, $i-1; } } return \@got; }); # A060833 -- N positions where previous turn right MyOEIS::compare_values (anum => 'A060833', name => 'N where previous turn right', func => sub { my ($count) = @_; my @got = (1); # extra initial 1 require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Right'); while (@got < $count) { my ($i, $value) = $seq->next; if ($value) { push @got, $i+1; } } return \@got; }); #------------------------------------------------------------------------------ # A099545 -- turn 1=left, 3=right MyOEIS::compare_values (anum => 'A099545', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Left'); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value ? 1 : 3; } return \@got; }); #------------------------------------------------------------------------------ # A003476 Daykin and Tucker alpha[n] # = RQ squares on right boundary, OFFSET=1 values 1, 2, 3, 5 # = S single points N=0 to N=2^(k-1) inclusive, with initial 1 for k=-1 one point # # * # | # *---* *---* # # k=0 k=1 # singles=2 singles=3 # # MyOEIS::compare_values (anum => 'A003476', max_value => 10000, func => sub { my ($count) = @_; my @got = (1); for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_n_to_singles ($dragon, 2**$k); } return \@got; }); #------------------------------------------------------------------------------ # A121238 - (-1)^(1+n+A088585(n)) is 1=left,-1=right, extra initial 1 # A088585 bisection or partial sums of A088567=non-squashing partitions # = A088575+1 # A088575 bisection of A088567 # A088567 a(0)=1, a(1)=1; # for m >= 1, a(2m) = a(2m-1) + a(m) - 1, # a(2m+1) = a(2m) + 1 # A090678 = A088567 mod 2. MyOEIS::compare_values (anum => 'A121238', func => sub { my ($count) = @_; my @got = (1); require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Left'); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value ? 1 : -1; } return \@got; }); #------------------------------------------------------------------------------ # A166242 - turn cumulative doubling/halving, is 2^(total turn) MyOEIS::compare_values (anum => 'A166242', func => sub { my ($count) = @_; my @got = (1); require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Left'); my $cumulative = 1; while (@got < $count) { my ($i, $value) = $seq->next; if ($value) { $cumulative *= 2; } else { $cumulative /= 2; } push @got, $cumulative; } return \@got; }); #------------------------------------------------------------------------------ # A112347 - Kronecker -1/n is 1=left,-1=right, extra initial 0 MyOEIS::compare_values (anum => 'A112347', func => sub { my ($count) = @_; my @got = (0); require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Left'); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value ? 1 : -1; } return \@got; }); #------------------------------------------------------------------------------ # A014710 -- turn 2=left, 1=right MyOEIS::compare_values (anum => 'A014710', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Left'); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value+1; } return \@got; }); #------------------------------------------------------------------------------ # A014709 -- turn 1=left, 2=right MyOEIS::compare_values (anum => 'A014709', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Right'); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value+1; } return \@got; }); #------------------------------------------------------------------------------ # A014577 -- turn 1=left, 0=right, starting from 1 # # cf A059125 is almost but not quite the same, the 8,24,or some such entries # differ MyOEIS::compare_values (anum => 'A014577', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Left'); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A014707 -- turn 0=left, 1=right, starting from 1 MyOEIS::compare_values (anum => 'A014707', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Right'); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A088431 - dragon turns run lengths MyOEIS::compare_values (anum => 'A088431', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Right'); my ($initial_i, $prev) = $seq->next; my $run = 1; # count for initial $prev_turn while (@got < $count) { my ($i, $value) = $seq->next; if ($value == $prev) { $run++; } else { push @got, $run; $run = 1; # count for new $turn value } $prev = $value; } return \@got; }); #------------------------------------------------------------------------------ # A007400 - 2 * run lengths, extra initial 0,1 # cf A007400 cont frac 1/2^1 + 1/2^2 + 1/2^4 + 1/2^8 + ... 1/2^(2^n) # = 0.8164215090218931... # 2,4,6 values # a(0)=0, # a(1)=1, # a(2)=4, # a(8n) = a(8n+3) = 2, # a(8n+4) = a(8n+7) = a(16n+5) = a(16n+14) = 4, # a(16n+6) = a(16n+13) = 6, # a(8n+1) = a(4n+1), # a(8n+2) = a(4n+2) MyOEIS::compare_values (anum => 'A007400', func => sub { my ($count) = @_; my @got = (0,1); require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Right'); my ($initial_i, $prev) = $seq->next; my $run = 1; # count for initial $prev_turn while (@got < $count) { my ($i, $value) = $seq->next; if ($value == $prev) { $run++; } else { push @got, 2 * $run; $run = 1; # count for new $turn value } $prev = $value; } return \@got; }); #------------------------------------------------------------------------------ # A003460 -- turn 1=left,0=right packed as octal high to low, in 2^n levels # bit-packing per Gardner, pages 215-217 of reprint in "Mathematical Magic Show" MyOEIS::compare_values (anum => 'A003460', func => sub { my ($count) = @_; my @got; my $bits = Math::BigInt->new(0); my $target_n_level = 2; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Left'); for (my $n = 1; @got < $count; $n++) { if ($n >= $target_n_level) { # not including n=2^level point itself my $octal = $bits->as_oct; # new enough Math::BigInt $octal =~ s/^0+//; # strip leading "0" push @got, Math::BigInt->new("$octal"); $target_n_level *= 2; } my ($i, $value) = $seq->next; $bits = 2*$bits + $value; } return \@got; }); #------------------------------------------------------------------------------ # A082410 -- complement reversal, is turn 1=left, 0=right MyOEIS::compare_values (anum => 'A082410', func => sub { my ($count) = @_; my @got = (0); require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object=>$dragon, turn_type => 'Left'); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value; # 1=left,0=right } return \@got; }); #------------------------------------------------------------------------------ # A077949 join area increments, ie. first differences MyOEIS::compare_values (anum => 'A077949', max_value => 10_000, func => sub { my ($count) = @_; my @got; my $prev = 0; for (my $k = 3; @got < $count; $k++) { my $join_area = $dragon->_UNDOCUMENTED_level_to_enclosed_area_join($k); push @got, $join_area - $prev; $prev = $join_area; } return \@got; }); # A003479 join area MyOEIS::compare_values (anum => 'A003479', max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 3; @got < $count; $k++) { push @got, $dragon->_UNDOCUMENTED_level_to_enclosed_area_join($k); } return \@got; }); #------------------------------------------------------------------------------ # A003478 enclosed area increment, ie. first differences MyOEIS::compare_values (anum => 'A003478', max_value => 10_000, func => sub { my ($count) = @_; my @got; my $prev_area = 0; for (my $k = 4; @got < $count; $k++) { my $area = MyOEIS::path_enclosed_area ($dragon, 2**$k); push @got, $area - $prev_area; $prev_area = $area; } return \@got; }); #------------------------------------------------------------------------------ # A003230 enclosed area to N <= 2^k MyOEIS::compare_values (anum => 'A003230', max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 4; @got < $count; $k++) { push @got, MyOEIS::path_enclosed_area ($dragon, 2**$k); } return \@got; }); #------------------------------------------------------------------------------ # A164395 single points N=0 to N=2^k-1 inclusive, for k=4 up # is count binary with no substrings equal to 0001 or 0101 MyOEIS::compare_values (anum => 'A164395', max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 4; @got < $count; $k++) { push @got, MyOEIS::path_n_to_singles ($dragon, 2**$k - 1); } return \@got; }); #------------------------------------------------------------------------------ # A227036 boundary length N <= 2^k MyOEIS::compare_values (anum => 'A227036', max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length ($dragon, 2**$k); } return \@got; }); #------------------------------------------------------------------------------ # A038189 -- bit above lowest 1, is 0=left,1=right MyOEIS::compare_values (anum => 'A038189', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'DragonCurve', turn_type => 'Right'); my @got = (0); # extra initial 0 while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); # A089013=A038189 but initial extra 1 MyOEIS::compare_values (anum => 'A089013', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'DragonCurve', turn_type => 'Right'); my @got = (1); # extra initial 1 while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/CellularRule190-oeis.t0000644000175000017500000000607213475103011017247 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt; use Test; plan tests => 4; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::CellularRule190; #------------------------------------------------------------------------------ # A071039 - 0/1 by rows rule 190 MyOEIS::compare_values (anum => 'A071039', func => sub { my ($count) = @_; my $path = Math::PlanePath::CellularRule190->new; my @got; my $x = 0; my $y = 0; while (@got < $count) { push @got, ($path->xy_is_visited($x,$y) ? 1 : 0); $x++; if ($x > $y) { $y++; $x = -$y; } } return \@got; }); #------------------------------------------------------------------------------ # A118111 - 0/1 by rows rule 190 (duplicate) MyOEIS::compare_values (anum => 'A118111', func => sub { my ($count) = @_; my $path = Math::PlanePath::CellularRule190->new; my @got; my $x = 0; my $y = 0; while (@got < $count) { push @got, ($path->xy_is_visited($x,$y) ? 1 : 0); $x++; if ($x > $y) { $y++; $x = -$y; } } return \@got; }); #------------------------------------------------------------------------------ # A037576 - rows as rule 190 binary bignums (base 4 periodic ...) MyOEIS::compare_values (anum => 'A037576', func => sub { my ($count) = @_; my $path = Math::PlanePath::CellularRule190->new; my @got; my $y = 0; while (@got < $count) { my $b = 0; foreach my $i (0 .. 2*$y+1) { if ($path->xy_is_visited ($y-$i, $y)) { $b += Math::BigInt->new(2) ** $i; } } push @got, "$b"; $y++; } return \@got; }); #------------------------------------------------------------------------------ # A071041 - 0/1 rule 246 MyOEIS::compare_values (anum => 'A071041', func => sub { my ($count) = @_; my $path = Math::PlanePath::CellularRule190->new (mirror => 1); my @got; my $x = 0; my $y = 0; while (@got < $count) { push @got, ($path->xy_is_visited($x,$y) ? 1 : 0); $x++; if ($x > $y) { $y++; $x = -$y; } } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/LTiling-oeis.t0000644000175000017500000000501413475335441015775 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2015, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 4; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::LTiling; #------------------------------------------------------------------------------ # A112539 -- X+Y mod 2 MyOEIS::compare_values (anum => 'A112539', func => sub { my ($count) = @_; my $path = Math::PlanePath::LTiling->new (L_fill => 'left'); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, ($x+$y)%2; } return \@got; }); MyOEIS::compare_values (anum => q{A112539}, func => sub { my ($count) = @_; my $path = Math::PlanePath::LTiling->new (L_fill => 'upper'); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, ($x+$y)%2; } return \@got; }); MyOEIS::compare_values (anum => q{A112539}, func => sub { my ($count) = @_; my $path = Math::PlanePath::LTiling->new (L_fill => 'middle'); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, ($x+$y+1)%2; } return \@got; }); #------------------------------------------------------------------------------ # A048647 -- N at transpose Y,X MyOEIS::compare_values (anum => 'A048647', func => sub { my ($count) = @_; my $path = Math::PlanePath::LTiling->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); ($x, $y) = ($y, $x); my $n = $path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/Flowsnake-oeis.t0000644000175000017500000001102713733351251016357 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2016, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 5; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::Flowsnake; my $path = Math::PlanePath::Flowsnake->new; #------------------------------------------------------------------------------ # A334485 -- X coordinate # A334486 -- X coordinate # # Y 60 deg # ^ # / # ----> X MyOEIS::compare_values (anum => 'A334485', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, ($x-$y)/2; } return \@got; }); # ~/OEIS/b334486.txt # A334486 -- Y coordinate MyOEIS::compare_values (anum => 'A334486', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # centres of successive hexagons which approach the centre # # centre is 3/8 # centre of first hexagon in set of 7 is 3/56 # then next is a reverse so 2/7-3/56 == 13/56 # then in centre of set of 7 and is a reverse so go through 4 to reach centre # etc # A262147 numerators # 3, 13, 115, 125, 19, 141, 1011, 1021, 7171, 7181, 1027, 7197, 50403, # a(n) = 50*a(n-6)-49*a(n-12) for n>12 # A262148 denominators # 56, 56, 392, 392, 56, 392 then a(n) = 49*a(n-6) # 56==8*7 # 392==8*7^2 # #------------------------------------------------------------------------------ # A261180 - direction 0 to 5 # # *---*---* # \ \ / # *---* *---* # / # *---* # 0, 1, 3, 2, 0, 0, 5, 0, 1 { my %dxdy_to_dir6 = ('2,0' => 0, # 2 1 '1,1' => 1, # \ / '-1,1' => 2, # 3 ---*--- 0 '-2,0' => 3, # / \ '-1,-1' => 4, # 4 5 '1,-1' => 5); MyOEIS::compare_values (anum => 'A261180', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($dx,$dy) = $path->n_to_dxdy($n); my $dir = $dxdy_to_dir6{"$dx,$dy"}; die if ! defined $dir; push @got, $dir; } return \@got; }); # same, mod 2 MyOEIS::compare_values (anum => 'A261185', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($dx,$dy) = $path->n_to_dxdy($n); my $dir = $dxdy_to_dir6{"$dx,$dy"}; die if ! defined $dir; push @got, $dir % 2; } return \@got; }); } #------------------------------------------------------------------------------ # A229214 - direction 1,2,3,-1,-2,-3 # # *---*---* # \ \ / # *---* *---* # / # *---* # 1, 2, -1, 3, 1, 1 { my %dxdy_to_dirpn3 = ('2,0' => 1, # 3 2 '1,1' => 2, # \ / '-1,1' => 3, # -1 ---*--- 1 '-2,0' => -1, # / \ '-1,-1' => -2, # -2 -3 '1,-1' => -3); MyOEIS::compare_values (anum => 'A229214', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($dx,$dy) = $path->n_to_dxdy($n); my $dir = $dxdy_to_dirpn3{"$dx,$dy"}; die if ! defined $dir; push @got, $dir; } return \@got; }); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/NumSeq-PlanePath-oeis.t0000644000175000017500000003026613774425566017536 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Check PlanePathCoord etc sequences against OEIS data. # # The full catalogue takes a long time to run, hence the want_...() # restrictions below. use 5.004; use strict; use File::Spec; use Test; plan tests => 1; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines # use Smart::Comments '###'; sub want_anum { my ($anum) = @_; # return 0 unless $anum =~ /A338754/; # return 0 unless $anum =~ /A151922|A183060/; # return 0 unless $anum =~ /A177702|A102283|A131756/; return 1; } sub want_planepath { my ($planepath) = @_; # return 0 unless $planepath =~ /PyramidSpi/; # return 0 unless $planepath =~ /Flowsnake/; # return 0 unless $planepath =~ /Octag|Pent|Hept/; # return 0 unless $planepath =~ /Divis|DiagonalRationals|CoprimeCol/; # return 0 unless $planepath =~ /Rows/; # return 0 unless $planepath =~ /LCorn|RationalsTree/; # return 0 unless $planepath =~ /^Corner$/i; # return 0 unless $planepath =~ /SierpinskiArrowhead/; # return 0 unless $planepath =~ /TriangleSpiralSkewed/; # return 0 unless $planepath =~ /DiamondSpiral/; # return 0 unless $planepath =~ /AlternateTerdragon/; return 0 unless $planepath =~ /Corner|PyramidSp/; # return 0 unless $planepath =~ /Square/; return 1; } sub want_coordinate { my ($type) = @_; # return 0 unless $type =~ /^[XY]$/; # return 0 unless $type =~ /NotStraight/; # return 0 unless $type =~ /^Abs[XY]/; # return 0 unless $type =~ /dDiff/i; # return 0 unless $type =~ /ExperimentalPairsYX/; # return 0 unless $type =~ /SLR|SRL|LSR/; return 1; } #------------------------------------------------------------------------------ # use POSIX (); # use constant DBL_INT_MAX => (POSIX::FLT_RADIX() ** POSIX::DBL_MANT_DIG()); # use constant MY_MAX => (POSIX::FLT_RADIX() ** (POSIX::DBL_MANT_DIG()-5)); sub _delete_duplicates { my ($arrayref) = @_; my %seen; @seen{@$arrayref} = (); @$arrayref = sort {$a<=>$b} keys %seen; } sub _min { my $ret = shift; while (@_) { my $next = shift; if ($ret > $next) { $ret = $next; } } return $ret; } sub _max { my $ret = shift; while (@_) { my $next = shift; if ($next > $ret) { $ret = $next; } } return $ret; } my %duplicate_anum = (A021015 => 'A010680', A081274 => 'A038764', ); #------------------------------------------------------------------------------ my $good = 1; my $total_checks = 0; sub check_class { my ($anum, $class, $parameters) = @_; ### check_class() ... ### $class ### $parameters my %parameters = @$parameters; # return unless $class =~ /PlanePathTurn/; # return unless $parameters{'planepath'} =~ /DiagonalRat/i; # return unless $parameters{'planepath'} =~ /AlternateP/; # return unless $parameters{'planepath'} =~ /Peano/; # return unless $parameters{'planepath'} =~ /PyramidRows/; # return unless $parameters{'planepath'} =~ /Fib/; # return unless $parameters{'planepath'} =~ /TriangleSpiralSkewed/; return unless want_anum($anum); return unless want_planepath($parameters{'planepath'} || ''); return unless want_coordinate($parameters{'coordinate_type'} || $parameters{'delta_type'} || $parameters{'line_type'} || $parameters{'turn_type'} || ''); eval "require $class" or die; my $name = join(',', $class, map {defined $_ ? $_ : '[undef]'} @$parameters); my $max_count = undef; if ($anum eq 'A038567' || $anum eq 'A038566' || $anum eq 'A020652' || $anum eq 'A020653') { # CoprimeColumns, DiagonalRationals shortened for now $max_count = 10000; } elsif ($anum eq 'A051132') { # Hypot $max_count = 1000; } elsif ($anum eq 'A173027') { # WythoffPreiminaryTriangle $max_count = 3000; } my ($want, $want_i_start) = MyOEIS::read_values ($anum, max_count => $max_count) or do { MyTestHelpers::diag("skip $anum $name, no file data"); return; }; ### read_values len: scalar(@$want) ### $want_i_start if ($anum eq 'A009003') { # PythagoreanHypots slow, only first 250 values for now ... splice @$want, 250; } elsif ($anum eq 'A003434') { # TotientSteps slow, only first 250 values for now ... splice @$want, 250; } elsif ($anum eq 'A005408') { # odd numbers # shorten for CellularRule rule=84 etc splice @$want, 500; } my $want_count = scalar(@$want); MyTestHelpers::diag ("$anum $name ($want_count values to $want->[-1])"); my $hi = $want->[-1]; if ($hi < @$want) { $hi = @$want; } ### $hi # hi => $hi my $seq = $class->new (@$parameters); ### seq class: ref $seq if ($seq->isa('Math::NumSeq::OEIS::File')) { die "Oops, not meant to exercies $seq"; } { ### $seq my $got_anum = $seq->oeis_anum; if (! defined $got_anum) { $got_anum = 'undef'; } my $want_anum = $duplicate_anum{$anum} || $anum; if ($got_anum ne $want_anum) { $good = 0; MyTestHelpers::diag ("bad: $name"); MyTestHelpers::diag ("got anum $got_anum"); MyTestHelpers::diag ("want anum $want_anum"); MyTestHelpers::diag (ref $seq); } } { my $got_i_start = $seq->i_start; if (! defined $want_i_start) { MyTestHelpers::diag ("skip i_start check: \"stripped\" values only"); } elsif ($got_i_start != $want_i_start && $anum ne 'A000004' # offset=0, but allow other i_start here && $anum ne 'A000012' # offset=0, but allow other i_start here ) { $good = 0; MyTestHelpers::diag ("bad: $name"); MyTestHelpers::diag ("got i_start ",$got_i_start); MyTestHelpers::diag ("want i_start ",$want_i_start); } } { ### by next() ... my @got; my $got = \@got; while (my ($i, $value) = $seq->next) { push @got, $value; if (@got >= @$want) { last; } } my $diff = MyOEIS::diff_nums($got, $want); if (defined $diff) { $good = 0; MyTestHelpers::diag ("bad: $name by next() hi=$hi"); MyTestHelpers::diag ($diff); MyTestHelpers::diag (ref $seq); MyTestHelpers::diag ("got len ".scalar(@$got)); MyTestHelpers::diag ("want len ".scalar(@$want)); if ($#$got > 200) { $#$got = 200 } if ($#$want > 200) { $#$want = 200 } MyTestHelpers::diag ("got ". join(',', map {defined() ? $_ : 'undef'} @$got)); MyTestHelpers::diag ("want ". join(',', map {defined() ? $_ : 'undef'} @$want)); } } { ### by next() after rewind ... $seq->rewind; my @got; my $got = \@got; while (my ($i, $value) = $seq->next) { # ### $i # ### $value push @got, $value; if (@got >= @$want) { last; } } my $diff = MyOEIS::diff_nums($got, $want); if (defined $diff) { $good = 0; MyTestHelpers::diag ("bad: $name by rewind next() hi=$hi"); MyTestHelpers::diag ($diff); MyTestHelpers::diag (ref $seq); MyTestHelpers::diag ("got len ".scalar(@$got)); MyTestHelpers::diag ("want len ".scalar(@$want)); if ($#$got > 200) { $#$got = 200 } if ($#$want > 200) { $#$want = 200 } MyTestHelpers::diag ("got ". join(',', map {defined() ? $_ : 'undef'} @$got)); MyTestHelpers::diag ("want ". join(',', map {defined() ? $_ : 'undef'} @$want)); } } { ### by pred() ... $seq->can('pred') or next; if ($seq->characteristic('count')) { ### no pred on characteristic(count) .. next; } if (! $seq->characteristic('increasing')) { ### no pred on not characteristic(increasing) .. next; } if ($seq->characteristic('digits')) { ### no pred on characteristic(digits) .. next; } if ($seq->characteristic('modulus')) { ### no pred on characteristic(modulus) .. next; } if ($seq->characteristic('pn1')) { ### no pred on characteristic(pn1) .. next; } $hi = 0; foreach my $want (@$want) { if ($want > $hi) { $hi = $want } } if ($hi > 1000) { $hi = 1000; $want = [ grep {$_<=$hi} @$want ]; } _delete_duplicates($want); #### $want my @got; foreach my $value (_min(@$want) .. $hi) { #### $value if ($seq->pred($value)) { push @got, $value; } } my $got = \@got; my $diff = MyOEIS::diff_nums($got, $want); if (defined $diff) { $good = 0; MyTestHelpers::diag ("bad: $name by pred() hi=$hi"); MyTestHelpers::diag ($diff); MyTestHelpers::diag (ref $seq); MyTestHelpers::diag ("got len ".scalar(@$got)); MyTestHelpers::diag ("want len ".scalar(@$want)); if ($#$got > 200) { $#$got = 200 } if ($#$want > 200) { $#$want = 200 } MyTestHelpers::diag ("got ". join(',', map {defined() ? $_ : 'undef'} @$got)); MyTestHelpers::diag ("want ". join(',', map {defined() ? $_ : 'undef'} @$want)); } { my $data_min = _min(@$want); my $values_min = $seq->values_min; if (defined $values_min && $values_min != $data_min) { $good = 0; MyTestHelpers::diag ("bad: $name values_min $values_min but data min $data_min"); } } { my $data_max = _max(@$want); my $values_max = $seq->values_max; if (defined $values_max && $values_max != $data_max) { $good = 0; MyTestHelpers::diag ("bad: $name values_max $values_max not seen in data, only $data_max"); } } } $total_checks++; } #------------------------------------------------------------------------------ # extras # check_class ('A059906', # ZOrderCurve second bit # 'Math::NumSeq::PlanePathCoord', # [ planepath => 'CornerReplicate', # coordinate_type => 'Y' ]); # exit 0; #------------------------------------------------------------------------------ # OEIS-Other vs files MyTestHelpers::diag ("\"Other\" uncatalogued sequences:"); { system("perl ../ns/tools/make-oeis-catalogue.pl --module=TempOther --other=only") == 0 or die; my $filename = File::Spec->rel2abs('lib/Math/NumSeq/OEIS/Catalogue/Plugin/TempOther.pm'); require $filename; unlink $filename or die "cannot unlink $filename: $!"; my $aref = Math::NumSeq::OEIS::Catalogue::Plugin::TempOther::info_arrayref(); foreach my $info (@$aref) { ### $info check_class ($info->{'anum'}, $info->{'class'}, $info->{'parameters'}); } MyTestHelpers::diag (""); } #------------------------------------------------------------------------------ # OEIS-Catalogue generated vs files MyTestHelpers::diag ("Catalogue sequences:"); { require Math::NumSeq::OEIS::Catalogue::Plugin::PlanePath; my $aref = Math::NumSeq::OEIS::Catalogue::Plugin::PlanePath->info_arrayref(); { require Math::NumSeq::OEIS::Catalogue::Plugin::PlanePathToothpick; my $aref2 = Math::NumSeq::OEIS::Catalogue::Plugin::PlanePathToothpick->info_arrayref(); $aref = [ @$aref, @$aref2 ]; } MyTestHelpers::diag ("total catalogue entries ",scalar(@$aref)); foreach my $info (@$aref) { ### $info check_class ($info->{'anum'}, $info->{'class'}, $info->{'parameters'}); } } MyTestHelpers::diag ("total checks $total_checks"); ok ($good); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/SierpinskiArrowhead-oeis.t0000644000175000017500000001155513717076171020420 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2018, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 7; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::SierpinskiArrowhead; use Math::NumSeq::PlanePathTurn; use MyOEIS; #------------------------------------------------------------------------------ # A334483 -- X coordinate of "diagonal" # A334484 -- Y coordinate of "diagonal" # catalogued # my(g=OEIS_bfile_gf("A334483")); x(n) = polcoeff(g,n); # my(g=OEIS_bfile_gf("A334484")); y(n) = polcoeff(g,n); # plothraw(vector(3^5,n,n--; x(n)), \ # vector(3^5,n,n--; y(n)), 1+8+16+32) # #------------------------------------------------------------------------------ # A189706 - turn sequence odd positions MyOEIS::compare_values (anum => 'A189706', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SierpinskiArrowhead', turn_type => 'Right'); my @got; for (my $i = 1; @got < $count; $i+=2) { push @got, $seq->ith($i); } return \@got; }); # A189706 = lowest non-1 and its position MyOEIS::compare_values (anum => q{A189706}, func => sub { my ($count) = @_; my @got; foreach my $i (0 .. $count-1) { push @got, lowest_non_1_xor_position($i); } return \@got; }); sub lowest_non_1_xor_position { my ($n) = @_; my $ret = 1; while (($n % 3) == 1) { $ret ^= 1; # flip for trailing 1s $n = int($n/3); } if (($n % 3) == 0) { $ret ^= 1; } return $ret; } #------------------------------------------------------------------------------ # A189707 - (N+1)/2 of positions of odd N left turns MyOEIS::compare_values (anum => 'A189707', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SierpinskiArrowhead', turn_type => 'Left'); my @got; for (my $i = 1; @got < $count; $i+=2) { my $left = $seq->ith($i); if ($left) { push @got, ($i+1)/2; } } return \@got; }); #------------------------------------------------------------------------------ # A189708 - (N+1)/2 of positions of odd N right turns MyOEIS::compare_values (anum => 'A189708', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SierpinskiArrowhead', turn_type => 'Right'); my @got; for (my $i = 1; @got < $count; $i+=2) { my $right = $seq->ith($i); if ($right) { push @got, ($i+1)/2; } } return \@got; }); #------------------------------------------------------------------------------ # A156595 - turn sequence even positions MyOEIS::compare_values (anum => 'A156595', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SierpinskiArrowhead', turn_type => 'Right'); my @got; for (my $i = 2; @got < $count; $i+=2) { push @got, $seq->ith($i); } return \@got; }); # A156595 = lowest non-2 and its position starting at n=0 MyOEIS::compare_values (anum => q{A156595}, name => 'A156595 by lowest non-2 and position', func => sub { my ($count) = @_; my @got; foreach my $i (0 .. $count-1) { push @got, lowest_non_2_xor_position($i); } return \@got; }); sub lowest_non_2_xor_position { my ($n) = @_; my $ret = 1; while (($n % 3) == 2) { $ret ^= 1; # flip for trailing 1s $n = int($n/3); } if (($n % 3) == 0) { $ret ^= 1; } return $ret; } # A156595 = lowest non-0 and its position starting at n=1 (per seq OFFSET) MyOEIS::compare_values (anum => q{A156595}, name => 'A156595 by lowest non-0 and position', func => sub { my ($count) = @_; my @got; foreach my $i (0 .. $count-1) { push @got, lowest_non_0_xor_position($i); } return \@got; }); sub lowest_non_0_xor_position { my ($n) = @_; my $ret = 0; while (($n % 3) == 2) { $ret ^= 1; # flip for trailing 1s $n = int($n/3); } $ret ^= ($n % 3); return $ret & 1; } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/DiagonalsAlternating-oeis.t0000644000175000017500000001343013717103307020517 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2015, 2018, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 11; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::DiagonalsAlternating; #------------------------------------------------------------------------------ # A319571 -- X,Y coordinates MyOEIS::compare_values (anum => 'A319571', func => sub { my ($count) = @_; my $path = Math::PlanePath::DiagonalsAlternating->new (n_start => 0); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; @got < $count or last; push @got, $y; } return \@got; }); # A319572 -- X coordinate MyOEIS::compare_values (anum => 'A319572', func => sub { my ($count) = @_; my $path = Math::PlanePath::DiagonalsAlternating->new (n_start => 0); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); # A319573 -- Y coordinate MyOEIS::compare_values (anum => 'A319573', func => sub { my ($count) = @_; my $path = Math::PlanePath::DiagonalsAlternating->new (n_start => 0); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A056011 -- permutation N at points by Diagonals,direction=up order MyOEIS::compare_values (anum => 'A056011', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::DiagonalsAlternating->new; my $diag = Math::PlanePath::Diagonals->new (direction => 'up'); for (my $n = $diag->n_start; @got < $count; $n++) { my ($x, $y) = $diag->n_to_xy ($n); push @got, $path->xy_to_n ($x,$y); } return \@got; }); # is self-inverse MyOEIS::compare_values (anum => q{A056011}, func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::Diagonals->new (direction => 'up'); my $diag = Math::PlanePath::DiagonalsAlternating->new; for (my $n = $diag->n_start; @got < $count; $n++) { my ($x, $y) = $diag->n_to_xy ($n); push @got, $path->xy_to_n ($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A056023 -- permutation N at points by Diagonals,direction=up order MyOEIS::compare_values (anum => 'A056023', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::DiagonalsAlternating->new; my $diag = Math::PlanePath::Diagonals->new (direction => 'down'); for (my $n = $diag->n_start; @got < $count; $n++) { my ($x, $y) = $diag->n_to_xy ($n); push @got, $path->xy_to_n ($x,$y); } return \@got; }); # is self-inverse MyOEIS::compare_values (anum => q{A056023}, func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::Diagonals->new (direction => 'down'); my $diag = Math::PlanePath::DiagonalsAlternating->new; for (my $n = $diag->n_start; @got < $count; $n++) { my ($x, $y) = $diag->n_to_xy ($n); push @got, $path->xy_to_n ($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A038722 -- permutation N at transpose Y,X n_start=1 MyOEIS::compare_values (anum => 'A038722', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::DiagonalsAlternating->new; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n ($y, $x); } return \@got; }); #------------------------------------------------------------------------------ # A061579 -- permutation N at transpose Y,X MyOEIS::compare_values (anum => 'A061579', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::DiagonalsAlternating->new (n_start => 0); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n ($y, $x); } return \@got; }); #------------------------------------------------------------------------------ # A131179 -- X axis, extra 0 MyOEIS::compare_values (anum => 'A131179', func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::DiagonalsAlternating->new; for (my $x = 0; @got < $count; $x++) { push @got, $path->xy_to_n ($x, 0); } return \@got; }); #------------------------------------------------------------------------------ # A128918 -- Y axis, extra 0 MyOEIS::compare_values (anum => 'A128918', func => sub { my ($count) = @_; my @got = (1); my $path = Math::PlanePath::DiagonalsAlternating->new; for (my $y = 0; @got < $count; $y++) { push @got, $path->xy_to_n (0, $y); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/DiagonalsOctant-oeis.t0000644000175000017500000002076613775044410017514 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2018, 2019, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt try => 'GMP'; use Test; plan tests => 13; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::DiagonalsOctant; use Math::PlanePath::Diagonals; use Math::PlanePath::PyramidRows; #------------------------------------------------------------------------------ # A274427 -- DiagonalsOctant part of Diagonals MyOEIS::compare_values (anum => 'A274427', func => sub { my ($count) = @_; my @got; my $oct = Math::PlanePath::DiagonalsOctant->new; my $all = Math::PlanePath::Diagonals->new; for (my $n = $oct->n_start; @got < $count; $n++) { my ($x,$y) = $oct->n_to_xy($n); push @got, $all->xy_to_n($x,$y); } my $path = Math::PlanePath::DiagonalsOctant->new (n_start => 0); return \@got; }); #------------------------------------------------------------------------------ # A079826 -- concat of rows numbers in diagonals octant order # rows numbered alternately left and right MyOEIS::compare_values (anum => q{A079826}, # not xreffed max_count => 10, # various dodginess from a(11)=785753403227 func => sub { my ($count) = @_; my @got; my $diag = Math::PlanePath::DiagonalsOctant->new; my $rows = Math::PlanePath::PyramidRows->new(step=>1); my $prev_d = 0; my $str = ''; for (my $n = Math::BigInt->new($diag->n_start); @got < $count; $n++) { my ($x,$y) = $diag->n_to_xy($n); my $d = $x+$y; if ($d != $prev_d) { push @got, Math::BigInt->new($str); $str = ''; $prev_d = $d; } if ($y % 2) { $x = $y-$x; } my $rn = $rows->xy_to_n($x,$y); if ($rn >= 73) { $rn -= 2; } if ($rn >= 99) { $rn -= 2; } if ($rn >= 129) { $rn -= 2; } $str .= $rn; } return \@got; }); # foreach my $y (0 .. 21) { # foreach my $x (0 .. $y) { # # if ($x+$y > 11) { # # print "..."; # # last; # # } # my $n = $rows->xy_to_n(($y % 2 ? $y-$x : $x), $y); # printf "%4d", $n; # } # print "\n"; # } #------------------------------------------------------------------------------ # A014616 -- N in column X=1 MyOEIS::compare_values (anum => 'A014616', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::DiagonalsOctant->new (direction => 'up', n_start => 0); for (my $y = 1; @got < $count; $y++) { push @got, $path->xy_to_n (1,$y); } return \@got; }); #------------------------------------------------------------------------------ # A079823 -- concat of rows numbers in diagonals octant order MyOEIS::compare_values (anum => q{A079823}, # not xreffed func => sub { my ($count) = @_; my @got; my $diag = Math::PlanePath::DiagonalsOctant->new; my $rows = Math::PlanePath::PyramidRows->new(step=>1); my $prev_d = 0; my $str = ''; for (my $n = $diag->n_start; @got < $count; $n++) { my ($x,$y) = $diag->n_to_xy($n); my $d = $x+$y; if ($d != $prev_d) { push @got, $str; $str = ''; $prev_d = $d; } $str .= $rows->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A091018 -- permutation diagonals octant -> rows, 0 based MyOEIS::compare_values (anum => 'A091018', func => sub { my ($count) = @_; my @got; my $diag = Math::PlanePath::DiagonalsOctant->new; my $rows = Math::PlanePath::PyramidRows->new(step=>1); for (my $n = $diag->n_start; @got < $count; $n++) { my ($x,$y) = $diag->n_to_xy($n); push @got, $rows->xy_to_n($x,$y) - 1; } return \@got; }); #------------------------------------------------------------------------------ # A090894 -- permutation diagonals octant -> rows, 0 based, upwards MyOEIS::compare_values (anum => 'A090894', func => sub { my ($count) = @_; my @got; my $diag = Math::PlanePath::DiagonalsOctant->new(direction=>'up'); my $rows = Math::PlanePath::PyramidRows->new(step=>1); for (my $n = $diag->n_start; @got < $count; $n++) { my ($x,$y) = $diag->n_to_xy($n); push @got, $rows->xy_to_n($x,$y) - 1; } return \@got; }); #------------------------------------------------------------------------------ # A091995 -- permutation diagonals octant -> rows, 1 based, upwards MyOEIS::compare_values (anum => 'A091995', func => sub { my ($count) = @_; my @got; my $diag = Math::PlanePath::DiagonalsOctant->new(direction=>'up'); my $rows = Math::PlanePath::PyramidRows->new(step=>1); for (my $n = $diag->n_start; @got < $count; $n++) { my ($x,$y) = $diag->n_to_xy($n); push @got, $rows->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A056536 -- permutation diagonals octant -> rows MyOEIS::compare_values (anum => 'A056536', func => sub { my ($count) = @_; my @got; my $diag = Math::PlanePath::DiagonalsOctant->new; my $rows = Math::PlanePath::PyramidRows->new(step=>1); for (my $n = $diag->n_start; @got < $count; $n++) { my ($x,$y) = $diag->n_to_xy($n); push @got, $rows->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A056537 -- permutation rows -> diagonals octant MyOEIS::compare_values (anum => 'A056537', func => sub { my ($count) = @_; my @got; my $diag = Math::PlanePath::DiagonalsOctant->new; my $rows = Math::PlanePath::PyramidRows->new(step=>1); for (my $n = $rows->n_start; @got < $count; $n++) { my ($x,$y) = $rows->n_to_xy($n); push @got, $diag->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A004652 -- N start,end of even diagonals MyOEIS::compare_values (anum => 'A004652', func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::DiagonalsOctant->new; for (my $y = 0; @got < $count; $y += 2) { push @got, $path->xy_to_n (0,$y); last unless @got < $count; push @got, $path->xy_to_n ($y/2,$y/2); } return \@got; }); #------------------------------------------------------------------------------ # A002620 -- N end each diagonal, extra initial 0s MyOEIS::compare_values (anum => 'A002620', func => sub { my ($count) = @_; my @got = (0,0); my $path = Math::PlanePath::DiagonalsOctant->new; for (my $x = 0; @got < $count; $x++) { push @got, $path->xy_to_n ($x,$x); last unless @got < $count; push @got, $path->xy_to_n ($x,$x+1); } return \@got; }); MyOEIS::compare_values (anum => 'A002620', func => sub { my ($count) = @_; my @got = (0,0); my $path = Math::PlanePath::DiagonalsOctant->new (direction => 'up'); for (my $y = 0; @got < $count; $y++) { push @got, $path->xy_to_n (0,$y); } return \@got; }); #------------------------------------------------------------------------------ # A092180 -- primes in rows, traversed by DiagonalOctant MyOEIS::compare_values (anum => q{A092180}, # not cross-reffed in docs func => sub { my ($count) = @_; my @got; my $diag = Math::PlanePath::DiagonalsOctant->new(direction=>'up'); my $rows = Math::PlanePath::PyramidRows->new(step=>1); for (my $n = $diag->n_start; @got < $count; $n++) { my ($x,$y) = $diag->n_to_xy($n); push @got, MyOEIS::ith_prime($rows->xy_to_n($x,$y)); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/QuintetReplicate-oeis.t0000644000175000017500000000401713643613664017722 0ustar gggg#!/usr/bin/perl -w # Copyright 2013, 2014, 2015, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt; use Test; plan tests => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::QuintetReplicate; #------------------------------------------------------------------------------ # A316657 -- X # A316658 -- Y # A316707 -- norm MyOEIS::compare_values (anum => 'A316657', func => sub { my ($count) = @_; my $path = Math::PlanePath::QuintetReplicate->new; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); push @got, $x; } return \@got; }); MyOEIS::compare_values (anum => 'A316658', func => sub { my ($count) = @_; my $path = Math::PlanePath::QuintetReplicate->new; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); push @got, $y; } return \@got; }); MyOEIS::compare_values (anum => 'A316707', func => sub { my ($count) = @_; my $path = Math::PlanePath::QuintetReplicate->new; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); push @got, $x**2 + $y**2; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/ImaginaryBase-oeis.t0000644000175000017500000001001513475106607017143 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 5; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::ImaginaryBase; use Math::PlanePath::Diagonals; use Math::PlanePath::Base::Digits 'bit_split_lowtohigh'; #------------------------------------------------------------------------------ # A057300 -- N at transpose Y,X, radix=2 MyOEIS::compare_values (anum => 'A057300', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::ImaginaryBase->new; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); ($x, $y) = ($y, $x); my $n = $path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ # A163327 -- N at transpose Y,X, radix=3 MyOEIS::compare_values (anum => 'A163327', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::ImaginaryBase->new (radix => 3); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); ($x, $y) = ($y, $x); my $n = $path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ # A126006 -- N at transpose Y,X, radix=4 MyOEIS::compare_values (anum => 'A126006', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::ImaginaryBase->new (radix => 4); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); ($x, $y) = ($y, $x); my $n = $path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ # A217558 -- N at transpose Y,X, radix=16 MyOEIS::compare_values (anum => 'A217558', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::ImaginaryBase->new (radix => 16); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); ($x, $y) = ($y, $x); my $n = $path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ # A039724 -- negabinary positives -> index, written in binary MyOEIS::compare_values (anum => q{A039724}, func => sub { my ($count) = @_; my @got; require Math::PlanePath::ZOrderCurve; my $path = Math::PlanePath::ImaginaryBase->new; my $zorder = Math::PlanePath::ZOrderCurve->new; for (my $nega = 0; @got < $count; $nega++) { my $n = $path->xy_to_n ($nega,0); $n = delete_odd_bits($n); push @got, to_binary($n); } return \@got; }); sub delete_odd_bits { my ($n) = @_; my @bits = bit_split_lowtohigh($n); my $bit = 1; my $ret = 0; while (@bits) { if (shift @bits) { $ret |= $bit; } shift @bits; $bit <<= 1; } return $ret; } # or by string ... # if (length($str) & 1) { $str = "0$str" } # $str =~ s/.(.)/$1/g; sub to_binary { my ($n) = @_; return ($n < 0 ? '-' : '') . sprintf('%b', abs($n)); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/DiagonalRationals-oeis.t0000644000175000017500000001034113774446150020027 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2019, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 6; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::DiagonalRationals; use Math::PlanePath::RationalsTree; my $diagrat = Math::PlanePath::DiagonalRationals->new; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # A038567 -- X+Y except no 0/1 in path MyOEIS::compare_values (anum => 'A038567', max_count => 10000, func => sub { my ($count) = @_; my @got = (1); for (my $n = $diagrat->n_start; @got < $count; $n++) { my ($x, $y) = $diagrat->n_to_xy ($n); push @got, $x+$y; } return \@got; }); #------------------------------------------------------------------------------ # A054430 -- N at transpose Y,X MyOEIS::compare_values (anum => 'A054430', func => sub { my ($count) = @_; my @got; for (my $n = $diagrat->n_start; @got < $count; $n++) { my ($x, $y) = $diagrat->n_to_xy ($n); ($x, $y) = ($y, $x); my $n = $diagrat->xy_to_n ($x, $y); push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ # A054431 - by anti-diagonals 1 if coprime, 0 if not MyOEIS::compare_values (anum => 'A054431', func => sub { my ($count) = @_; my @got; my $prev_n = $diagrat->n_start - 1; OUTER: for (my $y = 1; ; $y ++) { foreach my $x (1 .. $y-1) { my $n = $diagrat->xy_to_n($x,$y-$x); if (defined $n) { push @got, 1; if ($n != $prev_n + 1) { die "oops, not n+1"; } $prev_n = $n; } else { push @got, 0; } last OUTER if @got >= $count; } } return \@got; }); #------------------------------------------------------------------------------ # A054424 - permutation diagonal N -> SB N # A054426 - inverse SB N -> Cantor N MyOEIS::compare_values (anum => 'A054424', func => sub { my ($count) = @_; require Math::PlanePath::RationalsTree; my $sb = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my @got; foreach my $n (1 .. $count) { my ($x,$y) = $diagrat->n_to_xy ($n); push @got, $sb->xy_to_n($x,$y); } return \@got; }); MyOEIS::compare_values (anum => 'A054426', func => sub { my ($count) = @_; require Math::PlanePath::RationalsTree; my $sb = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my @got; foreach my $n (1 .. $count) { my ($x,$y) = $sb->n_to_xy ($n); push @got, $diagrat->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A054425 - A054424 mapping expanded out to 0s at common-factor X,Y MyOEIS::compare_values (anum => 'A054425', func => sub { my ($count) = @_; require Math::PlanePath::Diagonals; require Math::PlanePath::RationalsTree; my $diag = Math::PlanePath::Diagonals->new (x_start=>1, y_start=>1); my $sb = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my @got; for (my $n = $diag->n_start; @got < $count; $n++) { my ($x,$y) = $diag->n_to_xy($n); ### frac: "$x/$y" my $cn = $diagrat->xy_to_n ($x,$y); if (defined $cn) { push @got, $sb->xy_to_n($x,$y); } else { push @got, 0; } } return \@got; }); exit 0; Math-PlanePath-129/xt/oeis/TheodorusSpiral-oeis.t0000644000175000017500000000611413244716337017565 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 8; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::TheodorusSpiral; #------------------------------------------------------------------------------ # A172164 -- differences of loop lengths MyOEIS::compare_values (anum => 'A172164', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::TheodorusSpiral->new; my $n = $path->n_start + 1; my ($prev_x, $prev_y) = $path->n_to_xy ($n); my $prev_n = 1; my $prev_looplen = 0; my $first = 1; for ($n++; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); if ($y > 0 && $prev_y < 0) { my $looplen = $n-$prev_n; if ($first) { $first = 0; } else { push @got, $looplen - $prev_looplen; } $prev_n = $n; $prev_looplen = $looplen; } ($prev_x, $prev_y) = ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ # A137515 -- right triangles in n turns # 16, 53, 109, 185, 280, 395, 531, 685, 860, 1054, 1268, 1502, 1756, MyOEIS::compare_values (anum => 'A137515', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::TheodorusSpiral->new; my $n = $path->n_start + 1; my ($prev_x, $prev_y) = $path->n_to_xy ($n); for ($n++; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); if ($y > 0 && $prev_y < 0) { push @got, $n-2; } ($prev_x, $prev_y) = ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ # A072895 -- points to complete n revolutions # 17, 54, 110, 186, 281, 396, 532, 686, 861, 1055, 1269, 1503, 1757, MyOEIS::compare_values (anum => 'A072895', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::TheodorusSpiral->new; my $n = $path->n_start + 2; my ($prev_x, $prev_y) = $path->n_to_xy ($n); for ($n++; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); if ($y >= 0 && $prev_y <= 0) { push @got, $n-1; } ($prev_x, $prev_y) = ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/CellularRule54-oeis.t0000644000175000017500000000377713475103216017206 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2015, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 2; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::CellularRule54; my $path = Math::PlanePath::CellularRule54->new; #------------------------------------------------------------------------------ # A118109 - 0/1 by rows MyOEIS::compare_values (anum => 'A118109', func => sub { my ($count) = @_; my @got; for (my $y = 0; @got < $count; $y++) { my $str = ''; my $x = 0; foreach my $x (-$y .. $y) { $str .= ($path->xy_is_visited($x,$y) ? 1 : 0); } push @got, $str; } return \@got; }); #------------------------------------------------------------------------------ # A118108 - rows as bignum bits in decimal MyOEIS::compare_values (anum => 'A118108', func => sub { my ($count) = @_; require Math::BigInt; my @got; my $y = 0; foreach my $n (1 .. $count) { my $b = 0; foreach my $i (0 .. 2*$y+1) { if ($path->xy_to_n ($y-$i, $y)) { $b += Math::BigInt->new(2) ** $i; } } push @got, "$b"; $y++; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/RationalsTree-oeis.t0000644000175000017500000011236713760375641017224 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2017, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # cf A152975/A152976 redundant Stern-Brocot # inserting mediants to make ternary tree use 5.004; use strict; use Math::BigInt; use Test; plan tests => 58; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::RationalsTree; use Math::PlanePath::Base::Digits 'bit_split_lowtohigh','digit_join_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments '###'; sub gcd { my ($x, $y) = @_; #### _gcd(): "$x,$y" if ($y > $x) { $y %= $x; } for (;;) { if ($y <= 1) { return ($y == 0 ? $x : 1); } ($x,$y) = ($y, $x % $y); } } # GP-DEFINE read("my-oeis.gp"); #------------------------------------------------------------------------------ # A004755 -- map SB f -> f+1 MyOEIS::compare_values (anum => 'A004755', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $path->xy_to_n($x+$y,$y); # frac + 1 } return \@got; }); #------------------------------------------------------------------------------ # A065249 -- permutation SB f -> f/2 # A065250 -- permutation SB f -> 2f MyOEIS::compare_values (anum => 'A065249', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($x%2==0) { $x /= 2; } else { $y *= 2; } # frac/2 push @got, $path->xy_to_n($x,$y); } return \@got; }); MyOEIS::compare_values (anum => 'A065250', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($y%2==0) { $y /= 2; } else { $x *=2; } # frac*2 push @got, $path->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A153778 - SB parity X mod 2 # rationals.tex on sum bits alternating signs mod 3 MyOEIS::compare_values (anum => 'A153778', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x % 2; } return \@got; }); #------------------------------------------------------------------------------ # A258996 -- permutation CW<->Drib both ways # n=2^m+k, m>1, 0<=k<2^m # If m even, then a(2^(m+1) +k) = a(2^m+k) + 2^m # a(2^(m+1)+2^m+k) = a(2^m+k) + 2^(m+1) # If m odd, then a(2^(m+1) +k) = a(2^m+k) + 2^(m+1) # a(2^(m+1)+2^m+k) = a(2^m+k) + 2^m # # flip alternate bits starting from second lowest then upwards and most # singificant unchanged # # A258996(n) = my(v=binary(n)); forstep(i=#v-1,2,-2, v[i]=1-v[i]); fromdigits(v,2); # vector(20,n,A258996(n)) # # differences abs(n-a(n)) are then +/-1 at alternate bit positions # Set(vector(500,n,abs(n-A258996(n))))/2 MyOEIS::compare_values (anum => 'A258996', func => sub { my ($count) = @_; my $cw = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); my $drib = Math::PlanePath::RationalsTree->new (tree_type => 'Drib'); my @got; for (my $n = $cw->n_start; @got < $count; $n++) { my ($x, $y) = $cw->n_to_xy($n); push @got, $drib->xy_to_n($x,$y); } return \@got; }); MyOEIS::compare_values (anum => q{A258996}, func => sub { my ($count) = @_; my $cw = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); my $drib = Math::PlanePath::RationalsTree->new (tree_type => 'Drib'); my @got; for (my $n = $cw->n_start; @got < $count; $n++) { my ($x, $y) = $drib->n_to_xy($n); push @got, $cw->xy_to_n($x,$y); } return \@got; }); # return $n with every second bit flipped, starting from the second least # significant, and leaving the most significant unchanged sub flip_alternate_bits_lowtohigh { my ($n) = @_; my @bits = bit_split_lowtohigh($n); for (my $i = 1; $i < $#bits; $i+=2) { $bits[$i] ^= 1; } return digit_join_lowtohigh(\@bits,2); } MyOEIS::compare_values (anum => q{A258996}, func => sub { my ($count) = @_; my @got; for (my $n = 1; @got < $count; $n++) { push @got, flip_alternate_bits_lowtohigh($n); } return \@got; }); #------------------------------------------------------------------------------ # A258746 -- permutation SB<->Bird both ways # m=floor(log2(n)) # If m even, a(2*n) = 2*a(n) # a(2*n+1) = 2*a(n)+1 # If m odd, a(2*n) = 2*a(n)+1 # a(2*n+1) = 2*a(n) # flip alternate bits starting from third highest then downwards # # A258746(n) = my(v=binary(n)); forstep(i=3,#v,2, v[i]=1-v[i]); fromdigits(v,2); # vector(20,n,A258746(n)) # # differences abs(n-a(n)) are then +/-1 at alternate bit positions # Set(vector(500,n,n+=4; abs(n-A258746(n)))) # # gp 2.9.1 fromdigits() wrong result for negative digits when base=2^k, must subst() # A147992(n) = my(v=apply(d->2*d-1,binary(n)));v[1]=1;subst(Pol(v),'x,4); # vector(20,n,A147992(n)) # Set(concat(vector(20,n,A147992(n)),2*vector(20,n,A147992(n)))) # # union A147992 and 2*A147992 # which since A147992 always odd are disjoint # setintersect(Set(vector(20,n,A147992(n))),Set(2*vector(20,n,A147992(n)))) MyOEIS::compare_values (anum => 'A258746', func => sub { my ($count) = @_; my $sb = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my $bird = Math::PlanePath::RationalsTree->new (tree_type => 'Bird'); my @got; for (my $n = $sb->n_start; @got < $count; $n++) { my ($x, $y) = $sb->n_to_xy($n); push @got, $bird->xy_to_n($x,$y); } return \@got; }); MyOEIS::compare_values (anum => q{A258746}, func => sub { my ($count) = @_; my $sb = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my $bird = Math::PlanePath::RationalsTree->new (tree_type => 'Bird'); my @got; for (my $n = $sb->n_start; @got < $count; $n++) { my ($x, $y) = $bird->n_to_xy($n); push @got, $sb->xy_to_n($x,$y); } return \@got; }); # return $n with every second bit flipped, starting from the third most # significant and proceeding downwards sub flip_alternate_bits_hightolow { my ($n) = @_; my @bits = bit_split_lowtohigh($n); for (my $i = $#bits-2; $i >= 0; $i-=2) { $bits[$i] ^= 1; } return digit_join_lowtohigh(\@bits,2); } MyOEIS::compare_values (anum => q{A258746}, func => sub { my ($count) = @_; my @got; for (my $n = 1; @got < $count; $n++) { push @got, flip_alternate_bits_hightolow($n); } return \@got; }); #------------------------------------------------------------------------------ # A092569 -- permutation flip bits except most significant and least significant # HCS -> Bird MyOEIS::compare_values (anum => 'A092569', func => sub { my ($count) = @_; my $hcs = Math::PlanePath::RationalsTree->new (tree_type => 'HCS'); my $bird = Math::PlanePath::RationalsTree->new (tree_type => 'Bird'); my @got; for (my $n = 0; @got < $count; $n++) { my @bits = bit_split_lowtohigh($n); foreach my $i (1 .. $#bits-1) { $bits[$i] ^= 1; } push @got, digit_join_lowtohigh(\@bits,2); } return \@got; }); #------------------------------------------------------------------------------ # A153153 -- permutation CW->AYT MyOEIS::compare_values (anum => 'A153153', func => sub { my ($count) = @_; my $ayt = Math::PlanePath::RationalsTree->new (tree_type => 'AYT'); my $cw = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); my @got = (0); # initial 0 for (my $n = $cw->n_start; @got < $count; $n++) { my ($x, $y) = $cw->n_to_xy($n); push @got, $ayt->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A153154 -- permutation AYT->CW MyOEIS::compare_values (anum => 'A153154', func => sub { my ($count) = @_; my $ayt = Math::PlanePath::RationalsTree->new (tree_type => 'AYT'); my $cw = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); my @got = (0); # initial 0 for (my $n = $ayt->n_start; @got < $count; $n++) { my ($x, $y) = $ayt->n_to_xy($n); push @got, $cw->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A044051 N+1 of those N where SB and CW gives same X,Y # being binary palindromes below high 1-bit MyOEIS::compare_values (anum => 'A044051', func => sub { my ($count) = @_; my $sb = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my $cw = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); my @got = (1); for (my $n = $sb->n_start; @got < $count; $n++) { my ($x1,$y1) = $sb->n_to_xy($n) or die; my ($x2,$y2) = $cw->n_to_xy($n) or die; if ($x1 == $x2 && $y1 == $y2) { push @got, $n + 1; } } return \@got; }); #------------------------------------------------------------------------------ # A008776 total X+Y across row, 2*3^depth MyOEIS::compare_values (anum => 'A008776', max_count => 14, func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new; my @got; for (my $depth = 0; @got < $count; $depth++) { my ($n_lo, $n_hi) = $path->tree_depth_to_n_range($depth); my $total = 0; foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy ($n); $total += $x + $y; } push @got, $total; } return \@got; }); #------------------------------------------------------------------------------ # A000975 -- 010101 without consecutive equal bits, Bird tree X=1 column MyOEIS::compare_values (anum => 'A000975', max_count => 100, name => "Bird column X=1", func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'Bird'); my @got = (0); # extra initial 0 in A000975 for (my $y = Math::BigInt->new(1); @got < $count; $y++) { push @got, $path->xy_to_n (1, $y); } return \@got; }); #------------------------------------------------------------------------------ # A061547 -- 010101 without consecutive equal bits, Drib tree X=1 column # Y/1 in Drib, extra initial 0 in A061547 MyOEIS::compare_values (anum => 'A061547', max_count => 100, name => "Drib column X=1", func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'Drib'); my @got = (0); # extra initial 0 in A061547 for (my $y = Math::BigInt->new(1); @got < $count; $y++) { push @got, $path->xy_to_n (1, $y); } return \@got; }); #------------------------------------------------------------------------------ # A086893 -- Drib tree Y=1 row MyOEIS::compare_values (anum => 'A086893', max_count => 100, name => "Drib row Y=1", func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'Drib'); my @got; for (my $x = Math::BigInt->new(1); @got < $count; $x++) { push @got, $path->xy_to_n ($x, 1); } return \@got; }); #------------------------------------------------------------------------------ # A229742 -- HCS numerators # 0, 1, 2, 1, 3, 3, 1, 2, 4, 5, 4, 5, 1, 2, 3, 3, 5, 7, 7, 8, 5, 7, 7, 8, MyOEIS::compare_values (anum => 'A229742', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'HCS'); my @got = (0); # extra initial 0/1 for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $x; } return \@got; }); #------------------------------------------------------------------------------ # A071766 -- HCS denominators # 1, 1, 1, 2, 1, ... MyOEIS::compare_values (anum => 'A071766', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'HCS'); my @got = (1); # extra initial 1/1 for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A071585 -- HCS num+den # 1, 2, 3, 3, 4, ... MyOEIS::compare_values (anum => 'A071585', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'HCS'); my @got = (1); # extra initial 1/1 then Rat+1 for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $x+$y; } return \@got; }); #------------------------------------------------------------------------------ # A154435 -- permutation HCS->Bird, lamplighter MyOEIS::compare_values (anum => 'A154435', func => sub { my ($count) = @_; my $hcs = Math::PlanePath::RationalsTree->new (tree_type => 'HCS'); my $bird = Math::PlanePath::RationalsTree->new (tree_type => 'Bird'); my @got = (0); # initial 0 for (my $n = $hcs->n_start; @got < $count; $n++) { my ($x, $y) = $hcs->n_to_xy($n); push @got, $bird->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A154436 -- permutation Bird->HCS, lamplighter inverse MyOEIS::compare_values (anum => 'A154436', func => sub { my ($count) = @_; my $hcs = Math::PlanePath::RationalsTree->new (tree_type => 'HCS'); my $bird = Math::PlanePath::RationalsTree->new (tree_type => 'Bird'); my @got = (0); # initial 0 for (my $n = $bird->n_start; @got < $count; $n++) { my ($x, $y) = $bird->n_to_xy($n); push @got, $hcs->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A059893 -- bit-reversal permutation # CW<->SB MyOEIS::compare_values (anum => 'A059893', func => sub { my ($count) = @_; my $sb = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my $cw = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); my @got; for (my $n = $cw->n_start; @got < $count; $n++) { my ($x, $y) = $cw->n_to_xy($n); push @got, $sb->xy_to_n($x,$y); } return \@got; }); MyOEIS::compare_values (anum => 'A059893', func => sub { my ($count) = @_; my @got; my $sb = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my $cw = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); for (my $n = $sb->n_start; @got < $count; $n++) { my ($x, $y) = $sb->n_to_xy($n); push @got, $cw->xy_to_n($x,$y); } return \@got; }); # Drib<->Bird MyOEIS::compare_values (anum => 'A059893', func => sub { my ($count) = @_; my $bird = Math::PlanePath::RationalsTree->new (tree_type => 'Bird'); my $drib = Math::PlanePath::RationalsTree->new (tree_type => 'Drib'); my @got; for (my $n = $drib->n_start; @got < $count; $n++) { my ($x, $y) = $drib->n_to_xy($n); push @got, $bird->xy_to_n($x,$y); } return \@got; }); MyOEIS::compare_values (anum => 'A059893', func => sub { my ($count) = @_; my @got; my $bird = Math::PlanePath::RationalsTree->new (tree_type => 'Bird'); my $drib = Math::PlanePath::RationalsTree->new (tree_type => 'Drib'); for (my $n = $bird->n_start; @got < $count; $n++) { my ($x, $y) = $bird->n_to_xy($n); push @got, $drib->xy_to_n($x,$y); } return \@got; }); # AYT<->HCS MyOEIS::compare_values (anum => 'A059893', func => sub { my ($count) = @_; my $hcs = Math::PlanePath::RationalsTree->new (tree_type => 'HCS'); my $ayt = Math::PlanePath::RationalsTree->new (tree_type => 'AYT'); my @got; for (my $n = $ayt->n_start; @got < $count; $n++) { my ($x, $y) = $ayt->n_to_xy($n); push @got, $hcs->xy_to_n($x,$y); } return \@got; }); MyOEIS::compare_values (anum => 'A059893', func => sub { my ($count) = @_; my @got; my $hcs = Math::PlanePath::RationalsTree->new (tree_type => 'HCS'); my $ayt = Math::PlanePath::RationalsTree->new (tree_type => 'AYT'); for (my $n = $hcs->n_start; @got < $count; $n++) { my ($x, $y) = $hcs->n_to_xy($n); push @got, $ayt->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A047270 -- 3or5 mod 6, is CW positions of X>Y not both odd MyOEIS::compare_values (anum => 'A047270', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); if (xy_is_pythagorean($x,$y)) { push @got, $n; } } return \@got; }); sub xy_is_pythagorean { my ($x,$y) = @_; return ($x>$y && ($x%2)!=($y%2)); } #------------------------------------------------------------------------------ # A057431 -- SB num then den, initial 0/1, 1/0 too MyOEIS::compare_values (anum => 'A057431', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my @got = (0,1, 1,0); for (my $n = $path->n_start; ; $n++) { my ($x, $y) = $path->n_to_xy ($n); last if @got >= $count; push @got, $x; last if @got >= $count; push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A104106 AYT 2*N Left -- not quite # a(1) = 1 # if A(k) = sequence of first 2^k -1 terms, then # A(k+1) = A(k), 1, A(k) if a(k) = 0 # A(k+1) = A(k), 0, A(k) if a(k) = 1 # A104106 ,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1, # sub A104106_func { # my ($n) = @_; # my @array; # $array[1] = 1; # my $k = 1; # initially 2^1-1 = 2-1 = 1 term # while ($#array < $n) { # my $last = $#array; # push @array, # $array[$k] ? 0 : 1, # @array[1 .. $last]; # array slice # # print "\n$k array ",join(',',@array[1..$#array]),"\n"; # $k++; # } # return $array[$n]; # } # print "A104106_func: "; # foreach my $i (1 .. 20) { # print A104106_func($i),","; # } # print "\n"; # # { # require Math::NumSeq::PlanePathTurn; # my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'RationalsTree,tree_type=AYT', # turn_type => 'Left'); # print "seq: "; # foreach my $i (1 .. 20) { # print $seq->ith(2*$i),","; # } # print "\n"; # # foreach my $k (1 .. 100) { # my $i = 2*$k; # my $s = $seq->ith($i); # my $a = A104106_func($k+10); # my $diff = ($s != $a ? ' ***' : ''); # print "$i $s $a$diff\n"; # } # } #------------------------------------------------------------------------------ # HCS num=A071585 den=A071766 # A010060 is 1=right or straight, 0=left # straight only at i=2 1,1, 2,1, 3,1 { require Math::NumSeq::OEIS::File; require Math::NumberCruncher; require Math::BaseCnv; my $num = Math::NumSeq::OEIS::File->new(anum=>'A071585'); # OFFSET=0 my $den = Math::NumSeq::OEIS::File->new(anum=>'A071766'); # OFFSET=0 my $seq_A010060 = Math::NumSeq::OEIS->new(anum=>'A010060'); (undef, my $n1) = $num->next; (undef, my $n2) = $num->next; (undef, my $d1) = $den->next; (undef, my $d2) = $den->next; # $n1 += $d1; $n2 += $d2; my $count = 0; for (;;) { (my $i, my $n3) = $num->next or last; (undef, my $d3) = $den->next; # Clockwise() positive for clockwise=right, negative for anti=left my $turn = Math::NumberCruncher::Clockwise($n1,$d1, $n2,$d2, $n3,$d3); if ($turn > 0) { $turn = 1; } # 1=right elsif ($turn < 0) { $turn = 0; } # 0=left, 1=right else { $turn = 1; MyTestHelpers::diag ("straight i=$i $n1,$d1, $n2,$d2, $n3,$d3"); } # print "$turn,"; next; my $turn_by_A010060 = $seq_A010060->ith($i); # n of third of triplet if ($turn != $turn_by_A010060) { die "oops, wrong at i=$i"; } # if (is_pow2($i)) { print "\n"; } # my $i2 = Math::BaseCnv::cnv($i,10,2); # printf "%2s %5s %2s,%-2s %d %d\n", $i,$i2, $n3,$d3, $turn, $turn_by_A010060; $n1 = $n2; $n2 = $n3; $d1 = $d2; $d2 = $d3; $count++; } MyTestHelpers::diag ("HCS OEIS vs A010060 count $count"); ok (1,1); } #------------------------------------------------------------------------------ # A010060 -- HCS turn right is (-1)^count1bits of N+1, Thue-Morse +/-1 # OFFSET=0, extra initial n=0,1,2 then n=3 is N=2 MyOEIS::compare_values (anum => 'A010060', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'RationalsTree,tree_type=HCS', turn_type => 'Right'); my @got = (0,1,1); while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); # A106400 -- HCS left +/-1 thue-morse parity, OFFSET=0 MyOEIS::compare_values (anum => 'A106400', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'RationalsTree,tree_type=HCS', turn_type => 'Left'); my @got = (1,-1,-1); while (@got < $count) { my ($i,$value) = $seq->next; push @got, 2*$value-1; } return \@got; }); # +/-1 OFFSET=1, extra initial n=1,n=2 then n=3 is N=2 MyOEIS::compare_values (anum => 'A108784', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'RationalsTree,tree_type=HCS', turn_type => 'Right'); my @got = (1,1); while (@got < $count) { my ($i,$value) = $seq->next; push @got, 2*$value-1; } return \@got; }); # A010059 -- HCS Left, count0bits mod 2 of N+1 MyOEIS::compare_values (anum => 'A010059', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'RationalsTree,tree_type=HCS', turn_type => 'Left'); my @got = (1,0,0); while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A070990 -- CW Y-X is Stern diatomic first diffs, starting from N=2 MyOEIS::compare_values (anum => 'A070990', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); my @got; for (my $n = $path->n_start + 1; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $y - $x; } return \@got; }); #------------------------------------------------------------------------------ # A007814 -- CW floor(X/Y) is count trailing 1-bits # A007814 count trailing 0-bits is same, at N+1 MyOEIS::compare_values (anum => 'A007814', func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, int($x/$y); } return \@got; }); # A007814 -- AYT floor(X/Y) is count trailing 0-bits, # except at N=2^k where 1 fewer MyOEIS::compare_values (anum => 'A007814', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'AYT'); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); my $i = int($x/$y); if (is_pow2($n)) { $i--; } push @got, $i; } return \@got; }); sub is_pow2 { my ($n) = @_; while ($n > 1) { if ($n & 1) { return 0; } $n >>= 1; } return ($n == 1); } #------------------------------------------------------------------------------ # A004442 -- AYT N at transpose Y,X, flip low bit MyOEIS::compare_values (anum => 'A004442', func => sub { my ($count) = @_; my @got = (1,0); my $path = Math::PlanePath::RationalsTree->new (tree_type => 'AYT'); for (my $n = 2; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n ($y, $x); } return \@got; }); #------------------------------------------------------------------------------ # A063946 -- HCS N at transpose Y,X, flip second lowest bit MyOEIS::compare_values (anum => 'A063946', func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::RationalsTree->new (tree_type => 'HCS'); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n ($y, $x); } return \@got; }); #------------------------------------------------------------------------------ # A054429 -- N at transpose Y,X, row right to left foreach my $tree_type ('SB','CW','Bird','Drib') { MyOEIS::compare_values (anum => 'A054429', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::RationalsTree->new (tree_type => $tree_type); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n ($y, $x); } return \@got; }); } #------------------------------------------------------------------------------ # A072030 - subtraction steps for gcd(x,y) by triangle rows MyOEIS::compare_values (anum => q{A072030}, func => sub { my ($count) = @_; require Math::PlanePath::PyramidRows; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my $triangle = Math::PlanePath::PyramidRows->new (step => 1); my @got; for (my $n = $triangle->n_start; @got < $count; $n++) { my ($x,$y) = $triangle->n_to_xy ($n); next unless $x < $y; # so skipping GCD(x,x)==x taking 0 steps $x++; $y++; my $gcd = gcd($x,$y); $x /= $gcd; $y /= $gcd; my $n = $path->xy_to_n($x,$y); die unless defined $n; my $depth = $path->tree_n_to_depth($n); push @got, $depth; } return \@got; }); #------------------------------------------------------------------------------ # A072031 - row sums of A072030 subtraction steps for gcd(x,y) by rows MyOEIS::compare_values (anum => q{A072031}, func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new(tree_type => 'SB'); my @got; for (my $y = 2; @got < $count; $y++) { my $total = -1; # gcd(1,Y) taking 0 steps, maybe for (my $x = 1; $x < $y; $x++) { my $gcd = gcd($x,$y); my $n = $path->xy_to_n($x/$gcd,$y/$gcd); die unless defined $n; $total += $path->tree_n_to_depth($n); } push @got, $total+1; } return \@got; }); #------------------------------------------------------------------------------ # A003188 -- permutation SB->HCS, Gray code shift+xor MyOEIS::compare_values (anum => 'A003188', func => sub { my ($count) = @_; my $hcs = Math::PlanePath::RationalsTree->new (tree_type => 'HCS'); my $sb = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my @got = (0); # initial 0 for (my $n = $sb->n_start; @got < $count; $n++) { my ($x, $y) = $sb->n_to_xy($n); push @got, $hcs->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A006068 -- permutation HCS->SB, Gray code inverse MyOEIS::compare_values (anum => 'A006068', func => sub { my ($count) = @_; my $hcs = Math::PlanePath::RationalsTree->new (tree_type => 'HCS'); my $sb = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my @got = (0); # initial 0 for (my $n = $hcs->n_start; @got < $count; $n++) { my ($x, $y) = $hcs->n_to_xy($n); push @got, $sb->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # Stern diatomic A002487 # A002487 -- L denominators, L doesn't have initial 0,1 of diatomic MyOEIS::compare_values (anum => 'A002487', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'L'); my @got = (0,1); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $y; } return \@got; }); # A002487 -- CW numerators, is Stern diatomic MyOEIS::compare_values (anum => 'A002487', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); my @got = (0); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $x; } return \@got; }); # A002487 -- CW denominators are Stern diatomic MyOEIS::compare_values (anum => 'A002487', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); my @got = (0,1); # extra initial for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A154437 -- permutation AYT->Drib MyOEIS::compare_values (anum => 'A154437', func => sub { my ($count) = @_; my $drib = Math::PlanePath::RationalsTree->new (tree_type => 'Drib'); my $ayt = Math::PlanePath::RationalsTree->new (tree_type => 'AYT'); my @got = (0); # initial 0 for (my $n = $ayt->n_start; @got < $count; $n++) { my ($x, $y) = $ayt->n_to_xy($n); push @got, $drib->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A154438 -- permutation Drib->AYT MyOEIS::compare_values (anum => 'A154438', func => sub { my ($count) = @_; my $ayt = Math::PlanePath::RationalsTree->new (tree_type => 'AYT'); my $drib = Math::PlanePath::RationalsTree->new (tree_type => 'Drib'); my @got = (0); # initial 0 for (my $n = $drib->n_start; @got < $count; $n++) { my ($x, $y) = $drib->n_to_xy($n); push @got, $ayt->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A061547 -- pos of frac F(n)/F(n+1) in Stern diatomic, is CW N # F(n)/F(n+1) in CW, extra initial 0 MyOEIS::compare_values (anum => 'A061547', max_count => 100, func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); my @got = (0); # extra initial 0 in seq A061547 my $f1 = Math::BigInt->new(1); my $f0 = Math::BigInt->new(1); while (@got < $count) { push @got, $path->xy_to_n ($f0, $f1); ($f1,$f0) = ($f1+$f0,$f1); } return \@got; }); # #------------------------------------------------------------------------------ # # A113881 # # different as n=49 # # { # my $anum = 'A113881'; # my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); # my $skip; # my @got; # my $diff; # if ($bvalues) { # require Math::PlanePath::Diagonals; # my $path = Math::PlanePath::RationalsTree->new(tree_type => 'SB'); # my $diag = Math::PlanePath::Diagonals->new; # for (my $n = $diag->n_start; @got < $count; $n++) { # my ($x,$y) = $diag->n_to_xy ($n); # $x++; # $y++; # my $gcd = gcd($x,$y); # $x /= $gcd; # $y /= $gcd; # my $n = $path->xy_to_n($x,$y); # my $nbits = sprintf '%b', $n; # push @got, length($nbits); # } # $diff = diff_nums(\@got, $bvalues); # if ($diff) { # MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..30])); # MyTestHelpers::diag ("got: ",join(',',@got[0..30])); # } # } # skip (! $bvalues, # $diff, undef, # "$anum"); # } #------------------------------------------------------------------------------ # A088696 -- length of continued fraction of SB fractions if (! eval { require Math::ContinuedFraction; 1 }) { skip ("Math::ContinuedFraction not available", 0,0); } else { MyOEIS::compare_values (anum => 'A088696', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::RationalsTree->new(tree_type => 'SB'); OUTER: for (my $k = 1; @got < $count; $k++) { foreach my $n (2**$k .. 2**$k + 2**($k-1) - 1) { my ($x,$y) = $path->n_to_xy ($n); my $cf = Math::ContinuedFraction->from_ratio($x,$y); my $cfaref = $cf->to_array; my $cflen = scalar(@$cfaref); push @got, $cflen-1; # -1 to skip initial 0 term in $cf ### cf: "n=$n xy=$x/$y cflen=$cflen ".$cf->to_ascii last OUTER if @got >= $count; } } return \@got; }); } #------------------------------------------------------------------------------ # A086893 -- pos of frac F(n+1)/F(n) in Stern diatomic, is CW N MyOEIS::compare_values (anum => 'A086893', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); my @got; my $f1 = 1; my $f0 = 1; while (@got < $count) { push @got, $path->xy_to_n ($f1, $f0); ($f1,$f0) = ($f1+$f0,$f1); } return \@got; }); #------------------------------------------------------------------------------ # A007305 -- SB numerators MyOEIS::compare_values (anum => 'A007305', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my @got = (0,1); # extra initial for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $x; } return \@got; }); #------------------------------------------------------------------------------ # A047679 -- SB denominators MyOEIS::compare_values (anum => 'A047679', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my @got; foreach my $n (1 .. $count) { my ($x, $y) = $path->n_to_xy ($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A007306 -- SB num+den MyOEIS::compare_values (anum => 'A007306', func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my @got = (1,1); # extra initial for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $x+$y; } return \@got; }); #------------------------------------------------------------------------------ # A162911 -- Drib tree numerators = Bird tree reverse N MyOEIS::compare_values (anum => q{A162911}, func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'Bird'); my @got; foreach my $n (1 .. $count) { my ($x, $y) = $path->n_to_xy (bit_reverse ($n)); push @got, $x; } return \@got; }); sub bit_reverse { my ($n) = @_; my $rev = 1; while ($n > 1) { $rev = 2*$rev + ($n % 2); $n = int($n/2); } return $rev; } #------------------------------------------------------------------------------ # A162912 -- Drib tree denominators = Bird tree reverse MyOEIS::compare_values (anum => q{A162912}, func => sub { my ($count) = @_; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'Bird'); my @got; foreach my $n (1 .. $count) { my ($x, $y) = $path->n_to_xy (bit_reverse ($n)); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/AlternateTerdragon-oeis.t0000644000175000017500000000625013244716357020226 0ustar gggg#!/usr/bin/perl -w # Copyright 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 4; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::AlternateTerdragon; my $path = Math::PlanePath::AlternateTerdragon->new; sub ternary_digit_above_low_zeros { my ($n) = @_; if ($n == 0) { return 0; } while (($n % 3) == 0) { $n = int($n/3); } return ($n % 3); } #------------------------------------------------------------------------------ # A189715 - N of left turns MyOEIS::compare_values (anum => 'A189715', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); my @got; while (@got < $count) { my ($i, $value) = $seq->next; if ($value == 1) { push @got, $i; } } return \@got; }); # A189716 - N of right turns MyOEIS::compare_values (anum => 'A189716', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got; while (@got < $count) { my ($i, $value) = $seq->next; if ($value == 1) { push @got, $i; } } return \@got; }); #------------------------------------------------------------------------------ # A156595 - turn 0=left, 1=right MyOEIS::compare_values (anum => 'A156595', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got; while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value; } return \@got; }); # A189717 - cumulative A156595 = num right turns MyOEIS::compare_values (anum => 'A189717', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got; my $total = 0; while (@got < $count) { my ($i, $value) = $seq->next; $total += $value; push @got, $total; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/HeptSpiralSkewed-oeis.t0000644000175000017500000000264713475105545017662 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2015, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 1; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use List::Util 'min', 'max'; use Math::PlanePath::HeptSpiralSkewed; #------------------------------------------------------------------------------ # A140065 - N on Y axis MyOEIS::compare_values (anum => 'A140065', func => sub { my ($count) = @_; my $path = Math::PlanePath::HeptSpiralSkewed->new; my @got; for (my $y = 0; @got < $count; $y++) { my $n = $path->xy_to_n(0,$y); push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/CfracDigits-oeis.t0000644000175000017500000000617013244716476016626 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 5; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::CfracDigits; use Math::PlanePath::Base::Digits 'digit_join_lowtohigh'; #------------------------------------------------------------------------------ # A071766 -- radix=1 X numerators, same as HCS denominators # except at OFFSET=0 extra initial 1 from 0/1 MyOEIS::compare_values (anum => 'A071766', func => sub { my ($count) = @_; my $path = Math::PlanePath::CfracDigits->new (radix => 1); my @got = (1); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $x; } return \@got; }); #------------------------------------------------------------------------------ # A032924 - N in X=1 column, ternary no digit 0 MyOEIS::compare_values (anum => 'A032924', func => sub { my ($count) = @_; my $path = Math::PlanePath::CfracDigits->new; my @got; for (my $y = 3; @got < $count; $y++) { push @got, $path->xy_to_n(1,$y); } return \@got; }); #------------------------------------------------------------------------------ # A023705 - N in X=1 column, base4 no digit 0 MyOEIS::compare_values (anum => 'A023705', func => sub { my ($count) = @_; my $path = Math::PlanePath::CfracDigits->new (radix => 3); my @got; for (my $y = 3; @got < $count; $y++) { push @got, $path->xy_to_n(1,$y); } return \@got; }); #------------------------------------------------------------------------------ # A023721 - N in X=1 column, base5 no digit 0 MyOEIS::compare_values (anum => 'A023721', func => sub { my ($count) = @_; my $path = Math::PlanePath::CfracDigits->new (radix => 4); my @got; for (my $y = 3; @got < $count; $y++) { push @got, $path->xy_to_n(1,$y); } return \@got; }); #------------------------------------------------------------------------------ # A052382 - N in X=1 column, base5 no digit 0 MyOEIS::compare_values (anum => 'A052382', func => sub { my ($count) = @_; my $path = Math::PlanePath::CfracDigits->new (radix => 9); my @got; for (my $y = 3; @got < $count; $y++) { push @got, $path->xy_to_n(1,$y); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/OctagramSpiral-oeis.t0000644000175000017500000000257513244716475017360 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 7; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::OctagramSpiral; #------------------------------------------------------------------------------ # A125201 -- N on X axis, from X=1 onwards, 18-gonals + 1 MyOEIS::compare_values (anum => 'A125201', func => sub { my ($count) = @_; my $path = Math::PlanePath::OctagramSpiral->new; my @got; for (my $x = 1; @got < $count; $x++) { push @got, $path->xy_to_n($x,0); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/SquareSpiral-oeis.t0000644000175000017500000020247413776011543017055 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # A168022 Non-composite numbers in the eastern ray of the Ulam spiral as oriented on the March 1964 cover of Scientific American. # A168023 Non-composite numbers in the northern ray of the Ulam spiral as oriented on the March 1964 cover of Scientific American. # A168024 Non-composite numbers in the northwestern ray of the Ulam spiral as oriented on the March 1964 cover of Scientific American. # A168025 Non-composite numbers in the western ray of the Ulam spiral as oriented on the March 1964 cover of Scientific American. # A168026 Non-composite numbers in the southwestern ray of the Ulam spiral as oriented on the March 1964 cover of Scientific American. # A168027 Non-composite numbers in the southern ray of the Ulam spiral as oriented on the March 1964 cover of Scientific American. # A053823 Product of primes in n-th shell of prime spiral. # A053997 Sum of primes in n-th shell of prime spiral. # A053998 Smallest prime in n-th shell of prime spiral. # A004652 maybe? # A340171, A340172 double-spaced spiral use 5.004; use strict; use Carp 'croak'; use Math::BigInt; use Math::NumSeq::AllDigits; use Math::NumSeq::AlmostPrimes; use Math::NumSeq::PlanePathTurn; use Math::Prime::XS 'is_prime'; use POSIX 'ceil'; use Test; plan tests => 107; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use List::Util 'min','max','sum'; use Math::PlanePath::SquareSpiral; # uncomment this to run the ### lines # use Smart::Comments; my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); my @dir8_to_dx = (1,1, 0,-1, -1,-1, 0,1); my @dir8_to_dy = (0,1, 1,1, 0,-1, -1,-1); my $path = Math::PlanePath::SquareSpiral->new; # n_start=1 my $path_n_start_0 = Math::PlanePath::SquareSpiral->new (n_start => 0); ok ($path->n_start, 1); ok ($path_n_start_0->n_start, 0); # return 1,2,3,4 sub path_n_dir4_1 { my ($path, $n) = @_; my ($x,$y) = $path->n_to_xy($n); my ($next_x,$next_y) = $path->n_to_xy($n+1); return dxdy_to_dir4_1 ($next_x - $x, $next_y - $y); } # return 1,2,3,4, with Y reckoned increasing upwards sub dxdy_to_dir4_1 { my ($dx, $dy) = @_; if ($dx > 0) { return 1; } # east if ($dx < 0) { return 3; } # west if ($dy > 0) { return 2; } # north if ($dy < 0) { return 4; } # south } # GP-DEFINE read("my-oeis.gp"); #------------------------------------------------------------------------------ # A068225 -- permutation N at X+1,Y MyOEIS::compare_values (anum => 'A068225', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { push @got, A068225($n); } return \@got; }); # starting n=1 sub A068225 { my ($n) = @_; my ($x, $y) = $path->n_to_xy ($n); return $path->xy_to_n ($x+1,$y); } # A068226 -- permutation N at X-1,Y MyOEIS::compare_values (anum => 'A068226', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n ($x-1,$y); } return \@got; }); # A334751 -- permutation N at Y-1 MyOEIS::compare_values (anum => 'A334751', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n ($x, $y-1); } return \@got; }); # A334752 -- permutation N at Y+1 MyOEIS::compare_values (anum => 'A334752', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n ($x, $y+1); } return \@got; }); #------------------------------------------------------------------------------ # A320281 -- N values in pattern 4,5,5 on positive X axis MyOEIS::compare_values (anum => 'A320281', func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x++) { my $n = $path_n_start_0->xy_to_n ($x, 0); push @got, ceil($n*2/3); } return \@got; }); # A143978 -- N values in pattern 4,5,5 on X=Y diagonal both ways MyOEIS::compare_values (anum => 'A143978', func => sub { my ($count) = @_; my @got; for (my $x = 1; @got < $count; $x++) { my $n = $path_n_start_0->xy_to_n ($x, $x); push @got, int($n*2/3); @got < $count or next; $n = $path_n_start_0->xy_to_n (-$x, -$x); push @got, int($n*2/3); } return \@got; }); # A301696 -- N values in pattern 4,5,5 on X=-Y diagonal both ways MyOEIS::compare_values (anum => 'A301696', func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x++) { my $n = $path->xy_to_n ($x, -$x); push @got, ceil($n*2/3); } return \@got; }); #------------------------------------------------------------------------------ # A054567 -- N values on negative X axis, n_start=1 MyOEIS::compare_values (anum => 'A054567', func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x++) { my $n = $path->xy_to_n (-$x, 0); push @got, $n; } return \@got; }); # A317186 X axis positive and negative, n_start=1 MyOEIS::compare_values (anum => 'A317186', func => sub { my ($count) = @_; my @got; my $x = 0; for (;;) { last unless @got < $count; push @got, $path->xy_to_n(-$x, 0); $x++; last unless @got < $count; push @got, $path->xy_to_n($x, 0); } return \@got; }); #------------------------------------------------------------------------------ # A242601 -- X or Y of the turns # A242601 0, 0, 1, 1, -1, -1, 2, 2, -2, -2 # 1,1,-1,-1,2,2,-2,-2,3,3,-3,-3,4,4,-4,-4,5,5,-5,-5,6,6,-6,-6 # A242601 Y of turns # 0,1,1,-1,-1,2,2,-2,-2,3,3,-3,-3,4,4,-4,-4,5,5,-5,-5,6,6,-6,-6,7,7,-7,-7 # http://oeis.org/plot2a?name1=A242601&name2=A242601&tform1=untransformed&tform2=untransformed&shift=1&radiop1=xy&drawpoints=true&drawlines=true # GP-DEFINE A242601(n) = floor((n+2)/4)*(-1)^floor((n+2)/2) # vector(20,n,n--; A242601(n)) # my(l=List([])); \ # for(n=0,10,listput(l,A242601(n+1));listput(l,A242601(n))); \ # Vec(l) # not in OEIS: 0,0, 1,0, 1,1, -1,1, -1,-1, 2,-1, 2,2, -2,2, -2,-2, 3,-2, 3,3 # or transposed # not in OEIS: 0,0, 0,1, 1,1, 1,-1, -1,-1, -1,2, 2,2, 2,-2, -2,-2, -2,3, 3,3 # only various absolute values # my(x=vector(20,n,n--; A242601(n+1)), \ # y=vector(20,n,n--; A242601(n))); \ # plothraw(x,y,1) # A242601 X of turn corner MyOEIS::compare_values (anum => 'A242601', func => sub { my ($count) = @_; my @got = (0,0); my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SquareSpiral', turn_type => 'Left'); while (@got < $count) { my ($i,$value) = $seq->next; if ($value) { my ($x,$y) = $path->n_to_xy ($i); push @got, $x; } } return \@got; }); # A242601 Y of turn corner MyOEIS::compare_values (anum => 'A242601', func => sub { my ($count) = @_; my @got = (0); my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SquareSpiral', turn_type => 'Left'); while (@got < $count) { my ($i,$value) = $seq->next; if ($value) { my ($x,$y) = $path->n_to_xy ($i); push @got, $y; } } return \@got; }); #------------------------------------------------------------------------------ # A336336 -- squared distances (norms), all points # # cf A335298 norms of corner points in spread-out spiral, as if 2 arms MyOEIS::compare_values (anum => 'A336336', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); push @got, $x*$x + $y*$y; } return \@got; }); # GP-DEFINE A336336(n) = { # GP-DEFINE n>=1 || error(); # GP-DEFINE n--; my(m=sqrtint(n),k=ceil(m/2)); # GP-DEFINE \\ n -= 4*k^2; # GP-DEFINE \\ k^2 + if(n<0, if(abs(n)>m, (abs(n)-3*k)^2, # GP-DEFINE \\ (abs(n)-k)^2), # GP-DEFINE \\ if(abs(n)>m, (abs(n)-3*k)^2, # GP-DEFINE \\ (abs(n)-k)^2)); # GP-DEFINE \\ k^2 + if(abs(n)>m, (abs(n)-3*k)^2, # GP-DEFINE \\ (abs(n)-k)^2); # GP-DEFINE n=abs(n-4*k^2); # GP-DEFINE k^2 + (n-if(n>m,3,1)*k)^2; # GP-DEFINE } # GP-Test-Last vector(1024,n, A336336(n)) == \ # GP-Test-Last vector(1024,n, X(n)^2 + Y(n)^2) # # GP-Test my(v=OEIS_samples("A336336")); vector(#v,n, A336336(n)) == v /* OFFSET=1 */ # GP-Test my(g=OEIS_bfile_gf("A336336")); g==x*Polrev(vector(poldegree(g),n, A336336(n))) # poldegree(OEIS_bfile_gf("A336336")) #--- # MyOEIS::compare_values # (anum => 'A336336', # func => sub { # my ($count) = @_; # my @got; # my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SquareSpiral', # turn_type => 'Left'); # for (my $n = $path->n_start; @got < $count; $n++) { # if ($seq->ith($n)) { # my ($x,$y) = $path->n_to_xy ($n); # push @got, $x*$x + $y*$y; # } # } # return \@got; # }); # at left turns # not in OEIS: 1,2,2,2,5,8,8,8,13,18,18,18,25,32,32,32,41,50,50,50,61,72,72,72,85,98,98,98,113,128,128,128,145,162,162,162,181,200,200,200,221,242,242,242,265,288,288,288,313,338,338 # Hugo Pfoertner notes the values without duplications are ceiling(n^2/2) = A000982 # vector(20,n, A242601(n)^2 + A242601(n+1)^2) # vector(20,n, A242601(2*n)^2 + A242601(2*n+1)^2) # X(n) = sum(i=1,n, if(i%2==0,i)); # Y(n) = sum(i=1,n, if(i%2==1,i)); # Vec((1+x^8)/((1-x)*(1-x^4)) + O(x^30)) \\ 1,1,1,1, 2,2,2,2, 4,4,4,4, # vector(20,n,n--; A127365(n)) # A127365(n)^2 + A122461(n)^2 # vector(10,n, X(n)) # vector(10,n, Y(n)) # vector(10,n, X(n)^2 + Y(n)^2) # vector(10,n, norm(Z(n))) # Z(n) = sum(i=1,n, i*I^i); # vector(30,n, real(Z(n))) \\ A122461 # vector(30,n, imag(Z(n))) \\ A127365 # OEIS_samples("A127365") # vector(30,n,n--; -imag(Z(n))) # spread corners norms A335298 # 0, 1, 5, 8,8, 13, 25, 32,32, 41, 61, 72,72, 85, 113, 128,128, 145, 181, 200,200, 221, 265, 288,288, 313, 365, 392, # 392, 421, 481, 512, 512, 545, 613, 648, 648, 685, 761, 800, # 800, 841, 925, 968, 968, 1013, 1105, 1152, 1152, 1201, 1301, # 1352, 1352, 1405, 1513 #------------------------------------------------------------------------------ # A136626 -- count surrounding primes # OFFSET=0 given, but values are for starting 1 { # example surrounding n=13 given in A136626 and A136627 # math-image --path=SquareSpiral --all --output=numbers_dash --size=60x20 # # 65-64-63-62-61-60-59-58-57 90 # | | | # 66 37-36-35-34-33-32-31 56 89 # | | | | | # 67 38 17-16-15-14-13 30 55 88 # | | | | | | | # 68 39 18 5--4--3 12 29 54 87 # | | | | | | | | | # 69 40 19 6 1--2 11 28 53 86 # | | | | | | | | # 70 41 20 7--8--9-10 27 52 85 # | | | | | | # 71 42 21-22-23-24-25-26 51 84 # | | | | # 72 43-44-45-46-47-48-49-50 83 # | | # 73-74-75-76-77-78-79-80-81-82 # # GP-Test /* around n=32 */ \ # GP-Test select(isprime,[14,13,30,31,58,59,60,33]) == [13, 31, 59] # around n=13 my @want = (3, 12, 29, 30, 31, 32, 33, 14); my $n = 13; my ($x,$y) = $path->n_to_xy ($n); my $total = 0; foreach my $i (0 .. 7) { my $dir = ($i + 5)%8; # start South-West dir=5 my $sn = $path->xy_to_n ($x+$dir8_to_dx[$dir], $y+$dir8_to_dy[$dir]); ok ($sn, $want[$i]); } } MyOEIS::compare_values (anum => q{A136626}, func => sub { my ($count) = @_; my $verbose = 0; my @got; for (my $n = $path->n_start; @got < $count; $n++) { if ($verbose) { print "n=$n "; } my ($x,$y) = $path->n_to_xy ($n); my $total = 0; foreach my $dir (0 .. 7) { my $sn = $path->xy_to_n ($x+$dir8_to_dx[$dir], $y+$dir8_to_dy[$dir]); if (is_prime($sn)) { if ($verbose) { print " $sn"; } $total++; } } if ($verbose) { print " total $total\n"; } push @got, $total; } return \@got; }); # A136627 -- count self and surrounding primes MyOEIS::compare_values (anum => q{A136627}, func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); my $total = is_prime($n) ? 1 : 0; foreach my $dir (0 .. 7) { my $sn = $path->xy_to_n ($x+$dir8_to_dx[$dir], $y+$dir8_to_dy[$dir]); if (is_prime($sn)) { $total++; } } push @got, $total; } return \@got; }); #------------------------------------------------------------------------------ # A080037 -- N positions of straight ahead, and also 2 # 68 39 18 5--4--3 12 29 54 87 # | | | | | | | | | # 69 40 19 6 1--2 11 28 53 86 # | | | | | | | | # 70 41 20 7--8--9-10 27 52 85 MyOEIS::compare_values (anum => 'A080037', func => sub { my ($count) = @_; my @got = (2); my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SquareSpiral', turn_type => 'Straight'); while (@got < $count) { my ($i,$value) = $seq->next; if ($value) { push @got, $i; } } return \@got; }); sub A080037 { my ($n) = @_; return ($n==0 ? 2 : $n + int(sqrt(4*$n-3)) + 2); } MyOEIS::compare_values (anum => q{A080037}, name => 'A080037 vs func', func => sub { my ($count) = @_; return [ map {A080037($_)} 0 .. $count-1 ]; }); #------------------------------------------------------------------------------ # A033638 -- N positions of the turns # quarter-squares + 1 MyOEIS::compare_values (anum => 'A033638', max_value => 100_000, # bit slow by a naive search here func => sub { my ($count) = @_; my @got = (1,1); my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SquareSpiral', turn_type => 'LSR'); while (@got < $count) { my ($i,$value) = $seq->next; if ($value != 0) { push @got, $i; } } return \@got; }); sub A033638 { my ($n) = @_; return ( (7+(-1)**$n)/2 + $n*$n )/4; # formula in A033638 } MyOEIS::compare_values (anum => q{A033638}, name => 'A033638 vs func', func => sub { my ($count) = @_; return [ map {A033638($_)} 0 .. $count-1 ]; }); # A033638 and A080037 are complements # 2, 4, 6, 8, 9, 11, 12, 14, 15, 16, 18, 19, 20, 22, 23, 24, 25, 27 # 1,1,2, 3, 5, 7, 10, 13, 17, 21, 26, { my $bad = 0; my $i = 1; my $j = 3; # two initial 1s in A033638 ok (A080037($i), 4); ok (A033638($j), 3); foreach my $n (3 .. 10000) { my $by_i = (A080037($i)==$n); my $by_j = (A033638($j)==$n); if ($by_i && $by_j) { MyTestHelpers::diag ("duplicate $n"); last if $bad++ > 10; } unless ($by_i || $by_j) { MyTestHelpers::diag ("neither for $n"); last if $bad++ > 10; } if ($by_i) { $i++; } if ($by_j) { $j++; } } ok ($bad, 0, 'A033638 complement A080037'); } # foreach my $n (4 .. 50) { print A033638($n),","; } print "\n"; exit; #------------------------------------------------------------------------------ # A172979 -- N positions of the turns, which are also primes MyOEIS::compare_values (anum => 'A172979', func => sub { my ($count) = @_; my @got; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SquareSpiral', turn_type => 'LSR'); while (@got < $count) { my ($i,$value) = $seq->next; if ($value != 0 && is_prime($i)) { push @got, $i; } } return \@got; }); #------------------------------------------------------------------------------ # A265410 - smallest 8 directions neighbour MyOEIS::compare_values (anum => q{A265410}, # not shown in POD func => sub { my ($count) = @_; my @got = (0); for (my $n = $path->n_start + 2; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); my @lefts; foreach my $d (0..7) { my $left = $path->xy_to_n($x + $dir8_to_dx[$d], $y + $dir8_to_dy[$d]); if (defined $left && $left < $n) { push @lefts, $left; } } push @got, min(@lefts) || 0; } return \@got; }); #------------------------------------------------------------------------------ # A141481 -- sum of existing eight surrounding values so far # values so far kept in @got MyOEIS::compare_values (anum => q{A141481}, # not shown in POD func => sub { my ($count) = @_; my $path = $path_n_start_0; my @got = (1); for (my $n = $path->n_start + 1; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); my $sum = Math::BigInt->new(0); foreach my $i (0 .. $#dir8_to_dx) { my $sn = $path->xy_to_n ($x+$dir8_to_dx[$i], $y+$dir8_to_dy[$i]); if ($sn < $n) { $sum += $got[$sn]; # @got is 0-based } } push @got, $sum; } return \@got; }); # values so far kept in %plotted hash MyOEIS::compare_values (anum => q{A141481}, # not shown in POD func => sub { my ($count) = @_; my @got; my %plotted; $plotted{0,0} = Math::BigInt->new(1); push @got, 1; for (my $n = $path->n_start + 1; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); my $value = ( ($plotted{$x+1,$y+1} || 0) + ($plotted{$x+1,$y} || 0) + ($plotted{$x+1,$y-1} || 0) + ($plotted{$x-1,$y-1} || 0) + ($plotted{$x-1,$y} || 0) + ($plotted{$x-1,$y+1} || 0) + ($plotted{$x,$y-1} || 0) + ($plotted{$x,$y+1} || 0) ); $plotted{$x,$y} = $value; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A055086 -- direction, net total turn # OFFSET=0 # 0, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 6, ... { my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); ok ($seq->ith(1), undef); ok ($seq->ith(2), 1); } MyOEIS::compare_values (anum => 'A055086', name => 'direction', func => sub { my ($count) = @_; my @got; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); my $dir = 0; while (@got < $count) { push @got, $dir; my ($i,$value) = $seq->next; $dir += $value; # total lefts } return \@got; }); # A000267 -- direction + 1 # OFFSET=0 MyOEIS::compare_values (anum => 'A000267', name => 'direction + 1', func => sub { my ($count) = @_; my @got; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); my $dir = 1; while (@got < $count) { push @got, $dir; my ($i,$value) = $seq->next; $dir += $value; # total lefts } return \@got; }); # A063826 -- direction 1,2,3,4 = E,N,W,S MyOEIS::compare_values (anum => 'A063826', name => 'direction 1 to 4', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { push @got, path_n_dir4_1($path,$n); } return \@got; }); # A248333 total straights among the first n points # ~/OEIS/A248333.internal.txt # OFFSET=0 # 0, 0, 0, 0, 1, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 9, 9, 10, 11, 12, 12, # 0 1 2 3 4 # # 5 4 3 # 1---1---0 # | # 0---0 # 1 2 MyOEIS::compare_values (anum => 'A248333', func => sub { my ($count) = @_; my @got; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Straight'); my $straights = 0; my $n = 0; while (@got < $count) { push @got, $straights; # up to and including n, $n++; # first test at n=1 (which is false) if ($seq->ith($n)) { $straights++; } } return \@got; }); # GP-DEFINE A248333(n) = n - if(n,sqrtint(4*n-1)); # GP-Test A248333(3) == 0 # GP-Test A248333(4) == 1 # GP-Test A248333(5) == 1 # GP-Test A248333(6) == 2 # GP-Test my(v=OEIS_samples("A248333")); vector(#v,n,n--; A248333(n)) == v /* OFFSET=0 */ # GP-Test my(g=OEIS_bfile_gf("A248333")); g==Polrev(vector(poldegree(g)+1,n,n--;A248333(n))) # poldegree(OEIS_bfile_gf("A248333")) # OEIS_samples("A248333") # vector(20,n,n--; A248333(n)) # vector(50,n,n--; !(A248333(n+1) - A248333(n))) # not in OEIS: 1,0,1,0,1,1,0,1,1,0,1,1,1,0,1,1,1,0,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1 # complement of A240025 quarter squares predicate # A083479 total non-turn points among the first n points # origin n_start is a non-turn # any LSR!=0 is a turn, and otherwise not # (not the same as NotStraight since that is false at origin) MyOEIS::compare_values (anum => 'A083479', func => sub { my ($count) = @_; my @got; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my $nonturns = 0; my $n = 0; while (@got < $count) { push @got, $nonturns; $n++; if (! $seq->ith($n)) { $nonturns++; } } return \@got; }); # integers with A033638 inserted, so how many non-turns sub A083479 { my ($n) = @_; # formula by Gregory R. Bryant in A083479 $n >= 0 or croak "A083479() is for n>=0"; return ($n==0 ? 0 : $n+2 - ceil(sqrt(4*$n))); } MyOEIS::compare_values (anum => q{A083479}, name => 'A083479 vs func', func => sub { my ($count) = @_; return [ map {A083479($_)} 0 .. $count-1 ]; }); # GP-DEFINE sqrtint_ceil(n) = if(n==0,0, sqrtint(n-1)+1); # GP-Test vector(100,n,n--; sqrtint_ceil(n)) == \ # GP-Test vector(100,n,n--; sqrtint(n) + !issquare(n)) # GP-DEFINE A083479(n) = if(n==0,0, n+2 - sqrtint_ceil(4*n)); # GP-Test my(v=OEIS_samples("A083479")); vector(#v,n,n--; A083479(n)) == v /* OFFSET=0 */ # GP-Test my(n=0); n+2 - sqrtint_ceil(4*n) == 2 /* whereas want 0 */ #------------------------------------------------------------------------------ # A240025 -- turn sequence, but it has extra initial 1 # # 1--0--1 # | | # 0 1--1 # | # 1--0--0--1 MyOEIS::compare_values (anum => 'A240025', func => sub { my ($count) = @_; my @got = (1); # extra initial 1 in A240025 my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SquareSpiral', turn_type => 'Left'); while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); # GP-DEFINE n_is_corner_0based(n) = issquare(n) || issquare(4*n+1); # GP-DEFINE n_is_corner_1based(n) = n_is_corner_0based(n-1); # GP-DEFINE A240025(n) = n_is_corner_0based(n); # my(v=OEIS_samples("A240025")); vector(#v,n,n--; A240025(n)) == v \\ OFFSET=0 # my(g=OEIS_bfile_gf("A240025")); g==Polrev(vector(poldegree(g)+1,n,n--; A240025(n))) # poldegree(OEIS_bfile_gf("A240025")) #------------------------------------------------------------------------------ # A174344 X coordinate MyOEIS::compare_values (anum => 'A174344', func => sub { my ($count) = @_; my @got; for (my $n=1; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); # A274923 Y coordinate MyOEIS::compare_values (anum => 'A274923', func => sub { my ($count) = @_; my @got; for (my $n=1; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); # A268038 negative Y coordinate MyOEIS::compare_values (anum => 'A268038', func => sub { my ($count) = @_; my @got; for (my $n=1; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, -$y; } return \@got; }); # A296030 X,Y pairs MyOEIS::compare_values (anum => 'A296030', func => sub { my ($count) = @_; my @got; for (my $n=1; ; $n++) { my ($x,$y) = $path->n_to_xy($n); @got < $count or last; push @got, $x; @got < $count or last; push @got, $y; } return \@got; }); # GP-DEFINE \\ X cooordinate, 1-based, my line in A174344 # GP-DEFINE A174344(n) = { # GP-DEFINE n>=1 || error(); # GP-DEFINE n--; my(m=sqrtint(n),k=ceil(m/2)); # GP-DEFINE n -= 4*k^2; # GP-DEFINE if(n<0, if(n<-m, k, -k-n), if(n=1 || error(); # GP-DEFINE n--; my(m=sqrtint(n), k=ceil(m/2)); # GP-DEFINE n -= 4*k^2; # GP-DEFINE if(n<0, if(n<-m, 3*k+n, k), if(n N # GP-DEFINE \\ a couple of different ways # GP-DEFINE XY_to_N0(x,y) = { # GP-DEFINE if(x>abs(y), \\ right vertical # GP-DEFINE (4*x-2)*x - (x-y), # GP-DEFINE -x>abs(y), \\ left vertical # GP-DEFINE (4*x-2)*x + (x-y), # GP-DEFINE y>0, # GP-DEFINE (4*y-2)*y - (x-y), \\ top horizontal # GP-DEFINE (4*y-2)*y + (x-y)); \\ bottom horizontal # GP-DEFINE } # GP-DEFINE XY_to_N1(x,y) = XY_to_N0(x,y) + 1; # # GP-Test /* XY_to_N1() vs back again X() and Y() */ \ # GP-Test for(y=-1,20, \ # GP-Test for(x=-1,20, \ # GP-Test my(n=XY_to_N1(x,y), back_x=X(n),back_y=Y(n)); \ # GP-Test (x==back_x && y==back_y) \ # GP-Test || error("xy=",x,",",y," n="n" which is xy="back_x" "back_y))); \ # GP-Test 1 # GP-DEFINE check_XY_to_N0_func(func) = { # GP-DEFINE if(0, \\ view # GP-DEFINE forstep(y=4,-4,-1, # GP-DEFINE for(x=-4,4, # GP-DEFINE printf(" %4d", func(x,y))); # GP-DEFINE print())); # GP-DEFINE # GP-DEFINE for(y=-20,20, # GP-DEFINE for(x=-20,20, # GP-DEFINE my(want=XY_to_N0(x,y), # GP-DEFINE got=func(x,y)); # GP-DEFINE if(want!=got, # GP-DEFINE error("xy=",x,",",y," want=",want," got=",got)))); # GP-DEFINE 1; # GP-DEFINE } # Per # Ronald L. Graham, Donald E. Knuth, Oren Patashnik, "Concrete Mathematics", # Addison-Wesley, 1989, chapter 3 "Integer Functions", exercise 40 page 99, # answer page 498. # They spiral clockwise so y negated as compared to the form here. # # GP-Test check_XY_to_N0_func((x,y)-> \ # GP-Test y = -y; \ # GP-Test my(k=max(abs(x),abs(y))); \ # GP-Test (2*k)^2 + if(x>y, -1, 1) * (2*k + x + y); \ # GP-Test ) # Using x-y diag as offset NW,SE, like Graham, Knuth, Patashnik. # GP-Test check_XY_to_N0_func((x,y)-> \ # GP-Test my(r=2*max(abs(x),abs(y))); \ # GP-Test r^2 + if(x+y>0, -(x-y+r), x-y+r); \ # GP-Test ) # GP-Test /* transpose to go to x as radial distance */ \ # GP-Test /* then x-y diagonal as offset */ \ # GP-Test check_XY_to_N0_func((x,y)-> \ # GP-Test my(s = if(abs(x)>abs(y), -sign(x), [x,y]=[y,x];sign(x))); \ # GP-Test (4*x-2)*x + s*(x-y); \ # GP-Test ) # GP-Test check_XY_to_N0_func((x,y)-> \ # GP-Test my(s = if(abs(x)>abs(y), -sign(x), [x,y]=[y,x];sign(x))); \ # GP-Test 4*x^2 - 2*x + s*x - s*y; \ # GP-Test ) # # GP-Test check_XY_to_N0_func((x,y)-> \ # GP-Test if(abs(x)>abs(y), \ # GP-Test (4*x-2)*x - sign(x)*(x-y), \ # GP-Test (4*y-2)*y - sign(y)*(x-y)); \ # GP-Test ) # GP-Test check_XY_to_N0_func((x,y)-> \ # GP-Test if(abs(x)>abs(y), \ # GP-Test 4*x*x - abs(x) + sign(x)*(y - 2*abs(x)), \ # GP-Test (4*y-2)*y - sign(y)*(x-y)); \ # GP-Test ) # GP-Test check_XY_to_N0_func((x,y)-> \ # GP-Test my(s = if(abs(x)>abs(y), -sign(x), [x,y]=[y,x];sign(x))); \ # GP-Test my(t=x+y,d=x-y); \ # GP-Test t^2 + 2*d*t + d^2 - t - d + s*d; \ # GP-Test ) # GP-Test check_XY_to_N0_func((x,y)-> \ # GP-Test my(swap=abs(x)>abs(y)); \ # GP-Test my(s); \ # GP-Test my(t=x+y); \ # GP-Test my(d=x-y); \ # GP-Test swap == (abs(t+d)>abs(t-d)) || error(); \ # GP-Test swap == (sign(t)*sign(d) > 0) || error(); \ # GP-Test /* d *= (-1)^!swap; */ \ # GP-Test d *= (-1)^(sign(t)*sign(d) <= 0); \ # GP-Test /* d = abs(d)*if(sign(t)>0,-1,1); */ \ # GP-Test if(swap, s=-sign(t+d), s=sign(t+d)); \ # GP-Test t^2 + 2*d*t + d^2 - t - d + s*d; \ # GP-Test ) # GP-Test check_XY_to_N0_func((x,y)-> \ # GP-Test my(t=x+y); \ # GP-Test my(d=x-y); \ # GP-Test my(r=max(abs(x),abs(y))); \ # GP-Test if(t>0, 4*r^2 - 2*r - d, \ # GP-Test 4*r^2 + 2*r + d); \ # GP-Test ) # GP-Test check_XY_to_N0_func((x,y)-> \ # GP-Test my(t=x+y); \ # GP-Test my(d=x-y); \ # GP-Test my(r=2*max(abs(x),abs(y))); \ # GP-Test if(t>0, r^2 - (r+d), \ # GP-Test r^2 + (r+d)); \ # GP-Test ) # GP-DEFINE N1_to_left(n) = { # GP-DEFINE my(x=X(n),y=Y(n),ret=0); # GP-DEFINE for(d=0,3, # GP-DEFINE my(dz=I^d, dx=real(dz), dy=imag(dz), # GP-DEFINE left=XY_to_N1(x+dx,y+dy)); # GP-DEFINE if(left>=n-1,next); # GP-DEFINE if(ret,error("n="n" dxdy="dx","dy" left="left" already ret="ret)); # GP-DEFINE ret=left); # GP-DEFINE ret; # GP-DEFINE } # vector(30,n, N1_to_left(n)) # forstep(y=4,-4,-1, \ # for(x=-4,4, \ # printf(" %4d", XY_to_N0(x,y))); \ # print()); #------------------------------------------------------------------------------ # A180714 X+Y coordinate sum, OFFSET=0 MyOEIS::compare_values (anum => 'A180714', func => sub { my ($count) = @_; my $path = $path_n_start_0; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x + $y; } return \@got; }); # GP-DEFINE \\ coordinate sum X+Y, 0-based # GP-DEFINE A180714(n) = { # GP-DEFINE n>=0 || error(); # GP-DEFINE n++; # GP-DEFINE X(n) + Y(n); # GP-DEFINE } # GP-Test /* compact */ \ # GP-Test vector(1000,n,n--; A180714(n)) == \ # GP-Test vector(1000,n,n--; my(s=ceil(sqrtint(4*n)/2)); \ # GP-Test (s^2 - (s%2) - n)*(-1)^s ) # # GP-Test vector(1000,n,n--; A180714(n)) == \ # GP-Test vector(1000,n,n--; my(s=if(n,ceil(sqrtint(4*n-3)/2))); \ # GP-Test (s^2 - (s%2) - n)*(-1)^s ) # # GP-Test /* round-to-nearest */ \ # GP-Test vector(1000,n,n--; A180714(n)) == \ # GP-Test vector(1000,n,n--; my(s=round(sqrt(n))); \ # GP-Test (s^2 - (s%2) - n)*(-1)^s ) # (7/2)^2 == 49/4 # integer n is never half way # # GP-Test /* sqrtint need to push half way */ \ # GP-Test vector(1000,n,n--; A180714(n)) == \ # GP-Test vector(1000,n,n--; my(s=sqrtint(n)); \ # GP-Test (abs(n-s*(s+1)) - s)*(-1)^s + (s%2) ) # GP-Test vector(1000,n,n--; A180714(n)) == \ # GP-Test vector(1000,n,n--; my(s=sqrtint(n),r=n-s*(s+1)); \ # GP-Test (abs(r)-s)*(-1)^s + (s%2) ) # # GP-Test /* more or less the X,Y cases */ \ # GP-Test vector(1000,n,n--; A180714(n)) == \ # GP-Test vector(1000,n,n--; my(s=sqrtint(n),r=n-s*(s+1)); \ # GP-Test if(s%2==1, if(r<=0, -abs(r) +s, -abs(r) +s), \ # GP-Test if(r<=0, abs(r) -s, abs(r) -s) ) \ # GP-Test + (s%2) ) # GP-Test vector(1000,n,n--; A180714(n)) == \ # GP-Test vector(1000,n,n--; my(m=sqrtint(n),k=ceil(m/2)); \ # GP-Test n -= 4*k^2; \ # GP-Test if(n<-m, 4*k+n, \ # GP-Test n>=m, -(4*k-n), \ # GP-Test n<0 && n>=-m, -n, \ # GP-Test n>=0 && nA180714(n)==0,[1..300]) # select(n->n>=2 && A180714(n-1)==A180714(n+1),[1..300]) # # 16-15-14-13-12 ... # | | | # 17 4--3--2 11 28 0-based # | | | | | even squares X+Y=0 # 18 5 0--1 10 27 # | | | | # 19 6--7--8--9 26 # | | # 20-21-22-23-24-25 # A180714 increments mentioned in A180714 # vector(30,n,n--; A180714(n+1) - A180714(n)) # not in OEIS: 1, 1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 # X+Y at corners, as mentioned in A180714 # Vec(-x*(1+x)/((x-1)*(x^2+1)^2) + O(x^20)) # not in OEIS: 1, 2, 0, -2, 1, 4, 0, -4, 1, 6, 0, -6, 1, 8, 0, -8, 1, 10, 0 #------------------------------------------------------------------------------ # A265400 left side neighbour, n_start=1 sub A265400 { my ($n) = @_; $n >= 1 or die "A265400 is for n>=1"; my ($x,$y) = $path->n_to_xy ($n); $path->n_start == 1 or die; return max(0, map { my $n2 = $path->xy_to_n($x + $dir4_to_dx[$_], $y + $dir4_to_dy[$_]); defined $n2 && $n2 < $n-1 ? ($n2) : () } 0 .. 3); } MyOEIS::compare_values (anum => 'A265400', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { push @got, A265400($n); } return \@got; }); { # A265400() vs formula like the GP form below my $bad = 0; foreach my $n (1 .. 10000) { my $want = A265400($n); my $got; if (issquare($n-1) || issquare(4*$n-3)) { $got = 0; } else { $got = $n - 2*int(sqrt(4*$n - 3)) + 3; } unless ($got == $want) { $bad++; } } ok ($bad, 0, 'A265400 formula vs path'); } # cf Antti Karttunen Scheme code ~/OEIS/a260643.txt # # GP-DEFINE A265400(n) = { # GP-DEFINE n>=1 || error("A265400() is for n>=1"); # GP-DEFINE \\ if(n>1, n - 2*sqrtint(4*n-1) - 3); # GP-DEFINE \\ 2*sqrtint(4*n-1)-3; # GP-DEFINE if(issquare(n-1) || issquare(4*n-3), 0, n+3 - 2*sqrtint(4*n-3)); # GP-DEFINE } # forstep(y=5,-5,-1, \ # for(x=-5,5, \ # my(n=XY_to_N1(x,y)); \ # printf(" %4d/%-2d", n, A265400(n))); \ # print()); # my(v=OEIS_samples("A265400")); vector(#v,n, A265400(n)) == v /* OFFSET=1 */ # my(g=OEIS_bfile_gf("A265400")); g==x*Polrev(vector(poldegree(g),n, A265400(n))) # poldegree(OEIS_bfile_gf("A265400")) # vector(40,n, A265400(n)) # GP-DEFINE ceil_sqrt(n) = my(s); if(issquare(n,&s), s, sqrtint(n)+1); # GP-Test vector(1000,n, my(s=ceil_sqrt(n)); (s-1)^2 < n && n <= s^2) == \ # GP-Test vector(1000,n, 1) # GP-Test /* formula */ \ # GP-Test vector(1000,n, A265400(n)) == \ # GP-Test vector(1000,n, if(issquare(n-1) || issquare(4*n-3), 0, \ # GP-Test n+5 - 2*ceil_sqrt(4*n) )) # runs of offset to the left cell # vector(40,n, if(n_is_corner_1based(n),0, n - A265400(n))) # not in OEIS: 3, 0, 5, 0, 7, 7, 0, 9, 9, 0, 11, 11, 11, 0, 13, 13, 13, 0, 15, 15, 15, 15, 0, 17, 17, 17, 17, 0, 19, 19, 19, 19, 19, 0, 21, 21, 21 #------------------------------------------------------------------------------ # A010052 - issquare() helper sub issquare { my ($n) = @_; return int(sqrt($n))**2 == $n; } MyOEIS::compare_values (anum => q{A010052}, # not shown in POD func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { push @got, issquare($n) ? 1 : 0; } return \@got; }); #------------------------------------------------------------------------------ # A267682 Y axis positive and negative, n_start=1, origin twice MyOEIS::compare_values (anum => 'A267682', func => sub { my ($count) = @_; my @got; my $y = 0; for (;;) { push @got, $path->xy_to_n(0, $y); last unless @got < $count; push @got, $path->xy_to_n(0, -$y); last unless @got < $count; $y++; } return \@got; }); # A156859 Y axis positive and negative, n_start=0 MyOEIS::compare_values (anum => 'A156859', func => sub { my ($count) = @_; my $path = $path_n_start_0; my @got = (0); for (my $y = 1; @got < $count; $y++) { push @got, $path->xy_to_n(0, $y); last unless @got < $count; push @got, $path->xy_to_n(0, -$y); } return \@got; }); #------------------------------------------------------------------------------ # A059924 Write the numbers from 1 to n^2 in a spiralling square; a(n) is the # total of the sums of the two diagonals. MyOEIS::compare_values (anum => q{A059924}, # not shown in pod max_count => 1000, func => sub { my ($count) = @_; my @got = (0); for (my $n = 1; @got < $count; $n++) { ### A059924 ... push @got, A059924($n); } return \@got; }); # A059924 spirals inwards, use $square+1 - $t to reverse the path numbering sub A059924 { my ($n) = @_; ### A059924(): $n my $square = $n*$n; ### $square my $total = 0; my ($x,$y) = $path->n_to_xy($square); my $dx = ($x <= 0 ? 1 : -1); my $dy = ($y <= 0 ? 1 : -1); ### diagonal: "$x,$y dir $dx,$dy" for (;;) { my $t = $path->xy_to_n($x,$y); ### $t last if $t > $square; $total += $square+1 - $t; $x += $dx; $y += $dy; } $x -= $dx; $y -= $dy * $n; $dx = - $dx; ### diagonal: "$x,$y dir $dx,$dy" for (;;) { my $t = $path->xy_to_n($x,$y); ### $t last if $t > $square; $total += $square+1 - $t; $x += $dx; $y += $dy; } ### $total return $total; } #------------------------------------------------------------------------------ # A027709 -- unit squares figure boundary MyOEIS::compare_values (anum => 'A027709', func => sub { my ($count) = @_; my @got = (0); for (my $n = $path->n_start; @got < $count; $n++) { push @got, $path->_NOTDOCUMENTED_n_to_figure_boundary($n); } return \@got; }); #------------------------------------------------------------------------------ # A078633 -- grid sticks sub path_n_to_dsticks { my ($path, $n) = @_; my ($x,$y) = $path->n_to_xy($n); my $dsticks = 4; foreach my $i (0 .. $#dir4_to_dx) { my $an = $path->xy_to_n($x+$dir4_to_dx[$i], $y+$dir4_to_dy[$i]); $dsticks -= (defined $an && $an < $n); } return $dsticks; } MyOEIS::compare_values (anum => 'A078633', func => sub { my ($count) = @_; my @got; my $boundary = 0; for (my $n = $path->n_start; @got < $count; $n++) { $boundary += path_n_to_dsticks($path,$n); push @got, $boundary; } return \@got; }); #------------------------------------------------------------------------------ # A094768 -- cumulative spiro-fibonacci total of 4 neighbours MyOEIS::compare_values (anum => q{A094768}, func => sub { my ($count) = @_; my $path = $path_n_start_0; my $total = Math::BigInt->new(1); my @got = ($total); for (my $n = $path->n_start + 1; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n-1); foreach my $i (0 .. $#dir4_to_dx) { my $sn = $path->xy_to_n ($x+$dir4_to_dx[$i], $y+$dir4_to_dy[$i]); if ($sn < $n) { $total += $got[$sn]; } } $got[$n] = $total; } return \@got; }); #------------------------------------------------------------------------------ # A094767 -- cumulative spiro-fibonacci total of 8 neighbours MyOEIS::compare_values (anum => q{A094767}, func => sub { my ($count) = @_; my $path = $path_n_start_0; my $total = Math::BigInt->new(1); my @got = ($total); for (my $n = $path->n_start + 1; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n-1); foreach my $i (0 .. $#dir8_to_dx) { my $sn = $path->xy_to_n ($x+$dir8_to_dx[$i], $y+$dir8_to_dy[$i]); if ($sn < $n) { $total += $got[$sn]; } } $got[$n] = $total; } return \@got; }); #------------------------------------------------------------------------------ # A094769 -- cumulative spiro-fibonacci total of 8 neighbours starting 0,1 MyOEIS::compare_values (anum => q{A094769}, func => sub { my ($count) = @_; my $path = $path_n_start_0; my $total = Math::BigInt->new(1); my @got = (0, $total); for (my $n = $path->n_start + 2; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n-1); foreach my $i (0 .. $#dir8_to_dx) { my $sn = $path->xy_to_n ($x+$dir8_to_dx[$i], $y+$dir8_to_dy[$i]); if ($sn < $n) { $total += $got[$sn]; } } $got[$n] = $total; } return \@got; }); #------------------------------------------------------------------------------ # A078784 -- primes on any axis positive or negative MyOEIS::compare_values (anum => q{A078784}, # not shown in POD func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { next unless is_prime($n); my ($x,$y) = $path->n_to_xy ($n); if ($x == 0 || $y == 0) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A090925 -- permutation rotate +90 MyOEIS::compare_values (anum => 'A090925', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); ($x,$y) = (-$y,$x); # rotate +90 push @got, $path->xy_to_n ($x, $y); } return \@got; }); # A090928 -- permutation rotate +180 MyOEIS::compare_values (anum => 'A090928', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); ($x,$y) = (-$x,-$y); # rotate +180 push @got, $path->xy_to_n ($x, $y); } return \@got; }); # A090929 -- permutation rotate +270 MyOEIS::compare_values (anum => 'A090929', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); ($x,$y) = ($y,-$x); # rotate -90 push @got, $path->xy_to_n ($x, $y); } return \@got; }); # A090861 -- permutation rotate +180, opp direction MyOEIS::compare_values (anum => 'A090861', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); $y = -$y; # opp direction ($x,$y) = (-$x,-$y); # rotate 180 push @got, $path->xy_to_n ($x, $y); } return \@got; }); # A090915 -- permutation rotate +270, opp direction MyOEIS::compare_values (anum => 'A090915', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); $y = -$y; # opp direction ($x,$y) = ($y,-$x); # rotate -90 push @got, $path->xy_to_n ($x, $y); } return \@got; }); # A090930 -- permutation opp direction MyOEIS::compare_values (anum => 'A090930', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); $y = -$y; # opp direction push @got, $path->xy_to_n ($x, $y); } return \@got; }); # A185413 -- rotate 180, offset X+1,Y MyOEIS::compare_values (anum => 'A185413', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); $x = 1 - $x; push @got, $path->xy_to_n ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ # A078765 -- primes at integer radix sqrt(x^2+y^2), and not on axis MyOEIS::compare_values (anum => q{A078765}, # not shown in POD func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { next unless is_prime($n); my ($x,$y) = $path->n_to_xy ($n); if ($x != 0 && $y != 0 && is_perfect_square($x*$x+$y*$y)) { push @got, $n; } } return \@got; }); sub is_perfect_square { my ($n) = @_; my $sqrt = int(sqrt($n)); return ($sqrt*$sqrt == $n); } #------------------------------------------------------------------------------ # A200975 -- N on all four diagonals MyOEIS::compare_values (anum => 'A200975', func => sub { my ($count) = @_; my @got = (1); for (my $i = 1; @got < $count; $i++) { push @got, $path->xy_to_n($i,$i); last unless @got < $count; push @got, $path->xy_to_n(-$i,$i); last unless @got < $count; push @got, $path->xy_to_n(-$i,-$i); last unless @got < $count; push @got, $path->xy_to_n($i,-$i); last unless @got < $count; } return \@got; }); # #------------------------------------------------------------------------------ # # A195060 -- N on axis or diagonal ??? # # vertices generalized pentagonal 0,1,2,5,7,12,15,22,... # # union A001318, A032528, A045943 # # MyOEIS::compare_values # (anum => 'A195060', # func => sub { # my ($count) = @_; # my @got = (0); # for (my $n = $path->n_start; @got < $count; $n++) { # my ($x,$y) = $path->n_to_xy ($n); # if ($x == $y || $x == -$y || $x == 0 || $y == 0) { # push @got, $n; # } # } # return \@got; # }); # #------------------------------------------------------------------------------ # # A137932 -- count points not on diagonals up to nxn # # MyOEIS::compare_values # (anum => 'A137932', # max_value => 1000, # func => sub { # my ($count) = @_; # my @got; # for (my $k = 0; @got < $count; $k++) { # my $num = 0; # my ($cx,$cy) = $path->n_to_xy ($k*$k); # foreach my $n (1 .. $k*$k) { # my ($x,$y) = $path->n_to_xy ($n); # $num += (abs($x) != abs($y)); # } # push @got, $num; # } # return \@got; # }); #------------------------------------------------------------------------------ # A113688 -- isolated semi-primes # cf # A113689 Number of semiprimes in clumps of size >1 through n^2 in the semiprime spiral. MyOEIS::compare_values (anum => q{A113688}, # not shown in POD func => sub { my ($count) = @_; my $seq = Math::NumSeq::AlmostPrimes->new; my @got; N: for (my $n = $path->n_start; @got < $count; $n++) { next unless $seq->pred($n); # want n a semiprime my ($x,$y) = $path->n_to_xy ($n); foreach my $i (0 .. $#dir8_to_dx) { my $sn = $path->xy_to_n ($x+$dir8_to_dx[$i], $y+$dir8_to_dy[$i]); if ($seq->pred($sn)) { next N; # has a semiprime neighbour, skip } } push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ # A215470 -- primes with >=4 prime neighbours in 8 surround MyOEIS::compare_values (anum => 'A215470', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { next unless is_prime($n); my ($x,$y) = $path->n_to_xy ($n); my $num = 0; foreach my $i (0 .. $#dir8_to_dx) { my $sn = $path->xy_to_n ($x+$dir8_to_dx[$i], $y+$dir8_to_dy[$i]); if (is_prime($sn)) { $num++; } } if ($num >= 4) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A137930 sum leading and anti diagonal of nxn square MyOEIS::compare_values (anum => q{A137930}, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { push @got, diagonals_total($path,$k); } return \@got; }); MyOEIS::compare_values (anum => q{A137931}, # 2n x 2n func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k+=2) { push @got, diagonals_total($path,$k); } return \@got; }); # A114254 Sum of all terms on the two principal diagonals of a 2n+1 X 2n+1 square spiral. MyOEIS::compare_values (anum => q{A114254}, # 2n+1 x 2n+1 func => sub { my ($count) = @_; my @got; for (my $k = 1; @got < $count; $k+=2) { push @got, diagonals_total($path,$k); } return \@got; }); sub diagonals_total { my ($path, $k) = @_; ### diagonals_total(): $k if ($k == 0) { return 0; } my ($x,$y) = $path->n_to_xy ($k*$k); # corner my $dx = ($x > 0 ? -1 : 1); my $dy = ($y > 0 ? -1 : 1); ### corner: "$x,$y dx=$dx,dy=$dy" my %n; foreach my $i (0 .. $k-1) { my $n = $path->xy_to_n($x,$y); $n{$n} = 1; $x += $dx; $y += $dy; } $x -= $k*$dx; $dy = -$dy; $y += $dy; ### opposite: "$x,$y dx=$dx,dy=$dy" foreach my $i (0 .. $k-1) { my $n = $path->xy_to_n($x,$y); $n{$n} = 1; $x += $dx; $y += $dy; } ### n values: keys %n return sum(keys %n); } #------------------------------------------------------------------------------ # A059428 -- Prime[N] for N=corner MyOEIS::compare_values (anum => q{A059428}, func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my @got = (2); while (@got < $count) { my ($i,$value) = $seq->next; if ($value) { push @got, MyOEIS::ith_prime($i); # i=2 as first turn giving prime=3 } } return \@got; }); #------------------------------------------------------------------------------ # A123663 -- count total shared edges MyOEIS::compare_values (anum => q{A123663}, func => sub { my ($count) = @_; my @got; my $edges = 0; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); foreach my $sn ($path->xy_to_n($x+1,$y), $path->xy_to_n($x-1,$y), $path->xy_to_n($x,$y+1), $path->xy_to_n($x,$y-1)) { if ($sn < $n) { $edges++; } } push @got, $edges; } return \@got; }); #------------------------------------------------------------------------------ # A172294 -- jewels, composite surrounded by 4 primes NSEW, n_start = 0 # # Parity of loops mean n even has NSEW neighbours odd, and vice versa MyOEIS::compare_values (anum => q{A172294}, # not shown in POD func => sub { my ($count) = @_; my @got; my $path = $path_n_start_0; for (my $n = $path->n_start; @got < $count; $n++) { next if is_prime($n); my ($x,$y) = $path->n_to_xy ($n); if (is_prime ($path->xy_to_n($x+1,$y)) && is_prime ($path->xy_to_n($x-1,$y)) && is_prime ($path->xy_to_n($x,$y+1)) && is_prime ($path->xy_to_n($x,$y-1)) ) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A115258 -- isolated primes, 8 neighbours MyOEIS::compare_values (anum => q{A115258}, # not shown in POD func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { next unless is_prime($n); my ($x,$y) = $path->n_to_xy ($n); if (! is_prime ($path->xy_to_n($x+1,$y)) && ! is_prime ($path->xy_to_n($x-1,$y)) && ! is_prime ($path->xy_to_n($x,$y+1)) && ! is_prime ($path->xy_to_n($x,$y-1)) && ! is_prime ($path->xy_to_n($x+1,$y+1)) && ! is_prime ($path->xy_to_n($x-1,$y-1)) && ! is_prime ($path->xy_to_n($x-1,$y+1)) && ! is_prime ($path->xy_to_n($x+1,$y-1)) ) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A214177 -- sum of 4 neighbours MyOEIS::compare_values (anum => q{A214177}, # not shown in POD func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); push @got, ($path->xy_to_n($x+1,$y) + $path->xy_to_n($x-1,$y) + $path->xy_to_n($x,$y+1) + $path->xy_to_n($x,$y-1) ); } return \@got; }); #------------------------------------------------------------------------------ # A214176 -- sum of 8 neighbours MyOEIS::compare_values (anum => q{A214176}, # not shown in POD func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); push @got, ($path->xy_to_n($x+1,$y) + $path->xy_to_n($x-1,$y) + $path->xy_to_n($x,$y+1) + $path->xy_to_n($x,$y-1) + $path->xy_to_n($x+1,$y+1) + $path->xy_to_n($x-1,$y-1) + $path->xy_to_n($x-1,$y+1) + $path->xy_to_n($x+1,$y-1) ); } return \@got; }); #------------------------------------------------------------------------------ # A214664 -- X coord of prime N MyOEIS::compare_values (anum => 'A214664', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { next unless is_prime($n); my ($x,$y) = $path->n_to_xy ($n); push @got, $x; } return \@got; }); # A214665 -- Y coord of prime N MyOEIS::compare_values (anum => 'A214665', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { next unless is_prime($n); my ($x,$y) = $path->n_to_xy ($n); push @got, $y; } return \@got; }); # A214666 -- X coord of prime N, first to west MyOEIS::compare_values (anum => 'A214666', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { next unless is_prime($n); my ($x,$y) = $path->n_to_xy ($n); push @got, -$x; } return \@got; }); # A214667 -- Y coord of prime N, first to west MyOEIS::compare_values (anum => 'A214667', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { next unless is_prime($n); my ($x,$y) = $path->n_to_xy ($n); push @got, -$y; } return \@got; }); #------------------------------------------------------------------------------ # A143856 -- N values ENE slope=2 MyOEIS::compare_values (anum => 'A143856', func => sub { my ($count) = @_; my @got; for (my $i = 0; @got < $count; $i++) { push @got, $path->xy_to_n (2*$i, $i); } return \@got; }); #------------------------------------------------------------------------------ # A143861 -- N values NNE slope=2 MyOEIS::compare_values (anum => 'A143861', func => sub { my ($count) = @_; my @got; for (my $i = 0; @got < $count; $i++) { push @got, $path->xy_to_n ($i, 2*$i); } return \@got; }); #------------------------------------------------------------------------------ # A062410 -- a(n) is sum of existing numbers in row of a(n-1) MyOEIS::compare_values (anum => 'A062410', func => sub { my ($count) = @_; my @got; my %plotted; $plotted{0,0} = Math::BigInt->new(1); my $xmin = 0; my $ymin = 0; my $xmax = 0; my $ymax = 0; push @got, 1; for (my $n = $path->n_start + 1; @got < $count; $n++) { my ($prev_x, $prev_y) = $path->n_to_xy ($n-1); my ($x, $y) = $path->n_to_xy ($n); my $total = 0; if ($y == $prev_y) { ### column: "$ymin .. $ymax at x=$prev_x" foreach my $y ($ymin .. $ymax) { $total += $plotted{$prev_x,$y} || 0; } } else { ### row: "$xmin .. $xmax at y=$prev_y" foreach my $x ($xmin .. $xmax) { $total += $plotted{$x,$prev_y} || 0; } } ### total: "$total" $plotted{$x,$y} = $total; $xmin = min($xmin,$x); $xmax = max($xmax,$x); $ymin = min($ymin,$y); $ymax = max($ymax,$y); push @got, $total; } return \@got; }); #------------------------------------------------------------------------------ # A020703 -- permutation read clockwise, ie. transpose Y,X # also permutation rotate +90, opp direction MyOEIS::compare_values (anum => 'A020703', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n ($y, $x); } return \@got; }); #------------------------------------------------------------------------------ # A121496 -- run lengths of consecutive N in A068225 N at X+1,Y MyOEIS::compare_values (anum => 'A121496', func => sub { my ($count) = @_; my @got; my $num = 0; my $prev_right_n = A068225(1) - 1; # make first value look like a run for (my $n = $path->n_start; @got < $count; $n++) { my $right_n = A068225($n); if ($right_n == $prev_right_n + 1) { $num++; } else { push @got, $num; $num = 1; } $prev_right_n = $right_n; } return \@got; }); #------------------------------------------------------------------------------ # A054551 -- plot Nth prime at each N, values are those primes on X axis MyOEIS::compare_values (anum => 'A054551', func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x++) { my $n = $path->xy_to_n($x,0); push @got, MyOEIS::ith_prime($n); } return \@got; }); #------------------------------------------------------------------------------ # A054553 -- plot Nth prime at each N, values are those primes on X=Y diagonal MyOEIS::compare_values (anum => 'A054553', func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x++) { my $n = $path->xy_to_n($x,$x); push @got, MyOEIS::ith_prime($n); } return \@got; }); #------------------------------------------------------------------------------ # A054555 -- plot Nth prime at each N, values are those primes on Y axis MyOEIS::compare_values (anum => 'A054555', func => sub { my ($count) = @_; my @got; for (my $y = 0; @got < $count; $y++) { my $n = $path->xy_to_n(0,$y); push @got, MyOEIS::ith_prime($n); } return \@got; }); #------------------------------------------------------------------------------ # A053999 -- plot Nth prime at each N, values are those primes on South-East MyOEIS::compare_values (anum => 'A053999', func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x++) { my $n = $path->xy_to_n($x,-$x); push @got, MyOEIS::ith_prime($n); } return \@got; }); #------------------------------------------------------------------------------ # A054564 -- plot Nth prime at each N, values are those primes on North-West MyOEIS::compare_values (anum => 'A054564', func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x--) { my $n = $path->xy_to_n($x,-$x); push @got, MyOEIS::ith_prime($n); } return \@got; }); #------------------------------------------------------------------------------ # A054566 -- plot Nth prime at each N, values are those primes on negative X MyOEIS::compare_values (anum => 'A054566', func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x--) { my $n = $path->xy_to_n($x,0); push @got, MyOEIS::ith_prime($n); } return \@got; }); #------------------------------------------------------------------------------ # A137928 -- N values on diagonal X=1-Y positive and negative MyOEIS::compare_values (anum => 'A137928', func => sub { my ($count) = @_; my @got; for (my $y = 0; @got < $count; $y++) { push @got, $path->xy_to_n(1-$y,$y); last unless @got < $count; if ($y != 0) { push @got, $path->xy_to_n(1-(-$y),-$y); } } return \@got; }); #------------------------------------------------------------------------------ # A002061 -- central polygonal numbers, N values on diagonal X=Y pos and neg MyOEIS::compare_values (anum => 'A002061', func => sub { my ($count) = @_; my @got; for (my $y = 0; @got < $count; $y++) { push @got, $path->xy_to_n($y,$y); last unless @got < $count; push @got, $path->xy_to_n(-$y,-$y); } return \@got; }); #------------------------------------------------------------------------------ # A016814 -- N values (4n+1)^2 on SE diagonal every second square MyOEIS::compare_values (anum => 'A016814', func => sub { my ($count) = @_; my @got; for (my $i = 0; @got < $count; $i+=2) { push @got, $path->xy_to_n($i,-$i); } return \@got; }); #------------------------------------------------------------------------------ # A033952 -- AllDigits on negative Y axis MyOEIS::compare_values (anum => 'A033952', func => sub { my ($count) = @_; my @got; my $seq = Math::NumSeq::AllDigits->new; for (my $y = 0; @got < $count; $y--) { my $n = $path->xy_to_n (0, $y); push @got, $seq->ith($n); } return \@got; }); #------------------------------------------------------------------------------ # A033953 -- AllDigits starting 0, on negative Y axis MyOEIS::compare_values (anum => 'A033953', func => sub { my ($count) = @_; my @got; my $seq = Math::NumSeq::AllDigits->new; for (my $y = 0; @got < $count; $y--) { my $n = $path->xy_to_n (0, $y); push @got, $seq->ith($n-1); } return \@got; }); #------------------------------------------------------------------------------ # A033988 -- AllDigits starting 0, on negative X axis MyOEIS::compare_values (anum => 'A033988', func => sub { my ($count) = @_; my @got; my $seq = Math::NumSeq::AllDigits->new; for (my $x = 0; @got < $count; $x--) { my $n = $path->xy_to_n ($x, 0); push @got, $seq->ith($n-1); } return \@got; }); #------------------------------------------------------------------------------ # A033989 -- AllDigits starting 0, on positive Y axis MyOEIS::compare_values (anum => 'A033989', func => sub { my ($count) = @_; my @got; my $seq = Math::NumSeq::AllDigits->new; for (my $y = 0; @got < $count; $y++) { my $n = $path->xy_to_n (0, $y); push @got, $seq->ith($n-1); } return \@got; }); #------------------------------------------------------------------------------ # A033990 -- AllDigits starting 0, on positive X axis MyOEIS::compare_values (anum => 'A033990', func => sub { my ($count) = @_; my @got; my $seq = Math::NumSeq::AllDigits->new; for (my $x = 0; @got < $count; $x++) { my $n = $path->xy_to_n ($x, 0); push @got, $seq->ith($n-1); } return \@got; }); #------------------------------------------------------------------------------ # A054556 -- N values on Y axis (but OFFSET=1) MyOEIS::compare_values (anum => 'A054556', func => sub { my ($count) = @_; my @got; for (my $y = 0; @got < $count; $y++) { push @got, $path->xy_to_n(0,$y); } return \@got; }); #------------------------------------------------------------------------------ # A054554 -- N values on X=Y diagonal MyOEIS::compare_values (anum => 'A054554', func => sub { my ($count) = @_; my @got; for (my $i = 0; @got < $count; $i++) { push @got, $path->xy_to_n($i,$i); } return \@got; }); #------------------------------------------------------------------------------ # A054569 -- N values on negative X=Y diagonal, but OFFSET=1 MyOEIS::compare_values (anum => 'A054569', func => sub { my ($count) = @_; my @got; for (my $i = 0; @got < $count; $i++) { push @got, $path->xy_to_n(-$i,-$i); } return \@got; }); #------------------------------------------------------------------------------ #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/PeanoCurve-oeis.t0000644000175000017500000004442113751363077016512 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2015, 2018, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 23; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::PeanoCurve; my $peano = Math::PlanePath::PeanoCurve->new; use Math::PlanePath::Diagonals; use Math::PlanePath::ZOrderCurve; # GP-DEFINE read("my-oeis.gp"); # GP-DEFINE to_ternary(n) = fromdigits(digits(n,3)); # GP-DEFINE to_base9(n) = fromdigits(digits(n,9)); #------------------------------------------------------------------------------ # A163332 -- permutation Peano N at points in Z-Order radix=3 sequence MyOEIS::compare_values (anum => 'A163332', func => sub { my ($count) = @_; my $zorder = Math::PlanePath::ZOrderCurve->new (radix => 3); my @got; for (my $n = $zorder->n_start; @got < $count; $n++) { my ($x,$y) = $zorder->n_to_xy ($n); push @got, $peano->xy_to_n ($x,$y); } return \@got; }); MyOEIS::compare_values (anum => q{A163332}, func => sub { my ($count) = @_; my $zorder = Math::PlanePath::ZOrderCurve->new (radix => 3); my @got; for (my $n = $peano->n_start; @got < $count; $n++) { my ($x,$y) = $peano->n_to_xy ($n); # other way around push @got, $zorder->xy_to_n ($x,$y); } return \@got; }); # GP-DEFINE A163332(n) = { # GP-DEFINE my(v=digits(n,3),k=Mod([0,0],2)); # GP-DEFINE for(i=1,#v, if(k[1],v[i]=2-v[i]); k[2]+=v[i]; k=Vecrev(k)); # GP-DEFINE fromdigits(v,3); # GP-DEFINE } # GP-Test my(v=OEIS_samples("A163332")); /* OFFSET=0 */ \ # GP-Test vector(#v,n,n--; A163332(n)) == v # my(g=OEIS_bfile_gf("A163332")); \ # g==Polrev(vector(poldegree(g)+1,n,n--;A163332(n))) # poldegree(OEIS_bfile_gf("A163332")) # GP-DEFINE A163332_by_pos(n) = { # GP-DEFINE my(v=digits(n,3),k=Mod([0,0],2),p=1); # GP-DEFINE for(i=1,#v, if(k[p],v[i]=2-v[i]); p=3-p; k[p]+=v[i]); # GP-DEFINE fromdigits(v,3); # GP-DEFINE } # GP-Test vector(3^6,n,n--; A163332_by_pos(n)) == \ # GP-Test vector(3^6,n,n--; A163332(n)) # GP-DEFINE A163332_by_vars(n) = { # GP-DEFINE my(v=digits(n,3),x=Mod(0,2),y=x); # GP-DEFINE for(i=1,#v, if(x,v[i]=2-v[i]); [x,y]=[y+v[i],x]); # GP-DEFINE fromdigits(v,3); # GP-DEFINE } # GP-Test vector(3^6,n,n--; A163332_by_vars(n)) == \ # GP-Test vector(3^6,n,n--; A163332(n)) # GP-DEFINE A163332_by_passes(n) = { # GP-DEFINE my(v=digits(n,3)); # GP-DEFINE for(p=2,3, my(s=Mod(0,2)); # GP-DEFINE forstep(i=p,#v,2, s+=v[i-1]; if(s,v[i]=2-v[i]))); # GP-DEFINE fromdigits(v,3); # GP-DEFINE } # GP-Test vector(3^7,n,n--; A163332_by_passes(n)) == \ # GP-Test vector(3^7,n,n--; A163332(n)) # GP-DEFINE \\ none opp this both # GP-DEFINE \\ 1 4 7 10 # GP-DEFINE { my(table =[1,7,1, 7,1,7, 4,10,4, 10,4,10]); # GP-DEFINE A163332_by_table(n) = # GP-DEFINE my(v=digits(n,3),s=1); # GP-DEFINE for(i=1,#v, if(s>=7,v[i]=2-v[i]); s=table[s+v[i]]); # GP-DEFINE \\ print("i="i" s="s" digit "v[i]); # GP-DEFINE fromdigits(v,3); # GP-DEFINE } # GP-Test vector(3^7,n,n--; A163332_by_table(n)) == \ # GP-Test vector(3^7,n,n--; A163332(n)) # A163332_by_table(39) # A163332(39) # for(n=0,3^4, if(A163332_by_table(n) != A163332(n), print(n))); #------------------------------------------------------------------------------ # A163334 -- N by diagonals same axis MyOEIS::compare_values (anum => 'A163334', func => sub { my ($count) = @_; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up', n_start => 0); my @got; for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal->n_to_xy ($n); push @got, $peano->xy_to_n ($x, $y); } return \@got; }); # A163335 -- N by diagonals same axis, inverse MyOEIS::compare_values (anum => 'A163335', func => sub { my ($count) = @_; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up', n_start => 0); my @got; for (my $n = $peano->n_start; @got < $count; $n++) { my ($x, $y) = $peano->n_to_xy ($n); push @got, $diagonal->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A163336 -- diagonals opposite axis MyOEIS::compare_values (anum => 'A163336', func => sub { my ($count) = @_; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'down', n_start => 0); my @got; for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal->n_to_xy ($n); push @got, $peano->xy_to_n ($x, $y); } return \@got; }); # A163337 -- diagonals opposite axis, inverse MyOEIS::compare_values (anum => 'A163337', func => sub { my ($count) = @_; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'down', n_start => 0); my @got; for (my $n = $peano->n_start; @got < $count; $n++) { my ($x, $y) = $peano->n_to_xy ($n); push @got, $diagonal->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A163338 -- diagonals same axis, 1-based MyOEIS::compare_values (anum => 'A163338', func => sub { my ($count) = @_; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up'); my @got; for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal->n_to_xy ($n); push @got, $peano->xy_to_n ($x, $y) + 1; } return \@got; }); # A163339 -- diagonals same axis, 1-based, inverse MyOEIS::compare_values (anum => 'A163339', func => sub { my ($count) = @_; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up'); my @got; for (my $n = $peano->n_start; @got < $count; $n++) { my ($x, $y) = $peano->n_to_xy ($n); push @got, $diagonal->xy_to_n ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ # A163340 -- diagonals same axis, 1 based MyOEIS::compare_values (anum => 'A163340', func => sub { my ($count) = @_; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'down'); my @got; for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal->n_to_xy ($n); push @got, $peano->xy_to_n($x,$y) + 1; } return \@got; }); # A163341 -- diagonals same axis, 1-based, inverse MyOEIS::compare_values (anum => 'A163341', func => sub { my ($count) = @_; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'down'); my @got; for (my $n = $peano->n_start; @got < $count; $n++) { my ($x, $y) = $peano->n_to_xy ($n); push @got, $diagonal->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A163342 -- diagonal sums # no b-file as of Jan 2020 MyOEIS::compare_values (anum => 'A163342', func => sub { my ($count) = @_; my @got; for (my $d = 0; @got < $count; $d++) { my $sum = 0; foreach my $x (0 .. $d) { my $y = $d - $x; $sum += $peano->xy_to_n ($x, $y); } push @got, $sum; } return \@got; }); # A163479 -- diagonal sums div 6 MyOEIS::compare_values (anum => 'A163479', func => sub { my ($count) = @_; my @got; for (my $d = 0; @got < $count; $d++) { my $sum = 0; foreach my $x (0 .. $d) { my $y = $d - $x; $sum += $peano->xy_to_n ($x, $y); } push @got, int($sum/6); } return \@got; }); #------------------------------------------------------------------------------ # A163534 -- absolute direction 0=east, 1=south, 2=west, 3=north # Y coordinates reckoned down the page, so south is Y increasing MyOEIS::compare_values (anum => 'A163534', func => sub { my ($count) = @_; my @got; for (my $n = $peano->n_start; @got < $count; $n++) { my ($dx,$dy) = $peano->n_to_dxdy ($n); push @got, MyOEIS::dxdy_to_direction ($dx,$dy); } return \@got; }); #------------------------------------------------------------------------------ # A163535 -- absolute direction transpose 0=east, 1=south, 2=west, 3=north MyOEIS::compare_values (anum => 'A163535', func => sub { my ($count) = @_; my @got; for (my $n = $peano->n_start; @got < $count; $n++) { my ($dx,$dy) = $peano->n_to_dxdy ($n); push @got, MyOEIS::dxdy_to_direction ($dy,$dx); } return \@got; }); #------------------------------------------------------------------------------ # A145204 -- N+1 of positions of vertical steps, dx=0 MyOEIS::compare_values (anum => 'A145204', func => sub { my ($count) = @_; my @got = (0); for (my $n = $peano->n_start; @got < $count; $n++) { my ($dx,$dy) = $peano->n_to_dxdy($n); if ($dx == 0) { push @got, $n+1; } } return \@got; }); #------------------------------------------------------------------------------ # A014578 -- abs(dX), 1=horizontal 0=vertical, extra initial 0 MyOEIS::compare_values (anum => 'A014578', func => sub { my ($count) = @_; my @got = (0); for (my $n = $peano->n_start; @got < $count; $n++) { my ($dx,$dy) = $peano->n_to_dxdy($n); push @got, abs($dx); } return \@got; }); # A182581 -- abs(dY), but OFFSET=1 MyOEIS::compare_values (anum => 'A182581', func => sub { my ($count) = @_; my @got; for (my $n = $peano->n_start; @got < $count; $n++) { my ($dx,$dy) = $peano->n_to_dxdy($n); push @got, abs($dy); } return \@got; }); #------------------------------------------------------------------------------ # A007417 -- N+1 positions of horizontal step, dY==0, abs(dX)=1 # N+1 has even num trailing ternary 0-digits MyOEIS::compare_values (anum => 'A007417', func => sub { my ($count) = @_; my @got; for (my $n = $peano->n_start; @got < $count; $n++) { my ($dx,$dy) = $peano->n_to_dxdy($n); if ($dy == 0) { push @got, $n+1; } } return \@got; }); #------------------------------------------------------------------------------ # A163532 -- dX a(n)-a(n-1) so extra initial 0 MyOEIS::compare_values (anum => 'A163532', func => sub { my ($count) = @_; my @got = (0); # extra initial entry N=0 no change for (my $n = $peano->n_start; @got < $count; $n++) { my ($dx,$dy) = $peano->n_to_dxdy($n); push @got, $dx; } return \@got; }); # A163533 -- dY a(n)-a(n-1) MyOEIS::compare_values (anum => 'A163533', func => sub { my ($count) = @_; my @got = (0); # extra initial entry N=0 no change for (my $n = $peano->n_start; @got < $count; $n++) { my ($dx,$dy) = $peano->n_to_dxdy($n); push @got, $dy; } return \@got; }); #------------------------------------------------------------------------------ # A163333 -- Peano N <-> Z-Order radix=3, with digit swaps MyOEIS::compare_values (anum => 'A163333', func => sub { my ($count) = @_; my $zorder = Math::PlanePath::ZOrderCurve->new (radix => 3); my @got; for (my $n = $zorder->n_start; @got < $count; $n++) { my $nn = $n; { my ($x,$y) = $zorder->n_to_xy ($nn); ($x,$y) = ($y,$x); $nn = $zorder->xy_to_n ($x,$y); } { my ($x,$y) = $zorder->n_to_xy ($nn); $nn = $peano->xy_to_n ($x, $y); } { my ($x,$y) = $zorder->n_to_xy ($nn); ($x,$y) = ($y,$x); $nn = $zorder->xy_to_n ($x,$y); } push @got, $nn; } return \@got; }); MyOEIS::compare_values (anum => q{A163333}, func => sub { my ($count) = @_; my $zorder = Math::PlanePath::ZOrderCurve->new (radix => 3); my @got; for (my $n = 0; @got < $count; $n++) { my $nn = $n; { my ($x,$y) = $zorder->n_to_xy ($nn); ($x,$y) = ($y,$x); $nn = $zorder->xy_to_n ($x,$y); } { my ($x,$y) = $peano->n_to_xy ($nn); # other way around $nn = $zorder->xy_to_n ($x, $y); } { my ($x,$y) = $zorder->n_to_xy ($nn); ($x,$y) = ($y,$x); $nn = $zorder->xy_to_n ($x,$y); } push @got, $nn; } return \@got; }); #------------------------------------------------------------------------------ # A163480 - N on X axis (in Math::NumSeq::PlanePathN) # A163481 - N on Y axis (in Math::NumSeq::PlanePathN) # Peano coordinates A163528, A163529 # Z-order coordinates A163325, A163326 # GP-DEFINE \\ my code in A163325 # GP-DEFINE ZorderX(n) = fromdigits(digits(n,9)%3, 3); # GP-DEFINE \\ my code in A163326 # GP-DEFINE ZorderY(n) = fromdigits(digits(n,9)\3, 3); # # GP-DEFINE ZorderXYtoN(x,y) = { # GP-DEFINE x=digits(x,3); # GP-DEFINE y=digits(y,3); # GP-DEFINE if(#x<#y, x=concat(vector(#y-#x),x)); # GP-DEFINE if(#y<#x, y=concat(vector(#x-#y),y)); # GP-DEFINE fromdigits(x+3*y,9); # GP-DEFINE } # GP-Test vector(9^5,n,n--; ZorderXYtoN(ZorderX(n),ZorderY(n))) == \ # GP-Test vector(9^5,n,n--; n) # # GP-DEFINE \\ ternary odd positions are 0, so base 9 digits 0,1,2 only # GP-DEFINE A037314(n) = fromdigits(digits(n,3),9); # GP-Test my(v=OEIS_samples("A037314")); /* OFFSET=0 */ \ # GP-Test vector(#v,n,n--; A037314(n)) == v # GP-Test vector(3^5,n,n--; A037314(n)) == \ # GP-Test vector(3^5,n,n--; ZorderXYtoN(n,0)) # # GP-DEFINE A208665(n) = 3*fromdigits(digits(n,3),9); # GP-Test my(v=OEIS_samples("A208665")); /* OFFSET=1 */ \ # GP-Test vector(#v,n, A208665(n)) == v # GP-Test vector(3^5,n,n--; A208665(n)) == \ # GP-Test vector(3^5,n,n--; ZorderXYtoN(0,n)) # GP-DEFINE \\ Peano X -> N, on X axis # GP-DEFINE A163480(n) = A163332(A037314(n)); # GP-Test my(v=OEIS_samples("A163480")); /* OFFSET=0 */ \ # GP-Test vector(#v,n,n--; A163480(n)) == v # # GP-DEFINE A163480_compact(n) = { # GP-DEFINE my(v=digits(n,3),s=Mod(0,2)); # GP-DEFINE for(i=1,#v, if(s,v[i]+=6); s+=v[i]); # GP-DEFINE fromdigits(v,9); # GP-DEFINE } # GP-Test to_ternary(A163480(3)) == 120 # GP-Test to_ternary(A163480_compact(3)) == 120 # GP-Test vector(3^6,n,n--; A163480(n)) == \ # GP-Test vector(3^6,n,n--; A163480_compact(n)) # GP-DEFINE \\ Peano Y -> N, points on Y axis # GP-DEFINE A163481(n) = A163332(A208665(n)); # GP-Test my(v=OEIS_samples("A163481")); /* OFFSET=0 */ \ # GP-Test vector(#v,n,n--; A163481(n)) == v # # GP-Test /* Y axis from X axis */ \ # GP-Test vector(3^6,n,n--; A163481(n)) == \ # GP-Test vector(3^6,n,n--; 3*A163480(n) + if(n%2,2)) # # GP-DEFINE A163481_compact(n) = { # GP-DEFINE my(v=digits(n,3),s=Mod(0,2)); # GP-DEFINE for(i=1,#v, s+=v[i]; v[i]=3*v[i]+if(s,2)); # GP-DEFINE fromdigits(v,9); # GP-DEFINE } # GP-Test vector(3^6,n,n--; A163481(n)) == \ # GP-Test vector(3^6,n,n--; A163481_compact(n)) #------------------------------------------------------------------------------ # A163343 - N on diagonal (in Math::NumSeq::PlanePathN) # = 4*A163344 # GP-DEFINE \\ ternary reflected Gray code # GP-DEFINE Gray3(n) = { # GP-DEFINE my(v=digits(n,3),s=Mod(0,2)); # GP-DEFINE for(i=1,#v, if(s,v[i]=2-v[i]); s+=v[i]); # GP-DEFINE fromdigits(v,3); # GP-DEFINE } # GP-DEFINE A128173(n) = Gray3(n); # GP-Test my(v=OEIS_samples("A128173")); /* OFFSET=0 */ \ # GP-Test vector(#v,n,n--; A128173(n)) == v # my(g=OEIS_bfile_gf("A128173")); \ # g==Polrev(vector(poldegree(g)+1,n,n--;A128173(n))) # poldegree(OEIS_bfile_gf("A128173")) # # GP-DEFINE \\ 00 11 22 duplicate ternary digits # GP-DEFINE Dup3(n) = fromdigits(digits(n,3),9)<<2; # GP-DEFINE A338086(n) = Dup3(n); # GP-Test my(v=OEIS_samples("A338086")); /* OFFSET=1 */ \ # GP-Test vector(#v,n,n--; A338086(n)) == v # my(g=OEIS_bfile_gf("A338086")); \ # g==Polrev(vector(poldegree(g)+1,n,n--;A338086(n))) # poldegree(OEIS_bfile_gf("A338086")) # GP-DEFINE \\ Peano d -> N, on diagonal # GP-DEFINE A163343(n) = A163332(Dup3(n)); # GP-Test my(v=OEIS_samples("A163343")); /* OFFSET=0 */ \ # GP-Test vector(#v,n,n--; A163343(n)) == v # my(g=OEIS_bfile_gf("A163343")); \ # g==Polrev(vector(poldegree(g)+1,n,n--;A163343(n))) # poldegree(OEIS_bfile_gf("A163343")) # # GP-Test /* diagonal by Z-order diagonal converted */ \ # GP-Test vector(3^6,n,n--; A163343(n)) == \ # GP-Test vector(3^6,n,n--; A163332(A338086(n))) # # GP-Test /* diagonal by ternary reflected Gray then 3 -> 9 */ \ # GP-Test vector(3^6,n,n--; A163343(n)) == \ # GP-Test vector(3^6,n,n--; A338086(A128173(n))) # # GP-DEFINE A163343_compact(n) = { # GP-DEFINE my(v=digits(n,3),s=Mod(0,2)); # GP-DEFINE for(i=1,#v, if(s,v[i]=2-v[i]); s+=v[i]); # GP-DEFINE fromdigits(v,9)<<2; # GP-DEFINE } # GP-Test vector(3^6,n,n--; A163343(n)) == \ # GP-Test vector(3^6,n,n--; A163343_compact(n)) #------------------------------------------------------------------------------ # A163344 -- N/4 on X=Y diagonal MyOEIS::compare_values (anum => 'A163344', func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x++) { push @got, int($peano->xy_to_n($x,$x) / 4); } return \@got; }); # GP-DEFINE A163344(n) = A163343(n)/4; # GP-Test my(v=OEIS_samples("A163344")); /* OFFSET=1 */ \ # GP-Test vector(#v,n,n--; A163344(n)) == v # my(g=OEIS_bfile_gf("A163344")); \ # g==Polrev(vector(poldegree(g)+1,n,n--;A163344(n))) # poldegree(OEIS_bfile_gf("A163344")) #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/HilbertCurve-oeis.t0000644000175000017500000007631613776176363017060 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min', 'max'; use Test; plan tests => 48; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::HilbertCurve; use Math::PlanePath::Diagonals; use Math::PlanePath::ZOrderCurve; use Math::PlanePath::Base::Digits 'bit_split_lowtohigh'; use Math::NumSeq::PlanePathDelta; my $hilbert = Math::PlanePath::HilbertCurve->new; my $zorder = Math::PlanePath::ZOrderCurve->new; #------------------------------------------------------------------------------ sub zorder_perm { my ($n) = @_; my ($x, $y) = $zorder->n_to_xy ($n); return $hilbert->xy_to_n ($x, $y); } sub zorder_perm_inverse { my ($n) = @_; my ($x, $y) = $hilbert->n_to_xy ($n); return $zorder->xy_to_n ($x, $y); } sub zorder_perm_rep { my ($n, $reps) = @_; foreach (1 .. $reps) { $n = zorder_perm($n); } return $n; } sub zorder_cycle_length { my ($n) = @_; my $count = 1; my $p = $n; for (;;) { $p = zorder_perm($p); if ($p == $n) { last; } $count++; } return $count; } sub zorder_is_2cycle { my ($n) = @_; my $p1 = zorder_perm($n); if ($p1 == $n) { return 0; } my $p2 = zorder_perm($p1); return ($p2 == $n); } sub zorder_is_3cycle { my ($n) = @_; my $p1 = zorder_perm($n); if ($p1 == $n) { return 0; } my $p2 = zorder_perm($p1); if ($p2 == $n) { return 0; } my $p3 = zorder_perm($p2); return ($p3 == $n); } #------------------------------------------------------------------------------ # A166041 - N in Peano order MyOEIS::compare_values (anum => 'A166041', func => sub { my ($count) = @_; require Math::PlanePath::PeanoCurve; my $peano = Math::PlanePath::PeanoCurve->new; my @got; for (my $n = $peano->n_start; @got < $count; $n++) { my ($x, $y) = $peano->n_to_xy($n); push @got, $hilbert->xy_to_n ($x, $y); } return \@got; }); # inverse Peano in Hilbert order MyOEIS::compare_values (anum => 'A166042', func => sub { my ($count) = @_; require Math::PlanePath::PeanoCurve; my $peano = Math::PlanePath::PeanoCurve->new; my @got; for (my $n = $hilbert->n_start; @got < $count; $n++) { my ($x, $y) = $hilbert->n_to_xy($n); push @got, $peano->xy_to_n ($x, $y); } return \@got; }); # A166043 - N in Peano order, transpose MyOEIS::compare_values (anum => 'A166043', func => sub { my ($count) = @_; require Math::PlanePath::PeanoCurve; my $peano = Math::PlanePath::PeanoCurve->new; my @got; for (my $n = $peano->n_start; @got < $count; $n++) { my ($x, $y) = $peano->n_to_xy($n); ($x,$y) = ($y,$x); push @got, $hilbert->xy_to_n ($x, $y); } return \@got; }); # inverse Peano in Hilbert order, transpose MyOEIS::compare_values (anum => 'A166044', func => sub { my ($count) = @_; require Math::PlanePath::PeanoCurve; my $peano = Math::PlanePath::PeanoCurve->new; my @got; for (my $n = $hilbert->n_start; @got < $count; $n++) { my ($x, $y) = $hilbert->n_to_xy($n); ($x,$y) = ($y,$x); push @got, $peano->xy_to_n ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ # A059253 - X coordinate (PlanePathCoord catalogue) # Gosper HAKMEM 115. # GP-DEFINE table = {[[[0,0], [0,0], [0,0], [1,0]], # GP-DEFINE [[0,0], [0,1], [0,1], [0,0]], # GP-DEFINE [[0,0], [1,0], [1,1], [0,0]], # GP-DEFINE [[0,0], [1,1], [1,0], [0,1]], # GP-DEFINE # GP-DEFINE [[0,1], [0,0], [1,1], [1,1]], # GP-DEFINE [[0,1], [0,1], [0,1], [0,1]], # GP-DEFINE [[0,1], [1,0], [0,0], [0,1]], # GP-DEFINE [[0,1], [1,1], [1,0], [0,0]], # GP-DEFINE # GP-DEFINE [[1,0], [0,0], [0,0], [0,0]], # GP-DEFINE [[1,0], [0,1], [1,0], [1,0]], # GP-DEFINE [[1,0], [1,0], [1,1], [1,0]], # GP-DEFINE [[1,0], [1,1], [0,1], [1,1]], # GP-DEFINE # GP-DEFINE [[1,1], [0,0], [1,1], [0,1]], # GP-DEFINE [[1,1], [0,1], [1,0], [1,1]], # GP-DEFINE [[1,1], [1,0], [0,0], [1,1]], # GP-DEFINE [[1,1], [1,1], [0,1], [1,0]]]}; # GP-DEFINE table_lookup(cc,ab) = { # GP-DEFINE for(i=1,#table, # GP-DEFINE if(table[i][1]==cc && table[i][2]==ab, return(table[i]))); # GP-DEFINE error(); # GP-DEFINE } # GP-DEFINE Hilbert_xy(n) = { # GP-DEFINE my(cc=[0,0], v=digits(n,4)); # GP-DEFINE if(#v%2==1,v=concat(0,v)); # GP-DEFINE my(x=vector(#v), y=vector(#v)); # GP-DEFINE for(i=1,#v, # GP-DEFINE my(ab=[bittest(v[i],1),bittest(v[i],0)], # GP-DEFINE row=table_lookup(cc,ab)); # GP-DEFINE cc=row[4]; # GP-DEFINE x[i]=row[3][1]; # GP-DEFINE y[i]=row[3][2]); # GP-DEFINE [fromdigits(x,2), fromdigits(y,2)]; # GP-DEFINE } # vector(20,n, Hilbert_xy(n)[1]) # my(g=OEIS_bfile_gf("A059253")); \ # g==Polrev(vector(poldegree(g)+1,n,n--; Hilbert_xy(n)[1])) # poldegree(OEIS_bfile_gf("A059253")) # my(g=OEIS_bfile_gf("A059252")); \ # g==Polrev(vector(poldegree(g)+1,n,n--; Hilbert_xy(n)[2])) # poldegree(OEIS_bfile_gf("A059252")) # my(g=OEIS_bfile_gf("A059253")); x(n) = polcoeff(g,n); # my(g=OEIS_bfile_gf("A059252")); y(n) = polcoeff(g,n); # plothraw(vector(4^3+6,n,n--; Hilbert_xy(n)[1]), \ # vector(4^3+6,n,n--; Hilbert_xy(n)[2]), 1+8+16+32) # GP-DEFINE Hilbert_S(n) = { # GP-DEFINE my(c=[0,0], v=digits(n,4)); # GP-DEFINE if(#v%2==1,v=concat(0,v)); # GP-DEFINE my(x=vector(#v), y=vector(#v)); # GP-DEFINE for(i=1,#v, # GP-DEFINE my(a=bittest(v[i],1), b=bittest(v[i],0)); # GP-DEFINE x[i] = bitxor(a, c[(!b) + 1]); # GP-DEFINE y[i] = bitxor(x[i], b); # GP-DEFINE c = [ bitxor(c[1], (!a) && (!b)), # GP-DEFINE bitxor(c[2], a && b) ]); # GP-DEFINE [fromdigits(x,2), fromdigits(y,2)]; # GP-DEFINE } # GP-Test vector(4^7,n,n--; Hilbert_xy(n)) == \ # GP-Test vector(4^7,n,n--; Hilbert_S(n)) # my(g=OEIS_bfile_gf("A059253")); \ # g==Polrev(vector(poldegree(g)+1,n,n--; Hilbert_S(n)[1])) # poldegree(OEIS_bfile_gf("A059253")) # my(g=OEIS_bfile_gf("A059252")); \ # g==Polrev(vector(poldegree(g)+1,n,n--; Hilbert_S(n)[2])) # poldegree(OEIS_bfile_gf("A059252")) #--------- # GP-DEFINE Hilbert_dxdy(n) = Hilbert_xy(n+1) - Hilbert_xy(n); # # but A163538, A163539 start with a 0,0 # my(g=OEIS_bfile_gf("A163538")); \ # g==x*Polrev(vector(poldegree(g)+1,n,n--; Hilbert_dxdy(n)[1])) # poldegree(OEIS_bfile_gf("A163538")) # # my(g=OEIS_bfile_gf("A163539")); \ # g==x*Polrev(vector(poldegree(g)+1,n,n--; Hilbert_dxdy(n)[2])) # poldegree(OEIS_bfile_gf("A163539")) #--------- # dSum, dDiff # vector(50,n, vecsum(Hilbert_dxdy(n))) # not in OEIS: 1,-1,1,1,1,-1,1,1,1,-1,-1,-1,-1,1,1,1,1,-1,1,1,1,-1,1,1,1,-1,-1 # not A011601 Legendre(n,89) middle match # my(v=OEIS_samples("A011601")); v[1]==0 || error(); v=v[^1]; \ # v - vector(#v,n, vecsum(Hilbert_dxdy(n))) # vector(30,n, my(v=Hilbert_dxdy(n)); v[2]-v[1]) # not in OEIS: 1,-1,1,1,1,-1,1,1,1,-1,-1,-1,-1,1,1,1,1,-1,1,1,1,-1,1,1,1,-1,-1 # not A011601 Legendre(n,89) middle match #--------- # dir # # GP-DEFINE \\ Jorg's fxtbook hilbert_dir() form 0123 = ESNW # GP-DEFINE Hilbert_dir_0231(n) = { # GP-DEFINE my(d=Hilbert_dxdy(n+1)); # GP-DEFINE if(d[1]==0, if(d[2]==1, 2, \\ up # GP-DEFINE d[2]==-1,1, \\ down # GP-DEFINE error()), # GP-DEFINE d[2]==0, if(d[1]==1, 0, \\ right # GP-DEFINE d[1]==-1,3, \\ left # GP-DEFINE error()), # GP-DEFINE error()); # GP-DEFINE } # vector(20,n, Hilbert_dir_0231(n)) # not in OEIS: 3, 2, 2, 0, 1, 0, 2, 0, 1, 1, 3, 1, 0, 0, 2, 0, 1, 0, 0, 2 #------------------------------------------------------------------------------ # A083885 etc counts of segments in direction # dir=1 # not in OEIS: 2,4,20,64,272,1024,4160 # dir=2 # not in OEIS: 1,2,16,56,256,992,4096 # dir=3 # not in OEIS: 4,12,64,240,1024,4032 foreach my $elem ([0, 'A083885', 0], # [1, 'A000001', 0], # [2, 'A000001', 0], # [3, 'A000001', 0], ) { my ($want_dir4, $anum, $initial_k) = @$elem; MyOEIS::compare_values (anum => $anum, max_count => 8, name => "dir=$want_dir4", func => sub { my ($count) = @_; my @got; my $seq = Math::NumSeq::PlanePathDelta->new(planepath_object=>$hilbert, delta_type => 'Dir4'); my $n_target = 1; my $total = 0; while (@got < $count) { my ($n, $value) = $seq->next; if ($n == $n_target) { push @got, $total; $n_target *= 4; } $total += ($value == $want_dir4); } return \@got; }); } #------------------------------------------------------------------------------ # A147600 - num fixed points in 4^k blocks MyOEIS::compare_values (anum => q{A147600}, # not shown in POD max_count => 8, func => sub { my ($bvalues_count) = @_; my @got; my $target = 4; my $count = 0; for (my $n = 1; @got < $bvalues_count; $n++) { if ($n >= $target) { push @got, $count; $count = 0; $target *= 4; } if ($n == zorder_perm($n)) { $count++; } } return \@got; }); #------------------------------------------------------------------------------ # A163894 - first i for which (perm^n)[i] != i MyOEIS::compare_values (anum => 'A163894', max_count => 100, func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { push @got, A163894_perm_n_not($n); } return \@got; }); sub A163894_perm_n_not { my ($n) = @_; if ($n == 0) { return 0; } for (my $i = 0; ; $i++) { my $p = zorder_perm_rep ($i, $n); if ($p != $i) { return $i; } } } #------------------------------------------------------------------------------ # A163895 - position where A163894 is a new high MyOEIS::compare_values (anum => 'A163895', max_count => 8, func => sub { my ($count) = @_; my @got; my $high = -1; for (my $n = 0; @got < $count; $n++) { my $value = A163894_perm_n_not($n); if ($value > $high) { $high = $value; push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A139351 - HammingDist(X,Y) = count 1-bits at even bit positions in N MyOEIS::compare_values (name => 'HammingDist(X,Y)', anum => 'A139351', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x, $y) = $hilbert->n_to_xy($n); push @got, HammingDist($x,$y); } return \@got; }); MyOEIS::compare_values (name => 'count 1-bits at even bit positions', anum => qq{A139351}, func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my @nbits = bit_split_lowtohigh($n); my $total = 0; for (my $i = 0; $i <= $#nbits; $i+=2) { $total += $nbits[$i]; } push @got, $total; } return \@got; }); sub HammingDist { my ($x,$y) = @_; my @xbits = bit_split_lowtohigh($x); my @ybits = bit_split_lowtohigh($y); my $ret = 0; while (@xbits || @ybits) { $ret += (shift @xbits ? 1 : 0) ^ (shift @ybits ? 1 : 0); } return $ret; } #------------------------------------------------------------------------------ # A163893 - first diffs of positions where cycle length some new unseen value MyOEIS::compare_values (anum => 'A163893', name => 'cycle length by N', max_count => 20, func => sub { my ($count) = @_; my @got; my %seen = (1 => 1); my $prev = 0; for (my $n = 0; @got < $count; $n++) { my $len = zorder_cycle_length($n); if (! $seen{$len}) { push @got, $n-$prev; $prev = $n; $seen{$len} = 1; } } return \@got; }); #------------------------------------------------------------------------------ # A163896 - value where A163894 is a new high MyOEIS::compare_values (anum => 'A163896', max_count => 8, func => sub { my ($count) = @_; my @got; my $high = -1; for (my $n = 0; @got < $count; $n++) { my $value = A163894_perm_n_not($n); if ($value > $high) { $high = $value; push @got, $value; } } return \@got; }); #------------------------------------------------------------------------------ # A163900 - squared distance between Hilbert and Z order MyOEIS::compare_values (name => 'squared distance between Hilbert and ZOrder', anum => 'A163900', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($hx, $hy) = $hilbert->n_to_xy ($n); my ($zx, $zy) = $zorder->n_to_xy ($n); my $dx = $hx - $zx; my $dy = $hy - $zy; push @got, $dx**2 + $dy**2; } return \@got; }); #------------------------------------------------------------------------------ # A163891 - positions where cycle length some new previously unseen value # # len: 1, 1, 2, 2, 6, 3, 3, 6, 6, 6, 3, 3, 6, 3, 6, 3, 1, 3, 3, 3, 1, 1, 2, 2, # ^ # 91: 0 2 4 5 MyOEIS::compare_values (name => "cycle length by N", anum => 'A163891', max_count => 20, func => sub { my ($count) = @_; my @got; my %seen; for (my $n = 0; @got < $count; $n++) { my $len = zorder_cycle_length($n); if (! $seen{$len}) { push @got, $n; $seen{$len} = 1; } } return \@got; }); #------------------------------------------------------------------------------ # A165466 -- dx^2+dy^2 of Hilbert->Peano transposed MyOEIS::compare_values (anum => 'A165466', func => sub { my ($count) = @_; require Math::PlanePath::PeanoCurve; my $peano = Math::PlanePath::PeanoCurve->new; my @got; for (my $n = $hilbert->n_start; @got < $count; $n++) { my ($hx,$hy) = $hilbert->n_to_xy($n); my ($px,$py) = $peano->n_to_xy($n); ($px,$py) = ($py,$px); push @got, ($px-$hx)**2 + ($py-$hy)**2; } return \@got; }); # A165464 -- dx^2+dy^2 of Hilbert->Peano MyOEIS::compare_values (anum => 'A165464', func => sub { my ($count) = @_; require Math::PlanePath::PeanoCurve; my $peano = Math::PlanePath::PeanoCurve->new; my @got; for (my $n = $hilbert->n_start; @got < $count; $n++) { my ($hx,$hy) = $hilbert->n_to_xy($n); my ($px,$py) = $peano->n_to_xy($n); push @got, ($px-$hx)**2 + ($py-$hy)**2; } return \@got; }); #------------------------------------------------------------------------------ # A163538 -- dX # extra first entry for N=0 no change MyOEIS::compare_values (anum => 'A163538', func => sub { my ($count) = @_; my @got = (0); for (my $n = $hilbert->n_start; @got < $count; $n++) { my ($dx, $dy) = $hilbert->n_to_dxdy ($n); push @got, $dx; } return \@got; }); #------------------------------------------------------------------------------ # A163539 -- dY # extra first entry for N=0 no change MyOEIS::compare_values (anum => 'A163539', func => sub { my ($count) = @_; my @got = (0); for (my $n = $hilbert->n_start; @got < $count; $n++) { my ($dx, $dy) = $hilbert->n_to_dxdy ($n); push @got, $dy; } return \@got; }); #------------------------------------------------------------------------------ # A165467 -- N where Hilbert and Peano same X,Y MyOEIS::compare_values (anum => 'A165467', max_value => 100000, func => sub { my ($count) = @_; require Math::PlanePath::PeanoCurve; my $peano = Math::PlanePath::PeanoCurve->new; my @got; for (my $n = $hilbert->n_start; @got < $count; $n++) { my ($hx,$hy) = $hilbert->n_to_xy($n); my ($px,$py) = $peano->n_to_xy($n); if ($hx == $py && $hy == $px) { push @got, $n; } } return \@got; }); # A165465 -- N where Hilbert and Peano same X,Y MyOEIS::compare_values (anum => 'A165465', max_value => 100000, func => sub { my ($count) = @_; require Math::PlanePath::PeanoCurve; my $peano = Math::PlanePath::PeanoCurve->new; my @got; for (my $n = $hilbert->n_start; @got < $count; $n++) { my ($hx,$hy) = $hilbert->n_to_xy($n); my ($px,$py) = $peano->n_to_xy($n); if ($hx == $px && $hy == $py) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A163540 -- absolute direction (0=E,1=N,2=W,3=S) # sequence description is for Y coordinates reckoned down the page, # so Y increasing is described there as "South", whereas North here. MyOEIS::compare_values (anum => 'A163540', func => sub { my ($count) = @_; my @got; for (my $n = $hilbert->n_start; @got < $count; $n++) { my ($dx, $dy) = $hilbert->n_to_dxdy ($n); push @got, MyOEIS::dxdy_to_direction ($dx, $dy); } return \@got; }); # A163541 -- absolute direction transpose 0=east, 1=south, 2=west, 3=north # 1 / # | / transpose 0<->1 # 2---+---0 2<->3 # / | # / 3 my @dir4_transpose = (1,0, 3,2); MyOEIS::compare_values (anum => 'A163541', name => 'absolute direction transpose', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathDelta->new(planepath_object=>$hilbert, delta_type => 'Dir4'); my @got; while (@got < $count) { my ($n, $value) = $seq->next; push @got, $dir4_transpose[$value]; } return \@got; }); # Cf Joerg fxtbook section 1.31.1 hilbert_dir() directions 0..3 = >v^< # 0,2,3,2,2,0,1,0,2,0,1,1,3,1,0,0,2,0,1,0,0,2,3,2,0,2,3,3,1,3,2,2, # cf ENWS 0,1,2,1,1,0,3,0,1,0,3,3,2,3,0,0,1,0,3,0,0,1,2,1, # 2 up # | # left 3 ---*--- 0 right # | # 1 down #------------------------------------------------------------------------------ # A163909 - num 3-cycles in 4^k blocks, even k only MyOEIS::compare_values (anum => 'A163909', max_count => 5, func => sub { my ($bvalues_count) = @_; my @got; my $target = 1; my $target_even = 1; my $count = 0; my @seen; for (my $n = 0; @got < $bvalues_count; $n++) { if ($n >= $target) { if ($target_even) { push @got, $count; } $target_even ^= 1; $count = 0; $target *= 4; @seen = (); $#seen = $target; # pre-extend } unless ($seen[$n]) { my $p1 = zorder_perm($n); next if $p1 == $n; # a fixed point my $p2 = zorder_perm($p1); next if $p2 == $n; # a 2-cycle my $p3 = zorder_perm($p2); next unless $p3 == $n; # not a 3-cycle $count++; $seen[$n] = 1; $seen[$p1] = 1; $seen[$p2] = 1; } } return \@got; }); #------------------------------------------------------------------------------ # A163914 - num 3-cycles in 4^k blocks MyOEIS::compare_values (anum => 'A163914', max_count => 8, func => sub { my ($bvalues_count) = @_; my @got; my $target = 1; my $count = 0; my @seen; for (my $n = 0; @got < $bvalues_count; $n++) { if ($n >= $target) { push @got, $count; $count = 0; $target *= 4; @seen = (); $#seen = $target; # pre-extend } unless ($seen[$n]) { my $p1 = zorder_perm($n); next if $p1 == $n; # a fixed point my $p2 = zorder_perm($p1); next if $p2 == $n; # a 2-cycle my $p3 = zorder_perm($p2); next unless $p3 == $n; # not a 3-cycle $count++; $seen[$n] = 1; $seen[$p1] = 1; $seen[$p2] = 1; } } return \@got; }); #------------------------------------------------------------------------------ # A163908 - perm twice, by diagonals, inverse MyOEIS::compare_values (anum => 'A163908', func => sub { my ($count) = @_; my @got; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up'); # from same axis as Hilbert for (my $n = 0; @got < $count; $n++) { my $nn = zorder_perm_inverse(zorder_perm_inverse($n)); my ($x, $y) = $zorder->n_to_xy ($nn); my $dn = $diagonal->xy_to_n ($x, $y); push @got, $dn-1; } return \@got; }); #------------------------------------------------------------------------------ # A163907 - perm twice, by diagonals MyOEIS::compare_values (anum => 'A163907', func => sub { my ($count) = @_; my @got; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up'); # from same axis as Hilbert for (my $dn = $diagonal->n_start; @got < $count; $dn++) { my ($x, $y) = $diagonal->n_to_xy ($dn); my $n = $zorder->xy_to_n ($x, $y); push @got, zorder_perm(zorder_perm($n)); } return \@got; }); #------------------------------------------------------------------------------ # A163904 - cycle length by diagonals MyOEIS::compare_values (anum => 'A163904', func => sub { my ($count) = @_; my @got; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up'); # from same axis as Hilbert for (my $dn = $diagonal->n_start; @got < $count; $dn++) { my ($x, $y) = $diagonal->n_to_xy ($dn); my $hn = $hilbert->xy_to_n ($x, $y); push @got, zorder_cycle_length($hn); } return \@got; }); #------------------------------------------------------------------------------ # A163890 - cycle length by N MyOEIS::compare_values (anum => 'A163890', max_count => 10000, func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { push @got, zorder_cycle_length($n); } return \@got; }); #------------------------------------------------------------------------------ # A163912 - LCM of cycle lengths in 4^k blocks MyOEIS::compare_values (anum => 'A163912', max_count => 6, func => sub { my ($count) = @_; my @got; my $target = 1; my $max = 0; my %lengths; for (my $n = 0; @got < $count; $n++) { if ($n >= $target) { push @got, lcm(keys %lengths); $target *= 4; %lengths = (); } $lengths{zorder_cycle_length($n)} = 1; } return \@got; }); use Math::PlanePath::GcdRationals; sub lcm { my $lcm = 1; foreach my $n (@_) { my $gcd = Math::PlanePath::GcdRationals::_gcd($lcm,$n); $lcm = $lcm * $n / $gcd; } return $lcm; } #------------------------------------------------------------------------------ # A163911 - max cycle in 4^k blocks MyOEIS::compare_values (anum => 'A163911', max_count => 7, func => sub { my ($count) = @_; my @got; my $target = 1; my $max = 0; for (my $n = 0; @got < $count; $n++) { if ($n >= $target) { push @got, $max; $max = 0; $target *= 4; } $max = max ($max, zorder_cycle_length($n)); } return \@got; }); #------------------------------------------------------------------------------ # A163910 - num cycles in 4^k blocks MyOEIS::compare_values (anum => 'A163910', max_count => 9, func => sub { my ($bvalues_count) = @_; my @got; my $target = 1; my $count = 0; my @seen; for (my $n = 0; @got < $bvalues_count; $n++) { if ($n >= $target) { push @got, $count; $count = 0; $target *= 4; @seen = (); $#seen = $target; # pre-extend } $count++; my $p = $n; for (;;) { $p = zorder_perm($p); if ($seen[$p]) { $count--; last; } $seen[$p] = 1; last if $p == $n; } $seen[$n] = 1; } return \@got; }); #------------------------------------------------------------------------------ # A163355 - in Z order sequence MyOEIS::compare_values (anum => 'A163355', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { push @got, zorder_perm($n); } return \@got; }); # A163356 - inverse MyOEIS::compare_values (anum => 'A163356', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x, $y) = $hilbert->n_to_xy ($n); push @got, $zorder->xy_to_n ($x, $y); } return \@got; }); # A163905 - applied twice MyOEIS::compare_values (anum => 'A163905', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { push @got, zorder_perm(zorder_perm($n)); } return \@got; }); # A163915 - applied three times # A163905 - applied twice MyOEIS::compare_values (anum => 'A163915', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { push @got, zorder_perm(zorder_perm(zorder_perm($n))); } return \@got; }); # A163901 - fixed-point N values MyOEIS::compare_values (anum => 'A163901', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { if (zorder_perm($n) == $n) { push @got, $n; } } return \@got; }); # A163902 - 2-cycle N values MyOEIS::compare_values (anum => 'A163902', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { if (zorder_is_2cycle($n)) { push @got, $n; } } return \@got; }); # A163903 - 3-cycle N values MyOEIS::compare_values (anum => 'A163903', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { if (zorder_is_3cycle($n)) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A163357 - in diagonal sequence MyOEIS::compare_values (anum => 'A163357', func => sub { my ($count) = @_; my @got; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'down', n_start => 0); for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($y, $x) = $diagonal->n_to_xy ($n); push @got, $hilbert->xy_to_n ($x, $y); } return \@got; }); # A163358 - inverse MyOEIS::compare_values (anum => 'A163358', func => sub { my ($count) = @_; my @got; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'down', n_start => 0); for (my $n = $hilbert->n_start; @got < $count; $n++) { my ($y, $x) = $hilbert->n_to_xy ($n); push @got, $diagonal->xy_to_n ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ # A163359 - in diagonal sequence, opp sides MyOEIS::compare_values (anum => 'A163359', func => sub { my ($count) = @_; my @got; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'down'); # from opposite side for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal->n_to_xy ($n); push @got, $hilbert->xy_to_n ($x, $y); } return \@got; }); # A163360 - inverse MyOEIS::compare_values (anum => 'A163360', func => sub { my ($count) = @_; my @got; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'down', n_start => 0); for (my $n = $hilbert->n_start; @got < $count; $n++) { my ($x, $y) = $hilbert->n_to_xy ($n); push @got, $diagonal->xy_to_n ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ # A163361 - diagonal sequence, one based, same side MyOEIS::compare_values (anum => 'A163361', func => sub { my ($count) = @_; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up'); my @got; for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal->n_to_xy ($n); push @got, $hilbert->xy_to_n ($x, $y) + 1; # 1-based Hilbert } return \@got; }); # A163362 - inverse MyOEIS::compare_values (anum => 'A163362', func => sub { my ($count) = @_; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up'); my @got; for (my $n = $hilbert->n_start; @got < $count; $n++) { my ($x, $y) = $hilbert->n_to_xy ($n); push @got, $diagonal->xy_to_n ($x, $y); # 1-based Hilbert } return \@got; }); #------------------------------------------------------------------------------ # A163363 - diagonal sequence, one based, opp sides MyOEIS::compare_values (anum => 'A163363', func => sub { my ($count) = @_; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'down'); my @got; for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal->n_to_xy ($n); push @got, $hilbert->xy_to_n ($x, $y) + 1; } return \@got; }); # A163364 - inverse MyOEIS::compare_values (anum => 'A163364', func => sub { my ($count) = @_; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'down'); my @got; for (my $n = $hilbert->n_start; @got < $count; $n++) { my ($x, $y) = $hilbert->n_to_xy ($n); push @got, $diagonal->xy_to_n ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ # A163365 - diagonal sums MyOEIS::compare_values (anum => 'A163365', func => sub { my ($count) = @_; my @got; for (my $d = 0; @got < $count; $d++) { my $sum = 0; foreach my $x (0 .. $d) { my $y = $d - $x; $sum += $hilbert->xy_to_n ($x, $y); } push @got, $sum; } return \@got; }); # A163477 - diagonal sums divided by 4 MyOEIS::compare_values (anum => 'A163477', func => sub { my ($count) = @_; my @got; for (my $d = 0; @got < $count; $d++) { my $sum = 0; foreach my $x (0 .. $d) { my $y = $d - $x; $sum += $hilbert->xy_to_n ($x, $y); } push @got, int($sum/4); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/DragonMidpoint-oeis.t0000644000175000017500000001352513475623525017362 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; use Math::BigInt try => 'GMP'; plan tests => 27; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::DragonMidpoint; # uncomment this to run the ### lines # use Smart::Comments '###'; #------------------------------------------------------------------------------ # A090678 0=straight, 1=not straight, except A090678 has extra initial 1,1 MyOEIS::compare_values (anum => 'A090678', func => sub { my ($count) = @_; my @got = (1,1); require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath=>'DragonMidpoint', turn_type => 'NotStraight'); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A203175 figure boundary length to N=2^k-1 MyOEIS::compare_values (anum => 'A203175', name => 'boundary length', max_value => 10_000, func => sub { my ($count) = @_; my $path = Math::PlanePath::DragonMidpoint->new; my @got = (1,1,2); for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_n_to_figure_boundary($path, 2**$k-1); } return \@got; }); #------------------------------------------------------------------------------ # A077860 -- Y at N=2^k, starting k=1 N=2 # Re -(i+1)^k + i-1 { require Math::Complex; my $path = Math::PlanePath::DragonMidpoint->new; my $b = Math::Complex->make(1,1); foreach my $k (1 .. 10) { my $n = 2**$k; my ($x,$y) = $path->n_to_xy($n); my $c = $b; foreach (1 .. $k) { $c *= $b; } $c *= Math::Complex->make(0,-1); $c += Math::Complex->make(-1,1); ok ($c->Re, $x); ok ($c->Im, $y); # print $x,","; # print $c->Re,","; # print $c->Im,","; } } MyOEIS::compare_values (anum => 'A077860', func => sub { my ($count) = @_; my $path = Math::PlanePath::DragonMidpoint->new; my @got; for (my $n = Math::BigInt->new(2); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A073089 -- abs(dY), so 1 if step vertical, 0 if horizontal # with extra leading 0 MyOEIS::compare_values (anum => 'A073089', func => sub { my ($count) = @_; my $path = Math::PlanePath::DragonMidpoint->new; my @got = (0); my ($prev_x, $prev_y) = $path->n_to_xy (0); for (my $n = $path->n_start + 1; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); if ($x == $prev_x) { push @got, 1; # vertical } else { push @got, 0; # horizontal } ($prev_x,$prev_y) = ($x,$y); } return \@got; }); # A073089_func vs b-file MyOEIS::compare_values (anum => q{A073089}, func => sub { my ($count) = @_; my @got; for (my $n = 1; @got < $count; $n++) { push @got, A073089_func($n); } return \@got; }); # A073089_func vs path { my $path = Math::PlanePath::DragonMidpoint->new; my ($prev_x, $prev_y) = $path->n_to_xy (0); my $bad = 0; foreach my $n (0 .. 0x2FFF) { my ($x, $y) = $path->n_to_xy ($n); my ($nx, $ny) = $path->n_to_xy ($n+1); my $path_value = ($x == $nx ? 1 # vertical : 0); # horizontal my $a_value = A073089_func($n+2); if ($path_value != $a_value) { MyTestHelpers::diag ("diff n=$n path=$path_value acalc=$a_value"); MyTestHelpers::diag (" xy=$x,$y nxy=$nx,$ny"); last if ++$bad > 10; } } ok ($bad, 0, "A073089_func()"); } sub A073089_func { my ($n) = @_; ### A073089_func: $n for (;;) { if ($n <= 1) { return 0; } if (($n % 4) == 2) { return 0; } if (($n % 8) == 7) { return 0; } if (($n % 16) == 13) { return 0; } if (($n % 4) == 0) { return 1; } if (($n % 8) == 3) { return 1; } if (($n % 16) == 5) { return 1; } if (($n % 8) == 1) { $n = ($n-1)/2+1; # 8n+1 -> 4n+1 next; } die "oops"; } } # absdy_bitwise() vs path { my $path = Math::PlanePath::DragonMidpoint->new; my ($prev_x, $prev_y) = $path->n_to_xy (0); my $bad = 0; foreach my $n (0 .. 0x2FFF) { my ($x, $y) = $path->n_to_xy ($n); my ($nx, $ny) = $path->n_to_xy ($n+1); my $path_value = ($x == $nx ? 1 # vertical : 0); # horizontal my $a_value = absdy_bitwise($n); if ($path_value != $a_value) { MyTestHelpers::diag ("diff n=$n path=$path_value acalc=$a_value"); MyTestHelpers::diag (" xy=$x,$y nxy=$nx,$ny"); last if ++$bad > 10; } } ok ($bad, 0, "absdy_bitwise()"); } sub absdy_bitwise { my ($n) = @_; return ($n & 1) ^ bit_above_lowest_zero($n); } sub bit_above_lowest_zero { my ($n) = @_; for (;;) { if (($n % 2) == 0) { last; } $n = int($n/2); } $n = int($n/2); return ($n % 2); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/FractionsTree-oeis.t0000644000175000017500000001124413733351271017201 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2015, 2018, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 9; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::FractionsTree; # GP-DEFINE read("my-oeis.gp"); #------------------------------------------------------------------------------ # A093873 -- Kepler numerators # starting from 1/1 is duplicate tree halves, so repeat each row # 1 # - # 1 # # 1 1 i/(i+j) and j/(i+j) # - - # 2 2 # # 1 2 1 2 # - - - - # 3 3 3 3 # # 1 3 2 3 1 3 2 3 # - - - - - - - - # 4 4 5 5 4 4 5 5 # # 1 4 3 4 2 5 3 5 1 4 3 4 2 5 3 # - - - - - - - - - - - - - - - # 5 5 7 7 7 7 8 8 5 5 7 7 7 7 8 # 1 # / \ # 1 1 i/(i+j) and j/(i+j) # / \ / \ # 1 2 1 2 # / \ / \ / \ / \ # 1 3 2 3 1 3 2 3 # 1/1 # 1/2 1/2 # 1/3 2/3 1/3 2/3 # 1/4 3/4 2/5 3/4 1/4 3/4 2/5 3/5 # GP-DEFINE A093873_pq(n) = { # GP-DEFINE my(p=1,q=1); # GP-DEFINE forstep(i=logint(n,2)-1,0,-1, # GP-DEFINE [p,q] = [if(bittest(n,i),q,p), p+q]); # GP-DEFINE [p,q]; # GP-DEFINE } # GP-Test my(v=OEIS_samples("A093873")); /* OFFSET=1 */ \ # GP-Test vector(#v,n, A093873_pq(n)[1]) == v # GP-Test my(v=OEIS_samples("A093875")); /* OFFSET=1 */ \ # GP-Test vector(#v,n, A093873_pq(n)[2]) == v MyOEIS::compare_values (anum => 'A093873', func => sub { my ($count) = @_; my $path = Math::PlanePath::FractionsTree->new (tree_type => 'Kepler'); my @got; for (my $n = 1; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy(sans_second_highest_bit($n)); push @got, $x; } return \@got; }); # denominators MyOEIS::compare_values (anum => 'A093875', func => sub { my ($count) = @_; my $path = Math::PlanePath::FractionsTree->new (tree_type => 'Kepler'); my @got = (1); for (my $n = 2; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy(sans_second_highest_bit($n)); push @got, $y; } return \@got; }); sub sans_second_highest_bit { my ($n) = @_; my $h = high_bit($n); $h >>= 1; return $h + ($n & ($h-1)); } ok (sans_second_highest_bit(7), 3); ok (sans_second_highest_bit(9), 5); ok (sans_second_highest_bit(13), 5); MyOEIS::compare_values (anum => 'A162751', name => 'sans_second_highest_bit()', func => sub { my ($count) = @_; my @got; for (my $n = 1; @got < $count; $n++) { push @got, sans_second_highest_bit($n+1); } return \@got; }); sub high_bit { my ($n) = @_; my $bit = 1; while ($bit <= $n) { $bit <<= 1; } return $bit >> 1; } MyOEIS::compare_values (anum => 'A053644', # OFFSET=0 name => 'high_bit()', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { push @got, high_bit($n); } return \@got; }); #------------------------------------------------------------------------------ # A086593 -- Kepler half-tree denominators, every second value MyOEIS::compare_values (anum => 'A086593', name => 'Kepler half-tree denominators every second value', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::FractionsTree->new (tree_type => 'Kepler'); for (my $n = $path->n_start; @got < $count; $n += 2) { my ($x, $y) = $path->n_to_xy ($n); push @got, $y; } return \@got; }); # is also the sum X+Y, skipping initial 2 MyOEIS::compare_values (anum => q{A086593}, name => 'as sum X+Y', func => sub { my ($count) = @_; my $path = Math::PlanePath::FractionsTree->new (tree_type => 'Kepler'); my @got = (2); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $x+$y; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/CoprimeColumns-oeis.t0000644000175000017500000001451713676242164017404 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 8; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::CoprimeColumns; # uncomment this to run the ### lines # use Smart::Comments '###'; my $path = Math::PlanePath::CoprimeColumns->new; #------------------------------------------------------------------------------ # A127368 - Y coordinate of coprimes, 0 for non-coprimes MyOEIS::compare_values (anum => 'A127368', func => sub { my ($count) = @_; my @got; OUTER: for (my $x = 1; ; $x++) { foreach my $y (1 .. $x) { push @got, ($path->xy_is_visited($x,$y) ? $y : 0); last OUTER if @got >= $count; } } return \@got; }); #------------------------------------------------------------------------------ # A179594 - column of nxn unvisited block # is X here but Y in A179594 since it goes as rows of coprimes rather than # columns MyOEIS::compare_values (anum => 'A179594', max_count => 3, func => sub { my ($count) = @_; my @got; my $x = 1; for (my $size = 1; @got < $count; $size++) { for ( ; ! have_unvisited_square($x,$size); $x++) { } push @got, $x; } return \@got; }); # return true if there's a $size by $size unvisited square somewhere in # column $x sub have_unvisited_square { my ($x, $size) = @_; ### have_unvisited_square(): $x,$size my $count = 0; foreach my $y (2 .. $x) { if (have_unvisited_line($x,$y,$size)) { $count++; if ($count >= $size) { ### found at: "x=$x, y=$y count=$count" return 1; } } else { $count = 0; } } return 0; } # return true if $x,$y is the start (the leftmost point) of a $size length # line of unvisited points sub have_unvisited_line { my ($x,$y, $size) = @_; foreach my $i (0 .. $size-1) { if ($path->xy_is_visited($x,$y)) { return 0; } $x++; } return 1; } #------------------------------------------------------------------------------ # A002088 - totient sum along X axis, or diagonal of n_start=1 MyOEIS::compare_values (anum => 'A002088', func => sub { my ($count) = @_; my $path = Math::PlanePath::CoprimeColumns->new (n_start => 1); my @got = (0, 1); for (my $x = 2; @got < $count; $x++) { push @got, $path->xy_to_n($x,$x-1); } return \@got; }); MyOEIS::compare_values (anum => q{A002088}, func => sub { my ($count) = @_; my @got; for (my $x = 1; @got < $count; $x++) { push @got, $path->xy_to_n($x,1); } return \@got; }); #------------------------------------------------------------------------------ # A054428 - inverse, permutation SB N -> coprime columns N MyOEIS::compare_values (anum => 'A054428', func => sub { my ($count) = @_; my @got; require Math::PlanePath::RationalsTree; my $sb = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); for (my $n = 0; @got < $count; $n++) { my $sn = insert_second_highest_bit_one($n); my ($x,$y) = $sb->n_to_xy ($sn); ### sb: "$x/$y" my $cn = $path->xy_to_n($x,$y); if (! defined $cn) { die "oops, SB $x,$y"; } push @got, $cn+1; } return \@got; }); sub insert_second_highest_bit_one { my ($n) = @_; my $str = sprintf ('%b', $n); substr($str,1,0) = '1'; return oct("0b$str"); } # # ### assert: delete_second_highest_bit(1) == 1 # # ### assert: delete_second_highest_bit(2) == 1 # ### assert: delete_second_highest_bit(4) == 2 # ### assert: delete_second_highest_bit(5) == 3 #------------------------------------------------------------------------------ # A054427 - permutation coprime columns N -> SB N MyOEIS::compare_values (anum => 'A054427', func => sub { my ($count) = @_; my @got; require Math::PlanePath::RationalsTree; my $sb = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my $n = 0; while (@got < $count) { my ($x,$y) = $path->n_to_xy ($n++); ### frac: "$x/$y" my $sn = $sb->xy_to_n($x,$y); push @got, delete_second_highest_bit($sn) + 1; } return \@got; }); sub delete_second_highest_bit { my ($n) = @_; my $bit = 1; my $ret = 0; while ($bit <= $n) { $ret |= ($n & $bit); $bit <<= 1; } $bit >>= 1; $ret &= ~$bit; $bit >>= 1; $ret |= $bit; # ### $ret # ### $bit return $ret; } # ### assert: delete_second_highest_bit(1) == 1 # ### assert: delete_second_highest_bit(2) == 1 ### assert: delete_second_highest_bit(4) == 2 ### assert: delete_second_highest_bit(5) == 3 #------------------------------------------------------------------------------ # A121998 - list of <=k with a common factor MyOEIS::compare_values (anum => 'A121998', func => sub { my ($count) = @_; my @got; OUTER: for (my $x = 2; ; $x++) { for (my $y = 1; $y <= $x; $y++) { if (! $path->xy_is_visited($x,$y)) { push @got, $y; last OUTER unless @got < $count; } } } return \@got; }); #------------------------------------------------------------------------------ # A054521 - by columns 1 if coprimes, 0 if not MyOEIS::compare_values (anum => 'A054521', func => sub { my ($count) = @_; my @got; OUTER: for (my $x = 1; ; $x++) { foreach my $y (1 .. $x) { if ($path->xy_is_visited($x,$y)) { push @got, 1; } else { push @got, 0; } last OUTER if @got >= $count; } } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/FactorRationals-oeis.t0000644000175000017500000001452713774446222017541 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2018, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 14; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::FactorRationals; use Math::PlanePath::DiagonalRationals; use Math::PlanePath::RationalsTree; my $path = Math::PlanePath::FactorRationals->new; #------------------------------------------------------------------------------ # A053985 - negabinary pos->pn MyOEIS::compare_values (anum => 'A053985', func => sub { my ($count) = @_; my @got; for (my $i = 0; @got < $count; $i++) { push @got, Math::PlanePath::FactorRationals::_pos_to_pn__negabinary($i); } return \@got; }); # A005351 pn(+ve) -> pos MyOEIS::compare_values (anum => 'A005351', func => sub { my ($count) = @_; my @got; for (my $i = 0; @got < $count; $i++) { push @got, Math::PlanePath::FactorRationals::_pn_to_pos__negabinary($i); } return \@got; }); # A039724 pn(+ve) -> pos, in binary MyOEIS::compare_values (anum => 'A039724', func => sub { my ($count) = @_; my @got; for (my $i = 0; @got < $count; $i++) { push @got, sprintf('%b', Math::PlanePath::FactorRationals::_pn_to_pos__negabinary($i)); } return \@got; }); # A005352 pn(-ve) -> pos MyOEIS::compare_values (anum => 'A005352', func => sub { my ($count) = @_; my @got; for (my $i = -1; @got < $count; $i--) { push @got, Math::PlanePath::FactorRationals::_pn_to_pos__negabinary($i); } return \@got; }); #------------------------------------------------------------------------------ # A065620 - revbinary pos->pn MyOEIS::compare_values (anum => 'A065620', func => sub { my ($count) = @_; my @got; for (my $i = 0; @got < $count; $i++) { push @got, Math::PlanePath::FactorRationals::_pos_to_pn__revbinary($i); } return \@got; }); # A065621 pn(+ve) -> pos MyOEIS::compare_values (anum => 'A065621', func => sub { my ($count) = @_; my @got; for (my $i = 1; @got < $count; $i++) { push @got, Math::PlanePath::FactorRationals::_pn_to_pos__revbinary($i); } return \@got; }); # A048724 pn(-ve) -> pos n XOR 2n MyOEIS::compare_values (anum => 'A048724', func => sub { my ($count) = @_; my @got; for (my $i = 0; @got < $count; $i--) { push @got, Math::PlanePath::FactorRationals::_pn_to_pos__revbinary($i); } return \@got; }); #------------------------------------------------------------------------------ # A072345 -- X or Y at N=2^k, being alternately 1 and 2^k MyOEIS::compare_values (anum => 'A072345', func => sub { my ($count) = @_; my @got; for (my $n = 2; @got < $count; $n *= 2) { my ($x, $y) = $path->n_to_xy ($n); push @got, $x; # last unless @got < $count; # push @got, $y; } return\@got; }); MyOEIS::compare_values (anum => q{A072345}, func => sub { my ($count) = @_; my @got; for (my $n = 1; @got < $count; $n *= 2) { my ($x, $y) = $path->n_to_xy ($n); push @got, $y; } return\@got; }); #------------------------------------------------------------------------------ # A011262 -- N at transpose Y/X, incr odd powers, decr even powers # cf A011264 prime factorization decr odd powers, incr even powers MyOEIS::compare_values (anum => 'A011262', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); ($x, $y) = ($y, $x); my $n = $path->xy_to_n ($x, $y); push @got, $n; } return\@got; }); sub calc_A011262 { my ($n) = @_; my $ret = 1; for (my $p = 2; $p <= $n; $p++) { if (($n % $p) == 0) { my $count = 0; while (($n % $p) == 0) { $n /= $p; $count++; } $count = ($count & 1 ? $count+1 : $count-1); # $count++; # $count ^= 1; # $count--; $ret *= $p ** $count; } } return $ret; } MyOEIS::compare_values (anum => 'A011262', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { push @got, calc_A011262($n); } return \@got; }); #------------------------------------------------------------------------------ # A102631 - n^2/squarefreekernel(n), is column at X=1 MyOEIS::compare_values (anum => 'A102631', func => sub { my ($count) = @_; my @got; for (my $y = 1; @got < $count; $y++) { push @got, $path->xy_to_n (1, $y); } return \@got; }); #------------------------------------------------------------------------------ # A060837 - permutation DiagonalRationals N -> FactorRationals N MyOEIS::compare_values (anum => 'A060837', func => sub { my ($count) = @_; my @got; my $columns = Math::PlanePath::DiagonalRationals->new; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $columns->n_to_xy ($n); push @got, $path->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A071970 - permutation Stern a[i]/[ai+1] which is Calkin-Wilf N -> power N MyOEIS::compare_values (anum => 'A071970', func => sub { my ($count) = @_; my @got; my $sb = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $sb->n_to_xy ($n); push @got, $path->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/PentSpiral-oeis.t0000644000175000017500000000333613244716475016525 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2015, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 2; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::PentSpiral; #------------------------------------------------------------------------------ # A140066 - N on Y axis MyOEIS::compare_values (anum => 'A140066', func => sub { my ($count) = @_; my $path = Math::PlanePath::PentSpiral->new; my @got; for (my $y = 0; @got < $count; $y++) { push @got, $path->xy_to_n(0,$y); } return \@got; }); #------------------------------------------------------------------------------ # A134238 - N on South-West diagonal MyOEIS::compare_values (anum => 'A134238', func => sub { my ($count) = @_; my $path = Math::PlanePath::PentSpiral->new; my @got; for (my $i = 0; @got < $count; $i++) { push @got, $path->xy_to_n(-$i,-$i); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/CCurve-oeis.t0000644000175000017500000002312013716617554015626 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2017, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt try => 'GMP'; use Test; plan tests => 19; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::CCurve; use Math::NumSeq::PlanePathTurn; # uncomment this to run the ### lines # use Smart::Comments '###'; my $path = Math::PlanePath::CCurve->new; sub right_boundary { my ($n_end) = @_; return MyOEIS::path_boundary_length ($path, $n_end, side => 'right'); } use Memoize; Memoize::memoize('right_boundary'); #------------------------------------------------------------------------------ # A332251 -- X coordinate MyOEIS::compare_values (anum => 'A332251', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); # A332252 -- Y coordinate MyOEIS::compare_values (anum => 'A332252', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A036554 - positions ending odd 0 bits is where turn straight or reverse MyOEIS::compare_values (anum => 'A036554', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'CCurve', turn_type => 'Straight'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; if ($value) { push @got, $i; } # N where straight } return \@got; }); # A003159 - positions ending even 0 bits is where turn either left or right, # ie. not straight or reverse MyOEIS::compare_values (anum => 'A003159', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'CCurve', turn_type => 'NotStraight'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; if ($value) { push @got, $i; } # N where not straight } return \@got; }); #------------------------------------------------------------------------------ # A096268 - morphism turn 1=straight,0=not-straight # but OFFSET=0 is turn at N=1, so "next turn" MyOEIS::compare_values (anum => 'A096268', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'CCurve', turn_type => 'Straight'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); MyOEIS::compare_values (anum => q{A096268}, func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { push @got, count_low_1_bits($n) % 2; } return \@got; }); MyOEIS::compare_values (anum => q{A096268}, func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { push @got, count_low_0_bits($n+1) % 2; } return \@got; }); sub count_low_1_bits { my ($n) = @_; my $count = 0; while ($n % 2) { $count++; $n = int($n/2); } return $count; } sub count_low_0_bits { my ($n) = @_; if ($n == 0) { die; } my $count = 0; until ($n % 2) { $count++; $n /= 2; } return $count; } #------------------------------------------------------------------------------ # A035263 - morphism turn 0=straight, 1=not-straight MyOEIS::compare_values (anum => 'A035263', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'CCurve', turn_type => 'NotStraight'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); MyOEIS::compare_values (anum => q{A035263}, # second check func => sub { my ($count) = @_; my @got; for (my $n = 1; @got < $count; $n++) { push @got, (count_low_0_bits($n) + 1) % 2; } return \@got; }); #------------------------------------------------------------------------------ # A027383 right boundary differences # cf # CCurve right boundary diffs even terms # 6,14,30,62,126 # A000918 2^n - 2. # CCurve right boundary diffs odd terms # 10,22,46,94,190 # A033484 3*2^n - 2. MyOEIS::compare_values (anum => 'A027383', max_value => 5000, func => sub { my ($count) = @_; my @got = (1); for (my $k = 1; @got < $count; $k++) { my $b1 = right_boundary(2**$k); my $b2 = right_boundary(2**($k+1)); push @got, $b2 - $b1; } return \@got; }); # A131064 right boundary odd powers, extra initial 1 MyOEIS::compare_values (anum => 'A131064', max_value => 5000, func => sub { my ($count) = @_; my @got = (1); for (my $k = 1; @got < $count; $k++) { my $boundary = right_boundary(2**(2*$k-1)); # 1,3,5,.. push @got, $boundary; ### at: "k=$k $boundary" } return \@got; }); #------------------------------------------------------------------------------ # A038503 etc counts of segments in direction foreach my $elem ([0, 'A038503', 0], [1, 'A038504', 0], [2, 'A038505', 1], [3, 'A000749', 0]) { my ($dir, $anum, $initial_k) = @$elem; MyOEIS::compare_values (anum => $anum, name => "segments in direction dir=$dir", max_value => 10_000, func => sub { my ($count) = @_; require Math::NumSeq::PlanePathDelta; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => 'CCurve', delta_type => 'Dir4'); my $total = 0; my $k = $initial_k; my $n_end = 2**$k; my @got; for (;;) { my ($i,$value) = $seq->next; if ($i >= $n_end) { # $i now in next level push @got, $total; last if @got >= $count; $k++; $n_end = 2**$k; } $total += ($value==$dir); } return \@got; }); } #------------------------------------------------------------------------------ # A000120 - count 1 bits total turn is direction MyOEIS::compare_values (anum => 'A000120', fixup => sub { # mangle to mod 4 my ($bvalues) = @_; @$bvalues = map {$_ % 4} @$bvalues; }, func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathDelta; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => 'CCurve', delta_type => 'Dir4'); while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A007814 - count low 0s, is turn right - 1 MyOEIS::compare_values (anum => 'A007814', fixup => sub { # mangle to mod 4 my ($bvalues) = @_; @$bvalues = map {$_ % 4} @$bvalues; }, func => sub { my ($count) = @_; my @got; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'CCurve', turn_type => 'Turn4'); # 0,1,2,3 leftward while (@got < $count) { my ($i,$value) = $seq->next; push @got, (1-$value) % 4; # negate to right } return \@got; }); #------------------------------------------------------------------------------ # A104488 -- num Hamiltonian groups # No, different at n=67 and more # # MyOEIS::compare_values # (anum => 'A104488', # func => sub { # my ($count) = @_; # require Math::NumSeq::PlanePathTurn; # my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'CCurve', # turn_type => 'Right'); # my @got = (0,0,0,0);; # while (@got < $count) { # my ($i,$value) = $seq->next; # push @got, $value; # } # return \@got; # }); #------------------------------------------------------------------------------ # A146559 = (i+1)^k is X+iY at N=2^k # A009545 = Im # A146559 X at N=2^k, being Re((i+1)^k) # A009545 Y at N=2^k, being Im((i+1)^k) MyOEIS::compare_values (anum => 'A146559', func => sub { my ($count) = @_; my @got; for (my $n = Math::BigInt->new(1); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); MyOEIS::compare_values (anum => 'A009545', func => sub { my ($count) = @_; my @got; for (my $n = Math::BigInt->new(1); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/PyramidSpiral-oeis.t0000644000175000017500000001521313776245253017222 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2018, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # A217295 Permutation of natural numbers arising from applying the walk of triangular horizontal-last spiral (defined in A214226) to the data of square spiral (e.g. A214526). # A214227 -- sum of 4 neighbours horizontal-last use 5.004; use strict; use Test; plan tests => 6; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::PyramidSpiral; use Math::PlanePath::SquareSpiral; use Math::NumSeq::PlanePathTurn; #------------------------------------------------------------------------------ # A217013 - permutation, SquareSpiral -> PyramidSpiral # X,Y in SquareSpiral order, N of PyramidSpiral # A217294 - inverse { my $pyramid = Math::PlanePath::PyramidSpiral->new; my $square = Math::PlanePath::SquareSpiral->new; # N= 1 2 3 4 5 6 7 8 9 10 # 1, 3, 14, 4, 6, 7, 8, 2, 12, 30, 13, 32, 59, 33, 15, 5, 19, 20, 21 MyOEIS::compare_values (anum => 'A217013', func => sub { my ($count) = @_; my @got; for (my $n = $square->n_start; @got < $count; $n++) { my ($x, $y) = $square->n_to_xy($n); ($x,$y) = (-$y,$x); # rotate +90 push @got, $pyramid->xy_to_n($x,$y); } return \@got; }); # N= 1 2 3 4 5 6 7 8 9 10 # 1, 8, 2, 4, 16, 5, 6, 7, 22, 45, 23, 9, 11, 3, 15, 35, 63, 36, 17 MyOEIS::compare_values (anum => 'A217294', func => sub { my ($count) = @_; my @got; for (my $n = $pyramid->n_start; @got < $count; $n++) { my ($x,$y) = $pyramid->n_to_xy($n); ($x,$y) = ($y,-$x); # rotate -90 push @got, $square->xy_to_n ($x,$y); } return \@got; }); # Different side lengths by horizontal long side at different phase ... # # side lengths 1,3,2,3,7,4 1,1,2,5,3,4,9 # picture A214226 PyramidSpiral # 21 13 # / \ / \ # 20 7 22 14 3 12 # / / \ \ / / \ \ # 19 6 1 8 15 4 1--2 11 # / / \ \ / / \ # 18 5--4--3--2 9 16 5--6--7--8--9-10 # / \ / \ # 17 16 15 14 13 12 11 10 17-18-19-20-21-22-23-24-25-26 51 # # N= 1 2 3 4 5 6 7 8 9 10 # 1, 7, 22, 8, 2, 3, 4, 6, 20, 42, 21, 44, 75, 45, 23, 9, 11, 12, 13 # square spiral order, upward first, clockwise } #------------------------------------------------------------------------------ # A329116 - X coordinate MyOEIS::compare_values (anum => 'A329116', # OFFSET=0 func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PyramidSpiral->new (n_start => 0); for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); # A329972 - Y coordinate MyOEIS::compare_values (anum => 'A329972', # OFFSET=0 func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PyramidSpiral->new (n_start => 0); for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); # A053615 -- abs(X) distance to pronic MyOEIS::compare_values (anum => 'A053615', # OFFSET=0 func => sub { my ($count) = @_; my $path = Math::PlanePath::PyramidSpiral->new (n_start => 0); my @got; for (my $n = 0; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, abs($x); } return \@got; }); #------------------------------------------------------------------------------ # A080037 -- N positions not straight ahead # not in OEIS: 13,17,26,31,37,50,57,65,82,91,101,122,133,145,170 # MyOEIS::compare_values # (anum => 'A999999', # func => sub { # my ($count) = @_; # my @got; # my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'PyramidSpiral', # turn_type => 'Straight'); # while (@got < $count) { # my ($i,$value) = $seq->next; # if (! $value) { push @got, $i; } # } # return \@got; # }); #------------------------------------------------------------------------------ # A217294 - permutation PyramidSpiral -> SquareSpiral # X,Y in PyramidSpiral order, N of SquareSpiral # but A217294 conceived by square spiral going up and clockwise # and pyramid spiral going left and clockwise # which means rotate -90 here MyOEIS::compare_values (anum => 'A217294', func => sub { my ($count) = @_; require Math::PlanePath::SquareSpiral; my $pyramid = Math::PlanePath::PyramidSpiral->new; my $square = Math::PlanePath::SquareSpiral->new; my @got; for (my $n = $pyramid->n_start; @got < $count; $n++) { my ($x, $y) = $pyramid->n_to_xy($n); ($x,$y) = ($y,-$x); # rotate -90 push @got, $square->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A214250 -- sum of 8 neighbours N MyOEIS::compare_values (anum => 'A214250', func => sub { my ($count) = @_; my $path = Math::PlanePath::PyramidSpiral->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); push @got, ($path->xy_to_n($x+1,$y) + $path->xy_to_n($x-1,$y) + $path->xy_to_n($x,$y+1) + $path->xy_to_n($x,$y-1) + $path->xy_to_n($x+1,$y+1) + $path->xy_to_n($x-1,$y-1) + $path->xy_to_n($x-1,$y+1) + $path->xy_to_n($x+1,$y-1) ); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/MultipleRings-oeis.t0000644000175000017500000000360013475335457017237 0ustar gggg#!/usr/bin/perl -w # Copyright 2013, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::PlanePath::MultipleRings; use Test; plan tests => 2; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; #------------------------------------------------------------------------------ # A090915 -- permutation X,-Y mirror across X axis MyOEIS::compare_values (anum => 'A090915', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::MultipleRings->new(step=>8); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); ($x,$y) = ($x,-$y); push @got, $path->xy_to_n ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ # A002024 - n repeated n times, is step=1 Radius+1 MyOEIS::compare_values (anum => 'A002024', func => sub { my ($count) = @_; my $path = Math::PlanePath::MultipleRings->new(step=>1); my @got; for (my $n = $path->n_start; @got < $count; $n++) { push @got, $path->n_to_radius($n) + 1; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/TriangularHypot-oeis.t0000644000175000017500000006010213732301407017555 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Maybe? # A033686 One ninth of theta series of A2[hole]^2. use 5.004; use strict; use Test; plan tests => 22; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use List::Util 'min', 'max'; use Math::PlanePath::TriangularHypot; # uncomment this to run the ### lines # use Smart::Comments '###'; #------------------------------------------------------------------------------ # A005881 - theta of A2 centred on edge # theta = num points of norm==n # 4---------4 3,-1 = 3*3+3 = 12 # / \ / \ -3,-1 = 12 # / \ / \ 0, 2 = 0+3*2*2 = 12 # / \ / \ # / \ / \ 4,2 = 6*6+3*2*2 = 48 # 3---------2---------3 -4,2 = 48 # / \ / \ / \ 0,-4 = 0+3*4*4 = 48 # / \ / \ / \ # / \ / \ / \ # / \ / \ / \ # 3---------1----*----1---------3 # \ / \ / \ / # \ / \ / \ / # \ / \ / \ / # \ / \ / \ / # 3---------2---------3 # . . . . . . 5 # # . . . . . 4 # # . 4 . 4 . . 3 # # . . . . . . . 2 # # . 3 . 2 . 3 . . 1 # # . . . . . . . . . <- Y=0 # # . . . 1 o 1 . . . 3 -1 # # . . . . . . . . . -2 # # . . 3 . 2 . 3. . . . -3 # # . . . . . . . . . -4 # # . . 4 . 4 . . . -5 # # . . . . - . . . . -6 # # X=0 1 2 3 4 5 6 7 sub xy_is_tedge { my ($x, $y) = @_; return ($y % 2 == 0 && ($x+$y) % 4 == 2); } MyOEIS::compare_values (anum => q{A005881}, func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'even'); my @got; my $n = $path->n_start; my $num = 0; my $want_norm = 4; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); if (! xy_is_tedge($x,$y)) { $n++; next; } my $norm = $x*$x + 3*$y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num; $want_norm += 8; $num = 0; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $n++; $num++; } } return \@got; }); #------------------------------------------------------------------------------ # A004016 - count of points at distance n MyOEIS::compare_values (anum => 'A004016', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new; my @got; my $prev_h = 0; my $num = 0; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); my $h = ($x*$x + 3*$y*$y) / 4; # Same when rotate -45 as per POD notes. # ($x,$y) = (($x+$y)/2, # ($y-$x)/2); # $h = $x*$x + $x*$y + $y*$y; if ($h == $prev_h) { $num++; } else { $got[$prev_h] = $num; $num = 1; $prev_h = $h; } } $#got = $count-1; # trim foreach my $got (@got) { $got ||= 0 } # pad, mutate array return \@got; }); # A002324 num points of norm n, which is X^2+3*Y^2=4n with "even" points here # divide by 6 for 1/6 wedge # cf A004016 = 6*A002324 except for A004016(0)=1 skipped # cf A033687 = A002324(3n+1) MyOEIS::compare_values (anum => q{A002324}, func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'even'); my @got; my $n = $path->n_start + 1; # excluding N=0 my $num = 0; my $want_norm = 4; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); my $norm = $x*$x + 3*$y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num/6; $want_norm += 4; $num = 0; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $n++; $num++; } } return \@got; }); #------------------------------------------------------------------------------ # A005929 - theta series hexagons midpoint of edge # 2,0,0,0,0,0,4,0,0,0,0,0,4,0,0,0,0,0,4,0,0,0,0,0,2,0,0,0,0,0,4,0, # . . . . . . 5 # # . 3 . 3 . 4 # # . . . . . . 3 # # . 2 . . . 2 . 2 # # . . . . . . . . 1 # # . . . 1 o 1 . . . <- Y=0 # # . . . . . . . . . . -1 # # . . 2 . . . 2 . . -2 # # . . . . . . . . . . -3 # # . . . 3 . 3 . . . -4 # # . . . . . . . . -5 # # . . . . - . . . . -6 # # 2 = 4*4+3*2*2 = 28 # 3 = 2*2+3*4*4 = 52 sub xy_is_hexedge { my ($x, $y) = @_; my $k = $x + 3*$y; return ($y % 2 == 0 && ($k % 12 == 2 || $k % 12 == 10)); } # foreach my $y (0 .. 20) { # foreach my $x (0 .. 60) { # print xy_is_hexedge($x,$y) ? '*' : ' '; # } # print "\n"; # } MyOEIS::compare_values (anum => q{A005929}, func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'even'); my @got = (0); my $n = $path->n_start; my $num = 0; my $want_norm = 4; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); if (! xy_is_hexedge($x,$y)) { $n++; next; } my $norm = $x*$x + 3*$y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num; $want_norm += 4; $num = 0; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $n++; $num++; } } return \@got; }); # A045839 = A005929/2. MyOEIS::compare_values (anum => q{A045839}, func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'even'); my @got = (0); my $n = $path->n_start; my $num = 0; my $want_norm = 4; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); if (! xy_is_hexedge($x,$y)) { $n++; next; } my $norm = $x*$x + 3*$y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num/2; $want_norm += 4; $num = 0; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $n++; $num++; } } return \@got; }); #------------------------------------------------------------------------------ # A038588 - clusters A2 centred deep hole # 3, 6, 12, 18, 21, 27 ... # unique values from A038587 = 3,6,12,12,18,21,27,27,30, # which is partial sums A005882 theta relative hole, # = 3,3,6,0,6,3,6,0,3,6,6,0,6,0,6,0,9,6,0,0,6 # theta = num points of norm==n # 3---------3 3,-1 = 3*3+3 = 12 # / \ / \ -3,-1 = 12 # / \ / \ 0, 2 = 0+3*2*2 = 12 # / \ / \ # / \ / \ 4,2 = 6*6+3*2*2 = 48 # 2---------1---------2 -4,2 = 48 # / \ / \ / \ 0,-4 = 0+3*4*4 = 48 # / \ / \ / \ # / \ / * \ / \ # / \ / \ / \ # 3---------1---------1---------3 # \ / \ / \ / # \ / \ / \ / # \ / \ / \ / # \ / \ / \ / # 3---------2---------3 # . 3 . . 3 . 5 # # . . . . . 4 # # . . . . . . 3 # # 2 . . 1 . . 2 2 # # . . . . . . . . 1 # # . . . . o . . . . <- Y=0 # # 3 . . 1 . . 1 . . 3 -1 # # . . . . . . . . . -2 # # . . . . . . . . . . -3 # # . 3 . . 2 . . 3 . -4 # # . . . . . . . . -5 # # . . . . - . . . . -6 # X=0 1 2 3 4 5 6 7 # # X+Y=6k+2 # Y=3z+2 # # block X mod 6, Y mod 6 only X=0,Y=2 and X=3,Y=5 # X+6Y mod 36 = 2*6=12 or 3+6*5=33 cf -3+6*-1=-9= # shift down X=0,Y=0 X=3,Y=3 only # X+6Y mod 36 = 0 or 3+6*3=21 # # X=6k # also rotate +120 -(X+3Y)/2 = 6k is X+3Y = 12k # also rotate -120 (3Y-X)/2 = 6k is X-3Y = 12k sub xy_is_tcentred { my ($x, $y) = @_; return ($y % 3 == 2 &&($x+$y) % 6 == 2); # Wrong: # my $k = ($x + 6*$y) % 36; # return ($k == 0+6*2 || $k == 3+6*5); } # A033687 with zeros, full steps of norm, divide by 3 # cf A033687 = A002324(3n+1) # A033687 = A005882 / 3 # A033687 = A033685(3n+1) MyOEIS::compare_values (anum => q{A033687}, func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'even'); my @got; my $n = $path->n_start; my $num = 0; my $want_norm = 12; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); if (! xy_is_tcentred($x,$y)) { $n++; next; } my $norm = $x*$x + 3*$y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num/3; $want_norm += 36; $num = 0; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $n++; $num++; } } return \@got; }); # 0, 3, 0, 0, 3, 0, 0, 6, 0, 0, 0, 0, 0, 6, 0, 0, 3, 0, 0, 6, 0, 0, 0, 0, # 1, 1, 2, 0, 2, 1, 2, 0, 1, 2, 2, 0, 2, 0, 2, 0, 3, 2, 0, 0, 2, 1, 2, 0, # A033687 Theta series of hexagonal lattice A_2 with respect to deep hole. MyOEIS::compare_values (anum => q{A038588}, # no duplicates func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'even'); my @got; my $n = $path->n_start; my $num = 0; my $want_norm = 12; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); my $norm = $x*$x + 3*$y*$y; if (! xy_is_tcentred($x,$y)) { ### sk: "$n at $x,$y norm=$norm" $n++; next; } if ($norm > $want_norm) { ### push: $num push @got, $num; $want_norm = $norm; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $num++; $n++; } } return \@got; }); MyOEIS::compare_values (anum => q{A038587}, # with duplicates func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'even'); my @got; my $n = $path->n_start; my $num = 0; my $want_norm = 12; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); if (! xy_is_tcentred($x,$y)) { $n++; next; } my $norm = $x*$x + 3*$y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num; $want_norm += 36; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $num++; $n++; } } return \@got; }); MyOEIS::compare_values (anum => q{A005882}, # with zeros func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'even'); my @got; my $n = $path->n_start; my $num = 0; my $want_norm = 12; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); if (! xy_is_tcentred($x,$y)) { $n++; next; } my $norm = $x*$x + 3*$y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num; $want_norm += 36; $num = 0; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $n++; $num++; } } return \@got; }); MyOEIS::compare_values (anum => q{A033685}, # with zeros, 1/3 steps of norm func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'even'); my @got = (0); my $n = $path->n_start; my $num = 0; my $want_norm = 12; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); if (! xy_is_tcentred($x,$y)) { $n++; next; } my $norm = $x*$x + 3*$y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num; $want_norm += 12; $num = 0; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $n++; $num++; } } return \@got; }); #------------------------------------------------------------------------------ # A217219 - theta of honeycomb at centre hole # count of how many at norm=4*k, possibly zero MyOEIS::compare_values (anum => 'A217219', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new(points=>'hex_centred'); my @got; my $n = $path->n_start; my $num = 0; my $want_norm = 0; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); my $norm = $x*$x + 3*$y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num; $want_norm += 4; $num = 0; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $n++; $num++; } } return \@got; }); #------------------------------------------------------------------------------ # A113062 - theta of honeycomb at node, # count of how many at norm=4*k, possibly zero MyOEIS::compare_values (anum => 'A113062', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'hex'); my @got; my $n = $path->n_start; my $num = 0; my $want_norm = 0; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); my $norm = $x*$x + 3*$y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num; $want_norm += 4; $num = 0; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $n++; $num++; } } return \@got; }); MyOEIS::compare_values (anum => 'A113063', # divided by 3 func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'hex'); my @got; my $n = $path->n_start + 1; # excluding origin X=0,Y=0 my $num = 0; my $want_norm = 4; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); my $norm = $x*$x + 3*$y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num/3; $want_norm += 4; $num = 0; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $n++; $num++; } } return \@got; }); #------------------------------------------------------------------------------ # A014201 - number of solutions x^2+xy+y^2 <= n excluding 0,0 # # norm = x^2+x*y+y^2 <= n # = (X^2 + 3*Y^2) / 4 <= n # = X^2 + 3*Y^2 <= 4*n MyOEIS::compare_values (anum => 'A014201', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'even'); my @got; my $num = 0; my $want_norm = 0; my $n = $path->n_start + 1; # skip X=0,Y=0 at N=Nstart while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); ($x,$y) = (($y-$x)/2, ($x+$y)/2); my $norm = $x*$x + $x*$y + $y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num; $want_norm++; } else { $num++; ### point: "$n at $x,$y norm=$norm total num=$num" $n++; } } return \@got; }); #------------------------------------------------------------------------------ # A038589 - number of solutions x^2+xy+y^2 <= n including 0,0 # - sizes successive clusters A2 centred at lattice point MyOEIS::compare_values (anum => 'A038589', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'even'); my @got; my $num = 0; my $want_norm = 0; my $n = $path->n_start; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); ($x,$y) = (($y-$x)/2, ($x+$y)/2); my $norm = $x*$x + $x*$y + $y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num; $want_norm++; } else { $num++; ### point: "$n at $x,$y norm=$norm total num=$num" $n++; } } return \@got; }); #------------------------------------------------------------------------------ # A092572 - all X^2+3Y^2 values which occur, points="all" X>0,Y>0 MyOEIS::compare_values (anum => 'A092572', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'all'); my @got; my $prev_h = -1; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); next unless ($x > 0 && $y > 0); my $h = $x*$x + 3*$y*$y; if ($h != $prev_h) { push @got, $h; $prev_h = $h; } } return \@got; }); #------------------------------------------------------------------------------ # A158937 - all X^2+3Y^2 values which occur, points="all" X>0,Y>0, with repeats MyOEIS::compare_values (anum => 'A158937', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'all'); my @got; my $prev_h = -1; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); next unless ($x > 0 && $y > 0); my $h = $x*$x + 3*$y*$y; push @got, $h; } return \@got; }); #------------------------------------------------------------------------------ # A092573 - count of points at distance n, points="all" X>0,Y>0 MyOEIS::compare_values (anum => 'A092573', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'all'); my @got; my $prev_h = 0; my $num = 0; for (my $n = $path->n_start; @got+1 < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); next unless ($x > 0 && $y > 0); my $h = $x*$x + 3*$y*$y; if ($h == $prev_h) { $num++; } else { $got[$prev_h] = $num; $num = 1; $prev_h = $h; } } shift @got; # drop n=0, start from n=1 $#got = $count-1; # trim foreach my $got (@got) { $got ||= 0 } # pad, mutate array return \@got; }); #------------------------------------------------------------------------------ # A092574 - all X^2+3Y^2 values which occur, points="all" X>0,Y>0 gcd(X,Y)=1 MyOEIS::compare_values (anum => 'A092574', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'all'); my @got; my $prev_h = -1; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); next unless ($x > 0 && $y > 0); next unless gcd($x,$y) == 1; my $h = $x*$x + 3*$y*$y; if ($h != $prev_h) { push @got, $h; $prev_h = $h; } } return \@got; }); #------------------------------------------------------------------------------ # A092575 - count of points at distance n, points="all" X>0,Y>0 gcd(X,Y)=1 MyOEIS::compare_values (anum => 'A092575', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new (points => 'all'); my @got; my $prev_h = 0; my $num = 0; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); next unless ($x > 0 && $y > 0); next unless gcd($x,$y) == 1; my $h = $x*$x + 3*$y*$y; if ($h == $prev_h) { $num++; } else { $got[$prev_h] = $num; $num = 1; $prev_h = $h; } } shift @got; # drop n=0, start from n=1 $#got = $count-1; # trim foreach my $got (@got) { $got ||= 0 } # pad, mutate array return \@got; }); sub gcd { my ($x, $y) = @_; #### _gcd(): "$x,$y" if ($y > $x) { $y %= $x; } for (;;) { if ($y <= 1) { return ($y == 0 ? $x : 1); } ($x,$y) = ($y, $x % $y); } } #------------------------------------------------------------------------------ # A088534 - count of points 0<=x<=y, points="even" MyOEIS::compare_values (anum => 'A088534', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new; my @got = (0) x scalar($count); my $prev_h = 0; my $num = 0; for (my $n = $path->n_start; ; $n++) { my ($x,$y) = $path->n_to_xy($n); # next unless 0 <= $x && $x <= $y; next unless 0 <= $y && $y <= $x/3; my $h = ($x*$x + 3*$y*$y) / 4; # Same when rotate -45 as per POD notes. # ($x,$y) = (($x+$y)/2, # ($y-$x)/2); # $h = $x*$x + $x*$y + $y*$y; if ($h == $prev_h) { $num++; } else { last if $prev_h >= $count; $got[$prev_h] = $num; $num = 1; $prev_h = $h; } } return \@got; }); #------------------------------------------------------------------------------ # A003136 - Loeschian numbers, norms of A2 lattice MyOEIS::compare_values (anum => 'A003136', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new; my @got; my $prev_h = -1; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); my $h = ($x*$x + 3*$y*$y) / 4; if ($h != $prev_h) { push @got, $h; $prev_h = $h; } } return \@got; }); #------------------------------------------------------------------------------ # A035019 - count of each hypot distance MyOEIS::compare_values (anum => 'A035019', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangularHypot->new; my @got; my $prev_h = 0; my $num = 0; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); my $h = $x*$x + 3*$y*$y; if ($h == $prev_h) { $num++; } else { push @got, $num; $num = 1; $prev_h = $h; } } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/GosperSide-oeis.t0000644000175000017500000002171313676241442016503 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt try => 'GMP'; use Test; plan tests => 13; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::GosperSide; my $path = Math::PlanePath::GosperSide->new; { my %dxdy_to_dir6 = ('2,0' => 0, '1,1' => 1, '-1,1' => 2, '-2,0' => 3, '-1,-1' => 4, '1,-1' => 5); # return 0 if X,Y's are straight, 2 if left, 1 if right sub xy_turn_6 { my ($prev_x,$prev_y, $x,$y, $next_x,$next_y) = @_; my $prev_dx = $x - $prev_x; my $prev_dy = $y - $prev_y; my $dx = $next_x - $x; my $dy = $next_y - $y; my $prev_dir = $dxdy_to_dir6{"$prev_dx,$prev_dy"}; if (! defined $prev_dir) { die "oops, unrecognised $prev_dx,$prev_dy"; } my $dir = $dxdy_to_dir6{"$dx,$dy"}; if (! defined $dir) { die "oops, unrecognised $dx,$dy"; } return ($dir - $prev_dir) % 6; } } #------------------------------------------------------------------------------ # A229215 - direction 1,2,3,-1,-2,-3 { my %dxdy_to_dirpn3 = ('2,0' => 1, # -2 -3 '1,-1' => 2, # \ / '-1,-1' => 3, # -1 ---*--- 1 '-2,0' => -1, # / \ '-1,1' => -2, # 3 2 '1,1' => -3); MyOEIS::compare_values (anum => 'A229215', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($dx,$dy) = $path->n_to_dxdy($n); my $dir = $dxdy_to_dirpn3{"$dx,$dy"}; die if ! defined $dir; push @got, $dir; } return \@got; }); } #------------------------------------------------------------------------------ # A005823 - N ternary no 1s is net turn 0 MyOEIS::compare_values (anum => 'A005823', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my $total_turn = 0; my @got = (0); while (@got < $count) { my ($i, $value) = $seq->next; $total_turn += $value; if ($total_turn == 0) { push @got, $i; } } return \@got; }); #------------------------------------------------------------------------------ # A099450 - Y at N=3^k MyOEIS::compare_values (anum => 'A099450', func => sub { my ($count) = @_; my @got; for (my $k = Math::BigInt->new(1); @got < $count; $k++) { my ($x,$y) = $path->n_to_xy(3**$k); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A189673 - morphism turn 0=left, 1=right, extra initial 0 MyOEIS::compare_values (anum => 'A189673', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); my @got = (0); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A189640 - morphism turn 0=left, 1=right, extra initial 0 MyOEIS::compare_values (anum => 'A189640', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got = (0); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A060032 - turn 1=left, 2=right as bignums to 3^level MyOEIS::compare_values (anum => 'A060032', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; require Math::BigInt; for (my $level = 0; @got < $count; $level++) { my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my $big = Math::BigInt->new(0); foreach my $n (1 .. 3**$level) { my ($i, $value) = $seq->next; $big = 10*$big + $value+1; } push @got, $big; } return \@got; }); #------------------------------------------------------------------------------ # A062756 - ternary count 1s, is cumulative turn left=+1, right=-1 MyOEIS::compare_values (anum => 'A062756', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my @got = (0); # bvalues starts with an n=0 my $cumulative; while (@got < $count) { my ($i, $value) = $seq->next; $cumulative += $value; push @got, $cumulative; } return \@got; }); #------------------------------------------------------------------------------ # A080846 - turn 0=left, 1=right MyOEIS::compare_values (anum => 'A080846', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got; while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A038502 - taken mod 3 is 1=left, 2=right MyOEIS::compare_values (anum => 'A038502', fixup => sub { # mangle to mod 3 my ($bvalues) = @_; @$bvalues = map { $_ % 3 } @$bvalues; }, func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got; while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value+1; } return \@got; }); #------------------------------------------------------------------------------ # A026225 - positions of left turns MyOEIS::compare_values (anum => 'A026225', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); my @got; while (@got < $count) { my ($i, $value) = $seq->next; if ($value) { push @got, $i; } } return \@got; }); MyOEIS::compare_values (anum => q{A026225}, func => sub { my ($count) = @_; my @got; for (my $n = 1; @got < $count; $n++) { if (digit_above_low_zeros($n) == 1) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A026179 - positions of right turns MyOEIS::compare_values (anum => 'A026179', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got = (1); # extra 1 ... while (@got < $count) { my ($i, $value) = $seq->next; if ($value) { push @got, $i; } } return \@got; }); MyOEIS::compare_values (anum => 'A026179', func => sub { my ($count) = @_; my @got = (1); for (my $n = 1; @got < $count; $n++) { if (digit_above_low_zeros($n) == 2) { push @got, $n; } } return \@got; }); sub digit_above_low_zeros { my ($n) = @_; if ($n == 0) { return 0; } while (($n % 3) == 0) { $n = int($n/3); } return ($n % 3); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/QuadricCurve-oeis.t0000644000175000017500000000410013716617600017021 0ustar gggg#!/usr/bin/perl -w # Copyright 2013, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt try => 'GMP'; use Test; plan tests => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::QuadricCurve; my $path = Math::PlanePath::QuadricCurve->new; #------------------------------------------------------------------------------ # A332246 -- X coordinate # A332247 -- Y coordinate MyOEIS::compare_values (anum => 'A332246', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); MyOEIS::compare_values (anum => 'A332247', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A133851 -- Y at N=2^k is 2^(k/4) when k=0mod4, starting MyOEIS::compare_values (anum => 'A133851', max_count => 1000, func => sub { my ($count) = @_; my @got; for (my $n = Math::BigInt->new(2); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/WunderlichSerpentine-oeis.t0000644000175000017500000004604313754670614020607 0ustar gggg#!/usr/bin/perl -w # Copyright 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'max'; use Test; plan tests => 8; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::WunderlichSerpentine; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use Math::PlanePath; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::NumSeq::PlanePathTurn; # GP-DEFINE read("my-oeis.gp"); #------------------------------------------------------------------------------ # A163343 - X=Y diagonal in all serpentine types foreach my $type ('alternating', 'coil', 'Peano', '100 000 001', '000 111 000', '000 111 111', ) { MyOEIS::compare_values (anum => 'A163343', func => sub { my ($count) = @_; my $path = Math::PlanePath::WunderlichSerpentine->new (serpentine_type => $type); my @got; for (my $i = 0; @got < $count; $i++) { push @got, $path->xy_to_n($i,$i); } return \@got; }); } #------------------------------------------------------------------------------ # A332380 alternating type, diagonals across, so segment replacement # A332381 # GP-DEFINE A332380 = OEIS_bfile_func("A332380"); # GP-DEFINE A332381 = OEIS_bfile_func("A332381"); # plothraw(vector(3^6,n,n--; A332380(n)), \ # vector(3^6,n,n--; A332381(n)), 1+8+16+32) # poldegree(OEIS_bfile_gf("A332380")) # poldegree(OEIS_bfile_gf("A332381")) # midpoints # GP-DEFINE A332380mid(n) = (A332380(n+1) + A332380(n))/2; # GP-DEFINE A332381mid(n) = (A332381(n+1) + A332381(n))/2; # plothraw(vector(3^6,n,n--; A332380mid(n)), \ # vector(3^6,n,n--; A332381mid(n)), 1+8+16+32) # # rotated # plothraw(vector(3^6,n,n--; A332380mid(n) - A332381mid(n)), \ # vector(3^6,n,n--; A332380mid(n) + A332381mid(n)), 1+8+16+32) # # by func # GP-DEFINE A332380z(n) = A332380(n) + I*A332381(n); # vector(18,n,n--; A332380z(n)) # GP-DEFINE A332380zmid(n) = (A332380z(n+1) + A332380z(n))/2; # my(v=vector(9^3,n,n--; (1+I)*A332380z(n))); \ # plothraw(real(v),imag(v),1+32); # chamfered corners # GP-DEFINE A332380_dz(n) = A332380z(n+1) - A332380z(n); # GP-DEFINE A332380_midfrac(n,f) = A332380z(n) + A332380_dz(n)*f; # my(l=List([])); \ # for(n=0,9^2-1, \ # listput(l,A332380_midfrac(n,.1)); \ # listput(l,A332380_midfrac(n,.9))); \ # l=Vec(l);\ # plothraw(real(l),imag(l),1+8+16+32); # Remy Sigrist's directions code in A332380 # [R, U, L, D]=[0..3]; # p = [R, U, R, D, L, D, R, U, R]; # l=List([]); z=0; \ # for(n=0, 9^3, \ # listput(l,z); z += I^vecsum(apply(d -> p[1+d], digits(n, #p)))); # l=Vec(l); # l=vector(#l-1,n, (1+I) * (l[n+1]+l[n])/2); # plothraw(real(l),imag(l),1+32); # GP-DEFINE alt_dir_table = [0,1,0,-1,-2,-1,0,1,0]; # GP-DEFINE alt_dir(n) = { # GP-DEFINE my(v=digits(n,9)); # GP-DEFINE sum(i=1,#v, alt_dir_table[v[i]+1]); # GP-DEFINE } # vector(50,n,n--; alt_dir(n)) # A159195 abs values # S_0 = [1]; morphism t -> |t-1|,t,t+1; sequence gives limiting value of S_{2n+1} # # vector(20,n,n--; alt_dir(n) % 4) # not in OEIS: 0, 1, 0, 3, 2, 3, 0, 1, 0, 1, 2, 1, 0, 3, 0, 1, 2, 1, 0, 1 # GP-DEFINE \\ triplets 010 323 232 101 # GP-DEFINE \\ 121 030 303 212 # GP-DEFINE { my(table=[[0,1,0, 3,2,3, 0,1,0], # GP-DEFINE [1,2,1, 0,3,0, 1,2,1], # GP-DEFINE [2,3,2, 1,0,1, 2,3,2], # GP-DEFINE [3,0,3, 2,1,2, 3,0,3]]); # GP-DEFINE alt_dir_morphism(k) = # GP-DEFINE my(v=[0]); # GP-DEFINE for(i=1,k, v=concat(apply(t->table[t+1], v))); # GP-DEFINE v; # GP-DEFINE } # GP-Test my(v=alt_dir_morphism(5)); \ # GP-Test #v==9^5 && vector(#v,n,n--; alt_dir(n)%4) == v # GP-DEFINE \\ WRONG needs 8 states to make 8 different triplets # GP-DEFINE { my(table=[[0,1,0], # GP-DEFINE [3,2,3], # GP-DEFINE [0,3,0], # GP-DEFINE [1,2,1]]); # GP-DEFINE alt_dir_morphism3(k) = # GP-DEFINE my(v=[0]); # GP-DEFINE for(i=1,k, v=concat(apply(t->table[t+1], v))); # GP-DEFINE v; # GP-DEFINE } # alt_dir_morphism3(4) - \ # vector(81,n,n--; alt_dir(n)%4) # my(v=alt_dir_morphism3(4)); \ # #v==9^5; vector(#v,n,n--; alt_dir(n)%4) - v # GP-DEFINE my(table=[0,1,0,3,2,3,0,1,0]); \ # GP-DEFINE alt_dir_plus(n) = vecsum(apply(d->table[1+d], digits(n,9))); # vector(35,n,alt_dir_plus(n)) # not in OEIS: 1,0,3,2,3,0,1,0,1,2,1,4,3,4,1,2,1,0,1,0,3,2,3,0,1,0,3,4,3,6,5,6,3,4,3 # 1, -1,-1,-1, 1,1,1, -1 # GP-DEFINE alt_turn(n) = { # GP-DEFINE n>=1 || error(); # GP-DEFINE while(n%9==0, n/=9); # GP-DEFINE [1, -1,-1,-1, 1,1,1, -1][n%9]; # GP-DEFINE } # vector(35,n,alt_turn(n)) # vector(27,n,alt_turn(n)>0) # vector(27,n,alt_turn(n)<0) # not in OEIS: 1,-1,-1,-1,1,1,1,-1,1,1,-1,-1,-1,1,1,1,-1,-1,1,-1,-1,-1,1,1,1,-1,-1 # not A216430 parity num 2s # not in OEIS: 1,0,0,0,1,1,1,0,1,1,0,0,0,1,1,1,0,0,1,0,0,0,1,1,1,0,0 # not in OEIS: 0,1,1,1,0,0,0,1,0,0,1,1,1,0,0,0,1,1,0,1,1,1,0,0,0,1,1 #-------------------- # A332380 code # GP-DEFINE \\ A332380 by rotate and position table # GP-DEFINE { my(table=[[1,0], [I,1], [1,1+I], # GP-DEFINE [-I,2+I], [-1,2], [-I,1], # GP-DEFINE [1,1-I], [I,2-I], [1,2]]); # GP-DEFINE table == vector(9,n, [table[n][1], sum(i=1,n-1, table[i][1])]) \ # GP-DEFINE || error(); # GP-DEFINE A332380_compact(n) = # GP-DEFINE my(v=digits(n,9),rot=1); # GP-DEFINE for(i=1,#v, [rot,v[i]] = rot*table[v[i]+1]); # GP-DEFINE fromdigits(real(v),3); # GP-DEFINE } # GP-Test my(g=OEIS_bfile_gf("A332380")); \ # GP-Test g == Polrev(vector(poldegree(g)+1,n,n--;A332380_compact(n))) # # GP-DEFINE { my(table=[[1,0], [I,1], [1,1+I], # GP-DEFINE [-I,2+I], [-1,2], [-I,1], # GP-DEFINE [1,1-I], [I,2-I], [1,2]]); # GP-DEFINE A332381_compact(n) = # GP-DEFINE my(v=digits(n,9),rot=1); # GP-DEFINE for(i=1,#v, [rot,v[i]] = rot*table[v[i]+1]); # GP-DEFINE fromdigits(imag(v),3); # GP-DEFINE } # GP-Test my(g=OEIS_bfile_gf("A332381")); \ # GP-Test g == Polrev(vector(poldegree(g)+1,n,n--;A332381_compact(n))) #------------------------------------------------------------------------------ # A332380,A332381 serpentine 010 101 010 - N on X=Y and X=-Y diagonals # GP-DEFINE \\ 9x, 9x+2 and 9x-6 # GP-DEFINE alt_is_leadingdiag(n) = { # GP-DEFINE while(n, if(n%9==0, , # GP-DEFINE n%9==2, n-=2, # GP-DEFINE n%9==3, n+=6, # GP-DEFINE return(0)); # GP-DEFINE n%9==0 || error(); n/=9); # GP-DEFINE 1; # GP-DEFINE } # GP-Test vector(9^5,n,n--; alt_is_leadingdiag(n)) == \ # GP-Test vector(9^5,n,n--; A332380_compact(n) == A332381_compact(n)) # for(n=0,9^2, if(alt_is_leadingdiag(n) != (A332380(n)==A332381(n)), print(n))) # GP-DEFINE balanced_ternary_digits(n) = { # GP-DEFINE my(l=List([])); # GP-DEFINE while(n, my(a=n%3); if(a==2, a=-1); listput(l,a); n=(n-a)/3); # GP-DEFINE Vecrev(l); # GP-DEFINE } # GP-Test /* A072998 balanced ternary -1,0,1 coded as 0,1,2 decimal digits */ \ # GP-Test /* but 0 as one 0 digit so 1, an exception to all starting 2 */ \ # GP-Test my(want=OEIS_samples("A072998")); \ # GP-Test vector(#want,n,n--; \ # GP-Test if(n==0,1, \ # GP-Test fromdigits(apply(n->n+1,balanced_ternary_digits(n))))) \ # GP-Test == want # GP-DEFINE A072998_compact(n) = \ # GP-DEFINE fromdigits(digits(n + (3^(1+if(n,logint(2*n,3))) - 1)/2, 3)); # GP-Test my(want=OEIS_samples("A072998")); \ # GP-Test vector(#want,n,n--; A072998_compact(n)) == want # GP-Test vector(3^7,n,n--; A072998_compact(n)) == \ # GP-Test vector(3^7,n,n--; if(n==0,1, \ # GP-Test fromdigits(apply(n->n+1,balanced_ternary_digits(n))))) # GP-DEFINE { my(table=[-6,0,2]); # GP-DEFINE alt_leadingdiag_by_balanced_ternary(n) = # GP-DEFINE my(v=balanced_ternary_digits(n)); # GP-DEFINE fromdigits(apply(d->table[d+2],v), 9); # GP-DEFINE } # GP-Test my(v=select(alt_is_leadingdiag, [0..9^5])); \ # GP-Test #v==122 && v == vector(#v,n,n--; alt_leadingdiag_by_balanced_ternary(n)) # GP-DEFINE { my(table=[[1,0],[1,2],[2,-6],[2,0]]); # GP-DEFINE alt_leadingdiag_by_ternary(n) = # GP-DEFINE my(v=concat(0,digits(n,3)), c=1); # GP-DEFINE forstep(i=#v,1,-1, [c,v[i]]=table[c+v[i]]); # GP-DEFINE fromdigits(v,9); # GP-DEFINE } # GP-Test vector(3^8,n,n--; alt_leadingdiag_by_balanced_ternary(n)) == \ # GP-Test vector(3^8,n,n--; alt_leadingdiag_by_ternary(n)) # GP-DEFINE { my(table=[-6,0,2]); # GP-DEFINE alt_leadingdiag_by_offset(n) = # GP-DEFINE fromdigits(apply(d->table[d+1], # GP-DEFINE digits(n + (3^(1+if(n,logint(2*n,3))) - 1)/2, 3)), 9); # GP-DEFINE } # GP-Test vector(3^8,n,n--; alt_leadingdiag_by_offset(n)) == \ # GP-Test vector(3^8,n,n--; alt_leadingdiag_by_ternary(n)) #------------------------------------------------------------------------------ # Coordinates - Alternating # GP-DEFINE want_AltX = [0,0,0,1,1,1,2,2,2,2,1,0,0,1,2,2,1,0,0,0,0,1,1,1,2,2,2,3,4,5,5,4,3,3,4,5,5,5,5,4,4,4,3,3,3,3,4,5,5,4,3,3,4,5,6,6,6,7,7,7,8,8,8,8,7,6,6,7,8,8,7,6,6,6,6,7,7,7,8,8,8,8,7,6,6,7,8,8,7,6,5,5,5,4,4,4,3,3,3,2,1,0,0,1,2,2,1,0,0,0,0,1,1,1,2,2,2,3,4,5,5,4,3,3,4,5,6,6,6,7,7,7,8,8,8,8,7,6,6,7,8,8,7,6,5,5,5,4,4,4,3,3,3,2,1,0,0,1,2,2,1,0,0,0,0,1,1,1,2,2,2,2,1,0,0,1,2,2,1,0,0,0,0,1,1,1,2,2,2,3,4,5,5,4,3,3,4,5,5,5,5,4,4,4,3,3,3,3,4,5,5,4,3,3,4,5,6,6,6,7,7,7,8,8,8,8,7,6,6,7,8,8,7,6,6,6,6,7,7,7,8,8,8,9,10,11,11,10,9,9,10,11,12,12,12,13,13,13,14,14,14,15,16,17,17,16,15,15,16,17,17,17,17,16,16,16,15,15,15,14,13,12,12,13,14,14,13,12,11,11,11,10,10,10,9,9,9,9,10,11,11,10,9,9,10,11,12,12,12,13,13,13,14,14,14,15,16,17,17,16,15,15,16,17,17,17,17,16,16,16,15,15,15,15,16,17,17,16,15,15,16,17,17,17,17,16,16,16,15,15,15,14,13,12,12,13,14,14,13,12,12,12,12,13,13,13,14,14,14,14,13,12,12,13,14,14,13,12,11,11,11,10,10,10,9,9,9,9,10,11,11,10,9,9,10,11,11,11,11,10,10,10,9,9,9,9,10,11,11,10,9,9,10,11,12,12,12,13,13,13,14,14,14,15,16,17,17,16,15,15,16,17,17,17,17,16,16,16,15,15,15,14,13,12,12,13,14,14,13,12,11,11,11,10,10,10,9,9,9,9,10,11,11,10,9,9,10,11,12,12,12,13,13,13,14,14,14,15,16,17,17,16,15,15,16,17,18,18,18,19,19,19,20,20,20,20,19,18,18,19,20,20,19,18,18,18,18,19,19,19,20,20,20,21,22,23,23,22,21,21,22,23,23,23,23,22,22,22,21,21,21,21,22,23,23,22,21,21,22,23,24,24,24,25,25,25,26,26,26,26,25,24,24,25,26,26,25,24,24,24,24,25,25,25,26,26,26,26,25,24,24,25,26,26,25,24,23,23,23,22,22,22,21,21,21,20,19,18,18,19,20,20,19,18,18,18,18,19,19,19,20,20,20,21,22,23,23,22,21,21,22,23,24,24,24,25,25,25,26,26,26,26,25,24,24,25,26,26,25,24,23,23,23,22,22,22,21,21,21,20,19,18,18,19,20,20,19,18,18,18,18,19,19,19,20,20,20,20,19,18,18,19,20,20,19,18,18,18,18,19,19,19,20,20,20,21,22,23,23,22,21,21,22,23,23,23,23,22,22,22,21,21,21,21,22,23,23,22,21,21,22,23,24,24,24,25,25,25,26,26,26,26,25,24,24,25,26,26,25,24,24,24,24,25,25,25,26,26,26,26]; # GP-DEFINE want_AltY = [0,1,2,2,1,0,0,1,2,3,3,3,4,4,4,5,5,5,6,7,8,8,7,6,6,7,8,8,8,8,7,7,7,6,6,6,5,4,3,3,4,5,5,4,3,2,2,2,1,1,1,0,0,0,0,1,2,2,1,0,0,1,2,3,3,3,4,4,4,5,5,5,6,7,8,8,7,6,6,7,8,9,9,9,10,10,10,11,11,11,11,10,9,9,10,11,11,10,9,9,9,9,10,10,10,11,11,11,12,13,14,14,13,12,12,13,14,14,14,14,13,13,13,12,12,12,12,13,14,14,13,12,12,13,14,15,15,15,16,16,16,17,17,17,17,16,15,15,16,17,17,16,15,15,15,15,16,16,16,17,17,17,18,19,20,20,19,18,18,19,20,21,21,21,22,22,22,23,23,23,24,25,26,26,25,24,24,25,26,26,26,26,25,25,25,24,24,24,23,22,21,21,22,23,23,22,21,20,20,20,19,19,19,18,18,18,18,19,20,20,19,18,18,19,20,21,21,21,22,22,22,23,23,23,24,25,26,26,25,24,24,25,26,26,26,26,25,25,25,24,24,24,24,25,26,26,25,24,24,25,26,26,26,26,25,25,25,24,24,24,23,22,21,21,22,23,23,22,21,21,21,21,22,22,22,23,23,23,23,22,21,21,22,23,23,22,21,20,20,20,19,19,19,18,18,18,18,19,20,20,19,18,18,19,20,20,20,20,19,19,19,18,18,18,17,16,15,15,16,17,17,16,15,14,14,14,13,13,13,12,12,12,11,10,9,9,10,11,11,10,9,9,9,9,10,10,10,11,11,11,12,13,14,14,13,12,12,13,14,15,15,15,16,16,16,17,17,17,17,16,15,15,16,17,17,16,15,14,14,14,13,13,13,12,12,12,11,10,9,9,10,11,11,10,9,8,8,8,7,7,7,6,6,6,6,7,8,8,7,6,6,7,8,8,8,8,7,7,7,6,6,6,5,4,3,3,4,5,5,4,3,3,3,3,4,4,4,5,5,5,5,4,3,3,4,5,5,4,3,2,2,2,1,1,1,0,0,0,0,1,2,2,1,0,0,1,2,2,2,2,1,1,1,0,0,0,0,1,2,2,1,0,0,1,2,3,3,3,4,4,4,5,5,5,6,7,8,8,7,6,6,7,8,8,8,8,7,7,7,6,6,6,5,4,3,3,4,5,5,4,3,2,2,2,1,1,1,0,0,0,0,1,2,2,1,0,0,1,2,3,3,3,4,4,4,5,5,5,6,7,8,8,7,6,6,7,8,9,9,9,10,10,10,11,11,11,11,10,9,9,10,11,11,10,9,9,9,9,10,10,10,11,11,11,12,13,14,14,13,12,12,13,14,14,14,14,13,13,13,12,12,12,12,13,14,14,13,12,12,13,14,15,15,15,16,16,16,17,17,17,17,16,15,15,16,17,17,16,15,15,15,15,16,16,16,17,17,17,18,19,20,20,19,18,18,19,20,21,21,21,22,22,22,23,23,23,24,25,26,26,25,24,24,25,26,26,26,26,25,25,25,24,24,24,23,22,21,21,22,23,23,22,21,20,20,20,19,19,19,18,18,18,18,19,20,20,19,18,18,19,20,21,21,21,22,22,22,23,23,23,24,25,26,26,25,24,24,25,26,27]; # GP-DEFINE AltY(n) = { # GP-DEFINE my(v=digits(n,9), t=Mod(0,2), k=Mod(0,2)); # GP-DEFINE for(i=1,#v, my(d=v[i], y=if(t,d\3,d%3), c=d+y); # GP-DEFINE v[i]=if((k+=c)+t*c, 2-y, y); t+=d); # GP-DEFINE fromdigits(v,3); # GP-DEFINE } # GP-DEFINE AltY(n) = { # GP-DEFINE my(v=digits(n,9), t=Mod(0,2), k=Mod(0,2)); # GP-DEFINE for(i=1,#v, my(p=divrem(v[i],3),y); # GP-DEFINE if(t, y=if(k, 2-p[1],p[1]); k+=p[2], # GP-DEFINE y=if(k+=p[1], 2-p[2],p[2])); # GP-DEFINE t+=v[i]; v[i]=y); # GP-DEFINE fromdigits(v,3); # GP-DEFINE } # GP-Test vector(#want_AltY,n,n--; AltY(n)) == want_AltY # vector(161,n,n--; AltY(n)) - want_AltY[1..161] # vector(10,n,n--; AltY(n)) # AltY(90) # AltY(10) # want_AltY[10 +1] # digits(90,9) # divrem(1,3) == [0,1]~ # GP-Test vector(9^3,n,n--; AltY(n)) == \ # GP-Test vector(9^3,n,n--; \ # GP-Test (A332380(n) + A332380(n+1) + A332381(n) + A332381(n+1) - 1)/2) # GP-Test vector(9^5,n,n--; AltY(n)) == \ # GP-Test vector(9^5,n,n--; \ # GP-Test (A332380_compact(n) + A332380_compact(n+1) \ # GP-Test + A332381_compact(n) + A332381_compact(n+1) - 1)/2) # real(('x+'y*I)/(1+I)) == 'x/2 + 'y/2 # GP-DEFINE \\ arithmetic transposing # GP-DEFINE AltX(n) = { # GP-DEFINE my(v=digits(n,9), t=Mod(0,2), k=Mod(0,2)); # GP-DEFINE for(i=1,#v, my(d=v[i], x=if(t,d%3,d\3), c=d+x); # GP-DEFINE v[i]=if(k+t*c, 2-x, x); k+=c; t+=d); # GP-DEFINE fromdigits(v,3); # GP-DEFINE } # GP-DEFINE \\ conditional transposing # GP-DEFINE AltX(n) = { # GP-DEFINE my(v=digits(n,9), t=Mod(1,2), k=Mod(0,2)); # GP-DEFINE for(i=1,#v, my(p=divrem(v[i],3),x); # GP-DEFINE if(t, x=if(k, 2-p[1],p[1]); k+=p[2], # GP-DEFINE x=if(k+=p[1], 2-p[2],p[2])); # GP-DEFINE t+=v[i]; v[i]=x); # GP-DEFINE fromdigits(v,3); # GP-DEFINE } # GP-Test vector(#want_AltX,n,n--; AltX(n)) == want_AltX # vector(161,n,n--; AltX(n)) - want_AltX[1..161] # vector(10,n,n--; AltX(n)) # # GP-Test vector(9^3,n,n--; AltX(n)) == \ # GP-Test vector(9^3,n,n--; \ # GP-Test (A332380(n) + A332380(n+1) - A332381(n) - A332381(n+1) - 1)/2) # GP-Test vector(9^5,n,n--; AltX(n)) == \ # GP-Test vector(9^5,n,n--; \ # GP-Test (A332380_compact(n) + A332380_compact(n+1) \ # GP-Test - A332381_compact(n) - A332381_compact(n+1) - 1)/2) # GP-DEFINE dAltX(n) = AltX(n+1) - AltX(n); # GP-DEFINE dAltY(n) = AltY(n+1) - AltY(n); # # GP-Test /* divining diagonal direction from dx,dy and parity */ \ # GP-Test vector(9^3,n,n--; A332380(n)) == \ # GP-Test vector(9^3,n,n--; real( (AltX(n) + AltY(n)*I)/(1+I) - (n%2)/2) \ # GP-Test + if(n%2==1 && dAltY(n)==1, 1, \ # GP-Test n%2==1 && dAltY(n)==-1, 1, \ # GP-Test n%2==0 && dAltY(n)==1, 0, \ # GP-Test n%2==0 && dAltY(n)==-1, 1, \ # GP-Test n%2==1 && dAltX(n)==1, 1, \ # GP-Test n%2==1 && dAltX(n)==-1, 1, \ # GP-Test n%2==0 && dAltX(n)==1, 0, \ # GP-Test n%2==0 && dAltX(n)==-1, 1, \ # GP-Test 'x)) #------------------------------------------------------------------------------ # A323258 -- X coordinate, Robert Dickau's variation. # A323259 -- Y coordinate # # Wunderlich serpentine "alternating", but the least significant digit of N # which 9 points in 3x3 has a transpose along its diagonal. # # Occurs since the base figure is an S orientation but then it and # subsequent bigger 3^k x 3^k blocks are assembled in N orientation. # In all cases rotations to make the ends join up. # # So in an "even" block which is leading diagonal, transpose lowest ternary # digit of x,y. Or in odd block which is opposite diagonal, complement # 2-y,2-x. sub xy_low_transpose { my ($x,$y) = @_; my $xr = _divrem_mutate($x,3); my $yr = _divrem_mutate($y,3); if (($x+$y)&1) { ($xr,$yr) = (2-$yr,2-$xr); } else { ($xr,$yr) = ($yr,$xr); } return (3*$x+$xr, 3*$y+$yr); } MyOEIS::compare_values (anum => 'A323258', func => sub { my ($count) = @_; my $path = Math::PlanePath::WunderlichSerpentine->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); ($x,$y) = xy_low_transpose($x,$y); push @got, $y; } return \@got; }); MyOEIS::compare_values (anum => 'A323259', func => sub { my ($count) = @_; my $path = Math::PlanePath::WunderlichSerpentine->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); ($x,$y) = xy_low_transpose($x,$y); push @got, $x; } return \@got; }); # without low transpose: # not in OEIS: 0,1,2,2,1,0,0,1,2,3,3,3,4,4,4,5,5,5,6,7,8,8,7,6,6,7,8,8,8,8,7,7,7,6,6,6,5,4,3 # not in OEIS: 0,0,0,1,1,1,2,2,2,2,1,0,0,1,2,2,1,0,0,0,0,1,1,1,2,2,2,3,4,5,5,4,3,3,4,5,5,5,5,4,4,4,3,3,3,3,4,5,5,4,3 # ~/OEIS/a323258.png # ~/OEIS/b323258.txt # http://robertdickau.com/wunderlich.html # my(g=OEIS_bfile_gf("A323258")); x(n) = polcoeff(g,n); # my(g=OEIS_bfile_gf("A323259")); y(n) = polcoeff(g,n); # plothraw(vector(9^3+10,n,n--; x(n)), \ # vector(9^3+10,n,n--; y(n)), 1+8+16+32) # plothraw(vector(9^4+1,n,n--; x(n)), \ # vector(9^4+1,n,n--; y(n)), 1+8+16+32) # plothraw(vector(9^3,n, y(9*n-4)), \ # vector(9^3,n, x(9*n-4)), 1+8+16+32) #------------------------------------------------------------------------------ exit 0; __END__ # my $xr = $x % 3; # my $yr = $y % 3; # return ($x - $xr + $yr, # $y - $yr + $xr); # sub xy_high_transpose { # my ($x,$y) = @_; # my @x = digit_split_lowtohigh($x,3); # my @y = digit_split_lowtohigh($y,3); # my $max = max($#x,$#y); # if ($max >= 0) { # push @x, (0) x ($max - $#x); # push @y, (0) x ($max - $#y); # ($x[$max],$y[$max]) = ($y[$max],$x[$max]); # } # return (digit_join_lowtohigh(\@y,3), # digit_join_lowtohigh(\@x,3)); # } Math-PlanePath-129/xt/oeis/AnvilSpiral-oeis.t0000644000175000017500000000437713774704567016705 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2018, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::AnvilSpiral; #------------------------------------------------------------------------------ # A033581 - N on Y axis (6*n^2) except for initial N=2 MyOEIS::compare_values (anum => 'A033581', func => sub { my ($count) = @_; my $path = Math::PlanePath::AnvilSpiral->new (wider => 2); my @got = (0); for (my $y = 1; @got < $count; $y++) { push @got, $path->xy_to_n(0,$y); } return \@got; }); #------------------------------------------------------------------------------ # A136392 - N on Y negative, with offset making n=-Y+1 MyOEIS::compare_values (anum => 'A136392', func => sub { my ($count) = @_; my $path = Math::PlanePath::AnvilSpiral->new; my @got; for (my $y = 0; @got < $count; $y--) { push @got, $path->xy_to_n(0,$y); } return \@got; }); #------------------------------------------------------------------------------ # A033569 - N-1 on NW diagonal, wider=1, (2*n-1)*(3*n+1) MyOEIS::compare_values (anum => 'A033569', func => sub { my ($count) = @_; my $path = Math::PlanePath::AnvilSpiral->new (wider => 1); my @got = (-1); # A033569 initial -1 instead of 1 for (my $i = 1; @got < $count; $i++) { push @got, $path->xy_to_n(-$i, $i) - 1; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/CellularRule-oeis.t0000644000175000017500000016525313761670733017046 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # cf A094605 rule 30 period of nth diagonal # A094606 log2 of that period use 5.004; use strict; use Math::BigInt try => 'GMP'; # for bignums in reverse-add steps use List::Util 'min','max'; use Test; plan tests => 800; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::CellularRule; # uncomment this to run the ### lines # use Smart::Comments '###'; sub streq_array { my ($a1, $a2) = @_; if (! ref $a1 || ! ref $a2) { return 0; } while (@$a1 && @$a2) { if ($a1->[0] ne $a2->[0]) { MyTestHelpers::diag ("differ: ", $a1->[0], ' ', $a2->[0]); return 0; } shift @$a1; shift @$a2; } return (@$a1 == @$a2); } #------------------------------------------------------------------------------ # duplications foreach my $elem (# [ 'A071033', 'A118102', 'rule=94' ], # [ 'A071036', 'A118110', 'rule=150' ], [ 'A071037', 'A118172', 'rule=158' ], [ 'A071039', 'A118111', 'rule=190' ], ) { my ($anum1, $anum2, $name) = @$elem; my ($aref1) = MyOEIS::read_values($anum1); my ($aref2) = MyOEIS::read_values($anum2); $#$aref1 = min($#$aref1, $#$aref2); $#$aref2 = min($#$aref1, $#$aref2); my $str1 = join(',', @$aref1); my $str2 = join(',', @$aref2); print "$name ", $str1 eq $str2 ? "same" : "different","\n"; if ($str1 ne $str2) { print " $str1\n"; print " $str2\n"; } } #------------------------------------------------------------------------------ # A061579 - permutation N at -X,Y MyOEIS::compare_values (anum => 'A061579', func => sub { my ($count) = @_; my $path = Math::PlanePath::CellularRule->new (n_start => 0, rule => 50); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n (-$x,$y); } return \@got; }); #------------------------------------------------------------------------------ my @data = ( # Not quite, initial values differ # [ 'A051341', 7, 'bits' ], [ 'A265718', 1, 'bits' ], [ 'A265721', 1, 'bignum' ], [ 'A265720', 1, 'bignum', base=>2 ], [ 'A265722', 1, 'number_of', value=>1 ], [ 'A265723', 1, 'number_of', value=>0 ], [ 'A265724', 1, 'number_of', value=>0, cumulative=>1 ], # rule=2,10,34,42,66,74,98,106,130,138,162,170,194,202,226,234 (mirror image is rule 16) [ 'A098608', 2, 'bignum', base=>2 ], # 100^n # rule=3,35 (mirror image is rule 17) [ 'A263428', 3, 'bits' ], [ 'A266069', 3, 'bignum' ], [ 'A266068', 3, 'bignum', base=>2 ], [ 'A266070', 3, 'bits', part => 'centre' ], [ 'A266071', 3, 'bignum_central_column' ], [ 'A266072', 3, 'number_of', value=>1 ], [ 'A266073', 3, 'number_of', value=>0 ], [ 'A266074', 3, 'number_of', value=>0, cumulative=>1 ], # characteristic func of pronics m*(m+1) # rule=4,12,36,44,68,76,100,108,132,140,164,172,196,204,228,236 [ 'A005369', 4, 'bits' ], [ 'A011557', 4, 'bignum', base=>2 ], # 10^n [ 'A266174', 5, 'bits' ], [ 'A266176', 5, 'bignum' ], [ 'A266175', 5, 'bignum', base=>2 ], [ 'A266178', 6, 'bits' ], [ 'A266180', 6, 'bignum' ], [ 'A266179', 6, 'bignum', base=>2 ], [ 'A266216', 7, 'bits' ], [ 'A266218', 7, 'bignum' ], [ 'A266217', 7, 'bignum', base=>2 ], [ 'A266219', 7, 'bignum_central_column' ], [ 'A266220', 7, 'number_of', value=>1 ], [ 'A266222', 7, 'number_of', value=>0 ], [ 'A266221', 7, 'number_of', value=>1, cumulative=>1 ], [ 'A266223', 7, 'number_of', value=>0, cumulative=>1 ], [ 'A266243', 9, 'bits' ], [ 'A266245', 9, 'bignum' ], [ 'A266244', 9, 'bignum', base=>2 ], [ 'A266246', 9, 'bits', part => 'centre' ], [ 'A266247', 9, 'bignum_central_column' ], [ 'A266248', 9, 'bignum_central_column', base=>2 ], [ 'A266249', 9, 'number_of', value=>1 ], [ 'A266251', 9, 'number_of', value=>0 ], [ 'A266250', 9, 'number_of', value=>1, cumulative=>1 ], [ 'A266252', 9, 'number_of', value=>0, cumulative=>1 ], [ 'A266253', 11, 'bits' ], [ 'A266255', 11, 'bignum' ], [ 'A266254', 11, 'bignum', base=>2 ], [ 'A266256', 11, 'number_of', value=>1 ], [ 'A266258', 11, 'number_of', value=>0 ], [ 'A266257', 11, 'number_of', value=>1, cumulative=>1 ], [ 'A266259', 11, 'number_of', value=>0, cumulative=>1 ], [ 'A266282', 13, 'bits' ], [ 'A266284', 13, 'bignum' ], [ 'A266283', 13, 'bignum', base=>2 ], [ 'A266285', 13, 'number_of', value=>1 ], [ 'A266286', 13, 'number_of', value=>0 ], [ 'A266287', 13, 'number_of', value=>0, cumulative=>1 ], [ 'A266298', 14, 'bits' ], [ 'A266299', 14, 'bignum', base=>2 ], [ 'A266300', 15, 'bits' ], [ 'A266302', 15, 'bignum' ], [ 'A266301', 15, 'bignum', base=>2 ], [ 'A266303', 15, 'number_of', value=>1 ], [ 'A266304', 15, 'number_of', value=>0, cumulative=>1 ], [ 'A260552', 17, 'bits' ], [ 'A266090', 17, 'bignum' ], [ 'A260692', 17, 'bignum', base=>2 ], # rule=19 [ 'A266155', 19, 'bits' ], [ 'A266323', 19, 'bignum', base=>2 ], [ 'A266324', 19, 'bignum' ], # rule=20,52,148,180 (mirror image of rule 6) [ 'A266326', 20, 'bits' ], [ 'A266327', 20, 'bignum', base=>2 ], # rule=21 (mirror image of rule 7) [ 'A266377', 21, 'bits' ], [ 'A266379', 21, 'bignum', base=>2 ], [ 'A266380', 21, 'bignum' ], # rule=22 [ 'A071029', 22, 'bits' ], [ 'A266381', 22, 'bignum', base=>2 ], [ 'A266382', 22, 'bignum' ], [ 'A071043', 22, 'number_of', value=>0 ], [ 'A071044', 22, 'number_of', value=>1 ], [ 'A266383', 22, 'number_of', value=>1, cumulative=>1 ], [ 'A266384', 22, 'number_of', value=>0, cumulative=>1 ], # rule=23,31,55,63,87,95,119,127 [ 'A266434', 23, 'bits' ], [ 'A266435', 23, 'bignum', base=>2 ], [ 'A266436', 23, 'bignum' ], [ 'A266437', 23, 'number_of', value=>1 ], [ 'A266439', 23, 'number_of', value=>0 ], [ 'A266438', 23, 'number_of', value=>1, cumulative=>1 ], [ 'A266440', 23, 'number_of', value=>0, cumulative=>1 ], # rule=25 (mirror image is rule 67) [ 'A266441', 25, 'bits' ], [ 'A266443', 25, 'bignum' ], [ 'A266442', 25, 'bignum', base=>2 ], [ 'A266444', 25, 'bits', part => 'centre' ], [ 'A266445', 25, 'bignum_central_column' ], [ 'A266446', 25, 'bignum_central_column', base=>2 ], [ 'A266447', 25, 'number_of', value=>1 ], [ 'A266449', 25, 'number_of', value=>0 ], [ 'A266448', 25, 'number_of', value=>1, cumulative=>1 ], [ 'A266450', 25, 'number_of', value=>0, cumulative=>1 ], # rule=27 (mirror image is rule 83) [ 'A266459', 27, 'bits' ], [ 'A266461', 27, 'bignum' ], [ 'A266460', 27, 'bignum', base=>2 ], # rule=28,156 (mirror image is rule 70) [ 'A266502', 28, 'bits' ], [ 'A283642', 28, 'bignum' ], # sharing "Rule 678" [ 'A001045', 28, 'bignum', initial=>[0,1] ], # Jacobsthal [ 'A266508', 28, 'bignum', base=>2 ], [ 'A070909', 28, 'bits', part=>'right' ], # rule=29 (mirror image is rule 71) [ 'A266514', 29, 'bits' ], [ 'A266516', 29, 'bignum' ], [ 'A266515', 29, 'bignum', base=>2 ], # rule=30 (mirror image is rule 86) # 111 110 101 100 011 010 001 000 # 0 0 0 1 1 1 1 0 # 135 started from 0 = complement of rule 30 started from 1 [ 'A070950', 30, 'bits' ], [ 'A226463', 30, 'bits', complement => 1 ], # rule 135 starting from "0" [ 'A110240', 30, 'bignum' ], # cf A074890 some strange form [ 'A245549', 30, 'bignum', base=>2 ], [ 'A051023', 30, 'bits', part=>'centre' ], [ 'A261299', 30, 'bignum_central_column' ], [ 'A070951', 30, 'number_of', value=>0 ], [ 'A070952', 30, 'number_of', value=>1, max_count=>400, initial=>[0] ], [ 'A151929', 30, 'number_of_1s_first_diff', max_count=>200, initial=>[0], # without diffs yet applied ... ], [ 'A110267', 30, 'number_of', cumulative=>1 ], [ 'A265224', 30, 'number_of', cumulative=>1, value=>0 ], [ 'A226482', 30, 'number_of_runs' ], [ 'A110266', 30, 'number_of_runs', value=>1 ], [ 'A092539', 30, 'bignum_central_column', base=>2 ], [ 'A094603', 30, 'trailing_number_of', value=>1 ], [ 'A094604', 30, 'new_maximum_trailing_number_of', 1 ], [ 'A100053', 30, 'longest_run', value=>0 ], [ 'A266588', 37, 'bits' ], [ 'A266590', 37, 'bignum' ], [ 'A266589', 37, 'bignum', base=>2 ], [ 'A266591', 37, 'bits', part => 'centre' ], [ 'A266592', 37, 'bignum_central_column' ], [ 'A052997', 37, 'bignum_central_column', base=>2 ], [ 'A266593', 37, 'number_of', value=>1 ], [ 'A266595', 37, 'number_of', value=>0 ], [ 'A266594', 37, 'number_of', value=>1, cumulative=>1 ], [ 'A266596', 37, 'number_of', value=>0, cumulative=>1 ], [ 'A266605', 39, 'bits' ], [ 'A266607', 39, 'bignum' ], [ 'A266606', 39, 'bignum', base=>2 ], [ 'A266608', 41, 'bits' ], [ 'A266610', 41, 'bignum' ], [ 'A266609', 41, 'bignum', base=>2 ], [ 'A266611', 41, 'bits', part => 'centre' ], [ 'A266612', 41, 'bignum_central_column' ], [ 'A266613', 41, 'bignum_central_column', base=>2 ], [ 'A266614', 41, 'number_of', value=>1 ], [ 'A266616', 41, 'number_of', value=>0 ], [ 'A266615', 41, 'number_of', value=>1, cumulative=>1 ], [ 'A266617', 41, 'number_of', value=>0, cumulative=>1 ], [ 'A266619', 45, 'bits' ], [ 'A266622', 45, 'bignum' ], [ 'A266621', 45, 'bignum', base=>2 ], [ 'A266623', 45, 'bits', part => 'centre' ], [ 'A266624', 45, 'bignum_central_column' ], [ 'A266625', 45, 'bignum_central_column', base=>2 ], [ 'A266628', 45, 'number_of', value=>0 ], [ 'A266626', 45, 'number_of', value=>1 ], [ 'A266627', 45, 'number_of', value=>1, cumulative=>1 ], [ 'A266629', 45, 'number_of', value=>0, cumulative=>1 ], [ 'A266659', 47, 'bits' ], [ 'A266661', 47, 'bignum' ], [ 'A266660', 47, 'bignum', base=>2 ], [ 'A266664', 47, 'number_of', value=>0 ], [ 'A266662', 47, 'number_of', value=>1 ], [ 'A266663', 47, 'number_of', value=>1, cumulative=>1 ], [ 'A266665', 47, 'number_of', value=>0, cumulative=>1 ], [ 'A071028', 50, 'bits' ], [ 'A094028', 50, 'bignum', base=>2 ], [ 'A266666', 51, 'bits' ], [ 'A266668', 51, 'bignum' ], [ 'A266667', 51, 'bignum', base=>2 ], [ 'A266669', 53, 'bits' ], [ 'A266671', 53, 'bignum' ], [ 'A266670', 53, 'bignum', base=>2 ], [ 'A071030', 54, 'bits' ], [ 'A118108', 54, 'bignum' ], [ 'A118109', 54, 'bignum', base=>2 ], [ 'A259661', 54, 'bignum_central_column' ], [ 'A064455', 54, 'number_of', value=>1 ], [ 'A071045', 54, 'number_of', value=>0 ], [ 'A265225', 54, 'number_of', value=>1, cumulative=>1 ], [ 'A050187', 54, 'number_of', value=>0, cumulative=>1, y_start=>1 ], [ 'A266672', 57, 'bits' ], [ 'A266674', 57, 'bignum' ], [ 'A266673', 57, 'bignum', base=>2 ], [ 'A266716', 59, 'bits' ], [ 'A266717', 59, 'bignum', base=>2 ], [ 'A266718', 59, 'bignum' ], [ 'A266719', 59, 'bits', part=>'centre' ], [ 'A266720', 59, 'bignum_central_column' ], [ 'A266721', 59, 'bignum_central_column', base=>2 ], [ 'A266722', 59, 'number_of', value=>1 ], [ 'A266724', 59, 'number_of', value=>0 ], [ 'A266723', 59, 'number_of', value=>1, cumulative=>1 ], [ 'A266725', 59, 'number_of', value=>0, cumulative=>1 ], [ 'A006943', 60, 'bignum', base=>2 ], # Sierpinski [ 'A266786', 61, 'bits' ], [ 'A266788', 61, 'bignum' ], [ 'A266787', 61, 'bignum', base=>2 ], [ 'A266789', 61, 'bits', part=>'centre' ], [ 'A266790', 61, 'bignum_central_column' ], [ 'A266791', 61, 'bignum_central_column', base=>2 ], [ 'A266792', 61, 'number_of', value=>1 ], [ 'A266794', 61, 'number_of', value=>0 ], [ 'A266793', 61, 'number_of', value=>1, cumulative=>1 ], [ 'A266795', 61, 'number_of', value=>0, cumulative=>1 ], [ 'A071031', 62, 'bits' ], [ 'A266809', 62, 'bignum', base=>2 ], [ 'A266810', 62, 'bignum' ], [ 'A071046', 62, 'number_of', value=>0 ], [ 'A071047', 62, 'number_of', value=>1 ], [ 'A266811', 62, 'number_of', value=>1, cumulative=>1 ], [ 'A266813', 62, 'number_of', value=>0, cumulative=>1 ], [ 'A266837', 67, 'bits' ], [ 'A266838', 67, 'bignum', base=>2 ], [ 'A266839', 67, 'bignum' ], [ 'A266840', 69, 'bits' ], [ 'A266841', 69, 'bignum', base=>2 ], [ 'A266842', 69, 'bignum' ], [ 'A266843', 70, 'bits' ], [ 'A266844', 70, 'bignum', base=>2 ], [ 'A266846', 70, 'bignum' ], [ 'A071022', 70, 'bits', part=>'left' ], [ 'A080513', 70, 'number_of', value=>1 ], [ 'A266848', 71, 'bits' ], [ 'A266849', 71, 'bignum', base=>2 ], [ 'A266850', 71, 'bignum' ], [ 'A262448', 73, 'bits' ], [ 'A265122', 73, 'bignum', base=>2 ], [ 'A265156', 73, 'bignum' ], [ 'A265205', 73, 'number_of', value=>1 ], [ 'A265219', 73, 'number_of', value=>0 ], [ 'A265206', 73, 'number_of', value=>1, cumulative=>1 ], [ 'A265220', 73, 'number_of', value=>0, cumulative=>1 ], [ 'A266892', 75, 'bits' ], [ 'A266894', 75, 'bignum' ], [ 'A266893', 75, 'bignum', base=>2 ], [ 'A266895', 75, 'bits', part => 'centre' ], [ 'A266896', 75, 'bignum_central_column' ], [ 'A266897', 75, 'bignum_central_column', base=>2 ], [ 'A266900', 75, 'number_of', value=>0 ], [ 'A266898', 75, 'number_of', value=>1 ], [ 'A266899', 75, 'number_of', value=>1, cumulative=>1 ], [ 'A266901', 75, 'number_of', value=>0, cumulative=>1 ], [ 'A266872', 77, 'bignum', base=>2 ], [ 'A266873', 77, 'bignum' ], [ 'A266974', 78, 'bits' ], [ 'A266975', 78, 'bignum', base=>2 ], [ 'A266976', 78, 'bignum' ], [ 'A266977', 78, 'number_of', value=>1 ], [ 'A071023', 78, 'bits', part=>'left' ], [ 'A266978', 79, 'bits' ], [ 'A266979', 79, 'bignum', base=>2 ], [ 'A266980', 79, 'bignum' ], [ 'A266981', 79, 'number_of', value=>1 ], [ 'A266982', 81, 'bits' ], [ 'A266983', 81, 'bignum', base=>2 ], [ 'A266984', 81, 'bignum' ], [ 'A267001', 83, 'bits' ], [ 'A267002', 83, 'bignum', base=>2 ], [ 'A267003', 83, 'bignum' ], [ 'A267006', 84, 'bits' ], [ 'A267034', 85, 'bits' ], [ 'A267035', 85, 'bignum', base=>2 ], [ 'A267036', 85, 'bignum' ], # mirror image of rule 30 [ 'A071032', 86, 'bits' ], [ 'A265280', 86, 'bignum', base=>2 ], [ 'A265281', 86, 'bignum' ], [ 'A267037', 89, 'bits' ], [ 'A267038', 89, 'bignum', base=>2 ], [ 'A267039', 89, 'bignum' ], [ 'A265172', 90, 'bignum', base=>2 ], [ 'A001316', 90, 'number_of', value=>1 ], # Gould's sequence [ 'A071042', 90, 'number_of', value=>0 ], [ 'A267015', 91, 'bits' ], [ 'A267041', 91, 'bignum', base=>2 ], [ 'A267042', 91, 'bignum' ], [ 'A267043', 91, 'bits', part => 'centre' ], [ 'A267044', 91, 'bignum_central_column' ], [ 'A267045', 91, 'bignum_central_column', base=>2 ], [ 'A267048', 91, 'number_of', value=>0 ], [ 'A267046', 91, 'number_of', value=>1 ], [ 'A267047', 91, 'number_of', value=>1, cumulative=>1 ], [ 'A267049', 91, 'number_of', value=>0, cumulative=>1 ], [ 'A267050', 92, 'bits' ], [ 'A267051', 92, 'bignum', base=>2 ], [ 'A267052', 92, 'bignum' ], [ 'A071024', 92, 'bits', part=>'right' ], [ 'A267053', 93, 'bits' ], [ 'A267054', 93, 'bignum', base=>2 ], [ 'A267055', 93, 'bignum' ], [ 'A118102', 94, 'bits' ], [ 'A118101', 94, 'bignum' ], [ 'A071033', 94, 'bignum', base=>2 ], [ 'A265283', 94, 'number_of', value=>1 ], [ 'A265284', 94, 'number_of', value=>1, cumulative=>1 ], [ 'A267056', 97, 'bits' ], [ 'A267057', 97, 'bignum', base=>2 ], [ 'A267058', 97, 'bignum' ], [ 'A267126', 99, 'bits' ], [ 'A267127', 99, 'bignum', base=>2 ], [ 'A267128', 99, 'bignum' ], [ 'A267129', 101, 'bits' ], [ 'A267130', 101, 'bignum', base=>2 ], [ 'A267131', 101, 'bignum' ], [ 'A117998', 102, 'bignum' ], [ 'A265319', 102, 'bignum', base=>2 ], [ 'A267136', 103, 'bits' ], [ 'A267138', 103, 'bignum', base=>2 ], [ 'A267139', 103, 'bignum' ], [ 'A267145', 105, 'bits' ], [ 'A267146', 105, 'bignum', base=>2 ], [ 'A267147', 105, 'bignum' ], [ 'A267148', 105, 'number_of', value=>1 ], [ 'A267150', 105, 'number_of', value=>0 ], [ 'A267149', 105, 'number_of', value=>1, cumulative=>1 ], [ 'A267151', 105, 'number_of', value=>0, cumulative=>1 ], [ 'A267152', 107, 'bits' ], [ 'A267153', 107, 'bignum', base=>2 ], [ 'A267154', 107, 'bignum' ], [ 'A267155', 107, 'bits', part => 'centre' ], [ 'A267156', 107, 'bignum_central_column' ], [ 'A267157', 107, 'bignum_central_column', base=>2 ], [ 'A267160', 107, 'number_of', value=>0 ], [ 'A267158', 107, 'number_of', value=>1 ], [ 'A267159', 107, 'number_of', value=>1, cumulative=>1 ], [ 'A267161', 107, 'number_of', value=>0, cumulative=>1 ], [ 'A243566', 109, 'bits' ], [ 'A267206', 109, 'bignum', base=>2 ], [ 'A267207', 109, 'bignum' ], [ 'A267208', 109, 'bits', part => 'centre' ], [ 'A267209', 109, 'bignum_central_column' ], [ 'A267210', 109, 'bignum_central_column', base=>2 ], [ 'A267211', 109, 'number_of', value=>1 ], [ 'A267213', 109, 'number_of', value=>0 ], [ 'A267212', 109, 'number_of', value=>1, cumulative=>1 ], [ 'A267214', 109, 'number_of', value=>0, cumulative=>1 ], [ 'A075437', 110, 'bits' ], [ 'A117999', 110, 'bignum' ], [ 'A265320', 110, 'bignum', base=>2 ], [ 'A265322', 110, 'number_of', value=>0 ], [ 'A265321', 110, 'number_of', value=>1, cumulative=>1 ], [ 'A265323', 110, 'number_of', value=>0, cumulative=>1 ], [ 'A070887', 110, 'bits', part=>'left' ], [ 'A071049', 110, 'number_of', value=>1, initial=>[0] ], [ 'A267253', 111, 'bits' ], [ 'A267254', 111, 'bignum', base=>2 ], [ 'A267255', 111, 'bignum' ], [ 'A267256', 111, 'bits', part => 'centre' ], [ 'A267257', 111, 'bignum_central_column' ], [ 'A267258', 111, 'bignum_central_column', base=>2 ], [ 'A267259', 111, 'number_of', value=>1 ], [ 'A267261', 111, 'number_of', value=>0 ], [ 'A267260', 111, 'number_of', value=>1, cumulative=>1 ], [ 'A267262', 111, 'number_of', value=>0, cumulative=>1 ], [ 'A267269', 115, 'bits' ], [ 'A267270', 115, 'bignum', base=>2 ], [ 'A267271', 115, 'bignum' ], [ 'A267272', 117, 'bits' ], [ 'A267273', 117, 'bignum', base=>2 ], [ 'A267274', 117, 'bignum' ], [ 'A071034', 118, 'bits' ], [ 'A267275', 118, 'bignum', base=>2 ], [ 'A267276', 118, 'bignum' ], [ 'A267292', 121, 'bits' ], [ 'A267293', 121, 'bignum', base=>2 ], [ 'A267294', 121, 'bignum' ], [ 'A267349', 123, 'bits' ], [ 'A267350', 123, 'bignum', base=>2 ], [ 'A267351', 123, 'bignum' ], [ 'A267352', 123, 'number_of', value=>1 ], [ 'A267354', 123, 'number_of', value=>0 ], [ 'A267353', 123, 'number_of', value=>1, cumulative=>1 ], [ 'A267355', 124, 'bits' ], [ 'A267356', 124, 'bignum', base=>2 ], [ 'A267357', 124, 'bignum' ], [ 'A071025', 124, 'bits', part=>'right' ], [ 'A267358', 125, 'bits' ], [ 'A267359', 125, 'bignum', base=>2 ], [ 'A267360', 125, 'bignum' ], [ 'A071035', 126, 'bits' ], [ 'A267364', 126, 'bignum', base=>2 ], [ 'A267365', 126, 'bignum' ], [ 'A267366', 126, 'bignum_central_column' ], [ 'A267367', 126, 'bignum_central_column', base=>2 ], [ 'A071050', 126, 'number_of', value=>0 ], [ 'A071051', 126, 'number_of', value=>1 ], [ 'A267368', 126, 'number_of', value=>1, cumulative=>1 ], [ 'A267369', 126, 'number_of', value=>0, cumulative=>1 ], [ 'A267417', 129, 'bits' ], [ 'A267440', 129, 'bignum', base=>2 ], [ 'A267441', 129, 'bignum' ], [ 'A267442', 129, 'bits', part => 'centre' ], [ 'A267443', 129, 'bignum_central_column' ], [ 'A267444', 129, 'bignum_central_column', base=>2 ], [ 'A267445', 129, 'number_of', value=>1 ], [ 'A267447', 129, 'number_of', value=>0 ], [ 'A267446', 129, 'number_of', value=>1, cumulative=>1 ], [ 'A267448', 129, 'number_of', value=>0, cumulative=>1 ], [ 'A267418', 131, 'bits' ], [ 'A267449', 131, 'bignum', base=>2 ], [ 'A267450', 131, 'bignum' ], [ 'A267451', 131, 'number_of', value=>1 ], [ 'A267453', 131, 'number_of', value=>0 ], [ 'A267452', 131, 'number_of', value=>1, cumulative=>1 ], [ 'A267454', 131, 'number_of', value=>0, cumulative=>1 ], [ 'A267423', 133, 'bits' ], [ 'A267456', 133, 'bignum', base=>2 ], [ 'A267457', 133, 'bignum' ], [ 'A267458', 133, 'number_of', value=>1 ], [ 'A267460', 133, 'number_of', value=>0 ], [ 'A267459', 133, 'number_of', value=>1, cumulative=>1 ], [ 'A267461', 133, 'number_of', value=>0, cumulative=>1 ], # 111 110 101 100 011 010 001 000 # 1 0 0 0 0 1 1 1 [ 'A265695', 135, 'bits' ], [ 'A265697', 135, 'bignum' ], [ 'A265696', 135, 'bignum', base=>2 ], [ 'A265698', 135, 'bits', part => 'centre' ], [ 'A265699', 135, 'bignum_central_column' ], [ 'A265700', 135, 'bignum_central_column', base=>2 ], [ 'A265703', 135, 'number_of', value=>0 ], [ 'A265701', 135, 'number_of', value=>1 ], [ 'A265702', 135, 'number_of', value=>1, cumulative=>1 ], [ 'A265704', 135, 'number_of', value=>0, cumulative=>1 ], [ 'A071036', 150, 'bits' ], [ 'A038184', 150, 'bignum' ], [ 'A118110', 150, 'bignum', base=>2 ], # (previously also A245548) [ 'A038185', 150, 'bignum', part=>'left' ], # cut after central column [ 'A071053', 150, 'number_of', value=>1 ], [ 'A071052', 150, 'number_of', value=>0 ], [ 'A134659', 150, 'number_of', value=>1, cumulative=>1 ], [ 'A265223', 150, 'number_of', value=>0, cumulative=>1 ], [ 'A262866', 153, 'bignum' ], [ 'A262855', 153, 'bits' ], [ 'A262865', 153, 'bignum', part => 'centre', base=>2 ], [ 'A262867', 153, 'number_of', value=>1, cumulative=>1 ], [ 'A074330', 153, 'number_of', value=>0, cumulative=>1, y_start=>1 ], [ 'A071042', 153, 'number_of', value=>1, # cf rule 90 y_start=>1, initial=>[0] ], # sequence starts 0,... instead # [ 'A999999', 153, 'number_of', value=>0 ], # 2*A001316 [ 'A263243', 155, 'bits' ], [ 'A263244', 155, 'bignum', base=>2 ], [ 'A263245', 155, 'bignum' ], [ 'A263511', 155, 'number_of', value=>1, cumulative=>1 ], [ 'A071037', 158, 'bits' ], [ 'A118172', 158, 'bits' ], # duplicate [ 'A118171', 158, 'bignum' ], [ 'A265379', 158, 'bignum', base=>2 ], [ 'A265380', 158, 'bignum_central_column' ], [ 'A265381', 158, 'bignum_central_column', base=>2 ], [ 'A071054', 158, 'number_of', value=>1 ], [ 'A029578', 158, 'number_of', value=>0 ], [ 'A265382', 158, 'number_of', value=>1, cumulative=>1 ], [ 'A211538', 158, 'number_of', value=>0, cumulative=>1, initial=>[0] ], [ 'A267463', 137, 'bits' ], [ 'A267511', 137, 'bignum', base=>2 ], [ 'A267512', 137, 'bignum' ], [ 'A267513', 137, 'bits', part => 'centre' ], [ 'A267514', 137, 'bignum_central_column' ], [ 'A267515', 137, 'bignum_central_column', base=>2 ], [ 'A267516', 137, 'number_of', value=>1 ], [ 'A267518', 137, 'number_of', value=>0 ], [ 'A267517', 137, 'number_of', value=>1, cumulative=>1 ], [ 'A267519', 137, 'number_of', value=>0, cumulative=>1 ], [ 'A267520', 139, 'bits' ], [ 'A267523', 139, 'bignum', base=>2 ], [ 'A267524', 139, 'bignum_central_column' ], [ 'A267525', 141, 'bits' ], [ 'A267526', 141, 'bignum', base=>2 ], [ 'A267527', 141, 'bignum' ], [ 'A267528', 141, 'number_of', value=>1 ], [ 'A267530', 141, 'number_of', value=>0 ], [ 'A267529', 141, 'number_of', value=>1, cumulative=>1 ], [ 'A267531', 141, 'number_of', value=>0, cumulative=>1 ], [ 'A267533', 143, 'bits' ], [ 'A267535', 143, 'bignum', base=>2 ], [ 'A267536', 143, 'bignum' ], [ 'A267537', 143, 'bits', part => 'centre' ], [ 'A267538', 143, 'bignum_central_column' ], [ 'A267539', 143, 'bignum_central_column', base=>2 ], [ 'A262805', 145, 'bits' ], [ 'A262860', 145, 'bignum' ], [ 'A262859', 145, 'bignum', base=>2 ], [ 'A262808', 147, 'bits' ], [ 'A262862', 147, 'bignum' ], [ 'A262861', 147, 'bignum', base=>2 ], [ 'A262864', 147, 'bignum_central_column', base=>2 ], [ 'A262863', 147, 'bignum_central_column' ], [ 'A265246', 149, 'bits' ], # [ 'A226464', 149, 'bits' ], # no, this started from single 0 [ 'A265717', 149, 'bignum' ], [ 'A265715', 149, 'bignum', base=>2 ], [ 'A070909', 156, 'bits', part=>'right' ], [ 'A263804', 157, 'bits' ], [ 'A263806', 157, 'bignum' ], [ 'A263805', 157, 'bignum', base=>2 ], [ 'A263807', 157, 'number_of', value=>1, cumulative=>1 ], [ 'A263919', 163, 'bits' ], [ 'A266753', 163, 'bignum' ], [ 'A266752', 163, 'bignum', base=>2 ], [ 'A266754', 165, 'bits' ], [ 'A267246', 165, 'bignum', base=>2 ], [ 'A267247', 165, 'bignum' ], [ 'A267576', 167, 'bits' ], [ 'A267577', 167, 'bignum', base=>2 ], [ 'A267578', 167, 'bignum' ], [ 'A267579', 167, 'bits', part => 'centre' ], [ 'A267580', 167, 'bignum_central_column' ], [ 'A267581', 167, 'bignum_central_column', base=>2 ], [ 'A267582', 167, 'number_of', value=>1 ], [ 'A267583', 167, 'number_of', value=>1, cumulative=>1 ], [ 'A264442', 169, 'bits' ], [ 'A267585', 169, 'bignum', base=>2 ], [ 'A267586', 169, 'bignum' ], [ 'A267587', 169, 'bits', part => 'centre' ], [ 'A267588', 169, 'bignum_central_column' ], [ 'A267589', 169, 'bignum_central_column', base=>2 ], [ 'A267590', 169, 'number_of', value=>1 ], [ 'A267592', 169, 'number_of', value=>0 ], [ 'A267591', 169, 'number_of', value=>1, cumulative=>1 ], [ 'A267593', 169, 'number_of', value=>0, cumulative=>1 ], [ 'A267594', 173, 'bits' ], [ 'A267595', 173, 'bignum', base=>2 ], [ 'A267596', 173, 'bignum' ], [ 'A265186', 175, 'bits' ], [ 'A262779', 175, 'bignum', base=>2 ], [ 'A266678', 175, 'bits', part=>'centre' ], [ 'A266680', 175, 'bignum_central_column' ], [ 'A267604', 175, 'bignum_central_column', base=>2 ], [ 'A267598', 177, 'bits' ], [ 'A267599', 177, 'bignum', base=>2 ], [ 'A267605', 181, 'bits' ], [ 'A267606', 181, 'bignum', base=>2 ], [ 'A267607', 181, 'bignum' ], [ 'A071038', 182, 'bits' ], [ 'A267608', 182, 'bignum', base=>2 ], [ 'A267609', 182, 'bignum' ], [ 'A071055', 182, 'number_of', value=>0 ], [ 'A267610', 182, 'number_of', value=>0, cumulative=>1 ], [ 'A267612', 185, 'bits' ], [ 'A267613', 185, 'bignum', base=>2 ], [ 'A267614', 185, 'bignum' ], [ 'A267621', 187, 'bits' ], [ 'A267622', 187, 'bignum', base=>2 ], [ 'A267623', 187, 'bignum_central_column' ], [ 'A118174', 188, 'bits' ], [ 'A118173', 188, 'bignum' ], [ 'A265427', 188, 'bignum', base=>2 ], [ 'A071026', 188, 'bits', part=>'right' ], [ 'A265428', 188, 'number_of', value=>1 ], [ 'A265430', 188, 'number_of', value=>0 ], [ 'A265429', 188, 'number_of', value=>1, cumulative=>1 ], [ 'A265431', 188, 'number_of', value=>0, cumulative=>1 ], [ 'A267635', 189, 'bits' ], [ 'A118111', 190, 'bits' ], [ 'A071039', 190, 'bits' ], # dupliate [ 'A037576', 190, 'bignum' ], [ 'A265688', 190, 'bignum', base=>2 ], [ 'A032766', 190, 'number_of', value=>1, initial=>[0] ], [ 'A004526', 190, 'number_of', value=>0 ], [ 'A006578', 190, 'number_of', value=>1, cumulative=>1, initial=>[0] ], [ 'A002620', 190, 'number_of', value=>0, cumulative=>1 ], [ 'A166486', 190, 'bits', part => 'centre', initial=>[0] ], # rep 1,1,1,0 [ 'A265380', 190, 'bignum_central_column' ], # same rule 158 [ 'A265381', 190, 'bignum_central_column', base=>2 ], # [ 'A267636', 193, 'bits' ], [ 'A267645', 193, 'bignum', base=>2 ], [ 'A267646', 193, 'bignum' ], [ 'A267673', 195, 'bits' ], [ 'A267674', 195, 'bignum', base=>2 ], [ 'A267675', 195, 'bignum' ], # counts same as 141, bits different [ 'A267676', 197, 'bits' ], [ 'A267677', 197, 'bignum', base=>2 ], [ 'A267678', 197, 'bignum' ], [ 'A267528', 197, 'number_of', value=>1 ], [ 'A267530', 197, 'number_of', value=>0 ], [ 'A267529', 197, 'number_of', value=>1, cumulative=>1 ], [ 'A267531', 197, 'number_of', value=>0, cumulative=>1 ], [ 'A267687', 199, 'bits' ], [ 'A267688', 199, 'bignum', base=>2 ], [ 'A267689', 199, 'bignum' ], [ 'A267679', 201, 'bits' ], [ 'A267680', 201, 'bignum', base=>2 ], [ 'A267681', 201, 'bignum' ], [ 'A267682', 201, 'number_of', cumulative=>1 ], [ 'A267683', 203, 'bits' ], [ 'A267684', 203, 'bignum', base=>2 ], [ 'A267685', 203, 'bignum' ], [ 'A267704', 205, 'bits' ], [ 'A267705', 205, 'bignum', base=>2 ], [ 'A267708', 206, 'bits' ], [ 'A109241', 206, 'bignum', base=>2 ], [ 'A267773', 207, 'bits' ], [ 'A267774', 207, 'bignum' ], [ 'A267775', 207, 'bignum', base=>2 ], [ 'A267776', 209, 'bits' ], [ 'A267777', 209, 'bignum', base=>2 ], [ 'A267778', 211, 'bits' ], [ 'A267779', 211, 'bignum', base=>2 ], [ 'A267780', 211, 'bignum' ], [ 'A267800', 213, 'bits' ], [ 'A267801', 213, 'bignum', base=>2 ], [ 'A267802', 213, 'bignum' ], [ 'A071040', 214, 'bits' ], [ 'A267805', 214, 'bignum' ], [ 'A267804', 214, 'bignum', base=>2 ], [ 'A267810', 217, 'bits' ], [ 'A267811', 217, 'bignum', base=>2 ], [ 'A267812', 217, 'bignum' ], [ 'A267813', 219, 'bits' ], [ 'A267814', 221, 'bits' ], [ 'A267815', 221, 'bignum', base=>2 ], [ 'A267816', 221, 'bignum' ], [ 'A267841', 225, 'bits' ], [ 'A267842', 225, 'bignum', base=>2 ], [ 'A267843', 225, 'bignum' ], [ 'A078176', 225, 'bignum', part=>'whole', ystart=>1, inverse=>1 ], [ 'A267845', 227, 'bits' ], [ 'A267846', 227, 'bignum', base=>2 ], [ 'A267847', 227, 'bignum' ], [ 'A267848', 229, 'bits' ], [ 'A267850', 229, 'bignum', base=>2 ], [ 'A267851', 229, 'bignum' ], [ 'A267853', 230, 'bits' ], [ 'A267855', 230, 'bignum' ], [ 'A267854', 230, 'bignum', base=>2 ], [ 'A071027', 230, 'bits', part=>'left' ], [ 'A006977', 230, 'bignum', part=>'left' ], [ 'A267866', 231, 'bits' ], [ 'A267867', 231, 'bignum', base=>2 ], [ 'A267868', 233, 'bits' ], [ 'A267877', 233, 'bignum' ], [ 'A267876', 233, 'bignum', base=>2 ], [ 'A267878', 233, 'bits', part => 'centre' ], [ 'A267879', 233, 'bignum_central_column' ], [ 'A267880', 233, 'bignum_central_column', base=>2 ], [ 'A267881', 233, 'number_of', value=>1 ], [ 'A267883', 233, 'number_of', value=>0 ], [ 'A267882', 233, 'number_of', value=>1, cumulative=>1 ], [ 'A267884', 233, 'number_of', value=>0, cumulative=>1 ], [ 'A267869', 235, 'bits' ], [ 'A267885', 235, 'bignum', base=>2 ], [ 'A267886', 235, 'bignum' ], [ 'A267873', 235, 'number_of', value=>1 ], [ 'A267874', 235, 'number_of', value=>1, cumulative=>1 ], # 0s are fixed 0,1,2 [ 'A267870', 237, 'bits' ], [ 'A267888', 237, 'bignum' ], [ 'A267887', 237, 'bignum', base=>2 ], [ 'A267872', 237, 'number_of', value=>1 ], [ 'A267871', 239, 'bits' ], [ 'A267889', 239, 'bignum', base=>2 ], [ 'A267890', 239, 'bignum' ], [ 'A267919', 243, 'bits' ], [ 'A267920', 243, 'bignum', base=>2 ], [ 'A267921', 243, 'bignum' ], [ 'A267922', 245, 'bits' ], [ 'A267923', 245, 'bignum', base=>2 ], [ 'A267924', 245, 'bignum' ], [ 'A071041', 246, 'bits' ], [ 'A267926', 246, 'bignum' ], [ 'A267925', 246, 'bignum', base=>2 ], [ 'A267927', 249, 'bits' ], [ 'A267934', 249, 'bignum', base=>2 ], [ 'A267935', 249, 'bignum' ], [ 'A002450', 250, 'bignum', initial=>[0] ], # (4^n-1)/3 10101 extra 0 start [ 'A267936', 251, 'bits' ], [ 'A267937', 251, 'bignum', base=>2 ], [ 'A267938', 251, 'bignum' ], [ 'A118175', 252, 'bits' ], [ 'A267940', 253, 'bignum', base=>2 ], [ 'A267941', 253, 'bignum' ], # [ 'A060576', 255, 'bits' ], # homeomorphically irreducibles ... [ 'A071022', 198, 'bits', part=>'left' ], # right half solid 2^n-1 [ 'A118175', 220, 'bits' ], [ 'A000225', 220, 'bignum', initial=>[0] ], # 2^n-1 want start from 1 [ 'A000042', 220, 'bignum', base=>2 ], # half-width 1s [ 'A071048', 110, 'number_of', value=>0, part=>'left' ], #-------------------------------------------------------------------------- # Sierpinski triangle, 8 of whole # rule=60 right half [ 'A047999', 60, 'bits', part=>'right' ], # Sierpinski triangle in right [ 'A001317', 60, 'bignum' ], # Sierpinski triangle right half [ 'A075438', 60, 'bits' ], # including 0s in left half # rule=102 left half [ 'A047999', 102, 'bits', part=>'left' ], [ 'A075439', 102, 'bits' ], [ 'A038183', 18, 'bignum' ], # Sierpinski bignums [ 'A038183', 26, 'bignum' ], [ 'A038183', 82, 'bignum' ], [ 'A038183', 90, 'bignum' ], [ 'A038183', 146, 'bignum' ], [ 'A038183', 154, 'bignum' ], [ 'A038183', 210, 'bignum' ], [ 'A038183', 218, 'bignum' ], [ 'A070886', 18, 'bits' ], # Sierpinski 0/1 [ 'A070886', 26, 'bits' ], [ 'A070886', 82, 'bits' ], [ 'A070886', 90, 'bits' ], [ 'A070886', 146, 'bits' ], [ 'A070886', 154, 'bits' ], [ 'A070886', 210, 'bits' ], [ 'A070886', 218, 'bits' ], #-------------------------------------------------------------------------- # simple stuff # whole solid, decimal repunits [ 'A100706', 151, 'bignum', base=>2 ], # whole solid, values 2^(2n)-1 [ 'A083420', 151, 'bignum' ], # 8 of [ 'A083420', 159, 'bignum' ], [ 'A083420', 183, 'bignum' ], [ 'A083420', 191, 'bignum' ], [ 'A083420', 215, 'bignum' ], [ 'A083420', 223, 'bignum' ], [ 'A083420', 247, 'bignum' ], [ 'A083420', 254, 'bignum' ], # and also [ 'A083420', 222, 'bignum' ], # 2 of [ 'A083420', 255, 'bignum' ], # right half solid 2^n-1 [ 'A000225', 252, 'bignum', initial=>[0] ], # left half solid, # 2^n-1 [ 'A000225', 206, 'bignum', part=>'left', initial=>[0] ], # 0xCE [ 'A000225', 238, 'bignum', part=>'left', initial=>[0] ], # 0xEE # central column only, values all 1s [ 'A000012', 4, 'bignum', part=>'left' ], [ 'A000012', 12, 'bignum', part=>'left' ], [ 'A000012', 36, 'bignum', part=>'left' ], [ 'A000012', 44, 'bignum', part=>'left' ], [ 'A000012', 68, 'bignum', part=>'left' ], [ 'A000012', 76, 'bignum', part=>'left' ], [ 'A000012', 100, 'bignum', part=>'left' ], [ 'A000012', 108, 'bignum', part=>'left' ], [ 'A000012', 132, 'bignum', part=>'left' ], [ 'A000012', 140, 'bignum', part=>'left' ], [ 'A000012', 164, 'bignum', part=>'left' ], [ 'A000012', 172, 'bignum', part=>'left' ], [ 'A000012', 196, 'bignum', part=>'left' ], [ 'A000012', 204, 'bignum', part=>'left' ], [ 'A000012', 228, 'bignum', part=>'left' ], [ 'A000012', 236, 'bignum', part=>'left' ], # # central column only, central values N=1,2,3,etc all integers [ 'A000027', 4, 'central_column_N' ], [ 'A000027', 12, 'central_column_N' ], [ 'A000027', 36, 'central_column_N' ], [ 'A000027', 44, 'central_column_N' ], [ 'A000027', 76, 'central_column_N' ], [ 'A000027', 108, 'central_column_N' ], [ 'A000027', 132, 'central_column_N' ], [ 'A000027', 140, 'central_column_N' ], [ 'A000027', 164, 'central_column_N' ], [ 'A000027', 172, 'central_column_N' ], [ 'A000027', 196, 'central_column_N' ], [ 'A000027', 204, 'central_column_N' ], [ 'A000027', 228, 'central_column_N' ], [ 'A000027', 236, 'central_column_N' ], # # central column only, values 2^k [ 'A000079', 4, 'bignum' ], [ 'A000079', 12, 'bignum' ], [ 'A000079', 36, 'bignum' ], [ 'A000079', 44, 'bignum' ], [ 'A000079', 68, 'bignum' ], [ 'A000079', 76, 'bignum' ], [ 'A000079', 100, 'bignum' ], [ 'A000079', 108, 'bignum' ], [ 'A000079', 132, 'bignum' ], [ 'A000079', 140, 'bignum' ], [ 'A000079', 164, 'bignum' ], [ 'A000079', 172, 'bignum' ], [ 'A000079', 196, 'bignum' ], [ 'A000079', 204, 'bignum' ], [ 'A000079', 228, 'bignum' ], [ 'A000079', 236, 'bignum' ], # right diagonal only, values all 1, 16 of [ 'A000012', 0x10, 'bignum' ], [ 'A000012', 0x18, 'bignum' ], [ 'A000012', 0x30, 'bignum' ], [ 'A000012', 0x38, 'bignum' ], [ 'A000012', 0x50, 'bignum' ], [ 'A000012', 0x58, 'bignum' ], [ 'A000012', 0x70, 'bignum' ], [ 'A000012', 0x78, 'bignum' ], [ 'A000012', 0x90, 'bignum' ], [ 'A000012', 0x98, 'bignum' ], [ 'A000012', 0xB0, 'bignum' ], [ 'A000012', 0xB8, 'bignum' ], [ 'A000012', 0xD0, 'bignum' ], [ 'A000012', 0xD8, 'bignum' ], [ 'A000012', 0xF0, 'bignum' ], [ 'A000012', 0xF8, 'bignum' ], # left diagonal only, values 2^k [ 'A000079', 0x02, 'bignum', part=>'left' ], [ 'A000079', 0x0A, 'bignum', part=>'left' ], [ 'A000079', 0x22, 'bignum', part=>'left' ], [ 'A000079', 0x2A, 'bignum', part=>'left' ], [ 'A000079', 0x42, 'bignum', part=>'left' ], [ 'A000079', 0x4A, 'bignum', part=>'left' ], [ 'A000079', 0x62, 'bignum', part=>'left' ], [ 'A000079', 0x6A, 'bignum', part=>'left' ], [ 'A000079', 0x82, 'bignum', part=>'left' ], [ 'A000079', 0x8A, 'bignum', part=>'left' ], [ 'A000079', 0xA2, 'bignum', part=>'left' ], [ 'A000079', 0xAA, 'bignum', part=>'left' ], [ 'A000079', 0xC2, 'bignum', part=>'left' ], [ 'A000079', 0xCA, 'bignum', part=>'left' ], [ 'A000079', 0xE2, 'bignum', part=>'left' ], [ 'A000079', 0xEA, 'bignum', part=>'left' ], # bits, characteristic of square [ 'A010052', 0x02, 'bits' ], [ 'A010052', 0x0A, 'bits' ], [ 'A010052', 0x22, 'bits' ], [ 'A010052', 0x2A, 'bits' ], [ 'A010052', 0x42, 'bits' ], [ 'A010052', 0x4A, 'bits' ], [ 'A010052', 0x62, 'bits' ], [ 'A010052', 0x6A, 'bits' ], [ 'A010052', 0x82, 'bits' ], [ 'A010052', 0x8A, 'bits' ], [ 'A010052', 0xA2, 'bits' ], [ 'A010052', 0xAA, 'bits' ], [ 'A010052', 0xC2, 'bits' ], [ 'A010052', 0xCA, 'bits' ], [ 'A010052', 0xE2, 'bits' ], [ 'A010052', 0xEA, 'bits' ], ); # { # require Data::Dumper; # foreach my $i (0 .. $#data) { # my $e1 = $data[$i]; # my @a1 = @$e1; shift @a1; # my $a1 = Data::Dumper->Dump([\@a1],['args']); # ### $e1 # ### @a1 # ### $a1 # foreach my $j ($i+1 .. $#data) { # my $e2 = $data[$j]; # my @a2 = @$e2; shift @a2; # my $a2 = Data::Dumper->Dump([\@a2],['args']); # # if ($a1 eq $a2) { # print "duplicate $e1->[0] = $e2->[0] params $a1\n"; # } # } # } # } if (0) { my @seen; my $prev = $data[0]->[1]; foreach my $elem (@data) { my ($anum, $rule, $method, @params) = @$elem; if ($rule != $prev && $seen[$rule]) { warn "rule $rule second block, method=$method"; } $seen[$rule] = 1; $prev = $rule; } } foreach my $elem (@data) { ### $elem my ($anum, $rule, $method, @params) = @$elem; my $func = main->can($method) || die "Unrecognised method $method"; &$func ($anum, $rule, @params); } #------------------------------------------------------------------------------ # number of 0s or 1s in row sub number_of { my ($anum, $rule, %params) = @_; my $part = $params{'part'} || 'whole'; my $want_value = $params{'value'}; if (! defined $want_value) { $want_value = 1; } my $max_count = $params{'max_count'} || 100; MyOEIS::compare_values (anum => $anum, name => "$anum number of ${want_value}s in rows rule $rule, $part", max_count => $max_count, func => sub { my ($count) = @_; return number_of_make_values($count, $anum, $rule, %params); }); } sub number_of_1s_first_diff { my ($anum, $rule, %params) = @_; my $max_count = $params{'max_count'}; MyOEIS::compare_values (anum => $anum, name => "$anum number of 1s first differences", max_count => $max_count, func => sub { my ($count) = @_; my $aref = number_of_make_values($count+1, $anum, $rule, %params); return [ MyOEIS::first_differences(@$aref) ]; }); } sub number_of_make_values { my ($count, $anum, $rule, %params) = @_; my $initial = $params{'initial'} || []; my $y_start = $params{'y_start'} // 0; my $part = $params{'part'} || 'whole'; my $want_value = $params{'value'}; if (! defined $want_value) { $want_value = 1; } my $max_count = $params{'max_count'}; my $path = Math::PlanePath::CellularRule->new (rule => $rule); my @got = @$initial; my $number_of = 0; for (my $y = $y_start; @got < $count; $y++) { unless ($params{'cumulative'}) { $number_of = 0 } foreach my $x (($part eq 'right' || $part eq 'centre' ? 0 : -$y) .. ($part eq 'left' || $part eq 'centre' ? 0 : $y)) { my $n = $path->xy_to_n ($x, $y); my $got_value = (defined $n ? 1 : 0); if ($got_value == $want_value) { $number_of++; } } push @got, $number_of; } return \@got; } #------------------------------------------------------------------------------ # number of runs (or blocks) of value 0 or 1 sub number_of_runs { my ($anum, $rule, %params) = @_; my $want_value = $params{'value'}; my $max_count = $params{'max_count'} || 100; MyOEIS::compare_values (anum => $anum, name => "$anum number of runs in rows rule $rule" . (defined $want_value ? ", value $want_value" : ""), max_count => $max_count, func => sub { my ($count) = @_; my $path = Math::PlanePath::CellularRule->new (rule => $rule); my @got; for (my $y = 0; @got < $count; $y++) { my $prev = -1; my $number_of_runs = 0; foreach my $x (-$y .. $y) { my $n = $path->xy_to_n ($x, $y); my $got_value = (defined $n ? 1 : 0); if ((! defined $want_value || $got_value == $want_value) && $got_value != $prev) { $number_of_runs++; } $prev = $got_value; } push @got, $number_of_runs; } return \@got; }); } sub longest_run { my ($anum, $rule, %params) = @_; my $want_value = $params{'value'}; if (! defined $want_value) { $want_value = 1; } my $max_count = $params{'max_count'} || 100; MyOEIS::compare_values (anum => $anum, name => "$anum number of ${want_value}s in rows rule $rule", max_count => $max_count, func => sub { my ($count) = @_; my $path = Math::PlanePath::CellularRule->new (rule => $rule); my @got; for (my $y = 0; @got < $count; $y++) { my $longest = 0; my $len = 0; foreach my $x (-$y .. $y) { my $n = $path->xy_to_n ($x, $y); my $got_value = (defined $n ? 1 : 0); if ($got_value == $want_value) { $len++; } else { if ($len) { $longest = max($longest, $len); } $len = 0; } } push @got, $longest; } return \@got; }); } #------------------------------------------------------------------------------ # number of 0s or 1s in row at the rightmost end sub trailing_number_of { my ($anum, $rule, %params) = @_; my $initial = $params{'initial'} || []; my $part = $params{'part'} || 'whole'; my $want_value = $params{'value'}; if (! defined $want_value) { $want_value = 1; } MyOEIS::compare_values (anum => $anum, name => "$anum trailing number of ${want_value}s in rows rule $rule", func => sub { my ($count) = @_; my $path = Math::PlanePath::CellularRule->new (rule => $rule); my @got = @$initial; for (my $y = 0; @got < $count; $y++) { my $number_of = 0; for (my $x = $y; $x >= -$y; $x--) { my $n = $path->xy_to_n ($x, $y); my $got_value = (defined $n ? 1 : 0); if ($got_value == $want_value) { $number_of++; } else { last; } } push @got, $number_of; } return \@got; }); } sub new_maximum_trailing_number_of { my ($anum, $rule, $want_value) = @_; my $path = Math::PlanePath::CellularRule->new (rule => $rule); my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my @got; if ($bvalues) { MyTestHelpers::diag ("$anum new_maximum_trailing_number_of"); if ($anum eq 'A094604') { # new max only at Y=2^k, so limit search if ($#$bvalues > 10) { $#$bvalues = 10; } } my $prev = 0; for (my $y = 0; @got < @$bvalues; $y++) { my $count = 0; for (my $x = $y; $x >= -$y; $x--) { my $n = $path->xy_to_n ($x, $y); my $got_value = (defined $n ? 1 : 0); if ($got_value == $want_value) { $count++; } else { last; } } if ($count > $prev) { push @got, $count; $prev = $count; } } if (! streq_array(\@got, $bvalues)) { MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..20])); MyTestHelpers::diag ("got: ",join(',',@got[0..20])); } } skip (! $bvalues, streq_array(\@got, $bvalues), 1, "$anum"); } #------------------------------------------------------------------------------ # bignum rows sub bignum { my ($anum, $rule, %params) = @_; my $part = $params{'part'} || 'whole'; my $initial = $params{'initial'} || []; my $ystart = $params{'ystart'} || 0; my $inverse = $params{'inverse'} ? 1 : 0; # for bitwise invert my $base = $params{'base'} || 10; my $max_count = $params{'max_count'}; # if ($anum eq 'A000012') { # trim all-ones # if ($#$bvalues > 50) { $#$bvalues = 50; } # } MyOEIS::compare_values (anum => $anum, name => "$anum bignums $part, inverse=$inverse", max_count => $max_count, func => sub { my ($count) = @_; my $path = Math::PlanePath::CellularRule->new (rule => $rule); my @got = @$initial; for (my $y = $ystart; @got < $count; $y++) { my $b = Math::BigInt->new(0); foreach my $x (($part eq 'right' ? 0 : -$y) .. ($part eq 'left' ? 0 : $y)) { my $bit = ($path->xy_is_visited($x,$y) ? 1 : 0); if ($inverse) { $bit ^= 1; } $b = 2*$b + $bit; } if ($base == 2) { $b = $b->as_bin; $b =~ s/^0b//; } push @got, "$b"; } return \@got; }); } #------------------------------------------------------------------------------ # 0/1 by rows sub bits { my ($anum, $rule, %params) = @_; ### bits(): @_ my $part = $params{'part'} || 'whole'; my $initial = $params{'initial'} || []; MyOEIS::compare_values (anum => $anum, name => "$anum 0/1 rows rule $rule, $part", func => sub { my ($count) = @_; my $path = Math::PlanePath::CellularRule->new (rule => $rule); my @got = @$initial; OUTER: for (my $y = 0; ; $y++) { foreach my $x (($part eq 'right' || $part eq 'centre' ? 0 : -$y) .. ($part eq 'left' || $part eq 'centre' ? 0 : $y)) { last OUTER if @got >= $count; my $cell = $path->xy_is_visited ($x,$y) ? 1 : 0; if ($params{'complement'}) { $cell = 1-$cell; } push @got, $cell; } } return \@got; }); } #------------------------------------------------------------------------------ # bignum central vertical column in decimal sub bignum_central_column { my ($anum, $rule, %params) = @_; my $base = $params{'base'} || 10; MyOEIS::compare_values (anum => $anum, name => "$anum central column bignum, decimal", func => sub { my ($count) = @_; my $path = Math::PlanePath::CellularRule->new (rule => $rule); my @got; my $b = Math::BigInt->new(0); for (my $y = 0; @got < $count; $y++) { my $bit = ($path->xy_to_n (0, $y) ? 1 : 0); $b = $base*$b + $bit; push @got, "$b"; } return \@got; }); } #------------------------------------------------------------------------------ # N values of central vertical column sub central_column_N { my ($anum, $rule) = @_; MyOEIS::compare_values (anum => $anum, name => "$anum central column N", func => sub { my ($count) = @_; my $path = Math::PlanePath::CellularRule->new (rule => $rule); my @got; for (my $y = 0; @got < $count; $y++) { push @got, $path->xy_to_n (0, $y); } return \@got; }); } #------------------------------------------------------------------------------ # A071029 rule 22 ... ? # # 22 = 00010110 # 111 -> 0 # 110 -> 0 # 101 -> 0 # 100 -> 1 # 011 -> 0 # 010 -> 1 # 001 -> 1 # 000 -> 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, # 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, # 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, # 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 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, # 1, # 0, 1, 0, # 1, 0, 1, 0, 1, # 0, 1, 0, 1, 0, 1, 0, # 1, 0, 1, 0, 1, 0, 1, 0, 1, # 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, # 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, # 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, # 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 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, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0, 1, # 0 # A071043 Number of 0's in n-th row of triangle in A071029. # 0, 0, 3, 1, 7, 5, 9, 3, 15, 13, 17, 11, 21, 15, 21, 7, 31, 29, 33, 27, # 37, 31, 37, 23, 45, 39, 45, 31, 49, 35, 45, 15, 63, 61, 65, 59, 69, 63, # 69, 55, 77, 71, 77, 63, 81, 67, 77, 47, 93, 87, 93, 79, 97, 83, 93, 63, # 105, 91, 101, 71, 105, 75, 93, 31, 127, 125, 129 # # A071044 Number of 1's in n-th row of triangle in A071029. # 1, 3, 2, 6, 2, 6, 4, 12, 2, 6, 4, 12, 4, 12, 8, 24, 2, 6, 4, 12, 4, 12, # 8, 24, 4, 12, 8, 24, 8, 24, 16, 48, 2, 6, 4, 12, 4, 12, 8, 24, 4, 12, # 8, 24, 8, 24, 16, 48, 4, 12, 8, 24, 8, 24, 16, 48, 8, 24, 16, 48, 16, # 48, 32, 96, 2, 6, 4, 12, 4, 12, 8, 24, 4, 12, 8, 24, 8, 24, 16, 48 # # *** *** *** *** # * * * * # *** *** # * * # *** *** # * * # *** # * #------------------------------------------------------------------------------ # A071026 rule 188 # rows n+1 # # 1, # 1, 0, # 0, 1, 1, # 0, 1, 0, 1, # 1, 1, 1, 1, 0, # 0, 0, 1, 1, 0, 1, # 1, 1, 1, 1, 1, 1, 1, # 1, 0, 1, 1, 0, 0, 1, 1, # 1, 1, 0, 0, 0, 0, 0, 0, 1, # 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, # 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, # 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, # 0, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, # 0, 1, 1, 1, 0, 1, 1, 0 # # * *** * # ** *** # *** * # **** # * * # ** # * #------------------------------------------------------------------------------ # A071023 rule 78 # *** * * * # ** * * * # *** * * # ** * * # *** * # ** * # *** # ** # * # 1, 1, 1, # 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, # 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, # 1, 1, 1, # 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, # 1, 1, 1, 1, 1, 1, 1, 1, 1, # 0, 1, 1, 1, 1, # 0, 1, 1, 1, # 0, 1, 0, # 1, 1, 1 # 111 -> # 110 -> # 101 -> # 100 -> # 011 -> # 010 -> 1 # 001 -> 1 # 000 -> # 1, # 1, 1, # 0, 1, 0, # 1, 0, 1, 0, # 1, 0, 1, 0, 1, # 0, 1, 0, 1, 0, 1, # 0, 1, 0, 1, 1, 0, 1, # 0, 1, 0, 1, 0, 1, 0, 1, # 0, 1, 0, 1, 0, 1, 0, 1, 0, # 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, # 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, # 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, # 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, # 1, 1, 0, 1, 0, 1, 1, 1 #------------------------------------------------------------------------------ # A071024 rule 92 # 0, 1, 0, 1, 0, # 1, 1, 1, # 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, # 1, 1, 1, 1, # 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, # 1, 1, 1, # 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, # 1, 1, 1, 1, # 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0 #------------------------------------------------------------------------------ # A071027 rule 230 # * *** *** * # *** *** ** # * *** *** # *** **** # * *** * # *** ** # * *** # **** # * * # ** # * # 1, 1, 1, 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 1, 1, 0, # 1 #------------------------------------------------------------------------------ # # A071035 rule 126 sierpinski # # 1, # 1, 0, 1, # 1, 0, 1, # 1, 0, 0, 0, 1, # 1, 1, 1, 0, 1, 0, 1, 1, 1, # 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, # 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, # 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, # 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0 #------------------------------------------------------------------------------ # A071022 rule 70,198 # ** * * * * # * * * * * # ** * * * # * * * * # ** * * # * * * # ** * # * * # ** # * # 1, 0, # 1, 0, # 1, 0, # 1, 0, # 1, 0, # 1, 0, # 1, 0, # 1, 0, # 1, 0, # 1, 0, # 1, 0, # 1, 0, # 1, 1, 1, 1, 1, 1, 0, # 1, 1, 1, 0, # 1, 1, 0, # 1, 0, # 1, 1, 1, 0, # 1, 0, # 1, 1, 0, # 1, 0, # 1, 0, # 1, 1, 1, 0, # 1, 0, # 1, 0, # 1, 1, 0, # 1, 0, # 1, 0, # 1, 0, # 1, 1, 1, 0, # 1, 0, # 1, 0, # 1, 0, # 1, 1, 0, # 1, 0, # 1, 0, # 1, 0, # 1, 0, # 1, 1, 1, 0, # 1, 0, # 1, 0 #------------------------------------------------------------------------------ # A071030 - rule 54, rows 2n+1 # 0, # 1, 0, 1, # 0, 1, 0, 1, 0, # 1, 0, 1, 0, 1, 0, 1, # 0, 1, 0, 1, 0, 1, 0, 1, 0, # 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, # 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, # 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, # 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, # 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0 #------------------------------------------------------------------------------ # A071039 rule 190, rows 2n+1 # 1, # 0, 1, 0, # 1, 1, 1, 1, 1, # 0, 1, 0, 1, 0, 1, 0, # 1, 0, 1, 0, 1, 0, 1, 1, 1, # 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, # 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, # 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, # 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, # 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1 #------------------------------------------------------------------------------ # A071036 rule 150 # ** ** *** ** ** # * * * * * # *** *** *** # * * * # ** * ** # * * * # *** # * # 1, # 0, 1, 1, # 0, 1, 1, 0, 0, # 0, 1, 1, 1, 1, 0, 1, # 0, 1, 1, 0, 0, 0, 1, 1, 1, # 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, # 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, # 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, # 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, # 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1 #------------------------------------------------------------------------------ # A071022 rule 70,198 # A071023 rule 78 # A071024 rule 92 # A071025 rule 124 # A071026 rule 188 # A071027 rule 230 # A071028 rule 50 ok # A071029 rule 22 # A071030 rule 54 -- cf A118108 bignum A118109 binary bignum # A071031 rule 62 # A071032 rule 86 # A071033 rule 94 # A071034 rule 118 # A071035 rule 126 sierpinski # A071036 rule 150 # A071037 rule 158 # A071038 rule 182 # A071039 rule 190 # A071040 rule 214 # A071041 rule 246 # # A071042 num 0s in A070886 rule 90 sierpinski ok # A071043 num 0s in A071029 rule 22 ok # A071044 num 1s in A071029 rule 22 ok # A071045 num 0s in A071030 rule 54 ok # A071046 num 0s in A071031 rule 62 ok # A071047 # A071048 # A071049 # A071050 # A071051 num 1s in A071035 rule 126 sierpinski # A071052 # A071053 # A071054 # A071055 # # A267682 cumulative number of ON cells, by rows # A267682_samples = [1, 1, 4, 8, 15, 23, 34, 46, 61, 77, 96, 116, 139, 163, 190, 218, 249, 281, 316, 352, 391, 431, 474, 518, 565, 613, 664, 716, 771, 827, 886, 946, 1009, 1073, 1140, 1208, 1279, 1351, 1426, 1502, 1581, 1661, 1744, 1828, 1915, 2003, 2094, 2186, 2281, 2377, 2476]; # A267682(n) = n*(2*n-1)/2 + if(n%2==0,1,1/2); # A267682(n) = if(n%2==0, n^2 - (n-2)/2, n^2 - (n-1)/2); # vector(#A267682_samples,n,n--; A267682(n)) - \ # A267682_samples # recurrence_guess(A267682_samples) # vector(10,n,n--;n=2*n+1; A267682(n)) # even A054556 # odd A033951 # recurrence_guess(vector(10,n,n--; sum(i=0,n, 2*2*i+1 + 2*(2*i+1)+3))) exit 0; Math-PlanePath-129/xt/oeis/TriangleSpiral-oeis.t0000644000175000017500000001150313663400556017352 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2015, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt; use Test; plan tests => 6; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use List::Util 'min', 'max'; use Math::PlanePath::TriangleSpiral; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # A010054 -- turn sequence # # morphism # S -> S 1 # 1 -> 1 0 # 0 -> 0 MyOEIS::compare_values (anum => 'A010054', func => sub { my ($count) = @_; my @got = ('S'); while (@got <= $count) { @got = map { $_ eq 'S' ? ('S',1) : $_ eq '1' ? (1,0) : $_ eq '0' ? (0) : die } @got; } (shift @got) eq 'S' or die; $#got = $count-1; return \@got; }); #------------------------------------------------------------------------------ # A081272 -- N on Y axis MyOEIS::compare_values (anum => 'A081272', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::TriangleSpiral->new; for (my $y = 0; @got < $count; $y -= 2) { push @got, $path->xy_to_n (0,$y); } return \@got; }); #------------------------------------------------------------------------------ # A081275 -- N on slope=3 ENE MyOEIS::compare_values (anum => 'A081275', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::TriangleSpiral->new (n_start => 0); my $x = 2; my $y = 0; while (@got < $count) { push @got, $path->xy_to_n ($x,$y); $x += 3; $y += 1; } return \@got; }); #------------------------------------------------------------------------------ # A081589 -- N on slope=3 ENE MyOEIS::compare_values (anum => 'A081589', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::TriangleSpiral->new; my $x = 0; my $y = 0; while (@got < $count) { push @got, $path->xy_to_n ($x,$y); $x += 3; $y += 1; } return \@got; }); #------------------------------------------------------------------------------ # A038764 -- N on slope=2 WSW MyOEIS::compare_values (anum => 'A038764', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::TriangleSpiral->new; my $x = 0; my $y = 0; while (@got < $count) { push @got, $path->xy_to_n ($x,$y); $x += -3; $y += -1; } return \@got; }); #------------------------------------------------------------------------------ # A063177 -- a(n) is sum of existing numbers in row of a(n-1) MyOEIS::compare_values (anum => 'A063177', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangleSpiral->new; my @got; my %plotted; $plotted{0,0} = Math::BigInt->new(1); my $xmin = 0; my $ymin = 0; my $xmax = 0; my $ymax = 0; push @got, 1; for (my $n = $path->n_start + 1; @got < $count; $n++) { my ($prev_x, $prev_y) = $path->n_to_xy ($n-1); my ($x, $y) = $path->n_to_xy ($n); ### at: "$x,$y prev $prev_x,$prev_y" my $total = 0; if ($x > $prev_x) { ### forward diagonal ... foreach my $y ($ymin .. $ymax) { my $delta = $y - $prev_y; my $x = $prev_x + $delta; $total += $plotted{$x,$y} || 0; } } elsif ($y > $prev_y) { ### row: "$xmin .. $xmax at y=$prev_y" foreach my $x ($xmin .. $xmax) { $total += $plotted{$x,$prev_y} || 0; } } else { ### opp diagonal ... foreach my $y ($ymin .. $ymax) { my $delta = $y - $prev_y; my $x = $prev_x - $delta; $total += $plotted{$x,$y} || 0; } } ### total: "$total" $plotted{$x,$y} = $total; $xmin = min($xmin,$x); $xmax = max($xmax,$x); $ymin = min($ymin,$y); $ymax = max($ymax,$y); push @got, $total; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/AlternatePaper-oeis.t0000644000175000017500000004716714000716601017343 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BaseCnv 'cnv'; use Math::BigInt try => 'GMP'; use Math::PlanePath::AlternatePaper 124; # v.124 for n_to_n_list() use List::Util 'min'; use Test; plan tests => 36; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; my $paper = Math::PlanePath::AlternatePaper->new; #------------------------------------------------------------------------------ # A007088 -- N on X axis in base 4 MyOEIS::compare_values (anum => 'A007088', max_value => 2**28, func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x++) { my $n = $paper->xy_to_n ($x,0); push @got, cnv($n,10,4); } return \@got; }); # A169965 -- N on X=Y diagonal in base 4 MyOEIS::compare_values (anum => 'A169965', max_value => 2**28, func => sub { my ($count) = @_; my @got; for (my $i = 0; @got < $count; $i++) { my $n = $paper->xy_to_n ($i,$i); push @got, cnv($n,10,4); } return \@got; }); #------------------------------------------------------------------------------ # A004277 -- num visits in column X MyOEIS::compare_values (anum => 'A004277', func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x++) { my $total = 0; for (my $y = 0; ; $y++) { my @n_list = $paper->xy_to_n_list ($x,$y) or last; $total += scalar(@n_list); } push @got, $total; } return \@got; }); #------------------------------------------------------------------------------ # A151666 -- predicate N on X axis MyOEIS::compare_values (anum => 'A151666', func => sub { my ($count) = @_; my @got; for (my $n = $paper->n_start; @got < $count; $n++) { my ($x, $y) = $paper->n_to_xy ($n); push @got, $y==0 ? 1 : 0; } return \@got; }); # A270803 -- predicate segment N on X=Y leading diagonal, except not N=0 MyOEIS::compare_values (anum => 'A270803', func => sub { my ($count) = @_; my @got; for (my $n = $paper->n_start; @got < $count; $n++) { my ($x,$y) = $paper->n_to_xy ($n); my ($x1,$y1) = $paper->n_to_xy ($n+1); push @got, $n!=0 && $x==$y || $x1==$y1 ? 1 : 0; } return \@got; }); # A270804 -- N segments of diagonal stair step MyOEIS::compare_values (anum => 'A270804', func => sub { my ($count) = @_; my @got; for (my $i = 0; @got < $count; $i++) { # i+1,i+1 # | # i,i -- i+1,i push @got, $paper->xyxy_to_n ($i,$i, $i+1,$i); @got < $count or last; push @got, $paper->xyxy_to_n ($i+1,$i, $i+1,$i+1); } return \@got; }); #------------------------------------------------------------------------------ # A052955 single-visited points to N=2^k MyOEIS::compare_values (anum => 'A052955', max_value => 1000, func => sub { my ($count) = @_; my @got = (1); # extra initial 1 for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_n_to_singles ($paper, 2**$k); } return \@got; }); # A052940 single-visited points to N=4^k MyOEIS::compare_values (anum => 'A052940', max_value => 1000, func => sub { my ($count) = @_; my @got = (1); # initial 1 instead of 2 for (my $k = 1; @got < $count; $k++) { push @got, MyOEIS::path_n_to_singles ($paper, 4**$k); } return \@got; }); #------------------------------------------------------------------------------ # A068915 Y when N even, X when N odd MyOEIS::compare_values (anum => 'A068915', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x, $y) = $paper->n_to_xy ($n); push @got, ($n%2==0 ? $y : $x); } return \@got; }); # also equivalent to X when N even, Y when N odd, starting from N=1 MyOEIS::compare_values (anum => q{A068915}, func => sub { my ($count) = @_; my @got; for (my $n = 1; @got < $count; $n++) { my ($x, $y) = $paper->n_to_xy ($n); push @got, ($n%2==0 ? $x : $y); } return \@got; }); #------------------------------------------------------------------------------ # A080079 X-Y of last time on X+Y=s anti-diagonal MyOEIS::compare_values (anum => 'A080079', func => sub { my ($count) = @_; my @got; my @occur; my $target = 1; for (my $n = $paper->n_start + 1; @got < $count; $n++) { my ($x, $y) = $paper->n_to_xy ($n); my $s = $x + $y; $occur[$s]++; if ($occur[$s] == $s) { push @got, $x-$y; $target++; } } return \@got; }); # A020991 N-1 of last time on X+Y=s anti-diagonal MyOEIS::compare_values (anum => 'A020991', func => sub { my ($count) = @_; my @got; my @occur; my $target = 1; for (my $n = $paper->n_start + 1; @got < $count; $n++) { my ($x, $y) = $paper->n_to_xy ($n); my $s = $x + $y; $occur[$s]++; if ($occur[$s] == $s) { push @got, $n-1; $target++; } } return \@got; }); # A053645 Y of last time on X+Y=s anti-diagonal MyOEIS::compare_values (anum => 'A053645', max_count => 500, # because simple linear search func => sub { my ($count) = @_; my @got; my @occur; my $target = 1; for (my $n = $paper->n_start + 1; @got < $count; $n++) { my ($x, $y) = $paper->n_to_xy ($n); my $s = $x + $y; $occur[$s]++; if ($occur[$s] == $s) { push @got, $y; $target++; } } return \@got; }); # A053644 X of last time on X+Y=s anti-diagonal MyOEIS::compare_values (anum => 'A053644', max_count => 500, # because simple linear search func => sub { my ($count) = @_; my @got; my @occur = (-1); # hack for s=0 occurring 1 time my $target = 0; for (my $n = $paper->n_start; @got < $count; $n++) { my ($x, $y) = $paper->n_to_xy ($n); my $s = $x + $y; $occur[$s]++; if ($occur[$s] == $s) { push @got, $x; $target++; } } return \@got; }); #------------------------------------------------------------------------------ # A212591 N-1 of first time on X+Y=s anti-diagonal # seq 0, 1, 2, 5, 8, 9, 10, 21, 32, 33, 34, 37, 40, 41, 42, 85 # N 0 1 2 3 6, 9, 10, 11, 22, ... MyOEIS::compare_values (anum => 'A212591', max_count => 1000, # because simple linear search func => sub { my ($count) = @_; my @got; my $target = 1; for (my $n = $paper->n_start + 1; @got < $count; $n++) { my ($x, $y) = $paper->n_to_xy ($n); my $s = $x + $y; if ($s == $target) { push @got, $n-1; $target++; } } return \@got; }); # A047849 N of first time on X+Y=2^k anti-diagonal MyOEIS::compare_values (anum => 'A047849', max_count => 10, # because simple linear search func => sub { my ($count) = @_; my @got; for (my $k=0; @got < $count; $k++) { my $s = 2**$k; my @n_list; foreach my $y (0 .. $s) { my $x = $s - $y; $x+$y == $s or die; push @n_list, $paper->xy_to_n_list($x,$y); } push @got, min(@n_list); } return \@got; }); #------------------------------------------------------------------------------ # Skd segments in direction foreach my $elem (['A005418', 1,0, 1], # East ['A051437', 0,1, 2], # North ['A122746', -1,0, 3], # West ['A007179', 0,-1, 1], # South ) { my ($anum, $want_dx,$want_dy, $initial_k) = @$elem; MyOEIS::compare_values (anum => $anum, max_count => 14, func => sub { my ($count) = @_; my $path = Math::PlanePath::AlternatePaper->new; my @got; for (my $k = $initial_k||0; @got < $count; $k++) { my ($n_lo,$n_hi) = $path->level_to_n_range($k); push @got, scalar(grep { my ($dx,$dy) = $path->n_to_dxdy($_); $dx==$want_dx && $dy==$want_dy } $n_lo .. $n_hi-1); } return \@got; }); } # A122746 - also area increment to N=2^k MyOEIS::compare_values (anum => q{A122746}, max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 2; @got < $count; $k++) { push @got, (MyOEIS::path_enclosed_area($paper, 2**($k+1)) - MyOEIS::path_enclosed_area($paper, 2**$k)); } return \@got; }); #------------------------------------------------------------------------------ # A126684 - N single-visited points MyOEIS::compare_values (anum => 'A126684', func => sub { my ($count) = @_; my $path = Math::PlanePath::AlternatePaper->new; my @got; for (my $n = 0; @got < $count; $n++) { my @n_list = $path->n_to_n_list($n); if (@n_list == 1) { push @got, $n; } } return \@got; }); # A176237 - N double-visited points MyOEIS::compare_values (anum => 'A176237', func => sub { my ($count) = @_; my $path = Math::PlanePath::AlternatePaper->new; my @got; for (my $n = 0; @got < $count; $n++) { my @n_list = $path->n_to_n_list($n); if (@n_list == 2) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A274230 - area = doubles to N=2^k MyOEIS::compare_values (anum => 'A274230', max_count => 14, func => sub { my ($count) = @_; my $path = Math::PlanePath::AlternatePaper->new; my @got; for (my $k = 0; @got < $count; $k++) { my ($n_lo,$n_hi) = $path->level_to_n_range($k); push @got, scalar(grep { my @n_list = $path->n_to_n_list($_); @n_list == 2; # double-visited } $n_lo .. $n_hi); } return \@got; }); #------------------------------------------------------------------------------ # A181666 - n XOR other(n) occurring MyOEIS::compare_values (anum => 'A181666', func => sub { my ($count) = @_; require Math::PlanePath::Base::Digits; my $path = Math::PlanePath::AlternatePaper->new; my %seen; my @got; my $target_n = 256; for (my $n = 0; @got < $count || $n < $target_n; $n++) { my @n_list = $path->n_to_n_list($n); @n_list >= 2 or next; my $xor = $n_list[0] ^ $n_list[1]; next if $seen{$xor}++; push @got, $xor/4; ($target_n) = Math::PlanePath::Base::Digits::round_up_pow($n,2); } @got = sort {$a<=>$b} @got; $#got = $count-1; return \@got; }); #------------------------------------------------------------------------------ # A001196 - N on X axis, base 4 digits 0,3 only MyOEIS::compare_values (anum => 'A001196', func => sub { my ($count) = @_; my $path = Math::PlanePath::AlternatePaper->new (arms => 3); my @got; for (my $x = Math::BigInt->new(0); @got < $count; $x++) { my $n = $path->xy_to_n($x,0); push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ # A077957 -- Y at N=2^k, being alternately 0 and 2^(k/2) MyOEIS::compare_values (anum => 'A077957', max_count => 200, func => sub { my ($count) = @_; my @got; for (my $n = Math::BigInt->new(2); @got < $count; $n *= 2) { my ($x,$y) = $paper->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A028399 boundary to N=2*4^k MyOEIS::compare_values (anum => 'A028399', max_value => 10_000, func => sub { my ($count) = @_; my @got = (0); for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length($paper, 2*4**$k); } return \@got; }); # A131128 boundary to N=4^k MyOEIS::compare_values (anum => 'A131128', max_value => 10_000, func => sub { my ($count) = @_; my @got = (1); for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length($paper, 4**$k); } return \@got; }); # A027383 boundary/2 to N=2^k # is also boundary length verticals or horizontals since boundary is half # verticals and half horizontals MyOEIS::compare_values (anum => 'A027383', max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length($paper, 2**$k) / 2; } return \@got; }); #------------------------------------------------------------------------------ # A060867 area to N=2*4^k MyOEIS::compare_values (anum => 'A060867', max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 1; @got < $count; $k++) { push @got, MyOEIS::path_enclosed_area($paper, 2*4**$k); } return \@got; }); # A134057 area to N=4^k MyOEIS::compare_values (anum => 'A134057', max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_enclosed_area($paper, 4**$k); } return \@got; }); # A027556 area*2 to N=2^k MyOEIS::compare_values (anum => 'A027556', max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_enclosed_area($paper, 2**$k) * 2; } return \@got; }); #------------------------------------------------------------------------------ # A106665 -- turn 1=left, 0=right # OFFSET=0 cf first turn at N=1 here MyOEIS::compare_values (anum => 'A106665', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'AlternatePaper', turn_type => 'Left'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A090678 "non-squashing partitions" A088567 mod 2 # and A121241 which is 1,-1 # almost but not quite arms=2 turn_type=Left # A121241 1,-1 # A110036 2,0,-2 # A110037 1,0,-1 # MyOEIS::compare_values # (anum => 'A090678', # func => sub { # my ($count) = @_; # require Math::NumSeq::PlanePathTurn; # my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'AlternatePaper,arms=2', # turn_type => 'Left'); # my @got = (1,1,1,0,0,1,0,1,0,1,1,0,1,0,0,1,0,1); # while (@got < $count) { # my ($i,$value) = $seq->next; # push @got, $value; # } # return \@got; # }); #------------------------------------------------------------------------------ # A020985 - Golay/Rudin/Shapiro is dX and dY alternately # also is dSum in Math::NumSeq::PlanePathDelta MyOEIS::compare_values (anum => q{A020985}, # catalogued func => sub { my ($count) = @_; my @got; for (my $n = $paper->n_start; @got < $count; ) { { my ($dx, $dy) = $paper->n_to_dxdy ($n++); push @got, $dx; } last unless @got < $count; { my ($dx, $dy) = $paper->n_to_dxdy ($n++); push @got, $dy; } } return \@got; }); # A020987 GRS as 0,1 MyOEIS::compare_values (anum => 'A020987', func => sub { my ($count) = @_; my @got; for (my $n = $paper->n_start; @got < $count; ) { { my ($dx, $dy) = $paper->n_to_dxdy ($n++); push @got, $dx > 0 ? 1 : 0; } last unless @got < $count; { my ($dx, $dy) = $paper->n_to_dxdy ($n++); push @got, $dy > 0 ? 1 : 0; } } return \@got; }); #------------------------------------------------------------------------------ # A093573+1 - triangle of positions where cumulative=k # cumulative A020986 starts n=0 for GRS(0)=0 (A020985) # 0, # 1, 3, # 2, 4, 6, # 5, 7, 13, 15, # 8, 12, 14, 16, 26, # 9, 11, 17, 19, 25, 27 # # cf diagonals # 0 # 1 # 2, 4 # 3,7, 5 # 8, 6,14, 16 # 9,13, 15,27, 17 MyOEIS::compare_values (anum => 'A093573', func => sub { my ($count) = @_; my @got; OUTER: for (my $sum = 1; ; $sum++) { my @n_list; foreach my $y (0 .. $sum) { my $x = $sum - $y; push @n_list, $paper->xy_to_n_list($x,$y); } @n_list = sort {$a<=>$b} @n_list; foreach my $n (@n_list) { last OUTER if @got >= $count; push @got, $n-1; } } return \@got; }); #------------------------------------------------------------------------------ # A020986 - GRS cumulative # X+Y, starting from N=1 (doesn't have X+Y=0 for N=0) MyOEIS::compare_values (anum => 'A020986', func => sub { my ($count) = @_; my @got; for (my $n = $paper->n_start + 1; @got < $count; $n++) { my ($x, $y) = $paper->n_to_xy ($n); push @got, $x+$y; } return \@got; }); # is X coord undoubled, starting from N=2 (doesn't have X=0 for N=0) MyOEIS::compare_values (anum => q{A020986}, func => sub { my ($count) = @_; my @got; for (my $n = 2; @got < $count; $n += 2) { my ($x, $y) = $paper->n_to_xy ($n); push @got, $x; } return \@got; }); #------------------------------------------------------------------------------ # A022155 - positions of -1, is S,W steps MyOEIS::compare_values (anum => 'A022155', func => sub { my ($count) = @_; my @got; for (my $n = $paper->n_start; @got < $count; $n++) { my ($dx,$dy) = $paper->n_to_dxdy($n); if ($dx < 0 || $dy < 0) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A203463 - positions of 1, is N,E steps MyOEIS::compare_values (anum => 'A203463', func => sub { my ($count) = @_; my @got; for (my $n = $paper->n_start; @got < $count; $n++) { my ($dx,$dy) = $paper->n_to_dxdy($n); if ($dx > 0 || $dy > 0) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A020990 - Golay/Rudin/Shapiro * (-1)^k cumulative, is Y coord undoubled, # except N=0 MyOEIS::compare_values (anum => 'A020990', func => sub { my ($count) = @_; my @got; for (my $n = 2; @got < $count; $n += 2) { my ($x, $y) = $paper->n_to_xy ($n); push @got, $y; } return \@got; }); MyOEIS::compare_values (anum => q{A020990}, # checking again func => sub { my ($count) = @_; my @got; for (my $n = $paper->n_start + 1; @got < $count; $n++) { my ($x, $y) = $paper->n_to_xy ($n); push @got, $x-$y; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/KochCurve-oeis.t0000644000175000017500000005734113774441334016337 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2015, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 22; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::KochCurve; use Math::NumSeq::PlanePathDelta; use Math::NumSeq::PlanePathTurn; my $path = Math::PlanePath::KochCurve->new; # GP-DEFINE read("my-oeis.gp"); #------------------------------------------------------------------------------ # A332206 -- N on X axis MyOEIS::compare_values (anum => 'A332206', func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x++) { if (defined(my $n = $path->xy_to_n($x,0))) { push @got, $n; } } return \@got; }); # A001196 -- N segments on X axis, so N and N+1 points both on X axis # Segment N=0 on axis, then on expansion must new low base 4 digit 0 or 3. # All other segments leave the X axis and never return. # So all base 4 digits 0,3, which is binary 00 11 MyOEIS::compare_values (anum => 'A001196', func => sub { my ($count) = @_; my @got; for (my $x = 0; @got < $count; $x++) { if (defined(my $n = $path->xyxy_to_n($x,0, $x+2,0))) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A335358 -- (X-Y)/2 coordinate, at 60 degrees MyOEIS::compare_values (anum => 'A335358', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, ($x-$y)/2; } return \@got; }); # A335359 -- Y coordinate MyOEIS::compare_values (anum => 'A335359', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); # pos 0, 1, 1+w6, 2, 3 # rot 0, 1, -1, 0 # # GP-DEFINE w = quadgen(-3); # GP-Test (w-1/2)^2 == -3/4 # arg(w)*180/Pi # # pos_table = [0,1,1+w,2]; # rot_table = [1,w,conj(w),1]; # GP-DEFINE my(table = [[1,0], [w,1], [conj(w),1+w], [1,2]]); \ # GP-DEFINE Z(n) = { # GP-DEFINE my(v=digits(n,4),rot=1); # GP-DEFINE for(i=1,#v, [rot,v[i]] = rot*table[v[i]+1]); # GP-DEFINE subst(Pol(v),'x,3); # GP-DEFINE } # Z(1) # vector(12,n,n--; Z(n)) # OEIS_samples("A335358") # GP-Test my(v=OEIS_samples("A335358")); v==vector(#v,n,n--; real(Z(n))) # GP-Test my(v=OEIS_samples("A335359")); v==vector(#v,n,n--; imag(Z(n))) # GP-DEFINE { my(w=quadgen(-3), table=[[1,0], [w,1], [conj(w),1+w], [1,2]]); # GP-DEFINE X(n) = # GP-DEFINE my(v=digits(n,4),rot=1); # GP-DEFINE for(i=1,#v, [rot,v[i]] = rot*table[v[i]+1]); # GP-DEFINE fromdigits(real(v),3); # GP-DEFINE } # GP-Test my(v=OEIS_samples("A335358")); v==vector(#v,n,n--; X(n)) # GP-Test my(g=OEIS_bfile_gf("A335358")); \ # GP-Test g==Polrev(vector(poldegree(g)+1,n,n--; X(n))) # GP-DEFINE { my(w=quadgen(-3), table=[[1,0], [w,1], [conj(w),1+w], [1,2]]); # GP-DEFINE Y(n) = # GP-DEFINE my(v=digits(n,4),rot=1); # GP-DEFINE for(i=1,#v, [rot,v[i]] = rot*table[v[i]+1]); # GP-DEFINE fromdigits(imag(v),3); # GP-DEFINE } # GP-Test my(v=OEIS_samples("A335359")); v==vector(#v,n,n--; Y(n)) # GP-Test my(g=OEIS_bfile_gf("A335359")); \ # GP-Test g==Polrev(vector(poldegree(g)+1,n,n--; Y(n))) # GP-DEFINE \\ 0,2,3 mod 6 # GP-DEFINE A047244(n) = 2*n - (n%3==2); # GP-Test my(v=OEIS_samples("A047244")); v==vector(#v,n,n--; A047244(n)) # # GP-Test /* Andrey Zabolotskiy in A335359 */ \ # GP-Test vector(4^6,n,n--; X(n)) == \ # GP-Test vector(4^6,n,n--; Y(n) + Y(A047244(n))) # GP-Test vector(4^6,n,n--; Y(A047244(n))) == \ # GP-Test vector(4^6,n,n--; X(n)-Y(n)) # GP-Test vector(4^6,n,n--; Y(2*n)) == \ # GP-Test vector(4^6,n,n--; X(n)-Y(n)) # GP-Test X(4) == 3 # GP-Test Y(4) == 0 # GP-Test Y(A047244(4)) == 3 # vector(20,n,n--; A047244(n)) # vector(20,n,n--; X(n)-Y(n)) # not in OEIS: 0, 1, 0, 2, 3, 2, 0, 1, 0, 2, 3, 4, 6, 7, 6, 8, 9, 8, 6, 7 # vector(20,n,n--; Y(2*n)) # not in OEIS: 0, 1, 0, 2, 3, 2, 0, 1, 0, 2, 3, 4, 6, 7, 6, 8, 9, 8, 6, 7 # vector(20,n,n--; Y(2*n)) # vector(20,n,n--; Y(2*n)) # vector(20,n,n--; X(2*n)) # not in OEIS: 0, 1, 3, 2, 3, 5, 6, 7, 9, 8, 9, 7, 6, 7, 9, 8, 9, 11, 12, 13 # vector(50,n, X(n)-X(n-1)) # not in OEIS: 1, 0, 1, 1, 0, -1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, -1, 1, 0, -1, -1, 0, -1, 1, 0, 1, 1, 0, -1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, -1, 0, 1, 1, 0, 1, 1, 0 # GP-DEFINE dY(n) = Y(n+1) - Y(n); # vector(20,n, dY(2*n-1)) # not in OEIS: 1, 0, 1, 1, 0, -1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, -1, 1, 0 # turn 1, -2, 1 # for(k=200,210, print(digits(6*k + 3,4))) # GP-DEFINE w6 = quadgen(-3); # GP-DEFINE w3 = w6 - 1; # GP-DEFINE w12_times_sqrt3 = quadgen(-3) + 1; # GP-Test w12_times_sqrt3^6 == - 3^(6/2) # GP-DEFINE Z(n) = X(n) + Y(n)*w6; # GP-Test vector(4^6,n, conj(Z(2*n) / w12_times_sqrt3)) == \ # GP-Test vector(4^6,n, Z(n)) # GP-Test vector(4^6,n, X(n)+Y(n) + w3*Y(n)) == \ # GP-Test vector(4^6,n, Z(n)) # GP-Test vector(4^6,n, X(n)-Y(n) + (w6+1)*Y(n)) == \ # GP-Test vector(4^6,n, Z(n)) # z = x + w6*y # = x + (w3+1)*y # = x + w3*y + y # = x+y + w3*y # = x-y + (w6+1)*y basis 1, w6+1 at 30 degrees length sqrt3 #------------- # X+Y # vector(20,n,n--; X(n)+Y(n)) # not in OEIS: 0, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 6, 6, 7, 8, 8, 9, 10, 10, 11 # GP-DEFINE \\ curve 0---1 3---* # GP-DEFINE \\ \ / # GP-DEFINE \\ 2 # GP-DEFINE { my(w=quadgen(-3), table=[[1,0], [conj(w),1], [w,2-w], [1,2]]); # GP-DEFINE X120(n) = # GP-DEFINE my(v=digits(n,4),rot=1); # GP-DEFINE for(i=1,#v, [rot,v[i]] = rot*table[v[i]+1]); # GP-DEFINE fromdigits(real(v),3); # GP-DEFINE } # GP-Test vector(4^6,n,n--; X120(n)) == \ # GP-Test vector(4^6,n,n--; X(n) + Y(n)) #------------- # X-Y # vector(20,n,n--; X(n)-Y(n)) # 0, 1, 0, 2, 3, 2, 0, 1, 0, 2, 3, 4 # 0 -1 -3 -2 # Y # / # / # *-----X # GP-DEFINE { my(w=quadgen(-3), table=[[1,0], [w,1], [conj(w),1+w], [1,2]]); # GP-DEFINE XYdiff(n) = # GP-DEFINE my(v=digits(n,4),rot=1); # GP-DEFINE for(i=1,#v, [rot,v[i]] = rot*table[v[i]+1]); # GP-DEFINE fromdigits(imag(conj(v)),3); # GP-DEFINE } # vector(46,n,n--; XYdiff(n)) \\ like A335359 Y coord # vector(16,n,n--; X(n) - Y(n)) # vector(16,n,n--; X(n) + Y(n)) \\ like A335380 X coord # not in OEIS: 0, 1, 0, 2, 3, 2, 0, 1, 0, 2, 3, 4, 6, 7, 6, 8 # my(w=quadgen(-3), table=[[1,0], [w,1], [conj(w),1+w], [1,2]]); \ # for(c=0,1, my(table=if(c,conj(table),table)); \ # for(r=0,5, my(table=table*w^r); \ # my(f=(n)-> my(v=digits(n,4),rot=1); \ # for(i=1,#v, [rot,v[i]] = rot*table[v[i]+1]); \ # fromdigits(real(v),3)); \ # print(vector(16,n,n--; f(n))))) # # w=quadgen(-3) # for(r=0,5, print(imag(('x-'y*w)*w^r))) #------------------------------------------------------------------------------ # Cf wave-like at high resoluation # # * # / | Kochawave # / | # *---* *---* # my(g=OEIS_bfile_gf("A335380")); x(n) = polcoeff(g,n); # my(g=OEIS_bfile_gf("A335381")); y(n) = polcoeff(g,n); # plothraw(vector(3^3,n,n--; x(n)), \ # vector(3^3,n,n--; y(n)), 1+8+16+32) #------------------------------------------------------------------------------ # Koch square grid base 5 -> base 3 # A229217 directions # A332249 \ coords # A332250 / # # # 2---3 dirs 0 1 0 -1 0 # | | turns L R R L # 0---1 4---5 # # 10 # | # 8---9 2 # | | A229217 directions # 2--3,7--6 -1 --O-- 1 # | | | | # 0---1 4---5 -2 # # 1,2,1,-2,1, 2,-1,2,1,2, 1,2,1,-2,1, -2,1,-2,-1,-2, 1, # # GP-DEFINE A229217_turn(n) = { # GP-DEFINE my(r); # GP-DEFINE while([n,r]=divrem(n,5);r==0, n>0||error()); [1,-1,-1,1][r]; # GP-DEFINE } # vector(25,n, A229217_turn(n)) # vector(25,n, A229217_turn(n)==1) # vector(25,n, A229217_turn(n)==-1) # not in OEIS: 1,-1,-1,1,1,1,-1,-1,1,-1,1,-1,-1,1,-1,1,-1,-1,1,1,1,-1,-1,1,1 # not in OEIS: 1,0,0,1,1,1,0,0,1,0,1,0,0,1,0,1,0,0,1,1,1,0,0,1,1 # not in OEIS: 0,1,1,0,0,0,1,1,0,1,0,1,1,0,1,0,1,1,0,0,0,1,1,0,0 # GP-DEFINE \\ A229217 directions 1,2,-1,-2 # GP-DEFINE my(final=[1,2,-1,-2]); \ # GP-DEFINE A229217(n) = final[ vecsum([(d==1)-(d==3) | d<-digits(n-1,5)]) %4+1]; # GP-Test my(v=OEIS_samples("A229217")); /* OFFSET=1 */ \ # GP-Test v==vector(#v,n, A229217(n)) # # GP-DEFINE A229217_final = [1,2,-1,-2]; # GP-DEFINE dir_to_A229217_final(d) = A229217_final[d%4+1]; # GP-DEFINE A229217_final_to_dir(f) = [3,2,'none,0,1][f+3]; # GP-Test A229217_final_to_dir(1) == 0 # GP-Test A229217_final_to_dir(2) == 1 # GP-Test A229217_final_to_dir(-1) == 2 # GP-Test A229217_final_to_dir(-2) == 3 # GP-Test vector(5^6,n, n++; /* to 1-based */ \ # GP-Test ((A229217_final_to_dir(A229217(n)) \ # GP-Test - A229217_final_to_dir(A229217(n-1))) + 1) % 4 - 1) ==\ # GP-Test vector(5^6,n, A229217_turn(n)) # count 1s and 3s # vector(25,n, #select(d->d==1,digits(n,5))) # vector(25,n, #select(d->d==3,digits(n,5))) # not in OEIS: 1, 0, 0, 0, 1, 2, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1 # not in OEIS: 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 2, 1, 0, 0, 0, 1, 0, 0 # vector(50,n, my(v=digits(n,5)); sum(i=1,#v, (v[i]==1)-(v[i]==3))) # not in OEIS: 1,0,-1,0,1,2,1,0,1,0,1,0,-1,0,-1,0,-1,-2,-1,0,1,0,-1,0,1,2,1,0,1,2,3,2,1,2,1,2,1,0,1,0,1,0,-1,0,1,2,1,0,1,0 # # = 1,2,-1,-2 according as d(n) == 0,1,2,3 (mod 4) where d(n) = (base 5 count digit 1's) - (base 5 count digit 3's). # x=OEIS_bfile_func("A332249"); # y=OEIS_bfile_func("A332250"); # plothraw(vector(5^2,n,n--; x(n)), \ # vector(5^2,n,n--; y(n)), 1+8+16+32) # GP-DEFINE my(table=[[1,0], [I,1], [1,1+I], [-I,2+I], [1,2]]); \ # GP-DEFINE A332249(n) = { # GP-DEFINE my(v=digits(n,5),rot=1); # GP-DEFINE for(i=1,#v, [rot,v[i]] = rot*table[v[i]+1]); # GP-DEFINE fromdigits(real(v),3); # GP-DEFINE } # GP-Test my(v=OEIS_samples("A332249")); vector(#v,n,n--; A332249(n)) == v #------------------------------------------------------------------------------ # A065359 PmOneBits net direction, cumulative turn 1 or -2 MyOEIS::compare_values (anum => 'A065359', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'TTurn6n'); my @got; my $dir = 0; while (@got < $count) { push @got, $dir; my ($i,$value) = $seq->next; $dir += $value; } return \@got; }); # A229216 directions mod 6 as 1,2,3,-1,-2,-3 # # 3 2 downwards, so outwards of snowflake # \ / # -1 -- * -- 1 *---* *---* # / \ \ / \ # -2 -3 * ... # # Not quite right yet, sample values are for 3 expansions snowflake, not # infinite "fixed point". # # MyOEIS::compare_values # (anum => 'A229216', # func => sub { # my ($count) = @_; # my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, # turn_type => 'TTurn6n'); # my @got; # my $dir = 0; # my @dir6_to_A229216 = (1,-3,-2,-1,3,2); # while (@got < $count) { # push @got, $dir6_to_A229216[$dir%6]; # my ($i,$value) = $seq->next; # $dir += $value; # } # return \@got; # }); #------------------------------------------------------------------------------ # 2 # / \ / # 0---1 3---4 # A002450 number of right turns N=1 to N < 4^k MyOEIS::compare_values (anum => 'A002450', max_value => 100_000, func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got; foreach my $k (0 .. $count-1) { my $total = 0; foreach my $i (1 .. 4**$k-1) { $total += $seq->ith($i); } push @got, $total; } return \@got; }); # A020988 number of left turns N=1 to N < 4^k = (2/3)*(4^n-1). # duplicate A084180 MyOEIS::compare_values (anum => 'A020988', max_value => 100_000, func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); my @got; foreach my $k (0 .. $count-1) { my $total = 0; foreach my $i (1 .. 4**$k-1) { $total += $seq->ith($i); } push @got, $total; } return \@got; }); #------------------------------------------------------------------------------ # A050292 TurnsL # # Since partial sums of A035263 = Left. # MyOEIS::compare_values (anum => 'A050292', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); my @got; my $TurnsR = 0; while (@got < $count) { push @got, $TurnsR; my ($i,$value) = $seq->next; $TurnsR += $value; } return \@got; }); # A123087 TurnsR MyOEIS::compare_values (anum => 'A123087', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got; my $TurnsR = 0; while (@got < $count) { push @got, $TurnsR; my ($i,$value) = $seq->next; $TurnsR += $value; } return \@got; }); # A068639 TurnsLSR = TurnsL - TurnsR, cumulative +1 or -1 turn my A309873 MyOEIS::compare_values (anum => 'A068639', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my @got; my $total = 0; while (@got < $count) { push @got, $total; my ($i,$value) = $seq->next; $total += $value; } return \@got; }); # A197911 cumulative left=1,right=2 MyOEIS::compare_values (anum => 'A197911', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'SLR'); # 0,1,2 my @got; my $total = 0; while (@got < $count) { push @got, $total; my ($i,$value) = $seq->next; $total += $value; } return \@got; }); #------------------------------------------------------------------------------ # A309873 (mine) turn left=1,right=-1 MyOEIS::compare_values (anum => 'A309873', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A016153 - area under the curve, (9^n-4^n)/5 MyOEIS::compare_values (anum => 'A016153', max_value => 100_000, func => sub { my ($count) = @_; my $path = Math::PlanePath::KochCurve->new; my @got; for (my $k = 0; @got < $count; $k++) { my @points; my ($n_lo, $n_hi) = $path->level_to_n_range($k); foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); push @points, [$x,$y]; } push @got, points_to_area(\@points); } return \@got; }); sub points_to_area { my ($points) = @_; if (@$points < 3) { return 0; } require Math::Geometry::Planar; my $polygon = Math::Geometry::Planar->new; $polygon->points($points); return $polygon->area; } #------------------------------------------------------------------------------ # A177702 - abs(dX) from N=1 onwards, repeating 1,1,2 MyOEIS::compare_values (anum => 'A177702', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathDelta->new (planepath_object => $path, delta_type => 'AbsdX'); $seq->seek_to_i(1); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A217586 # Not quite turn sequence ... # differs 0<->1 at n=2^k # # a(1) = 1 # if a(n) = 0 then a(2*n) = 1 and a(2*n+1) = 0 # opposite low bit # if a(n) = 1 then a(2*n) = 0 and a(2*n+1) = 0 # both 0 # # a(2n+1)=0 # odd always left # a(2n) = 1-a(n) # even 0 or 1 as odd or even # a(4n) = 1-a(2n) = 1-(1-a(n)) = a(n) # a(4n+2) = 1-a(2n+1) = 1-0 = 1 # 4n+2 always right # except a(0+2) = 1-a(1) = 1-1 = 0 # A Right N differ # 1 0 1 * # 0 1 10 * # 0 0 11 # 1 0 100 * # 0 0 101 # 1 1 110 # 0 0 111 # 0 1 1000 * # 0 0 1001 # 1 1 1010 # 0 0 1011 # 0 0 1100 # 0 0 1101 # 1 1 1110 # 0 0 1111 # 1 0 10000 * # 0 0 # 1 1 # 0 0 # 0 0 # 0 0 # 1 1 # 0 0 # 1 1 MyOEIS::compare_values (anum => q{A217586}, func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got; while (@got < $count) { # $seq->next; my ($i,$value) = $seq->next; if (is_pow2($i)) { $value ^= 1; } push @got, $value; # push @got, A217586_func($i) } return \@got; }); sub A217586_func { my ($n) = @_; if ($n < 1) { die "A217586_func() must have n>=1"; } { while (($n & 3) == 0) { $n >>= 2; } if ($n == 1) { return 1; } if (($n & 3) == 2) { if ($n == 2) { return 0; } else { return 1; } } if ($n & 1) { return 0; } } # { # if ($n == 1) { # return 1; # } # if (A217586_func($n >> 1)) { # if ($n & 1) { # return 0; # } else { # return 0; # } # } else { # if ($n & 1) { # return 0; # } else { # return 1; # } # } # } # # { # if ($n == 1) { # return 1; # } # my $bit = $n & 1; # if (A217586_func($n >> 1)) { # return 0; # } else { # return $bit ^ 1; # } # } } sub is_pow2 { my ($n) = @_; while ($n > 1) { if ($n & 1) { return 0; } $n >>= 1; } return ($n == 1); } #------------------------------------------------------------------------------ # A035263 is turn left=1,right=0 at OFFSET=1 # morphism 1 -> 10, 0 -> 11 MyOEIS::compare_values (anum => 'A035263', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); # also left=0,right=1 at even N MyOEIS::compare_values (anum => q{A035263}, func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; if (($i & 1) == 0) { push @got, $value; } } return \@got; }); #------------------------------------------------------------------------------ # A073059 a(4k+3)= 1 ..11 = 1 # a(4k+2) = a(4k+4) = 0 ..00 ..10 = 0 # a(16k+13) = 1 1101 # a(4n+1) = a(n) ..01 = base4 above # a(n) = 1-A035263(n-1) is Koch 1=left,0=right by morphism OFFSET=1 # so A073059 is next turn 0=left,1=right # ??? # # MyOEIS::compare_values # (anum => q{A073059}, # func => sub { # my ($count) = @_; # my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, # turn_type => 'Left'); # my @got = (0); # while (@got < $count) { # $seq->next; # my ($i,$value) = $seq->next; # push @got, $value; # } # return \@got; # }); #------------------------------------------------------------------------------ # A096268 - morphism turn 1=right,0=left # but OFFSET=0 is turn at N=1 MyOEIS::compare_values (anum => 'A096268', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A029883 - Thue-Morse first diffs MyOEIS::compare_values (anum => 'A029883', fixup => sub { my ($bvalues) = @_; @$bvalues = map {abs} @$bvalues; }, func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A089045 - +/- increment MyOEIS::compare_values (anum => 'A089045', fixup => sub { my ($bvalues) = @_; @$bvalues = map {abs} @$bvalues; }, func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A003159 - N end in even number of 0 bits, is positions of left turn MyOEIS::compare_values (anum => 'A003159', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; if ($value == 1) { # left push @got, $i; } } return \@got; }); #------------------------------------------------------------------------------ # A036554 - N end in odd number of 0 bits, position of right turns MyOEIS::compare_values (anum => 'A036554', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; if ($value == 1) { # right push @got, $i; } } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/ZOrderCurve-oeis.t0000644000175000017500000004610113752215746016652 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2018, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 12; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::ZOrderCurve; use Math::PlanePath::Diagonals; # GP-DEFINE read("my-oeis.gp"); #------------------------------------------------------------------------------ # Radix = 3 # GP-DEFINE \\ A163325 ternary X # GP-DEFINE X3(n) = fromdigits(digits(n,9)%3,3); # GP-DEFINE A163325(n) = X3(n); # GP-Test my(v=OEIS_samples("A163325")); /* OFFSET=0 */ \ # GP-Test vector(#v,n,n--; A163325(n)) == v # GP-Test my(g=OEIS_bfile_gf("A163325")); \ # GP-Test g==Polrev(vector(poldegree(g)+1,n,n--;A163325(n))) # # GP-DEFINE \\ A163326 ternary Y # GP-DEFINE Y3(n) = fromdigits(digits(n, 9)\3, 3); # GP-DEFINE A163326(n) = Y3(n); # GP-Test my(v=OEIS_samples("A163326")); /* OFFSET=0 */ \ # GP-Test vector(#v,n,n--; A163326(n)) == v # GP-Test my(g=OEIS_bfile_gf("A163326")); \ # GP-Test g==Polrev(vector(poldegree(g)+1,n,n--;A163326(n))) # GP-DEFINE \\ 00 01 02 above ternary # GP-DEFINE A037314(n) = fromdigits(digits(n,3), 9); # GP-Test my(v=OEIS_samples("A037314")); /* OFFSET=0 */ \ # GP-Test vector(#v,n,n--; A037314(n)) == v # # GP-DEFINE \\ 00 10 20 below ternary # GP-DEFINE A208665(n) = fromdigits(digits(n,3), 9) * 3; # GP-Test my(v=OEIS_samples("A208665")); /* OFFSET=1 */ \ # GP-Test vector(#v,n, A208665(n)) == v # # GP-DEFINE \\ 00 11 22 duplicate ternary digits # GP-DEFINE Dup3(n) = fromdigits(digits(n,3),9)<<2; # GP-DEFINE A338086(n) = Dup3(n); # GP-Test my(v=OEIS_samples("A338086")); /* OFFSET=1 */ \ # GP-Test vector(#v,n,n--; A338086(n)) == v # GP-Test vector(3^7,n,n--; X3(Dup3(n))) == \ # GP-Test vector(3^7,n,n--; n) # GP-Test vector(3^7,n,n--; Y3(Dup3(n))) == \ # GP-Test vector(3^7,n,n--; n) # GP-Test vector(3^7,n,n--; Dup3(n)) == \ # GP-Test vector(3^7,n,n--; A037314(n) + A208665(n)) # GP-Test vector(3^7,n,n--; Dup3(n)) == \ # GP-Test vector(3^7,n,n--; 4*A037314(n)) # GP-Test vector(3^7,n,n--; Dup3(n)) == \ # GP-Test vector(3^7,n,n--; (4/3)*A208665(n)) # GP-DEFINE to_ternary(n)=fromdigits(digits(n,3))*sign(n); # GP-DEFINE from_ternary(n)=fromdigits(digits(n),3); # GP-Test from_ternary(2201) == 73 # GP-Test from_ternary(22220011) == 6484 # GP-Test Dup3(73) == 6484 # GP-Test fromdigits([8,8,0,4],9) == 6484 # system("rm /tmp/b338086.txt"); \ # my(len=3^8); print("len "len" last "len-1); \ # for(n=0,len-1, write("/tmp/b338086.txt",n," ",A338086(n))); \ # system("ls -l /tmp/b338086.txt"); # GP-Test my(g=OEIS_bfile_gf("A338086")); \ # GP-Test g==Polrev(vector(poldegree(g)+1,n,n--;A338086(n))) # poldegree(OEIS_bfile_gf("A338086")) # 3^8-1 # poldegree(OEIS_bfile_gf("A163325")) # poldegree(OEIS_bfile_gf("A163326")) # poldegree(OEIS_bfile_gf("A037314")) # poldegree(OEIS_bfile_gf("A208665")) # poldegree(OEIS_bfile_gf("A163338")) # poldegree(OEIS_bfile_gf("A163339")) \\ Peano by diagonals 3^8 #------------------------------------------------------------------------------ # Radix = 10 # GP-DEFINE X10(n) = fromdigits(digits(n,100)%10,10); # GP-DEFINE Y10(n) = fromdigits(digits(n,100)\10,10); # GP-DEFINE \\ 00 01 02 zero digit above each decimal # GP-DEFINE A051022(n) = fromdigits(digits(n),100); # GP-Test my(v=OEIS_samples("A051022")); /* OFFSET=0 */ \ # GP-Test vector(#v,n,n--; A051022(n)) == v # GP-Test my(g=OEIS_bfile_gf("A051022")); \ # GP-Test g==Polrev(vector(poldegree(g)+1,n,n--;A051022(n))) # poldegree(OEIS_bfile_gf("A051022")) # ~/OEIS/b051022.txt # # GP-Test /* Reinhard Zumkeller in A051022 */ \ # GP-Test vector(20000,n,n--; A051022(n)) == \ # GP-Test vector(20000,n,n--; if(n<10,n, A051022(floor(n/10))*100 + n%10)) # Past n<=10 was wrong at n=10 # vector(20,n,n--; A051022(n)) # vector(20,n,n--; if(n<=10,n, A051022(floor(n/10))*100 + n%10)) # GP-Test /* A092908 = primes in A051022, every second digit 0 */ \ # GP-Test my(v=OEIS_samples("A092908")); /* OFFSET=0 */ \ # GP-Test my(limit=v[#v], got=List([])); \ # GP-Test for(n=0,oo, my(t=A051022(n)); if(t>limit,break); \ # GP-Test if(isprime(t), listput(got,t))); \ # GP-Test Vec(got) == v # GP-Test /* A092909 = A051022(primes), indices with 0 digits */ \ # GP-Test my(v=OEIS_samples("A092909")); /* OFFSET=0 */ \ # GP-Test apply(A051022,primes(#v)) == v #------ # GP-DEFINE \\ 00 11 22 duplicate digits decimal # GP-DEFINE A338754(n) = fromdigits(digits(n),100)*11; # GP-Test my(v=OEIS_samples("A338754")); /* OFFSET=0 */ \ # GP-Test vector(#v,n,n--; A338754(n)) == v # GP-Test my(g=OEIS_bfile_gf("A338754")); \ # GP-Test g==Polrev(vector(poldegree(g)+1,n,n--;A338754(n))) # poldegree(OEIS_bfile_gf("A338754")) # GP-Test my(a=A338754); a(5517) == 55551177 # size match A051022 # system("rm /tmp/b338754.txt"); \ # my(len=10^4+1); print("len "len" last "len-1); \ # for(n=0,len-1, write("/tmp/b338754.txt",n," ",A338754(n))); \ # system("ls -l /tmp/b338754.txt"); # GP-Test my(g=OEIS_bfile_gf("A338754")); \ # GP-Test g==Polrev(vector(poldegree(g)+1,n,n--;A338754(n))) # poldegree(OEIS_bfile_gf("A338754")) # 3^8-1 # GP-Test vector(10000,n,n--; A051022(n)) == \ # GP-Test vector(10000,n,n--; A338754(n)/11) # # GP-Test vector(10000,n,n--; A338754(n)) == \ # GP-Test vector(10000,n,n--; 11*A051022(n)) #------------------------------------------------------------------------------ # A044836 decimal more even length runs than odd length runs # # not the same as A338754 only even length runs # GP-DEFINE vector_run_lengths(v) = { # GP-DEFINE if(#v==0,return([])); # GP-DEFINE my(l=List([]),run=1); # GP-DEFINE for(i=2,#v, if(v[i]==v[i-1],run++, listput(l,run);run=1)); # GP-DEFINE listput(l,run); # GP-DEFINE Vec(l); # GP-DEFINE } # GP-Test vector_run_lengths([]) == [] # GP-Test vector_run_lengths([99]) == [1] # GP-Test vector_run_lengths([99,99,99]) == [3] # GP-Test vector_run_lengths([99,7,7,99]) == [1,2,1] # GP-DEFINE isA044836(n) = { # GP-DEFINE my(v = vector_run_lengths(digits(n)) % 2, # GP-DEFINE num_odd = hammingweight(v), # GP-DEFINE num_even = #v - num_odd); # GP-DEFINE num_even > num_odd; # GP-DEFINE } # GP-Test my(v=OEIS_samples("A044836")); \ # GP-Test select(isA044836, [0..v[#v]]) == v # GP-Test my(v=Vecrev(OEIS_bfile_gf("A044836")/x)); /* OFFSET=1 */ \ # GP-Test /* v=v[1..100]; */ \ # GP-Test my(got=List([])); \ # GP-Test for(n=0,v[#v], if(isA044836(n), listput(got,n))); \ # GP-Test Vec(got)==v # poldegree(OEIS_bfile_gf("A044836")) # ~/OEIS/b044836.txt # GP-Test /* Remy Sigrist in A044836 */ \ # GP-Test my(is=(n,base=10)-> my (v=0); while (n, my (d=n%base,w=0); \ # GP-Test while (n%base==d, n\=base; w++); v+=(-1)^w); v>0); \ # GP-Test vector(10000,n,n--; is(n)) == \ # GP-Test vector(10000,n,n--; isA044836(n)) # GP-DEFINE A044836(n) = { # GP-DEFINE n>=1||error(); # GP-DEFINE my(ret=-1); # GP-DEFINE while(1, # GP-DEFINE until(isA044836(ret),ret++); # GP-DEFINE n--; if(n==0,return(ret))); # GP-DEFINE } # GP-Test my(v=OEIS_samples("A044836")); \ # GP-Test vector(#v,n, A044836(n)) == v /* OFFSET=1 */ # GP-Test A044836(100) == 10011 # GP-Test A338754(100) == 110000 # GP-Test isA044836(110000) == 1 # vector(100,n, A338754(n)) - \ # vector(100,n, A044836(n)) # GP-Test vector(99,n, A338754(n)) == \ # GP-Test vector(99,n, A044836(n)) #-------- # GP-DEFINE \\ count num even length runs # GP-DEFINE A044941(n) = { # GP-DEFINE my(v = vector_run_lengths(digits(n)) % 2, # GP-DEFINE num_odd = hammingweight(v)); # GP-DEFINE #v - num_odd; # GP-DEFINE } # GP-Test my(v=OEIS_samples("A044941")); \ # GP-Test vector(#v,n, A044941(n)) == v /* OFFSET=1 */ # GP-Test my(g=OEIS_bfile_gf("A044941")); \ # GP-Test g==Polrev(vector(poldegree(g)+1,n,n--;A044941(n))) # poldegree(OEIS_bfile_gf("A044941")) # ~/OEIS/b044941.txt # GP-Test A044941(1100) == 2 # GP-Test A044941(11000) == 1 # GP-Test A044941(110000) == 2 # GP-Test A044941(11000011) == 3 # GP-Test A044941(110000211) == 3 # GP-Test A044941(1100002113) == 3 # GP-Test A044941(11000021133333) == 3 # GP-Test A044941(1000021133333) == 2 # GP-DEFINE A044941_compact(n) = { # GP-DEFINE if(n==0,0, my(v=digits(n),p=1,ret=0); # GP-DEFINE for(i=2,#v, if(v[i]!=v[i-1], ret+=(i-p+1)%2; p=i)); # GP-DEFINE ret+(#v-p)%2); # GP-DEFINE } # GP-DEFINE \\ print("run p="p" to i="i); # GP-DEFINE \\ print("final p="p" to #v="#v); # GP-Test vector(10000,n,n--; A044941(n)) == \ # GP-Test vector(10000,n,n--; A044941_compact(n)) # GP-Test A044941_compact(0) == 0 # GP-Test for(rep=1,1000, \ # GP-Test my(runs=vector(random(20)+1,i, random(10)+1), \ # GP-Test vecs=vector(#runs,i, vector(runs[i],j, i%9+1)), \ # GP-Test n=fromdigits(if(#vecs==0,[],concat(vecs)))); \ # GP-Test A044941_compact(n) == A044941(n) || error(n)); \ # GP-Test 1; # my(c=0); for(n=0,1000000, if(A044941_compact(n) != A044941(n), print(n); if(c++>20,break))); # A044941(11) # A044941(111) # A044941_compact(110011001100) # A044941_compact(11000) # vector(4,k,k++; sum(n=1,10^k-1, A044941(n))) # vector(4,k,k++; sum(n=1,10^k, A044941(n))) # 11, 1100, 110011, 11001100, 1100110011 # apply(A044941, [11, 1100, 110011, 11001100, 1100110011]) # A153435 # my(t=0); for(n=1,1000000, if(A044941(n)==t, print(t" "n); t++)) #------------------------------------------------------------------------------ # A057300 -- self-inverse permutation N at transpose Y,X, radix=2 # A163327 -- N at transpose Y,X, radix=3 # A126006 -- N at transpose Y,X, radix=4 # A217558 -- N at transpose Y,X, radix=16, conceived as 1-byte nibble swap # more bases # not in OEIS: 5,10,15,20,1,6,11,16,21,2,7,12,17 # not in OEIS: 6,12,18,24,30,1,7,13,19,25,31,2,8 # not in OEIS: 7,14,21,28,35,42,1,8,15,22,29,36 # not in OEIS: 8,16,24,32,40,48,56,1,9,17,25,33 # not in OEIS: 9,18,27,36,45,54,63,72,1,10,19,28 # not in OEIS: 10,20,30,40,50,60,70,80,90,1,11,21,31,41,51,61,71,81,91 foreach my $elem (['A057300', 2], ['A163327', 3], ['A126006', 4], ['A217558', 16], ) { my ($anum,$radix) = @$elem; MyOEIS::compare_values (anum => $anum, func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::ZOrderCurve->new (radix => $radix); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); ($x, $y) = ($y, $x); my $n = $path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); } # A126007 - base 4 low digit fixed, X <-> Y flips above there MyOEIS::compare_values (anum => q{A126007}, # not much path interpretation func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::ZOrderCurve->new (radix => 4); for (my $n = $path->n_start; @got < $count; $n++) { my $low = $n & 3; my ($x,$y) = $path->n_to_xy ($n >> 2); my $high = $path->xy_to_n ($y,$x); push @got, ($high << 2) + $low; } return \@got; }); #------------------------------------------------------------------------------ # A163325 -- radix=3 X coordinate MyOEIS::compare_values (anum => q{A163325}, # catalogued too func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::ZOrderCurve->new (radix => 3); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $x; } return \@got; }); # A163326 -- radix=3 Y coordinate MyOEIS::compare_values (anum => q{A163326}, # catalogued too func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::ZOrderCurve->new (radix => 3); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $y; } return \@got; }); # GP-DEFINE \\ extract ternary digit k of n # GP-DEFINE A030341(n,k) = (n\3^k)%3; # # GP-DEFINE \\ powers of 3 alternating with 0s # GP-DEFINE A254006(n) = if(n%2==0, 3^(n/2), 0); # GP-Test my(v=OEIS_samples("A254006")); /* OFFSET=0 */ \ # GP-Test vector(#v,n,n--; A254006(n)) == v # GP-Test my(g=OEIS_bfile_gf("A254006")); \ # GP-Test g==Polrev(vector(poldegree(g)+1,n,n--;A254006(n))) # poldegree(OEIS_bfile_gf("A254006")) # # GP-DEFINE A163325(n) = fromdigits(digits(n, 9)%3, 3); \\ mine # # GP-Test /* Philippe Deleham in A163325 */ \ # GP-Test my(b=A254006); \ # GP-Test vector(3^6,n,n--; A163325(n)) == \ # GP-Test vector(3^6,n,n--; sum(k=0,if(n,logint(n,3)), A030341(n,k)*b(k))) #------------------------------------------------------------------------------ # A163328 -- radix=3 zorder N of an x,y point in diagonals order, same axis MyOEIS::compare_values (anum => 'A163328', func => sub { my ($count) = @_; my @got; my $zorder = Math::PlanePath::ZOrderCurve->new (radix => 3); my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up', n_start => 0); for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal->n_to_xy ($n); push @got, $zorder->xy_to_n ($x, $y); } return \@got; }); # A163329 -- radix=3 diagonals N of an x,y point in zorder # inverse perm of A163328 MyOEIS::compare_values (anum => 'A163329', func => sub { my ($count) = @_; my @got; my $zorder = Math::PlanePath::ZOrderCurve->new (radix => 3); my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up', n_start => 0); for (my $n = $zorder->n_start; @got < $count; $n++) { my ($x, $y) = $zorder->n_to_xy ($n); push @got, $diagonal->xy_to_n ($x, $y); } return \@got; }); # GP-DEFINE \\ diagonal locations, Z-order N # GP-DEFINE A163328(n) = { # GP-DEFINE my(d=(sqrtint(8*n+1)-1)\2); n -= d*(d+1)/2; # GP-DEFINE subst(Pol(3*digits(n,3)) + Pol(digits(d-n,3)),'x,9); # GP-DEFINE } # GP-Test my(v=OEIS_samples("A163328")); /* OFFSET=0 */ \ # GP-Test v == vector(#v,n,n--; A163328(n)) # GP-Test my(g=OEIS_bfile_gf("A163328")); \ # GP-Test g==Polrev(vector(poldegree(g)+1,n,n--;A163328(n))) # poldegree(OEIS_bfile_gf("A163328")) # vector(20,n,n--; A163328(n)) # OEIS_samples("A163328") # GP-Test my(x,y); vector(9,n,n--; [y,x]=divrem(n,3); x+y) == \ # GP-Test [0,1,2, 1,2,3, 2,3,4] # GP-Test my(x,y); vector(9,n,n--; [y,x]=divrem(n,3); x+3*y) == \ # GP-Test [0..8] # GP-DEFINE \\ Z-order locations, diagonal N # GP-DEFINE { my(table=[0,1,2, 1,2,3, 2,3,4]); # GP-DEFINE A163329(n) = my(v=digits(n,9)); # GP-DEFINE ( fromdigits(apply(d->table[d+1],v),3)^2 # GP-DEFINE + fromdigits(v,3) )/2; # GP-DEFINE } # GP-Test my(v=OEIS_samples("A163329")); /* OFFSET=0 */ \ # GP-Test v == vector(#v,n,n--; A163329(n)) # GP-Test my(g=OEIS_bfile_gf("A163329")); \ # GP-Test g==Polrev(vector(poldegree(g)+1,n,n--;A163329(n))) # poldegree(OEIS_bfile_gf("A163329")) # vector(20,n,n--; A163329(n)) #------------------------------------------------------------------------------ # A163330 -- radix=3 diagonals opposite axis MyOEIS::compare_values (anum => 'A163330', func => sub { my ($count) = @_; my @got; my $zorder = Math::PlanePath::ZOrderCurve->new (radix => 3); my $diagonal = Math::PlanePath::Diagonals->new (direction => 'down', n_start => 0); for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal->n_to_xy ($n); push @got, $zorder->xy_to_n ($x, $y); } return \@got; }); # A163331 -- radix=3 diagonals same axis, inverse MyOEIS::compare_values (anum => 'A163331', func => sub { my ($count) = @_; my @got; my $zorder = Math::PlanePath::ZOrderCurve->new (radix => 3); my $diagonal = Math::PlanePath::Diagonals->new (direction => 'down', n_start => 0); for (my $n = $zorder->n_start; @got < $count; $n++) { my ($x, $y) = $zorder->n_to_xy ($n); push @got, $diagonal->xy_to_n ($x, $y); } return \@got; }); # GP-DEFINE A163330(n) = { # GP-DEFINE my(d=(sqrtint(8*n+1)-1)\2); n -= d*(d+1)/2; # GP-DEFINE subst(Pol(digits(n,3)) + Pol(3*digits(d-n,3)),'x,9); # GP-DEFINE } # GP-Test my(v=OEIS_samples("A163330")); /* OFFSET=0 */ \ # GP-Test v == vector(#v,n,n--; A163330(n)) # GP-Test my(g=OEIS_bfile_gf("A163330")); \ # GP-Test g==Polrev(vector(poldegree(g)+1,n,n--;A163330(n))) # poldegree(OEIS_bfile_gf("A163330")) # vector(20,n,n--; A163330(n)) # OEIS_samples("A163330") # GP-Test my(x,y); vector(9,n,n--; [y,x]=divrem(n,3); x+y) == \ # GP-Test [0,1,2, 1,2,3, 2,3,4] # GP-Test my(x,y); vector(9,n,n--; [y,x]=divrem(n,3); 3*x+y) == \ # GP-Test [0,3,6, 1,4,7, 2,5,8] # GP-DEFINE { my(table1=[0,1,2, 1,2,3, 2,3,4], # GP-DEFINE table2=[0,3,6, 1,4,7, 2,5,8]); # GP-DEFINE A163331(n) = my(v=digits(n,9)); # GP-DEFINE ( fromdigits([table1[d+1]|d<-v],3)^2 # GP-DEFINE + fromdigits([table2[d+1]|d<-v],3) )/2; # GP-DEFINE } # GP-Test my(v=OEIS_samples("A163331")); /* OFFSET=0 */ \ # GP-Test v == vector(#v,n,n--; A163331(n)) # GP-Test my(g=OEIS_bfile_gf("A163331")); \ # GP-Test g==Polrev(vector(poldegree(g)+1,n,n--;A163331(n))) # poldegree(OEIS_bfile_gf("A163331")) # vector(20,n,n--; A163331(n)) #------------------------------------------------------------------------------ # A054238 -- permutation, binary, diagonals same axis MyOEIS::compare_values (anum => 'A054238', func => sub { my ($count) = @_; my @got; my $zorder = Math::PlanePath::ZOrderCurve->new; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up', n_start => 0); for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal->n_to_xy ($n); push @got, $zorder->xy_to_n ($x, $y); } return \@got; }); # A054239 -- diagonals same axis, binary, inverse MyOEIS::compare_values (anum => 'A054239', func => sub { my ($count) = @_; my @got; my $zorder = Math::PlanePath::ZOrderCurve->new; my $diagonal = Math::PlanePath::Diagonals->new (direction => 'up', n_start => 0); for (my $n = $zorder->n_start; @got < $count; $n++) { my ($x, $y) = $zorder->n_to_xy ($n); push @got, $diagonal->xy_to_n ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/DiamondSpiral-oeis.t0000644000175000017500000002227313774446470017176 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 7; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::DiamondSpiral; my $path = Math::PlanePath::DiamondSpiral->new; # GP-DEFINE read("my-oeis.gp"); #------------------------------------------------------------------------------ # A217296 -- permutation DiamondSpiral -> SquareSpiral rotate +90 # 1 2 3 4 5 6 7 8 # 1, 4, 6, 8, 2, 3, 15, 5, MyOEIS::compare_values (anum => 'A217296', func => sub { my ($count) = @_; my @got; require Math::PlanePath::SquareSpiral; my $square = Math::PlanePath::SquareSpiral->new; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); ($x,$y) = (-$y,$x); # rotate +90 push @got, $square->xy_to_n ($x, $y); } return \@got; }); # inverse # not in OEIS: 1,3,10,4,12,5,6,2,8,18,9,20,35,21,11,23,39,24,13,14 # MyOEIS::compare_values # (anum => 'A217296', # func => sub { # my ($count) = @_; # my @got; # require Math::PlanePath::SquareSpiral; # my $square = Math::PlanePath::SquareSpiral->new; # for (my $n = $path->n_start; @got < $count; $n++) { # my ($x, $y) = $square->n_to_xy ($n); # ($x,$y) = (-$y,$x); # rotate +90 # push @got, $path->xy_to_n ($x, $y); # } # return \@got; # }); #------------------------------------------------------------------------------ # A184636 -- N on Y axis, from Y=2 onwards, if this really is 2*n^2 MyOEIS::compare_values (anum => 'A184636', func => sub { my ($count) = @_; my $path = Math::PlanePath::DiamondSpiral->new (n_start => 0); my @got = (3); for (my $y = 2; @got < $count; $y++) { push @got, $path->xy_to_n(0,$y); } return \@got; }); # A184636 = floor(1/{(n^4+2*n)^(1/4)}), where {}=fractional part # maybe 2*n^2 # GP-Test my(v=OEIS_samples("A184636")); /* OFFSET=1 */ \ # GP-Test vector(#v,n, 2*n^2 + if(n==1,1)) == v # vector(10,n, (n^4+2*n)^(1/4)) # vector(10,n, (n^4+2*n)^(1/4) - n) # floor((n^4+2*n)^(1/4)) = n # GP-Test vector(1000,n, sqrtnint(n^4+2*n, 4)) == \ # GP-Test vector(1000,n, n) # GP-Test (n+1)^4 == n^4 + 4*n^3 + 6*n^2 + 4*n + 1 # factor(4*n^3 + 6*n^2 + 4*n + 1 -2*n) # # (n^4+2*n)^(1/4) = floor( (n^4+2*n)^(1/4) ) + frac( (n^4+2*n)^(1/4) ) # (x+1)^(1/4) # apply(numerator,Vec((x+1)^(1/4))) # apply(denominator,Vec((x+1)^(1/4))) # (n^4+2*n)^(1/4) = n + F # F = (n^4+2*n)^(1/4) - n # A184636 = floor( 1/F ) # 0 <= 1/F - 2*n^2 < 1 ? # vector(10,n, (n^4+2*n)^(1/4) - n) # vector(10,n, 1/( (n^4+2*n)^(1/4) - n ) - 2*n^2 ) # plot(n=1,10, 1/( (n^4+2*n)^(1/4) - n ) - 2*n^2 ) # # 1/( (n^4+2*n)^(1/4) - n ) - 2*n^2 = y # 1/( (n^4+2*n)^(1/4) - n ) = y + 2*n^2 # (y + 2*n^2)*( (n^4+2*n)^(1/4) - n ) = 1 # (y + 2*n^2)* (n^4+2*n)^(1/4) = (y + 2*n^2)*n + 1 # (y + 2*n^2)^4 * (n^4+2*n) = ((y + 2*n^2)*n + 1)^4 # (y + 2*n^2)^4 * (n^4+2*n) - ((y + 2*n^2)*n + 1)^4 = 0 # GP-Test subst( (y + 2*n^2)^4 * (n^4+2*n) - ((y + 2*n^2)*n + 1)^4, y, 0) == \ # GP-Test -24*n^6 - 8*n^3 - 1 # GP-Test subst( (y + 2*n^2)^4 * (n^4+2*n) - ((y + 2*n^2)*n + 1)^4, y, 1) == \ # GP-Test 16*n^7 - 24*n^6 + 24*n^5 - 24*n^4 + 4*n^3 - 6*n^2 - 2*n - 1 # 1/( (n^4+2*n)^(1/4) - n ) - 2*n^2 < 0 # 1/( (n^4+2*n)^(1/4) - n ) < 2*n^2 # 2*n^2 * ( (n^4+2*n)^(1/4) - n ) > 1 # 2*n^2*(n^4+2*n)^(1/4) - 2*n^3 > 1 # 2*n^2*(n^4+2*n)^(1/4) > 2*n^3 + 1 # (2*n^2)^4*(n^4+2*n) > (2*n^3 + 1)^4 # (2*n^2)^4*(n^4+2*n) - (2*n^3 + 1)^4 > 0 # -24*n^6 - 8*n^3 - 1 > 0 # 24*n^6 + 8*n^3 + 1 > 0 # # 1/( (n^4+2*n)^(1/4) - n ) - 2*n^2 > 1 # 1/( (n^4+2*n)^(1/4) - n ) > 2*n^2 + 1 # (2*n^2 + 1) * ( (n^4+2*n)^(1/4) - n ) < 1 # (2*n^2+1)*(n^4+2*n)^(1/4) - (2*n^2+1)*n < 1 # (2*n^2+1)*(n^4+2*n)^(1/4) < (2*n^2+1)*n + 1 # (2*n^2+1)^4*(n^4+2*n) < ((2*n^2+1)*n + 1)^4 # (2*n^2+1)^4*(n^4+2*n) - ((2*n^2+1)*n + 1)^4 < 0 # 16*n^7 - 24*n^6 + 24*n^5 - 24*n^4 + 4*n^3 - 6*n^2 - 2*n - 1 < 0 #------------------------------------------------------------------------------ # A188551 -- N positions of turns Nstart=-1 MyOEIS::compare_values (anum => 'A188551', max_value => 100_000, func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'DiamondSpiral,n_start=-1', turn_type => 'LSR'); while (@got < $count) { my ($i,$value) = $seq->next; if ($value != 0 && $i >= 1) { push @got, $i; } } return \@got; }); # also prime MyOEIS::compare_values (anum => 'A188552', max_value => 100_000, func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; require Math::Prime::XS; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'DiamondSpiral,n_start=-1', turn_type => 'LSR'); while (@got < $count) { my ($i,$value) = $seq->next; if ($value != 0 && $i >= 1 && Math::Prime::XS::is_prime($i)) { push @got, $i; } } return \@got; }); #------------------------------------------------------------------------------ # A217015 -- permutation SquareSpiral rotate -90 -> DiamondSpiral # 1 2 3 4 5 6 # 1, 5, 6, 2, 8, 3, 10, 4, # # 19 3 # / \ # 20 9 18 2 # / / \ # 21 10---3---8 17 1 # / / | |\ \ # 22 11 4 1 2 7 16 <- Y=0 # \ \ | | / / # 23 12 5---6 15 ... -1 # \ \ / / # 24 13--14 27 -2 # \ / # 25--26 -3 # 37--36--35--34--33--32--31 3 # | | # 38 17--16--15--14--13 30 2 # | | | | # 39 18 5---4---3 12 29 1 # | | | | | | # 40 19 6 1---2 11 28 ... <- Y=0 # | | | | | | # 41 20 7---8---9--10 27 52 -1 # | | | | # 42 21--22--23--24--25--26 51 -2 # | | # 43--44--45--46--47--48--49--50 -3 MyOEIS::compare_values (anum => 'A217015', func => sub { my ($count) = @_; my @got; require Math::PlanePath::SquareSpiral; my $square = Math::PlanePath::SquareSpiral->new; for (my $n = $square->n_start; @got < $count; $n++) { my ($x, $y) = $square->n_to_xy ($n); ($x,$y) = ($y,-$x); # rotate -90 push @got, $path->xy_to_n ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ # A215468 -- N sum 8 neighbours MyOEIS::compare_values (anum => 'A215468', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); push @got, ($path->xy_to_n($x+1,$y) + $path->xy_to_n($x-1,$y) + $path->xy_to_n($x,$y+1) + $path->xy_to_n($x,$y-1) + $path->xy_to_n($x+1,$y+1) + $path->xy_to_n($x-1,$y-1) + $path->xy_to_n($x-1,$y+1) + $path->xy_to_n($x+1,$y-1)); } return \@got; }); #------------------------------------------------------------------------------ # A215471 -- primes with >=5 prime neighbours in 8 surround MyOEIS::compare_values (anum => 'A215471', func => sub { my ($count) = @_; require Math::Prime::XS; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); my $num = ((!! Math::Prime::XS::is_prime ($path->xy_to_n($x+1,$y))) + (!! Math::Prime::XS::is_prime ($path->xy_to_n($x-1,$y))) + (!! Math::Prime::XS::is_prime ($path->xy_to_n($x,$y+1))) + (!! Math::Prime::XS::is_prime ($path->xy_to_n($x,$y-1))) + (!! Math::Prime::XS::is_prime ($path->xy_to_n($x+1,$y+1))) + (!! Math::Prime::XS::is_prime ($path->xy_to_n($x-1,$y-1))) + (!! Math::Prime::XS::is_prime ($path->xy_to_n($x-1,$y+1))) + (!! Math::Prime::XS::is_prime ($path->xy_to_n($x+1,$y-1))) ); if ($num >= 5) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/Staircase-oeis.t0000644000175000017500000000450213775045424016354 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2018, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::Staircase; use Math::PlanePath::Diagonals; #------------------------------------------------------------------------------ # A210521 - staircase points traversed by diagonals { my $diag = Math::PlanePath::Diagonals->new (direction => 'down'); my $stair = Math::PlanePath::Staircase->new; MyOEIS::compare_values (anum => 'A210521', func => sub { my ($count) = @_; my @got; for (my $n = $diag->n_start; @got < $count; $n++) { my ($x, $y) = $diag->n_to_xy ($n); push @got, $stair->xy_to_n($x,$y); } return \@got; }); # A199855 - inverse MyOEIS::compare_values (anum => 'A199855', func => sub { my ($count) = @_; my @got; for (my $n = $stair->n_start; @got < $count; $n++) { my ($x, $y) = $stair->n_to_xy ($n); push @got, $diag->xy_to_n($x,$y); } return \@got; }); } #------------------------------------------------------------------------------ # A128918 -- N on X axis except initial 1,1 MyOEIS::compare_values (anum => 'A128918', func => sub { my ($count) = @_; my $path = Math::PlanePath::Staircase->new (n_start => 2); my @got = (1,1); for (my $x = 0; @got < $count; $x++) { my $n = $path->xy_to_n ($x, 0); push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/GcdRationals-oeis.t0000644000175000017500000001320113475105274017001 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 7; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::GcdRationals; #------------------------------------------------------------------------------ # A178340 Bernoulli denominator = int(X/Y) + 1 # Not quite since A178340 is reduced rational. First different at n=49. # # MyOEIS::compare_values # (anum => q{A178340}, # func => sub { # my ($count) = @_; # my $path = Math::PlanePath::GcdRationals->new; # my @got = (1); # for (my $n = $path->n_start; @got < $count; $n++) { # my ($x,$y) = $path->n_to_xy($n); # push @got, int($x/$y) + 1; # } # return \@got; # }); # ceil(X/Y) # not in OEIS: 1,2,1,3,1,1,4,1,2,1,5,1,1,1,1,6,1,2,3,2,1,7,1,1,1,1,1,1,8,1,2,1,4,1,2,1,9,1,1,3,1,1,3,1,1,10,1,2,1,2,5,2,1,2,1,11,1,1,1,1,1,1,1,1,1,1,12,1,2,3,4,1,6,1,4,3,2,1,13 # not A178340 denominator of coeffs in Bernoulli triangle # # MyOEIS::compare_values # (anum => 'A178340', # func => sub { # my ($count) = @_; # my $path = Math::PlanePath::GcdRationals->new # (pairs_order => 'rows_reverse'); # my @got; # my $n_start = $path->n_start; # for (my $n = $n_start; @got < $count; $n++) { # my ($x,$y) = $path->n_to_xy($n); # push @got, div_ceil($x,$y); # } # return \@got; # }); sub div_ceil { my ($n,$d) = @_; return int (($n+$d-1) / $d); } #------------------------------------------------------------------------------ # A050873 = int(X/Y) + A023532 # so int(X/Y) = A050873 - A023532 { my ($b2) = MyOEIS::read_values('A023532'); MyOEIS::compare_values (anum => 'A050873', max_count => scalar(@$b2), func => sub { my ($count) = @_; my $path = Math::PlanePath::GcdRationals->new; my @got; my $n_start = $path->n_start; for (my $n = $n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, int($x/$y) + $b2->[$n-$n_start]; } return \@got; }); } #------------------------------------------------------------------------------ # A033638 - diagonals_down X=1 column, quarter squares + 1, squares+pronic + 1 MyOEIS::compare_values (anum => 'A033638', func => sub { my ($count) = @_; my $path = Math::PlanePath::GcdRationals->new (pairs_order => 'diagonals_down'); my @got = (1); for (my $y = 1; @got < $count; $y++) { push @got, $path->xy_to_n(1,$y); } return \@got; }); #------------------------------------------------------------------------------ # A002061 - X axis pairs_order=diagonals_up, central polygonals MyOEIS::compare_values (anum => 'A002061', func => sub { my ($count) = @_; my $path = Math::PlanePath::GcdRationals->new (pairs_order => 'diagonals_up'); my @got = (1); for (my $x = 1; @got < $count; $x++) { push @got, $path->xy_to_n($x,1); } return \@got; }); #------------------------------------------------------------------------------ # A000124 - Y axis pairs_order=rows (the default), triangular+1 MyOEIS::compare_values (anum => 'A000124', func => sub { my ($count) = @_; my $path = Math::PlanePath::GcdRationals->new; my @got; for (my $y = 1; @got < $count; $y++) { push @got, $path->xy_to_n(1,$y); } return \@got; }); #------------------------------------------------------------------------------ # A000290 - X axis pairs_order=diagonals_down, perfect squares MyOEIS::compare_values (anum => 'A000290', func => sub { my ($count) = @_; my $path = Math::PlanePath::GcdRationals->new (pairs_order => 'diagonals_down'); my @got = (0); for (my $x = 1; @got < $count; $x++) { push @got, $path->xy_to_n($x,1); } return \@got; }); #------------------------------------------------------------------------------ # A002620 - Y axis pairs_order=diagonals_up, squares and pronic MyOEIS::compare_values (anum => 'A002620', func => sub { my ($count) = @_; my $path = Math::PlanePath::GcdRationals->new (pairs_order => 'diagonals_up'); my @got = (0,0); for (my $y = 1; @got < $count; $y++) { push @got, $path->xy_to_n(1,$y); } return \@got; }); #------------------------------------------------------------------------------ # A002522 - Y=X+1 above diagonal pairs_order=diagonals_up, squares+1 MyOEIS::compare_values (anum => 'A002522', func => sub { my ($count) = @_; my $path = Math::PlanePath::GcdRationals->new (pairs_order => 'diagonals_up'); my @got = (1); for (my $i = 1; @got < $count; $i++) { push @got, $path->xy_to_n($i,$i+1); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/MPeaks-oeis.t0000644000175000017500000000570513475335447015630 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 5; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::MPeaks; #------------------------------------------------------------------------------ # A049450 -- N on Y axis, n_start=0, extra initial 0 MyOEIS::compare_values (anum => 'A049450', func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::MPeaks->new (n_start => 0); for (my $y = 0; @got < $count; $y++) { push @got, $path->xy_to_n (0,$y); } return \@got; }); #------------------------------------------------------------------------------ # A056106 -- N on Y axis, n_start=1, extra initial 1 MyOEIS::compare_values (anum => 'A056106', func => sub { my ($count) = @_; my @got = (1); my $path = Math::PlanePath::MPeaks->new; for (my $y = 0; @got < $count; $y++) { push @got, $path->xy_to_n (0,$y); } return \@got; }); #------------------------------------------------------------------------------ # A027599 -- N on Y axis, n_start=2, extra initial 6,2 MyOEIS::compare_values (anum => 'A027599', func => sub { my ($count) = @_; my @got = (6,2); my $path = Math::PlanePath::MPeaks->new (n_start => 2); for (my $y = 0; @got < $count; $y++) { push @got, $path->xy_to_n (0,$y); } return \@got; }); #------------------------------------------------------------------------------ # A056109 -- N on X negative axis MyOEIS::compare_values (anum => 'A056109', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::MPeaks->new; for (my $x = -1; @got < $count; $x--) { push @got, $path->xy_to_n ($x,0); } return \@got; }); #------------------------------------------------------------------------------ # A045944 -- N on X axis MyOEIS::compare_values (anum => 'A045944', func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::MPeaks->new; for (my $x = 1; @got < $count; $x++) { push @got, $path->xy_to_n ($x,0); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/PythagoreanTree-oeis.t0000644000175000017500000012273113675637351017551 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2016, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min','max','sum'; use Math::BigInt try => 'GMP'; use Tie::Array::Sorted; use Test; plan tests => 54; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::PythagoreanTree; use Math::PlanePath::GcdRationals; *gcd = \&Math::PlanePath::GcdRationals::_gcd; # uncomment this to run the ### lines # use Smart::Comments; # A024408 perimeters occurring more than once #------------------------------------------------------------------------------ # Helpers # GP-DEFINE read("my-oeis.gp"); sub pq_acceptable { my ($p,$q) = @_; return ($p > $q && $q >= 1 && ($p % 2) != ($q % 2) && gcd($p,$q) == 1); } { my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); my $bad = 0; foreach my $p (-10, 30) { foreach my $q (-10, 30) { unless (pq_acceptable($p,$q) == $path->xy_is_visited($p,$q)) { $bad++; } } } ok ($bad, 0); } sub perimeter_of_pq { my ($p,$q) = @_; return 2*$p*($p+$q); } { my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); my $path_AB = Math::PlanePath::PythagoreanTree->new (coordinates => 'AB'); my $path_AC = Math::PlanePath::PythagoreanTree->new (coordinates => 'AC'); my $bad = 0; foreach my $n ($path->n_start .. 50) { my ($p,$q) = $path->n_to_xy($n); my ($A,$B) = $path_AB->n_to_xy($n); my ($A_again,$C) = $path_AC->n_to_xy($n); unless (perimeter_of_pq($p,$q) == $A+$B+$C) { $bad++; } } ok ($bad, 0); } #------------------------------------------------------------------------------ # A009096 perimeters of all triples, with multiplicity MyOEIS::compare_values (anum => 'A009096', # max_count => 66, func => sub { my ($count) = @_; my @primitives; my $aref = perimeterpqs_list_new(); my $max_perimeter = 0; for (;;) { my $elem = perimeterpqs_list_next($aref); my ($perimeter,$p,$q) = @$elem; last if @primitives >= $count && $perimeter != $max_perimeter; $max_perimeter = $perimeter; push @primitives, [$p*$p-$q*$q, 2*$p*$q, $p*$p+$q*$q]; } my @multiples; foreach my $triple (@primitives) { my ($A_primitive,$B_primitive,$C_primitive) = @$triple; for (my $i = 1; ; $i++) { my ($A,$B,$C) = ($i*$A_primitive, $i*$B_primitive, $i*$C_primitive); last if $A+$B+$C > $max_perimeter; push @multiples, [$A,$B,$C]; } } @multiples = sort triple_cmp_by_perimeter_and_decreasing_area @multiples; my @got = map {sum(@$_)} @multiples; $#got = $count-1; return \@got; }); #------------------------------------------------------------------------------ # A103605 all triples, primitive and not, # ordered by increasing perimeter, then by decreasing area # GP-DEFINE A = (p^2 - q^2)*m; # GP-DEFINE B = 2*p*q*m; # GP-DEFINE C = (p^2 + q^2)*m; # GP-DEFINE A2 = (p2^2 - q2^2)*m2; # GP-DEFINE B2 = 2*p2*q2*m2; # GP-DEFINE C2 = (p2^2 + q2^2)*m2; # GP-DEFINE per = A + B + C; # GP-DEFINE per2 = A2 + B2 + C2; # GP-DEFINE area = A*B; # GP-DEFINE area2 = A2*B2; # GP-Test A == (p+q)*(p-q)*m # GP-Test A*B == (p+q)*(p-q) *m^2 * 2*p*q # GP-Test A2*B2 == (p2+q2)*(p2-q2)*m2^2 * 2*p2*q2 # (small-small2)*(A*B - A2*B2) >=0 ? # so order by area same as order by A ? # GP-Test per == 2*m*p^2 + 2*m*p*q # GP-Test per == 2*m*p*(p+q) # GP-Test per == A * (2*p)/(p-q) # GP-Test per == B * (p+q)/q # GP-Test A == per * (p-q)/(2*p) # GP-Test A == per * 1/2*(1-q/p) # GP-Test B == per * q/(p+q) # GP-Test A*B == per^2 * (p-q)/(2*p) * q/(p+q) # GP-Test A*B == per^2 * 1/2 * q/p * (p-q)/(p+q) # GP-Test A*B == per^2 * 1/2 * (1-q/p)/(1 + p/q) # GP-DEFINE halfperimeter_to_mpq_list(h) = { # GP-DEFINE my(l=List([])); # GP-DEFINE fordiv(h,m, # GP-DEFINE my(M=h/m); # GP-DEFINE fordiv(M,p, # GP-DEFINE p>=2 || next; # GP-DEFINE my(p_plus_q = M/p, # GP-DEFINE q = p_plus_q - p); # GP-DEFINE q>=1 || next; # GP-DEFINE p>q || next; # GP-DEFINE if(gcd(p,q)==1, # GP-DEFINE listput(l,[m,p,q])))); # GP-DEFINE Vec(l); # GP-DEFINE } # for(h=1,20, \ # my(l=halfperimeter_to_mpq_list(h)); \ # print(h" "l); \ # for(i=1,#l, \ # my(m,p,q); [m,p,q]=l[i]; \ # my(a=(p^2-q^2)*m, \ # b=2*p*q*m, \ # c=(p^2+q^2)*m); \ # print(" "p","q" *"m" "a","b","c); \ # a^2 + b^2 == c^2 || error(); \ # )) sub triple_sans_gcd { my ($triple) = @_; my ($A,$B,$C) = @$triple; my $g = gcd($A,gcd($B,$C)); return [$A/$g, $B/$g, $C/$g]; } # $a and $b are arrayrefs [$A,$B,$C] legs of a triple sub triple_cmp_by_perimeter_and_decreasing_area { # return sum(@$a) <=> sum(@$b) # perimeter # # || $a->[0]*$a->[1] <=> $b->[0]*$b->[1] # area increasing # # || $b->[0]*$b->[1] <=> $a->[0]*$a->[1] # area decreasing # || -($a->[0]*$a->[1]*$a->[2] <=> $b->[0]*$b->[1]*$b->[2] ) # || die "oops, same perimeter and area"; if (my $order = sum(@$a) <=> sum(@$b)) { return $order; } return $b->[0]*$b->[2] <=> $a->[0]*$a->[2]; # my $a = triple_sans_gcd($a); # my $b = triple_sans_gcd($b); # return $b->[0]*$b->[1] <=> $a->[0]*$a->[1]; # area decreasing } sub triple_cmp_by_perimeter_and_even { return sum(@$a) <=> sum(@$b) # perimeter || $a->[1] <=> $b->[1] # even member increasing || die "oops, same perimeter and area"; } # 20,48,52, 24,45,51, 30,40,50 bfile # 30,40,50, 24,45,51, 20,48,52 # ~/OEIS/b103605.txt # 22 15 # 23 20 # 24 25 # # 25 10 # 26 24 # 27 26 # # GP-Test 15+20+25 == 60 # GP-Test 10+24+26 == 60 # GP-Test 15*20 == 300 # GP-Test 10*24 == 240 # GP-Test gcd([15,20,25]) == 5 # GP-Test gcd([10,24,26]) == 2 # GP-Test [15,20,25]/5 == [3,4,5] # GP-Test [10,24,26]/2 == [5,12,13] # ~/OEIS/b103605.txt # /tmp/x.txt # 58 20 # 59 48 # 60 52 # # 61 24 # 62 45 # 63 51 # # 64 30 # 65 40 # 66 50 # # GP-Test 20+48+52 == 120 # GP-Test 24+45+51 == 120 # GP-Test 30+40+50 == 120 # GP-Test 20*48/2 == 480 # GP-Test 24*45/2 == 540 # GP-Test 30*40/2 == 600 # GP-Test gcd([20,48,52]) == 4 # GP-Test gcd([24,45,51]) == 3 # GP-Test gcd([30,40,50]) == 10 # GP-Test [20,48,52]/4 == [5,12,13] # GP-Test [24,45,51]/3 == [8,15,17] # GP-Test [30,40,50]/10 == [3,4,5] # 3,4,5, 6,8,10, 5,12,13, 9,12,15, 8,15,17, 12,16,20, 7,24,25, 15,20,25, # 10,24,26, 20,21,29, 18,24,30, 16,30,34, 21,28,35, 12,35,37, 15,36,39, # 9,40,41, 24,32,40 MyOEIS::compare_values (anum => q{A103605}, max_count => 3*100, name => 'all triples (primitive and not) by perimeter then something', func => sub { my ($count) = @_; my @primitives; my $aref = perimeterpqs_list_new(); my $max_perimeter = 0; for (;;) { my $elem = perimeterpqs_list_next($aref); my ($perimeter,$p,$q) = @$elem; last if @primitives >= $count && $perimeter != $max_perimeter; $max_perimeter = $perimeter; push @primitives, [$p*$p-$q*$q, 2*$p*$q, $p*$p+$q*$q]; } my @multiples; foreach my $triple (@primitives) { my ($A_primitive,$B_primitive,$C_primitive) = @$triple; for (my $i = 1; ; $i++) { my ($A,$B,$C) = ($i*$A_primitive, $i*$B_primitive, $i*$C_primitive); last if $A+$B+$C > $max_perimeter; push @multiples, [$A,$B,$C]; } } @multiples = sort triple_cmp_by_perimeter_and_decreasing_area @multiples; # @multiples = sort triple_cmp_by_perimeter_and_even @multiples; my @got = map {sort {$a<=>$b} @$_} @multiples; $#got = $count-1; if (0) { open OUT, '> /tmp/x.txt' or die; foreach my $i (0 .. $#got) { print OUT $i+1," ",$got[$i],"\n" or die; } close OUT or die; } return \@got; }); #------------------------------------------------------------------------------ # A024364 - ordered perimeter, with duplications # $elem is an arrayref [$perimeter,$p,$q, ...]. # Return a corresponding [$perimeter,$p,$next_q, ...] # which is the next bigger primitive p,q, and its corresponding perimeter. sub perimeterpq_next_q { my ($elem) = @_; my ($perimeter,$p,$q) = @$elem; my $first = ($q==0); for ($q++; $q < $p; $q++) { if (pq_acceptable($p,$q)) { return [perimeter_of_pq($p,$q), $p, $q, $first]; } } return (); } sub perimeterpq_cmp_perimeter_then_even { my ($a,$b) = @_; return $a->[0] <=> $b->[0] || $a->[1]*$a->[2] <=> $b->[1]*$b->[2] || die "oops, same perimeter and even"; } sub perimeterpqs_list_new { my $p = 2; my $q = 1; tie my @pending, "Tie::Array::Sorted", \&perimeterpq_cmp_perimeter_then_even; push @pending, [perimeter_of_pq($p,$q), $p, $q, 1]; return \@pending; } sub perimeterpqs_list_next { my ($aref) = @_; my $elem = shift @$aref; my ($perimeter,$p,$q,$first) = @$elem; push @$aref, perimeterpq_next_q($elem); if ($first) { ### push new: $p+1 push @$aref, perimeterpq_next_q([0, $p+1, 0]); } return $elem; } MyOEIS::compare_values (anum => 'A024364', # max_count => 121, func => sub { my ($count) = @_; my @got; my $aref = perimeterpqs_list_new(); while (@got < $count) { my $elem = perimeterpqs_list_next($aref); ### list elem: $elem my ($perimeter,$p,$q) = @$elem; push @got, $perimeter; } return \@got; }); #------------------------------------------------------------------------------ # A070109 - how many primitives with perimeter n # A078926 - how many primitives with perimeter 2n (since always even) # includes factorizing for divisors as solutions # Return a list of [$perimeter, $p, $q] # of all perimeters <= $max_perimeter, in no particular order sub perimeterpq_list { my ($max_perimeter) = @_; my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); my @got; # perimeter = 2*p*(p+q) <= M # 2*p^2 < M # p < sqrt(M/2) limit for p # then # p+q <= M/2/p # q <= M/2/p - p foreach my $p (2 .. int(sqrt($max_perimeter/2))) { foreach my $q (1 .. $p) { my $perimeter = perimeter_of_pq($p,$q); last if $perimeter > $max_perimeter; if ($path->xy_is_visited($p,$q)) { push @got, [$perimeter,$p,$q]; } } } return @got; } # Return a list of counts for P = 0 .. $max_perimeter where $counts[$P] is # how many primitive Pythagorean triples have perimeter $P. sub perimeters_counts_array { my ($max_perimeter) = @_; my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); my @got; foreach my $elem (perimeterpq_list($max_perimeter)) { my ($perimeter,$p,$q) = @$elem; $got[$perimeter]++; } foreach my $i (0 .. $max_perimeter) { $got[$i] ||= 0; } return @got; # Tree descents don't really do much in terms of perimeter, may as well # loop over p and q directly. # # my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); # my @pending = Math::BigInt->new($path->n_start); # while (defined (my $n = pop @pending)) { # my ($p,$q) = $path->n_to_xy($n); # my $perimeter = perimeter_of_pq($p,$q); # if ($perimeter <= $max_perimeter) { # $got[$perimeter]++; # } # my $C = $p*$p + $q*$q; # if ($C < $max_perimeter) { # push @pending, $path->tree_n_children($n); # } # } } # A078926 - count of primtive triples with perimeter 2*n # http://oeis.org/A078926/b078926.txt # to n=158730 1.3mb MyOEIS::compare_values (anum => q{A078926}, # max_count => 1000, func => sub { my ($count) = @_; # its OFFSET=1 so 1..$count is perimeters 2..2*$count my @got = perimeters_counts_array(2*$count); @got = @got[map {2*$_} 1 .. $count]; return \@got; }); MyOEIS::compare_values (anum => q{A078926}, # max_count => 1000, func => sub { my ($count) = @_; my @got; my $aref = perimeterpqs_list_new(); for (;;) { my $elem = perimeterpqs_list_next($aref); ### list elem: $elem my ($perimeter,$p,$q) = @$elem; $perimeter /= 2; last if $perimeter > $count; $got[$perimeter]++; } foreach my $i (0 .. $count) { $got[$i] ||= 0; } # its OFFSET=1 so 1..$count is perimeters 2..2*$count shift @got; return \@got; }); # A070109 - count of primtive triples with perimeter n # ~/OEIS/b070109.txt 20000 entries, 150k MyOEIS::compare_values (anum => q{A070109}, # max_count => 1000, func => sub { my ($count) = @_; my @got = perimeters_counts_array($count); shift @got; # no n=0 return \@got; }); MyOEIS::compare_values (anum => q{A070109}, # max_count => 1000, func => sub { my ($count) = @_; my @got; my $aref = perimeterpqs_list_new(); for (;;) { my $elem = perimeterpqs_list_next($aref); ### list elem: $elem my ($perimeter,$p,$q) = @$elem; last if $perimeter > $count; $got[$perimeter]++; } foreach my $i (0 .. $count) { $got[$i] ||= 0; } # its OFFSET=1 so perimeters 1..$count shift @got; return \@got; }); #------------------------------------------------------------------------------ # A103606 - primitive triples by perimeter and then by even member # As noted by Wolfdieter Lang in deciding the ordering of A103606, if two # triples have the same perimeter and even member then they are equal. # p^2 - q^2 # 2pq # p^2 + q^2 # perimeter 2*p^2 + 2pq = 2*x^2 + 2xy # and evens 2pq=2xy is 2*p^2=2*x^2 and so p=x and q=y # # perimeter = 2*p*(p+q) MyOEIS::compare_values (anum => q{A103606}, max_count => 500, name => 'primitive triples by perimeter then even member', func => sub { my ($count) = @_; my @got; my $aref = perimeterpqs_list_new(); while (@got < $count) { my $elem = perimeterpqs_list_next($aref); my ($perimeter,$p,$q) = @$elem; push @got, sort {$a<=>$b} $p*$p-$q*$q, 2*$p*$q, $p*$p+$q*$q; } $#got = $count-1; return \@got; }); # This is not particularly efficient. A loop for perimeter/2 = p*(p+q) # based on factorizing is much better. It helps a bit to truncate @pending # so it doesn't keep more than the remaining wanted number of triples. # MyOEIS::compare_values (anum => q{A103606}, max_count => 500, bfilename => '/tmp/b103606-mine.txt', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); tie my @pending, "Tie::Array::Sorted", \&by_perimeter_cmp; push @pending, by_perimeter_n_to_elem($path, Math::BigInt->new($path->n_start)); my $want_more_triples = int($count/3) + 30; while (@got < $count) { # print scalar(@pending)," ",scalar(@got),"\r"; ### @pending my $elem = shift @pending; my ($perimeter,$B,$triple,$n) = @$elem; # if (@got == 21147) { # MyTestHelpers::diag(by_perimeter_str($elem)); # } push @got, @$triple; $want_more_triples--; push @pending, map {by_perimeter_n_to_elem($path,$_)} $path->tree_n_children($n); if ($#pending > $want_more_triples) { $#pending = $want_more_triples; # truncate } } $#got = $count-1; return \@got; }); sub by_perimeter_cmp { my ($a,$b) = @_; return $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] || die("oops, same perimeter and even:\n", by_perimeter_str($a),"\n", by_perimeter_str($b)); } sub by_perimeter_str { my ($elem) = @_; my ($perimeter,$B,$triple,$n) = @$elem; return "$perimeter,$B,[".join(',',@$triple)."],$n (".ref($n)||''; } sub by_perimeter_n_to_elem { my ($path,$n) = @_; ref $n or die "not a ref: $n"; my ($p,$q) = $path->n_to_xy($n); my $A = $p*$p - $q*$q; my $B = 2*$p*$q; my $C = $p*$p + $q*$q; my $perimeter = $A + $B + $C; return [ $perimeter, # sort perimeter $B, # then even term [min($A,$B), max($A,$B), $C], # triple $n ]; # n # max($A,$B), } # p^2-q^2 > pq # p^2 > pq + q^2 # p^2 - pq > q^2 # p(p-q) > q^2 # GP-Test for(n=1,5000, \ # GP-Test my(d=divisors(n)); \ # GP-Test if(#d%2, \ # GP-Test my(m=d[(#d+1)/2]); \ # GP-Test m^2==n || error(d); \ # GP-Test , \ # GP-Test d[#d/2]*d[#d/2+1]==n || error(d); \ # GP-Test )); \ # GP-Test 1 # GP-Test for(n=1,5000, \ # GP-Test my(f=factor(n), \ # GP-Test d=divisors(n)); \ # GP-Test d=select(x->gcd(x,n/x)==1,d); \ # GP-Test for(i=1,matsize(f)[1], f[i,1]=f[i,1]^f[i,2]; f[i,2]=1); \ # GP-Test divisors(f) == d || error()); \ # GP-Test 1 # GP-DEFINE A103606_vector(len) = { # GP-DEFINE my(debug=0); # GP-DEFINE my(ret=vector(len+(-len%3)), \\ up to a multiple of 3 # GP-DEFINE upto=0); \\ ready for pre-increment # GP-DEFINE for(H=6,oo, \\ half H=perimeter/2 # GP-DEFINE my(d=factor(H),prev_B=0); # GP-DEFINE for(i=1,matsize(d)[1], d[i,1]=d[i,1]^d[i,2]; d[i,2]=1); # GP-DEFINE d=divisors(d); # GP-DEFINE if(debug,print(d)); # GP-DEFINE for(i=(#d+3)\2,#d, \\ ascending s # GP-DEFINE my(s=d[i], \\ p smaller # GP-DEFINE p=d[#d-i+1], \\ s bigger p*s==H # GP-DEFINE q=s-p); # GP-DEFINE # GP-DEFINE p*s==H || error(); \\ 2*p*(p+q) = perimeter # GP-DEFINE s>p || error(); # GP-DEFINE q>=1 || error(d); # GP-DEFINE gcd(s,p)==1 || error(); # GP-DEFINE # GP-DEFINE \\ p decreasing, q=s-p increasing, so once p>q fails # GP-DEFINE \\ it fails for all the rest of this d # GP-DEFINE p>q || break; # GP-DEFINE # GP-DEFINE s%2 || next; # GP-DEFINE \\ (s%2 && gcd(s,p)==1) || next; # GP-DEFINE if(debug,print(" "p" "q" "d" "i)); # GP-DEFINE my(P=sqr(p),Q=sqr(q), A=P-Q, B=2*p*q, C=P+Q); # GP-DEFINE # GP-DEFINE (A>0 && B>0 && C>0) || error(); # GP-DEFINE A^2+B^2==C^2 || error(A" "B" "C); # GP-DEFINE B > prev_B || error(d); # GP-DEFINE A+B+C == 2*H || error(); # GP-DEFINE H == p*(p+q) || error(); # GP-DEFINE B+2*p^2 == 2*H || error(); # GP-DEFINE B == 2*(H - p^2) || error(); # GP-DEFINE # GP-DEFINE if(debug,print(" push "A" "B" "C" "p" "q)); # GP-DEFINE ret[upto++] = min(A,B); # GP-DEFINE ret[upto++] = max(A,B); # GP-DEFINE ret[upto++] = P+Q; if(upto>=len,break(2)))); # GP-DEFINE Vec(ret,len); # GP-DEFINE } # A103606_vector(27) # OEIS_samples("A103606") # GP-Test vector(50,len, #A103606_vector(len)) == \ # GP-Test vector(50,len, len) # GP-Test my(v=OEIS_samples("A103606")); A103606_vector(#v) == v # my(g=OEIS_bfile_gf("A103606")); g==Polrev(A103606_vector(poldegree(g))) # poldegree(OEIS_bfile_gf("A103606")) # my(v=A103606_vector(30000)); \ # system("rm /tmp/b103606-mine.txt"); \ # for(n=1,#v, write("/tmp/b103606-mine.txt",n," ",v[n])); \ # system("ls -l /tmp/b103606-mine.txt"); # GP-DEFINE \\ Though nice to generate in perimeter order, probably # GP-DEFINE \\ easier and faster to go all $p,$q like perimeterpq_list() # GP-DEFINE \\ and sort. # GP-DEFINE \\ Each column of q would be in ascending order of even leg, # GP-DEFINE \\ so can setunion() rather than full sort. # GP-DEFINE \\ Would have to keep going p until its smallest 2*p*(p+1) # GP-DEFINE \\ perimeter is bigger than the perimeter at the target len. # GP-DEFINE # GP-DEFINE A103606_vector_compact(len) = { # GP-DEFINE my(ret=vector(len+(-len%3)),upto=0); # GP-DEFINE for(H=6,oo, my(f=factor(H)); \\ half perimeter # GP-DEFINE for(i=1,matsize(f)[1], f[i,1]=f[i,1]^f[i,2]; f[i,2]=1); # GP-DEFINE my(d=divisors(f)); \\ no split prime powers # GP-DEFINE for(i=(#d+3)\2,#d, \\ ascending even leg "B" # GP-DEFINE my(s=d[i],p=d[#d-i+1],q=s-p); \\ p*(p+q)==H # GP-DEFINE p>q || break; s%2 || next; # GP-DEFINE [ret[upto++],ret[upto++]] = vecsort([p^2-q^2, 2*p*q]); # GP-DEFINE ret[upto++] = p^2+q^2; # GP-DEFINE if(upto>=len,return(Vec(ret,len))))); # GP-DEFINE } # GP-Test my(v=OEIS_samples("A103606")); A103606_vector_compact(#v) == v # GP-Test vector(50,len, #A103606_vector_compact(len)) == \ # GP-Test vector(50,len, len) # GP-Test A103606_vector_compact(30000) == \ # GP-Test A103606_vector(30000) # C = 2*L # C = L+L+sqrt(2)*L # = (2+sqrt(2))*L # L needs 2*L <= C <= (2+sqrt(2))*L # perimeter = 2*C # perimeter = (1 + 2*1/sqrt(2)) * C # (1 + 2*1/sqrt(2)) = 2.41421 # C = p^2 + q^2 > p^2 + 1 # C = p^2 + q^2 < 2*p^2 C/2 < p^2 < C-1 # so given C, have p^2 > C/2 # given p, have perimeter = 2*p*(p+q) > 2*p^2 > C # #------------------------------------------------------------------------------ # A094194 C leg sorted on p MyOEIS::compare_values (anum => 'A094194', func => sub { my ($count) = @_; my @got; for (my $p = 2; ; $p++) { for (my $q = 1; $q < $p; $q++) { if (pq_acceptable($p,$q)) { if (@got >= $count) { return \@got; } push @got, $p*$p + $q*$q; } } } }); # A120097 bigger leg sorted on p MyOEIS::compare_values (anum => 'A120097', func => sub { my ($count) = @_; my @got; for (my $p = 2; ; $p++) { for (my $q = 1; $q < $p; $q++) { if (pq_acceptable($p,$q)) { if (@got >= $count) { return \@got; } my $A = $p*$p - $q*$q; my $B = 2*$p*$q; push @got, max($A,$B); } } } }); # A120098 smaller leg sorted on p MyOEIS::compare_values (anum => 'A120098', func => sub { my ($count) = @_; my @got; for (my $p = 2; ; $p++) { for (my $q = 1; $q < $p; $q++) { if (pq_acceptable($p,$q)) { if (@got >= $count) { return \@got; } my $A = $p*$p - $q*$q; my $B = 2*$p*$q; push @got, min($A,$B); } } } }); #------------------------------------------------------------------------------ # A321782 - UAD p by rows # for p (but not for others) HtoL and LtoH are the same sequence foreach my $digit_order ('HtoL', 'LtoH') { MyOEIS::compare_values (anum => 'A321782', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); ### $path for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); } # A321783 - UAD q by rows MyOEIS::compare_values (anum => 'A321783', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); # A321784 - UAD p+q by rows MyOEIS::compare_values (anum => 'A321784', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x + $y; } return \@got; }); # A321785 - UAD p-q by rows MyOEIS::compare_values (anum => 'A321785', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x - $y; } return \@got; }); #------------------------------------------------------------------------------ # A321768 - UAD A leg MyOEIS::compare_values (anum => 'A321768', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); # A321769 - UAD A leg MyOEIS::compare_values (anum => 'A321769', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); # A321770 - UAD C leg MyOEIS::compare_values (anum => 'A321770', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'AC'); for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A002315 Pell(2k) - Pell(2k-1), is row P-Q ("NSW" numbers) MyOEIS::compare_values (anum => 'A002315', max_count => 11, func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); for (my $depth = 0; @got < $count; $depth++) { my $total = 0; foreach my $n ($path->tree_depth_to_n($depth) .. $path->tree_depth_to_n_end($depth)) { my ($x,$y) = $path->n_to_xy($n); $total += $x - $y; } push @got, $total; } return \@got; }); # A001541 is row P+Q even Pell + odd Pell # = A001542 + A001653 # my(s=OEIS_samples("A001541")[^1], \ # e=OEIS_samples("A001542")[^1], \ # o=OEIS_samples("A001653"), \ # len=vecmin([#s,#e,#o])); \ # e[1..len] + o[1..len] == s[1..len] # MyOEIS::compare_values (anum => 'A001541', max_count => 11, func => sub { my ($count) = @_; my @got = (1); my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); for (my $depth = 0; @got < $count; $depth++) { my $x_total = 0; foreach my $n ($path->tree_depth_to_n($depth) .. $path->tree_depth_to_n_end($depth)) { my ($x,$y) = $path->n_to_xy($n); $x_total += $x + $y; } push @got, $x_total; } return \@got; }); # A001653 odd Pells, is row Q total MyOEIS::compare_values (anum => 'A001653', max_count => 11, func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); for (my $depth = 0; @got < $count; $depth++) { my $x_total = 0; foreach my $n ($path->tree_depth_to_n($depth) .. $path->tree_depth_to_n_end($depth)) { my ($x,$y) = $path->n_to_xy($n); $x_total += $y; } push @got, $x_total; } return \@got; }); # A001542 even Pell, is row P total MyOEIS::compare_values (anum => 'A001542', max_count => 11, func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); for (my $depth = 0; @got < $count; $depth++) { my $x_total = 0; foreach my $n ($path->tree_depth_to_n($depth) .. $path->tree_depth_to_n_end($depth)) { my ($x,$y) = $path->n_to_xy($n); $x_total += $x; } push @got, $x_total; } return \@got; }); #------------------------------------------------------------------------------ # A000244 = 3^n is N of A repeatedly in middle of row MyOEIS::compare_values (anum => 'A000244', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new; for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { push @got, ($path->tree_depth_to_n_end($depth) + $path->tree_depth_to_n($depth) + 1) / 2; } return \@got; }); #------------------------------------------------------------------------------ # A052940 matrix T repeatedly coordinate P, binary 101111111111 = 3*2^n-1 MyOEIS::compare_values (anum => 'A052940', func => sub { my ($count) = @_; my @got = (1); my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'UMT', coordinates => 'PQ'); for (my $depth = Math::BigInt->new(1); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy($path->tree_depth_to_n_end($depth)); push @got, $x; } return \@got; }); # A055010 same MyOEIS::compare_values (anum => 'A055010', func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'UMT', coordinates => 'PQ'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy($path->tree_depth_to_n_end($depth)); push @got, $x; } return \@got; }); # A083329 same MyOEIS::compare_values (anum => 'A083329', max_count => 200, # touch slow func => sub { my ($count) = @_; my @got = (1); my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'UMT', coordinates => 'PQ'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy($path->tree_depth_to_n_end($depth)); push @got, $x; } return \@got; }); # A153893 same MyOEIS::compare_values (anum => 'A153893', max_count => 200, # touch slow func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'UMT', coordinates => 'PQ'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy($path->tree_depth_to_n_end($depth)); push @got, $x; } return \@got; }); # A093357 matrix T repeatedly coordinate B, binary 10111..111000..000 MyOEIS::compare_values (anum => 'A093357', func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'UMT', coordinates => 'AB'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy($path->tree_depth_to_n_end($depth)); push @got, $y; } return \@got; }); # A134057 matrix T repeatedly coordinate A, binomial(2^n-1,2) # binary 111..11101000..0001 MyOEIS::compare_values (anum => 'A134057', func => sub { my ($count) = @_; my @got = (0,0); my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'UMT', coordinates => 'AB'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy($path->tree_depth_to_n_end($depth)); push @got, $x; } return \@got; }); #------------------------------------------------------------------------------ # A106624 matrix K3 repeatedly P,Q pairs 2^k-1,2^k MyOEIS::compare_values (anum => 'A106624', max_count => 200, # touch slow to 5000 values func => sub { my ($count) = @_; my @got = (1,0); my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'FB', coordinates => 'PQ'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy($path->tree_depth_to_n_end($depth)); push @got, $x, $y; } return \@got; }); #------------------------------------------------------------------------------ # A054881 matrix K2 repeatedly "B" coordinate MyOEIS::compare_values (anum => 'A054881', # max_count => 100, func => sub { my ($count) = @_; my @got = (1,0); my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'FB', coordinates => 'AB'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy(3 ** $depth); push @got, $y; } return \@got; }); # A015249 matrix K2 repeatedly "A" coordinate MyOEIS::compare_values (anum => 'A015249', # max_count => 100, func => sub { my ($count) = @_; my @got = (1); my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'FB', coordinates => 'AB'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy(3 ** $depth); push @got, $x; } return \@got; }); # A084152 same MyOEIS::compare_values (anum => 'A084152', # max_count => 100, func => sub { my ($count) = @_; my @got = (0,0,1); my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'FB', coordinates => 'AB'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy(3 ** $depth); push @got, $x; } return \@got; }); # A084175 same MyOEIS::compare_values (anum => 'A084175', # max_count => 100, func => sub { my ($count) = @_; my @got = (0,1); my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'FB', coordinates => 'AB'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy(3 ** $depth); push @got, $x; } return \@got; }); #------------------------------------------------------------------------------ # A085601 = matrix K1 repeatedly "C" coordinate, binary 10010001 MyOEIS::compare_values (anum => 'A085601', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'FB', coordinates => 'AC'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy($path->tree_depth_to_n($depth)); push @got, $y; } return \@got; }); # A028403 = matrix K1 repeatedly "B" coordinate, binary 10010000 MyOEIS::compare_values (anum => 'A028403', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'FB', coordinates => 'AB'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy($path->tree_depth_to_n($depth)); push @got, $y; } return \@got; }); # A007582 = matrix K1 repeatedly "B/4" coordinate, binary 1001000 MyOEIS::compare_values (anum => 'A007582', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'FB', coordinates => 'AB'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy($path->tree_depth_to_n($depth)); push @got, $y/4; } return \@got; }); #------------------------------------------------------------------------------ # A084159 matrix A repeatedly "A" coordinate, Pell oblongs MyOEIS::compare_values (anum => 'A084159', max_count => 200, # touch slow func => sub { my ($count) = @_; my @got = (1); my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'AB'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy(3 ** $depth); push @got, $x; } return \@got; }); # A046727 matrix A repeatedly "A" coordinate MyOEIS::compare_values (anum => 'A046727', max_count => 200, # touch slow func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'AB'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy(3 ** $depth); push @got, $x; } return \@got; }); # A046729 matrix A repeatedly "B" coordinate MyOEIS::compare_values (anum => 'A046729', func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'AB'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy(3 ** $depth); push @got, $y; } return \@got; }); # A001653 matrix A repeatedly "C" coordinate MyOEIS::compare_values (anum => 'A001653', func => sub { my ($count) = @_; my @got = (1); my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'AC'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy(3 ** $depth); push @got, $y; } return \@got; }); # A001652 matrix A repeatedly "S" smaller coordinate MyOEIS::compare_values (anum => 'A001652', # max_count => 50, func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'SM'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy(3 ** $depth); push @got, $x; } return \@got; }); # A046090 matrix A repeatedly "M" coordinate MyOEIS::compare_values (anum => 'A046090', max_count => 200, # touch slow func => sub { my ($count) = @_; my @got = (1); my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'SM'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy(3 ** $depth); push @got, $y; } return \@got; }); # A000129 matrix A repeatedly "P" coordinate MyOEIS::compare_values (anum => 'A000129', max_count => 200, # touch slow func => sub { my ($count) = @_; my @got = (0,1); my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy(3 ** $depth); push @got, $x; } return \@got; }); #------------------------------------------------------------------------------ # A099776 = matrix U repeatedly "C" coordinate MyOEIS::compare_values (anum => 'A099776', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'AC'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy($path->tree_depth_to_n($depth)); push @got, $y; } return \@got; }); # A001844 centred squares same MyOEIS::compare_values (anum => 'A001844', max_count => 200, # touch slow func => sub { my ($count) = @_; my @got = (1); my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'AC'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy($path->tree_depth_to_n($depth)); push @got, $y; } return \@got; }); # A046092 matrix U repeatedly "B" coordinate = 4*triangular MyOEIS::compare_values (anum => 'A046092', max_count => 200, # touch slow func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'AB'); for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy($path->tree_depth_to_n($depth)); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A000466 matrix D repeatedly "A" coordinate = 4n^2-1 MyOEIS::compare_values (anum => 'A000466', max_count => 200, # touch slow func => sub { my ($count) = @_; my @got = (-1); my $path = Math::PlanePath::PythagoreanTree->new; for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { my ($x,$y) = $path->n_to_xy($path->tree_depth_to_n_end($depth)); push @got, $x; } return \@got; }); #------------------------------------------------------------------------------ # A058529 - all prime factors == +/-1 mod 8 # is differences mid-small legs MyOEIS::compare_values (anum => 'A058529', max_count => 35, func => sub { my ($count) = @_; my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'SM'); my %seen; for (my $n = $path->n_start; $n < 100000; $n++) { my ($s,$m) = $path->n_to_xy($n); my $diff = $m - $s; $seen{$diff} = 1; } my @got = sort {$a<=>$b} keys %seen; $#got = $count-1; return \@got; }); #------------------------------------------------------------------------------ # A003462 = (3^n-1)/2 is tree_depth_to_n_end() MyOEIS::compare_values (anum => 'A003462', func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::PythagoreanTree->new; for (my $depth = Math::BigInt->new(0); @got < $count; $depth++) { push @got, $path->tree_depth_to_n_end($depth); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/HypotOctant-oeis.t0000644000175000017500000000615313475106574016717 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use List::Util 'min', 'max'; use Math::PlanePath::HypotOctant; # #------------------------------------------------------------------------------ # # A001844 # # { # my $anum = 'A001844'; # my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); # # my $diff; # if ($bvalues) { # my @got; # my $path = Math::PlanePath::HypotOctant->new; # my $i = 0; # for (my $i = 0; @got < $count; $i++) { # push @got, $i*$i + ($i+1)*($i+1); # } # # return \@got; # if ($diff) { # MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..20])); # MyTestHelpers::diag ("got: ",join(',',@got[0..20])); # } # } # skip (! $bvalues, # $diff, # undef, # "$anum"); # } #------------------------------------------------------------------------------ # A057653 MyOEIS::compare_values (anum => 'A057653', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::HypotOctant->new (points => 'odd'); my $prev = 0; for (my $n = $path->n_start; @got < $count; $n++) { my $rsquared = $path->n_to_rsquared($n); if ($rsquared != $prev) { $prev = $rsquared; push @got, $rsquared; } } return \@got; }); #------------------------------------------------------------------------------ # A024507 MyOEIS::compare_values (anum => 'A024507', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::HypotOctant->new; my $i = 0; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($y != 0 && $x != $y) { push @got, $path->n_to_rsquared($n); } } return \@got; }); #------------------------------------------------------------------------------ # A024509 MyOEIS::compare_values (anum => 'A024509', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::HypotOctant->new; my $i = 0; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($y != 0) { push @got, $path->n_to_rsquared($n); } } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/PyramidSides-oeis.t0000644000175000017500000000356013246357411017031 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 2; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::PyramidSides; #------------------------------------------------------------------------------ # A020703 - permutation N at -X,Y MyOEIS::compare_values (anum => 'A020703', func => sub { my ($count) = @_; my $path = Math::PlanePath::PyramidSides->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n (-$x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A004201 -- N for which X>=0 MyOEIS::compare_values (anum => 'A004201', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PyramidSides->new; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); if ($x >= 0) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/WythoffArray-oeis.t0000644000175000017500000006646413774322635017103 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # cf # A141104 Lower Even Swappage of Upper Wythoff Sequence. # A141105 Upper Even Swappage of Upper Wythoff Sequence. # A141106 Lower Odd Swappage of Upper Wythoff Sequence. # A141107 Upper Odd Swappage of Upper Wythoff Sequence. # # decimal digits of sum reciprocals of row 2 to 5 # A228040, A228041, A228042, A228043 use 5.004; use strict; use Carp 'croak'; use List::Util 'max'; use Math::BigInt try => 'GMP'; use Math::BaseCnv 'cnv'; use Math::NumSeq::Fibbinary; use Math::NumSeq::FibbinaryBitCount; use Math::NumSeq::FibonacciWord; use Test; plan tests => 47; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::WythoffArray; use Math::PlanePath::CoprimeColumns; *_coprime = \&Math::PlanePath::CoprimeColumns::_coprime; use Math::PlanePath::Diagonals; use Math::NumSeq::PlanePathTurn; # uncomment this to run the ### lines # use Smart::Comments '###'; # P+A=B P=B-A sub pair_left_justify { my ($a,$b) = @_; my $count = 0; while ($a <= $b) { ($a,$b) = ($b-$a,$a); if ($count > 10) { die "oops cannot left justify $a,$b"; } } return ($a,$b); } # path_find_row_with_pair() returns the row Y which contains the Fibonacci # sequence which includes $a,$b somewhere, so W(X,Y)==$a and W(X+1,Y)==$b. # # If $a,$b are before the start of a row then the pair are stepped forward # as necessary. So they specify a Fibonacci-type recurrent sequence which # is sought. # sub path_find_row_with_pair { my ($path, $a, $b) = @_; ### path_find_row_with_pair(): "$a, $b" if (($a == 0 && $b == 0) || $b < 0) { croak "path_find_row_with_pair $a,$b"; } for (my $count = 0; $count < 50; ($a,$b) = ($b,$a+$b)) { ### at: "a=$a b=$b" my ($x,$y) = $path->n_to_xy($a) or next; if ($path->xy_to_n($x+1,$y) == $b) { ### found: " $a $b at X=$x, Y=$y" return $y; } } die "oops, pair $a,$b not found"; } { my $seq = Math::NumSeq::Fibbinary->new; sub to_Zeck_bitstr { my ($n) = @_; return sprintf '%b', $seq->ith($n); } ok (to_Zeck_bitstr(12), 10101); } #------------------------------------------------------------------------------ # A114579 -- N at transpose Y,X # # In Zeckendorf base # not in OEIS: 1,101,1001,10,10001,100,1010,10101,1000,10100 MyOEIS::compare_values (anum => 'A114579', # max_count => 100, # big b-file func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy (Math::BigInt->new($n)); my $t = $path->xy_to_n ($y, $x); push @got, $t; } return \@got; }); #------------------------------------------------------------------------------ # A186007 -- row(i+j) - row(i) # R(4,1) row 4+1=5 sub row 1 # row=5 | 12 20 32 52 84 136 220 356 576 932 1508 # row=1 | 1 2 3 5 8 13 21 34 55 89 144 # 11 18 29 # tail of row2 # R(4,3) row 4+3=7 sub row 4 # row=7 | 17 28 45 73 118 191 309 500 809 1309 2118 # row=4 | 9 15 24 39 63 102 165 267 432 699 1131 # 8 13 # tail of row=1 fibs # row=7 | 17 28 45 73 118 191 309 500 809 1309 2118 # row=3 | 6 10 16 26 42 68 110 178 288 466 754 # 11 18 # tail of row=2 lucas # B-values # 1, pos=0 # 1,1, pos=1 to 2 # 1,1, 1, pos=3 to 5 # 2,1, 3,1, pos=6 to 9 # 1,3, 1,1,1, pos=10 to 14 # 3,1, 1,1,1,1, pos=15 to 20 # 2,4, 3,3,2,1,1, pos=21 to 27 # 1,2, 8,1,3,1,1,1, # 4,1, 1,3,1,2,1,3,1, # 3,6, 4,2,4,1,3,1,1,1, # 2,3,11,1,2,3,1,2,1,1,1, # 5 # 1, pos=0 # 1,1, pos=1 to 2 # 1,1, 1, pos=3 to 5 # 2,1, 3,1, pos=6 to 9 # 1,3, 1,1,1, pos=10 to 14 # 3,1, 2,1,1,1, pos=15 to 20 <- # 2,4, 1,3,2,1,1, pos=21 to 27 <- # 1,2, 3,1,3,1,1,1, # 4,1, 8,3,1,2,1,3,1, # 3,6, 1,2,4,1,3,1,1,1, # 2,3, 4,1,2,3,1,2,1,1,1, # 5 # row 9 of W: 22,36,58,94,... # row 3 of W: 6,10,16,26,... # # (row 9)-(row 3): 16,26,42,68 tail of row 3 # code 1....3....1....2....1....3....8....1....4.... # data 1....3....1.... 1....3....8....1....4....11 { my $path = Math::PlanePath::WythoffArray->new (x_start=>1, y_start=>1); my $diag = Math::PlanePath::Diagonals->new (x_start=>1, y_start=>1, direction => 'up', n_start => 1); sub my_A186007 { my ($n) = @_; if ($n < 1) { die; } my ($i,$j) = $diag->n_to_xy($n); # by anti-diagonals ($i,$j) = ($i+$j, $j); my $ia = $path->xy_to_n(1,$i) or die; my $ib = $path->xy_to_n(2,$i) or die; my $ja = $path->xy_to_n(1,$j) or die; my $jb = $path->xy_to_n(2,$j) or die; my $da = $ia-$ja; my $db = $ib-$jb; my $d = path_find_row_with_pair($path, $da,$db); # print "n=$n i=$i iab=$ia,$ib j=$j jab=$ja,$jb diff=$da,$db at d=$d\n"; return $d; } # foreach my $y (1 .. 5) { # print " "; # foreach my $x (1 .. 10) { # my $n = $diag->xy_to_n($x,$y); # printf "%d....", my_A186007($n); # } # print "\n\n"; # } # # print "R(2,6) = ",$diag->xy_to_n(6,2),"\n"; } MyOEIS::compare_values (anum => 'A186007', func => sub { my ($count) = @_; my @got; for (my $n = 1; @got < $count; $n++) { push @got, my_A186007($n); } return \@got; }); #------------------------------------------------------------------------------ # A185735 -- row(i)+row(j) of left-justified array # 1 0 1 1 2 3 # 2 1 3 4 7 11 # 2 0 2 2 4 6 # 3 0 3 3 6 9 # 4 0 4 4 8 12 # 3 1 4 5 9 14 # row1+row2= 1,0+2,1 = 3,1 = row6 # row1+row3= 1,0+2,0 = 4,0 = row4 MyOEIS::compare_values (anum => 'A185735', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new (x_start=>1, y_start=>1); # Y>=1, 0<=Xnew (x_start=>1, y_start=>1); my @got; for (my $d = $diag->n_start; @got < $count; $d++) { my ($i,$j) = $diag->n_to_xy($d); # by anti-diagonals # if ($i > $j) { ($i,$j) = ($j,$i); } my $ia = $path->xy_to_n(1,$i) or die; my $ib = $path->xy_to_n(2,$i) or die; my $ja = $path->xy_to_n(1,$j) or die; my $jb = $path->xy_to_n(2,$j) or die; ($ia,$ib) = pair_left_justify($ia,$ib); ($ja,$jb) = pair_left_justify($ja,$jb); push @got, path_find_row_with_pair($path, $ia+$ja, $ib+$jb); } return \@got; }); #------------------------------------------------------------------------------ # A165357 - Left-justified Wythoff Array by diagonals { my $path = Math::PlanePath::WythoffArray->new; sub left_justified_row_start { my ($y) = @_; return pair_left_justify($path->xy_to_n(0,$y), $path->xy_to_n(1,$y)); } sub left_justified_xy_to_n { my ($x,$y) = @_; my ($a,$b) = left_justified_row_start($y); foreach (1 .. $x) { ($a,$b) = ($b,$a+$b); } return $a; } # foreach my $y (0 .. 5) { # foreach my $x (0 .. 10) { # printf "%3d ", left_justified_xy_to_n($x,$y); # } # print "\n"; # } } MyOEIS::compare_values (anum => 'A165357', func => sub { my ($count) = @_; my $diag = Math::PlanePath::Diagonals->new (direction => 'up'); my @got; for (my $d = $diag->n_start; @got < $count; $d++) { my ($x,$y) = $diag->n_to_xy($d); # by anti-diagonals push @got, left_justified_xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A185737 -- accumulation array, by antidiagonals # accumulation being total sum N in rectangle 0,0 to X,Y MyOEIS::compare_values (anum => 'A185737', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new; my $diag = Math::PlanePath::Diagonals->new (direction => 'up'); my @got; for (my $d = $diag->n_start; @got < $count; $d++) { my ($x,$y) = $diag->n_to_xy($d); # by anti-diagonals push @got, path_rect_to_accumulation($path, 0,0, $x,$y); } return \@got; }); sub path_rect_to_accumulation { my ($path, $x1,$y1, $x2,$y2) = @_; # $x1 = round_nearest ($x1); # $y1 = round_nearest ($y1); # $x2 = round_nearest ($x2); # $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; my $accumulation = 0; foreach my $x ($x1 .. $x2) { foreach my $y ($y1 .. $y2) { $accumulation += $path->xy_to_n($x,$y); } } return $accumulation; } #------------------------------------------------------------------------------ # A173028 -- row number which is x * row(y), by diagonals # Return pair ($a,$b) which is in the $k'th coprime row of WythoffArray $path # First pair at $k==1. sub coprime_pair { my ($path, $k) = @_; my $x = $path->x_minimum; for (my $y = $path->y_minimum; ; $y++) { my $a = $path->xy_to_n($x, $y); my $b = $path->xy_to_n($x+1,$y); if (_coprime($a,$b)) { $k--; if ($k <= 0) { return ($a,$b); } } } } # Return the row number Y of WythoffArray $path which contains $multiple # times the $k'th coprime row. sub path_y_of_multiple { my ($path, $multiple, $k) = @_; ### path_y_of_multiple: "$multiple,$k" if ($multiple < 1) { croak "path_y_of_multiple multiple=$multiple"; } ($a,$b) = coprime_pair($path,$k); return path_find_row_with_pair($path, $a*$multiple, $b*$multiple); } # { # my $path = Math::PlanePath::WythoffArray->new (x_start=>1, y_start=>1); # foreach my $y (1 .. 5) { # foreach my $x (1 .. 10) { # printf "%3d ", path_y_of_multiple($path,$x,$y)//-1; # } # print "\n"; # } # } MyOEIS::compare_values (anum => 'A173028', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new (x_start=>1, y_start=>1); my $diag = Math::PlanePath::Diagonals->new (x_start => $path->x_minimum, y_start => $path->y_minimum, direction => 'up'); my @got; for (my $d = $diag->n_start; @got < $count; $d++) { my ($x,$y) = $diag->n_to_xy($d); # by anti-diagonals push @got, path_y_of_multiple($path,$x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A139764 -- lowest Zeckendorf term fibonacci value, # is N on X axis for the column containing n MyOEIS::compare_values (anum => 'A139764', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n($x,0); # down to axis # Across to Y axis, not in OEIS # push @got, $path->xy_to_n(0,$y); # across to axis } return \@got; }); #------------------------------------------------------------------------------ # A220249 -- which row is n * Lucas numbers MyOEIS::compare_values (anum => 'A220249', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new (x_start=>1, y_start=>1); my @got; for (my $k = 1; @got < $count; $k++) { # Lucas numbers starting 1, 3 push @got, path_find_row_with_pair($path, $k, $k*3); } return \@got; }); #------------------------------------------------------------------------------ # A173027 -- which row is n * Fibonacci numbers MyOEIS::compare_values (anum => 'A173027', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new (x_start=>1, y_start=>1); my @got; for (my $k = 1; @got < $count; $k++) { # Fibonacci numbers starting 1, 1 push @got, path_find_row_with_pair($path, $k, $k); } return \@got; }); #------------------------------------------------------------------------------ # A035614 -- X coord, starting 0 # but is OFFSET=0 so start N=0 MyOEIS::compare_values (anum => 'A035614', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $x; } return \@got; }); #------------------------------------------------------------------------------ # A188436 -- [3r]-[nr]-[3r-nr], where r=(1+sqrt(5))/2 and []=floor. # positions of right turns # Y axis turn right: 0 1 00 101 00 1 00 101 # Fibonacci word: 0 1 00 101 00 1 00 101 # # N on Y axis # 101010 # 101001 # 100101 # 100001 # 10101 # 10001 # 1001 # 101 # 1 # A188436: 00000 001000000010000100000001000000010000100000001000010000000100000 # path: 001000000010000100000001000000010000100000001000010000000100000 MyOEIS::compare_values (anum => 'A188436', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'WythoffArray', turn_type => 'Right'); my @got = (0,0,0,0,0); while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); use constant PHI => (1 + sqrt(5)) / 2; use POSIX 'floor'; sub A188436_func { my ($n) = @_; floor(3*PHI) - floor($n*PHI)-floor(3*PHI-$n*PHI); } { my $seq = Math::NumSeq::Fibbinary->new; my $bad = 0; foreach (1 .. 50000) { my ($i,$seq_value) = $seq->next; $seq_value = ($seq_value % 8 == 5 ? 1 : 0); # if ($seq_value) { print "$i," } my $func_value = A188436_func($i+4); if ($func_value != $seq_value) { print "$i fibbinary seq=$seq_value func=$func_value\n"; last if $bad++ > 20; } } ok (0, $bad); } { my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'WythoffArray', turn_type => 'Right'); my $bad = 0; foreach (1 .. 50000) { my ($i,$seq_value) = $seq->next; my $func_value = A188436_func($i+4); if ($func_value != $seq_value) { print "$i turn seq=$seq_value func=$func_value\n"; last if $bad++ > 20; } } ok (0, $bad); } # [3r]-[(n+4)r]-[3r-(n+4)r] # = [3r]-[(n+4)r]-[3r-nr-4r] # = [3r]-[nr+4r]-[-r-nr] # some of Y axis 4,12,17,25,33,38,46 #------------------------------------------------------------------------------ # A003622 -- Y coordinate of right turns is "odd" Zeckendorf base MyOEIS::compare_values (anum => 'A003622', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; if ($value) { my ($x,$y) = $path->n_to_xy($i); $x == 0 or die "oops, right turn supposed to be at X=0"; push @got, $y; } } return \@got; }); #------------------------------------------------------------------------------ # A134860 -- Wythoff AAB numbers # N position of right turns, being Zeckendorf ending "...101" MyOEIS::compare_values (anum => 'A134860', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'WythoffArray', turn_type => 'Right'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; if ($value) { push @got, $i; } } return \@got; }); #------------------------------------------------------------------------------ # Y axis 0=left,1=right is Fibonacci word { my $path = Math::PlanePath::WythoffArray->new; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my $fw = Math::NumSeq::FibonacciWord->new; my $bad = 0; foreach my $y (1 .. 1000) { my $n = $path->xy_to_n(0, Math::BigInt->new($y)); my $seq_value = $seq->ith($n); my $fw_value = $fw->ith($y); if ($fw_value != $seq_value) { print "y=$y n=$n seq=$seq_value fw=$fw_value\n"; last if $bad++ > 20; } } ok (0, $bad); } #------------------------------------------------------------------------------ # A080164 -- Wythoff difference array # diff(x,y) = wythoff(2x+1,y) - wythoff(2x,y) MyOEIS::compare_values (anum => 'A080164', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new; my $diag = Math::PlanePath::Diagonals->new (direction => 'up'); my @got; for (my $d = $diag->n_start; @got < $count; $d++) { my ($x,$y) = $diag->n_to_xy($d); # by anti-diagonals push @got, $path->xy_to_n(2*$x+1,$y) - $path->xy_to_n(2*$x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A143299 number of Zeckendorf 1-bits in row Y # cf A007895 which is the fibbinary bit count Math::NumSeq::FibbinaryBitCount MyOEIS::compare_values (anum => 'A143299', func => sub { my ($count) = @_; my $seq = Math::NumSeq::FibbinaryBitCount->new; my $path = Math::PlanePath::WythoffArray->new; my @got; for (my $y = 0; @got < $count; $y++) { my $n = $path->xy_to_n(0,$y); push @got, $seq->ith($n); } return \@got; }); #------------------------------------------------------------------------------ # A137707 secondary Wythoff array ??? # A137707 Secondary Wythoff Array read by antidiagonals. # A137708 Secondary Lower Wythoff Sequence. # A137709 Secondary Upper Wythoff Sequence. # MyOEIS::compare_values # (anum => 'A137707', # func => sub { # my ($count) = @_; # my $path = Math::PlanePath::WythoffArray->new; # my $diag = Math::PlanePath::Diagonals->new; # my @got; # for (my $d = $diag->n_start; @got < $count; $d++) { # my ($x,$y) = $diag->n_to_xy($d); # by anti-diagonals # if ($y % 2) { # push @got, $path->xy_to_n($x,$y-1) + 1; # } else { # push @got, $path->xy_to_n($x,$y); # } # } # return \@got; # }); #------------------------------------------------------------------------------ # A083398 -- anti-diagonals needed to cover numbers 1 to n # maybe n_range_to_rect() ... # max(X+Y) for 1 to n MyOEIS::compare_values (anum => 'A083398', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new; my @got; my @diag; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); $diag[$n] = $x+$y + 1; # +1 to count first diagonal as 1 push @got, max(@diag[1..$n]); } return \@got; }); #------------------------------------------------------------------------------ # N in columns foreach my $elem ([ 'A003622', 0 ], # N on Y axis, OFFSET=1 [ 'A035336', 1 ], # N in X=1 column OFFSET=1 [ 'A066097', 1 ], # N in X=1 column, duplicate OFFSET=0 # per list in A035513 [ 'A035337', 2 ], # OFFSET=0 [ 'A035338', 3 ], # OFFSET=0 [ 'A035339', 4 ], # OFFSET=0 [ 'A035340', 5 ], # OFFSET=0 ) { my ($anum, $x, %options) = @$elem; MyOEIS::compare_values (anum => $anum, func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new; my @got = @{$options{'extra_initial'}||[]}; for (my $y = Math::BigInt->new(0); @got < $count; $y++) { push @got, $path->xy_to_n ($x, $y); } return \@got; }); } #------------------------------------------------------------------------------ # A160997 Antidiagonal sums of the Wythoff array A035513 MyOEIS::compare_values (anum => 'A160997', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new; my @got; for (my $d = 0; @got < $count; $d++) { my $total = 0; foreach my $x (0 .. $d) { $total += $path->xy_to_n($x,$d-$x); } push @got, $total; } return \@got; }); #------------------------------------------------------------------------------ # A005248 -- every second N on Y=1 row, every second Lucas number MyOEIS::compare_values (anum => q{A005248}, func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new; my @got = (2,3); # initial skipped for (my $x = Math::BigInt->new(1); @got < $count; $x+=2) { push @got, $path->xy_to_n ($x, 1); } return \@got; }); #------------------------------------------------------------------------------ # N on rows # per list in A035513 foreach my $elem ([ 'A000045', 0, extra_initial=>[0,1] ], # X axis Fibonaccis [ 'A006355', 2, extra_initial=>[1,0,2,2,4] ], [ 'A022086', 3, extra_initial=>[0,3,3,6] ], [ 'A022087', 4, extra_initial=>[0,4,4,8] ], [ 'A000285', 5, extra_initial=>[1,4,5,9] ], [ 'A022095', 6, extra_initial=>[1,5,6,11] ], # sum of Fibonacci and Lucas numbers [ 'A013655', 7, extra_initial=>[3,2,5,7,12] ], [ 'A022112', 8, extra_initial=>[2,6,8,14] ], [ 'A022113', 9, extra_initial=>[2,7,9,16] ], [ 'A022120', 10, extra_initial=>[3,7,10,17] ], [ 'A022121', 11, extra_initial=>[3,8,11,19] ], [ 'A022379', 12, extra_initial=>[3,9,12,21] ], [ 'A022130', 13, extra_initial=>[4,9,13,22] ], [ 'A022382', 14, extra_initial=>[4,10,14,24] ], [ 'A022088', 15, extra_initial=>[0,5,5,10,15,25] ], [ 'A022136', 16, extra_initial=>[5,11,16,27] ], [ 'A022137', 17, extra_initial=>[5,12,17,29] ], [ 'A022089', 18, extra_initial=>[0,6,6,12,18,30] ], [ 'A022388', 19, extra_initial=>[6,13,19,32] ], [ 'A022096', 20, extra_initial=>[1,6,7,13,20,33] ], [ 'A022090', 21, extra_initial=>[0,7,7,14,21,35] ], [ 'A022389', 22, extra_initial=>[7,15,22,37] ], [ 'A022097', 23, extra_initial=>[1,7,8,15,23,38] ], [ 'A022091', 24, extra_initial=>[0,8,8,16,24,40] ], [ 'A022390', 25, extra_initial=>[8,17,25,42] ], [ 'A022098', 26, extra_initial=>[1,8,9,17,26,43], ], [ 'A022092', 27, extra_initial=>[0,9,9,18,27,45], ], ) { my ($anum, $y, %options) = @$elem; MyOEIS::compare_values (anum => $anum, func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new; my @got = @{$options{'extra_initial'}||[]}; for (my $x = Math::BigInt->new(0); @got < $count; $x++) { push @got, $path->xy_to_n ($x, $y); } return \@got; }); } #------------------------------------------------------------------------------ # A064274 -- inverse perm of by diagonals up from X axis MyOEIS::compare_values (anum => 'A064274', func => sub { my ($count) = @_; my $diagonals = Math::PlanePath::Diagonals->new (direction => 'up'); my $wythoff = Math::PlanePath::WythoffArray->new; my @got = (0); # extra 0 for (my $n = $diagonals->n_start; @got < $count; $n++) { my ($x, $y) = $wythoff->n_to_xy ($n); $x = Math::BigInt->new($x); $y = Math::BigInt->new($y); push @got, $diagonals->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A003849 -- Fibonacci word MyOEIS::compare_values (anum => 'A003849', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new; my @got = (0); for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, ($x == 0 ? 1 : 0); } return \@got; }); #------------------------------------------------------------------------------ # A000201 -- N+1 for N not on Y axis, spectrum of phi MyOEIS::compare_values (anum => 'A000201', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new; my @got = (1); for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($x != 0) { push @got, $n+1; } } return \@got; }); #------------------------------------------------------------------------------ # A022342 -- N not on Y axis, even Zeckendorfs MyOEIS::compare_values (anum => 'A022342', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new; my @got = (0); for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($x != 0) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A001950 -- N+1 of the N's on Y axis, spectrum MyOEIS::compare_values (anum => 'A001950', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffArray->new; my @got; for (my $y = 0; @got < $count; $y++) { my $n = $path->xy_to_n(0,$y); push @got, $n+1; } return \@got; }); #------------------------------------------------------------------------------ # A083412 -- by diagonals, down from Y axis MyOEIS::compare_values (anum => 'A083412', func => sub { my ($count) = @_; my $diagonals = Math::PlanePath::Diagonals->new (direction => 'down'); my $wythoff = Math::PlanePath::WythoffArray->new; my @got; for (my $n = $diagonals->n_start; @got < $count; $n++) { my ($x, $y) = $diagonals->n_to_xy ($n); push @got, $wythoff->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A035513 -- by diagonals, up from X axis MyOEIS::compare_values (anum => 'A035513', func => sub { my ($count) = @_; my $diagonals = Math::PlanePath::Diagonals->new (direction => 'up'); my $wythoff = Math::PlanePath::WythoffArray->new; my @got; for (my $n = $diagonals->n_start; @got < $count; $n++) { my ($x, $y) = $diagonals->n_to_xy ($n); $x = Math::BigInt->new($x); $y = Math::BigInt->new($y); push @got, $wythoff->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/TerdragonCurve-oeis.t0000644000175000017500000004735613774702041017400 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt; use Test; plan tests => 47; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::TerdragonCurve; use Math::NumSeq::PlanePathTurn; # uncomment this to run the ### lines # use Smart::Comments '###'; my $path = Math::PlanePath::TerdragonCurve->new; sub ternary_digit_above_low_zeros { my ($n) = @_; if ($n == 0) { return 0; } while (($n % 3) == 0) { $n = int($n/3); } return ($n % 3); } #------------------------------------------------------------------------------ # A189674 - num left turns 1..N MyOEIS::compare_values (anum => 'A189674', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); my $total = 0; my @got; for (my $n = $path->n_start + 1; @got < $count; $n++) { push @got, $total; $total += $seq->ith($n); } return \@got; }); # A189641 - num right turns 1..N MyOEIS::compare_values (anum => 'A189641', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my $total = 0; my @got; for (my $n = $path->n_start + 1; @got < $count; $n++) { push @got, $total; $total += $seq->ith($n); } return \@got; }); # A189672 - num right turns 1..N, sans one initial 0 MyOEIS::compare_values (anum => 'A189672', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my $total = 0; my @got; for (my $n = $path->n_start + 1; @got < $count; $n++) { $total += $seq->ith($n); push @got, $total; } return \@got; }); #------------------------------------------------------------------------------ # A133162 - 1 for each segment, 2 when right turn between MyOEIS::compare_values (anum => 'A133162', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got; for (my $n = $path->n_start; @got < $count; $n++) { push @got, 1; if ($seq->ith($n+1)) { push @got, 2; } } return \@got; }); #------------------------------------------------------------------------------ # A000001 -- (X-Y)/2 coordinate, at 60 degrees # # not in OEIS: 0,1,0,1,0,0,-1,0,-1,0,-1,-1,-2,-2,-1,-1,-2,-2,-3,-2,-3,-2,-3,-3 # MyOEIS::compare_values # (anum => 'A000001', # func => sub { # my ($count) = @_; # my @got; # for (my $n = 0; @got < $count; $n++) { # my ($x,$y) = $path->n_to_xy($n); # push @got, ($x-$y)/2; # } # return \@got; # }); # # A000001 -- (X+Y)/2 coordinate, at 120 degrees # # not in OEIS: 0,1,1,2,2,1,1,2,2,3,3,2,2,1,2,1,1,0,0,1,1,2,2,1,1,2,2,3,3,2,2,1 # MyOEIS::compare_values # (anum => 'A000001', # func => sub { # my ($count) = @_; # my @got; # for (my $n = 0; @got < $count; $n++) { # my ($x,$y) = $path->n_to_xy($n); # push @got, ($x+$y)/2; # } # return \@got; # }); # # # A000001 -- Y coordinate # # not in OEIS: 0,0,1,1,2,1,2,2,3,3,4,3,4,3,3,2,3,2,3,3,4,4,5,4,5,5,6,6,7,6,7,6 # MyOEIS::compare_values # (anum => 'A000001', # func => sub { # my ($count) = @_; # my @got; # for (my $n = 0; @got < $count; $n++) { # my ($x,$y) = $path->n_to_xy($n); # push @got, $y; # } # return \@got; # }); #------------------------------------------------------------------------------ # A005823 - N positions with net turn == 0, no ternary 1s # A023692 through A023698 # N positions where direction = 1 to 7, ternary num 1s foreach my $elem (['A005823',0], ['A023692',1], ['A023693',2], ['A023694',3], ['A023695',4], ['A023696',5], ['A023697',6], ['A023698',7]) { my ($anum, $want_dir) = @$elem; MyOEIS::compare_values (anum => $anum, func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my $i = 0; my $dir = 0; my @got; while (@got < $count) { if ($dir == $want_dir) { push @got, $i; } $i++; my ($this_i, $value) = $seq->next; $i == $this_i or die; $dir += $value; } return \@got; }); } #------------------------------------------------------------------------------ # A026141,A026171 - dTurnLeft step N positions of left turns foreach my $anum ('A026141','A026171') { MyOEIS::compare_values (anum => $anum, name => "dTurnLeft $anum", func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); my $prev = 0; my @got; while (@got < $count) { my ($i, $value) = $seq->next; if ($value == 1) { push @got, $i-$prev; $prev = $i; } } return \@got; }); } # A026181,A131989 - dTurnRight step N positions of right turns foreach my $anum ('A026181','A131989') { MyOEIS::compare_values (anum => $anum, name => "dTurnRight $anum", func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my $prev = 0; if ($anum eq 'A026181') { # skip one initial my $value; do { ($prev,$value) = $seq->next; } until ($value == 1); } my @got; while (@got < $count) { my ($i, $value) = $seq->next; if ($value == 1) { push @got, $i-$prev; $prev = $i; } } return \@got; }); } #------------------------------------------------------------------------------ # A057682 level X # A057083 level Y foreach my $elem (['A057682', 1, 0, 0, [0,1]], # X ['A057083', 1, 1, 1, [] ], # Y ['A057681', 2, 0, 0, [1,1]], # X arms=2 ['A103312', 2, 0, 0, [0,1,1]], # X arms=2 ['A057682', 2, 1, 0, [0] ], # Y arms=2 ['A057681', 3, 1, 0, [1]], # Y arms=3 ['A103312', 3, 1, 0, [0,1]], # Y arms=3 ) { my ($anum, $arms, $coord, $initial_level, $initial_got) = @$elem; my $path = Math::PlanePath::TerdragonCurve->new (arms => $arms); MyOEIS::compare_values (anum => $anum, name => "$anum arms=$arms", func => sub { my ($count) = @_; my @got = @$initial_got; for (my $k = $initial_level; @got < $count; $k++) { my ($n_lo,$n_hi) = $path->level_to_n_range(Math::BigInt->new($k)); my @coords = $path->n_to_xy($n_hi); push @got, $coords[$coord]; } return \@got; }); } #------------------------------------------------------------------------------ # A092236 etc counts of segments in direction within level foreach my $elem ([1, 'A057083', [], 1], [0, 'A092236', [], 0], [1, 'A135254', [0], 0], [2, 'A133474', [0], 0]) { my ($dir, $anum, $initial_got, $offset_3k) = @$elem; MyOEIS::compare_values (anum => $anum, max_value => 8, func => sub { my ($count) = @_; my @got = @$initial_got; my $n = $path->n_start; my $total = 0; my $k = 2*$offset_3k; while (@got < $count) { ### @got my $n_end = 3**$k; for ( ; $n < $n_end; $n++) { $total += (dxdy_to_dir3($path->n_to_dxdy($n)) == $dir); } if ($offset_3k) { push @got, $total - 3**($k-1); } else { push @got, $total; } $k++; } return \@got; }); } sub dxdy_to_dir3 { my ($dx,$dy) = @_; if ($dx == 2 && $dy == 0) { return 0; } if ($dx == -1) { if ($dy == 1) { return 1; } if ($dy == -1) { return 2; } } return undef; } #------------------------------------------------------------------------------ # A111286 boundary length is 2 then 3*2^k for points N <= 3^k MyOEIS::compare_values (anum => 'A111286', max_value => 10_000, func => sub { my ($count) = @_; my @got = (1); for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length ($path, 3**$k, lattice_type => 'triangular'); } return \@got; }); # A007283 boundary length is 3*2^k for points N <= 3^k, # except initial MyOEIS::compare_values (anum => 'A007283', max_value => 10_000, func => sub { my ($count) = @_; my @got = (3); # path initial boundary=2 vs bvalues=3 for (my $k = 1; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length ($path, 3**$k, lattice_type => 'triangular'); } return \@got; }); # A164346 boundary even powers, is 3*4^n # also one side, odd powers MyOEIS::compare_values (anum => 'A164346', max_value => 10_000, func => sub { my ($count) = @_; my @got = (3); for (my $k = 1; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length ($path, 3**(2*$k), lattice_type => 'triangular'); } return \@got; }); MyOEIS::compare_values (anum => q{A164346}, max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length ($path, 3**(2*$k+1), lattice_type => 'triangular', side => 'left'); } return \@got; }); # A002023 boundary odd powers 6*4^n # also even powers one side MyOEIS::compare_values (anum => 'A002023', max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length ($path, 3**(2*$k+1), lattice_type => 'triangular'); } return \@got; }); MyOEIS::compare_values (anum => q{A002023}, max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 1; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length ($path, 3**(2*$k), lattice_type => 'triangular', side => 'right'); } return \@got; }); #------------------------------------------------------------------------------ # A003945 R[k] boundary length MyOEIS::compare_values (anum => 'A003945', max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length ($path, 3**$k, side => 'right', lattice_type => 'triangular'); } return \@got; }); #------------------------------------------------------------------------------ # A042950 V[k] boundary length MyOEIS::compare_values (anum => 'A042950', max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length ($path, 2 * 3**$k, side => 'left', lattice_type => 'triangular'); } return \@got; }); #------------------------------------------------------------------------------ # A118004 1/2 enclosed area odd levels points N <= 3^(2k+1), is 9^k-4^k # area[k] = 2*(3^(k-1)-2^(k-1)) # area[2k+1]/2 = 2*(3^(2k+1-1)-2^(2k+1-1))/2 # = 9^k - 4^k MyOEIS::compare_values (anum => 'A118004', max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { my $area = MyOEIS::path_enclosed_area ($path, 3**(2*$k+1), lattice_type => 'triangular'); push @got, $area/2; } return \@got; }); # A056182 enclosed area is 2*(3^(k-1)-2^(k-1)) for points N <= 3^k MyOEIS::compare_values (anum => 'A056182', max_value => 10_000, func => sub { my ($count) = @_; my @got; for (my $k = 1; @got < $count; $k++) { push @got, MyOEIS::path_enclosed_area ($path, 3**$k, lattice_type => 'triangular'); } return \@got; }); #------------------------------------------------------------------------------ # A136442 1,1,0,1,1,0,1,0,0,1,1,0,1,1,0,1,0,0,1,1,0,1,0,0, # OFFSET =0,1,2,3,... # left 1,1,0,1,1,0,0,1,0,1,1,0,1,1,0,0,1,0,0,1,0,1,1,0,0,1,0,1,1,0,1,1,0,0,1,0,1,1,0,1,1,0,0,1,0,0,1,0,1,1,0,0,1,0,0,1,0,1,1,0,0,1,0,1,1,0,1,1,0,0,1,0,0,1,0,1,1,0,0,1,0,1,1,0,1,1,0,0,1,0,1,1,0,1,1,0,0,1,0,0,1,0,1,1,0 # N=1,2,3,... # Not quite # # MyOEIS::compare_values # (anum => 'A136442', # func => sub { # my ($count) = @_; # my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, # turn_type => 'Left'); # my @got = (1); # while (@got < $count) { # my ($i, $value) = $seq->next; # push @got, $value; # } # return \@got; # }); #------------------------------------------------------------------------------ # A060032 - turn 1=left, 2=right as bignums to 3^level MyOEIS::compare_values (anum => 'A060032', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my @got; for (my $level = 0; @got < $count; $level++) { my $big = Math::BigInt->new(0); foreach my $n (1 .. 3**$level) { my $value = $seq->ith($n); if ($value == -1) { $value = 2; } $big = 10*$big + $value; } push @got, $big; } return \@got; }); #------------------------------------------------------------------------------ # A189673 - morphism turn 1=left, 0=right, extra initial 0 MyOEIS::compare_values (anum => 'A189673', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); my @got = (0); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A189640 - morphism turn 0=left, 1=right, extra initial 0 MyOEIS::compare_values (anum => 'A189640', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got = (0); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A062756 - ternary count 1s, is cumulative turn MyOEIS::compare_values (anum => 'A062756', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my @got; my $cumulative = 0; for (;;) { push @got, $cumulative; last if @got >= $count; my ($i, $value) = $seq->next; $cumulative += $value; } return \@got; }); #------------------------------------------------------------------------------ # A080846 - turn 0=left, 1=right MyOEIS::compare_values (anum => 'A080846', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got; while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A038502 - taken mod 3 is turn 1=left, 2=right MyOEIS::compare_values (anum => 'A038502', fixup => sub { # mangle to mod 3 my ($bvalues) = @_; @$bvalues = map { $_ % 3 } @$bvalues; }, func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); my @got; while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value+1; } return \@got; }); #------------------------------------------------------------------------------ # A026225 - N positions of left turns MyOEIS::compare_values (anum => 'A026225', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Left'); my @got; while (@got < $count) { my ($i, $value) = $seq->next; if ($value == 1) { push @got, $i; } } return \@got; }); MyOEIS::compare_values (anum => q{A026225}, func => sub { my ($count) = @_; my @got; for (my $n = 1; @got < $count; $n++) { if (ternary_digit_above_low_zeros($n) == 1) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A026179 - positions of right turns MyOEIS::compare_values (anum => 'A026179', func => sub { my ($count) = @_; my @got = (1); # extra initial 1 ... my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); while (@got < $count) { my ($i, $value) = $seq->next; if ($value == 1) { push @got, $i; } } return \@got; }); MyOEIS::compare_values (anum => q{A026179}, func => sub { my ($count) = @_; my @got = (1); for (my $n = 1; @got < $count; $n++) { if (ternary_digit_above_low_zeros($n) == 2) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/QuintetCentres-oeis.t0000644000175000017500000000452113474706140017407 0ustar gggg#!/usr/bin/perl -w # Copyright 2013, 2014, 2015, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt; use Test; plan tests => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::QuintetCentres; #------------------------------------------------------------------------------ # A099456 -- level end Y MyOEIS::compare_values (anum => 'A099456', func => sub { my ($count) = @_; my $path = Math::PlanePath::QuintetCentres->new; my @got; for (my $level = Math::BigInt->new(1); @got < $count; $level++) { my ($n_lo, $n_hi) = $path->level_to_n_range($level); my ($x,$y) = $path->n_to_xy($n_hi); push @got, $y; } return \@got; }); # A139011 -- level end X - 1, Re (2+i)^k MyOEIS::compare_values (anum => 'A139011', func => sub { my ($count) = @_; my $path = Math::PlanePath::QuintetCentres->new; my @got; for (my $level = Math::BigInt->new(0); @got < $count; $level++) { my ($n_lo, $n_hi) = $path->level_to_n_range($level); my ($x,$y) = $path->n_to_xy($n_hi); push @got, $x + 1; } return \@got; }); # A139011 -- arms=2 level end Y, Re (2+i)^k MyOEIS::compare_values (anum => q{A139011}, func => sub { my ($count) = @_; my $path = Math::PlanePath::QuintetCentres->new (arms => 2); my @got; for (my $level = Math::BigInt->new(0); @got < $count; $level++) { my ($n_lo, $n_hi) = $path->level_to_n_range($level); my ($x,$y) = $path->n_to_xy($n_hi); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/HexSpiral-oeis.t0000644000175000017500000002473613767217233016350 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # A182619 Number of vertices that are connected to two edges in a spiral without holes constructed with n hexagons. # A182617 Number of toothpicks in a toothpick spiral around n cells on hexagonal net. # A182618 Number of new grid points that are covered by the toothpicks added at n-th-stage to the toothpick spiral of A182617. # A063178 Hexagonal spiral sequence: sequence is written as a hexagonal spiral around a `dummy' center, each entry is the sum of the row in the previous direction containing the previous entry. # A063253 Values of A063178 on folding point positions of the spiral. # A063254 Values of A062410 on folding point positions of the spiral. # A063255 Values of A063177 on folding point positions of the spiral. # A113519 Semiprimes in first spoke of a hexagonal spiral (A056105). # A113524 Semiprimes in second spoke of a hexagonal spiral (A056106). # A113525 Semiprimes in third spoke of a hexagonal spiral (A056107). # A113527 Semiprimes in fourth spoke of a hexagonal spiral (A056108). # A113528 Semiprimes in fifth spoke of a hexagonal spiral (A056109). # A113530 Semiprimes in sixth spoke of a hexagonal spiral (A003215). Semiprime hex (or centered hexagonal) numbers. # A113653 Isolated semiprimes in the hexagonal spiral. use 5.004; use strict; use Test; plan tests => 18; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use List::Util 'min', 'max'; use Math::PlanePath::HexSpiral; use Math::NumSeq::PlanePathTurn; # uncomment this to run the ### lines # use Smart::Comments '###'; my @dir6_to_dx = (2, 1,-1,-2, -1, 1); my @dir6_to_dy = (0, 1, 1, 0, -1,-1); #------------------------------------------------------------------------------ # sloped lines foreach my $elem (['A062783', 3, 1, n_start => 0], ['A063436', -3, -1, n_start => 0], # ['A063436', -3, 1, n_start => 0], # not in OEIS: 0,13,50,111,196,305,438 # ['A063436', 3, -1, n_start => 0], # not in OEIS: 0,7,38,93,172,275 # not A270704 is double # ['A000001', 0, 2, n_start => 0], # not in OEIS: 0,11,46,105,188,295 ['A000002', 0, -2, n_start => 0], # not in OEIS: 0,17,58,123,212,325,462 # names of these are angles starting vertical Y axis and # measuring clockwise ['A244802', 3, 1], # 30 deg 1, 10, 43, 100 ['A244803', 0, 2], # 90 deg 1, 12, 47, 106 ['A244804', -3, 1], # 150 deg ['A244805', -3, -1], # -150 deg ['A244806', 0, -2], # -90 deg ) { my ($anum,$dx,$dy, %options) = @$elem; MyOEIS::compare_values (anum => $anum, name => "line slope $dx,$dy", func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::HexSpiral->new (%options); my $x = 0; my $y = 0; while (@got < $count) { push @got, $path->xy_to_n ($x,$y); $x += $dx; $y += $dy; } return \@got; }); } #------------------------------------------------------------------------------ # A001399 -- N where turn left MyOEIS::compare_values (anum => 'A001399', max_value => 100_000, func => sub { my ($count) = @_; my @got = (1); # extra initial 1 in A001399 my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'HexSpiral,n_start=0', turn_type => 'Left'); while (@got < $count) { my ($i,$value) = $seq->next; if ($value) { push @got, $i; } } return \@got; }); #------------------------------------------------------------------------------ # A328818 -- X coordinate # A307012 -- Y coordinate MyOEIS::compare_values (anum => 'A328818', func => sub { my ($count) = @_; my $path = Math::PlanePath::HexSpiral->new (n_start => 0); my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); MyOEIS::compare_values (anum => 'A307012', func => sub { my ($count) = @_; my $path = Math::PlanePath::HexSpiral->new (n_start => 0); my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); # A307011 horiz # A307012 60 deg # A307013 120 deg # (X-Y)/2 MyOEIS::compare_values (anum => 'A307011', func => sub { my ($count) = @_; my $path = Math::PlanePath::HexSpiral->new (n_start => 0); my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, ($x-$y)/2; } return \@got; }); # (X+Y)/2 MyOEIS::compare_values (anum => 'A307013', func => sub { my ($count) = @_; my $path = Math::PlanePath::HexSpiral->new (n_start => 0); my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, ($x+$y)/2; } return \@got; }); #------------------------------------------------------------------------------ # A274920 -- smallest of 0,1,2 not an existing neighbour MyOEIS::compare_values (anum => q{A274920}, # not shown in POD func => sub { my ($count) = @_; my $path = Math::PlanePath::HexSpiral->new (n_start => 0); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); my @seen; foreach my $dir6 (0 .. 5) { my $n2 = $path->xy_to_n($x + $dir6_to_dx[$dir6], $y + $dir6_to_dy[$dir6]); defined $n2 or die; if ($n2 < $n) { $seen[$got[$n2]] = 1; } } for (my $i = 0; ; $i++) { if (!$seen[$i]) { push @got, $i; last; } } } return \@got; }); #------------------------------------------------------------------------------ # A135708 -- grid sticks of N hexagons # /\ /\ # | | | # \/ \/ MyOEIS::compare_values (anum => 'A135708', func => sub { my ($count) = @_; my $path = Math::PlanePath::HexSpiral->new; my @got; my $boundary = 0; for (my $n = $path->n_start; @got < $count; $n++) { $boundary += 6 - triangular_num_preceding_neighbours($path,$n); push @got, $boundary; } return \@got; }); #------------------------------------------------------------------------------ # A135711 -- boundary length of N hexagons # /\ /\ # | | | # \/ \/ MyOEIS::compare_values (anum => 'A135711', func => sub { my ($count) = @_; my $path = Math::PlanePath::HexSpiral->new; my @got; my $boundary = 0; for (my $n = $path->n_start; @got < $count; $n++) { $boundary += 6 - 2*triangular_num_preceding_neighbours($path,$n); push @got, $boundary; } return \@got; }); BEGIN { my @surround6_dx = (2, 1,-1, -2, -1, 1); my @surround6_dy = (0, 1, 1, 0, -1, -1); sub triangular_num_preceding_neighbours { my ($path, $n) = @_; my ($x,$y) = $path->n_to_xy ($n); my $count = 0; foreach my $i (0 .. $#surround6_dx) { my $n2 = $path->xy_to_n($x + $surround6_dx[$i], $y + $surround6_dy[$i]); $count += (defined $n2 && $n2 < $n); } return $count; } } #------------------------------------------------------------------------------ # A063436 -- N on slope=3 WSW MyOEIS::compare_values (anum => 'A063436', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::HexSpiral->new (n_start => 0); my $x = 0; my $y = 0; while (@got < $count) { push @got, $path->xy_to_n ($x,$y); $x -= 3; $y -= 1; } return \@got; }); #------------------------------------------------------------------------------ # A063178 -- a(n) is sum of existing numbers in row of a(n-1) # 42 # \ # 2-----1 33 # / \ \ # 3 0-----1 23 # \ / # 5-----8----10 # # ^ ^ ^ ^ ^ ^ ^ MyOEIS::compare_values (anum => 'A063178', func => sub { my ($count) = @_; my $path = Math::PlanePath::HexSpiral->new; my @got; require Math::BigInt; my %plotted; $plotted{2,0} = Math::BigInt->new(1); my $xmin = 0; my $ymin = 0; my $xmax = 2; my $ymax = 0; push @got, 1; for (my $n = $path->n_start + 2; @got < $count; $n++) { my ($prev_x, $prev_y) = $path->n_to_xy ($n-1); my ($x, $y) = $path->n_to_xy ($n); ### at: "$x,$y prev $prev_x,$prev_y" my $total = 0; if (($y > $prev_y && $x < $prev_x) || ($y < $prev_y && $x > $prev_x)) { ### forward diagonal ... foreach my $y ($ymin .. $ymax) { my $delta = $y - $prev_y; my $x = $prev_x + $delta; $total += $plotted{$x,$y} || 0; } } elsif (($y == $prev_y && $x < $prev_x) || ($y == $prev_y && $x > $prev_x)) { ### opp diagonal ... foreach my $y ($ymin .. $ymax) { my $delta = $y - $prev_y; my $x = $prev_x - $delta; $total += $plotted{$x,$y} || 0; } } else { ### row: "$xmin .. $xmax at y=$prev_y" foreach my $x ($xmin .. $xmax) { $total += $plotted{$x,$prev_y} || 0; } } ### total: "$total" $plotted{$x,$y} = $total; $xmin = min($xmin,$x); $xmax = max($xmax,$x); $ymin = min($ymin,$y); $ymax = max($ymax,$y); push @got, $total; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/PentSpiralSkewed-oeis.t0000644000175000017500000000422413246357424017662 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::PentSpiralSkewed; #------------------------------------------------------------------------------ # A140066 - N on Y axis MyOEIS::compare_values (anum => 'A140066', func => sub { my ($count) = @_; my $path = Math::PlanePath::PentSpiralSkewed->new; my @got; for (my $y = 0; @got < $count; $y++) { push @got, $path->xy_to_n(0,$y); } return \@got; }); #------------------------------------------------------------------------------ # A147875 - N on Y negative axis, n_start=0, second heptagonals MyOEIS::compare_values (anum => 'A147875', func => sub { my ($count) = @_; my $path = Math::PlanePath::PentSpiralSkewed->new (n_start => 0); my @got; for (my $y = 0; @got < $count; $y--) { push @got, $path->xy_to_n(0,$y); } return \@got; }); #------------------------------------------------------------------------------ # A134238 - N on Y negative axis MyOEIS::compare_values (anum => 'A134238', func => sub { my ($count) = @_; my $path = Math::PlanePath::PentSpiralSkewed->new; my @got; for (my $y = 0; @got < $count; $y--) { push @got, $path->xy_to_n(0,$y); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/HIndexing-oeis.t0000644000175000017500000000376513716617262016325 0ustar gggg#!/usr/bin/perl -w # Copyright 2013, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt; use Test; plan tests => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::HIndexing; my $path = Math::PlanePath::HIndexing->new; #------------------------------------------------------------------------------ # A334235 -- X coordinate MyOEIS::compare_values (anum => 'A334235', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); # A334236 -- Y coordinate MyOEIS::compare_values (anum => 'A334236', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A097110 -- Y at N=2^k MyOEIS::compare_values (anum => 'A097110', func => sub { my ($count) = @_; my @got; for (my $n = Math::BigInt->new(1); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/FilledRings-oeis.t0000644000175000017500000000632213474705461016642 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::PlanePath::FilledRings; use Test; plan tests => 5; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; #------------------------------------------------------------------------------ # A036704 -- count |z|<=n+1/2 MyOEIS::compare_values (anum => 'A036704', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::FilledRings->new (n_start => 0); for (my $x = 1; @got < $count; $x++) { push @got, $path->xy_to_n($x,0); } return \@got; }); #------------------------------------------------------------------------------ # A036708 -- half plane count n-1/2 < |z|<=n+1/2, b>=0 # first diffs of half plane count # N(X)/2+X-1 - (N(X-1)/2+X-1-1) # = (N(X)-N(X-1))/2 + X-1 - X + 2 # = (N(X)-N(X-1))/2 + 1 MyOEIS::compare_values (anum => 'A036708', func => sub { my ($count) = @_; my @got = (1); my $path = Math::PlanePath::FilledRings->new; for (my $x = 2; @got < $count; $x++) { push @got, ($path->xy_to_n($x,0)-$path->xy_to_n($x-1,0))/2 + 1; } return \@got; }); #------------------------------------------------------------------------------ # A036707 -- half plane count |z|<=n+1/2, b>=0 MyOEIS::compare_values (anum => 'A036707', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::FilledRings->new; for (my $x = 1; @got < $count; $x++) { push @got, $path->xy_to_n($x,0)/2 + $x-1; } return \@got; }); #------------------------------------------------------------------------------ # A036706 -- 1/4 of first diffs of N along X axis, MyOEIS::compare_values (anum => 'A036706', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::FilledRings->new; for (my $x = 1; @got < $count; $x++) { push @got, int (($path->xy_to_n($x,0) - $path->xy_to_n($x-1,0)) / 4); } return \@got; }); #------------------------------------------------------------------------------ # A036705 -- first diffs of N along X axis, # count of z=a+bi satisfying n-1/2 < |z| <= n+1/2 MyOEIS::compare_values (anum => 'A036705', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::FilledRings->new; for (my $x = 1; @got < $count; $x++) { push @got, $path->xy_to_n($x,0) - $path->xy_to_n($x-1,0); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/PowerArray-oeis.t0000644000175000017500000003767413775161630016547 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2018, 2019, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt try => 'GMP'; use Test; plan tests => 40; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::PowerArray; use Math::PlanePath::Diagonals; #------------------------------------------------------------------------------ # A000975 -- radix=3, Y at N=2^k, being Y=1010101..101 in binary MyOEIS::compare_values (anum => 'A000975', max_count => 1000, func => sub { my ($count) = @_; my $path = Math::PlanePath::PowerArray->new (radix => 3); my @got; for (my $n = Math::BigInt->new(1); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A016051 -- radix=3, N in column X=1 # 9*n+3 and 9*n+6 foreach my $elem ([q{A001651}, 0], # in PlanePathCoord ['A016051', 1], ['A051063', 2], ) { my ($anum,$column_x, @extra_initial) = @$elem; MyOEIS::compare_values (anum => $anum, name => "column X=$column_x", func => sub { my ($count) = @_; my $path = Math::PlanePath::PowerArray->new (radix => 3); my @got = @extra_initial; for (my $y = Math::BigInt->new(0); @got < $count; $y++) { push @got, $path->xy_to_n($column_x, $y); } return \@got; }); } # column at X=3 # not in OEIS: 27,54,108,135,189,216,270,297,351,378 #---------- # A008776 - X row at Y=1 # in general 3^x * (1or2 mod 3) foreach my $elem ([q{A000244}, 0], # in PlanePathCoord ['A008776', 1], ['A003946', 2, 1], ['A005030', 3], ['A005032', 4], # 7*3^x etc ['A005051', 5], ['A005052', 6], ['A120354', 7], ['A258597', 8], # ['', 9], # only 2*A005032 to get 14 ) { my ($anum,$row_y, @extra_initial) = @$elem; MyOEIS::compare_values (anum => $anum, func => sub { my ($count) = @_; my $path = Math::PlanePath::PowerArray->new (radix => 3); my @got = @extra_initial; for (my $x = Math::BigInt->new(0); @got < $count; $x++) { push @got, $path->xy_to_n($x, $row_y); } return \@got; }); } #------------------------------------------------------------------------------ # A135764 -- dispersion traversed by diagonals, down from Y axis { my $diagonals = Math::PlanePath::Diagonals->new (direction => 'down'); my $power = Math::PlanePath::PowerArray->new; MyOEIS::compare_values (anum => 'A135764', func => sub { my ($count) = @_; my @got; for (my $n = $diagonals->n_start; @got < $count; $n++) { my ($x, $y) = $diagonals->n_to_xy ($n); push @got, $power->xy_to_n($x,$y); } return \@got; }); # A249725 - inverse MyOEIS::compare_values (anum => 'A249725', func => sub { my ($count) = @_; my @got; for (my $n = $power->n_start; @got < $count; $n++) { my ($x, $y) = $power->n_to_xy ($n); push @got, $diagonals->xy_to_n($x,$y); } return \@got; }); } #------------------------------------------------------------------------------ # A117303 -- permutation, N at transpose (2*x-1)*2^(y-1) <--> (2*y-1)*2^(x-1) MyOEIS::compare_values (anum => 'A117303', func => sub { my ($count) = @_; my $path = Math::PlanePath::PowerArray->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy($n); push @got, $path->xy_to_n ($y, $x); } return \@got; }); #------------------------------------------------------------------------------ # A151754 -- radix=10, Y at N=2^k starting k=1 N=2, floor(2^k*9/10) MyOEIS::compare_values (anum => 'A151754', func => sub { my ($count) = @_; my $path = Math::PlanePath::PowerArray->new (radix => 10); my @got; for (my $n = Math::BigInt->new(2); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); $x == 0 or die; push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A050603 -- radix=2 abs(dX), but OFFSET=0 MyOEIS::compare_values (anum => 'A050603', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PowerArray->new (radix => 2); for (my $n = $path->n_start; @got < $count; $n++) { my ($dx,$dy) = $path->n_to_dxdy($n); push @got, abs($dx); } return \@got; }); #------------------------------------------------------------------------------ # A003159 -- radix=2, N which is in X even MyOEIS::compare_values (anum => 'A003159', func => sub { my ($count) = @_; my $path = Math::PlanePath::PowerArray->new (radix => 2); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy($n); if ($x % 2 == 0) { push @got, $n; } } return \@got; }); # A036554 complement, N which is in X odd MyOEIS::compare_values (anum => 'A036554', func => sub { my ($count) = @_; my $path = Math::PlanePath::PowerArray->new (radix => 2); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy($n); if ($x % 2 == 1) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A007417 -- radix=3, N which is in X even MyOEIS::compare_values (anum => 'A007417', func => sub { my ($count) = @_; my $path = Math::PlanePath::PowerArray->new (radix => 3); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy($n); if ($x % 2 == 0) { push @got, $n; } } return \@got; }); # A145204 complement, N which is in X odd, and extra initial 0 MyOEIS::compare_values (anum => 'A145204', func => sub { my ($count) = @_; my $path = Math::PlanePath::PowerArray->new (radix => 3); my @got = (0); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy($n); if ($x % 2 == 1) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A141396 -- radix=3, permutation, N by diagonals MyOEIS::compare_values (anum => 'A141396', func => sub { my ($count) = @_; my $power = Math::PlanePath::PowerArray->new (radix => 3); my $diagonal = Math::PlanePath::Diagonals->new (direction => 'down'); my @got; for (my $n = $diagonal->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal->n_to_xy($n); push @got, $power->xy_to_n ($x, $y); } return \@got; }); #------------------------------------------------------------------------------ # A191449 -- radix=3, permutation, N by diagonals up from X axis MyOEIS::compare_values (anum => 'A191449', func => sub { my ($count) = @_; my @got; my $diagonals = Math::PlanePath::Diagonals->new (direction => 'up'); my $power = Math::PlanePath::PowerArray->new (radix => 3); for (my $n = $diagonals->n_start; @got < $count; $n++) { my ($x, $y) = $diagonals->n_to_xy ($n); push @got, $power->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A075300 -- dispersion traversed by diagonals, minus 1, so starts from 0 MyOEIS::compare_values (anum => 'A075300', func => sub { my ($count) = @_; my $diagonals = Math::PlanePath::Diagonals->new (direction => 'up'); my $power = Math::PlanePath::PowerArray->new; my @got; for (my $n = $diagonals->n_start; @got < $count; $n++) { my ($x, $y) = $diagonals->n_to_xy ($n); push @got, $power->xy_to_n($x,$y) - 1; } return \@got; }); #------------------------------------------------------------------------------ # A001651 -- radix=3, N on Y axis, not divisible by 3 MyOEIS::compare_values (anum => 'A001651', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PowerArray->new (radix => 3); for (my $y = 0; @got < $count; $y++) { push @got, $path->xy_to_n(0,$y); } return \@got; }); #------------------------------------------------------------------------------ # A067251 -- radix=10, N on Y axis, no trailing 0 digits MyOEIS::compare_values (anum => 'A067251', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PowerArray->new (radix => 10); for (my $y = 0; @got < $count; $y++) { push @got, $path->xy_to_n(0,$y); } return \@got; }); #------------------------------------------------------------------------------ # A153733 remove trailing 1s MyOEIS::compare_values (anum => 'A153733', func => sub { my ($count) = @_; my @got; my $power = Math::PlanePath::PowerArray->new; for (my $n = $power->n_start; @got < $count; $n++) { my ($x, $y) = $power->n_to_xy ($n); push @got, 2*$y; } return \@got; }); #------------------------------------------------------------------------------ # A000265 -- 2*Y+1, odd part of n dividing out factors of 2 MyOEIS::compare_values (anum => 'A000265', func => sub { my ($count) = @_; my @got; my $power = Math::PlanePath::PowerArray->new; for (my $n = $power->n_start; @got < $count; $n++) { my ($x, $y) = $power->n_to_xy ($n); push @got, 2*$y+1; } return \@got; }); #------------------------------------------------------------------------------ # A094267 -- dX, but OFFSET=0 MyOEIS::compare_values (anum => 'A094267', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PowerArray->new (radix => 2); for (my $n = $path->n_start; @got < $count; $n++) { my ($dx,$dy) = $path->n_to_dxdy($n); push @got, $dx; } return \@got; }); #------------------------------------------------------------------------------ # A108715 -- dY MyOEIS::compare_values (anum => 'A108715', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PowerArray->new (radix => 2); for (my $n = $path->n_start; @got < $count; $n++) { my ($dx,$dy) = $path->n_to_dxdy($n); push @got, $dy; } return \@got; }); #------------------------------------------------------------------------------ # A118417 -- N on X=Y+1 diagonal MyOEIS::compare_values (anum => 'A118417', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PowerArray->new (radix => 2); for (my $i = Math::BigInt->new(0); @got < $count; $i++) { push @got, $path->xy_to_n($i+1,$i); } return \@got; }); #------------------------------------------------------------------------------ # A005408 -- N on Y axis, odd numbers MyOEIS::compare_values (anum => 'A005408', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PowerArray->new; for (my $y = 0; @got < $count; $y++) { push @got, $path->xy_to_n(0,$y); } return \@got; }); #------------------------------------------------------------------------------ # A057716 -- N not on X axis, the non 2^X MyOEIS::compare_values (anum => 'A057716', func => sub { my ($count) = @_; my @got = (0); # extra 0 my $path = Math::PlanePath::PowerArray->new (radix => 2); for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($y != 0) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A135765 -- odd numbers radix 3, down from Y axis # # 0 1 2 3 4 5 6 # 0 . . 3 4 . . 7 8 . . 11 12 # 2*y+($y%2) # # math-image --all --wx --path=PowerArray,radix=3 --output=numbers --size=15x20 # # A135765 odd numbers by factors of 3 # product A000244 3^n, A007310 1or5 mod 6 is LCF>=5 # 1 5 7 11 13 17 19 23 25 29 # 3 15 21 33 39 51 57 69 75 # 9 25 63 99 117 153 171 207 # 27 135 189 297 351 459 513 # 81 405 567 891 1053 1377 # 243 1215 1701 2673 3159 # 729 3645 5103 8019 # 2187 10935 15309 # 6561 32805 # MyOEIS::compare_values (anum => 'A135765', func => sub { my ($count) = @_; my @got; my $diagonals = Math::PlanePath::Diagonals->new (direction => 'down'); my $power = Math::PlanePath::PowerArray->new (radix => 3); for (my $n = $diagonals->n_start; @got < $count; $n++) { my ($x, $y) = $diagonals->n_to_xy ($n); $y = 2*$y+($y%2); # stretch push @got, $power->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A006519 -- 2^X coord MyOEIS::compare_values (anum => 'A006519', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PowerArray->new (radix => 2); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, 2**$x; } return \@got; }); #------------------------------------------------------------------------------ # A025480 -- Y coord MyOEIS::compare_values (anum => 'A025480', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PowerArray->new (radix => 2); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A003602 -- Y+1 coord, k for which N=(2k-1)*2^m MyOEIS::compare_values (anum => 'A003602', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PowerArray->new (radixt => 2); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $y+1; } return \@got; }); #------------------------------------------------------------------------------ # A054582 -- dispersion traversed by diagonals, up from X axis { my $diagonals = Math::PlanePath::Diagonals->new (direction => 'up'); my $power = Math::PlanePath::PowerArray->new; MyOEIS::compare_values (anum => 'A054582', func => sub { my ($count) = @_; my @got; for (my $n = $diagonals->n_start; @got < $count; $n++) { my ($x, $y) = $diagonals->n_to_xy ($n); push @got, $power->xy_to_n($x,$y); } return \@got; }); # A209268 - inverse MyOEIS::compare_values (anum => 'A209268', func => sub { my ($count) = @_; my @got; for (my $n = $power->n_start; @got < $count; $n++) { my ($x, $y) = $power->n_to_xy ($n); push @got, $diagonals->xy_to_n($x,$y); } return \@got; }); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/ComplexPlus-oeis.t0000644000175000017500000000743513475621011016706 0ustar gggg#!/usr/bin/perl -w # Copyright 2016, 2017, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt try => 'GMP'; use Test; plan tests => 16; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines # use Smart::Comments '###'; use Math::PlanePath::ComplexPlus; my $path = Math::PlanePath::ComplexPlus->new; #------------------------------------------------------------------------------ # A290885 = -X MyOEIS::compare_values (anum => 'A290885', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, -$x; } return \@got; }); # A290884 = Y MyOEIS::compare_values (anum => 'A290884', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); # A290886 = norm X^2+Y^2 MyOEIS::compare_values (anum => 'A290886', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x**2 + $y**2; } return \@got; }); #------------------------------------------------------------------------------ # A077950, A077870 location of ComplexMinus origin in ComplexPlus # 3 6 7 2 3 k=3 PlusOffsetJ=1+I # 2 4 5 0 1 # 1 2 3 6 7 # Y=0 0 1 4 5 # # X=0 1 2 { my $max_count = 12; my ($A077950) = MyOEIS::read_values('A077950', max_count => $max_count); my ($A077870) = MyOEIS::read_values('A077870', max_count => $max_count); ### $A077950 ### $A077870 unshift @$A077950, 0, 0; unshift @$A077870, 0, 0, 0; require Math::PlanePath::ComplexMinus; my $minus = Math::PlanePath::ComplexMinus->new; foreach my $k (0 .. $max_count-1) { my ($n_lo, $n_hi) = $path->level_to_n_range($k); my (%minus_points, %plus_points); my $dx = $A077950->[$k]; my $dy = $A077870->[$k]; ### dxdy: "$dx, $dy" foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $minus->n_to_xy($n); if ($k&1) { $y = -$y; } else { $x = -$x; } $x += $dx; $y += $dy; $minus_points{"$x,$y"} = 1; ($x,$y) = $path->n_to_xy($n); $plus_points{"$x,$y"} = 1; } ### %plus_points ### %minus_points my $plus_str = join(' ',sort keys %plus_points); my $minus_str = join(' ',sort keys %minus_points); ok ($plus_str, $minus_str); } } #------------------------------------------------------------------------------ # A146559 - dX at N=2^k-1, for k>=1 MyOEIS::compare_values (anum => 'A146559', max_count => 300, # more than 64 bits func => sub { my ($count) = @_; my @got = (1); for (my $k = 0; @got < $count; $k++) { my $n = Math::BigInt->new(2)**$k - 1; ### N: "$n" my ($dx,$dy) = $path->n_to_dxdy ($n); push @got, $dx; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/GrayCode-oeis.t0000644000175000017500000007437613754565435016161 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 42; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::GrayCode; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use Math::PlanePath::Diagonals; use Math::NumSeq::PlanePathTurn; # GP-DEFINE read("my-oeis.gp"); #------------------------------------------------------------------------------ # Helpers # GP-Test my(want=50*10^6); /* more stack */ \ # GP-Test if(default(parisizemax) 'A309952', func => sub { my ($count) = @_; my $path = Math::PlanePath::GrayCode->new (apply_type => 'Ts'); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); #------------------------------------------------------------------------------ # A064706 - binary reflected Gray twice # # (n XOR n>>1) XOR (n XOR n>>1) >> 1 # = n XOR n>>1 XOR n>>1 XOR n>>2 # = n XOR n>>2 MyOEIS::compare_values (anum => 'A064706', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { push @got, to_Gray_reflected(to_Gray_reflected($n,2),2); } return \@got; }); # A064707 - binary reflected UnGray twice MyOEIS::compare_values (anum => 'A064707', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { push @got, from_Gray_reflected(from_Gray_reflected($n,2),2); } return \@got; }); # GP-DEFINE \\ A003188 # GP-DEFINE binary_reflected_Gray(n) = bitxor(n,n>>1); # # GP-DEFINE \\ A006068 # GP-DEFINE binary_reflected_UnGray(g) = { # GP-DEFINE my(v=binary(g),r=0); # GP-DEFINE for(i=2,#v, \\ high to low # GP-DEFINE v[i] = bitxor(v[i],v[i-1])); # GP-DEFINE fromdigits(v,2); # GP-DEFINE } # my(v=OEIS_samples("A006068")); vector(#v,n,n--; binary_reflected_UnGray(n)) == v \\ OFFSET=0 # my(g=OEIS_bfile_gf("A006068")); g==Polrev(vector(poldegree(g)+1,n,n--;binary_reflected_UnGray(n))) # poldegree(OEIS_bfile_gf("A006068")) # # GP-DEFINE \\ double binary Gray # GP-DEFINE A064706(n) = binary_reflected_Gray(binary_reflected_Gray(n)); # my(v=OEIS_samples("A064706")); vector(#v,n,n--; A064706(n)) == v \\ OFFSET=0 # my(g=OEIS_bfile_gf("A064706")); g==Polrev(vector(poldegree(g)+1,n,n--;A064706(n))) # poldegree(OEIS_bfile_gf("A064706")) # GP-DEFINE \\ double binary UnGray # GP-DEFINE A064707(n) = { # GP-DEFINE my(v=binary(n)); # GP-DEFINE for(i=3,#v,v[i]=bitxor(v[i],v[i-2])); # GP-DEFINE fromdigits(v,2); # GP-DEFINE } # my(v=OEIS_samples("A064707")); vector(#v,n,n--; A064707(n)) == v \\ OFFSET=0 # my(g=OEIS_bfile_gf("A064707")); g==Polrev(vector(poldegree(g)+1,n,n--;A064707(n))) # poldegree(OEIS_bfile_gf("A064707")) # GP-Test vector(2^14,n,n--; A064707(A064706(n))) == \ # GP-Test vector(2^14,n,n--; n) # GP-Test vector(2^14,n,n--; A064706(A064707(n))) == \ # GP-Test vector(2^14,n,n--; n) # # GP-Test /* by shifts like Jorg and Paul D. Hanna in UnGray A006068 */ \ # GP-Test /* bit lengths of ops 1 + 2 + ... + 2^log(nlen) */ \ # GP-Test /* so linear in nlen rounded up to next power of 2 */ \ # GP-Test vector(2^14,n,n--; A064707(n)) == \ # GP-Test vector(2^14,n,n--; \ # GP-Test my(s=1,ns); while(ns=n>>(s<<=1), n=bitxor(n,ns)); n) # # GP-DEFINE extract_even_bits(n) = fromdigits(digits(n,4)%2,2); # GP-DEFINE extract_odd_bits(n) = fromdigits(digits(n,4)>>1,2); # GP-DEFINE spread_even_bits(n) = fromdigits(digits(n,2),4); # GP-DEFINE spread_odd_bits(n) = fromdigits(digits(n,2)<<1,4); # GP-Test /* double binary Gray as applied to evens and odds separately */ \ # GP-Test /* per Antti Karttunen formula in A064706 */ \ # GP-Test vector(2^14,n,n--; A064707(n)) == \ # GP-Test vector(2^14,n,n--; \ # GP-Test my(e=extract_even_bits(n)); \ # GP-Test my(o=extract_odd_bits(n)); \ # GP-Test e=binary_reflected_UnGray(e); \ # GP-Test o=binary_reflected_UnGray(o); \ # GP-Test spread_even_bits(e) + spread_odd_bits(o)) #------------------------------------------------------------------------------ # A098488 - decimal modular Gray MyOEIS::compare_values (anum => 'A098488', func => sub { my ($count) = @_; my $radix = 10; my @got; for (my $n = 0; @got < $count; $n++) { my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_to_gray_modular($digits,$radix); push @got, digit_join_lowtohigh($digits,$radix); } return \@got; }); # A226134 - decimal modular UnGray MyOEIS::compare_values (anum => 'A226134', func => sub { my ($count) = @_; my $radix = 10; my @got; for (my $n = 0; @got < $count; $n++) { my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_from_gray_modular($digits,$radix); push @got, digit_join_lowtohigh($digits,$radix); } return \@got; }); # GP-DEFINE A098488(n) = my(v=digits(n)); forstep(i=#v,2,-1, v[i]=(v[i]-v[i-1])%10); fromdigits(v); # # GP-Test /* Martin Cohn, example 4 Gray column */ \ # GP-Test my(want=[6764,6765,6766,6767,6768,6769,6760, \ # GP-Test 6860,6861,6862,6863,6864,6865], \ # GP-Test lo=6393, hi=6405); \ # GP-Test for(n=lo,hi, my(i=n-lo+1); \ # GP-Test A098488(n) == want[i] || error()); \ # GP-Test 1 # GP-Test /* Martin Cohn, example 4 matrix, for any 4-digit number */ \ # GP-Test my(m=[1,9,0,0; 0,1,9,0; 0,0,1,9; 0,0,0,1]); \ # GP-Test forvec(v=vector(4,i, [0,9]), \ # GP-Test A098488(fromdigits(v)) == fromdigits((v*m)%10) || error(v*m)); \ # GP-Test 1 # GP-DEFINE to_Gray(n,base) = { # GP-DEFINE my(v=digits(n,base)); # GP-DEFINE forstep(i=#v,2,-1, v[i]=(v[i]-v[i-1])%base); # GP-DEFINE fromdigits(v,base); # GP-DEFINE } # GP-Test vector(10^5,n,n--; A098488(n)) == \ # GP-Test vector(10^5,n,n--; to_Gray(n,10)) # vector(10^5,n,n--; to_Gray(n,10)) #------------------------------------------------------------------------------ # A007913 -- square free part of N # mod 2 skip N even is Left turns MyOEIS::compare_values (anum => q{A007913}, # not xreffed in GrayCode.pm fixup => sub { my ($bvalues) = @_; foreach (@$bvalues) { $_ %= 2; } }, func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'GrayCode', turn_type => 'NotStraight'); my @got; while (@got < $count) { my ($n,$value) = $seq->next; my ($n2,$value2) = $seq->next; # undouble push @got, $value; $value==$value2 || die "oops"; } return \@got; }); #------------------------------------------------------------------------------ # A065882 -- low base4 non-zero digit # mod 2 is NotStraight MyOEIS::compare_values (anum => 'A065882', fixup => sub { my ($bvalues) = @_; foreach (@$bvalues) { $_ %= 2; } }, func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'GrayCode', turn_type => 'NotStraight'); my @got; while (@got < $count) { my ($n,$value) = $seq->next; my ($n2,$value2) = $seq->next; # undouble push @got, $value; $value==$value2 || die "oops"; } return \@got; }); #------------------------------------------------------------------------------ # A003159 -- (N+1)/2 of positions of Left turns MyOEIS::compare_values (anum => 'A003159', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'GrayCode', turn_type => 'NotStraight'); my @got; while (@got < $count) { my ($n,$value) = $seq->next; my ($n2,$value2) = $seq->next; # undouble if ($value) { push @got, ($n+1)/2; } $value==$value2 || die "oops"; } return \@got; }); # A036554 -- (N+1)/2 of positions of Straight turns MyOEIS::compare_values (anum => 'A036554', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'GrayCode', turn_type => 'Straight'); my @got; while (@got < $count) { my ($n,$value) = $seq->next; # undouble my ($n2,$value2) = $seq->next; $value==$value2 || die "oops"; if ($value) { push @got, ($n+1)/2; } } return \@got; }); #------------------------------------------------------------------------------ # A039963 -- Left turns MyOEIS::compare_values (anum => 'A039963', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'GrayCode', turn_type => 'Left'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); # A035263 -- Left turns undoubled, skip N even MyOEIS::compare_values (anum => 'A035263', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'GrayCode', turn_type => 'Left'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; my ($i2,$value2) = $seq->next; # undouble $value==$value2 || die "oops"; } return \@got; }); #------------------------------------------------------------------------------ # A003188 -- Gray code radix=2 is ZOrder X,Y -> Gray TsF # and Gray FsT X,Y -> ZOrder MyOEIS::compare_values (anum => 'A003188', func => sub { my ($count) = @_; require Math::PlanePath::ZOrderCurve; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'TsF'); my $zorder_path = Math::PlanePath::ZOrderCurve->new; my @got; for (my $n = $zorder_path->n_start; @got < $count; $n++) { my ($x, $y) = $zorder_path->n_to_xy ($n); my $n = $gray_path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); MyOEIS::compare_values (anum => q{A003188}, func => sub { my ($count) = @_; require Math::PlanePath::ZOrderCurve; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'FsT'); my $zorder_path = Math::PlanePath::ZOrderCurve->new; my @got; for (my $n = $gray_path->n_start; @got < $count; $n++) { my ($x, $y) = $gray_path->n_to_xy ($n); my $n = $zorder_path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ # A006068 -- UnGray, inverse Gray TsT X,Y -> ZOrder N # and ZOrder X,Y -> Gray FsF MyOEIS::compare_values (anum => q{A006068}, func => sub { my ($count) = @_; require Math::PlanePath::ZOrderCurve; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'TsF'); my $zorder_path = Math::PlanePath::ZOrderCurve->new; my @got; for (my $n = $gray_path->n_start; @got < $count; $n++) { my ($x, $y) = $gray_path->n_to_xy ($n); my $n = $zorder_path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); # A006068 -- UnGray, ZOrder X,Y -> Gray FsT N MyOEIS::compare_values (anum => q{A006068}, func => sub { my ($count) = @_; require Math::PlanePath::ZOrderCurve; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'FsT'); my $zorder_path = Math::PlanePath::ZOrderCurve->new; my @got; for (my $n = $zorder_path->n_start; @got < $count; $n++) { my ($x, $y) = $zorder_path->n_to_xy ($n); my $n = $gray_path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ # A064707 -- permutation radix=2 TsF -> FsT # inverse square of A003188 Gray code # A064706 -- permutation radix=2 FsT -> TsF # square of A003188 Gray code ZOrder->TsF # not same as A100281,A100282 MyOEIS::compare_values (anum => q{A064707}, func => sub { my ($count) = @_; my $TsF_path = Math::PlanePath::GrayCode->new (apply_type => 'TsF'); my $FsT_path = Math::PlanePath::GrayCode->new (apply_type => 'FsT'); my @got; for (my $n = $TsF_path->n_start; @got < $count; $n++) { my ($x, $y) = $TsF_path->n_to_xy ($n); my $n = $FsT_path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); MyOEIS::compare_values (anum => q{A064706}, func => sub { my ($count) = @_; my $TsF_path = Math::PlanePath::GrayCode->new (apply_type => 'TsF'); my $FsT_path = Math::PlanePath::GrayCode->new (apply_type => 'FsT'); my @got; for (my $n = $FsT_path->n_start; @got < $count; $n++) { my ($x, $y) = $FsT_path->n_to_xy ($n); my $n = $TsF_path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); # { # my $seq = Math::NumSeq::OEIS->new(anum=>'A099896'); # sub A100281_by_twice { # my ($i) = @_; # $i = $seq->ith($i); # if (defined $i) { $i = $seq->ith($i); } # return $i; # } # } # sub A100281_by_func { # my ($i) = @_; # $i = ($i ^ ($i>>1) ^ ($i>>2)); # $i = ($i ^ ($i>>1) ^ ($i>>2)); # return $i; # } #------------------------------------------------------------------------------ # A099896 -- permutation Peano radix=2 -> Gray sF, from N=1 onwards # n XOR [n/2] XOR [n/4] # 1, 3, 2, 7, 6, 4, 5, 14, 15, 13, 12, 9, 8, 10, 11, 28, 29, 31, 30, 27, # to_gray = n xor n/2 # PeanoCurve radix=2 # # 54--55 49--48 43--42 44--45 64--65 71--70 93--92 90--91 493-492 # | | | | | | | | | # 53--52 50--51 40--41 47--46 67--66 68--69 94--95 89--88 494-495 # # 56--57 63--62 37--36 34--35 78--79 73--72 83--82 84--85 483-482 # | | | | | | | | | # 59--58 60--61 38--39 33--32 77--76 74--75 80--81 87--86 480-481 # # 13--12 10--11 16--17 23--22 123-122 124-125 102-103 97--96 470-471 # | | | | | | | | | # 14--15 9-- 8 19--18 20--21 120-121 127-126 101-100 98--99 469-468 # # 3-- 2 4-- 5 30--31 25--24 117-116 114-115 104-105 111-110 472-473 # | | | | | | | | | # 0-- 1 7-- 6 29--28 26--27 118-119 113-112 107-106 108-109 475-474 # apply_type => "sF" # # 7 | 32--33 37--36 52--53 49--48 # | / \ / \ # 6 | 34--35 39--38 54--55 51--50 # | # 5 | 42--43 47--46 62--63 59--58 # | \ / \ / # 4 | 40--41 45--44 60--61 57--56 # | # 3 | 8-- 9 13--12 28--29 25--24 # | / \ / \ # 2 | 10--11 15--14 30--31 27--26 # | # 1 | 2-- 3 7-- 6 22--23 19--18 # | \ / \ / # Y=0 | 0-- 1 5-- 4 20--21 17--16 # | # +--------------------------------- # X=0 1 2 3 4 5 6 7 MyOEIS::compare_values (anum => 'A099896', func => sub { my ($count) = @_; require Math::PlanePath::PeanoCurve; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'sF'); my $peano_path = Math::PlanePath::PeanoCurve->new (radix => 2); my @got; for (my $n = 1; @got < $count; $n++) { my ($x, $y) = $peano_path->n_to_xy ($n); my $n = $gray_path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); # A100280 -- inverse MyOEIS::compare_values (anum => 'A100280', func => sub { my ($count) = @_; require Math::PlanePath::PeanoCurve; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'sF'); my $peano_path = Math::PlanePath::PeanoCurve->new (radix => 2); my @got; for (my $n = $gray_path->n_start; @got < $count; $n++) { my ($x, $y) = $gray_path->n_to_xy ($n); my $n = $peano_path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ # A163233 -- permutation diagonals sF MyOEIS::compare_values (anum => 'A163233', func => sub { my ($count) = @_; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'sF'); my $diagonal_path = Math::PlanePath::Diagonals->new (direction => 'up'); my @got; for (my $n = $diagonal_path->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal_path->n_to_xy ($n); my $n = $gray_path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); # A163234 -- diagonals sF inverse MyOEIS::compare_values (anum => 'A163234', func => sub { my ($count) = @_; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'sF'); my $diagonal_path = Math::PlanePath::Diagonals->new (direction => 'up', n_start => 0); my @got; for (my $n = $gray_path->n_start; @got < $count; $n++) { my ($x, $y) = $gray_path->n_to_xy ($n); my $n = $diagonal_path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); #------------------------------------------------------------------------------ # A163235 -- diagonals sF, opposite side start MyOEIS::compare_values (anum => 'A163235', func => sub { my ($count) = @_; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'sF'); my $diagonal_path = Math::PlanePath::Diagonals->new (direction => 'down'); my @got; for (my $n = $diagonal_path->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal_path->n_to_xy ($n); my $n = $gray_path->xy_to_n ($x, $y); push @got, $n; } return \@got; }); # A163236 -- diagonals sF inverse, opposite side start MyOEIS::compare_values (anum => 'A163236', func => sub { my ($count) = @_; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'sF'); my $diagonal_path = Math::PlanePath::Diagonals->new (direction => 'down'); my @got; for (my $n = $gray_path->n_start; @got < $count; $n++) { my ($x, $y) = $gray_path->n_to_xy ($n); my $n = $diagonal_path->xy_to_n ($x, $y); push @got, $n + $gray_path->n_start - $diagonal_path->n_start; } return \@got; }); #------------------------------------------------------------------------------ # A163237 -- diagonals sF, same side start, flip base-4 digits 2,3 sub flip_base4_23 { my ($n) = @_; my @digits = digit_split_lowtohigh($n,4); foreach my $digit (@digits) { if ($digit == 2) { $digit = 3; } elsif ($digit == 3) { $digit = 2; } } return digit_join_lowtohigh(\@digits,4); } MyOEIS::compare_values (anum => 'A163237', func => sub { my ($count) = @_; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'sF'); my $diagonal_path = Math::PlanePath::Diagonals->new (direction => 'up'); my @got; for (my $n = $diagonal_path->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal_path->n_to_xy ($n); my $n = $gray_path->xy_to_n ($x, $y); $n = flip_base4_23($n); push @got, $n; } return \@got; }); # A163238 -- inverse MyOEIS::compare_values (anum => 'A163238', func => sub { my ($count) = @_; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'sF'); my $diagonal_path = Math::PlanePath::Diagonals->new (direction => 'up'); my @got; for (my $n = $gray_path->n_start; @got < $count; $n++) { my $n = flip_base4_23($n); my ($x, $y) = $gray_path->n_to_xy ($n); $n = $diagonal_path->xy_to_n ($x, $y); push @got, $n + $gray_path->n_start - $diagonal_path->n_start; } return \@got; }); #------------------------------------------------------------------------------ # A163239 -- diagonals sF, opposite side start, flip base-4 digits 2,3 MyOEIS::compare_values (anum => 'A163239', func => sub { my ($count) = @_; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'sF'); my $diagonal_path = Math::PlanePath::Diagonals->new (direction => 'down'); my @got; for (my $n = $diagonal_path->n_start; @got < $count; $n++) { my ($x, $y) = $diagonal_path->n_to_xy ($n); my $n = $gray_path->xy_to_n ($x, $y); $n = flip_base4_23($n); push @got, $n; } return \@got; }); # A163240 -- inverse MyOEIS::compare_values (anum => 'A163240', func => sub { my ($count) = @_; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'sF'); my $diagonal_path = Math::PlanePath::Diagonals->new (direction => 'down'); my @got; for (my $n = $gray_path->n_start; @got < $count; $n++) { my $n = flip_base4_23($n); my ($x, $y) = $gray_path->n_to_xy ($n); $n = $diagonal_path->xy_to_n ($x, $y); push @got, $n + $gray_path->n_start - $diagonal_path->n_start; } return \@got; }); #------------------------------------------------------------------------------ # A163242 -- sF diagonal sums MyOEIS::compare_values (anum => 'A163242', func => sub { my ($count) = @_; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'sF'); my @got; for (my $y = 0; @got < $count; $y++) { my $sum = 0; foreach my $i (0 .. $y) { $sum += $gray_path->xy_to_n ($i, $y-$i); } push @got, $sum; } return \@got; }); #------------------------------------------------------------------------------ # A163478 -- sF diagonal sums, divided by 3 MyOEIS::compare_values (anum => 'A163478', func => sub { my ($count) = @_; my $gray_path = Math::PlanePath::GrayCode->new (apply_type => 'sF'); my @got; for (my $y = 0; @got < $count; $y++) { my $sum = 0; foreach my $i (0 .. $y) { $sum += $gray_path->xy_to_n ($i, $y-$i); } push @got, $sum / 3; } return \@got; }); #------------------------------------------------------------------------------ # A003188 - binary reflected Gray # modular and reflected same in binary MyOEIS::compare_values (anum => 'A003188', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { push @got, to_Gray_reflected($n,2); } return \@got; }); MyOEIS::compare_values (anum => 'A003188', func => sub { my ($count) = @_; my $radix = 2; my @got; for (my $n = 0; @got < $count; $n++) { my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_to_gray_modular($digits,$radix); push @got, digit_join_lowtohigh($digits,$radix); } return \@got; }); # A014550 - binary Gray reflected, in binary MyOEIS::compare_values (anum => 'A014550', func => sub { my ($count) = @_; my $radix = 2; my @got; for (my $n = 0; @got < $count; $n++) { my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_to_gray_reflected($digits,$radix); push @got, digit_join_lowtohigh($digits,10); } return \@got; }); MyOEIS::compare_values (anum => q{A014550}, func => sub { my ($count) = @_; my $radix = 2; my @got; for (my $n = 0; @got < $count; $n++) { my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_to_gray_modular($digits,$radix); push @got, digit_join_lowtohigh($digits,10); } return \@got; }); # A006068 - binary Gray reflected inverse MyOEIS::compare_values (anum => q{A006068}, func => sub { my ($count) = @_; my $radix = 2; my @got; for (my $n = 0; @got < $count; $n++) { my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_from_gray_reflected($digits,$radix); push @got, digit_join_lowtohigh($digits,$radix); } return \@got; }); MyOEIS::compare_values (anum => q{A006068}, func => sub { my ($count) = @_; my $radix = 2; my @got; for (my $n = 0; @got < $count; $n++) { my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_from_gray_modular($digits,$radix); push @got, digit_join_lowtohigh($digits,$radix); } return \@got; }); # binary reflected Gray code increments # lowest 1-bit of N, and negate if bit above it is a 1 MyOEIS::compare_values (anum => 'A055975', func => sub { my ($count) = @_; my @got; for (my $n = 1; @got < $count; $n++) { push @got, to_Gray_reflected($n,2) - to_Gray_reflected($n-1,2); } return \@got; }); # A119972 - signed n according as binary reflected Gray code increment negative # dragon curve turn(n) * n MyOEIS::compare_values (anum => 'A119972', func => sub { my ($count) = @_; my @got; for (my $n = 1; @got < $count; $n++) { push @got, to_Gray_reflected($n,2) > to_Gray_reflected($n-1,2) ? $n : -$n; } return \@got; }); # A119974 - insert 0s into A119972 ... # https://oeis.org/A119974/table # # A220466 - something bit wise crossreffed from increments A055975 ... #------------------------------------------------------------------------------ # A105530 - ternary Gray modular MyOEIS::compare_values (anum => 'A105530', func => sub { my ($count) = @_; my $radix = 3; my @got; for (my $n = 0; @got < $count; $n++) { my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_to_gray_modular($digits,$radix); push @got, digit_join_lowtohigh($digits,$radix); } return \@got; }); # A105529 - ternary Gray modular inverse MyOEIS::compare_values (anum => 'A105529', func => sub { my ($count) = @_; my $radix = 3; my @got; for (my $n = 0; @got < $count; $n++) { my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_from_gray_modular($digits,$radix); push @got, digit_join_lowtohigh($digits,$radix); } return \@got; }); # GP-DEFINE A105530(n) = my(v=digits(n,3)); forstep(i=#v,2,-1, v[i]=(v[i]-v[i-1])%3); fromdigits(v,3); # my(v=OEIS_samples("A105530")); vector(#v,n,n--; A105530(n)) == v \\ OFFSET=0 # my(g=OEIS_bfile_gf("A105530")); g==Polrev(vector(poldegree(g)+1,n,n--;A105530(n))) # poldegree(OEIS_bfile_gf("A105530")) # GP-Test vector(3^5,n,n--; A105530(n)) == \ # GP-Test vector(3^5,n,n--; to_Gray(n,3)) # vector(20,n, to_Gray(n,4)) # vector(20,n, to_Gray(n,5)) # not in OEIS: 1, 2, 3, 7, 4, 5, 6, 10, 11, 8, 9, 13, 14, 15, 12, 28, 29, 30, 31, 19 # not in OEIS: 1, 2, 3, 4, 9, 5, 6, 7, 8, 13, 14, 10, 11, 12, 17, 18, 19, 15, 16, 21 #------------------------------------------------------------------------------ # A128173 - ternary Gray reflected # odd radix to and from are the same MyOEIS::compare_values (anum => 'A128173', func => sub { my ($count) = @_; my $radix = 3; my @got; for (my $n = 0; @got < $count; $n++) { my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_to_gray_reflected($digits,$radix); push @got, digit_join_lowtohigh($digits,$radix); } return \@got; }); MyOEIS::compare_values (anum => q{A128173}, func => sub { my ($count) = @_; my $radix = 3; my @got; for (my $n = 0; @got < $count; $n++) { my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_from_gray_reflected($digits,$radix); push @got, digit_join_lowtohigh($digits,$radix); } return \@got; }); # GP-DEFINE \\ mine in A128173 # GP-DEFINE ternary_reflected_Gray(n) = { # GP-DEFINE my(v=digits(n,3),r=Mod(0,2)); # GP-DEFINE for(i=1,#v, if(r,v[i]=2-v[i]); r+=v[i]); fromdigits(v,3); # GP-DEFINE } # GP-DEFINE A128173 = ternary_reflected_Gray; # GP-Test my(v=OEIS_samples("A128173")); /* OFFSET=0 */ \ # GP-Test vector(#v,n,n--; A128173(n)) == v # my(g=OEIS_bfile_gf("A128173")); \ # g==Polrev(vector(poldegree(g)+1,n,n--; A128173(n))) # poldegree(OEIS_bfile_gf("A128173")) #------------------------------------------------------------------------------ # A003100 - decimal Gray reflected MyOEIS::compare_values (anum => 'A003100', func => sub { my ($count) = @_; my $radix = 10; my @got; for (my $n = 0; @got < $count; $n++) { my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_to_gray_reflected($digits,$radix); push @got, digit_join_lowtohigh($digits,$radix); } return \@got; }); # A174025 - decimal Gray reflected inverse MyOEIS::compare_values (anum => 'A174025', func => sub { my ($count) = @_; my $radix = 10; my @got; for (my $n = 0; @got < $count; $n++) { my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_from_gray_reflected($digits,$radix); push @got, digit_join_lowtohigh($digits,$radix); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/SierpinskiCurve-oeis.t0000644000175000017500000000566213474706343017573 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt; use Test; plan tests => 4; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::SierpinskiCurve; use Math::NumSeq::PlanePathDelta; use Math::NumSeq::PlanePathTurn; #------------------------------------------------------------------------------ # A081026 -- X at N=2^k MyOEIS::compare_values (anum => 'A081026', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiCurve->new; my @got = (1); for (my $n = Math::BigInt->new(1); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); #------------------------------------------------------------------------------ # A081706 - N-1 positions of left turns MyOEIS::compare_values (anum => 'A081706', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SierpinskiCurve', turn_type => 'Left'); my @got; for (my $n = $seq->i_start; @got < $count; $n++) { my ($i,$value) = $seq->next; if ($value) { # if a left turn push @got, $i-1; } } return \@got; }); #------------------------------------------------------------------------------ # A039963 - turn 1=right,0=left # R,R L,L R,R MyOEIS::compare_values (anum => 'A039963', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SierpinskiCurve', turn_type => 'Right'); my @got; for (my $n = $seq->i_start; @got < $count; $n++) { push @got, $seq->ith($n); } return \@got; }); #------------------------------------------------------------------------------ # A127254 - abs(dY) extra initial 1 MyOEIS::compare_values (anum => 'A127254', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => 'SierpinskiCurve', delta_type => 'AbsdY'); my @got = (1); while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/Diagonals-oeis.t0000644000175000017500000002435113717576217016350 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; use Math::BigInt try => 'GMP'; plan tests => 16; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::Diagonals; use Math::NumSeq::PlanePathTurn; use Math::NumSeq::PlanePathCoord; #------------------------------------------------------------------------------ # A097806 - Riordan 1,1,x-1 zeros foreach my $direction ('down','up') { MyOEIS::compare_values (anum => 'A097806', func => sub { my ($count) = @_; my $path = Math::PlanePath::Diagonals->new(direction => $direction); my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object => $path, turn_type => 'NotStraight'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); } #------------------------------------------------------------------------------ # A057554 -- X,Y successively MyOEIS::compare_values (anum => 'A057554', func => sub { my ($count) = @_; my $path = Math::PlanePath::Diagonals->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; if (@got < $count) { push @got, $y; } } return \@got; }); # A057555 -- X,Y successively, x_start=1,y_start=1 MyOEIS::compare_values (anum => 'A057555', func => sub { my ($count) = @_; my $path = Math::PlanePath::Diagonals->new (x_start=>1, y_start=>1); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; if (@got < $count) { push @got, $y; } } return \@got; }); #------------------------------------------------------------------------------ # A057046 -- X at N=2^k { my $path = Math::PlanePath::Diagonals->new (x_start=>1, y_start=>1); MyOEIS::compare_values (anum => 'A057046', func => sub { my ($count) = @_; my @got; for (my $n = Math::BigInt->new(1); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); # A057047 -- Y at N=2^k MyOEIS::compare_values (anum => 'A057047', func => sub { my ($count) = @_; my @got; for (my $n = Math::BigInt->new(1); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); } #------------------------------------------------------------------------------ # A185787 -- total N in row up to Y=X diagonal MyOEIS::compare_values (anum => 'A185787', max_count => 1000, func => sub { my ($count) = @_; my $path = Math::PlanePath::Diagonals->new; my @got; for (my $y = 0; @got < $count; $y++) { push @got, path_rect_to_accumulation ($path, 0,$y, $y,$y); } return \@got; }); #------------------------------------------------------------------------------ # A100182 -- total N in column to X=Y leading diagonal # tetragonal anti-prism numbers (7*n^3 - 3*n^2 + 2*n)/6 MyOEIS::compare_values (anum => 'A100182', max_count => 1000, func => sub { my ($count) = @_; my $path = Math::PlanePath::Diagonals->new; my @got; for (my $x = 0; @got < $count; $x++) { push @got, path_rect_to_accumulation ($path, $x,0, $x,$x); } return \@got; }); #------------------------------------------------------------------------------ # A185788 -- total N in row to X=Y-1 before leading diagonal MyOEIS::compare_values (anum => 'A185788', max_count => 1000, func => sub { my ($count) = @_; my $path = Math::PlanePath::Diagonals->new; my @got = (0); for (my $y = 1; @got < $count; $y++) { push @got, path_rect_to_accumulation ($path, 0,$y, $y-1,$y); } return \@got; }); #------------------------------------------------------------------------------ # A101165 -- total N in column up to Y=X-1 before leading diagonal MyOEIS::compare_values (anum => 'A101165', max_count => 1000, func => sub { my ($count) = @_; my $path = Math::PlanePath::Diagonals->new; my @got = (0); for (my $x = 1; @got < $count; $x++) { push @got, path_rect_to_accumulation ($path, $x,0, $x,$x-1); } return \@got; }); #------------------------------------------------------------------------------ # A185506 -- accumulation array, by antidiagonals # accumulation being total sum N in rectangle 0,0 to X,Y MyOEIS::compare_values (anum => 'A185506', func => sub { my ($count) = @_; my $path = Math::PlanePath::Diagonals->new; my @got; for (my $d = $path->n_start; @got < $count; $d++) { my ($x,$y) = $path->n_to_xy($d); # by anti-diagonals push @got, path_rect_to_accumulation($path, 0,0, $x,$y); } return \@got; }); sub path_rect_to_accumulation { my ($path, $x1,$y1, $x2,$y2) = @_; # $x1 = round_nearest ($x1); # $y1 = round_nearest ($y1); # $x2 = round_nearest ($x2); # $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; my $accumulation = 0; foreach my $x ($x1 .. $x2) { foreach my $y ($y1 .. $y2) { $accumulation += $path->xy_to_n($x,$y); } } return $accumulation; } #------------------------------------------------------------------------------ # A103451 -- turn 1=left or right, 0=straight # but has extra n=1 whereas path first turn at starts N=2 MyOEIS::compare_values (anum => 'A103451', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'Diagonals', turn_type => 'LSR'); my @got = (1); while (@got < $count) { my ($i,$value) = $seq->next; push @got, abs($value); } return \@got; }); #------------------------------------------------------------------------------ # A103452 -- turn 1=left,0=straight,-1=right # but has extra n=1 whereas path first turn at starts N=2 MyOEIS::compare_values (anum => 'A103452', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'Diagonals', turn_type => 'LSR'); my @got = (1); while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A215200 -- Kronecker(n-k,k) by rows, n>=1 1<=k<=n # for n=6 runs n-k=5,4,3,2,1,0 for n=1 runs n-k=0 # k=1,2,3,4,5,6 k=1 # x=n-k y=k is diagonal up from X axis MyOEIS::compare_values (anum => q{A215200}, func => sub { my ($count) = @_; my $path = Math::PlanePath::Diagonals->new (direction => 'up', x_start => 0, y_start => 1); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); push @got, Math::NumSeq::PlanePathCoord::_kronecker_symbol($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A038722 -- permutation N at transpose Y,X, n_start=1 MyOEIS::compare_values (anum => 'A038722', func => sub { my ($count) = @_; my $path = Math::PlanePath::Diagonals->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n ($y, $x); } return \@got; }); MyOEIS::compare_values (anum => 'A038722', func => sub { my ($count) = @_; my $path = Math::PlanePath::Diagonals->new (direction => 'up'); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n ($y, $x); } return \@got; }); #------------------------------------------------------------------------------ # A061579 -- permutation N at transpose Y,X MyOEIS::compare_values (anum => 'A061579', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::Diagonals->new (n_start => 0); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n ($y, $x); } return \@got; }); MyOEIS::compare_values (anum => 'A061579', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::Diagonals->new (n_start => 0, direction => 'up'); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n ($y, $x); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/TriangleSpiralSkewed-oeis.t0000644000175000017500000002362213246362136020520 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # "type-2" skewed to the right # # 4 # / | # 14 4 5 3 ... skew="right" # 13 3 5 / | | # 12 2 1 6 6 1--2 12 # 11 10 9 8 7 / | # 7--8--9--10-11 # "type-3" diagonal first 29 # 16 15 14 13-12-11 28 # / # 7 17 4--3--2 10 27 skew="up" # 6 8 | / / # 5 1 9 18 5 1 9 26 # 4 3 2 10 | / # 15 14 13 12 11 19 6 8 25 # | / # 20 7 24 # / # 21 23 # |/ # 22 # TriangleSpiralSkewed # # 4 # |\ # 5 3 ... # | \ \ # 6 1--2 12 # | \ # 7--8--9-10-11 # use 5.004; use strict; use Test; plan tests => 14; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use List::Util 'min', 'max'; use Math::PlanePath::TriangleSpiralSkewed; #------------------------------------------------------------------------------ # A214230 -- sum of 8 neighbouring N, skew="left" MyOEIS::compare_values (anum => 'A214230', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangleSpiralSkewed->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { push @got, path_n_sum_surround8($path,$n); } return \@got; }); # A214251 -- sum of 8 neighbouring N, "type 2" skew="right" MyOEIS::compare_values (anum => 'A214251', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangleSpiralSkewed->new (skew => 'right'); my @got; for (my $n = $path->n_start; @got < $count; $n++) { push @got, path_n_sum_surround8($path,$n); } return \@got; }); # A214252 -- sum of 8 neighbouring N, "type 3" skew="up" MyOEIS::compare_values (anum => 'A214252', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangleSpiralSkewed->new (skew => 'up'); my @got; for (my $n = $path->n_start; @got < $count; $n++) { push @got, path_n_sum_surround8($path,$n); } return \@got; }); sub path_n_sum_surround8 { my ($path, $n) = @_; my ($x,$y) = $path->n_to_xy ($n); return ($path->xy_to_n($x+1,$y) + $path->xy_to_n($x-1,$y) + $path->xy_to_n($x,$y+1) + $path->xy_to_n($x,$y-1) + $path->xy_to_n($x+1,$y+1) + $path->xy_to_n($x-1,$y-1) + $path->xy_to_n($x-1,$y+1) + $path->xy_to_n($x+1,$y-1)); } #------------------------------------------------------------------------------ # A214231 -- sum of 4 neighbouring N MyOEIS::compare_values (anum => 'A214231', func => sub { my ($count) = @_; my $path = Math::PlanePath::TriangleSpiralSkewed->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { push @got, path_n_sum_surround4($path,$n); } return \@got; }); sub path_n_sum_surround4 { my ($path, $n) = @_; my ($x,$y) = $path->n_to_xy ($n); return ($path->xy_to_n($x+1,$y) + $path->xy_to_n($x-1,$y) + $path->xy_to_n($x,$y+1) + $path->xy_to_n($x,$y-1) ); } #------------------------------------------------------------------------------ # A081272 -- N on slope=2 SSE MyOEIS::compare_values (anum => 'A081272', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::TriangleSpiralSkewed->new; my $x = 0; my $y = 0; while (@got < $count) { push @got, $path->xy_to_n ($x,$y); $x += 1; $y -= 2; } return \@got; }); #------------------------------------------------------------------------------ # A081275 -- N on X=Y+1 diagonal MyOEIS::compare_values (anum => 'A081275', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::TriangleSpiralSkewed->new (n_start => 0); for (my $y = 0; @got < $count; $y++) { my $x = $y + 1; push @got, $path->xy_to_n ($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A217010 -- permutation N values by SquareSpiral order MyOEIS::compare_values (anum => 'A217010', func => sub { my ($count) = @_; require Math::PlanePath::SquareSpiral; my $tsp = Math::PlanePath::TriangleSpiralSkewed->new; my $square = Math::PlanePath::SquareSpiral->new; my @got; for (my $n = $square->n_start; @got < $count; $n++) { my ($x, $y) = $square->n_to_xy ($n); push @got, $tsp->xy_to_n ($x,$y); } return \@got; }); # A217291 -- inverse, TriangleSpiralSkewed X,Y order, SquareSpiral N MyOEIS::compare_values (anum => 'A217291', func => sub { my ($count) = @_; require Math::PlanePath::SquareSpiral; my $tsp = Math::PlanePath::TriangleSpiralSkewed->new; my $square = Math::PlanePath::SquareSpiral->new; my @got; for (my $n = $tsp->n_start; @got < $count; $n++) { my ($x, $y) = $tsp->n_to_xy ($n); push @got, $square->xy_to_n ($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A217011 -- permutation N values by SquareSpiral order, type-2, skew="right" # SquareSpiral North first then clockwise # Triangle West first then clockwise # rotate 90 degrees to compensate MyOEIS::compare_values (anum => 'A217011', func => sub { my ($count) = @_; require Math::PlanePath::SquareSpiral; my $tsp = Math::PlanePath::TriangleSpiralSkewed->new (skew => 'right'); my $square = Math::PlanePath::SquareSpiral->new; my @got; for (my $n = $square->n_start; @got < $count; $n++) { my ($x, $y) = $square->n_to_xy ($n); ($x,$y) = (-$y,$x); # rotate +90 push @got, $tsp->xy_to_n ($x,$y); } return \@got; }); # A217292 -- inverse, TriangleSpiralSkewed X,Y order, SquareSpiral N MyOEIS::compare_values (anum => 'A217292', func => sub { my ($count) = @_; require Math::PlanePath::SquareSpiral; my $tsp = Math::PlanePath::TriangleSpiralSkewed->new (skew => 'right'); my $square = Math::PlanePath::SquareSpiral->new; my @got; for (my $n = $tsp->n_start; @got < $count; $n++) { my ($x, $y) = $tsp->n_to_xy ($n); ($x,$y) = ($y,-$x); # rotate -90 push @got, $square->xy_to_n ($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A217012 -- permutation N values by SquareSpiral order, type-3, skew="up" # SquareSpiral North first then clockwise # Triangle South-East first then clockwise # rotate 90 degrees to compensate MyOEIS::compare_values (anum => 'A217012', func => sub { my ($count) = @_; require Math::PlanePath::SquareSpiral; my $tsp = Math::PlanePath::TriangleSpiralSkewed->new (skew => 'up'); my $square = Math::PlanePath::SquareSpiral->new; my @got; for (my $n = $square->n_start; @got < $count; $n++) { my ($x, $y) = $square->n_to_xy ($n); ($x,$y) = ($y,-$x); # rotate -90 push @got, $tsp->xy_to_n ($x,$y); } return \@got; }); # A217293 -- inverse, TriangleSpiralSkewed X,Y order, SquareSpiral N MyOEIS::compare_values (anum => 'A217293', func => sub { my ($count) = @_; require Math::PlanePath::SquareSpiral; my $tsp = Math::PlanePath::TriangleSpiralSkewed->new (skew => 'up'); my $square = Math::PlanePath::SquareSpiral->new; my @got; for (my $n = $tsp->n_start; @got < $count; $n++) { my ($x, $y) = $tsp->n_to_xy ($n); ($x,$y) = (-$y,$x); # rotate +90 push @got, $square->xy_to_n ($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A217012 -- permutation N values by SquareSpiral order # SquareSpiral North first then clockwise # Triangle South-East first then clockwise # rotate 180 degrees to compensate to skew="down" MyOEIS::compare_values (anum => q{A217012}, func => sub { my ($count) = @_; require Math::PlanePath::SquareSpiral; my $tsp = Math::PlanePath::TriangleSpiralSkewed->new (skew => 'down'); my $square = Math::PlanePath::SquareSpiral->new; my @got; for (my $n = $square->n_start; @got < $count; $n++) { my ($x, $y) = $square->n_to_xy ($n); ($x,$y) = (-$x,-$y); # rotate 180 push @got, $tsp->xy_to_n ($x,$y); } return \@got; }); # A217293 -- inverse, TriangleSpiralSkewed X,Y order, SquareSpiral N MyOEIS::compare_values (anum => q{A217293}, func => sub { my ($count) = @_; require Math::PlanePath::SquareSpiral; my $tsp = Math::PlanePath::TriangleSpiralSkewed->new (skew => 'down'); my $square = Math::PlanePath::SquareSpiral->new; my @got; for (my $n = $tsp->n_start; @got < $count; $n++) { my ($x, $y) = $tsp->n_to_xy ($n); ($x,$y) = (-$x,-$y); # rotate 180 push @got, $square->xy_to_n ($x,$y); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/PyramidRows-oeis.t0000644000175000017500000002224213246362151016707 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; use List::Util 'sum'; plan tests => 13; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::PyramidRows; #------------------------------------------------------------------------------ # A079824 sum of digits on step=1 downward diagonals MyOEIS::compare_values (anum => 'A079824', func => sub { my ($count) = @_; my $path = Math::PlanePath::PyramidRows->new (n_start => 1, step => 1); my @got; foreach my $y (0 .. $count-1) { my $total = 0; for (my $i = 0; ; $i++) { my $n = $path->xy_to_n($i,$y-$i); last if ! defined $n; $total += $n; } push @got, $total; } return \@got; }); # A079823 concatenation of digits on step=1 downward diagonals MyOEIS::compare_values (anum => 'A079823', func => sub { my ($count) = @_; my $path = Math::PlanePath::PyramidRows->new (n_start => 1, step => 1); my @got; foreach my $y (0 .. $count-1) { my $concat = ''; for (my $i = 0; ; $i++) { my $n = $path->xy_to_n($i,$y-$i); last if ! defined $n; $concat .= $n; } push @got, $concat; } return \@got; }); #------------------------------------------------------------------------------ # A020703 - step=2 permutation N at -X,Y MyOEIS::compare_values (anum => 'A020703', func => sub { my ($count) = @_; my $path = Math::PlanePath::PyramidRows->new (n_start => 1, step => 2); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n (-$x,$y); } return \@got; }); # A221217 - step=4 permutation N at -X,Y MyOEIS::compare_values (anum => 'A221217', func => sub { my ($count) = @_; my $path = Math::PlanePath::PyramidRows->new (n_start => 1, step => 4); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, $path->xy_to_n (-$x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A053615 -- distance to pronic is abs(X) MyOEIS::compare_values (anum => 'A053615', func => sub { my ($count) = @_; my $path = Math::PlanePath::PyramidRows->new (n_start => 0); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); push @got, abs($x); } return \@got; }); #------------------------------------------------------------------------------ # A103451 -- turn 1=left or right, 0=straight # but has extra n=1 whereas path first turn at starts N=2 MyOEIS::compare_values (anum => 'A103451', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'PyramidRows,step=1', turn_type => 'LSR'); my @got = (1); while (@got < $count) { my ($i,$value) = $seq->next; push @got, abs($value); } return \@got; }); #------------------------------------------------------------------------------ # A103452 -- turn 1=left,0=straight,-1=right # but has extra n=1 whereas path first turn at starts N=2 MyOEIS::compare_values (anum => 'A103452', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'PyramidRows,step=1', turn_type => 'LSR'); my @got = (1); while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ # A050873 -- step=1 GCD(X+1,Y+1) by rows MyOEIS::compare_values (anum => 'A050873', func => sub { my ($count) = @_; require Math::PlanePath::GcdRationals; my $path = Math::PlanePath::PyramidRows->new (step => 1); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); push @got, Math::PlanePath::GcdRationals::_gcd($x+1,$y+1); } return \@got; }); #------------------------------------------------------------------------------ # A051173 -- step=1 LCM(X+1,Y+1) by rows MyOEIS::compare_values (anum => 'A051173', func => sub { my ($count) = @_; require Math::PlanePath::GcdRationals; my $path = Math::PlanePath::PyramidRows->new (step => 1); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); push @got, ($x+1) * ($y+1) / Math::PlanePath::GcdRationals::_gcd($x+1,$y+1); } return \@got; }); #------------------------------------------------------------------------------ # A215200 -- Kronecker(n-k,k) by rows, n>=1 1<=k<=n MyOEIS::compare_values (anum => q{A215200}, func => sub { my ($count) = @_; my $path = Math::PlanePath::PyramidRows->new (step => 1); require Math::NumSeq::PlanePathCoord; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy ($n); next if $x == 0 || $y == 0; my $n = $y; my $k = $x; push @got, Math::NumSeq::PlanePathCoord::_kronecker_symbol($n-$k,$k); } return \@got; }); #------------------------------------------------------------------------------ # A004201 -- N for which X>=0, step=2 MyOEIS::compare_values (anum => 'A004201', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PyramidRows->new (step => 2); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); if ($x >= 0) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A079824 -- diagonal sums # cf A079825 with rows numbered alternately left and right # a(21)=(n/6)*(7*n^2-6*n+5) MyOEIS::compare_values (anum => 'A079824', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::PyramidRows->new(step=>1); for (my $y = 0; @got < $count; $y++) { my @diag; foreach my $i (0 .. $y) { my $n = $path->xy_to_n($i,$y-$i); next if ! defined $n; push @diag, $n; } my $total = sum(@diag); push @got, $total; # if ($y <= 21) { # MyTestHelpers::diag (join('+',@diag)," = $total"); # } } return \@got; }); #------------------------------------------------------------------------------ # A000217 -- step=1 X=Y diagonal, the triangular numbers from 1 MyOEIS::compare_values (anum => 'A000217', func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::PyramidRows->new (step => 1); for (my $i = 0; @got < $count; $i++) { push @got, $path->xy_to_n($i,$i); } return \@got; }); #------------------------------------------------------------------------------ # A000290 -- step=2 X=Y diagonal, the squares from 1 MyOEIS::compare_values (anum => 'A000290', func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::PyramidRows->new (step => 2); for (my $i = 0; @got < $count; $i++) { push @got, $path->xy_to_n($i,$i); } return \@got; }); #------------------------------------------------------------------------------ # A167407 -- dDiffXY step=1, extra initial 0 MyOEIS::compare_values (anum => 'A167407', func => sub { my ($count) = @_; my $path = Math::PlanePath::PyramidRows->new (step => 1); my @got = (0); for (my $n = $path->n_start; @got < $count; $n++) { my ($dx, $dy) = $path->n_to_dxdy ($n); push @got, $dx-$dy; } return \@got; }); #------------------------------------------------------------------------------ # A010052 -- step=2 dY, 1 at squares MyOEIS::compare_values (anum => 'A010052', func => sub { my ($count) = @_; my @got = (1); my $path = Math::PlanePath::PyramidRows->new (step => 2); for (my $n = $path->n_start; @got < $count; $n++) { my ($x, $y) = $path->n_to_xy ($n); my ($next_x, $next_y) = $path->n_to_xy ($n+1); push @got, $next_y - $y; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/FibonacciWordFractal-oeis.t0000644000175000017500000000730013717577477020461 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 5; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::Diagonals; use Math::PlanePath::FibonacciWordFractal; use Math::NumSeq::PlanePathTurn; my $path = Math::PlanePath::FibonacciWordFractal->new; #------------------------------------------------------------------------------ # A265318 - by diagonals, starting 1, with 0s for unvisited points MyOEIS::compare_values (anum => 'A265318', func => sub { my ($count) = @_; my $diag = Math::PlanePath::Diagonals->new (direction => 'up'); my @got; for (my $n = $diag->n_start; @got < $count; $n++) { my ($x,$y) = $diag->n_to_xy($n); my $n = $path->xy_to_n($x,$y); push @got, defined $n ? $n+1 : 0; } return \@got; }); #------------------------------------------------------------------------------ # A003849 - Fibonacci word 0/1, 0=straight,1=left or right MyOEIS::compare_values (anum => 'A003849', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Straight'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); # A143668 - Fibonacci 0=right,1=straight,2=left MyOEIS::compare_values (anum => 'A143668', func => sub { my ($count) = @_; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value + 1; } return \@got; }); #------------------------------------------------------------------------------ # A332298 -- X coordinate # A332299 -- Y coordinate MyOEIS::compare_values (anum => 'A332298', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start + 1; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); MyOEIS::compare_values (anum => 'A332299', func => sub { my ($count) = @_; my @got; for (my $n = $path->n_start + 1; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y - 1; } return \@got; }); # my(g=OEIS_bfile_gf("A332298")); x(n) = polcoeff(g,n); # my(g=OEIS_bfile_gf("A332299")); y(n) = polcoeff(g,n); # plothraw(vector(3^6,n,n--; x(n)), \ # vector(3^6,n,n--; y(n)), 8+16+32+128) # # 0, 1, 2, 2, 3, 4, 4, 4, 3, 3, 3, 4, 4, 4, 3, 2, 2, 1, 0, 0, 0, 1, 1, 1, # 0, 0, 0, -1, -1, -1, 0, 1, 1, 2, 3, 3, 4, 5, 5, 5, 4, 4, 4, 5, 6, 6, 7, # ~/OEIS/a332298.png # A003849(n)=my(k=2); while(fibonacci(k)<=n, k++); while(n>1, while(fibonacci(k--)>n, ); n-=fibonacci(k)); n==1; # vector(10,n,n--; A003849(n)) # 0, 1, 0, 0, 1, 0, 1 # #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/KochSquareflakes-oeis.t0000644000175000017500000000572713737203406017675 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 2; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::KochSquareflakes; my $path = Math::PlanePath::KochSquareflakes->new; #------------------------------------------------------------------------------ # A332204 -- X coordinate # A332205 -- Y coordinate # ~/OEIS/a332204.gp.txt # * # / \ 0, 45, -45, 0 degrees # / \ # *---* *---* # my(x=OEIS_bfile_func("A332204"), \ # y=OEIS_bfile_func("A332205")); \ # plothraw(vector(3^3,n,n--; x(n)), \ # vector(3^3,n,n--; y(n)), 1+8+16+32) MyOEIS::compare_values (anum => 'A332204', func => sub { my ($count) = @_; my @got; my $k = 0; while ($count > 4**$k) { $k++; } my $lo = (4**($k+1) - 1) / 3; my ($x_lo,$y_lo) = $path->n_to_xy($lo); for (my $n = $lo; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x - $x_lo; } return \@got; }); MyOEIS::compare_values (anum => 'A332205', func => sub { my ($count) = @_; my @got; my $k = 0; while ($count > 4**$k) { $k++; } my $lo = (4**($k+1) - 1) / 3; my ($x_lo,$y_lo) = $path->n_to_xy($lo); for (my $n = $lo; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, - ($y - $y_lo); } return \@got; }); # A332204, A332205 segments unit horizontally, sqrt(2) diagonally, 45 degrees # # 3 * # / # / # 2 *--* # -90 | # 1 * * # / \ / # / \ / # 0 *--* *--* # +45 +45 # 0 1 2 3 4 5 6 7 # my(g=OEIS_bfile_gf("A332204")); x(n) = polcoeff(g,n); # my(g=OEIS_bfile_gf("A332205")); y(n) = polcoeff(g,n); # plothraw(vector(4^3,n,n--; x(n)), \ # vector(4^3,n,n--; y(n)), 1+8+16+32) # # midx(n) = (x(n+1) + x(n))/2; # midy(n) = (y(n+1) + y(n))/2; # plothraw(vector(3^6,n,n--; midx(n)), \ # vector(3^6,n,n--; midy(n)), 1+8+16+32) # plothraw(vector(3^6,n,n--; midx(n) - midy(n)), \ # vector(3^6,n,n--; midx(n) + midy(n)), 1+8+16+32) #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/DivisibleColumns-oeis.t0000644000175000017500000000441313676242267017716 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2018, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::DivisibleColumns; #------------------------------------------------------------------------------ # A077597 - N on X=Y diagonal, being cumulative count divisors - 1 MyOEIS::compare_values (anum => 'A077597', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::DivisibleColumns->new; for (my $x = 1; @got < $count; $x++) { push @got, $path->xy_to_n($x,$x); } return \@got; }); #------------------------------------------------------------------------------ # A027751 - Y coord, proper divisors, extra initial 1 MyOEIS::compare_values (anum => 'A027751', func => sub { my ($count) = @_; my @got = (1); my $path = Math::PlanePath::DivisibleColumns->new (divisor_type => 'proper'); for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A006218 - cumulative count of divisors MyOEIS::compare_values (anum => 'A006218', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::DivisibleColumns->new; for (my $x = 1; @got < $count; $x++) { push @got, $path->xy_to_n($x,1); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/Hypot-oeis.t0000644000175000017500000001472513475106524015545 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt try => 'GMP'; use Math::BigRat; use Math::Trig 'pi'; use Test; plan tests => 6; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::Hypot; # uncomment this to run the ### lines # use Smart::Comments '###'; #------------------------------------------------------------------------------ # A199015 -- partial sums of A008441 MyOEIS::compare_values (anum => 'A199015', func => sub { my ($count) = @_; my $path = Math::PlanePath::Hypot->new(points=>'square_centred'); my @got; my $n = $path->n_start; my $num = 0; my $want_norm = 2; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); my $norm = $x*$x + $y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num/4; $want_norm += 8; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $n++; $num++; } } return \@got; }); #------------------------------------------------------------------------------ # A005883 -- count points with norm==4*n+1 # Theta series of square lattice with respect to deep hole. # # same as "odd" turned 45-degrees # # 3 . 2 . 2 . 3 # # . . . . . . . # # 2 . 1 . 1 . 2 # # . . . o . . . # # 2 . 1 . 1 . 2 # # . . . . . . . # # 3 . 2 . 2 . 3 # # 4, 8, 4, 8,8,0,12,8,0,8,8,8,4,8,0,8,16,0,8,0,4 MyOEIS::compare_values (anum => 'A005883', func => sub { my ($count) = @_; my $path = Math::PlanePath::Hypot->new(points=>'square_centred'); my @got; my $n = $path->n_start; my $num = 0; my $want_norm = 2; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); my $norm = $x*$x + $y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num; $want_norm += 8; $num = 0; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $n++; $num++; } } return \@got; }); # A008441 = A005883/4 # how many ways to write n = x(x+1)/2 + y(y+1)/2 sum two triangulars MyOEIS::compare_values (anum => 'A008441', func => sub { my ($count) = @_; my $path = Math::PlanePath::Hypot->new(points=>'square_centred'); my @got; my $n = $path->n_start; my $num = 0; my $want_norm = 2; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); my $norm = $x*$x + $y*$y; if ($norm > $want_norm) { ### push: $num push @got, $num/4; $want_norm += 8; $num = 0; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $n++; $num++; } } return \@got; }); # MyOEIS::compare_values # (anum => 'A005883', # func => sub { # my ($count) = @_; # my @got; # my $path = Math::PlanePath::Hypot->new (points => 'square_centred'); # my $n = $path->n_start; # my $i = 0; # for (my $i = 0; @got < $count; $i++) { # my $points = 0; # for (;;) { # my $h = $path->n_to_rsquared($n); # if ($h > 4*$i+1) { # last; # } # $points++; # $n++; # } # ### $points # push @got, $points; # } # return \@got; # }); #------------------------------------------------------------------------------ # A004020 Theta series of square lattice with respect to edge. # 2,4,2,4,4 # # 2 . 2 . # # . . . . . . # # . 1 o 1 . # # . . . . # # . 2 . 2 . # # Y mod 2 == 0 # X mod 2 == 1 # X+2Y mod 4 == 1 sub xy_is_edge { my ($x, $y) = @_; return ($y%2 == 0 && $x%2 == 1); } MyOEIS::compare_values (anum => q{A004020}, # with zeros func => sub { my ($count) = @_; my $path = Math::PlanePath::Hypot->new; my @got; my $n = $path->n_start; my $num = 0; my $want_norm = 1; while (@got < $count) { my ($x,$y) = $path->n_to_xy($n); if (! xy_is_edge($x,$y)) { $n++; next; } my $norm = $path->n_to_rsquared($n); if ($norm > $want_norm) { ### push: $num push @got, $num; $want_norm += 4; $num = 0; } else { ### point: "$n at $x,$y norm=$norm total num=$num" $n++; $num++; } } return \@got; }); #------------------------------------------------------------------------------ # A093837 - denominators N(r) / r^2 { my $path = Math::PlanePath::Hypot->new; sub Nr { my ($r) = @_; my $n = $path->xy_to_n($r,0); for (;;) { my $m = $n+1; my ($x,$y) = $path->n_to_xy($m); if ($x*$x+$y*$y > $r*$r) { return $n; } $n = $m; } } } MyOEIS::compare_values (anum => q{A093837}, func => sub { my ($count) = @_; my @got; for (my $r = 1; @got < $count; $r++) { my $Nr = Nr($r); my $rsquared = $r*$r; my $frac = Math::BigRat->new("$Nr/$rsquared"); push @got, $frac->denominator; } return \@got; }); #------------------------------------------------------------------------------ # A093832 - N(r) / r^2 > pi MyOEIS::compare_values (anum => q{A093832}, max_count => 15, func => sub { my ($count) = @_; my @got; for (my $r = 1; @got < $count; $r++) { my $Nr = Nr($r); my $rsquared = $r*$r; if ($Nr / $rsquared > pi()) { push @got, $r; } } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/SierpinskiTriangle-oeis.t0000644000175000017500000006475613716620216020256 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2015, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'sum'; use Math::BigInt try => 'GMP'; use Test; plan tests => 23; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::NumSeq::BalancedBinary; use Math::PlanePath::SierpinskiTriangle; use Math::PlanePath::KochCurve; *_digit_join_hightolow = \&Math::PlanePath::KochCurve::_digit_join_hightolow; # uncomment this to run the ### lines # use Smart::Comments '###'; # { # my $path = Math::PlanePath::SierpinskiTriangle->new; # print branch_reduced_breadth_bits($path,4); # exit 0; # } #------------------------------------------------------------------------------ # Helpers { my $bal = Math::NumSeq::BalancedBinary->new; # $aref is an arrayref of 1,0 bits. sub dyck_bits_to_index { my ($aref) = @_; my $value = _digit_join_hightolow($aref, 2, Math::BigInt->new(0)); return $bal->value_to_i($value); } } sub CountLowZeros { my ($n) = @_; my $ret = 0; until ($n & 1) { $n>>=1; $ret++; $n or die; } return $ret; } sub CountOnes { my ($n) = @_; my $ret = 0; while ($n) { $ret += $n&1; $n>>=1; } return $ret; } #------------------------------------------------------------------------------ # A001316 - Gould's sequence, number of 1s in each row MyOEIS::compare_values (anum => 'A001316', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; my $prev_y = 0; my $num = 0; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($y == $prev_y) { $num++; } else { push @got, $num; $prev_y = $y; $num = 1; } } return \@got; }); # cf Sierpinski Graph # A233775 - num vertices across a row # each N is a unit triangle # # *-----*-----* # \ N / \N+1/ Y # \ / \ / any of X,Y visited, # X-----* <--- row of vertices or X-1,Y-1 below # \ 1 / Y-1 or X+1,Y-1 below # \ / # * MyOEIS::compare_values (anum => 'A233775', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; for (my $y = 0; @got < $count; $y++) { my $count = 0; for (my $x = -$y; $x <= $y; $x+=2) { if ($path->xy_is_visited ($x,$y) || $path->xy_is_visited ($x-1,$y-1) || $path->xy_is_visited ($x+1,$y-1)) { $count++; } } push @got, $count; } return \@got; }); # Johan Falk has this as (2^CountLowZeros(n) + 1) * 2^(CountOnes(n)-1) MyOEIS::compare_values (anum => q{A233775}, func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got = (1); for (my $n = 1; @got < $count; $n++) { push @got, (2**CountLowZeros($n) + 1) * 2**(CountOnes($n)-1); } return \@got; }); # GP-DEFINE CountLowZeros(n) = valuation(n,2); # GP-DEFINE CountOnes(n) = hammingweight(n); # GP-DEFINE A233775(n) = { # GP-DEFINE if(n==0,1, (2^CountLowZeros(n) + 1) * 2^(CountOnes(n)-1)); # GP-DEFINE } # my(v=OEIS_samples("A233775")); vector(#v,n,n--;A233775(n)) == v # GP-Test vector(8,k, vector(2^k-1,n, A233775(2^k + n))) == \ # GP-Test vector(8,k, vector(2^k-1,n, 2*A233775(n))) # GP-Test vector(8,k, A233775(2^k)) == \ # GP-Test vector(8,k, 2^k + 1) # GP-Test A233775(0) == 0 + 1 # GP-DEFINE ShuffleVector(v) = { # GP-DEFINE forstep(i=#v,1,-1, # GP-DEFINE if(v[i], # GP-DEFINE v=concat(v[i..#v],select(b->b, v[1..i-1])); # GP-DEFINE break)); # GP-DEFINE v; # GP-DEFINE } # GP-Test ShuffleVector([1,0,1,0,0]) == [1,0,0, 1] # GP-Test ShuffleVector([1,0,1,1,0,0,0]) == [1,0,0,0, 1,1] # GP-Test ShuffleVector([1,1,0,1,0,0,1,1]) == [1, 1,1,1,1] # GP-Test ShuffleVector([1,0,1,1,0,1,0,0,0]) == [1,0,0,0,1,1,1] # GP-DEFINE ShuffleOnes(n) = fromdigits(ShuffleVector(binary(n)),2); # GP-Test vector(2^12,n,n--; A233775(n)) == \ # GP-Test vector(2^12,n,n--; ShuffleOnes(n) + 1) # vector(15,n, ShuffleOnes(n)) # not in OEIS: 1, 2, 3, 4, 3, 5, 7, 8, 3, 5, 7, 9, 7, 11, 15 #------------------------------------------------------------------------------ # A130047 - left half Pascal mod 2 MyOEIS::compare_values (anum => 'A130047', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; for (my $y = 0; @got < $count; $y++) { for (my $x = -$y; $x <= 0 && @got < $count; $x += 2) { push @got, $path->xy_is_visited($x,$y) ? 1 : 0; } } return \@got; }); #------------------------------------------------------------------------------ # Branch-reduced breadth-wise # # Nodes with just 1 child are collapsed out. # cf Homeomorphic same if dropping/adding single-child nodes # # A080318 decimal # A080319 binary # A080320 positions in A014486 list of balanced # # 10, branch reduced # 111000, # 11111110000000, # 1111111-11000011-0000000, # 11111111100001111111111000000000000000, # # . . # * # plain 10 # # . . . . # # * * # \ / # * # plain 111000 # # . . . . # # * . . * # \ / . . . . # * * * * # \ / \ / # * * # plain 1111001000 reduced 111000 # # . . . . . . . . # * * * * # \ / \ / . . .... .. # * . . * * * * * # \ / \ / \ / # * * * * # \ / \ / # * * # plain reduced 11111110000000 # # . . . . # * * # \ . . . . . . / # * * * * # \ / \ / # * . . * # \ / # * * # \ / # * # # . . . . . . . . # * * * * # \ / \ / # * * # \ . . . . . . / . . . . . . . . 7 trailing # * * * * * * * * # \ / \ / \ / ....\ / # * . . * * * * * # \ / \ / \ / # * * * * # \ / \ / # * * # reduced 1111111110000110000000 # # * * * * # \ . . / \ . . / # * * * * # \ / \ / # * * # \ . . . . . . / # * * * * # \ / \ / # * . . * # \ / # * * # \ / # * # # * * * * * * * * # \ / \ / \ / \ / # * * * * # \ . . / \ . . / # * * * * # \ / \ / .. .. ............ 15 trailing # * * * * * * * * * * # \ . . . . . . / \ / \/ \/ \/ # * * * * * * * * # \ / \ / \ / ....\ / # * . . * * * * * # \ / \ / \ / # * * * * # \ / \ / # * * # reduced 11111111100001111111111000000000000000 # # 1111111110000111111111111000000000000110000000 # 11111111100001111111111110000000000001111111111000000000000000 # [9] [4] [12] [12] [10] [15]# # # 331698516757016399905370236824584576 # 11111111100001111111111110000000000001111111111110000111100001111111\ # 11111111111110000000000000000000000000000110000000 # 2 0 0 0 0 0 0 2 2 0 0 0 0 0 0 2 # 11 2 2 2 2 2 2 2 2 # 10 2 0 0 2 2 0 0 2 # 9 2 2 0 0 0 0 2 2 ## 6 2 2 2 2 # 5 2 0 0 2 # 3 2 2 # 2 2 # 0 { # double-up check my ($one) = MyOEIS::read_values('A080268'); my ($two) = MyOEIS::read_values('A080318'); my $path = Math::PlanePath::SierpinskiTriangle->new; require Math::BigInt; for (my $i = 0; $i <= $#$one && $i+1 <= $#$two; $i++) { my $o = $one->[$i]; my $t = $two->[$i+1]; my $ob = Math::BigInt->new("$o")->as_bin; $ob =~ s/^0b//; my $o2 = $ob; $o2 =~ s/(.)/$1$1/g; # double $o2 = "1".$o2."0"; my $tb = Math::BigInt->new("$t")->as_bin; $tb =~ s/^0b//; # print "o $o\nob $ob\no2 $o2\ntb $tb\n\n"; $tb eq $o2 or die "x"; } } # decimal, by path MyOEIS::compare_values (anum => 'A080318', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; for (my $depth = 0; @got < $count; $depth++) { ### $depth my @bits = branch_reduced_breadth_bits($path, $depth); ### @bits push @got, _digit_join_hightolow(\@bits, 2, Math::BigInt->new(0)); } return \@got; }); # binary, by path MyOEIS::compare_values (anum => 'A080319', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; # foreach my $depth (0 .. 11) { # my @bits = branch_reduced_breadth_bits($path, $depth); # print @bits,"\n"; # } my @got; for (my $depth = 0; @got < $count; $depth++) { my @bits = branch_reduced_breadth_bits($path, $depth); push @got, _digit_join_hightolow(\@bits, 10, Math::BigInt->new(0)); } return \@got; }); # position in list of all balanced binary (A014486) MyOEIS::compare_values (anum => 'A080320', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; for (my $depth = 0; @got < $count; $depth++) { my @bits = branch_reduced_breadth_bits($path, $depth); push @got, dyck_bits_to_index(\@bits); } return \@got; }); # Return a list of 0,1 bits. # sub branch_reduced_breadth_bits { my ($path, $limit) = @_; my @pending_n = ($path->n_start); my @ret; foreach (0 .. $limit) { ### pending_n: join(',',map{$_//'undef'}@pending_n) my @new_n; foreach my $n (@pending_n) { if (! defined $n) { push @ret, 0; next; } my ($x,$y) = $path->n_to_xy($n); push @ret, 1; $y += 1; foreach my $dx (-1, 1) { my $n_child = $path->xy_to_n($x+$dx,$y); if (defined $n_child) { $n_child = path_tree_n_branch_reduce($path,$n_child); } push @new_n, $n_child; } } @pending_n = @new_n; } ### final ... ### pending_n: join(',',map{$_//'undef'}@pending_n) ### ret: join('',@ret) . ' ' .('0' x $#pending_n) return @ret, ((0) x $#pending_n); } # sub path_tree_n_branch_reduced_children { # my ($path, $n) = @_; # for (;;) { # my @n_children = $path->tree_n_children($n); # if (@n_children != 1) { # return @n_children; # } # $n = $n_children[0]; # } # } # If $n has only 1 child then descend through it and any further # 1-child nodes to return an N which has 2 or more children. # If all the descendents of $n are 1-child then return undef. sub path_tree_n_branch_reduce { my ($path, $n) = @_; my @n_children = $path->tree_n_children($n); if (@n_children == 1) { do { $n = $n_children[0]; @n_children = $path->tree_n_children($n) or return undef; } while (@n_children == 1); } return $n; } # Return $x,$y moved down to a "branch reduced" position, if necessary. # A branch reduced tree has all nodes as either leaves or with 2 or more # children. If $x,$y has only 1 child then follow down that child node and # any 1-child nodes below, until reaching a 0 or 2 or more node. If $x,$y # already has 0 or 2 or more then it's returned unchanged. # sub path_tree_xy_branch_reduced { my ($path, $x,$y) = @_; for (;;) { my @xy_list = path_tree_xy_children($path, $x,$y); if (@xy_list == 2) { ($x,$y) = @xy_list; # single child, descend } else { last; # multiple children or nothing, return this $x,$y } } return ($x,$y); } # Return a list ($x1,$y1, $x2,$y2, ...) which are the children of $x,$y. sub path_tree_xy_children { my ($path, $x,$y) = @_; return map {$path->n_to_xy($_)} map {$path->tree_n_children($_)} $path->xy_to_n_list($x,$y); } # Return the number of children of $x,$y, or undef if $x,$y is not visited. sub path_tree_xy_num_children { my ($path, $x,$y) = @_; my $n = $path->xy_to_n($x,$y); if (! defined $n) { return undef; } return $path->tree_n_num_children($path,$n); } # Return true if $x,$y is a leaf node, ie. has no children. sub path_tree_xy_is_leaf { my ($path, $x,$y) = @_; my $n = $path->xy_to_n($x,$y); if (! defined $n) { return undef; } return path_tree_n_is_leaf($path,$n); } # Return true if $n is a leaf node, ie. has no children. sub path_tree_n_is_leaf { my ($path, $n) = @_; my $num_children = $path->tree_n_num_children($n); if (! defined $num_children) { return undef; } return $num_children == 0; } # Return a list of 0,1 bits. # sub DOUBLEUP_branch_reduced_breadth_bits { my ($path, $limit) = @_; my @pending_x = (0); my @pending_y = (0); my @ret = (1); foreach (1 .. $limit) { my @new_x; my @new_y; foreach my $i (0 .. $#pending_x) { my $x = $pending_x[$i]; my $y = $pending_y[$i]; if ($path->xy_is_visited($x,$y)) { push @ret, 1,1; push @new_x, $x-1; push @new_y, $y+1; push @new_x, $x+1; push @new_y, $y+1; } else { push @ret, 0,0; } } @pending_x = @new_x; @pending_y = @new_y; } return (@ret, ((0) x $#pending_x)); # pending open nodes } #------------------------------------------------------------------------------ # A001317 - rows as binary bignums, without the skipped (x^y)&1==1 points of # triangular lattice MyOEIS::compare_values (anum => 'A001317', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new (align => 'right'); my @got; require Math::BigInt; for (my $y = 0; @got < $count; $y++) { my $b = 0; foreach my $x (0 .. $y) { if ($path->xy_is_visited($x,$y)) { $b += Math::BigInt->new(2) ** $x; } } push @got, "$b"; } return \@got; }); #------------------------------------------------------------------------------ # Dyck coded, depth-first # A080263 sierpinski 2, 50, 906, 247986 # A080264 binary 10, 110010, 1110001010, 111100100010110010 # ( ) # # * * * * # \ / \ / # * * * * # \ / \ / # * * * * * * # \ / \ / \ / # * * * * # 10 110010 1,1100,0101,0 11,110010,0010,110010 # 10, 110010, 1110001010, 111100100010110010 # (())() # [(())()] # binary MyOEIS::compare_values (anum => 'A080264', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; for (my $depth = 1; @got < $count; $depth++) { my @bits = dyck_tree_bits($path, 0,0, $depth); push @got, _digit_join_hightolow(\@bits, 10, Math::BigInt->new(0)); } return \@got; }); # position in list of all balanced binary (A014486) MyOEIS::compare_values (anum => 'A080265', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; for (my $depth = 1; @got < $count; $depth++) { my @bits = dyck_tree_bits($path, 0,0, $depth); push @got, dyck_bits_to_index(\@bits); } return \@got; }); # decimal MyOEIS::compare_values (anum => 'A080263', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; for (my $depth = 1; @got < $count; $depth++) { my @bits = dyck_tree_bits($path, 0,0, $depth); push @got, _digit_join_hightolow(\@bits, 2, Math::BigInt->new(0)); } return \@got; }); # No-such node = 0. # Node = 1,left,right. # Drop very last 0 at end. # sub dyck_tree_bits { my ($path, $x,$y, $limit) = @_; my @ret = dyck_tree_bits_z ($path, $x,$y, $limit); pop @ret; return @ret; } sub dyck_tree_bits_z { my ($path, $x,$y, $limit) = @_; if ($limit > 0 && $path->xy_is_visited($x,$y)) { return (1, dyck_tree_bits_z($path, $x-1,$y+1, $limit-1), # left dyck_tree_bits_z($path, $x+1,$y+1, $limit-1)); # right } else { return (0); } } # Doesn't distinguish left and right. # sub parens_bits_z { # my ($path, $x,$y, $limit) = @_; # if ($limit > 0 && $path->xy_is_visited($x,$y)) { # return (1, # parens_bits_z($path, $x-1,$y+1, $limit-1), # left # parens_bits_z($path, $x+1,$y+1, $limit-1), # right # 0); # } else { # return (); # } # } #------------------------------------------------------------------------------ # breath-wise "level-order" # # A080268 decimal 2, 56, 968, 249728, 3996680, # A080269 binary 10, 111000, 1111001000, 111100111110000000, 1111001111110000001000, # (( (()) () )) # # 111100111111000000111111001100111111111000000000000000 # # cf A057118 permute depth<->breadth # # position in list of all balanced binary (A014486) MyOEIS::compare_values (anum => 'A080270', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; for (my $depth = 1; @got < $count; $depth++) { my @bits = level_order_bits($path, $depth); push @got, dyck_bits_to_index(\@bits); } return \@got; }); # decimal MyOEIS::compare_values (anum => 'A080268', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; for (my $depth = 1; @got < $count; $depth++) { my @bits = level_order_bits($path, $depth); push @got, Math::BigInt->new("0b".join('',@bits)); } return \@got; }); # binary MyOEIS::compare_values (anum => 'A080269', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; for (my $depth = 1; @got < $count; $depth++) { my @bits = level_order_bits($path, $depth); push @got, _digit_join_hightolow(\@bits, 10, Math::BigInt->new(0)); } return \@got; }); # Return a list of 0,1 bits. # No-such node = 0. # Node = 1. # Nodes descend to left,right breadth-wise in next level. # Drop very last 0 at end. # sub level_order_bits { my ($path, $limit) = @_; my @pending_x = (0); my @pending_y = (0); my @ret; foreach (1 .. $limit) { my @new_x; my @new_y; foreach my $i (0 .. $#pending_x) { my $x = $pending_x[$i]; my $y = $pending_y[$i]; if ($path->xy_is_visited($x,$y)) { push @ret, 1; push @new_x, $x-1; push @new_y, $y+1; push @new_x, $x+1; push @new_y, $y+1; } else { push @ret, 0; } } @pending_x = @new_x; @pending_y = @new_y; } push @ret, (0) x (scalar(@pending_x)-1); return @ret; } #------------------------------------------------------------------------------ # A106344 - by dX=-3,dY=+1 slopes upwards # cf A106346 its matrix inverse, or something # # 1 # 0, 1 # 0, 1, 1, # 0, 0, 0, 1, # 0, 0, 1, 1, 1, # 0, 0, 0, 1, 0, 1, # 0, 0, 0, 1, 0, 1, 1, # 0, 0, 0, 0, 0, 0, 0, 1, # 0, 0, 0, 0, 1, 0, 1, 1, 1, # 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, # 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, # 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, # 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, # 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1 # 19 20 21 22 23 24 25 26 # 15 16 17 18 # 11 12 13 14 . # 9 10 . # 5 6 7 8 . # 3 . 4 . # 1 2 . . # 0 . . . # path(x,y) = binomial(y,(x+y)/2) # T(n,k)=binomial(k,n-k) # y=k # (x+y)/2=n-k # x+k=2n-2k # x=2n-3k MyOEIS::compare_values (anum => 'A106344', func => sub { my ($count) = @_; # align="left" is dX=1,dY=1 diagonals my $path = Math::PlanePath::SierpinskiTriangle->new (align => 'left'); my @got; my $xstart = 0; my $x = 0; my $y = 0; while (@got < $count) { my $n = $path->xy_to_n($x,$y); push @got, (defined $n ? 1 : 0); $x += 1; $y += 1; if ($x > 0) { $xstart--; $x = $xstart; $y = 0; } } return \@got; }); MyOEIS::compare_values (anum => q{A106344}, func => sub { my ($count) = @_; # align="right" is dX=2,dY=1 slopes, chess knight moves my $path = Math::PlanePath::SierpinskiTriangle->new (align => 'right'); my @got; my $xstart = 0; my $x = 0; my $y = 0; while (@got < $count) { my $n = $path->xy_to_n($x,$y); push @got, (defined $n ? 1 : 0); $x += 2; $y += 1; if ($x > $y) { $xstart--; $x = $xstart; $y = 0; } } return \@got; }); MyOEIS::compare_values (anum => q{A106344}, func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; my $xstart = 0; my $x = 0; my $y = 0; while (@got < $count) { my $n = $path->xy_to_n($x,$y); push @got, (defined $n ? 1 : 0); $x += 3; $y += 1; if ($x > $y) { $xstart -= 2; $x = $xstart; $y = 0; } } return \@got; }); MyOEIS::compare_values (anum => q{A106344}, func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; OUTER: for (my $n = 0; ; $n++) { for (my $k = 0; $k <= $n; $k++) { my $n = $path->xy_to_n(2*$n-3*$k,$k); push @got, (defined $n ? 1 : 0); if (@got >= $count) { last OUTER; } } } return \@got; }); MyOEIS::compare_values (anum => q{A106344}, func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; require Math::BigInt; OUTER: for (my $n = 0; ; $n++) { for (my $k = 0; $k <= $n; $k++) { # my $b = Math::BigInt->new($k); # $b->bnok($n-$k); # binomial(k,k-n) # $b->bmod(2); # push @got, $b; push @got, binomial_mod2 ($k, $n-$k); if (@got >= $count) { last OUTER; } } } return \@got; }); # my $b = Math::BigInt->new($k); # $b->bnok($n-$k); # binomial(k,k-n) # $b->bmod(2); sub binomial_mod2 { my ($n, $k) = @_; return Math::BigInt->new($n)->bnok($k)->bmod(2)->numify; } #------------------------------------------------------------------------------ # A106345 - # k=0..floor(n/2) of binomial(k, n-2k) # # path(x,y) = binomial(y,(x+y)/2) # T(n,k)=binomial(k,n-2k) # y=k # (x+y)/2=n-2k # x+k=2n-4k # x=2n-5k MyOEIS::compare_values (anum => 'A106345', max_count => 1000, # touch slow, shorten func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; for (my $xstart = 0; @got < $count; $xstart -= 2) { my $x = $xstart; my $y = 0; my $total = 0; while ($x <= $y) { my $n = $path->xy_to_n($x,$y); if (defined $n) { $total++; } $x += 5; $y += 1; } push @got, $total; } return \@got; }); #------------------------------------------------------------------------------ # A002487 - stern diatomic count along of dX=3,dY=1 slopes MyOEIS::compare_values (anum => 'A002487', max_count => 1000, # touch slow, shorten func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got = (0); for (my $xstart = 0; @got < $count; $xstart -= 2) { my $x = $xstart; my $y = 0; my $total = 0; while ($x <= $y) { my $n = $path->xy_to_n($x,$y); if (defined $n) { $total++; } $x += 3; $y += 1; } push @got, $total; } return \@got; }); #------------------------------------------------------------------------------ # A047999 - 1/0 by rows, without the skipped (x^y)&1==1 points of triangular # lattice MyOEIS::compare_values (anum => 'A047999', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new; my @got; my $x = 0; my $y = 0; foreach my $n (1 .. $count) { push @got, ($path->xy_is_visited($x,$y) ? 1 : 0); $x += 2; if ($x > $y) { $y++; $x = -$y; } } return \@got; }); MyOEIS::compare_values (anum => q{A047999}, func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new (align => "right"); my @got; my $x = 0; my $y = 0; foreach my $n (1 .. $count) { push @got, ($path->xy_is_visited($x,$y) ? 1 : 0); $x++; if ($x > $y) { $y++; $x = 0; } } return \@got; }); #------------------------------------------------------------------------------ # A075438 - 1/0 by rows of "right", including blank 0s in left of pyramid MyOEIS::compare_values (anum => 'A075438', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiTriangle->new (align => 'right'); my @got; my $x = 0; my $y = 0; foreach my $n (1 .. $count) { push @got, ($path->xy_is_visited($x,$y) ? 1 : 0); $x++; if ($x > $y) { $y++; $x = -$y; } } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/oeis/R5DragonCurve-oeis.t0000644000175000017500000001531513474706164017071 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt; use Test; plan tests => 12; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::R5DragonCurve; my $path = Math::PlanePath::R5DragonCurve->new; #------------------------------------------------------------------------------ # A135518 -- Odistinct sum distinct abs(n-other(n)) MyOEIS::compare_values (anum => 'A135518', max_count => 5, func => sub { my ($count) = @_; my @got; for (my $k = 1; @got < $count; $k++) { my ($n_lo, $n_hi) = $path->level_to_n_range($k); my $total = 0; my %seen; foreach my $n ($n_lo .. $n_hi) { my @n_list = $path->n_to_n_list($n); @n_list == 2 or next; my $d = abs($n_list[0]-$n_list[1]); next if $seen{$d}++; $total += $d; } push @got, $total/4; } return \@got; }); #------------------------------------------------------------------------------ # A006495 -- level end X, b^k MyOEIS::compare_values (anum => 'A006495', func => sub { my ($count) = @_; my @got; for (my $k = Math::BigInt->new(0); @got < $count; $k++) { my ($n_lo, $n_hi) = $path->level_to_n_range($k); my ($x,$y) = $path->n_to_xy($n_hi); push @got, $x; } return \@got; }); # A006496 -- level end Y, b^k MyOEIS::compare_values (anum => 'A006496', func => sub { my ($count) = @_; my @got; for (my $k = Math::BigInt->new(0); @got < $count; $k++) { my ($n_lo, $n_hi) = $path->level_to_n_range($k); my ($x,$y) = $path->n_to_xy($n_hi); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A008776 single-visited points to N=5^k MyOEIS::compare_values (anum => 'A008776', max_value => 10_0, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_n_to_singles ($path, 5**$k); } return \@got; }); #------------------------------------------------------------------------------ # A198859 boundary, one side only, N=0 to 25^k, even levels foreach my $side ('right', 'left') { MyOEIS::compare_values (anum => 'A198859', max_value => 50_000, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length($path, 25**$k, side => $side); } return \@got; }); } # A198963 boundary, one side only, N=0 to 5*25^k, odd levels foreach my $side ('right', 'left') { MyOEIS::compare_values (anum => 'A198963', max_value => 50_000, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length($path, 5*25**$k, side => $side); } return \@got; }); } # A048473 right or left side boundary for points N <= 5^k # which is 1/2 of whole boundary foreach my $side ('right', 'left') { MyOEIS::compare_values (anum => 'A048473', max_value => 50_000, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length($path, 5**$k, side => $side); } return \@got; }); } #------------------------------------------------------------------------------ # A079004 boundary length for points N <= 5^k MyOEIS::compare_values (anum => 'A079004', max_value => 50_000, func => sub { my ($count) = @_; my @got = (7,10); for (my $k = 1; @got < $count; $k++) { push @got, MyOEIS::path_boundary_length($path, 5**$k); } return \@got; }); #------------------------------------------------------------------------------ # A005058 1/2 * enclosed area to N <= 5^k, first differences # A005059 1/4 * enclosed area to N <= 5^k, first differences MyOEIS::compare_values (anum => 'A005059', max_value => 50_000, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { push @got, (MyOEIS::path_enclosed_area($path, 5**($k+1)) - MyOEIS::path_enclosed_area($path, 5**$k)) / 4; } return \@got; }); MyOEIS::compare_values (anum => 'A005058', max_value => 50_000, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { push @got, (MyOEIS::path_enclosed_area($path, 5**($k+1)) - MyOEIS::path_enclosed_area($path, 5**$k)) / 2; } return \@got; }); # A007798 1/2 * enclosed area to N <= 5^k # A016209 1/4 * enclosed area to N <= 5^k MyOEIS::compare_values (anum => 'A007798', max_value => 100_000, func => sub { my ($count) = @_; my @got; for (my $k = 1; @got < $count; $k++) { push @got, MyOEIS::path_enclosed_area($path, 5**$k) / 2; } return \@got; }); MyOEIS::compare_values (anum => 'A016209', max_value => 100_000, func => sub { my ($count) = @_; my @got; for (my $k = 2; @got < $count; $k++) { push @got, MyOEIS::path_enclosed_area($path, 5**$k) / 4; } return \@got; }); #------------------------------------------------------------------------------ # A175337 -- turn 0=left,1=right MyOEIS::compare_values (anum => 'A175337', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'R5DragonCurve', turn_type => 'Right'); my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/KochCurve-more.t0000644000175000017500000000632113475604655015400 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min', 'max'; use Test; plan tests => 1; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::KochCurve; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # rect_to_n_range() on various boxes { my $path = Math::PlanePath::KochCurve->new; my $n_start = $path->n_start; my $bad = 0; my $report = sub { MyTestHelpers::diag (@_); $bad++; }; my $count = 0; foreach my $y1 (-2 .. 10, 18, 30, 50, 100) { foreach my $y2 ($y1 .. $y1 + 10) { foreach my $x1 (-2 .. 10, 18, 30, 50, 100) { my $min; my $max; foreach my $x2 ($x1 .. $x1 + 10) { $count++; my @col = map {$path->xy_to_n($x2,$_)} $y1 .. $y2; @col = grep {defined} @col; $min = min(grep {defined} $min, @col); $max = max(grep {defined} $max, @col); my $want_min = (defined $min ? $min : 1); my $want_max = (defined $max ? $max : 0); ### @col ### rect: "$x1,$y1 $x2,$y2 expect N=$want_min..$want_max" foreach my $x_swap (0, 1) { my ($x1,$x2) = ($x_swap ? ($x1,$x2) : ($x2,$x1)); foreach my $y_swap (0, 1) { my ($y1,$y2) = ($y_swap ? ($y1,$y2) : ($y2,$y1)); my ($got_min, $got_max) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); defined $got_min or &$report ("rect_to_n_range() got_min undef"); defined $got_max or &$report ("rect_to_n_range() got_max undef"); $got_min >= $n_start or &$report ("rect_to_n_range() got_min=$got_min is before n_start=$n_start"); if (! defined $min || ! defined $max) { next; # outside } unless ($got_min == $want_min) { &$report ("rect_to_n_range() bad min $x1,$y1 $x2,$y2 got_min=$got_min want_min=$want_min".(defined $min ? '' : '[nomin]') ); } unless ($got_max == $want_max) { &$report ("rect_to_n_range() bad max $x1,$y1 $x2,$y2 got $got_max want $want_max".(defined $max ? '' : '[nomax]')); } } } } } } } MyTestHelpers::diag ("total $count rectangles"); ok (! $bad); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/MyOEIS.pm0000644000175000017500000005163613662655403013771 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2020 Kevin Ryde # MyOEIS.pm is shared by several distributions. # # MyOEIS.pm 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, or (at your option) any later # version. # # MyOEIS.pm 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 file. If not, see . package MyOEIS; use strict; use Carp 'croak'; use File::Spec; use List::Util 'sum'; # uncomment this to run the ### lines # use Smart::Comments; my $without; sub import { shift; foreach (@_) { if ($_ eq '-without') { $without = 1; } else { die __PACKAGE__." unknown option $_"; } } } # Return $aref, $i_start, $filename sub read_values { my ($anum, %option) = @_; ### read_values() ... ### %option if ($without) { return; } my $i_start; my $filename; my $next; if (my $seq = eval { require Math::NumSeq::OEIS::File; Math::NumSeq::OEIS::File->new (anum => $anum, _b_filename => $option{'bfilename'}) }) { ### $seq $next = sub { my ($i, $value) = $seq->next; return $value; }; $filename = $seq->{'filename'}; $i_start = $seq->i_start; } else { require Math::OEIS::Stripped; my @values = Math::OEIS::Stripped->anum_to_values($anum); if (! @values) { MyTestHelpers::diag ("$anum not available"); return; } ### from stripped ... $next = sub { return shift @values; }; $filename = Math::OEIS::Stripped->filename; } my $desc = $anum; # has ".scalar(@bvalues)." values"; my @bvalues; for (;;) { my $value = &$next(); if (! defined $value) { $desc .= " has ".scalar(@bvalues)." values"; last; } if ((defined $option{'max_count'} && @bvalues >= $option{'max_count'}) || (defined $option{'max_value'} && $value > $option{'max_value'})) { $desc .= " shortened to ".scalar(@bvalues)." values"; last; } push @bvalues, $value; } if (@bvalues) { $desc .= " to $bvalues[-1]"; } MyTestHelpers::diag ($desc); return (\@bvalues, $i_start, $filename); } # with Y reckoned increasing downwards sub dxdy_to_direction { my ($dx, $dy) = @_; if ($dx > 0) { return 0; } # east if ($dx < 0) { return 2; } # west if ($dy > 0) { return 1; } # south if ($dy < 0) { return 3; } # north } sub compare_values { my %option = @_; require MyTestHelpers; my $anum = $option{'anum'} || croak "Missing anum parameter"; my $name = $option{'name'}; if (!defined $name) { $name = ""; } my $func = $option{'func'} || croak "Missing func parameter"; my ($bvalues, $lo, $filename) = MyOEIS::read_values ($anum, max_count => $option{'max_count'}, max_value => $option{'max_value'}, bfilename => $option{'bfilename'}); my $diff; if ($bvalues) { if (my $fixup = $option{'fixup'}) { &$fixup($bvalues); } my ($got,@rest) = &$func(scalar(@$bvalues)); if (@rest) { croak "Oops, func return more than just an arrayref"; } if (ref $got ne 'ARRAY') { croak "Oops, func return not an arrayref"; } ### $got ### $bvalues $diff = diff_nums($got, $bvalues); if ($diff) { MyTestHelpers::diag ("bvalues: ",join_values($bvalues)); MyTestHelpers::diag ("got: ",join_values($got)); } } if (defined $Test::TestLevel) { require Test; local $Test::TestLevel = $Test::TestLevel + 1; Test::skip (! $bvalues, $diff, undef, "$anum $name"); } elsif (defined $diff) { print "$diff\n"; } } sub join_values { my ($aref) = @_; if (! @$aref) { return ''; } my $str = $aref->[0]; foreach my $i (1 .. $#$aref) { my $value = $aref->[$i]; if (! defined $value) { $value = 'undef'; } last if length($str)+1+length($value) >= 275; $str .= ','; $str .= $value; } return $str; } sub diff_nums { my ($gotaref, $wantaref) = @_; my $diff; for (my $i = 0; $i < @$gotaref; $i++) { if ($i > @$wantaref) { return "want ends prematurely pos=$i"; } my $got = $gotaref->[$i]; my $want = $wantaref->[$i]; if (! defined $got && ! defined $want) { next; } if (defined $got != defined $want) { if (defined $diff) { return "$diff, and more diff"; } $diff = "different pos=$i got=".(defined $got ? $got : '[undef]') ." want=".(defined $want ? $want : '[undef]'); } unless ($got =~ /^[0-9.-]+$/) { if (defined $diff) { return "$diff, and more diff"; } $diff = "not a number pos=$i got='$got'"; } unless ($want =~ /^[0-9.-]+$/) { if (defined $diff) { return "$diff, and more diff"; } $diff = "not a number pos=$i want='$want'"; } if ($got != $want) { if (defined $diff) { return "$diff, and more diff"; } $diff = "different pos=$i numbers got=$got want=$want"; } } if (@$gotaref < @$wantaref) { if (defined $diff) { $diff .= ', '; } $diff .= 'got ends prematurely'; } return $diff; } # counting from 1 for prime=2 sub ith_prime { my ($i) = @_; if ($i < 1) { croak "Oops, ith_prime() i=$i"; } require Math::Prime::XS; my $to = 100; for (;;) { my @primes = Math::Prime::XS::primes($to); if (@primes >= $i) { return $primes[$i-1]; } $to *= 2; } } #------------------------------------------------------------------------------ sub first_differences { my $prev = shift; return map { my $diff = $_-$prev; $prev = $_; $diff } @_; } #------------------------------------------------------------------------------ # unit square boundary { my %lattice_type_to_dfunc = (square => \&path_n_to_dboundary, triangular => \&path_n_to_dhexboundary); sub path_n_to_figure_boundary { my ($path, $n_end, %options) = @_; my $boundary = 0; my $dfunc = $lattice_type_to_dfunc{$options{'lattice_type'} || 'square'}; foreach my $n ($path->n_start .. $n_end) { # print "$n ",&$dfunc($path, $n),"\n"; $boundary += &$dfunc($path, $n); } return $boundary; } } BEGIN { my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); sub path_n_to_dboundary { my ($path, $n) = @_; my ($x,$y) = $path->n_to_xy($n) or return 0; { my @n_list = $path->xy_to_n_list($x,$y); if ($n > $n_list[0]) { return 0; } } my $dboundary = 4; foreach my $i (0 .. $#dir4_to_dx) { my $an = $path->xy_to_n($x+$dir4_to_dx[$i], $y+$dir4_to_dy[$i]); $dboundary -= 2*(defined $an && $an < $n); } return $dboundary; } sub path_n_to_dsticks { my ($path, $n) = @_; my ($x,$y) = $path->n_to_xy($n) or return 0; my $dsticks = 4; foreach my $i (0 .. $#dir4_to_dx) { my $an = $path->xy_to_n($x+$dir4_to_dx[$i], $y+$dir4_to_dy[$i]); $dsticks -= (defined $an && $an < $n); } return $dsticks; } } #------------------------------------------------------------------------------ # Return the area enclosed by the curve N=n_start() to N <= $n_limit. # # lattice_type => 'triangular' # Means take the six-way triangular lattice points as adjacent and # measure in X/2 and Y*sqrt(3)/2 so that the points are unit steps. # sub path_enclosed_area { my ($path, $n_limit, %options) = @_; ### path_enclosed_area() ... my $points = path_boundary_points($path, $n_limit, %options); ### $points if (@$points <= 2) { return 0; } require Math::Geometry::Planar; my $polygon = Math::Geometry::Planar->new; $polygon->points($points); return $polygon->area; } { my %lattice_type_to_divisor = (square => 1, triangular => 4); # Return the length of the boundary of the curve N=n_start() to N <= $n_limit. # # lattice_type => 'triangular' # Means take the six-way triangular lattice points as adjacent and # measure in X/2 and Y*sqrt(3)/2 so that the points are unit steps. # sub path_boundary_length { my ($path, $n_limit, %options) = @_; ### path_boundary_length(): "n_limit=$n_limit" my $points = path_boundary_points($path, $n_limit, %options); ### $points my $lattice_type = ($options{'lattice_type'} || 'square'); my $triangular_mult = ($lattice_type eq 'triangular' ? 3 : 1); my $divisor = ($options{'divisor'} || $lattice_type_to_divisor{$lattice_type}); my $side = ($options{'side'} || 'all'); ### $divisor my $boundary = 0; foreach my $i (($side eq 'all' ? 0 : 1) .. $#$points) { ### hypot: ($points->[$i]->[0] - $points->[$i-1]->[0])**2 + $triangular_mult*($points->[$i]->[1] - $points->[$i-1]->[1])**2 $boundary += sqrt((( $points->[$i]->[0] - $points->[$i-1]->[0])**2 + $triangular_mult * ($points->[$i]->[1] - $points->[$i-1]->[1])**2) / $divisor); } ### $boundary return $boundary; } } { my @dir4_to_dxdy = ([1,0], [0,1], [-1,0], [0,-1]); my @dir6_to_dxdy = ([2,0], [1,1], [-1,1], [-2,0], [-1,-1], [1,-1]); my %lattice_type_to_dirtable = (square => \@dir4_to_dxdy, triangular => \@dir6_to_dxdy); # Return arrayref of points [ [$x,$y], ..., [$to_x,$to_y]] # which are the points on the boundary of the curve from $x,$y to # $to_x,$to_y inclusive. # # lattice_type => 'triangular' # Means take the six-way triangular lattice points as adjacent. # sub path_boundary_points_ft { my ($path, $n_limit, $x,$y, $to_x,$to_y, %options) = @_; ### path_boundary_points_ft(): "$x,$y to $to_x,$to_y" ### $n_limit # my @dirtable = $path->_UNDOCUMENTED__dxdy_list; # $lattice_type_to_dirtable{$lattice_type}; my $lattice_type = ($options{'lattice_type'} || 'square'); my @dirtable = @{$lattice_type_to_dirtable{$lattice_type}}; my $dirmod = scalar(@dirtable); my $dirrev = $dirmod / 2 - 1; ### @dirtable ### $dirmod ### $dirrev my $arms = $path->arms_count; my @points; my $dir = $options{'dir'} // 1; my @n_list; # FIXME: can be on boundary without having untraversed edge if (! defined $dir) { foreach my $i (0 .. $dirmod) { my ($dx,$dy) = @{$dirtable[$i]}; if (! defined ($path->xyxy_to_n($x,$y, $x+$dx,$y+$dy))) { $dir = $i; last; } } if (! defined $dir) { die "Oops, $x,$y apparently not on boundary"; } } TOBOUNDARY: for (;;) { @n_list = $path->xy_to_n_list($x,$y) or die "Oops, no n_list at $x,$y"; foreach my $i (1 .. $dirmod) { my $test_dir = ($dir + $i) % $dirmod; my ($dx,$dy) = @{$dirtable[$test_dir]}; my @next_n_list = $path->xy_to_n_list($x+$dx,$y+$dy); if (! any_consecutive(\@n_list, \@next_n_list, $n_limit, $arms)) { ### is boundary: "dxdy = $dx,$dy test_dir=$test_dir" $dir = ($test_dir + 1) % $dirmod; last TOBOUNDARY; } } my ($dx,$dy) = @{$dirtable[$dir]}; if ($x == $to_x && $y == $to_y) { $to_x -= $dx; $to_y -= $dy; } $x -= $dx; $y -= $dy; ### towards boundary: "$x, $y" } ### initial: "dir=$dir n_list=".join(',',@n_list)." seeking to_xy=$to_x,$to_y" for (;;) { ### at: "xy=$x,$y n_list=".join(',',@n_list) push @points, [$x,$y]; $dir = ($dir - $dirrev) % $dirmod; my $found = 0; foreach (1 .. $dirmod) { my ($dx,$dy) = @{$dirtable[$dir]}; my @next_n_list = $path->xy_to_n_list($x+$dx,$y+$dy); ### consider: "dir=$dir next_n_list=".join(',',@next_n_list) if (any_consecutive(\@n_list, \@next_n_list, $n_limit, $arms)) { ### yes, consecutive, go: "dir=$dir dx=$dx,dy=$dy" @n_list = @next_n_list; $x += $dx; $y += $dy; $found = 1; last; } $dir = ($dir+1) % $dirmod; } if (! $found) { die "oops, direction of next boundary step not found"; } if ($x == $to_x && $y == $to_y) { ### stop at: "$x,$y" unless ($x == $points[0][0] && $y == $points[0][1]) { push @points, [$x,$y]; } last; } } return \@points; } } # Return arrayref of points [ [$x1,$y1], [$x2,$y2], ... ] # which are the points on the boundary of the curve N=n_start() to N <= $n_limit # The final point should be taken to return to the initial $x1,$y1. # # lattice_type => 'triangular' # Means take the six-way triangular lattice points as adjacent. # sub path_boundary_points { my ($path, $n_limit, %options) = @_; ### path_boundary_points(): "n_limit=$n_limit" ### %options my $x = 0; my $y = 0; my $to_x = $x; my $to_y = $y; if ($options{'side'} && $options{'side'} eq 'right') { ($to_x,$to_y) = $path->n_to_xy($n_limit); } elsif ($options{'side'} && $options{'side'} eq 'left') { ($x,$y) = $path->n_to_xy($n_limit); } return path_boundary_points_ft($path, $n_limit, $x,$y, $to_x,$to_y, %options); } # $aref and $bref are arrayrefs of N values. # Return true if any pair of values $aref->[a], $bref->[b] are consecutive. # Values in the arrays which are > $n_limit are ignored. sub any_consecutive { my ($aref, $bref, $n_limit, $arms) = @_; foreach my $a (@$aref) { next if $a > $n_limit; foreach my $b (@$bref) { next if $b > $n_limit; if (abs($a-$b) == $arms) { return 1; } } } return 0; } # Return the count of single points in the path from N=Nstart to N=$n_end # inclusive. Anything which happends beyond $n_end does not count, so a # point which is doubled somewhere beyond $n_end is still reckoned as single. # sub path_n_to_singles { my ($path, $n_end) = @_; my $ret = 0; foreach my $n ($path->n_start .. $n_end) { my @n_list = $path->n_to_n_list($n); if (@n_list == 1 || (@n_list == 2 && $n == $n_list[0] && $n_list[1] > $n_end)) { $ret++; } } return $ret; } # Return the count of doubled points in the path from N=Nstart to N=$n_end # inclusive. Anything which happends beyond $n_end does not count, so a # point which is doubled somewhere beyond $n_end is not reckoned as doubled # here. # sub path_n_to_doubles { my ($path, $n_end) = @_; my $ret = 0; foreach my $n ($path->n_start .. $n_end) { my @n_list = $path->n_to_n_list($n); if (@n_list == 2 && $n == $n_list[0] && $n_list[1] <= $n_end) { $ret++; } } return $ret; } # # Return true if the X,Y point at $n is visited only once. # sub path_n_is_single { # my ($path, $n) = @_; # my ($x,$y) = $path->n_to_xy($n) or return 0; # my @n_list = $path->xy_to_n_list($x,$y); # return scalar(@n_list) == 1; # } # Return the count of distinct visited points in the path from N=Nstart to # N=$n_end inclusive. # sub path_n_to_visited { my ($path, $n_end) = @_; my $ret = 0; foreach my $n ($path->n_start .. $n_end) { my @n_list = $path->n_to_n_list($n); if ($n_list[0] == $n) { # relying on sorted @n_list $ret++; } } return $ret; } #------------------------------------------------------------------------------ sub gf_term { my ($gf_str, $i) = @_; my ($num,$den) = ($gf_str =~ m{(.*)/(.*)}) or die $gf_str; $num = Math::Polynomial->new(poly_parse($num)); $den = Math::Polynomial->new(poly_parse($den)); my $q; foreach (0 .. $i) { $q = $num->coeff(0) / $den->coeff(0); $num -= $q * $den; $num->coeff(0) == 0 or die; } return $q; } sub poly_parse { my ($str) = @_; ### poly_parse(): $str unless ($str =~ /^\s*[+-]/) { $str = "+ $str"; } my @coeffs; my $end = 0; ### $str while ($str =~ m{\s*([+-]) # +/- between terms (\s*(-?\d+))? # coefficient ((\s*\*)? # optional * multiplier \s*x # variable \s*(\^\s*(\d+))?)? # optional exponent \s* }xg) { ### between: $1 ### coeff : $2 ### x : $4 $end = pos($str); last if ! defined $2 && ! defined $4; my $coeff = (defined $2 ? $2 : 1); my $power = (defined $7 ? $7 : defined $4 ? 1 : 0); if ($1 eq '-') { $coeff = -$coeff; } $coeffs[$power] += $coeff; ### $coeff ### $power ### $end } ### final coeffs: @coeffs $end == length($str) or die "parse $str fail at pos=$end"; foreach (@coeffs) { $_ ||= 0 } require Math::Polynomial; return Math::Polynomial->new(@coeffs); } #------------------------------------------------------------------------------ # boundary iterator sub path_make_boundary_iterator { my ($path, %option) = @_; my $x = $option{'x'}; my $y = $option{'y'}; if (! defined $x) { ($x,$y) = $path->n_to_xy($path->n_start); } my $dir = $option{'dir'}; if (! defined $dir) { $dir = 1; } my @n_list = $path->xy_to_n_list($x,$y); # my $dirmod = scalar(@$dirtable); # my $dirrev = $dirmod / 2 - 1; # ### $dirmod # ### $dirrev # # my $arms = $path->arms_count; # my @points; # my $dir = $options{'dir'} // 1; return sub { my $ret_x = $x; my $ret_y = $y; return ($ret_x,$ret_y); }; } #------------------------------------------------------------------------------ # recurrence guess # sub guess_recurrence { # my @values = @_; # # require Math::Matrix; # } #------------------------------------------------------------------------------ # polynomial partial fractions # # $numerator / product(@denominators) is a polynomial fraction. # Return a list of polynomials p1,p2,... which are numerators of partial # fractions so # # p1 p2 $numerator # -- + -- + ... = ---------------------- # d1 d2 product(@denominators) # sub polynomial_partial_fractions { my ($numerator, @denominators) = @_; ### denominators: "@denominators" my $total_degree = sum(map {$_->degree} @denominators); ### $total_degree ### numerator degree: $numerator->degree if ($numerator->degree >= $total_degree) { croak "Numerator degree should be less than total denominators"; } require Math::Matrix; my $m = math_matrix_new_zero($total_degree); my @prods; { my $r = 0; foreach my $i (0 .. $#denominators) { my $degree = $denominators[$i]->degree; if ($degree < 0) { croak "Zero denominator"; } # product of denominators excluding this $denominators[$i] my $prod = Math::Polynomial->new(1); foreach my $j (0 .. $#denominators) { if ($i != $j) { $prod *= $denominators[$j] } } push @prods, $prod; my $prod_degree = $prod->degree; ### prod: "$prod" ### $prod_degree foreach my $c (0 .. $degree-1) { foreach my $j (0 .. $prod_degree) { $m->[$r][$c+$j] += $prod->coeff($j); } $r++; } } } ### m: "\n$m" $m = $m->transpose; ### transposed: "\n$m" ### det: $m->determinant if ($m->determinant == 0) { die "Oops, matrix not invertible"; } my $v = Math::Matrix->new(map {[$numerator->coeff($_)]} 0 .. $total_degree-1); ### vector: "\n$v" $m = $m->concat($v); ### concat: "\n$m" my $s = $m->solve; ### solve: "\n$s" my @ret; { my $check = Math::Polynomial->new(0); my $r = 0; foreach my $i (0 .. $#denominators) { if ($denominators[$i]->degree < 0) { croak "Zero denominator"; } my @coeffs; foreach my $j (1 .. $denominators[$i]->degree) { push @coeffs, $s->[$r][0]; $r++; } my $ret = Math::Polynomial->new(@coeffs); push @ret, $ret; $check += $ret * $prods[$i]; } unless ($check == $numerator) { die "Oops, multiply back as check not equal to original numerator, got $check want $numerator\n numerators: ",join(' ',@ret); } } return @ret; } # Return a Math::Matrix which is $rows x $columns of zeros. # If $columns is omitted then square $rows x $rows. sub math_matrix_new_zero { my ($rows, $columns) = @_; if (! defined $columns) { $columns = $rows; } return Math::Matrix->new(map { [ (0) x $columns ] } 0 .. $rows-1); } # a + b*x + c*x^2 d 2 + 2*x^2 # ---------------- + --- = --------------------- # 1 - x - 2*x^3 1-x (1 - x - 2*x^3)*(1-x) # # (a + b*x + c*x^2)*(1-x) + d*(1 - x - 2*x^3) = 2 + 2*x^2 # # a - a*x # b*x - b*x^2 # c*x^2 - c*x^3 # d -d*x -2d*x^3 # = 2 + 2*x^2 # m = [1,0,0,1; -1,1,0,-1; 0,-1,1,0; 0,0,-1,-2] # v = [2;0;2;0] # matsolve(m,v) # # a = -2 4 # b = 2 2 # c = 4 4 # d = 4 -2 # # (-2 + 2*x + 4*x^2)/(1 - x - 2*x^3) + 4 /(1-x) == (2 + 2*x^2)/(1 - x - 2*x^3)*(1-x) 1; __END__ Math-PlanePath-129/xt/0-examples-xrefs.t0000644000175000017500000000430012230011245015611 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2013 Kevin Ryde # 0-examples-xrefs.t is shared by several distributions. # # 0-examples-xrefs.t 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, or (at your option) any # later version. # # 0-examples-xrefs.t 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 file. If not, see . BEGIN { require 5 } use strict; use ExtUtils::Manifest; use Test::More; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } my $manifest = ExtUtils::Manifest::maniread(); my @example_files = grep m{examples/.*\.pl$}, keys %$manifest; my @lib_files = grep m{lib/.*\.(pm|pod)$}, keys %$manifest; sub any_file_contains_example { my ($example) = @_; my $filename; foreach $filename (@lib_files) { if (pod_contains_example($filename, $example)) { return 1; } } foreach $filename (@example_files) { if ($filename ne $example && raw_contains_example($filename, $example)) { return 1; } } return 0; } sub pod_contains_example { my ($filename, $example) = @_; open FH, "< $filename" or die "Cannot open $filename: $!"; my $content = do { local $/; }; # slurp close FH or die "Error closing $filename: $!"; return scalar ($content =~ /F<\Q$example\E> |F\s+directory /xs); } sub raw_contains_example { my ($filename, $example) = @_; $example =~ s{^examples/}{}; open FH, "< $filename" or die "Cannot open $filename: $!"; my $ret = scalar (grep /\b\Q$example\E\b/, ); close FH or die "Error closing $filename: $!"; return $ret > 0; } plan tests => scalar(@example_files) + 1; my $example; foreach $example (@example_files) { is (any_file_contains_example($example), 1, "$example mentioned in some lib/ file"); } ok(1); exit 0; Math-PlanePath-129/xt/PlanePath-subclasses.t0000644000175000017500000033375413774317315016601 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Exercise the various PlanePath subclasses checking for consistency between # n_to_xy() and xy_to_n() and the various range methods, etc. # use 5.004; use strict; use List::Util; use Test; plan tests => 5; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments; use Math::PlanePath; use Math::PlanePath::Base::Generic 'is_infinite'; use Math::PlanePath::Base::Digits 'round_down_pow'; my $verbose = 1; my @modules = ( # modules marked "*" are from Math-PlanePath-Toothpick or # elsewhere and are skipped if not available to test # module list begin 'CornerAlternating', 'CornerAlternating,n_start=0', 'CornerAlternating,n_start=101', 'CornerAlternating,wider=1', 'CornerAlternating,wider=1,n_start=101', 'CornerAlternating,wider=3,n_start=37', 'CornerAlternating,wider=4,n_start=37', 'CornerAlternating,wider=10', 'PeanoDiagonals', 'PeanoDiagonals,radix=2', 'PeanoDiagonals,radix=4', 'PeanoDiagonals,radix=5', 'PeanoDiagonals,radix=17', 'PeanoCurve', 'PeanoCurve,radix=2', 'PeanoCurve,radix=4', 'PeanoCurve,radix=5', 'PeanoCurve,radix=17', 'Columns', 'Columns,height=1', 'Columns,height=2', 'Columns,n_start=0', 'Columns,height=37,n_start=0', 'Columns,height=37,n_start=123', 'Rows', 'Rows,width=1', 'Rows,width=2', 'Rows,n_start=0', 'Rows,width=37,n_start=0', 'Rows,width=37,n_start=123', 'AlternatePaper', 'AlternatePaper,arms=2', 'AlternatePaper,arms=3', 'AlternatePaper,arms=4', 'AlternatePaper,arms=5', 'AlternatePaper,arms=6', 'AlternatePaper,arms=7', 'AlternatePaper,arms=8', 'CCurve', 'AlternateTerdragon', 'AlternateTerdragon,arms=2', 'AlternateTerdragon,arms=3', 'AlternateTerdragon,arms=4', 'AlternateTerdragon,arms=5', 'AlternateTerdragon,arms=6', 'TerdragonRounded', 'TerdragonRounded,arms=2', 'TerdragonRounded,arms=3', 'TerdragonRounded,arms=4', 'TerdragonRounded,arms=5', 'TerdragonRounded,arms=6', 'TerdragonCurve', 'TerdragonCurve,arms=2', 'TerdragonCurve,arms=3', 'TerdragonCurve,arms=4', 'TerdragonCurve,arms=5', 'TerdragonCurve,arms=6', 'SquareReplicate', 'GosperReplicate', 'GosperSide', 'QuintetReplicate', 'QuintetCurve', 'QuintetCurve,arms=2', 'QuintetCurve,arms=3', 'QuintetCurve,arms=4', 'QuintetCentres', 'QuintetCentres,arms=2', 'QuintetCentres,arms=3', 'QuintetCentres,arms=4', 'GrayCode', 'GrayCode,radix=3', 'GrayCode,radix=4', 'GrayCode,radix=5', 'GrayCode,radix=6', 'GrayCode,radix=37', 'GrayCode,apply_type=FsT', 'GrayCode,apply_type=FsT,radix=10', 'GrayCode,apply_type=Fs', 'GrayCode,apply_type=Fs,radix=10', 'GrayCode,apply_type=Ts', 'GrayCode,apply_type=Ts,radix=10', 'GrayCode,apply_type=sF', 'GrayCode,apply_type=sF,radix=10', 'GrayCode,apply_type=sT', 'GrayCode,apply_type=sT,radix=10', 'GrayCode,radix=4,gray_type=modular', 'CfracDigits,radix=1', 'CfracDigits', 'CfracDigits,radix=3', 'CfracDigits,radix=4', 'CfracDigits,radix=10', 'CfracDigits,radix=37', 'DigitGroups', 'DigitGroups,radix=3', 'DigitGroups,radix=4', 'DigitGroups,radix=5', 'DigitGroups,radix=37', 'ChanTree', 'ChanTree,n_start=1234', 'ChanTree,k=2', 'ChanTree,k=2,n_start=1234', 'ChanTree,k=3', 'ChanTree,k=4', 'ChanTree,k=5', 'ChanTree,k=6', 'ChanTree,k=7', 'ChanTree,k=8', 'ChanTree,reduced=1', 'ChanTree,reduced=1,k=2', 'ChanTree,reduced=1,k=3', 'ChanTree,reduced=1,k=4', 'ChanTree,reduced=1,k=5', 'ChanTree,reduced=1,k=6', 'ChanTree,reduced=1,k=7', 'ChanTree,reduced=1,k=8', 'ImaginaryHalf', 'ImaginaryHalf,radix=3', 'ImaginaryHalf,radix=4', 'ImaginaryHalf,radix=5', 'ImaginaryHalf,radix=37', 'ImaginaryHalf,digit_order=XXY,radix=3', 'ImaginaryHalf,digit_order=YXX,radix=3', 'ImaginaryHalf,digit_order=XnXY,radix=3', 'ImaginaryHalf,digit_order=XnYX,radix=3', 'ImaginaryHalf,digit_order=YXnX,radix=3', 'ImaginaryHalf,digit_order=XXY,radix=3', 'MultipleRings,ring_shape=polygon,step=3', 'MultipleRings,ring_shape=polygon,step=4', 'MultipleRings,ring_shape=polygon,step=5', 'MultipleRings,ring_shape=polygon,step=6', 'MultipleRings,ring_shape=polygon,step=7', 'MultipleRings,ring_shape=polygon,step=8', 'MultipleRings,ring_shape=polygon,step=9', 'MultipleRings,ring_shape=polygon,step=12', 'MultipleRings,ring_shape=polygon,step=37', 'MultipleRings', 'MultipleRings,step=0', 'MultipleRings,step=1', 'MultipleRings,step=2', 'MultipleRings,step=3', 'MultipleRings,step=4', 'MultipleRings,step=5', 'MultipleRings,step=6', 'MultipleRings,step=7', 'MultipleRings,step=8', 'MultipleRings,step=37', 'FilledRings', 'FilledRings,n_start=0', 'FilledRings,n_start=37', 'Corner,n_start=101', 'Corner,wider=1,n_start=101', 'Corner,wider=2,n_start=37', 'Corner,wider=13,n_start=37', 'Corner', 'Corner,wider=1', 'Corner,wider=2', 'Corner,wider=37', 'Corner,n_start=0', 'Corner,wider=1,n_start=0', 'Corner,wider=2,n_start=0', 'Corner,wider=37,n_start=0', 'HexSpiral', 'HexSpiral,n_start=0', 'HexSpiral,n_start=37', 'HexSpiral,wider=10,n_start=37', 'HexSpiral,wider=1', 'HexSpiral,wider=2', 'HexSpiral,wider=3', 'HexSpiral,wider=4', 'HexSpiral,wider=5', 'HexSpiral,wider=37', 'HexSpiralSkewed', 'HexSpiralSkewed,n_start=0', 'HexSpiralSkewed,n_start=37', 'HexSpiralSkewed,wider=10,n_start=37', 'HexSpiralSkewed,wider=1', 'HexSpiralSkewed,wider=2', 'HexSpiralSkewed,wider=3', 'HexSpiralSkewed,wider=4', 'HexSpiralSkewed,wider=5', 'HexSpiralSkewed,wider=37', 'PixelRings', 'ImaginaryBase', 'ImaginaryBase,radix=3', 'ImaginaryBase,radix=4', 'ImaginaryBase,radix=5', 'ImaginaryBase,radix=37', 'TriangularHypot', 'TriangularHypot,n_start=0', 'TriangularHypot,n_start=37', 'TriangularHypot,points=odd', 'TriangularHypot,points=all', 'TriangularHypot,points=hex', 'TriangularHypot,points=hex_rotated', 'TriangularHypot,points=hex_centred', 'GreekKeySpiral,turns=0,n_start=100', 'GreekKeySpiral,turns=1,n_start=100', 'GreekKeySpiral,turns=2,n_start=100', 'GreekKeySpiral,turns=3,n_start=100', 'GreekKeySpiral,turns=4,n_start=100', 'GreekKeySpiral,turns=5,n_start=100', 'GreekKeySpiral,turns=6,n_start=100', 'GreekKeySpiral,turns=7,n_start=100', 'GreekKeySpiral,turns=8,n_start=100', 'GreekKeySpiral,turns=9,n_start=100', 'GreekKeySpiral,turns=10,n_start=100', 'GreekKeySpiral,turns=11,n_start=100', 'GreekKeySpiral,turns=37,n_start=100', 'SquareSpiral,n_start=0', 'SquareSpiral,n_start=37', 'SquareSpiral,wider=5,n_start=0', 'SquareSpiral,wider=5,n_start=37', 'SquareSpiral,wider=6,n_start=0', 'SquareSpiral,wider=6,n_start=37', 'SquareSpiral', 'SquareSpiral,wider=1', 'SquareSpiral,wider=2', 'SquareSpiral,wider=3', 'SquareSpiral,wider=4', 'SquareSpiral,wider=5', 'SquareSpiral,wider=6', 'SquareSpiral,wider=37', 'TerdragonMidpoint', 'TerdragonMidpoint,arms=2', 'TerdragonMidpoint,arms=3', 'TerdragonMidpoint,arms=4', 'TerdragonMidpoint,arms=5', 'TerdragonMidpoint,arms=6', 'AnvilSpiral,n_start=0', 'AnvilSpiral,n_start=37', 'AnvilSpiral,n_start=37,wider=9', 'AnvilSpiral', 'AnvilSpiral,wider=1', 'AnvilSpiral,wider=2', 'AnvilSpiral,wider=9', 'AnvilSpiral,wider=17', 'UlamWarburton', 'UlamWarburton,parts=1', 'UlamWarburton,parts=2', 'UlamWarburton,parts=octant', 'UlamWarburton,parts=octant_up', 'UlamWarburton,n_start=0', 'UlamWarburton,n_start=0,parts=2', 'UlamWarburton,n_start=0,parts=1', 'UlamWarburton,n_start=37', 'UlamWarburton,n_start=37,parts=2', 'UlamWarburton,n_start=37,parts=1', 'UlamWarburtonQuarter,parts=octant', 'UlamWarburtonQuarter,parts=octant,n_start=37', 'UlamWarburtonQuarter,parts=octant_up', 'UlamWarburtonQuarter,parts=octant_up,n_start=37', 'UlamWarburtonQuarter', 'UlamWarburtonQuarter,n_start=0', 'UlamWarburtonQuarter,n_start=37', '*LCornerTree', # parts=4 '*LCornerTree,parts=1', '*LCornerTree,parts=2', '*LCornerTree,parts=3', '*LCornerTree,parts=octant_up+1', '*LCornerTree,parts=octant+1', '*LCornerTree,parts=wedge+1', '*LCornerTree,parts=diagonal-1', '*LCornerTree,parts=diagonal', '*LCornerTree,parts=wedge', '*LCornerTree,parts=octant_up', '*LCornerTree,parts=octant', '*OneOfEight', '*OneOfEight,parts=1', '*OneOfEight,parts=octant', '*OneOfEight,parts=octant_up', '*OneOfEight,parts=wedge', '*OneOfEight,parts=3side', # '*OneOfEight,parts=side', '*OneOfEight,parts=3mid', 'PythagoreanTree', 'PythagoreanTree,coordinates=AC', 'PythagoreanTree,coordinates=BC', 'PythagoreanTree,coordinates=PQ', 'PythagoreanTree,coordinates=SM', 'PythagoreanTree,coordinates=SC', 'PythagoreanTree,coordinates=MC', 'PythagoreanTree,tree_type=FB', 'PythagoreanTree,tree_type=FB,coordinates=AC', 'PythagoreanTree,tree_type=FB,coordinates=BC', 'PythagoreanTree,tree_type=FB,coordinates=PQ', 'PythagoreanTree,tree_type=FB,coordinates=SM', 'PythagoreanTree,tree_type=FB,coordinates=SC', 'PythagoreanTree,tree_type=FB,coordinates=MC', 'PythagoreanTree,tree_type=UMT', 'PythagoreanTree,tree_type=UMT,coordinates=AC', 'PythagoreanTree,tree_type=UMT,coordinates=BC', 'PythagoreanTree,tree_type=UMT,coordinates=PQ', 'PythagoreanTree,tree_type=UMT,coordinates=SM', 'PythagoreanTree,tree_type=UMT,coordinates=SC', 'PythagoreanTree,tree_type=UMT,coordinates=MC', 'SierpinskiArrowhead', 'SierpinskiArrowhead,align=right', 'SierpinskiArrowhead,align=left', 'SierpinskiArrowhead,align=diagonal', 'SierpinskiArrowheadCentres', 'SierpinskiArrowheadCentres,align=right', 'SierpinskiArrowheadCentres,align=left', 'SierpinskiArrowheadCentres,align=diagonal', 'SierpinskiTriangle', 'SierpinskiTriangle,n_start=37', 'SierpinskiTriangle,align=left', 'SierpinskiTriangle,align=right', 'SierpinskiTriangle,align=diagonal', 'HilbertSides', 'HilbertCurve', 'HilbertSpiral', '*ToothpickTree', '*ToothpickTree,parts=1', '*ToothpickTree,parts=2', '*ToothpickTree,parts=3', '*ToothpickTree,parts=wedge', '*ToothpickTree,parts=two_horiz', '*ToothpickTree,parts=octant', '*ToothpickTree,parts=octant_up', '*ToothpickReplicate', '*ToothpickReplicate,parts=1', '*ToothpickReplicate,parts=2', '*ToothpickReplicate,parts=3', '*HTree', '*LCornerReplicate', '*ToothpickUpist', 'SierpinskiCurveStair', 'SierpinskiCurveStair,diagonal_length=2', 'SierpinskiCurveStair,diagonal_length=3', 'SierpinskiCurveStair,diagonal_length=4', 'SierpinskiCurveStair,arms=2', 'SierpinskiCurveStair,arms=3,diagonal_length=2', 'SierpinskiCurveStair,arms=4', 'SierpinskiCurveStair,arms=5', 'SierpinskiCurveStair,arms=6,diagonal_length=5', 'SierpinskiCurveStair,arms=7', 'SierpinskiCurveStair,arms=8', 'HIndexing', 'KochSquareflakes', 'KochSquareflakes,inward=>1', 'KochCurve', 'KochPeaks', 'KochSnowflakes', 'SierpinskiCurve', 'SierpinskiCurve,arms=2', 'SierpinskiCurve,arms=3', 'SierpinskiCurve,arms=4', 'SierpinskiCurve,arms=5', 'SierpinskiCurve,arms=6', 'SierpinskiCurve,arms=7', 'SierpinskiCurve,arms=8', 'SierpinskiCurve,diagonal_spacing=5', 'SierpinskiCurve,straight_spacing=5', 'SierpinskiCurve,diagonal_spacing=3,straight_spacing=7', 'SierpinskiCurve,diagonal_spacing=3,straight_spacing=7,arms=7', 'R5DragonMidpoint', 'R5DragonMidpoint,arms=2', 'R5DragonMidpoint,arms=3', 'R5DragonMidpoint,arms=4', 'R5DragonCurve', 'R5DragonCurve,arms=2', 'R5DragonCurve,arms=3', 'R5DragonCurve,arms=4', 'QuadricCurve', 'QuadricIslands', 'LTiling', 'LTiling,L_fill=ends', 'LTiling,L_fill=all', 'FibonacciWordFractal', 'ComplexRevolving', 'ComplexPlus', 'ComplexPlus,realpart=2', 'ComplexPlus,realpart=3', 'ComplexPlus,realpart=4', 'ComplexPlus,realpart=5', 'ComplexMinus', 'ComplexMinus,realpart=2', 'ComplexMinus,realpart=3', 'ComplexMinus,realpart=4', 'ComplexMinus,realpart=5', 'DekkingCurve', 'DekkingCurve,arms=2', 'DekkingCurve,arms=3', 'DekkingCurve,arms=4', 'DekkingCentres', 'DragonMidpoint', 'DragonMidpoint,arms=2', 'DragonMidpoint,arms=3', 'DragonMidpoint,arms=4', 'DragonRounded', 'DragonRounded,arms=2', 'DragonRounded,arms=3', 'DragonRounded,arms=4', 'DragonCurve', 'DragonCurve,arms=2', 'DragonCurve,arms=3', 'DragonCurve,arms=4', 'ZOrderCurve', 'ZOrderCurve,radix=3', 'ZOrderCurve,radix=5', 'ZOrderCurve,radix=9', 'ZOrderCurve,radix=37', 'Flowsnake', 'Flowsnake,arms=2', 'Flowsnake,arms=3', 'FlowsnakeCentres', 'FlowsnakeCentres,arms=2', 'FlowsnakeCentres,arms=3', 'CellularRule,rule=18', # Sierpinski 'CellularRule,rule=18,n_start=0', 'CellularRule,rule=18,n_start=37', 'CubicBase', 'CubicBase,radix=3', 'CubicBase,radix=4', 'CubicBase,radix=37', 'GosperIslands', 'PowerArray', 'PowerArray,radix=3', 'PowerArray,radix=4', 'WythoffPreliminaryTriangle', 'WythoffArray', 'WythoffArray,x_start=1', 'WythoffArray,y_start=1', 'WythoffArray,x_start=1,y_start=1', 'WythoffArray,x_start=5,y_start=7', 'DiagonalsAlternating', 'DiagonalsAlternating,n_start=0', 'DiagonalsAlternating,n_start=37', 'DiagonalsAlternating,x_start=5', 'DiagonalsAlternating,x_start=2,y_start=5', # Math::PlanePath::CellularRule::Line 'CellularRule,rule=2', # left line 'CellularRule,rule=2,n_start=0', 'CellularRule,rule=2,n_start=37', 'CellularRule,rule=4', # centre line 'CellularRule,rule=4,n_start=0', 'CellularRule,rule=4,n_start=37', 'CellularRule,rule=16', # right line 'CellularRule,rule=16,n_start=0', 'CellularRule,rule=16,n_start=37', 'CellularRule,rule=6', # left 1,2 line 'CellularRule,rule=6,n_start=0', 'CellularRule,rule=6,n_start=37', 'CellularRule,rule=20', # right 1,2 line 'CellularRule,rule=20,n_start=0', 'CellularRule,rule=20,n_start=37', # Math::PlanePath::CellularRule::Two 'CellularRule,rule=14', # left 2 cell line 'CellularRule,rule=14,n_start=0', 'CellularRule,rule=14,n_start=37', 'CellularRule,rule=84', # right 2 cell line 'CellularRule,rule=84,n_start=0', 'CellularRule,rule=84,n_start=37', 'CellularRule', 'CellularRule,n_start=0', 'CellularRule,n_start=37', 'CellularRule,rule=206', # left solid 'CellularRule,rule=206,n_start=0', 'CellularRule,rule=206,n_start=37', 'CellularRule,rule=0', # blank 'CellularRule,rule=60', 'CellularRule,rule=220', # right half solid 'CellularRule,rule=222', # full solid 'CretanLabyrinth', 'MPeaks', 'MPeaks,n_start=0', 'MPeaks,n_start=37', '*ToothpickSpiral', '*ToothpickSpiral,n_start=0', '*ToothpickSpiral,n_start=37', 'WunderlichSerpentine', 'WunderlichSerpentine,serpentine_type=100_000_00000', 'WunderlichSerpentine,serpentine_type=110_000_00000', 'WunderlichSerpentine,serpentine_type=111_000_00000', 'WunderlichSerpentine,serpentine_type=10000_00000_00000,radix=5', 'WunderlichSerpentine,serpentine_type=11000_00000_00000,radix=5', 'WunderlichSerpentine,serpentine_type=11100_00000_00000,radix=5', 'WunderlichSerpentine,serpentine_type=11110_00000_00000,radix=5', 'WunderlichSerpentine,serpentine_type=11111_00000_00000,radix=5', 'WunderlichSerpentine,serpentine_type=11111_10000_00000,radix=5', 'WunderlichSerpentine,serpentine_type=11111_11000_00000,radix=5', 'WunderlichSerpentine,serpentine_type=000_000_001', 'WunderlichSerpentine,serpentine_type=010_000_001', 'WunderlichSerpentine,serpentine_type=001_000_001', 'WunderlichSerpentine,serpentine_type=000_100_001', 'WunderlichSerpentine,serpentine_type=000_000_001,radix=5', 'WunderlichSerpentine,serpentine_type=010_000_001,radix=5', 'WunderlichSerpentine,serpentine_type=001_000_001,radix=5', 'WunderlichSerpentine,serpentine_type=000_100_001,radix=5', 'WunderlichSerpentine,radix=2', 'WunderlichSerpentine,radix=4', 'WunderlichSerpentine,radix=5,serpentine_type=coil', # 111..111 'VogelFloret', 'ArchimedeanChords', 'TheodorusSpiral', 'SacksSpiral', 'Hypot,n_start=37', 'Hypot,points=even,n_start=37', 'Hypot', 'Hypot,points=even', 'Hypot,points=odd', 'HypotOctant', 'HypotOctant,points=even', 'HypotOctant,points=odd', 'PyramidRows,align=right', 'PyramidRows,align=right,step=0', 'PyramidRows,align=right,step=1', 'PyramidRows,align=right,step=3', 'PyramidRows,align=right,step=4', 'PyramidRows,align=right,step=5', 'PyramidRows,align=right,step=37', 'PyramidRows,align=left', 'PyramidRows,align=left,step=0', 'PyramidRows,align=left,step=1', 'PyramidRows,align=left,step=3', 'PyramidRows,align=left,step=4', 'PyramidRows,align=left,step=5', 'PyramidRows,align=left,step=37', 'PyramidRows', 'PyramidRows,step=0', 'PyramidRows,step=1', 'PyramidRows,step=3', 'PyramidRows,step=4', 'PyramidRows,step=5', 'PyramidRows,step=37', 'PyramidRows,step=0,n_start=37', 'PyramidRows,step=1,n_start=37', 'PyramidRows,step=2,n_start=37', 'PyramidRows,align=right,step=5,n_start=37', 'PyramidRows,align=left,step=3,n_start=37', 'TriangleSpiralSkewed', 'TriangleSpiralSkewed,n_start=0', 'TriangleSpiralSkewed,n_start=37', 'TriangleSpiralSkewed,skew=right', 'TriangleSpiralSkewed,skew=right,n_start=0', 'TriangleSpiralSkewed,skew=right,n_start=37', 'TriangleSpiralSkewed,skew=up', 'TriangleSpiralSkewed,skew=up,n_start=0', 'TriangleSpiralSkewed,skew=up,n_start=37', 'TriangleSpiralSkewed,skew=down', 'TriangleSpiralSkewed,skew=down,n_start=0', 'TriangleSpiralSkewed,skew=down,n_start=37', 'TriangleSpiral', 'TriangleSpiral,n_start=0', 'TriangleSpiral,n_start=37', 'KnightSpiral', 'KnightSpiral,n_start=0', 'KnightSpiral,n_start=37', 'AlternatePaperMidpoint', 'AlternatePaperMidpoint,arms=2', 'AlternatePaperMidpoint,arms=3', 'AlternatePaperMidpoint,arms=4', 'AlternatePaperMidpoint,arms=5', 'AlternatePaperMidpoint,arms=6', 'AlternatePaperMidpoint,arms=7', 'AlternatePaperMidpoint,arms=8', 'PentSpiral', 'PentSpiral,n_start=0', 'PentSpiral,n_start=37', 'PentSpiralSkewed', 'PentSpiralSkewed,n_start=0', 'PentSpiralSkewed,n_start=37', 'CellularRule54', 'CellularRule54,n_start=0', 'CellularRule54,n_start=37', 'CellularRule57', 'CellularRule57,n_start=0', 'CellularRule57,n_start=37', 'CellularRule57,mirror=1', 'CellularRule57,mirror=1,n_start=0', 'CellularRule57,mirror=1,n_start=37', 'CellularRule190', 'CellularRule190,n_start=0', 'CellularRule190,n_start=37', 'CellularRule190,mirror=1', 'CellularRule190,mirror=1,n_start=0', 'CellularRule190,mirror=1,n_start=37', 'DivisibleColumns', 'DivisibleColumns,n_start=37', 'DivisibleColumns,divisor_type=proper', 'CoprimeColumns', 'CoprimeColumns,n_start=37', 'DiamondArms', 'SquareArms', 'HexArms', 'AR2W2Curve', 'AR2W2Curve,start_shape=D2', 'AR2W2Curve,start_shape=B2', 'AR2W2Curve,start_shape=B1rev', 'AR2W2Curve,start_shape=D1rev', 'AR2W2Curve,start_shape=A2rev', 'BetaOmega', 'KochelCurve', 'CincoCurve', 'WunderlichMeander', 'AztecDiamondRings', 'AztecDiamondRings,n_start=0', 'AztecDiamondRings,n_start=37', 'FactorRationals,sign_encoding=revbinary', 'FactorRationals', 'FactorRationals,sign_encoding=odd/even', 'FactorRationals,sign_encoding=negabinary', 'FactorRationals,sign_encoding=spread', 'PyramidSides', 'PyramidSides,n_start=0', 'PyramidSides,n_start=37', 'Diagonals', 'Diagonals,direction=up', 'Diagonals,n_start=0', 'Diagonals,direction=up,n_start=0', 'Diagonals,n_start=37', 'Diagonals,direction=up,n_start=37', 'Diagonals,x_start=5', 'Diagonals,direction=up,x_start=5', 'Diagonals,x_start=2,y_start=5', 'Diagonals,direction=up,x_start=2,y_start=5', 'PyramidSpiral', 'PyramidSpiral,n_start=0', 'PyramidSpiral,n_start=37', 'HeptSpiralSkewed', 'HeptSpiralSkewed,n_start=0', 'HeptSpiralSkewed,n_start=37', 'Staircase', 'Staircase,n_start=0', 'Staircase,n_start=37', 'StaircaseAlternating', 'StaircaseAlternating,n_start=0', 'StaircaseAlternating,n_start=37', 'StaircaseAlternating,end_type=square', 'StaircaseAlternating,end_type=square,n_start=0', 'StaircaseAlternating,end_type=square,n_start=37', 'OctagramSpiral', 'OctagramSpiral,n_start=0', 'OctagramSpiral,n_start=37', 'CornerReplicate', 'RationalsTree', 'RationalsTree,tree_type=CW', 'RationalsTree,tree_type=AYT', 'RationalsTree,tree_type=Bird', 'RationalsTree,tree_type=Drib', 'RationalsTree,tree_type=L', 'RationalsTree,tree_type=HCS', # '*PeninsulaBridge', 'DiagonalRationals', 'DiagonalRationals,n_start=37', 'DiagonalRationals,direction=up', 'DiagonalRationals,direction=up,n_start=37', 'GcdRationals', 'GcdRationals,pairs_order=rows_reverse', 'GcdRationals,pairs_order=diagonals_down', 'GcdRationals,pairs_order=diagonals_up', 'DiamondSpiral', 'DiamondSpiral,n_start=0', 'DiamondSpiral,n_start=37', 'FractionsTree', 'DiagonalsOctant', 'DiagonalsOctant,direction=up', 'DiagonalsOctant,n_start=0', 'DiagonalsOctant,direction=up,n_start=0', 'DiagonalsOctant,n_start=37', 'DiagonalsOctant,direction=up,n_start=37', 'File', # module list end # cellular 0 to 255 (map {("CellularRule,rule=$_", "CellularRule,rule=$_,n_start=0", "CellularRule,rule=$_,n_start=37")} 0..255), ); @modules = grep { module_exists($_) } @modules; sub module_exists { my ($module) = @_; if ($module =~ /^\*([^,]+)/) { require Module::Util; my $filename = Module::Util::find_installed("Math::PlanePath::$1"); if ($filename) { return 1; } else { MyTestHelpers::diag ("skip optional $module"); return 0; } } else { return 1; # not optional } } foreach (@modules) { s/^\*// } my @classes = map {(module_parse($_))[0]} @modules; { my %seen; @classes = grep {!$seen{$_}++} @classes } # uniq sub module_parse { my ($mod) = @_; my ($class, @parameters) = split /,/, $mod; return ("Math::PlanePath::$class", map {/(.*?)=(.*)/ or die; ($1 => $2)} @parameters); } sub module_to_pathobj { my ($mod) = @_; my ($class, @parameters) = module_parse($mod); ### $mod ### @parameters eval "require $class" or die; return $class->new (@parameters); } { eval { require Module::Util; my %classes = map {$_=>1} @classes; foreach my $module (Module::Util::find_in_namespace('Math::PlanePath')) { next if $classes{$module}; # listed, good next if $module =~ /^Math::PlanePath::[^:]+::/; # skip Base etc submods MyTestHelpers::diag ("other module ",$module); } }; } BEGIN { my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); # return the change in figure boundary from N to N+1 sub path_n_to_dboundary { my ($path, $n) = @_; $n += 1; my ($x,$y) = $path->n_to_xy($n) or do { if ($n == $path->n_start - 1) { return 4; } else { return undef; } }; ### N+1 at: "n=$n xy=$x,$y" my $dboundary = 4; foreach my $i (0 .. $#dir4_to_dx) { my $an = $path->xy_to_n($x+$dir4_to_dx[$i], $y+$dir4_to_dy[$i]); ### consider: "xy=".($x+$dir4_to_dx[$i]).",".($y+$dir4_to_dy[$i])." is an=".($an||'false') $dboundary -= 2*(defined $an && $an < $n); } ### $dboundary return $dboundary; } } #------------------------------------------------------------------------------ # VERSION my $want_version = 129; ok ($Math::PlanePath::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); #------------------------------------------------------------------------------ # new and VERSION # foreach my $class (@classes) { # eval "require $class" or die; # # ok (eval { $class->VERSION($want_version); 1 }, # 1, # "VERSION class check $want_version in $class"); # ok (! eval { $class->VERSION($check_version); 1 }, # 1, # "VERSION class check $check_version in $class"); # # my $path = $class->new; # ok ($path->VERSION, $want_version, # "VERSION object method in $class"); # # ok (eval { $path->VERSION($want_version); 1 }, # 1, # "VERSION object check $want_version in $class"); # ok (! eval { $path->VERSION($check_version); 1 }, # 1, # "VERSION object check $check_version in $class"); # } #------------------------------------------------------------------------------ # x_negative, y_negative foreach my $mod (@modules) { my $path = module_to_pathobj($mod); $path->x_negative; $path->y_negative; $path->n_start; # ok (1,1, 'x_negative(),y_negative(),n_start() methods run'); } #------------------------------------------------------------------------------ # n_to_xy, xy_to_n my %xy_maximum_duplication = ('Math::PlanePath::HilbertSides' => 2, 'Math::PlanePath::DragonCurve' => 2, 'Math::PlanePath::R5DragonCurve' => 2, 'Math::PlanePath::CCurve' => 9999, 'Math::PlanePath::AlternatePaper' => 2, 'AlternatePaper,arms=4' => 3, 'AlternatePaper,arms=5' => 3, 'AlternatePaper,arms=6' => 3, 'AlternatePaper,arms=7' => 3, 'AlternatePaper,arms=8' => 3, 'Math::PlanePath::AlternateTerdragon' => 3, 'Math::PlanePath::TerdragonCurve' => 3, 'Math::PlanePath::KochSnowflakes' => 2, 'Math::PlanePath::QuadricIslands' => 2, 'Math::PlanePath::PeanoDiagonals' => 2, ); my %xy_maximum_duplication_at_origin = ('Math::PlanePath::DragonCurve' => 4, 'Math::PlanePath::AlternateTerdragon' => 6, 'AlternatePaper,arms=8' => 3, 'Math::PlanePath::TerdragonCurve' => 6, 'Math::PlanePath::R5DragonCurve' => 4, ); # modules for which rect_to_n_range() is exact my %rect_exact = ( 'Math::PlanePath::CornerAlternating' => 1, # rect_to_n_range exact begin 'Math::PlanePath::ImaginaryBase' => 1, 'Math::PlanePath::CincoCurve' => 1, 'Math::PlanePath::DiagonalsAlternating' => 1, 'Math::PlanePath::CornerReplicate' => 1, 'Math::PlanePath::Rows' => 1, 'Math::PlanePath::Columns' => 1, 'Math::PlanePath::Diagonals' => 1, 'Math::PlanePath::DiagonalsOctant' => 1, 'Math::PlanePath::Staircase' => 1, 'Math::PlanePath::StaircaseAlternating' => 1, 'Math::PlanePath::PyramidRows' => 1, 'Math::PlanePath::PyramidSides' => 1, 'Math::PlanePath::CellularRule190' => 1, 'Math::PlanePath::Corner' => 1, 'Math::PlanePath::HilbertCurve' => 1, 'Math::PlanePath::HilbertSpiral' => 1, 'Math::PlanePath::PeanoCurve' => 1, 'Math::PlanePath::ZOrderCurve' => 1, 'Math::PlanePath::Flowsnake' => 1, 'Math::PlanePath::FlowsnakeCentres' => 1, 'Math::PlanePath::QuintetCurve' => 1, 'Math::PlanePath::QuintetCentres' => 1, 'Math::PlanePath::DiamondSpiral' => 1, 'Math::PlanePath::AztecDiamondRings' => 1, 'Math::PlanePath::BetaOmega' => 1, 'Math::PlanePath::AR2W2Curve' => 1, 'Math::PlanePath::KochelCurve' => 1, 'Math::PlanePath::WunderlichMeander' => 1, 'Math::PlanePath::File' => 1, 'Math::PlanePath::KochCurve' => 1, # rect_to_n_range exact end ); my %rect_exact_hi = (%rect_exact, # high is exact but low is not 'Math::PlanePath::SquareSpiral' => 1, 'Math::PlanePath::SquareArms' => 1, 'Math::PlanePath::TriangleSpiralSkewed' => 1, 'Math::PlanePath::MPeaks' => 1, ); my %rect_before_n_start = ('Math::PlanePath::Rows' => 1, 'Math::PlanePath::Columns' => 1, ); my %non_linear_frac = ( 'Math::PlanePath::SacksSpiral' => 1, 'Math::PlanePath::VogelFloret' => 1, 'Math::PlanePath::PeanoDiagonals' => 1, ); #------------------------------------------------------------------------------ my ($pos_infinity, $neg_infinity, $nan); my ($is_infinity, $is_nan); if (! eval { require Data::Float; 1 }) { MyTestHelpers::diag ("Data::Float not available"); } elsif (! Data::Float::have_infinite()) { MyTestHelpers::diag ("Data::Float have_infinite() is false"); } else { $is_infinity = sub { my ($x) = @_; return defined($x) && Data::Float::float_is_infinite($x); }; $is_nan = sub { my ($x) = @_; return defined($x) && Data::Float::float_is_nan($x); }; $pos_infinity = Data::Float::pos_infinity(); $neg_infinity = Data::Float::neg_infinity(); $nan = Data::Float::nan(); } sub pos_infinity_maybe { return (defined $pos_infinity ? $pos_infinity : ()); } sub neg_infinity_maybe { return (defined $neg_infinity ? $neg_infinity : ()); } sub nan_maybe { return (defined $nan ? $nan : ()); } sub dbl_max { require POSIX; return POSIX::DBL_MAX(); } sub dbl_max_neg { require POSIX; return - POSIX::DBL_MAX(); } sub dbl_max_for_class_xy { my ($path) = @_; ### dbl_max_for_class_xy(): "$path" if ($path->isa('Math::PlanePath::CoprimeColumns') || $path->isa('Math::PlanePath::DiagonalRationals') || $path->isa('Math::PlanePath::DivisibleColumns') || $path->isa('Math::PlanePath::CellularRule') || $path->isa('Math::PlanePath::DragonCurve') || $path->isa('Math::PlanePath::PixelRings') ) { ### don't try DBL_MAX on this path xy_to_n() ... return (); } return dbl_max(); } sub dbl_max_neg_for_class_xy { my ($path) = @_; if (dbl_max_for_class_xy($path)) { return dbl_max_neg(); } else { return (); } } sub dbl_max_for_class_rect { my ($path) = @_; # no DBL_MAX on these if ($path->isa('Math::PlanePath::CoprimeColumns') || $path->isa('Math::PlanePath::DiagonalRationals') || $path->isa('Math::PlanePath::DivisibleColumns') || $path->isa('Math::PlanePath::CellularRule') || $path->isa('Math::PlanePath::PixelRings') ) { ### don't try DBL_MAX on this path rect_to_n_range() ... return (); } return dbl_max(); } sub dbl_max_neg_for_class_rect { my ($path) = @_; if (dbl_max_for_class_rect($path)) { return dbl_max_neg(); } else { return (); } } sub is_pos_infinity { my ($n) = @_; return defined $n && defined $pos_infinity && $n == $pos_infinity; } sub is_neg_infinity { my ($n) = @_; return defined $n && defined $neg_infinity && $n == $neg_infinity; } sub pythagorean_diag { my ($path,$x,$y) = @_; $path->isa('Math::PlanePath::PythagoreanTree') or return; my $z = Math::Libm::hypot ($x, $y); my $z_not_int = (int($z) != $z); my $z_even = ! ($z & 1); MyTestHelpers::diag ("x=$x y=$y, hypot z=$z z_not_int='$z_not_int' z_even='$z_even'"); my $psq = ($z+$x)/2; my $p = sqrt(($z+$x)/2); my $p_not_int = ($p != int($p)); MyTestHelpers::diag ("psq=$psq p=$p p_not_int='$p_not_int'"); my $qsq = ($z-$x)/2; my $q = sqrt(($z-$x)/2); my $q_not_int = ($q != int($q)); MyTestHelpers::diag ("qsq=$qsq q=$q q_not_int='$q_not_int'"); } { my $default_limit = ($ENV{'MATH_PLANEPATH_TEST_LIMIT'} || 30); my $rect_limit = $ENV{'MATH_PLANEPATH_TEST_RECT_LIMIT'} || 4; MyTestHelpers::diag ("test limit $default_limit, rect limit $rect_limit"); my $good = 1; foreach my $mod (@modules) { if ($verbose) { MyTestHelpers::diag ($mod); } my ($class, %parameters) = module_parse($mod); ### $class eval "require $class" or die; my $xy_maximum_duplication = $xy_maximum_duplication{$mod} // $xy_maximum_duplication{$class} // 1; my $xy_maximum_duplication_at_origin = $xy_maximum_duplication_at_origin{$mod} // $xy_maximum_duplication_at_origin{$class} // $xy_maximum_duplication; # # MyTestHelpers::diag ($mod); # my $depth_limit = 10; my $limit = $default_limit; if (defined (my $step = $parameters{'step'})) { if ($limit < 6*$step) { $limit = 6*$step; # so goes into x/y negative } } if ($mod =~ /^ArchimedeanChords/) { if ($limit > 1100) { $limit = 1100; # bit slow otherwise } } if ($mod =~ /^CoprimeColumns|^DiagonalRationals/) { if ($limit > 1100) { $limit = 1100; # bit slow otherwise } } my $report = sub { my $name = $mod; MyTestHelpers::diag ($name, ' oops ', @_); $good = 0; # exit 1; }; my $path = $class->new (width => 20, height => 20, %parameters); my $arms_count = $path->arms_count; my $n_start = $path->n_start; if ($mod !~ /,/) { # base class only my $parameter_info_hash = $path->parameter_info_hash; if (my $pinfo = $parameter_info_hash->{'n_start'}) { $pinfo->{'default'} == $n_start or &$report("parameter info n_start default $pinfo->{'default'} but path->n_start $n_start"); } if (my $pinfo = $parameter_info_hash->{'arms'}) { $pinfo->{'default'} == $arms_count or &$report("parameter info arms_count default $pinfo->{'default'} but path->arms_count $arms_count"); } foreach my $pinfo ($path->parameter_info_list) { if ($pinfo->{'type'} eq 'enum') { my $choices = $pinfo->{'choices'}; my $num_choices = scalar(@$choices); if (my $choices_display = $pinfo->{'choices_display'}) { my $num_choices_display = scalar(@$choices_display); if ($num_choices != $num_choices_display) { &$report("parameter info $pinfo->{'name'} choices $num_choices but choices_display $num_choices_display"); } } } } ### level_to_n_range() different among arms ... # This checks that if there's an arms parameter then the # level_to_n_range() code takes account of it. if (my $pinfo = $parameter_info_hash->{'arms'}) { my %seen; foreach my $arms ($pinfo->{'minimum'} .. $pinfo->{'maximum'}) { my $apath = $class->new (arms => $arms); my ($n_lo, $n_hi) = $apath->level_to_n_range(3) or next; if (exists $seen{$n_hi}) { &$report ("level_to_n_range() n_hi=$n_hi at arms=$arms is same as from arms=$seen{$n_hi}"); } else { $seen{$n_hi} = $arms; } } ### %seen } ### level_to_n_range() follows n_start ... if (my $pinfo = $parameter_info_hash->{'n_start'}) { my $apath = $class->new (n_start => 100); my ($n_lo_100, $n_hi_100) = $path->level_to_n_range(3) or next; my $bpath = $class->new (n_start => 200); my ($n_lo_200, $n_hi_200) = $path->level_to_n_range(3) or next; if ($n_lo_100 + 100 == $n_lo_200 && $n_hi_100 + 100 == $n_hi_200) { &$report ("level_to_n_range() not affected by n_start"); } } } if ($parameters{'arms'} && $arms_count != $parameters{'arms'}) { &$report("arms_count()==$arms_count expect $parameters{'arms'}"); } unless ($arms_count >= 1) { &$report("arms_count()==$arms_count should be >=1"); } my $n_limit = $n_start + $limit; my $n_frac_discontinuity = $path->n_frac_discontinuity; my $x_negative_at_n = $path->x_negative_at_n; if (defined $x_negative_at_n) { $x_negative_at_n >= $n_start or &$report ("x_negative_at_n() = $x_negative_at_n is < n_start=$n_start"); } my $y_negative_at_n = $path->y_negative_at_n; if (defined $y_negative_at_n) { $y_negative_at_n >= $n_start or &$report ("y_negative_at_n() = $y_negative_at_n is < n_start=$n_start"); } # _UNDOCUMENTED__dxdy_list() # my @_UNDOCUMENTED__dxdy_list = $path->_UNDOCUMENTED__dxdy_list; # list ($dx,$dy, $dx,$dy, ...) @_UNDOCUMENTED__dxdy_list % 2 == 0 or &$report ("_UNDOCUMENTED__dxdy_list() not an even number of values"); my %_UNDOCUMENTED__dxdy_list; # keys "$dx,$dy" for (my $i = 0; $i < $#_UNDOCUMENTED__dxdy_list; $i += 2) { $_UNDOCUMENTED__dxdy_list{"$_UNDOCUMENTED__dxdy_list[$i],$_UNDOCUMENTED__dxdy_list[$i+1]"} = 1; } for (my $i = 2; $i < $#_UNDOCUMENTED__dxdy_list; $i += 2) { if (dxdy_cmp ($_UNDOCUMENTED__dxdy_list[$i-2],$_UNDOCUMENTED__dxdy_list[$i-1], $_UNDOCUMENTED__dxdy_list[$i],$_UNDOCUMENTED__dxdy_list[$i+1]) >= 0) { &$report ("_UNDOCUMENTED__dxdy_list() entries not sorted: $_UNDOCUMENTED__dxdy_list[$i-2],$_UNDOCUMENTED__dxdy_list[$i-1] then $_UNDOCUMENTED__dxdy_list[$i],$_UNDOCUMENTED__dxdy_list[$i+1]"); } } { my ($x,$y) = $path->n_to_xy($n_start); if (! defined $x) { unless ($path->isa('Math::PlanePath::File')) { &$report("n_start()==$n_start doesn't have an n_to_xy()"); } } else { my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); if ($n_lo > $n_start || $n_hi < $n_start) { &$report("n_start()==$n_start outside rect_to_n_range() $n_lo..$n_hi"); } } } if (# VogelFloret has a secret undocumented return for N=0 ! $path->isa('Math::PlanePath::VogelFloret') # Rows/Columns secret undocumented extend into negatives ... && ! $path->isa('Math::PlanePath::Rows') && ! $path->isa('Math::PlanePath::Columns')) { my $n = $n_start - 1; { my @xy = $path->n_to_xy($n); if (scalar @xy) { &$report("n_to_xy() at n_start()-1=$n has X,Y but should not"); } } foreach my $method ('n_to_rsquared', 'n_to_radius') { my @ret = $path->$method($n); if (scalar(@ret) != 1) { &$report("$method() at n_start()-1 return not one value"); } elsif (defined $ret[0]) { &$report("$method() at n_start()-1 has defined value but should not"); } foreach my $offset (1, 2, 123) { ### n_to_r (n_start - offset): $offset my $n = $n_start - $offset; my @ret = $path->$method($n); if ($path->isa('Math::PlanePath::File')) { @ret = (undef); # all undefs for File } my $num_values = scalar(@ret); $num_values == 1 or &$report("$method(n_start - $offset) got $num_values values, want 1"); if ($path->isa('Math::PlanePath::Rows') || $path->isa('Math::PlanePath::Columns')) { ### Rows,Columns has secret values for negative N, pretend not ... @ret = (undef); } if ($offset == 1 && $path->isa('Math::PlanePath::VogelFloret')) { ### VogelFloret has a secret undocumented return for N=0 ... @ret = (undef); } my ($ret) = @ret; if (defined $ret) { &$report("$method($n) n_start-$offset is ",$ret," expected undef"); } } } } { my $saw_warning; local $SIG{'__WARN__'} = sub { $saw_warning = 1; }; foreach my $method ('n_to_xy','n_to_dxdy', 'n_to_rsquared', 'n_to_radius', ($path->tree_n_num_children($n_start) ? ('tree_n_to_depth', 'tree_depth_to_n', 'tree_depth_to_n_end', 'tree_depth_to_n_range', 'tree_n_parent', 'tree_n_root', 'tree_n_children', 'tree_n_num_children', ) : ())){ $saw_warning = 0; $path->$method(undef); $saw_warning or &$report("$method(undef) doesn't give a warning"); } { $saw_warning = 0; $path->xy_to_n(0,undef); $saw_warning or &$report("xy_to_n(0,undef) doesn't give a warning"); } { $saw_warning = 0; $path->xy_to_n(undef,0); $saw_warning or &$report("xy_to_n(undef,0) doesn't give a warning"); } # No warning if xy_is_visited() is a constant, skip test in that case. unless (coderef_is_const($path->can('xy_is_visited'))) { $saw_warning = 0; $path->xy_is_visited(0,undef); $saw_warning or &$report("xy_is_visited(0,undef) doesn't give a warning"); $saw_warning = 0; $path->xy_is_visited(undef,0); $saw_warning or &$report("xy_is_visited(undef,0) doesn't give a warning"); } } # undef ok if nothing sensible # +/-inf ok # nan not intended, but might be ok # finite could be a fixed x==0 if (defined $pos_infinity) { { ### n_to_xy($pos_infinity) ... my ($x, $y) = $path->n_to_xy($pos_infinity); if ($path->isa('Math::PlanePath::File')) { # all undefs for File if (! defined $x) { $x = $pos_infinity } if (! defined $y) { $y = $pos_infinity } } elsif ($path->isa('Math::PlanePath::PyramidRows') && ! $parameters{'step'}) { # x==0 normal from step==0, fake it up to pass test if (defined $x && $x == 0) { $x = $pos_infinity } } (is_pos_infinity($x) || is_neg_infinity($x) || &$is_nan($x)) or &$report("n_to_xy($pos_infinity) x is $x"); (is_pos_infinity($y) || is_neg_infinity($y) || &$is_nan($y)) or &$report("n_to_xy($pos_infinity) y is $y"); } { ### n_to_dxdy($pos_infinity) ... my @dxdy = $path->n_to_xy($pos_infinity); if ($path->isa('Math::PlanePath::File')) { # all undefs for File @dxdy = ($pos_infinity, $pos_infinity); } my $num_values = scalar(@dxdy); $num_values == 2 or &$report("n_to_dxdy(pos_infinity) got $num_values values, want 2"); my ($dx,$dy) = @dxdy; (is_pos_infinity($dx) || is_neg_infinity($dx) || &$is_nan($dx)) or &$report("n_to_dxdy($pos_infinity) dx is $dx"); (is_pos_infinity($dy) || is_neg_infinity($dy) || &$is_nan($dy)) or &$report("n_to_dxdy($pos_infinity) dy is $dy"); } foreach my $method ('n_to_rsquared','n_to_radius') { ### n_to_r pos_infinity ... my @ret = $path->$method($pos_infinity); if ($path->isa('Math::PlanePath::File')) { # all undefs for File @ret = ($pos_infinity); } my $num_values = scalar(@ret); $num_values == 1 or &$report("$method(pos_infinity) got $num_values values, want 1"); my ($ret) = @ret; # allow NaN too, since sqrt(+inf) in various classes gives nan (is_pos_infinity($ret) || &$is_nan($ret)) or &$report("$method($pos_infinity) ",$ret," expected +infinity"); } { ### tree_n_children($pos_infinity) ... my @children = $path->tree_n_children($pos_infinity); } { ### tree_n_num_children($pos_infinity) ... my $num_children = $path->tree_n_num_children($pos_infinity); } { ### tree_n_to_subheight($pos_infinity) ... my $height = $path->tree_n_to_subheight($pos_infinity); if ($path->tree_n_num_children($n_start)) { unless (! defined $height || is_pos_infinity($height)) { &$report("tree_n_to_subheight($pos_infinity) ",$height," expected +inf"); } } else { unless (equal(0,$height)) { &$report("tree_n_to_subheight($pos_infinity) ",$height," expected 0"); } } } # { # ### _EXPERIMENTAL__tree_n_to_leafdist($pos_infinity) ... # my $leafdist = $path->_EXPERIMENTAL__tree_n_to_leafdist($pos_infinity); # # if ($path->tree_n_num_children($n_start)) { # # unless (! defined $leafdist || is_pos_infinity($leafdist)) { # # &$report("_EXPERIMENTAL__tree_n_to_leafdist($pos_infinity) ",$leafdist," expected +inf"); # # } # # } else { # # unless (equal(0,$leafdist)) { # # &$report("_EXPERIMENTAL__tree_n_to_leafdist($pos_infinity) ",$leafdist," expected 0"); # # } # # } # } } if (defined $neg_infinity) { { ### n_to_xy($neg_infinity) ... my @xy = $path->n_to_xy($neg_infinity); if ($path->isa('Math::PlanePath::Rows')) { # secret negative n for Rows my ($x, $y) = @xy; ($x==$pos_infinity || $x==$neg_infinity || &$is_nan($x)) or &$report("n_to_xy($neg_infinity) x is $x"); ($y==$neg_infinity) or &$report("n_to_xy($neg_infinity) y is $y"); } elsif ($path->isa('Math::PlanePath::Columns')) { # secret negative n for Columns my ($x, $y) = @xy; ($x==$neg_infinity) or &$report("n_to_xy($neg_infinity) x is $x"); ($y==$pos_infinity || $y==$neg_infinity || &$is_nan($y)) or &$report("n_to_xy($neg_infinity) y is $y"); } else { scalar(@xy) == 0 or &$report("n_to_xy($neg_infinity) xy is ",join(',',@xy)); } } { ### n_to_dxdy($neg_infinity) ... my @dxdy = $path->n_to_xy($neg_infinity); my $num_values = scalar(@dxdy); if (($path->isa('Math::PlanePath::Rows') || $path->isa('Math::PlanePath::Columns')) && $num_values == 2) { # Rows,Columns has secret values for negative N, pretend not $num_values = 0; } $num_values == 0 or &$report("n_to_dxdy(neg_infinity) got $num_values values, want 0"); } foreach my $method ('n_to_rsquared','n_to_radius') { ### n_to_r (neg_infinity) ... my @ret = $path->$method($neg_infinity); if ($path->isa('Math::PlanePath::File')) { @ret = (undef); # all undefs for File } my $num_values = scalar(@ret); $num_values == 1 or &$report("$method($neg_infinity) got $num_values values, want 1"); if ($path->isa('Math::PlanePath::Rows') || $path->isa('Math::PlanePath::Columns')) { ### Rows,Columns has secret values for negative N, pretend not ... @ret = (undef); } my ($ret) = @ret; if (defined $ret) { &$report("$method($neg_infinity) $ret expected undef"); } } { ### tree_n_children($neg_infinity) ... my @children = $path->tree_n_children($neg_infinity); if (@children) { &$report("tree_n_children($neg_infinity) ",@children," expected none"); } } { ### tree_n_num_children($neg_infinity) ... my $num_children = $path->tree_n_num_children($neg_infinity); if (defined $num_children) { &$report("tree_n_children($neg_infinity) ",$num_children," expected undef"); } } { ### tree_n_to_subheight($neg_infinity) ... my $height = $path->tree_n_to_subheight($neg_infinity); if ($path->tree_n_num_children($n_start)) { if (defined $height) { &$report("tree_n_to_subheight($neg_infinity) ",$height," expected undef"); } } } if ($path->can('_EXPERIMENTAL__tree_n_to_leafdist')) { my $leafdist = $path->_EXPERIMENTAL__tree_n_to_leafdist($neg_infinity); if ($path->tree_n_num_children($n_start)) { if (defined $leafdist) { &$report("_EXPERIMENTAL__tree_n_to_leafdist($neg_infinity) ",$leafdist," expected undef"); } } } } # nan input documented loosely as yet ... if (defined $nan) { { my @xy = $path->n_to_xy($nan); if ($path->isa('Math::PlanePath::File')) { # allow empty from File without filename if (! @xy) { @xy = ($nan, $nan); } } elsif ($path->isa('Math::PlanePath::PyramidRows') && ! $parameters{'step'}) { # x==0 normal from step==0, fake it up to pass test if (defined $xy[0] && $xy[0] == 0) { $xy[0] = $nan } } my ($x, $y) = @xy; &$is_nan($x) or &$report("n_to_xy($nan) x not nan, got ", $x); &$is_nan($y) or &$report("n_to_xy($nan) y not nan, got ", $y); } { my @dxdy = $path->n_to_xy($nan); if ($path->isa('Math::PlanePath::File') && @dxdy == 0) { # allow empty from File without filename @dxdy = ($nan, $nan); } my $num_values = scalar(@dxdy); $num_values == 2 or &$report("n_to_dxdy(nan) got $num_values values, want 2"); my ($dx,$dy) = @dxdy; &$is_nan($dx) or &$report("n_to_dxdy($nan) dx not nan, got ", $dx); &$is_nan($dy) or &$report("n_to_dxdy($nan) dy not nan, got ", $dy); } { ### tree_n_children($nan) ... my @children = $path->tree_n_children($nan); # ENHANCE-ME: what should nan return? # if (@children) { # &$report("tree_n_children($nan) ",@children," expected none"); # } } { ### tree_n_num_children($nan) ... my $num_children = $path->tree_n_num_children($nan); # ENHANCE-ME: what should nan return? # &$is_nan($num_children) # or &$report("tree_n_children($nan) ",$num_children," expected nan"); } { ### tree_n_to_subheight($nan) ... my $height = $path->tree_n_to_subheight($nan); if ($path->tree_n_num_children($n_start)) { (! defined $height || &$is_nan($height)) or &$report("tree_n_to_subheight($nan) ",$height," expected nan"); } } # { # ### _EXPERIMENTAL__tree_n_to_leafdist($nan) ... # my $leafdist = $path->_EXPERIMENTAL__tree_n_to_leafdist($nan); # if ($path->tree_n_num_children($n_start)) { # (! defined $leafdist || &$is_nan($leafdist)) # or &$report("_EXPERIMENTAL__tree_n_to_leafdist($nan) ",$leafdist," expected nan"); # } # } } foreach my $x (0, pos_infinity_maybe(), neg_infinity_maybe(), dbl_max_for_class_xy($path), dbl_max_neg_for_class_xy($path)) { foreach my $y (0, pos_infinity_maybe(), neg_infinity_maybe(),, dbl_max_for_class_xy($path), dbl_max_neg_for_class_xy($path)) { next if ! defined $y; ### xy_to_n: $x, $y my @n = $path->xy_to_n($x,$y); scalar(@n) == 1 or &$report("xy_to_n($x,$y) want 1 value, got ",scalar(@n)); # my $n = $n[0]; # &$is_infinity($n) or &$report("xy_to_n($x,$y) n not inf, got ",$n); } } foreach my $x1 (0, pos_infinity_maybe(), neg_infinity_maybe(), dbl_max_for_class_rect($path), dbl_max_neg_for_class_rect($path)) { foreach my $x2 (0, pos_infinity_maybe(), neg_infinity_maybe(), dbl_max_for_class_rect($path), dbl_max_neg_for_class_rect($path)) { foreach my $y1 (0, pos_infinity_maybe(), neg_infinity_maybe(), dbl_max_for_class_rect($path), dbl_max_neg_for_class_rect($path)) { foreach my $y2 (0, pos_infinity_maybe(), neg_infinity_maybe(), dbl_max_for_class_rect($path), dbl_max_neg_for_class_rect($path)) { my @nn = $path->rect_to_n_range($x1,$y1, $x2,$y2); scalar(@nn) == 2 or &$report("rect_to_n_range($x1,$y1, $x2,$y2) want 2 values, got ",scalar(@nn)); # &$is_infinity($n) or &$report("xy_to_n($x,$y) n not inf, got ",$n); } } } } my $x_minimum = $path->x_minimum; my $x_maximum = $path->x_maximum; my $y_minimum = $path->y_minimum; my $y_maximum = $path->y_maximum; my $sumxy_minimum = $path->sumxy_minimum; my $sumxy_maximum = $path->sumxy_maximum; my $sumabsxy_minimum = $path->sumabsxy_minimum; my $sumabsxy_maximum = $path->sumabsxy_maximum; my $diffxy_minimum = $path->diffxy_minimum; my $diffxy_maximum = $path->diffxy_maximum; my $absdiffxy_minimum = $path->absdiffxy_minimum; my $absdiffxy_maximum = $path->absdiffxy_maximum; my $gcdxy_minimum = $path->gcdxy_minimum; my $gcdxy_maximum = $path->gcdxy_maximum; my $turn_any_left = $path->turn_any_left; my $turn_any_right = $path->turn_any_right; my $turn_any_straight = $path->turn_any_straight; my %saw_n_to_xy; my %count_n_to_xy; my $got_x_negative_at_n; my $got_y_negative_at_n; my $got_x_minimum; my $got_y_minimum; my (@prev_x,@prev_y, @prev_dx,@prev_dy); my ($dx_minimum, $dy_minimum); my ($dx_maximum, $dy_maximum); my %seen_dxdy; my $seen__UNDOCUMENTED__dxdy_list_at_n; my $got_turn_any_left_at_n; my $got_turn_any_right_at_n; my $got_turn_any_straight_at_n; my @n_to_x; my @n_to_y; foreach my $n ($n_start .. $n_limit) { my ($x, $y) = $path->n_to_xy ($n) or next; $n_to_x[$n] = $x; $n_to_y[$n] = $y; defined $x or &$report("n_to_xy($n) X undef"); defined $y or &$report("n_to_xy($n) Y undef"); my $arm = $n % $arms_count; if ($x < 0) { if (! defined $got_x_negative_at_n) { $got_x_negative_at_n= $n; } } if ($y < 0) { if (! defined $got_y_negative_at_n) { $got_y_negative_at_n= $n; } } if (defined $x_minimum && $x < $x_minimum) { &$report("n_to_xy($n) X=$x below x_minimum=$x_minimum"); } if (defined $x_maximum && $x > $x_maximum) { &$report("n_to_xy($n) X=$x below x_maximum=$x_maximum"); } if (defined $y_minimum && $y < $y_minimum) { &$report("n_to_xy($n) Y=$y below y_minimum=$y_minimum"); } if (defined $y_maximum && $y > $y_maximum) { &$report("n_to_xy($n) Y=$y below y_maximum=$y_maximum"); } # if (! defined $got_x_minimum || $x < $got_x_minimum) { # $got_x_minimum = $x; # } # if (! defined $got_y_minimum || $y < $got_y_minimum) { # $got_y_minimum = $y; # } # if (! defined $got_x_maximum || $x < $got_x_maximum) { # $got_x_maximum = $x; # } # if (! defined $got_y_maximum || $y < $got_y_maximum) { # $got_y_maximum = $y; # } { my $sumxy = $x + $y; if (defined $sumxy_minimum && $sumxy < $sumxy_minimum) { &$report("n_to_xy($n) X+Y=$sumxy below sumxy_minimum=$sumxy_minimum"); } if (defined $sumxy_maximum && $sumxy > $sumxy_maximum) { &$report("n_to_xy($n) X+Y=$sumxy above sumxy_maximum=$sumxy_maximum"); } } { my $sumabsxy = abs($x) + abs($y); if (defined $sumabsxy_minimum && $sumabsxy < $sumabsxy_minimum) { &$report("n_to_xy($n) abs(X)+abs(Y)=$sumabsxy below sumabsxy_minimum=$sumabsxy_minimum"); } if (defined $sumabsxy_maximum && $sumabsxy > $sumabsxy_maximum) { &$report("n_to_xy($n) abs(X)+abs(Y)=$sumabsxy above sumabsxy_maximum=$sumabsxy_maximum"); } } { my $diffxy = $x - $y; if (defined $diffxy_minimum && $diffxy < $diffxy_minimum) { &$report("n_to_xy($n) X-Y=$diffxy below diffxy_minimum=$diffxy_minimum"); } if (defined $diffxy_maximum && $diffxy > $diffxy_maximum) { &$report("n_to_xy($n) X-Y=$diffxy above diffxy_maximum=$diffxy_maximum"); } } { my $absdiffxy = abs($x - $y); if (defined $absdiffxy_minimum && $absdiffxy < $absdiffxy_minimum) { &$report("n_to_xy($n) abs(X-Y)=$absdiffxy below absdiffxy_minimum=$absdiffxy_minimum"); } if (defined $absdiffxy_maximum && $absdiffxy > $absdiffxy_maximum) { &$report("n_to_xy($n) abs(X-Y)=$absdiffxy above absdiffxy_maximum=$absdiffxy_maximum"); } } { my $gcdxy = gcd(abs($x),abs($y)); if (defined $gcdxy_minimum && $gcdxy < $gcdxy_minimum) { &$report("n_to_xy($n) gcd($x,$y)=$gcdxy below gcdxy_minimum=$gcdxy_minimum"); } if (defined $gcdxy_maximum && $gcdxy > $gcdxy_maximum) { &$report("n_to_xy($n) gcd($x,$y)=$gcdxy above gcdxy_maximum=$gcdxy_maximum"); } } my $xystr = (int($x) == $x && int($y) == $y ? sprintf('%d,%d', $x,$y) : sprintf('%.3f,%.3f', $x,$y)); if ($count_n_to_xy{$xystr}++ > $xy_maximum_duplication) { unless ($x == 0 && $y == 0 && $count_n_to_xy{$xystr} <= $xy_maximum_duplication_at_origin) { &$report ("n_to_xy($n) duplicate$count_n_to_xy{$xystr} xy=$xystr prev n=$saw_n_to_xy{$xystr} (should be max duplication $xy_maximum_duplication)"); } } $saw_n_to_xy{$xystr} = $n; my ($dx,$dy); if (defined $prev_x[$arm]) { $dx = $x - $prev_x[$arm]; } if (defined $prev_y[$arm]) { $dy = $y - $prev_y[$arm]; } $prev_x[$arm] = $x; $prev_y[$arm] = $y; my $dxdy_str = (defined $dx && defined $dy ? "$dx,$dy" : undef); if (defined $dxdy_str) { if (! defined $seen_dxdy{$dxdy_str}) { $seen_dxdy{$dxdy_str} ||= [$dx,$dy]; $seen__UNDOCUMENTED__dxdy_list_at_n = $n-$arms_count; } if (@_UNDOCUMENTED__dxdy_list) { $_UNDOCUMENTED__dxdy_list{$dxdy_str} or &$report ("N=$n dxdy=$dxdy_str not in _UNDOCUMENTED__dxdy_list"); } } if (defined $dx) { if (! defined $dx_maximum || $dx > $dx_maximum) { $dx_maximum = $dx; } if (! defined $dx_minimum || $dx < $dx_minimum) { $dx_minimum = $dx; } } if (defined $dy) { if (! defined $dy_maximum || $dy > $dy_maximum) { $dy_maximum = $dy; } if (! defined $dy_minimum || $dy < $dy_minimum) { $dy_minimum = $dy; } } # FIXME: Rows and Columns shouldn't take turn from negative N? my $LSR = ($n < $n_start + $arms_count ? undef : $path->_UNDOCUMENTED__n_to_turn_LSR($n)); my $prev_dx = $prev_dx[$arm]; my $prev_dy = $prev_dy[$arm]; if (defined $LSR) { # print "turn N=$n_of_turn at $x,$y dxdy prev $prev_dx,$prev_dy this $dx,$dy is LSR=$LSR\n"; if ($LSR > 0) { $turn_any_left or &$report ("turn_any_left() false but left at N=$n"); if (! defined $got_turn_any_left_at_n) { $got_turn_any_left_at_n = $n; } } if (! $LSR) { $turn_any_straight or &$report ("turn_any_straight() false but straight at N=$n"); if (! defined $got_turn_any_straight_at_n) { $got_turn_any_straight_at_n = $n; } # print "straight at N=$n_of_turn dxdy $prev_dx,$prev_dy then $dx,$dy\n"; } if ($LSR < 0) { $turn_any_right or &$report ("turn_any_right() false but right at N=$n"); if (! defined $got_turn_any_right_at_n) { $got_turn_any_right_at_n = $n; } } } $prev_dx[$arm] = $dx; $prev_dy[$arm] = $dy; { my $x2 = $x + ($x >= 0 ? .4 : -.4); my $y2 = $y + ($y >= 0 ? .4 : -.4); my ($n_lo, $n_hi) = $path->rect_to_n_range (0,0, $x2,$y2); $n_lo <= $n or &$report ("rect_to_n_range(0,0, $x2,$y2) lo n=$n xy=$xystr, got n_lo=$n_lo"); $n_hi >= $n or &$report ("rect_to_n_range(0,0, $x2,$y2) hi n=$n xy=$xystr, got n_hi=$n_hi"); $n_lo == int($n_lo) or &$report ("rect_to_n_range(0,0, $x2,$y2) lo n=$n xy=$xystr, got n_lo=$n_lo, integer"); $n_hi == int($n_hi) or &$report ("rect_to_n_range(0,0, $x2,$y2) hi n=$n xy=$xystr, got n_hi=$n_hi, integer"); $n_lo >= $n_start or &$report ("rect_to_n_range(0,0, $x2,$y2) n_lo=$n_lo is before n_start=$n_start"); } { my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ($rect_exact{$class} ? $n_lo == $n : $n_lo <= $n) or &$report ("rect_to_n_range() lo n=$n xy=$xystr, got $n_lo"); ($rect_exact_hi{$class} ? $n_hi == $n : $n_hi >= $n) or &$report ("rect_to_n_range() hi n=$n xy=$xystr, got $n_hi"); $n_lo == int($n_lo) or &$report ("rect_to_n_range() lo n=$n xy=$xystr, got n_lo=$n_lo, should be an integer"); $n_hi == int($n_hi) or &$report ("rect_to_n_range() hi n=$n xy=$xystr, got n_hi=$n_hi, should be an integer"); $n_lo >= $n_start or &$report ("rect_to_n_range() n_lo=$n_lo is before n_start=$n_start"); } unless ($xy_maximum_duplication > 0) { foreach my $x_offset (0) { # bit slow: , -0.2, 0.2) { foreach my $y_offset (0, +0.2) { # bit slow: , -0.2) { my $rev_n = $path->xy_to_n ($x + $x_offset, $y + $y_offset); ### try xy_to_n from: "n=$n xy=$x,$y xy=$xystr x_offset=$x_offset y_offset=$y_offset" ### $rev_n unless (defined $rev_n && $n == $rev_n) { &$report ("xy_to_n() rev n=$n xy=$xystr x_offset=$x_offset y_offset=$y_offset got ".(defined $rev_n ? $rev_n : 'undef')); pythagorean_diag($path,$x,$y); } } } } } #-------------------------------------------------------------------------- ### xy_to_n_list() ... foreach my $x (-2 .. 5) { foreach my $y (-2 .. 5) { my @n_list = $path->xy_to_n_list($x,$y); my $this_max_duplication = $xy_maximum_duplication; my $this_max_duplication_type = 'xy_maximum_duplication'; if (! defined $this_max_duplication) { $this_max_duplication = 1; $this_max_duplication_type = 'default'; } if ($x==0 && $y==0 && defined $xy_maximum_duplication_at_origin) { $this_max_duplication = $xy_maximum_duplication_at_origin; $this_max_duplication_type = 'xy_maximum_duplication_at_origin'; } my $got_length = scalar(@n_list); unless ($got_length <= $this_max_duplication) { &$report ("xy_to_n_list() x=$y,y=$y list length $got_length more than $this_max_duplication_type = $this_max_duplication"); } foreach my $i (0 .. $#n_list) { if (! defined $n_list[$i]) { &$report ("xy_to_n_list() x=$y,y=$y contains undef"); } } } } #-------------------------------------------------------------------------- # turn_any_left(), turn_any_straight(), turn_any_right() if ($turn_any_left && ! defined $got_turn_any_left_at_n) { my $at_n; if ($path->can('_UNDOCUMENTED__turn_any_left_at_n')) { $at_n = $path->_UNDOCUMENTED__turn_any_left_at_n; } if (defined $at_n && $n_limit <= $at_n) { MyTestHelpers::diag (" skip n_limit=$n_limit < turn left at_n=$at_n"); } elsif ($path->isa('Math::PlanePath::File')) { MyTestHelpers::diag (" skip turn_any_left() not established for File"); } else { &$report ("turn_any_left() true but not seen to N=$n_limit"); } } if ($turn_any_straight && ! defined $got_turn_any_straight_at_n) { my $at_n; if ($path->can('_UNDOCUMENTED__turn_any_straight_at_n')) { $at_n = $path->_UNDOCUMENTED__turn_any_straight_at_n; } if (defined $at_n && $n_limit <= $at_n) { MyTestHelpers::diag (" skip n_limit=$n_limit < turn straight at_n=$at_n"); } elsif ($path->isa('Math::PlanePath::File')) { MyTestHelpers::diag (" skip turn_any_straight() not established for File"); } elsif ($path->isa('Math::PlanePath::MultipleRings') && $path->{'ring_shape'} eq 'polygon' && $path->{'step'} == 8) { MyTestHelpers::diag (" skip MultipleRings,ring_shape=polygon,step=8 turn_any_straight() due to round-off"); } else { &$report ("turn_any_straight() true but not seen to N=$n_limit"); } } if ($turn_any_right && ! defined $got_turn_any_right_at_n) { my $at_n; if ($path->can('_UNDOCUMENTED__turn_any_right_at_n')) { $at_n = $path->_UNDOCUMENTED__turn_any_right_at_n; } if (defined $at_n && $n_limit <= $at_n) { MyTestHelpers::diag (" skip n_limit=$n_limit < turn right at_n=$at_n"); } elsif ($path->isa('Math::PlanePath::File')) { MyTestHelpers::diag (" skip turn_any_right() not established for File"); } else { &$report ("turn_any_right() true but not seen to N=$n_limit"); } } foreach my $elem (['_UNDOCUMENTED__turn_any_left_at_n', 1,$got_turn_any_left_at_n ], ['_UNDOCUMENTED__turn_any_straight_at_n',0,$got_turn_any_straight_at_n ], ['_UNDOCUMENTED__turn_any_right_at_n', -1,$got_turn_any_right_at_n ]){ my ($method, $want_LSR, $seen_at_n) = @$elem; if ($path->can($method)) { if (defined(my $n = $path->$method)) { my $got_LSR = $path->_UNDOCUMENTED__n_to_turn_LSR($n); unless ($got_LSR == $want_LSR) { &$report ("$method()=$n at that N got LSR=$got_LSR want $want_LSR"); } if (defined $seen_at_n) { $n == $seen_at_n or &$report ("$method()=$n but saw first at N=$seen_at_n"); } } } } #-------------------------------------------------------------------------- ### n_to_xy() fractional ... unless ($non_linear_frac{$class} || defined $n_frac_discontinuity) { foreach my $n ($n_start .. $#n_to_x - $arms_count) { my $x = $n_to_x[$n]; my $y = $n_to_y[$n]; my $next_x = $n_to_x[$n+$arms_count]; my $next_y = $n_to_y[$n+$arms_count]; next unless defined $x && defined $next_x; my $dx = $next_x - $x; my $dy = $next_y - $y; foreach my $frac (0.25, 0.75) { my $n_frac = $n + $frac; my ($got_x,$got_y) = $path->n_to_xy($n_frac); my $want_x = $x + $frac*$dx; my $want_y = $y + $frac*$dy; abs($want_x - $got_x) < 0.00001 or &$report ("n_to_xy($n_frac) got_x=$got_x want_x=$want_x"); abs($want_y - $got_y) < 0.00001 or &$report ("n_to_xy($n_frac) got_y=$got_y want_y=$want_y"); } } } #-------------------------------------------------------------------------- ### n_to_dxdy() ... if ($path->can('n_to_dxdy') != Math::PlanePath->can('n_to_dxdy')) { MyTestHelpers::diag ($mod, ' n_to_dxdy()'); foreach my $n ($n_start .. $#n_to_x - $arms_count) { my $x = $n_to_x[$n]; my $y = $n_to_y[$n]; my $next_x = $n_to_x[$n+$arms_count]; my $next_y = $n_to_y[$n+$arms_count]; next unless defined $x && defined $next_x; my $want_dx = $next_x - $x; my $want_dy = $next_y - $y; my ($got_dx,$got_dy) = $path->n_to_dxdy($n); $want_dx == $got_dx or &$report ("n_to_dxdy($n) got_dx=$got_dx want_dx=$want_dx (next_x=$n_to_x[$n+$arms_count], x=$n_to_x[$n])"); $want_dy == $got_dy or &$report ("n_to_dxdy($n) got_dy=$got_dy want_dy=$want_dy"); } foreach my $n ($n_start .. $n_limit) { foreach my $offset (0.25, 0.75) { my $n = $n + $offset; my ($x,$y) = $path->n_to_xy($n); my ($next_x,$next_y) = $path->n_to_xy($n+$arms_count); my $want_dx = ($next_x - $x); my $want_dy = ($next_y - $y); my ($got_dx,$got_dy) = $path->n_to_dxdy($n); $want_dx == $got_dx or &$report ("n_to_dxdy($n) got_dx=$got_dx want_dx=$want_dx"); $want_dy == $got_dy or &$report ("n_to_dxdy($n) got_dy=$got_dy want_dy=$want_dy"); } } } #-------------------------------------------------------------------------- ### n_to_rsquared() vs X^2,Y^2 ... if ($path->can('n_to_rsquared') != Math::PlanePath->can('n_to_rsquared')) { foreach my $n ($n_start .. $#n_to_x) { my $x = $n_to_x[$n]; my $y = $n_to_y[$n]; my ($n_to_rsquared) = $path->n_to_rsquared($n); my $xy_to_rsquared = $x*$x + $y*$y; if (abs($n_to_rsquared - $xy_to_rsquared) > 0.0000001) { &$report ("n_to_rsquared() at n=$n,x=$x,y=$y got $n_to_rsquared whereas x^2+y^2=$xy_to_rsquared"); } } } #-------------------------------------------------------------------------- ### n_to_radius() vs X^2,Y^2 ... if ($path->can('n_to_radius') != Math::PlanePath->can('n_to_radius')) { foreach my $n ($n_start .. $#n_to_x) { my $x = $n_to_x[$n]; my $y = $n_to_y[$n]; my ($n_to_radius) = $path->n_to_radius($n); my $xy_to_radius = sqrt($x*$x + $y*$y); if (abs($n_to_radius - $xy_to_radius) > 0.0000001) { &$report ("n_to_radius() at n=$n,x=$x,y=$y got $n_to_radius whereas x^2+y^2=$xy_to_radius"); } } } #-------------------------------------------------------------------------- ### _UNDOCUMENTED__n_to_turn_LSR() ... if ($path->can('_UNDOCUMENTED__n_to_turn_LSR') && ! (# nasty hack to allow Rows and Columns going before n_start $class->isa('Math::PlanePath::Rows') || $class->isa('Math::PlanePath::Columns'))) { foreach my $n ($n_start-1) { my $got = $path->_UNDOCUMENTED__n_to_turn_LSR($n); if (defined $got) { my ($x,$y) = $path->n_to_xy($n); &$report ("_UNDOCUMENTED__n_to_turn_LSR() at n=$n want undef got $got"); } } # no infinite loop on N=infinity etc, but don't care about the value foreach my $n (pos_infinity_maybe(), neg_infinity_maybe(), nan_maybe()) { $path->_UNDOCUMENTED__n_to_turn_LSR($n); } } #-------------------------------------------------------------------------- ### _NOTDOCUMENTED_n_to_figure_boundary() ... if ($path->can('_NOTDOCUMENTED_n_to_figure_boundary')) { my $want = 4; my $bad = 0; foreach my $n ($n_start .. $n_start + 1000) { my $got = $path->_NOTDOCUMENTED_n_to_figure_boundary($n); if ($want != $got) { my ($x,$y) = $path->n_to_xy($n); &$report ("_NOTDOCUMENTED_n_to_figure_boundary() at n=$n,x=$x,y=$y got $got whereas want $want"); last if $bad++ > 20; } $want += path_n_to_dboundary($path,$n); } } #-------------------------------------------------------------------------- ### level_to_n_range() and with n_to_level() ... foreach my $n ($n_start-1, $n_start-100) { my $got = $path->n_to_level($n); if (defined $got) { &$report ("n_to_level() not undef on N=$n before n_start=$n_start"); } } my $have_level_to_n_range = do { my @n_range = $path->level_to_n_range(0); scalar(@n_range) }; if ($have_level_to_n_range) { my @n_range; my $bad = 0; foreach my $n ($n_start .. $n_start+100) { my $level = $path->n_to_level($n); if (! defined $level) { &$report ("n_to_level($n) undef"); last; } if ($level < 0) { &$report ("n_to_level() negative"); last if $bad++ > 10; next; } $n_range[$level] ||= [ $path->level_to_n_range($level) ]; my ($n_lo, $n_hi) = @{$n_range[$level]}; unless ($n >= $n_lo && $n <= $n_hi) { &$report ("n_to_level($n)=$level has $n outside $n_lo .. $n_hi"); last if $bad++ > 10; } } } # n_to_level() just before and after level_to_n_range() high limit if ($have_level_to_n_range) { foreach my $level (0 .. 10) { my ($n_lo, $n_hi) = $path->level_to_n_range($level); last if $n_hi > 2**24; foreach my $offset (-6 .. 0) { my $n = $n_hi + $offset; next if $n < $n_start; my $got_level = $path->n_to_level($n_hi); unless ($got_level == $level) { &$report ("n_to_level(n_hi$offset=$n)=$got_level but level_to_n_range($level)= $n_lo .. $n_hi"); } } foreach my $offset (1 .. 6) { my $n = $n_hi + $offset; my $got_level = $path->n_to_level($n_hi+1); my $want_level = $level+1; unless ($got_level == $want_level) { &$report ("n_to_level(n_hi+$offset=$n)=$got_level but level_to_n_range($level)= $n_lo .. $n_hi want $want_level"); } } } } #-------------------------------------------------------------------------- ### n_to_xy() various bogus values return 0 or 2 values and not crash ... foreach my $n (-100, -2, -1, -0.6, -0.5, -0.4, 0, 0.4, 0.5, 0.6) { my @xy = $path->n_to_xy ($n); (@xy == 0 || @xy == 2) or &$report ("n_to_xy() n=$n got ",scalar(@xy)," values"); } foreach my $elem ([-1,-1, -1,-1], ) { my ($x1,$y1,$x2,$y2) = @$elem; my ($got_lo, $got_hi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); (defined $got_lo && defined $got_hi) or &$report ("rect_to_n_range() x1=$x1,y1=$y1, x2=$x2,y2=$y2 undefs"); if ($got_hi >= $got_lo) { $got_lo >= $n_start or &$report ("rect_to_n_range() got_lo=$got_lo is before n_start=$n_start"); } } #-------------------------------------------------------------------------- ### _UNDOCUMENTED__n_is_x_positive() ... if ($path->can('_UNDOCUMENTED__n_is_x_positive')) { foreach my $n (0 .. $arms_count * 256) { my ($x,$y) = $path->n_to_xy($n); my $want = ($x >= 0 && $y == 0 ? 1 : 0); my $got = $path->_UNDOCUMENTED__n_is_x_positive($n) ? 1 : 0; unless ($got == $want) { &$report ("_UNDOCUMENTED__n_is_x_positive() n=$n want $want got $got"); } } } #-------------------------------------------------------------------------- ### _UNDOCUMENTED__n_is_diagonal_NE() ... if ($path->can('_UNDOCUMENTED__n_is_diagonal_NE')) { foreach my $n (0 .. $arms_count * 256) { my ($x,$y) = $path->n_to_xy($n); my $want = ($x >= 0 && $x == $y ? 1 : 0); my $got = $path->_UNDOCUMENTED__n_is_diagonal_NE($n) ? 1 : 0; unless ($got == $want) { &$report ("_UNDOCUMENTED__n_is_diagonal_NE() n=$n want $want got $got"); } } } #-------------------------------------------------------------------------- ### _UNDOCUMENTED__dxdy_list() completeness ... if (@_UNDOCUMENTED__dxdy_list) { my $_UNDOCUMENTED__dxdy_list_at_n; my $dxdy_num = int(scalar(@_UNDOCUMENTED__dxdy_list)/2); my $seen_dxdy_num = scalar keys %seen_dxdy; $_UNDOCUMENTED__dxdy_list_at_n = $path->_UNDOCUMENTED__dxdy_list_at_n; if (defined $_UNDOCUMENTED__dxdy_list_at_n) { $_UNDOCUMENTED__dxdy_list_at_n >= $n_start or &$report ("_UNDOCUMENTED__dxdy_list_at_n() = $_UNDOCUMENTED__dxdy_list_at_n is < n_start=$n_start"); if ($seen_dxdy_num == $dxdy_num) { $seen__UNDOCUMENTED__dxdy_list_at_n == $_UNDOCUMENTED__dxdy_list_at_n or &$report ("_UNDOCUMENTED__dxdy_list_at_n() = $_UNDOCUMENTED__dxdy_list_at_n but seen__UNDOCUMENTED__dxdy_list_at_n=$seen__UNDOCUMENTED__dxdy_list_at_n"); } } else { $_UNDOCUMENTED__dxdy_list_at_n = $n_start; } if ($n_limit - $arms_count < $_UNDOCUMENTED__dxdy_list_at_n) { MyTestHelpers::diag (" skip n_limit=$n_limit <= _UNDOCUMENTED__dxdy_list_at_n=$_UNDOCUMENTED__dxdy_list_at_n"); } else { foreach my $dxdy_str (keys %_UNDOCUMENTED__dxdy_list) { if (! $seen_dxdy{$dxdy_str}) { &$report ("_UNDOCUMENTED__dxdy_list() has $dxdy_str not seen to n_limit=$n_limit"); } } } } else { my $seen_dxdy_count = scalar keys %seen_dxdy; if ($seen_dxdy_count > 0 && $seen_dxdy_count <= 10 && ($dx_maximum||0) < 4 && ($dy_maximum||0) < 4 && ($dx_minimum||0) > -4 && ($dy_minimum||0) > -4) { MyTestHelpers::diag (" possible dxdy list: ", join(' ', keys %seen_dxdy)); } } #-------------------------------------------------------------------------- ### x negative xy_to_n() ... foreach my $x (-100, -99) { ### $x my @n = $path->xy_to_n ($x,-1); ### @n (scalar(@n) == 1) or &$report ("xy_to_n($x,-1) array context got ",scalar(@n)," values but should be 1, possibly undef"); } { my $x_negative = ($path->x_negative ? 1 : 0); my $got_x_negative = (defined $got_x_negative_at_n ? 1 : 0); # if ($mod eq 'ComplexPlus,realpart=2' # || $mod eq 'ComplexPlus,realpart=3' # || $mod eq 'ComplexPlus,realpart=4' # || $mod eq 'ComplexPlus,realpart=5' # ) { # # these don't get to X negative in small rectangle # $got_x_negative = 1; # } if ($n_limit < (defined $x_negative_at_n ? $x_negative_at_n : $n_start)) { MyTestHelpers::diag (" skip n_limit=$n_limit <= x_negative_at_n=$x_negative_at_n"); } else { ($x_negative == $got_x_negative) or &$report ("x_negative() $x_negative but in rect to n=$limit got $got_x_negative (x_negative_at_n=$x_negative_at_n)"); } if (defined $got_x_negative_at_n) { equal($x_negative_at_n, $got_x_negative_at_n) or &$report ("x_negative_at_n() = ",$x_negative_at_n," but got_x_negative_at_n=$got_x_negative_at_n"); } if (defined $x_negative_at_n && $x_negative_at_n < 0x100_0000) { { my ($x,$y) = $path->n_to_xy($x_negative_at_n); $x < 0 or &$report ("x_negative_at_n()=$x_negative_at_n but xy=$x,$y"); } if ($x_negative_at_n > $n_start) { my $n = $x_negative_at_n - 1; my ($x,$y) = $path->n_to_xy($n); $x >= 0 or &$report ("x_negative_at_n()=$x_negative_at_n but at N=$n xy=$x,$y"); } } } { my $y_negative = ($path->y_negative ? 1 : 0); my $got_y_negative = (defined $got_y_negative_at_n ? 1 : 0); # if (($mod eq 'ComplexPlus' && $limit < 32) # first y_neg at N=32 # || $mod eq 'ComplexPlus,realpart=2' # y_neg big # || $mod eq 'ComplexPlus,realpart=3' # || $mod eq 'ComplexPlus,realpart=4' # || $mod eq 'ComplexPlus,realpart=5' # || $mod eq 'ComplexMinus,realpart=3' # || $mod eq 'ComplexMinus,realpart=4' # || $mod eq 'ComplexMinus,realpart=5' # ) { # # GosperSide take a long time to get # # to Y negative, not reached by the rectangle # # considered here. ComplexMinus doesn't get there # # on realpart==5 or bigger too. # $got_y_negative = 1; # } if ($n_limit < (defined $y_negative_at_n ? $y_negative_at_n : $n_start)) { MyTestHelpers::diag (" skip n_limit=$n_limit <= y_negative_at_n=$y_negative_at_n"); } else { ($y_negative == $got_y_negative) or &$report ("y_negative() $y_negative but in rect to n=$limit got $got_y_negative (y_negative_at_n=$y_negative_at_n)"); } if (defined $got_y_negative_at_n) { equal($y_negative_at_n, $got_y_negative_at_n) or &$report ("y_negative_at_n() = ",$y_negative_at_n," but got_y_negative_at_n=$got_y_negative_at_n"); } if (defined $y_negative_at_n && $y_negative_at_n < 0x100_0000) { { # n_to_xy() of y_negative_at_n should be Y < 0 my ($x,$y) = $path->n_to_xy($y_negative_at_n); $y < 0 or &$report ("y_negative_at_n()=$y_negative_at_n but xy=$x,$y"); } { # n_to_xy() of y_negative_at_n - 1 should be Y >= 0, # unless y_negative_at_n is at n_start my $n = $y_negative_at_n - 1; if ($n >= $n_start) { my ($x,$y) = $path->n_to_xy($n); $y >= 0 or &$report ("y_negative_at_n()=$y_negative_at_n but at N=$n xy=$x,$y"); } } } } if ($path->figure ne 'circle' # bit slow && ! ($path->isa('Math::PlanePath::Flowsnake'))) { my $x_min = ($path->x_negative ? - int($rect_limit/2) : -2); my $y_min = ($path->y_negative ? - int($rect_limit/2) : -2); my $x_max = $x_min + $rect_limit; my $y_max = $y_min + $rect_limit; my $data; foreach my $x ($x_min .. $x_max) { foreach my $y ($y_min .. $y_max) { my $n = $path->xy_to_n ($x, $y); if (defined $n && $n < $n_start && ! $path->isa('Math::PlanePath::Rows') && ! $path->isa('Math::PlanePath::Columns')) { &$report ("xy_to_n($x,$y) gives n=$n < n_start=$n_start"); } $data->{$y}->{$x} = $n; } } #### $data # MyTestHelpers::diag ("rect check ..."); foreach my $y1 ($y_min .. $y_max) { foreach my $y2 ($y1 .. $y_max) { foreach my $x1 ($x_min .. $x_max) { my $min; my $max; foreach my $x2 ($x1 .. $x_max) { my @col = map {$data->{$_}->{$x2}} $y1 .. $y2; @col = grep {defined} @col; $min = List::Util::min (grep {defined} $min, @col); $max = List::Util::max (grep {defined} $max, @col); my $want_min = (defined $min ? $min : 1); my $want_max = (defined $max ? $max : 0); ### @col ### rect: "$x1,$y1 $x2,$y2 expect N=$want_min..$want_max" foreach my $x_swap (0, 1) { my ($x1,$x2) = ($x_swap ? ($x1,$x2) : ($x2,$x1)); foreach my $y_swap (0, 1) { my ($y1,$y2) = ($y_swap ? ($y1,$y2) : ($y2,$y1)); my ($got_min, $got_max) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); defined $got_min or &$report ("rect_to_n_range($x1,$y1, $x2,$y2) got_min undef"); defined $got_max or &$report ("rect_to_n_range($x1,$y1, $x2,$y2) got_max undef"); if ($got_max >= $got_min) { $got_min >= $n_start or $rect_before_n_start{$class} or &$report ("rect_to_n_range() got_min=$got_min is before n_start=$n_start"); } if (! defined $min || ! defined $max) { if (! $rect_exact_hi{$class}) { next; # outside } } unless ($rect_exact{$class} ? $got_min == $want_min : $got_min <= $want_min) { ### $x1 ### $y1 ### $x2 ### $y2 ### got: $path->rect_to_n_range ($x1,$y1, $x2,$y2) ### $want_min ### $want_max ### $got_min ### $got_max ### @col ### $data &$report ("rect_to_n_range($x1,$y1, $x2,$y2) bad min got_min=$got_min want_min=$want_min".(defined $min ? '' : '[nomin]') ); } unless ($rect_exact_hi{$class} ? $got_max == $want_max : $got_max >= $want_max) { &$report ("rect_to_n_range($x1,$y1, $x2,$y2 ) bad max got $got_max want $want_max".(defined $max ? '' : '[nomax]')); } } } } } } } if ($path->can('xy_is_visited') != Math::PlanePath->can('xy_is_visited')) { # MyTestHelpers::diag ("xy_is_visited() check ..."); foreach my $y ($y_min .. $y_max) { foreach my $x ($x_min .. $x_max) { my $got_visited = ($path->xy_is_visited($x,$y) ? 1 : 0); my $want_visited = (defined($data->{$y}->{$x}) ? 1 : 0); unless ($got_visited == $want_visited) { &$report ("xy_is_visited($x,$y) got $got_visited want $want_visited"); } } } } } my $is_a_tree; { my @n_children = $path->tree_n_children($n_start); if (@n_children) { $is_a_tree = 1; } } my $num_children_minimum = $path->tree_num_children_minimum; my $num_children_maximum = $path->tree_num_children_maximum; ($num_children_maximum >= $num_children_minimum) or &$report ("tree_num_children_maximum() is ",$num_children_maximum, "expect >= tree_num_children_minimum() is ",$num_children_minimum); my @num_children_list = $path->tree_num_children_list; my $num_children_list_str = join(',',@num_children_list); my %num_children_hash; @num_children_hash{@num_children_list} = (); # hash slice @num_children_list >= 1 or &$report ("tree_num_children_list() is empty"); $num_children_list[0] == $num_children_minimum or &$report ("tree_num_children_list() first != minimum"); $num_children_list[-1] == $num_children_maximum or &$report ("tree_num_children_list() last != maximum"); join(',',sort {$a<=>$b} @num_children_list) eq $num_children_list_str or &$report ("tree_num_children_list() not sorted"); # tree_any_leaf() is the same as tree_num_children_minimum()==0 my $any_leaf = $path->tree_any_leaf; ((!!$any_leaf) == ($num_children_minimum==0)) or &$report ("tree_any_leaf() is ",$any_leaf," but tree_num_children_minimum() is ",$num_children_minimum); my $num_roots = $path->tree_num_roots; if ($is_a_tree) { $num_roots > 0 or &$report ("tree_num_roots() should be > 0, got ", $num_roots); } else { $num_roots == 0 or &$report ("tree_num_roots() should be 0 for non-tree, got ", $num_roots); } my @root_n_list = $path->tree_root_n_list; my $root_n_list_str = join(',',@root_n_list); scalar(@root_n_list) == $num_roots or &$report ("tree_root_n_list() $root_n_list_str expected num_roots=$num_roots many values"); my %root_n_list; foreach my $root_n (@root_n_list) { if (exists $root_n_list{$root_n}) { &$report ("tree_root_n_list() duplicate $root_n in list $root_n_list_str"); } $root_n_list{$root_n} = 1; } ### tree_n_root() of each ... my $have_class_tree_n_root = ($path->can('tree_n_root') != Math::PlanePath->can('tree_n_root')); if ($have_class_tree_n_root) { MyTestHelpers::diag ("tree_n_root() specific implementation ..."); } foreach my $n ($n_start .. $n_start+$limit) { my $root_n = $path->tree_n_root($n); if ($is_a_tree) { if (! defined $root_n || ! $root_n_list{$root_n}) { &$report ("tree_n_root($n) got ",$root_n," is not a root ($root_n_list_str)"); } if ($have_class_tree_n_root) { my $root_n_by_search = $path->Math::PlanePath::tree_n_root($n); $root_n == $root_n_by_search or &$report ("tree_n_root($n) got ",$root_n," but by search is ",$root_n_by_search); } } else { if (defined $root_n) { &$report ("tree_n_root($n) got ",$root_n," expected undef for non-tree"); } } } ### tree_n_children before n_start ... foreach my $n ($n_start-5 .. $n_start-1) { { my @n_children = $path->tree_n_children($n); (@n_children == 0) or &$report ("tree_n_children($n) before n_start=$n_start unexpectedly got ",scalar(@n_children)," values:",@n_children); } { my $num_children = $path->tree_n_num_children($n); if (defined $num_children) { &$report ("tree_n_num_children($n) before n_start=$n_start unexpectedly $num_children not undef"); } } } ### tree_n_parent() before n_start ... foreach my $n ($n_start-5 .. $n_start) { my $n_parent = $path->tree_n_parent($n); if (defined $n_parent) { &$report ("tree_n_parent($n) <= n_start=$n_start unexpectedly got parent ",$n_parent); } } ### tree_n_children() look at tree_n_parent of each ... { my %unseen_num_children = %num_children_hash; foreach my $n ($n_start .. $n_start+$limit, ($path->isa('Math::PlanePath::OneOfEight') ? (37, # first with 2 children in parts=4 58) # first with 3 children in parts=4 : ())) { ### $n my @n_children = $path->tree_n_children($n); ### @n_children my $num_children = scalar(@n_children); exists $num_children_hash{$num_children} or &$report ("tree_n_children($n)=$num_children not in tree_num_children_list()=$num_children_list_str"); delete $unseen_num_children{$num_children}; foreach my $n_child (@n_children) { my $got_n_parent = $path->tree_n_parent($n_child); ($got_n_parent == $n) or &$report ("tree_n_parent($n_child) got $got_n_parent want $n"); } } if (%unseen_num_children) { &$report ("tree_num_children_list() values not seen: ", join(',',sort {$a<=>$b} keys %unseen_num_children), " of total=$num_children_list_str"); } } ### tree_n_to_depth() before n_start ... foreach my $n ($n_start-5 .. $n_start-1) { my $depth = $path->tree_n_to_depth($n); if (defined $depth) { &$report ("tree_n_to_depth($n) < n_start=$n_start unexpectedly got depth ",$depth); } } my @depth_to_width_by_count; my @depth_to_n_seen; my @depth_to_n_end_seen; if ($path->can('tree_n_to_depth') != Math::PlanePath->can('tree_n_to_depth')) { ### tree_n_to_depth() vs count up by parents ... # MyTestHelpers::diag ($mod, ' tree_n_to_depth()'); foreach my $n ($n_start .. $n_start+$limit) { my $want_depth = path_tree_n_to_depth_by_parents($path,$n); my $got_depth = $path->tree_n_to_depth($n); if (! defined $got_depth || ! defined $want_depth || $got_depth != $want_depth) { &$report ("tree_n_to_depth($n) got ",$got_depth," want ",$want_depth); } if ($got_depth >= 0 && $got_depth <= $depth_limit) { $depth_to_width_by_count[$got_depth]++; if (! defined $depth_to_n_seen[$got_depth]) { $depth_to_n_seen[$got_depth] = $n; } $depth_to_n_end_seen[$got_depth] = $n; } } } if ($path->can('tree_n_to_subheight') != Math::PlanePath->can('tree_n_to_subheight')) { ### tree_n_to_subheight() vs search downwards ... # MyTestHelpers::diag ($mod, ' tree_n_to_subheight()'); foreach my $n ($n_start .. $n_start+$limit) { my $want_height = path_tree_n_to_subheight_by_search($path,$n); my $got_height = $path->tree_n_to_subheight($n); if (! equal($got_height,$want_height)) { &$report ("tree_n_to_subheight($n) got ",$got_height," want ",$want_height); } } } if ($path->can('_EXPERIMENTAL__tree_n_to_leafdist') # != Math::PlanePath->can('_EXPERIMENTAL__tree_n_to_leafdist') ) { ### _EXPERIMENTAL__tree_n_to_leafdist() vs search downwards ... # MyTestHelpers::diag ($mod, ' _EXPERIMENTAL__tree_n_to_leafdist()'); foreach my $n ($n_start .. $n_start+$limit) { my $want_height = path_tree_n_to_leafdist_by_search($path,$n); my $got_height = $path->_EXPERIMENTAL__tree_n_to_leafdist($n); if (! equal($got_height,$want_height)) { &$report ("_EXPERIMENTAL__tree_n_to_leafdist($n) got ",$got_height," want ",$want_height); } } } ### tree_depth_to_n() on depth<0 ... foreach my $depth (-2 .. -1) { foreach my $method ('tree_depth_to_n','tree_depth_to_n_end') { my $n = $path->$method($depth); if (defined $n) { &$report ("$method($depth) unexpectedly got n=",$n); } } { my @ret = $path->tree_depth_to_n_range($depth); scalar(@ret) == 0 or &$report ("tree_depth_to_n_range($depth) not an empty return"); } } ### tree_depth_to_n() ... if ($is_a_tree) { my $n_rows_are_contiguous = path_tree_n_rows_are_contiguous($path); foreach my $depth (0 .. $depth_limit) { my $n = $path->tree_depth_to_n($depth); if (! defined $n) { &$report ("tree_depth_to_n($depth) should not be undef"); next; } if ($n != int($n)) { &$report ("tree_depth_to_n($depth) not an integer: ",$n); next; } if ($n <= $limit) { my $want_n = $depth_to_n_seen[$depth]; if (! defined $want_n || $n != $want_n) { &$report ("tree_depth_to_n($depth)=$n but depth_to_n_seen[$depth]=",$want_n); } } my $n_end = $path->tree_depth_to_n_end($depth); $n_end >= $n or &$report ("tree_depth_to_n_end($depth) $n_end less than tree_depth_to_n() start $n"); my ($n_range_lo, $n_range_hi) = $path->tree_depth_to_n_range($depth); $n_range_lo == $n or &$report ("tree_depth_to_n_range($depth) $n_range_lo != tree_depth_to_n() start $n"); $n_range_hi == $n_end or &$report ("tree_depth_to_n_range($depth) $n_range_hi != tree_depth_to_n_end() start $n_end"); { my $got_depth = $path->tree_n_to_depth($n); if (! defined $got_depth || $got_depth != $depth) { &$report ("tree_depth_to_n($depth)=$n reverse got_depth=",$got_depth); } } { my $got_depth = $path->tree_n_to_depth($n-1); if (defined $got_depth && $got_depth >= $depth) { &$report ("tree_depth_to_n($depth)=$n reverse of n-1 got_depth=",$got_depth); } } { my $got_depth = $path->tree_n_to_depth($n_end); if (! defined $got_depth || $got_depth != $depth) { &$report ("tree_depth_to_n_end($depth)=$n_end reverse n_end got_depth=",$got_depth); } } { my $got_depth = $path->tree_n_to_depth($n_end+1); if (defined $got_depth && $got_depth <= $depth) { &$report ("tree_depth_to_n($depth)=$n reverse of n_end+1 got_depth=",$got_depth); } } if ($n_end <= $limit) { my $got_width = $path->tree_depth_to_width($depth); my $want_width = $depth_to_width_by_count[$depth] || 0; if ($got_width != $want_width) { &$report ("tree_depth_to_width($depth)=$got_width but counting want=$want_width"); } } } } ### done mod: $mod } ok ($good, 1); } #------------------------------------------------------------------------------ # path calculations # Return true if the rows of the tree are numbered contiguously, so each row # starts immediately following the previous with no overlapping. sub path_tree_n_rows_are_contiguous { my ($path) = @_; foreach my $depth (0 .. 10) { my $n_end = $path->tree_depth_to_n_end($depth); my $n_next = $path->tree_depth_to_n($depth+1); if ($n_next != $n_end+1) { return 0; } } return 1; } # Unused for now. # # sub path_tree_depth_to_width_by_count { # my ($path, $depth) = @_; # ### path_tree_depth_to_width_by_count(): $depth # my $width = 0; # my ($n_lo, $n_hi) = $path->tree_depth_to_n_range($depth); # ### $n_lo # ### $n_hi # foreach my $n ($n_lo .. $n_hi) { # ### d: $path->tree_n_to_depth($n) # $width += ($path->tree_n_to_depth($n) == $depth); # } # ### $width # return $width; # } sub path_tree_n_to_depth_by_parents { my ($path, $n) = @_; if ($n < $path->n_start) { return undef; } my $depth = 0; for (;;) { my $parent_n = $path->tree_n_parent($n); last if ! defined $parent_n; if ($parent_n >= $n) { die "Oops, tree parent $parent_n >= child $n in ", ref $path; } $n = $parent_n; $depth++; } return $depth; } # use Smart::Comments; use constant SUBHEIGHT_SEARCH_LIMIT => 50; sub path_tree_n_to_subheight_by_search { my ($path, $n, $limit) = @_; if ($path->isa('Math::PlanePath::HTree') && is_pow2($n)) { return undef; # infinite } if (! defined $limit) { $limit = SUBHEIGHT_SEARCH_LIMIT; } if ($limit <= 0) { return undef; # presumed infinite } if (! exists $path->{'path_tree_n_to_subheight_by_search__cache'}->{$n}) { my @children = $path->tree_n_children($n); my $height = 0; foreach my $n_child (@children) { my $h = path_tree_n_to_subheight_by_search($path,$n_child,$limit-1); if (! defined $h) { $height = undef; # infinite last; } $h++; if ($h >= $height) { $height = $h; # new bigger subheight among the children } } ### maximum is: $height if (defined $height || $limit >= SUBHEIGHT_SEARCH_LIMIT*4/5) { ### set cache: "n=$n ".($height//'[undef]') $path->{'path_tree_n_to_subheight_by_search__cache'}->{$n} = $height; ### cache: $path->{'path_tree_n_to_subheight_by_search__cache'} } } ### path_tree_n_to_subheight_by_search(): "n=$n" return $path->{'path_tree_n_to_subheight_by_search__cache'}->{$n}; # my @n = ($n); # my $height = 0; # my @pending = ($n); # for (;;) { # my $n = pop @pending; # @n = map {} @n # or return $height; # # if (defined my $h = $path->{'path_tree_n_to_subheight_by_search__cache'}->{$n}) { # return $height + $h; # } # @n = map {$path->tree_n_children($_)} @n # or return $height; # $height++; # if (@n > 200 || $height > 200) { # return undef; # presumed infinite # } # } } # no Smart::Comments; sub path_tree_n_to_leafdist_by_search { my ($path, $n, $limit) = @_; if (! defined $limit) { $limit = SUBHEIGHT_SEARCH_LIMIT; } ### path_tree_n_to_leafdist_by_search(): "n=$n limit=$limit" if ($limit <= 0) { return undef; # presumed infinite } if (! exists $path->{'path_tree_n_to_leafdist_by_search__cache'}->{$n}) { my @children = $path->tree_n_children($n); my $leafdist = 0; if (@children) { my @min; foreach my $child_n (@children) { my $child_leafdist = path_tree_n_to_leafdist_by_search ($path, $child_n, List::Util::min(@min,$limit-1)); if (defined $child_leafdist) { if ($child_leafdist == 0) { # child is a leaf, distance to it is 1 @min = (1); last; } push @min, $child_leafdist+1; } } $leafdist = List::Util::min(@min); ### for: "n=$n min of ".join(',',@min)." children=".join(',',@children)." gives ",$leafdist } else { ### for: "n=$n is a leaf node" } if (defined $leafdist || $limit >= SUBHEIGHT_SEARCH_LIMIT*4/5) { $path->{'path_tree_n_to_leafdist_by_search__cache'}->{$n} = $leafdist; } } ### path_tree_n_to_leafdist_by_search(): "n=$n" return $path->{'path_tree_n_to_leafdist_by_search__cache'}->{$n}; } # no Smart::Comments; #------------------------------------------------------------------------------ # generic sub equal { my ($x,$y) = @_; return ((! defined $x && ! defined $y) || (defined $x && defined $y && $x == $y)); } use POSIX 'fmod'; sub gcd { my ($x,$y) = @_; $x = abs($x); $y = abs($y); if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } # hack to recognise 1/3 from KochSnowflakes if ($x == 1 && $y == 1/3) { return $y; } if ($x == 0) { return $y; } if ($y > $x) { $y = fmod($y,$x); } for (;;) { ### assert: $x >= 1 if ($y == 0) { return $x; # gcd(x,0)=x } if ($y < 0.00001) { return 0; } ($x,$y) = ($y, fmod($x,$y)); } } sub is_pow2 { my ($n) = @_; my ($pow,$exp) = round_down_pow ($n, 2); return ($n == $pow); } sub coderef_is_const { my ($coderef) = @_; # FIXME: is not quite right? Is XSUBANY present on ALIAS: xsubs too? require B; return defined(B::svref_2object(\&coderef_is_const)->XSUBANY); } CHECK { # my $coderef_is_const_check = 1; use constant coderef_is_const_check => 1; coderef_is_const(\&coderef_is_const_check) or die; } use constant pi => atan2(1,0)*4; # $a and $b are arrayrefs [$dx,$dy] # Return an order +ve,0,-ve between them, first by angle then by length. sub dxdy_cmp { my ($a_dx,$a_dy, $b_dx,$b_dy) = @_; return dxdy_cmp_angle($a_dx,$a_dy, $b_dx,$b_dy) || dxdy_cmp_length($a_dx,$a_dy, $b_dx,$b_dy) || 0; } sub dxdy_cmp_angle { my ($a_dx,$a_dy, $b_dx,$b_dy) = @_; my $a_angle = atan2($a_dy,$a_dx); my $b_angle = atan2($b_dy,$b_dx); if ($a_angle < 0) { $a_angle += 2*pi(); } if ($b_angle < 0) { $b_angle += 2*pi(); } return $a_angle <=> $b_angle; } sub dxdy_cmp_length { my ($a_dx,$a_dy, $b_dx,$b_dy) = @_; return ($a_dx**2 + $a_dy**2 <=> $b_dx**2 + $b_dy**2); } sub path_n_to_LSR_with_rounding { my ($path, $n) = @_; my ($prev_dx,$prev_dy) = $path->n_to_dxdy($n - $path->arms_count) or return 98; my ($dx,$dy) = $path->n_to_dxdy($n) or return 99; my $LSR = $dy*$prev_dx - $dx*$prev_dy; if (abs($LSR) < 1e-10) { $LSR = 0; } $LSR = ($LSR <=> 0); # 1,undef,-1 # print "path_n_to_LSR dxdy $prev_dx,$prev_dy then $dx,$dy is LSR=$LSR\n"; return $LSR; } exit 0; Math-PlanePath-129/xt/0-Test-YAML-Meta.t0000755000175000017500000000346113046214104015230 0ustar gggg#!/usr/bin/perl -w # 0-Test-YAML-Meta.t -- run Test::CPAN::Meta::YAML if available # Copyright 2009, 2010, 2011, 2013, 2014, 2017 Kevin Ryde # 0-Test-YAML-Meta.t is shared by several distributions. # # 0-Test-YAML-Meta.t 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, or (at your option) any later # version. # # 0-Test-YAML-Meta.t 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 file. If not, see . use 5.004; use strict; use Test::More; my $meta_filename = 'META.yml'; unless (-e $meta_filename) { plan skip_all => "$meta_filename doesn't exist -- assume this is a working directory not a dist"; } plan tests => 3; SKIP: { eval { require CPAN::Meta::Validator; 1 } or skip "due to CPAN::Meta::Validator not available -- $@"; eval { require YAML; 1 } or skip "due to YAML module not available -- $@", 1; diag "CPAN::Meta::Validator version ", CPAN::Meta::Validator->VERSION; my $struct = YAML::LoadFile ($meta_filename); my $cmv = CPAN::Meta::Validator->new($struct); ok ($cmv->is_valid); if (! $cmv->is_valid) { diag "CPAN::Meta::Validator errors:"; foreach ($cmv->errors) { diag $_; } } } { # Test::CPAN::Meta::YAML version 0.15 for upper case "optional_features" names # eval 'use Test::CPAN::Meta::YAML 0.15; 1' or plan skip_all => "due to Test::CPAN::Meta::YAML 0.15 not available -- $@"; Test::CPAN::Meta::YAML::meta_spec_ok('META.yml'); } exit 0; Math-PlanePath-129/xt/R5DragonCurve-hog.t0000644000175000017500000000615213601570034015732 0ustar gggg#!/usr/bin/perl -w # Copyright 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use File::Slurp; use FindBin; use Graph; use List::Util 'min', 'max'; use Test; plan tests => 4; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::R5DragonCurve; use File::Spec; use lib File::Spec->catdir('devel','lib'); use MyGraphs; #------------------------------------------------------------------------------ sub make_graph { my ($level) = @_; my $path = Math::PlanePath::R5DragonCurve->new; my $graph = Graph->new (undirected => 1); my ($n_lo, $n_hi) = $path->level_to_n_range($level); foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); $graph->add_vertex("$x,$y"); } foreach my $n ($n_lo .. $n_hi-1) { my ($x,$y) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); $graph->add_edge("$x,$y", "$x2,$y2"); } return $graph; } { my %shown; { my $content = File::Slurp::read_file (File::Spec->catfile($FindBin::Bin, File::Spec->updir, 'lib','Math','PlanePath','R5DragonCurve.pm')); $content =~ /=head1 HOUSE OF GRAPHS.*?=head1/s or die; $content = $&; my $count = 0; while ($content =~ /^ +(?\d+) +level=(?\d+)/mg) { $count++; my $id = $+{'id'}; my $level = $+{'level'}; $shown{"level=$level"} = $+{'id'}; } ok ($count, 4, 'HOG ID number of lines'); } ok (scalar(keys %shown), 4); ### %shown my $extras = 0; my $compared = 0; my $others = 0; my %seen; # 5^4 == 625 foreach my $level (0 .. 4) { my $graph = make_graph($level); my $g6_str = MyGraphs::Graph_to_graph6_str($graph); $g6_str = MyGraphs::graph6_str_to_canonical($g6_str); next if $seen{$g6_str}++; my $key = "level=$level"; if (my $id = $shown{$key}) { MyGraphs::hog_compare($id, $g6_str); $compared++; } else { if (MyGraphs::hog_grep($g6_str)) { $others++; my $name = $graph->get_graph_attribute('name'); MyTestHelpers::diag ("HOG $key in HOG, not shown in POD"); MyTestHelpers::diag ($name); MyTestHelpers::diag ($g6_str); # MyGraphs::Graph_view($graph); $extras++; } } } ok ($extras, 0); ok ($others, 0); MyTestHelpers::diag ("POD HOG $compared compares, $others others"); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/AlternatePaper-hog.t0000644000175000017500000000721413774712350016225 0ustar gggg#!/usr/bin/perl -w # Copyright 2019, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use File::Slurp; use FindBin; use Graph; use List::Util 'min', 'max'; use Test; plan tests => 5; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::AlternatePaper; use File::Spec; use lib File::Spec->catdir('devel','lib'); use MyGraphs; #------------------------------------------------------------------------------ sub make_graph { my ($level) = @_; my $path = Math::PlanePath::AlternatePaper->new; my $graph = Graph->new (undirected => 1); my ($n_lo, $n_hi) = $path->level_to_n_range($level); foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); $graph->add_vertex("$x,$y"); } foreach my $n ($n_lo .. $n_hi-1) { my ($x,$y) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); $graph->add_edge("$x,$y", "$x2,$y2"); } return $graph; } { my %shown; { my $content = File::Slurp::read_file (File::Spec->catfile($FindBin::Bin, File::Spec->updir, 'lib','Math','PlanePath','AlternatePaper.pm')); $content =~ /=head1 HOUSE OF GRAPHS.*?=head1/s or die; $content = $&; my $count = 0; while ($content =~ /^ +(?\d+) +level=(?\d+)/mg) { $count++; my $id = $+{'id'}; my $level = $+{'level'}; $shown{"level=$level"} = $+{'id'}; } ok ($count, 9, 'HOG ID number of lines'); } ok (scalar(keys %shown), 9); ### %shown my $extras = 0; my $compared = 0; my $others = 0; my %seen; foreach my $level (0 .. 9) { my $graph = make_graph($level); last if $graph->vertices >= 256; my $g6_str = MyGraphs::Graph_to_graph6_str($graph); $g6_str = MyGraphs::graph6_str_to_canonical($g6_str); next if $seen{$g6_str}++; my $key = "level=$level"; if (my $id = $shown{$key}) { MyGraphs::hog_compare($id, $g6_str); $compared++; } else { $others++; if (MyGraphs::hog_grep($g6_str)) { MyTestHelpers::diag ("HOG $key in HOG, not shown in POD"); my $name = $graph->get_graph_attribute('name'); MyTestHelpers::diag ($name); MyTestHelpers::diag ($g6_str); # MyGraphs::Graph_view($graph); $extras++; } } } ok ($extras, 0); ok ($others, 0); MyTestHelpers::diag ("POD HOG $compared compares, $others others"); } #------------------------------------------------------------------------------ # A086341 - Graph Diameter, k>=3 MyOEIS::compare_values (anum => 'A086341', max_count => 8, func => sub { my ($count) = @_; my @got; for (my $k = 0; @got < $count; $k++) { my $graph = make_graph($k); my $got= $graph->diameter; if ($k==1) { $got == 2 or die; $got = 3; } # exceptions if ($k==2) { $got == 4 or die; $got = 3; } push @got, $got; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/PixelRings-image.t0000644000175000017500000001061312136177167015706 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::PixelRings; my $test_count = (tests => 2)[1]; plan tests => $test_count; if (! eval 'use Image::Base 1.09; 1') { # version 1.09 for ellipse fixes MyTestHelpers::diag ('skip due to Image::Base 1.09 not available -- ',$@); foreach (1 .. $test_count) { skip ('due to no Image::Base 1.09', 1, 1); } exit 0; } # uncomment this to run the ### lines #use Smart::Comments; sub dump_coords { my ($href) = @_; my $x_min = 0; my $y_min = 0; foreach my $key (keys %$href) { my ($x,$y) = split /,/, $key; if ($x < $x_min) { $x_min = $x; } if ($y < $y_min) { $y_min = $y; } } my @rows; foreach my $key (keys %$href) { my ($x,$y) = split /,/, $key; $rows[$y-$y_min]->[$x-$x_min] = '*'; } foreach my $row (reverse @rows) { my $str = ''; if ($row) { foreach my $char (@$row) { if ($char) { $str .= " $char"; } else { $str .= " "; } } } MyTestHelpers::diag ($str); } } my %image_coords; my $offset = 100; { package MyImage; use vars '@ISA'; @ISA = ('Image::Base'); sub new { my $class = shift; return bless {@_}, $class; } sub xy { my ($self, $x, $y, $colour) = @_; $x -= $offset; $y -= $offset; ### image_coords: "$x,$y" $image_coords{"$x,$y"} = 1; } } #------------------------------------------------------------------------------ # _cumul_extend() { my $path = Math::PlanePath::PixelRings->new; my $image = MyImage->new; my $good = 1; my $limit = 500; foreach my $r (1 .. $limit) { %image_coords = (); $image->ellipse (-$r+$offset,-$r+$offset, $r+$offset,$r+$offset, 'white'); my $image_count = scalar(@{[keys %image_coords]}); Math::PlanePath::PixelRings::_cumul_extend($path); my $got = $path->{'cumul'}->[$r+1]; my $want = $path->{'cumul'}->[$r] + $image_count; if ($got != $want) { $good = 0; MyTestHelpers::diag ("_cumul_extend() r=$r wrong: want=$want got=$got"); } } ok ($good, 1, "_cumul_extend() to $limit"); } #------------------------------------------------------------------------------ # coords { my $path = Math::PlanePath::PixelRings->new; my $image = MyImage->new; my $n = 1; my $good = 1; my $limit = 100; foreach my $r (0 .. $limit) { %image_coords = (); $image->ellipse (-$r+$offset,-$r+$offset, $r+$offset,$r+$offset, 'white'); my $image_count = scalar(@{[keys %image_coords]}); ### $image_count ### from n: $n my %path_coords; while ($image_count--) { my ($x,$y) = $path->n_to_xy($n++); # perl 5.6.0 through 5.6.2 ends up giving "-0" when stringizing (as of # the code in PixelRings version 19), avoid that so the hash keys # compare with "eq" successfully $x = "$x"; $y = "$y"; if ($x eq '-0') { $x = '0'; } if ($y eq '-0') { $y = '0'; } ### path_coords: "$x,$y" $path_coords{"$x,$y"} = 1; } ### %image_coords ### %path_coords if (! eq_hash (\%path_coords, \%image_coords)) { MyTestHelpers::diag ("Wrong coords at r=$r"); MyTestHelpers::diag ("image: ", join(',', sort keys %image_coords)); MyTestHelpers::diag ("path: ", join(',', sort keys %path_coords)); dump_coords (\%image_coords); dump_coords (\%path_coords); $good = 0; } } ok ($good, 1, 'n_to_xy() compared to image->ellipse()'); } sub eq_hash { my ($x, $y) = @_; foreach my $key (keys %$x) { if (! exists $y->{$key}) { return 0; } } foreach my $key (keys %$y) { if (! exists $x->{$key}) { return 0; } } return 1; } exit 0; Math-PlanePath-129/xt/0-META-read.t0000755000175000017500000001071512136177162014337 0ustar gggg#!/usr/bin/perl -w # 0-META-read.t -- check META.yml can be read by various YAML modules # Copyright 2009, 2010, 2011, 2012, 2013 Kevin Ryde # 0-META-read.t is shared among several distributions. # # 0-META-read.t 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, or (at your option) any later # version. # # 0-META-read.t 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 file. If not, see . use 5.005; use strict; use Test::More; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # When some of META.yml is generated by explicit text in Makefile.PL it can # be easy to make a mistake in the syntax, or indentation, etc, so the idea # here is to check it's readable from some of the YAML readers. # # The various readers differ in how strictly they look at the syntax. # There's no attempt here to say one of them is best or tightest or # whatever, just see that they all work. # # See 0-Test-YAML-Meta.t for Test::YAML::Meta which looks into field # contents, as well as maybe the YAML formatting. my $meta_filename; # allow for ancient perl, maybe eval { require FindBin; 1 } # new in 5.004 or plan skip_all => "FindBin not available -- $@"; eval { require File::Spec; 1 } # new in 5.005 or plan skip_all => "File::Spec not available -- $@"; diag "FindBin $FindBin::Bin"; $meta_filename = File::Spec->catfile ($FindBin::Bin, File::Spec->updir, 'META.yml'); -e $meta_filename or plan skip_all => "$meta_filename doesn't exist -- assume this is a working directory not a dist"; plan tests => 5; SKIP: { eval { require YAML; 1 } or skip "due to YAML module not available -- $@", 1; my $ok = eval { YAML::LoadFile ($meta_filename); 1 } or diag "YAML::LoadFile() error -- $@"; ok ($ok, "Read $meta_filename with YAML module"); } # YAML 0.68 is in fact YAML::Old, or something weird -- don't think they can # load together # # SKIP: { # eval { require YAML::Old; 1 } # or skip 'due to YAML::Old not available -- $@', 1; # # eval { YAML::Old::LoadFile ($meta_filename) }; # is ($@, '', # "Read $meta_filename with YAML::Old"); # } SKIP: { eval { require YAML::Syck; 1 } or skip "due to YAML::Syck not available -- $@", 1; my $ok = eval { YAML::Syck::LoadFile ($meta_filename); 1 } or diag "YAML::Syck::LoadFile() error -- $@"; ok ($ok, "Read $meta_filename with YAML::Syck"); } SKIP: { eval { require YAML::Tiny; 1 } or skip "due to YAML::Tiny not available -- $@", 1; my $ok = eval { YAML::Tiny->read ($meta_filename); 1 } or diag "YAML::Tiny->read() error -- $@"; ok ($ok, "Read $meta_filename with YAML::Tiny"); } SKIP: { eval { require YAML::XS; 1 } or skip "due to YAML::XS not available -- $@", 1; my $ok = eval { YAML::XS::LoadFile ($meta_filename); 1 } or diag "YAML::XS::LoadFile() error -- $@"; ok ($ok, "Read $meta_filename with YAML::XS"); } # Parse::CPAN::Meta describes itself for use on "typical" META.yml, so not # sure if demanding it works will more exercise its subset of yaml than the # correctness of our META.yml. At any rate might like to know if it fails, # so as to avoid tricky yaml for everyone's benefit, maybe. # SKIP: { eval { require Parse::CPAN::Meta; 1 } or skip "due to Parse::CPAN::Meta not available -- $@", 1; my $ok = eval { Parse::CPAN::Meta::LoadFile ($meta_filename); 1 } or diag "Parse::CPAN::Meta::LoadFile() error -- $@"; ok ($ok, "Read $meta_filename with Parse::CPAN::Meta::LoadFile"); } # Data::YAML::Reader 0.06 doesn't like header "--- #YAML:1.0" with the # # part produced by other YAML writers, so skip for now # # SKIP: { # eval { require Data::YAML::Reader; 1 } # or skip 'due to Data::YAML::Reader not available -- $@', 1; # # my $reader = Data::YAML::Reader->new; # open my $fh, '<', $meta_filename # or die "Cannot open $meta_filename"; # my $str = do { local $/=undef; <$fh> }; # close $fh or die; # # # if ($str !~ /\.\.\.$/) { # # $str .= "..."; # # } # my @lines = split /\n/, $str; # push @lines, "..."; # use Data::Dumper; # print Dumper(\@lines); # # # { local $,="\n"; print @lines,"\n"; } exit 0; Math-PlanePath-129/xt/ChanTree-slow.t0000644000175000017500000000710313475604675015223 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 22;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::ChanTree; use Math::PlanePath::CoprimeColumns; *_coprime = \&Math::PlanePath::CoprimeColumns::_coprime; use Math::PlanePath::GcdRationals; *_gcd = \&Math::PlanePath::GcdRationals::_gcd; #------------------------------------------------------------------------------ # n_to_xy() reversal { require Math::PlanePath::GcdRationals; foreach my $k (3 .. 7) { foreach my $reduced (0, 1) { my $path = Math::PlanePath::ChanTree->new (k => $k, reduced => $reduced); foreach my $n ($path->n_start .. 500) { my ($x,$y) = $path->n_to_xy($n); my $rev = $path->xy_to_n($x,$y); if (! defined $rev || $rev != $n) { $rev = (defined $rev ? $rev : 'undef'); die "k=$k reduced=$reduced n_to_xy($n)=$x,$y but reverse xy_to_n($x,$y) is rev=$rev"; } if ($reduced) { my $gcd = Math::PlanePath::GcdRationals::_gcd($x,$y); if ($gcd > 1) { die "k=$k reduced=$reduced n_to_xy($n)=$x,$y common factor $gcd"; } } } ok ($k, $k); } } } #------------------------------------------------------------------------------ # block of points eval 'use Math::BigInt try=>q{GMP}; 1' || eval 'use Math::BigInt; 1' || die; { my $size = 100; foreach my $k (2 .. 7) { foreach my $reduced (0, 1) { my $path = Math::PlanePath::ChanTree->new (k => $k, reduced => $reduced); my %seen_n; foreach my $x (1 .. $size) { foreach my $y (1 .. $size) { my $n = $path->xy_to_n(Math::BigInt->new($x), Math::BigInt->new($y)); if ($reduced) { if (is_reduced_xy($k,$x,$y)) { if (! defined $n) { die "k=$k reduced=$reduced xy_to_n($x,$y) is reduced point but n=undef"; } } else { if (defined $n) { my $gcd = Math::PlanePath::GcdRationals::_gcd($x,$y); die "k=$k reduced=$reduced xy_to_n($x,$y) is not reduced point (gcd=$gcd) but still have n=$n"; } } } if (defined $n) { if ($seen_n{$n}) { die "k=$k xy_to_n($x,$y) is n=$n, but previously xy_to_n($seen_n{$n}) was n=$n"; } $seen_n{$n} = "$x,$y"; } } } ok ($k, $k); } } } sub is_reduced_xy { my ($k, $x, $y) = @_; if (! _coprime($x,$y)) { return 0; } if (($k & 1) && is_both_odd($x,$y)) { return 0; } return 1; } sub is_both_odd { my ($x, $y) = @_; return ($x % 2) && ($y % 2); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/slow/0002755000175000017500000000000014001441522013320 5ustar ggggMath-PlanePath-129/xt/slow/TerdragonCurve-slow.t0000644000175000017500000003265012451351065017436 0ustar gggg#!/usr/bin/perl -w # Copyright 2014, 2015 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min','max'; use Test; plan tests => 339; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use lib 'xt'; use MyOEIS; # uncomment this to run the ### lines # use Smart::Comments; use Memoize; use Math::PlanePath::TerdragonCurve; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh','round_down_pow'; use Math::PlanePath; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; #------------------------------------------------------------------------------ # left boundary samples { my $path = Math::PlanePath::TerdragonCurve->new; # examples in terdragon paper ok ($path->_UNDOCUMENTED__left_boundary_i_to_n(8), 50); ok (!! $path->_UNDOCUMENTED__n_segment_is_left_boundary(0,0), 1); ok (!! $path->_UNDOCUMENTED__n_segment_is_left_boundary(0,1), 1); ok (!! $path->_UNDOCUMENTED__n_segment_is_left_boundary(0,-1), 1); ok (!! $path->_UNDOCUMENTED__n_segment_is_left_boundary(2,0), ''); ok (!! $path->_UNDOCUMENTED__n_segment_is_left_boundary(2,1), 1); ok (!! $path->_UNDOCUMENTED__n_segment_is_left_boundary(2,-1), 1); } #------------------------------------------------------------------------------ # left boundary infinite data my @left_infinite = (0, 1, 5, 15, 16, 17, 45, 46, 50, 51, 52, 53); my %left_infinite = map {$_=>1} @left_infinite; { my $path = Math::PlanePath::TerdragonCurve->new; my $bad = 0; foreach my $n (0 .. $left_infinite[-1]) { my $want = $left_infinite{$n} ? 1 : 0; foreach my $method ('_UNDOCUMENTED__n_segment_is_left_boundary', sub { my ($path, $n) = @_; my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); return path_triangular_xyxy_is_left_boundary ($path, $x1,$y1, $x2,$y2); }) { my $got = $path->$method($n) ? 1 : 0; if ($got != $want) { MyTestHelpers::diag("oops, $method n=$n want=$want got=$got"); die; last if $bad++ > 10; } } } ok ($bad, 0); } { my $path = Math::PlanePath::TerdragonCurve->new; foreach my $i (0 .. $#left_infinite) { my $want = $left_infinite[$i]; my $got = $path->_UNDOCUMENTED__left_boundary_i_to_n($i); ok ($got, $want, "i=$i want=$want got=$got"); } } #------------------------------------------------------------------------------ # left boundary levels data my @left_levels = ([0], [0,1,2], [0,1, 5,6,7,8], [0,1,5, 15,16,17,18,19,23,24,25,26]); my @left_levels_hash = map { my $h = {map {$_=>1} @$_}; $h } @left_levels; { my $path = Math::PlanePath::TerdragonCurve->new; foreach my $level (0 .. $#left_levels) { my $hash = $left_levels_hash[$level]; my ($n_lo, $n_hi) = $path->level_to_n_range($level); foreach my $n ($n_lo .. $n_hi-1) { my $want = $hash->{$n} ? 1 : 0; foreach my $method ('_UNDOCUMENTED__n_segment_is_left_boundary', sub { my ($path, $n, $level) = @_; my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); return path_triangular_xyxy_is_left_boundary ($path, $x1,$y1, $x2,$y2, $level); }) { my $got = $path->$method($n,$level) ? 1 : 0; ok ($got, $want, "level=$level $method n=$n want=$want got=$got"); } } my $aref = $left_levels[$level]; foreach my $i (0 .. $#$aref) { my $want = $aref->[$i]; my $got = $path->_UNDOCUMENTED__left_boundary_i_to_n($i,$level); ok ($got, $want, "i=$i want=$want got=".(defined $got ? $got : '[undef]')); } } } my %left_all_hash = map {map {$_=>1} @$_} @left_levels; my @left_all = sort {$a<=>$b} keys %left_all_hash; { my $path = Math::PlanePath::TerdragonCurve->new; foreach my $n (0 .. max(keys %left_all_hash)) { my $want = $left_all_hash{$n} ? 1 : 0; foreach my $method ('_UNDOCUMENTED__n_segment_is_left_boundary', sub { my ($path, $n, $level) = @_; my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); return path_triangular_xyxy_is_left_boundary ($path, $x1,$y1, $x2,$y2, $level); }) { my $got = $path->$method($n,-1) ? 1 : 0; ok ($got, $want, "all $method n=$n want=$want got=$got"); } } # NOT WORKING YET # foreach my $i (0 .. $#left_all) { # my $want = $left_all[$i]; # my $got = $path->_UNDOCUMENTED__left_boundary_i_to_n($i,-1); # ok ($got, $want, # "i=$i want=$want got=".(defined $got ? $got : '[undef]')); # } } #------------------------------------------------------------------------------ # left boundary vs xyxy func { my $path = Math::PlanePath::TerdragonCurve->new; my $bad = 0; OUTER: foreach my $level (-1, 0 .. 6, undef) { my $name = (! defined $level ? 'infinite' : $level < 0 ? 'all' : "level=$level"); my $i = 0; my $ni = $path->_UNDOCUMENTED__left_boundary_i_to_n($i); foreach my $n (0 .. 3**6-1) { my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); my $want_pred = path_triangular_xyxy_is_left_boundary($path, $x1,$y1, $x2,$y2, $level) ? 1 : 0; my $got_pred = $path->_UNDOCUMENTED__n_segment_is_left_boundary($n,$level) ? 1 : 0; if ($want_pred != $got_pred) { MyTestHelpers::diag("oops, $name n=$n pred want $want_pred got $got_pred"); last if $bad++ > 10; } my $got_ni = (defined $ni && $n == $ni ? 1 : 0); if ($got_ni != $want_pred) { MyTestHelpers::diag("oops, $name n=$n ni=".(defined $ni ? $ni : '[undef]')." want_pred=$want_pred"); last OUTER if $bad++ > 10; } if (defined $ni && $n >= $ni) { $i++; $ni = $path->_UNDOCUMENTED__left_boundary_i_to_n($i, $level); } } } ok ($bad, 0); } # return true if line segment $x1,$y1 to $x2,$y2 is on the left boundary # of triangular path $path # use Smart::Comments; sub path_triangular_xyxy_is_left_boundary { my ($path, $x1,$y1, $x2,$y2, $level) = @_; ### path_triangular_xyxy_is_left_boundary(): "$x1,$y1 to $x2,$y2" my $n = $path->xyxy_to_n ($x1,$y1, $x2,$y2); my $n_hi; if (defined $level) { if ($level < 0) { ($n_hi) = round_down_pow($n,3); $n_hi *= 3; } else { $n_hi = 3**$level; if ($n >= $n_hi) { ### segment beyond level, so not boundary ... return 0; } } } ### $n_hi my $dx = $x2-$x1; my $dy = $y2-$y1; my $x3 = $x1 + ($dx-3*$dy)/2; # dx,dy rotate +60 my $y3 = $y1 + ($dy+$dx)/2; ### points: "left side $x3,$y2" foreach my $n1 ($path->xyxy_to_n_either ($x1,$y1, $x3,$y3), $path->xyxy_to_n_either ($x2,$y2, $x3,$y3)) { ### $n1 if (! defined $n1) { ### never traversed, so boundary ... return 1; } if ($n_hi && $n1 >= $n_hi) { ### traversed beyond our target level, so boundary ... return 1; } } return 0; } #------------------------------------------------------------------------------ # right boundary N { my $path = Math::PlanePath::TerdragonCurve->new; my $i = 0; my $ni = $path->_UNDOCUMENTED__right_boundary_i_to_n($i); foreach my $n (0 .. 3**4-1) { my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); my $want_pred = path_triangular_xyxy_is_right_boundary($path, $x1,$y1, $x2,$y2) ? 1 : 0; my $got_pred = $path->_UNDOCUMENTED__n_segment_is_right_boundary($n) ? 1 : 0; ok ($want_pred, $got_pred, "n=$n pred want $want_pred got $got_pred"); ok (($n == $ni) == $want_pred, 1); if ($n >= $ni) { $i++; $ni = $path->_UNDOCUMENTED__right_boundary_i_to_n($i); } } } # return true if line segment $x1,$y1 to $x2,$y2 is on the right boundary # of triangular path $path sub path_triangular_xyxy_is_right_boundary { my ($path, $x1,$y1, $x2,$y2) = @_; my $dx = $x2-$x1; my $dy = $y2-$y1; my $x3 = $x1 + ($dx+3*$dy)/2; # dx,dy rotate -60 so right my $y3 = $y1 + ($dy-$dx)/2; ### path_triangular_xyxy_is_right_boundary(): "$x1,$y1 $x2,$y2 $x3,$y3" return (! defined ($path->xyxy_to_n_either ($x1,$y1, $x3,$y3)) || ! defined ($path->xyxy_to_n_either ($x2,$y2, $x3,$y3))); } # MyOEIS triangular boundary bits broken exit 0; #------------------------------------------------------------------------------ # B my $path = Math::PlanePath::TerdragonCurve->new; { # samples values from terdragon.tex my @want = (2, 6, 12, 24, 48, 96); foreach my $k (0 .. $#want) { my $got = B_from_path($path,$k); my $want = $want[$k]; ok ($got,$want); } } { # B[k+1] = R[k] + U[k] foreach my $k (0 .. 5) { my $r = R_from_path($path,$k); my $u = U_from_path($path,$k); my $b = B_from_path($path,$k+1); ok ($r+$u, $b, "k=$k R+U=B"); } } { # B[k] = 2, 3*2^k foreach my $k (0 .. 10) { my $want = b_from_path($path,$k); my $got = B_from_formula($k); ok ($got,$want); } sub B_from_formula { my ($k) = @_; return ($k==0 ? 2 : 3*2**$k); } } #------------------------------------------------------------------------------ # R { # R[k] = B[k]/2 my $sum = 1; foreach my $k (0 .. 8) { my $b = B_from_path($path,$k); my $r = R_from_path($path,$k); ok ($r,$b/2); } } { # samples from terdragon.tex my @want = (1, 3, 6, 12, 24, 48); foreach my $k (0 .. $#want) { my $got = R_from_path($path,$k); my $want = $want[$k]; ok ($got,$want); } } { # R[k+1] = R[k] + U[k] foreach my $k (1 .. 8) { my $r0 = R_from_path($path,$k); my $r1 = R_from_path($path,$k+1); my $u = R_from_path($path,$k); ok ($r0+$u, $r1); } } #------------------------------------------------------------------------------ # Area { # A[k] = (2*3^k - B[k])/4 foreach my $k (0 .. 8) { my $b = B_from_path($path,$k); my $got = (2*3**$k - $b)/4; my $want = A_from_path($path,$k); ok ($got,$want); } } { # A[k] = if(k==0,0, 2*(3^(k-1)-2^(k-1))) foreach my $k (0 .. 8) { my $got = A_from_formula($k); my $want = A_from_path($path,$k); ok ($got,$want); } sub A_from_formula { my ($k) = @_; return ($k==0 ? 0 : 2*(3**($k-1) - 2**($k-1))); } } #------------------------------------------------------------------------------ # boundary lengths sub B_from_path { my ($path, $k) = @_; my $n_limit = 3**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit, lattice_type => 'triangular'); return scalar(@$points); } BEGIN { memoize('B_from_path') } sub L_from_path { my ($path, $k) = @_; my $n_limit = 3**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit, lattice_type => 'triangular', side => 'left'); return scalar(@$points) - 1; } BEGIN { memoize('L_from_path') } sub R_from_path { my ($path, $k) = @_; my $n_limit = 3**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit, lattice_type => 'triangular', side => 'right'); return scalar(@$points) - 1; } BEGIN { memoize('R_from_path') } sub U_from_path { my ($path, $k) = @_; my $n_limit = 3**$k; my ($x,$y) = $path->n_to_xy($n_limit); my ($to_x,$to_y) = $path->n_to_xy(0); my $points = MyOEIS::path_boundary_points_ft($path, 3*$n_limit, $x,$y, $to_x,$to_y, lattice_type => 'triangular', dir => 1); return scalar(@$points) - 1; } BEGIN { memoize('U_from_path') } sub A_from_path { my ($path, $k) = @_; return MyOEIS::path_enclosed_area($path, 3**$k, lattice_type => 'triangular'); } BEGIN { memoize('A_from_path') } #------------------------------------------------------------------------------ # U { # samples from terdragon.tex my @want = (2, 3, 6, 12, 24, 48); foreach my $k (0 .. $#want) { my $got = U_from_path($path,$k); my $want = $want[$k]; ok ($got,$want); } } { # U[k+1] = R[k] + U[k] foreach my $k (0 .. 10) { my $u = U_from_path($path,$k); my $r = R_from_path($path,$k); my $got = $r + $u; my $want = U_from_path($path,$k+1); ok ($got,$want); } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/slow/R5DragonCurve-slow.t0000644000175000017500000003563612451431730017137 0ustar gggg#!/usr/bin/perl -w # Copyright 2014, 2015 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min','max'; use Test; plan tests => 218; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use lib 'xt'; use MyOEIS; # uncomment this to run the ### lines # use Smart::Comments; use Memoize; use Math::PlanePath::R5DragonCurve; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh'; use Math::PlanePath; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; #------------------------------------------------------------------------------ # right boundary N { my $bad = 0; foreach my $arms (1) { my $path = Math::PlanePath::R5DragonCurve->new (arms => $arms); my $i = 0; foreach my $n (0 .. 5**5-1) { my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n + $arms); my $want_pred = path_xyxy_is_right_boundary($path, $x1,$y1, $x2,$y2) ? 1 : 0; foreach my $method ('_UNDOCUMENTED__n_segment_is_right_boundary', 'main::n_segment_is_right_boundary__by_digitpairs_allowed', 'main::n_segment_is_right_boundary__by_digitpairs_disallowed', 'main::n_segment_is_right_boundary_by_hightolow_states', ) { my $got_pred = $path->_UNDOCUMENTED__n_segment_is_right_boundary($n) ? 1 : 0; unless ($want_pred == $got_pred) { MyTestHelpers::diag ("oops, $method() arms=$arms n=$n pred traverse=$want_pred method=$got_pred"); last if $bad++ > 10; } } } } ok ($bad, 0); } BEGIN { my @table = (undef, [ 1, 1, 2, 3, 4 ], # R -> RRCDE [ 1, 2 ], # C -> RC___ [undef, 3 ], # D -> _D___ [undef, 4, 2, 3, 4 ], # E -> _ECDE ); sub n_segment_is_right_boundary_by_hightolow_states { my ($self, $n) = @_; my $state = 1; foreach my $digit (reverse digit_split_lowtohigh($n,5)) { # high to low $state = $table[$state][$digit] || return 0; } return 1; } } BEGIN { my @allowed_pairs = ([ 1, '', 1, 1, 1 ], # 00, __, 02, 03, 04 0s all allowed '', # 1s deleted [ 1, '', 0, 0, 0 ], # 20, __, __, __, __ [ 0, '', 0, 0, 0 ], # __, __, __, __, __ 3s none allowed [ 0, '', 1, 1, 1 ], # __, __, 42, 43, 44 ); my @disallowed_pairs = ([ 0, '', 0, 0, 0 ], # __, __, __, __, __ 0s none disallowed '', # 1s deleted [ 0, '', 1, 1, 1 ], # __, __, 22, 23, 24 [ 1, '', 1, 1, 1 ], # 30, __, 32, 33, 34 3s all disallowed [ 1, '', 0, 0, 0 ], # 40, __, __, __, __ ); sub n_segment_is_right_boundary__by_digitpairs_disallowed { my ($self, $n) = @_; ### n_segment_is_right_boundary__by_digitpairs(): "n=$n" my $prev = 0; if (_divrem_mutate($n, $self->{'arms'})) { # FIXME: is this right ? $prev = 4; } foreach my $digit (reverse digit_split_lowtohigh($n,5)) { # high to low next if $digit == 1; ### pair: "$prev $digit table=".($table[$prev][$digit] || 0) if ($disallowed_pairs[$prev][$digit]) { ### no ... return 0; } $prev = $digit; } ### yes ... return 1; } sub n_segment_is_right_boundary__by_digitpairs_allowed { my ($self, $n) = @_; ### n_segment_is_right_boundary__by_digitpairs(): "n=$n" my $prev = 0; if (_divrem_mutate($n, $self->{'arms'})) { # FIXME: is this right ? $prev = 4; } foreach my $digit (reverse digit_split_lowtohigh($n,5)) { # high to low next if $digit == 1; ### pair: "$prev $digit table=".($table[$prev][$digit] || 0) if (! $allowed_pairs[$prev][$digit]) { ### no ... return 0; } $prev = $digit; } ### yes ... return 1; } } # return true if line segment $x1,$y1 to $x2,$y2 is on the right boundary sub path_xyxy_is_right_boundary { my ($path, $x1,$y1, $x2,$y2) = @_; ### path_xyxy_is_right_boundary() ... my $dx = $x2-$x1; my $dy = $y2-$y1; ($dx,$dy) = ($dy,-$dx); # rotate -90 ### one: "$x1,$y1 to ".($x1+$dx).",".($y1+$dy) ### two: "$x2,$y2 to ".($x2+$dx).",".($y2+$dy) return (! defined ($path->xyxy_to_n_either ($x1,$y1, $x1+$dx,$y1+$dy)) || ! defined ($path->xyxy_to_n_either ($x2,$y2, $x2+$dx,$y2+$dy)) || ! defined ($path->xyxy_to_n_either ($x1+$dx,$y1+$dy, $x2+$dx,$y2+$dy))); } #------------------------------------------------------------------------------ # left boundary N { my $bad = 0; foreach my $arms (1) { my $path = Math::PlanePath::R5DragonCurve->new (arms => $arms); my $i = 0; foreach my $level (0 .. 7) { my ($n_start, $n_end) = $path->level_to_n_range($level); foreach my $n ($n_start .. $n_end-1) { my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n + $arms); my $want_pred = path_xyxy_is_left_boundary($path, $x1,$y1, $x2,$y2, $level) ? 1 : 0; { my $got_pred = $path->_UNDOCUMENTED__n_segment_is_left_boundary($n, $level) ? 1 : 0; unless ($want_pred == $got_pred) { MyTestHelpers::diag ("oops, _UNDOCUMENTED__n_segment_is_left_boundary() arms=$arms level=$level n=$n pred traverse=$want_pred method=$got_pred"); last if $bad++ > 10; } } } } } ok ($bad, 0); exit 0; } { my $bad = 0; foreach my $arms (1) { my $path = Math::PlanePath::R5DragonCurve->new (arms => $arms); my $i = 0; foreach my $n (0 .. 5**7-1) { my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n + $arms); my $want_pred = path_xyxy_is_left_boundary($path, $x1,$y1, $x2,$y2) ? 1 : 0; { my $got_pred = $path->_UNDOCUMENTED__n_segment_is_left_boundary($n) ? 1 : 0; unless ($want_pred == $got_pred) { MyTestHelpers::diag ("oops, _UNDOCUMENTED__n_segment_is_left_boundary() arms=$arms n=$n pred traverse=$want_pred method=$got_pred"); last if $bad++ > 10; } } # { # my $got_pred = n_segment_is_left_boundary__by_digitpairs($path,$n) ? 1 : 0; # unless ($want_pred == $got_pred) { # MyTestHelpers::diag ("oops, n_segment_is_left_boundary__by_digitpairs() arms=$arms n=$n pred traverse=$want_pred method=$got_pred"); # last if $bad++ > 10; # } # } # { # my $got_pred = n_segment_is_left_boundary_by_hightolow_states($path,$n) ? 1 : 0; # unless ($want_pred == $got_pred) { # MyTestHelpers::diag ("n_segment_is_left_boundary_by_hightolow_states(), n_segment_is_left_boundary__by_digitpairs() arms=$arms n=$n pred traverse=$want_pred method=$got_pred"); # last if $bad++ > 10; # } # } } } ok ($bad, 0); } BEGIN { my @table = (undef, [ 1, 1, 2, 3, 4 ], # R -> RRCDE [ 1, 2 ], # C -> RC___ [undef, 3 ], # D -> _D___ [undef, 4, 2, 3, 4 ], # E -> _ECDE ); sub n_segment_is_left_boundary_by_hightolow_states { my ($self, $n) = @_; my $state = 1; foreach my $digit (reverse digit_split_lowtohigh($n,5)) { # high to low $state = $table[$state][$digit] || return 0; } return 1; } } BEGIN { my @table = ([ 1, undef, 1, 1, 1 ], # 00, 02, 03, 04 undef, [ 1 ], # 20 [ ], # 3 none [ undef, undef, 1, 1, 1 ], # 4 ); sub n_segment_is_left_boundary__by_digitpairs { my ($self, $n) = @_; ### n_segment_is_left_boundary__by_digitpairs(): "n=$n" my $prev = 0; { my $arms = $self->{'arms'}; if (_divrem_mutate($n, $arms) != $arms-1) { $prev = 1; } } foreach my $digit (reverse digit_split_lowtohigh($n,5)) { # high to low next if $digit == 1; ### pair: "$prev $digit table=".($table[$prev][$digit] || 0) unless ($table[$prev][$digit]) { ### no ... return 0; } $prev = $digit; } ### yes ... return 1; } } # return true if line segment $x1,$y1 to $x2,$y2 is on the left boundary sub path_xyxy_is_left_boundary { my ($path, $x1,$y1, $x2,$y2, $level) = @_; ### path_xyxy_is_left_boundary() ... my $dx = $x2-$x1; my $dy = $y2-$y1; ($dx,$dy) = (-$dy,$dx); # rotate +90 ### one: "$x1,$y1 to ".($x1+$dx).",".($y1+$dy) ### two: "$x2,$y2 to ".($x2+$dx).",".($y2+$dy) return (! path_xyxy_is_traversed_in_level ($path, $x1,$y1, $x1+$dx,$y1+$dy, $level) || ! path_xyxy_is_traversed_in_level ($path, $x2,$y2, $x2+$dx,$y2+$dy, $level) || ! path_xyxy_is_traversed_in_level ($path, $x1+$dx,$y1+$dy, $x2+$dx,$y2+$dy, $level)); } # return true if line segment $x1,$y1 to $x2,$y2 is traversed, # ie. consecutive N goes from $x1,$y1 to $x2,$y2, in either direction. sub path_xyxy_is_traversed_in_level { my ($path, $x1,$y1, $x2,$y2, $level) = @_; ### path_xyxy_is_traversed_in_level(): "$x1,$y1, $x2,$y2" my $arms = $path->arms_count; my $n_limit; if (defined $level) { $n_limit = 5**$level; } foreach my $n1 ($path->xy_to_n_list($x1,$y1)) { next if defined $n_limit && $n1 >= $n_limit; foreach my $n2 ($path->xy_to_n_list($x2,$y2)) { next if defined $n_limit && $n2 >= $n_limit; if (abs($n1-$n2) == $arms) { ### yes: "$n1 to $n2" return 1; } } } ### no ... return 0; } my $path = Math::PlanePath::R5DragonCurve->new; #------------------------------------------------------------------------------ # B { # POD samples my @want = (2, 10, 34, 106, 322, 970, 2914); foreach my $k (0 .. $#want) { my $got = B_from_path($path,$k); my $want = $want[$k]; ok ($got,$want); } } { # B[k] = 4*R[k] + 2*U[k] foreach my $k (0 .. 10) { my $r = R_from_path($path,$k); my $u = U_from_path($path,$k); my $b = B_from_path($path,$k+1); ok (4*$r+2*$u,$b); } } { # B[k+2] = 4*B[k+1] - 3*B[k] foreach my $k (0 .. 10) { my $b0 = B_from_path($path,$k); my $b1 = B_from_path($path,$k+1); my $got = 4*$b1 - 3*$b0; my $want = B_from_path($path,$k+2); ok ($got,$want); } } { # B[k] = 4*3^k - 2 foreach my $k (0 .. 10) { my $want = b_from_path($path,$k); my $got = 4*3**$k - 2; ok ($got,$want); } } #------------------------------------------------------------------------------ # R { # R[k] = B[k]/2 my $sum = 1; foreach my $k (0 .. 8) { my $b = B_from_path($path,$k); my $r = R_from_path($path,$k); ok ($r,$b/2); } } { # POD samples my @want = (1,5,17,53); foreach my $k (0 .. $#want) { my $got = R_from_path($path,$k); my $want = $want[$k]; ok ($got,$want); } } { # R[k+1] = 2*R[k] + U[k] foreach my $k (1 .. 8) { my $r0 = R_from_path($path,$k); my $r1 = R_from_path($path,$k+1); my $u = R_from_path($path,$k); ok (2*$r0+$u, $r1); } } #------------------------------------------------------------------------------ # Area sub A_recurrence { my ($k) = @_; if ($k <= 0) { return 0; } if ($k == 1) { return 0; } if ($k == 2) { return 4; } if ($k == 3) { return 36; } return (9*A_recurrence($k-1) - 23*A_recurrence($k-2) + 15*A_recurrence($k-3)); } BEGIN { memoize('A_recurrence') } { # A[k] = (2*5^k - B[k])/4 foreach my $k (0 .. 8) { my $b = B_from_path($path,$k); ### $b my $got = (2*5**$k - $b)/4; my $want = A_from_path($path,$k); ok ($got,$want); } } { # A[k] recurrence foreach my $k (0 .. 8) { my $n_limit = 5**$k; my $got = A_recurrence($k); my $want = A_from_path($path,$k); ok ($got,$want, "k=$k"); } } { # A[k] = (5^k - 2*3^k + 1)/2 foreach my $k (0 .. 8) { my $got = (5**$k - 2*3**$k + 1)/2; my $want = A_from_path($path,$k); ok ($got,$want); } } #------------------------------------------------------------------------------ # boundary lengths sub B_from_path { my ($path, $k) = @_; my $n_limit = 5**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit); return scalar(@$points); } BEGIN { memoize('B_from_path') } sub L_from_path { my ($path, $k) = @_; my $n_limit = 5**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit, side => 'left'); return scalar(@$points) - 1; } BEGIN { memoize('L_from_path') } sub R_from_path { my ($path, $k) = @_; my $n_limit = 5**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit, side => 'right'); return scalar(@$points) - 1; } BEGIN { memoize('R_from_path') } sub U_from_path { my ($path, $k) = @_; my $n_limit = 5**$k; my ($x,$y) = $path->n_to_xy(3*$n_limit); my ($to_x,$to_y) = $path->n_to_xy(0); my $points = MyOEIS::path_boundary_points_ft($path, 5*$n_limit, $x,$y, $to_x,$to_y, dir => 1); return scalar(@$points) - 1; } BEGIN { memoize('U_from_path') } sub A_from_path { my ($path, $k) = @_; return MyOEIS::path_enclosed_area($path, 5**$k); } BEGIN { memoize('A_from_path') } # #------------------------------------------------------------------------------ # # U # # { # # POD samples # my @want = (3, 6, 8, 12, 20, 32, 52, 88, 148, 248, 420, 712, 1204, 2040); # foreach my $k (0 .. $#want) { # my $got = U_from_path($path,$k); # my $want = $want[$k]; # ok ($got,$want); # } # } # { # # U[k+1] = U[k] + V[k] # # foreach my $k (0 .. 10) { # my $u = U_from_path($path,$k); # my $v = V_from_path($path,$k); # my $got = $u + $v; # my $want = U_from_path($path,$k+1); # ok ($got,$want); # } # } # { # # U[k+1] = U[k] + L[k] k>=1 # foreach my $k (1 .. 10) { # my $u = U_from_path($path,$k); # my $l = L_from_path($path,$k); # my $got = $u + $l; # my $want = U_from_path($path,$k+1); # ok ($got,$want); # } # } # { # # U[k+4] = 2*U[k+3] - U[k+2] + 2*U[k+1] - 2*U[k] for k >= 1 # # foreach my $k (1 .. 10) { # my $u0 = U_from_path($path,$k); # my $u1 = U_from_path($path,$k+1); # my $u2 = U_from_path($path,$k+2); # my $u3 = U_from_path($path,$k+3); # my $got = 2*$u3 - $u2 + 2*$u1 - 2*$u0; # my $want = U_from_path($path,$k+4); # ok ($got,$want); # } # } # { # # U[k] = L[k+2] - R[k] # foreach my $k (0 .. 10) { # my $l = L_from_path($path,$k+2); # my $r = R_from_path($path,$k); # my $got = $l - $r; # my $want = U_from_path($path,$k); # ok ($got,$want); # } # } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/slow/ComplexMinus-slow.t0000644000175000017500000000546312302552226017126 0ustar gggg#!/usr/bin/perl -w # Copyright 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min','max'; use Test; plan tests => 218; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use lib 'xt'; use MyOEIS; # uncomment this to run the ### lines # use Smart::Comments; use Math::PlanePath::ComplexMinus; #------------------------------------------------------------------------------ # figure boundary { # _UNDOCUMENTED_level_to_figure_boundary() foreach my $realpart (1 .. 10) { my $path = Math::PlanePath::ComplexMinus->new (realpart => $realpart); my $norm = $realpart*$realpart + 1; foreach my $level (0 .. 14) { my $n_level_end = $norm**$level - 1; last if $n_level_end > 10_000; my $got = $path->_UNDOCUMENTED_level_to_figure_boundary($level); my $want = path_n_to_figure_boundary($path, $n_level_end); ok ($got, $want, "_UNDOCUMENTED_level_to_figure_boundary() realpart=$realpart level=$level n_level_end=$n_level_end"); ### $got ### $want } } } # Return the boundary of unit squares at Nstart to N inclusive. sub path_n_to_figure_boundary { my ($path, $n) = @_; ### path_n_to_figure_boundary(): $n my $boundary = 4; foreach my $n ($path->n_start() .. $n-1) { ### "n=$n dboundary=".(path_n_to_dboundary($path,$n)) $boundary += path_n_to_dboundary($path,$n); } return $boundary; } BEGIN { my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); # return the change in figure boundary from N to N+1 sub path_n_to_dboundary { my ($path, $n) = @_; $n += 1; my ($x,$y) = $path->n_to_xy($n) or do { if ($n == $path->n_start - 1) { return 4; } else { return undef; } }; ### N+1 at: "n=$n xy=$x,$y" my $dboundary = 4; foreach my $i (0 .. $#dir4_to_dx) { my $an = $path->xy_to_n($x+$dir4_to_dx[$i], $y+$dir4_to_dy[$i]); ### consider: "xy=".($x+$dir4_to_dx[$i]).",".($y+$dir4_to_dy[$i])." is an=".($an||'false') $dboundary -= 2*(defined $an && $an < $n); } ### $dboundary return $dboundary; } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/slow/DragonCurve-slow.t0000644000175000017500000003621612321135263016721 0ustar gggg#!/usr/bin/perl -w # Copyright 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min','max'; use Test; plan tests => 218; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use lib 'xt'; use MyOEIS; # uncomment this to run the ### lines # use Smart::Comments; use Memoize; use Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new; my $midpath = Math::PlanePath::DragonMidpoint->new; #------------------------------------------------------------------------------ # MB = midpoint square figures boundary { my @want = (4, 6, 10, 18, 30, 50, 86, 146, 246, 418, 710, 1202); # per POD foreach my $k (0 .. $#want) { my $got = MB_from_path($path,$k); ok ($want[$k],$got); } } { # MB[k] = B[k] + 4 foreach my $k (0 .. 10) { my $mb = MB_from_path($path,$k); my $b2 = B_from_path($path,$k) + 4; ok ($mb,$b2, "k=$k"); } } sub MB_from_path { my ($path, $k) = @_; return MyOEIS::path_n_to_figure_boundary($midpath, 2**$k-1); } BEGIN { memoize('MB_from_path'); } #------------------------------------------------------------------------------ # P = points visited ok ($path->_UNDOCUMENTED_level_to_visited(4), 16); ok ($path->_UNDOCUMENTED_level_to_visited(5), 29); ok ($path->_UNDOCUMENTED_level_to_visited(6), 54); { foreach my $k (0 .. 10) { my $got = $path->_UNDOCUMENTED_level_to_visited($k); my $want = P_from_path($path,$k); ok ($got,$want, "k=$k"); } } sub P_from_path { my ($path, $k) = @_; return MyOEIS::path_n_to_visited($path, 2**$k); } BEGIN { memoize('P_from_path'); } #------------------------------------------------------------------------------ # RU = right U { foreach my $k (0 .. 10) { my $got = $path->_UNDOCUMENTED_level_to_u_right_line_boundary($k); my $want = RU_from_path($path,$k); ok ($got,$want); } } sub RU_from_path { my ($path, $k) = @_; return MyOEIS::path_boundary_length($path, 3 * 2**$k, side => 'right'); } BEGIN { memoize('RU_from_path'); } #------------------------------------------------------------------------------ # BU = total U { foreach my $k (0 .. 10) { my $got = $path->_UNDOCUMENTED_level_to_u_line_boundary($k); my $want = BU_from_path($path,$k); ok ($got,$want); } } sub BU_from_path { my ($path, $k) = @_; return MyOEIS::path_boundary_length($path, 3 * 2**$k); } BEGIN { memoize('BU_from_path'); } #------------------------------------------------------------------------------ # U { foreach my $k (0 .. 10) { my $got = $path->_UNDOCUMENTED_level_to_u_left_line_boundary($k); my $want = U_from_path($path,$k); ok ($got,$want); } } { # POD samples my @want = (3, 6, 8, 12, 20, 32, 52, 88, 148, 248, 420, 712, 1204, 2040); foreach my $k (0 .. $#want) { my $got = U_from_path($path,$k); my $want = $want[$k]; ok ($got,$want); } } { # U[k+1] = U[k] + V[k] foreach my $k (0 .. 10) { my $u = U_from_path($path,$k); my $v = V_from_path($path,$k); my $got = $u + $v; my $want = U_from_path($path,$k+1); ok ($got,$want); } } { # U[k+1] = U[k] + L[k] k>=1 foreach my $k (1 .. 10) { my $u = U_from_path($path,$k); my $l = L_from_path($path,$k); my $got = $u + $l; my $want = U_from_path($path,$k+1); ok ($got,$want); } } { # U[k+4] = 2*U[k+3] - U[k+2] + 2*U[k+1] - 2*U[k] for k >= 1 foreach my $k (1 .. 10) { my $u0 = U_from_path($path,$k); my $u1 = U_from_path($path,$k+1); my $u2 = U_from_path($path,$k+2); my $u3 = U_from_path($path,$k+3); my $got = 2*$u3 - $u2 + 2*$u1 - 2*$u0; my $want = U_from_path($path,$k+4); ok ($got,$want); } } { # U[k] = L[k+2] - R[k] foreach my $k (0 .. 10) { my $l = L_from_path($path,$k+2); my $r = R_from_path($path,$k); my $got = $l - $r; my $want = U_from_path($path,$k); ok ($got,$want); } } #------------------------------------------------------------------------------ # B { MyOEIS::poly_parse('1 - 2*x^3'); my $num = MyOEIS::poly_parse('2 + 2*x^2'); my $den = MyOEIS::poly_parse('1 - x - 2*x^3')*MyOEIS::poly_parse('1-x'); print MyOEIS::poly_parse('2 + 2*x^2'),"\n"; print MyOEIS::poly_parse('1 - x - 2*x^3'),"\n"; print MyOEIS::poly_parse('1-x'),"\n"; print $den,"\n"; exit; } { # _UNDOCUMENTED_level_to_line_boundary() foreach my $k (0 .. 14) { my $got = $path->_UNDOCUMENTED_level_to_line_boundary($k); my $want = B_from_path($path,$k); ok ($got, $want, "_UNDOCUMENTED_level_to_line_boundary() k=$k"); } } { # POD samples my @want = (2, 4, 8, 16, 28, 48, 84, 144, 244, 416, 708, 1200, 2036); foreach my $k (0 .. $#want) { my $got = B_from_path($path,$k); my $want = $want[$k]; ok ($got,$want); } } { # B[k+4] = 2*B[k+3] - B[k+2] + 2*B[k+1] - 2*B[k] for k >= 0 foreach my $k (0 .. 10) { my $b0 = B_from_path($path,$k); my $b1 = B_from_path($path,$k+1); my $b2 = B_from_path($path,$k+2); my $b3 = B_from_path($path,$k+3); my $got = 2*$b3 - $b2 + 2*$b1 - 2*$b0; my $want = B_from_path($path,$k+4); ok ($got,$want); } } { # B[k] = L[k] + R[k] foreach my $k (0 .. 10) { my $l = L_from_path($path,$k); my $r = R_from_path($path,$k); my $got = $l + $r; my $want = B_from_path($path,$k); ok ($got,$want); } } #------------------------------------------------------------------------------ # S = Singles { # S[k] = 1 + B[k]/2 foreach my $k (0 .. 10) { my $got = 1 + B_from_path($path,$k)/2; my $want = MyOEIS::path_n_to_singles($path, 2**$k); ok ($got,$want); } } { # Single[N] = N+1 - 2*Doubled[N] points 0 to N inclusive my $n_start = $path->n_start; for (my $length = 0; $length < 128; $length++) { my $n_end = $n_start + $length; my $singles = MyOEIS::path_n_to_singles($path, $n_end); my $doubles = MyOEIS::path_n_to_doubles($path, $n_end); ### $n_start ### $n_end ### $singles ### $doubles my $got = $singles + 2*$doubles; ok ($got, $length+1); } } { # S[k] recurrence foreach my $k (0 .. 10) { my $got = S_recurrence($k); my $want = MyOEIS::path_n_to_singles($path, 2**$k); ok ($got,$want); } sub S_recurrence { my ($k) = @_; if ($k < 0) { die; } if ($k == 0) { return 2; } if ($k == 1) { return 3; } if ($k == 2) { return 5; } if ($k == 3) { return 9; } return (S_recurrence($k-1) + 2*S_recurrence($k-3)); } BEGIN { memoize('S_recurrence'); } } #------------------------------------------------------------------------------ # Doubles { foreach my $k (0 .. 10) { my $n_limit = 2**$k; my $got = $path->_UNDOCUMENTED_level_to_doubled_points($k); my $want = MyOEIS::path_n_to_doubles($path, $n_limit); ok ($got,$want); } } # Doubles[N] = Area[N] for all N { foreach my $k (0 .. 10) { my $n_limit = 2**$k; my $got = A_recurrence($k); my $want = MyOEIS::path_n_to_doubles($path, $n_limit); ok ($got,$want); } } #------------------------------------------------------------------------------ # L { # _UNDOCUMENTED_level_to_left_line_boundary() foreach my $k (0 .. 14) { my $got = $path->_UNDOCUMENTED_level_to_left_line_boundary($k); my $want = L_from_path($path,$k); ok ($got, $want, "_UNDOCUMENTED_level_to_left_line_boundary() k=$k"); } } { # POD samples my @want = (1, 2, 4, 8, 12, 20, 36, 60, 100, 172, 292, 492, 836, 1420); foreach my $k (0 .. $#want) { my $got = L_from_path($path,$k); my $want = $want[$k]; ok ($got,$want); } } { # L[k+1] = T[k] foreach my $k (0 .. 10) { my $l = L_from_path($path,$k+1); my $t = T_from_path($path,$k); ok ($l,$t); } } #------------------------------------------------------------------------------ # R { # _UNDOCUMENTED_level_to_right_line_boundary() foreach my $k (0 .. 14) { my $got = $path->_UNDOCUMENTED_level_to_right_line_boundary($k); my $want = R_from_path($path,$k); ok ($got, $want, "_UNDOCUMENTED_level_to_right_line_boundary() k=$k"); } } { # R[k] = L[k-1] + L[k-2] + ... + L[0] + 1 my $sum = 1; foreach my $k (0 .. 14) { my $r = R_from_path($path,$k); ok ($sum,$r); $sum += L_from_path($path,$k); } } { # POD samples my @want = (1, 2, 4, 8, 16, 28, 48, 84, 144, 244, 416, 708, 1200, 2036); foreach my $k (0 .. $#want) { my $got = R_from_path($path,$k); my $want = $want[$k]; ok ($got,$want); } } { # R[k+4] = 2*R[k+3] - R[k+2] + 2*R[k+1] - 2*R[k] for k >= 1 foreach my $k (1 .. 10) { my $r0 = R_from_path($path,$k); my $r1 = R_from_path($path,$k+1); my $r2 = R_from_path($path,$k+2); my $r3 = R_from_path($path,$k+3); my $got = 2*$r3 - $r2 + 2*$r1 - 2*$r0; my $want = R_from_path($path,$k+4); ok ($got,$want); } } { # R[k+1] = L[k] + R[k] foreach my $k (0 .. 10) { my $l = L_from_path($path,$k); my $r = R_from_path($path,$k); my $got = $l + $r; my $want = R_from_path($path,$k+1); ok ($got,$want); } } #------------------------------------------------------------------------------ # 4^k extents in the POD { foreach my $k (1 .. 12) { next unless $k & 1; my $n_end = 4**$k; my ($xmin,$ymin, $xmax,$ymax) = path_n_to_extents_rect($path,$n_end); $xmin < $xmax or die; $ymin < $ymax or die; my $wmin = $xmin; my $wmax = $xmax; my $lmin = $ymin; my $lmax = $ymax; foreach (-2 .. $k) { ($wmax,$wmin, $lmax,$lmin) = ($lmax,$lmin, -$wmin,-$wmax); } $wmin < $wmax or die; $lmin < $lmax or die; my ($f_lmin,$f_lmax, $f_wmin,$f_wmax) = formula_k_to_lw_extents($k); $f_wmin < $f_wmax or die; $f_lmin < $f_lmax or die; ### $k ### xy extents: "$xmin to $xmax $ymin to $ymax" ### lw extents: "$lmin to $lmax $wmin to $wmax" ### lw f exts : "$f_lmin to $f_lmax $f_wmin to $f_wmax" ok ($f_lmin, $lmin, "k=$k"); ok ($f_lmax, $lmax, "k=$k"); ok ($f_wmin, $wmin, "k=$k"); ok ($f_wmax, $wmax, "k=$k"); } } # return ($xmin,$xmax, $ymin,$ymax) sub formula_k_to_lw_extents { my ($k) = @_; my $lmax = ($k % 2 == 0 ? (7*2**$k - 4)/6 : (7*2**$k - 2)/6); my $lmin = ($k % 2 == 0 ? - (2**$k - 1)/3 : - (2**$k - 2)/3); my $wmax = ($k % 2 == 0 ? (2*2**$k - 2) / 3 : (2*2**$k - 1) / 3); my $wmin = $lmin; return ($lmin,$lmax, $wmin,$wmax); } # return ($xmin,$ymin, $xmax,$ymax) # which is rectangle containing all points n_start() to $n inclusive sub path_n_to_extents_rect { my ($path, $n) = @_; my $xmin = 0; my $xmax = 0; my $ymin = 0; my $ymax = 0; for my $i ($path->n_start .. $n) { my ($x,$y) = $path->n_to_xy($i); $xmin = min($xmin,$x); $xmax = max($xmax,$x); $ymin = min($ymin,$y); $ymax = max($ymax,$y); } return ($xmin,$ymin, $xmax,$ymax); } #------------------------------------------------------------------------------ # path calculations sub B_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit); return scalar(@$points); } BEGIN { memoize('B_from_path'); } sub L_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit, side => 'left'); return scalar(@$points) - 1; } BEGIN { memoize('L_from_path'); } sub R_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit, side => 'right'); return scalar(@$points) - 1; } BEGIN { memoize('R_from_path'); } sub T_from_path { my ($path, $k) = @_; # 2 to 4 my $n_limit = 2**$k; my ($x,$y) = $path->n_to_xy(2*$n_limit); my ($to_x,$to_y) = $path->n_to_xy(4*$n_limit); my $points = MyOEIS::path_boundary_points_ft($path, 4*$n_limit, $x,$y, $to_x,$to_y, dir => 2); return scalar(@$points) - 1; } BEGIN { memoize('T_from_path'); } sub U_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my ($x,$y) = $path->n_to_xy(3*$n_limit); my ($to_x,$to_y) = $path->n_to_xy(0); my $points = MyOEIS::path_boundary_points_ft($path, 3*$n_limit, $x,$y, $to_x,$to_y, dir => 1); return scalar(@$points) - 1; } BEGIN { memoize('U_from_path'); } sub V_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my ($x,$y) = $path->n_to_xy(6*$n_limit); my ($to_x,$to_y) = $path->n_to_xy(3*$n_limit); my $points = MyOEIS::path_boundary_points_ft($path, 8*$n_limit, $x,$y, $to_x,$to_y, dir => 0); return scalar(@$points) - 1; } BEGIN { memoize('V_from_path'); } sub A_from_path { my ($path, $k) = @_; return MyOEIS::path_enclosed_area($path, 2**$k); } BEGIN { memoize('A_from_path'); } #------------------------------------------------------------------------------ # Area sub A_recurrence { my ($k) = @_; if ($k <= 0) { return 0; } if ($k == 1) { return 0; } if ($k == 2) { return 0; } if ($k == 3) { return 0; } if ($k == 4) { return 1; } return (4*A_recurrence($k-1) - 5*A_recurrence($k-2) + 4*A_recurrence($k-3) - 6*A_recurrence($k-4) + 4*A_recurrence($k-5)); } memoize('A_from_path'); { # A[k] recurrence foreach my $k (0 .. 10) { my $got = A_recurrence($k); my $want = A_from_path($path,$k); ok ($got,$want); } } { # A[k] = 2^k - B[k]/2 foreach my $k (0 .. 10) { my $b = B_from_path($path,$k); my $got = 2**($k-1) - $b/4; my $want = A_from_path($path,$k); ok ($got,$want); } } #------------------------------------------------------------------------------ # subst eliminating U { # L[k+3]-R[k+1] = L[k+2]-R[k] + L[k] k >= 1 foreach my $k (1 .. 10) { my $lhs = L_from_path($path,$k+3) - R_from_path($path,$k+1); my $rhs = (L_from_path($path,$k+2) - R_from_path($path,$k) + L_from_path($path,$k)); ok ($lhs,$rhs); } } #------------------------------------------------------------------------------ # T { # T[k+1] = U[k] + R[k] foreach my $k (0 .. 10) { my $r = R_from_path($path,$k); my $u = U_from_path($path,$k); my $got = $r + $u; my $want = T_from_path($path,$k+1); ok ($got,$want, "k=$k"); } } #------------------------------------------------------------------------------ # V { # V[k+1] = T[k] foreach my $k (0 .. 10) { my $v = V_from_path($path,$k+1); my $t = T_from_path($path,$k); ok ($v,$t); } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/slow/HilbertCurve-slow.t0000644000175000017500000000561112342460401017071 0ustar gggg#!/usr/bin/perl -w # Copyright 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min','max'; use Test; plan tests => 87; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use lib 'xt'; use MyOEIS; use Memoize; # uncomment this to run the ### lines # use Smart::Comments; use Math::PlanePath::HilbertCurve; my $path = Math::PlanePath::HilbertCurve->new; #------------------------------------------------------------------------------ # count of segments by direction claimed in the POD { my %want = ('0,1' => [ 0, 1, 4, 19, 64, 271, 1024, 4159, 16384, ], # dir=1=N '1,0' => [ 0, 1, 5, 16, 71, 256, 1055, 4096, 16511, ], # dir=2=E '0,-1' => [ 0, 0, 4, 12, 64, 240, 1024, 4032, 16384, ], # dir=3=S '-1,0' => [ 0, 1, 2, 16, 56, 256, 992, 4096, 16256, ], # dir=4=W ); my %count = ('0,1' => 0, '1,0' => 0, '0,-1' => 0, '-1,0' => 0); my $n = 0; foreach my $k (0 .. $#{$want{'0,1'}}) { my $n_end = 4**$k-1; while ($n < $n_end) { my ($dx,$dy) = $path->n_to_dxdy($n++); $count{"$dx,$dy"}++; ### count: "n=$n $dx,$dy" } ### count now: "$count{'0,1'}, $count{'1,0'} $count{'0,-1'} $count{'-1,0'}" foreach my $dxdy (keys %want) { my $pod = $want{$dxdy}->[$k]; my $count = $count{$dxdy}; ok ($pod, $count, "$dxdy samples"); my $func = c_func($dxdy,$k); ok ($func, $count, "$dxdy func=$func count=$count"); } } } sub c_func { my ($dxdy, $k) = @_; if ($dxdy eq '0,1') { # dir=1=N if ($k == 0) { return 0; } if ($k % 2) { return 4**($k-1) + 2**($k-1) - 1; } return 4**($k-1); } if ($dxdy eq '1,0') { # dir=2=E if ($k == 0) { return 0; } if ($k % 2) { return 4**($k-1); } return 4**($k-1) + 2**($k-1) - 1; } if ($dxdy eq '0,-1') { # dir=3=S if ($k == 0) { return 0; } if ($k % 2) { return 4**($k-1) - 2**($k-1); } return 4**($k-1); } if ($dxdy eq '-1,0') { # dir=4=W if ($k == 0) { return 0; } if ($k % 2) { return 4**($k-1); } return 4**($k-1) - 2**($k-1); } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/slow/NumSeq-PlanePathCoord.t0000644000175000017500000022005413774325543017603 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2018, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; use Data::Float 'pos_infinity'; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::Base::Generic 'is_infinite'; # uncomment this to run the ### lines # use Smart::Comments '###'; my $test_count = (tests => 1913)[1]; plan tests => $test_count; if (! eval { require Math::NumSeq; 1 }) { MyTestHelpers::diag ('skip due to Math::NumSeq not available -- ',$@); foreach (1 .. $test_count) { skip ('due to no Math::NumSeq', 1, 1); } exit 0; } require Math::NumSeq::PlanePathCoord; sub want_planepath { my ($planepath) = @_; # return 0 unless $planepath =~ /HTree/; # return 0 unless $planepath =~ /DiagonalRationals/; # return 0 unless $planepath =~ /FactorRationals/; # return 0 unless $planepath =~ /MultipleRings/; # return 0 unless $planepath =~ /Anvil/; return 1; } sub want_coordinate { my ($type) = @_; # return 0 unless $type =~ /sumabs|absdiff/i; # return 0 unless $type =~ /d[XY]/; # return 0 unless $type =~ /^dAbsDiff/; # return 0 unless $type =~ /TR/; # return 0 unless $type =~ /RSquared|Radius/; # return 0 unless $type =~ /Left|Right|LSR|SLR|SRL/; # return 0 unless $type =~ /Dir4|Dir6/; # return 0 unless $type =~ /LeafDistance/; # return 0 unless $type =~ /Min|Max/; # return 0 unless $type =~ /dSum|dDiffXY|Absd|d[XY]/; # return 0 unless $type =~ /^(X|Y|Sum|DiffXY|dX|dY|AbsdX|AbsdY|dSum|dDiffXY|Dir4)$/; # return 0 unless $type =~ /^(X|Y|Sum|DiffXY|DiffYX)$/; return 0 unless $type =~ /^(Left|Right|Straight|S..|.S.)$/; return 1; } #------------------------------------------------------------------------------ # characteristic() foreach my $elem (['increasing',0 ], # default SquareSpiral X not monotonic ['non_decreasing', 1, planepath => 'Hypot', coordinate_type => 'Radius' ], ['non_decreasing', 1, planepath => 'Hypot', coordinate_type => 'Radius' ], ['non_decreasing', 1, planepath => 'HypotOctant', coordinate_type => 'Radius' ], ['non_decreasing', 1, planepath => 'HypotOctant', coordinate_type => 'RSquared' ], ['smaller', 1, planepath => 'SquareSpiral', coordinate_type => 'X' ], ['smaller', 1, planepath => 'SquareSpiral', coordinate_type => 'RSquared' ], ['smaller', 0, planepath => 'MultipleRings,step=0', coordinate_type => 'RSquared' ], ['smaller', 0, planepath => 'MultipleRings,step=1', coordinate_type => 'RSquared' ], ['smaller', 1, planepath => 'MultipleRings,step=2', coordinate_type => 'RSquared' ], ['increasing', 1, planepath => 'TheodorusSpiral', coordinate_type => 'Radius' ], ['increasing', 1, planepath => 'TheodorusSpiral', coordinate_type => 'RSquared' ], ['non_decreasing', 1, planepath => 'TheodorusSpiral', coordinate_type => 'Radius' ], ['non_decreasing', 1, planepath => 'TheodorusSpiral', coordinate_type => 'RSquared' ], ['smaller', 1, planepath => 'TheodorusSpiral', coordinate_type => 'Radius' ], ['smaller', 0, planepath => 'TheodorusSpiral', coordinate_type => 'RSquared' ], ['increasing', 1, planepath => 'VogelFloret', coordinate_type => 'Radius' ], ['increasing', 1, planepath => 'VogelFloret', coordinate_type => 'RSquared' ], ['non_decreasing', 1, planepath => 'VogelFloret', coordinate_type => 'Radius' ], ['non_decreasing', 1, planepath => 'VogelFloret', coordinate_type => 'RSquared' ], ['smaller', 1, planepath => 'VogelFloret', coordinate_type => 'Radius' ], ['smaller', 0, planepath => 'VogelFloret', coordinate_type => 'RSquared' ], ['increasing', 1, planepath => 'SacksSpiral', coordinate_type => 'Radius' ], ['increasing', 1, planepath => 'SacksSpiral', coordinate_type => 'RSquared' ], ['non_decreasing', 1, planepath => 'SacksSpiral', coordinate_type => 'Radius' ], ['non_decreasing', 1, planepath => 'SacksSpiral', coordinate_type => 'RSquared' ], ['smaller', 1, planepath => 'SacksSpiral', coordinate_type => 'Radius' ], ['smaller', 0, planepath => 'SacksSpiral', coordinate_type => 'RSquared' ], ) { my ($key, $want, @parameters) = @$elem; my $seq = Math::NumSeq::PlanePathCoord->new (@parameters); ok ($seq->characteristic($key) ? 1 : 0, $want, "characteristic($key) on ".join(', ',@parameters)); } #------------------------------------------------------------------------------ # values_min(), values_max() foreach my $elem ([undef,undef, planepath => 'SquareSpiral' ], # default coordinate_type=>X [0,undef, planepath => 'SquareSpiral', coordinate_type => 'Radius' ], [0,undef, planepath => 'SquareSpiral', coordinate_type => 'RSquared' ], [0,undef, planepath => 'HilbertCurve', coordinate_type => 'X' ], [0,undef, planepath => 'HilbertCurve', coordinate_type => 'Y' ], [0,undef, planepath => 'HilbertCurve', coordinate_type => 'Sum' ], [0,undef, planepath => 'HilbertCurve', coordinate_type => 'Product' ], [undef,undef, planepath => 'CellularRule54', coordinate_type => 'X' ], [0,undef, planepath => 'CellularRule54', coordinate_type => 'Y' ], [0,undef, planepath => 'CellularRule54', coordinate_type => 'Sum' ], [undef,undef, planepath => 'CellularRule54', coordinate_type => 'Product' ], [0,undef, planepath => 'CellularRule54', coordinate_type => 'Radius' ], [0,undef, planepath => 'CellularRule54', coordinate_type => 'RSquared' ], [undef,0, planepath => 'CellularRule54', coordinate_type => 'DiffXY' ], [0,undef, planepath => 'CellularRule54', coordinate_type => 'DiffYX' ], [0,undef, planepath => 'CellularRule54', coordinate_type => 'AbsDiff' ], [undef,undef, planepath => 'CellularRule190', coordinate_type => 'X' ], [0,undef, planepath => 'CellularRule190', coordinate_type => 'Y' ], [0,undef, planepath => 'CellularRule190', coordinate_type => 'Sum' ], [undef,undef, planepath => 'CellularRule190', coordinate_type => 'Product' ], [0,undef, planepath => 'CellularRule190', coordinate_type => 'Radius' ], [0,undef, planepath => 'CellularRule190', coordinate_type => 'RSquared' ], [undef,undef, planepath => 'UlamWarburton', coordinate_type => 'X' ], [undef,undef, planepath => 'UlamWarburton', coordinate_type => 'Y' ], [undef,undef, planepath => 'UlamWarburton', coordinate_type => 'Sum' ], [undef,undef, planepath => 'UlamWarburton', coordinate_type => 'Product' ], [0,undef, planepath => 'UlamWarburton', coordinate_type => 'Radius' ], [0,undef, planepath => 'UlamWarburton', coordinate_type => 'RSquared' ], [0,undef, planepath => 'UlamWarburtonQuarter', coordinate_type => 'X' ], [0,undef, planepath => 'UlamWarburtonQuarter', coordinate_type => 'Y' ], [0,undef, planepath => 'UlamWarburtonQuarter', coordinate_type => 'Sum' ], [0,undef, planepath => 'UlamWarburtonQuarter', coordinate_type => 'Product' ], [0,undef, planepath => 'UlamWarburtonQuarter', coordinate_type => 'Radius' ], [0,undef, planepath => 'UlamWarburtonQuarter', coordinate_type => 'RSquared' ], [3,undef, planepath => 'PythagoreanTree', coordinate_type => 'X' ], [4,undef, planepath => 'PythagoreanTree', coordinate_type => 'Y' ], [7,undef, planepath => 'PythagoreanTree', coordinate_type => 'Sum' ], [3*4,undef, planepath => 'PythagoreanTree', coordinate_type => 'Product' ], [5,undef, planepath => 'PythagoreanTree', coordinate_type => 'Radius' ], [25,undef, planepath => 'PythagoreanTree', coordinate_type => 'RSquared' ], [undef,undef, planepath => 'PythagoreanTree', coordinate_type => 'DiffXY' ], [undef,undef, planepath => 'PythagoreanTree', coordinate_type => 'DiffYX' ], [1,undef, planepath => 'PythagoreanTree', coordinate_type => 'AbsDiff' ], [2,undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'X' ], [1,undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'Y' ], [3,undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'Sum' ], [2,undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'Product' ], #[sqrt(5),undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'Radius' ], [5,undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'RSquared' ], [1,undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'DiffXY' ], [undef,-1, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'DiffYX' ], [1,undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'AbsDiff' ], [0,undef, planepath => 'HypotOctant', coordinate_type => 'X' ], [0,undef, planepath => 'HypotOctant', coordinate_type => 'Y' ], [0,undef, planepath => 'HypotOctant', coordinate_type => 'Sum' ], [0,undef, planepath => 'HypotOctant', coordinate_type => 'Product' ], [0,undef, planepath => 'HypotOctant', coordinate_type => 'Radius' ], [0,undef, planepath => 'HypotOctant', coordinate_type => 'RSquared' ], [0,undef, planepath => 'HypotOctant', coordinate_type => 'DiffXY' ], [undef,0, planepath => 'HypotOctant', coordinate_type => 'DiffYX' ], [0,undef, planepath => 'HypotOctant', coordinate_type => 'AbsDiff' ], [2,undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'X' ], [1,undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'Y' ], [3,undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'Sum' ], [2,undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'Product' ], # [sqrt(5),undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'Radius' ], [5,undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'RSquared' ], [1,undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'DiffXY' ], [undef,-1, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'DiffYX' ], [1,undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'AbsDiff' ], [1,undef, planepath => 'DivisibleColumns', coordinate_type => 'X' ], [1,undef, planepath => 'DivisibleColumns', coordinate_type => 'Y' ], [2,undef, planepath => 'DivisibleColumns', coordinate_type => 'Sum' ], [1,undef, planepath => 'DivisibleColumns', coordinate_type => 'Product' ], # [sqrt(2),undef, planepath => 'DivisibleColumns', coordinate_type => 'Radius' ], [2,undef, planepath => 'DivisibleColumns', coordinate_type => 'RSquared' ], [0,undef, planepath => 'DivisibleColumns', coordinate_type => 'DiffXY' ], [undef,0, planepath => 'DivisibleColumns', coordinate_type => 'DiffYX' ], [0,undef, planepath => 'DivisibleColumns', coordinate_type => 'AbsDiff' ], [1,undef, planepath => 'CoprimeColumns', coordinate_type => 'X' ], [1,undef, planepath => 'CoprimeColumns', coordinate_type => 'Y' ], [2,undef, planepath => 'CoprimeColumns', coordinate_type => 'Sum' ], [1,undef, planepath => 'CoprimeColumns', coordinate_type => 'Product' ], # [sqrt(2),undef, planepath => 'CoprimeColumns', coordinate_type => 'Radius' ], [2,undef, planepath => 'CoprimeColumns', coordinate_type => 'RSquared' ], [0,undef, planepath => 'CoprimeColumns', coordinate_type => 'DiffXY' ], [undef,0, planepath => 'CoprimeColumns', coordinate_type => 'DiffYX' ], [0,undef, planepath => 'CoprimeColumns', coordinate_type => 'AbsDiff' ], [1,undef, planepath => 'RationalsTree', coordinate_type => 'X' ], [1,undef, planepath => 'RationalsTree', coordinate_type => 'Y' ], # X>=1 and Y>=1 always so Sum>=2 [2,undef, planepath => 'RationalsTree', coordinate_type => 'Sum' ], [1,undef, planepath => 'RationalsTree', coordinate_type => 'Product' ], # [sqrt(2),undef, planepath => 'RationalsTree', coordinate_type => 'Radius' ], [2,undef, planepath => 'RationalsTree', coordinate_type => 'RSquared' ], # whole first quadrant so diff positive and negative [undef,undef, planepath => 'RationalsTree', coordinate_type => 'DiffXY' ], [undef,undef, planepath => 'RationalsTree', coordinate_type => 'DiffYX' ], [0,undef, planepath => 'RationalsTree', coordinate_type => 'AbsDiff' ], [0,undef, planepath => 'QuadricCurve', coordinate_type => 'X' ], [undef,undef, planepath => 'QuadricCurve', coordinate_type => 'Y' ], [0,undef, planepath => 'QuadricCurve', coordinate_type => 'Sum' ], [undef,undef, planepath => 'QuadricCurve', coordinate_type => 'Product' ], [0,undef, planepath => 'QuadricCurve', coordinate_type => 'Radius' ], [0,undef, planepath => 'QuadricCurve', coordinate_type => 'RSquared' ], [0,undef, planepath => 'QuadricCurve', coordinate_type => 'DiffXY' ], [undef,0, planepath => 'QuadricCurve', coordinate_type => 'DiffYX' ], [0,undef, planepath => 'QuadricCurve', coordinate_type => 'AbsDiff' ], [0,5, planepath => 'Rows,width=6', coordinate_type => 'X' ], [0,undef, planepath => 'Rows,width=6', coordinate_type => 'Y' ], [0,undef, planepath => 'Rows,width=6', coordinate_type => 'Sum' ], [0,undef, planepath => 'Rows,width=6', coordinate_type => 'Product' ], [0,undef, planepath => 'Rows,width=6', coordinate_type => 'Radius' ], [0,undef, planepath => 'Rows,width=6', coordinate_type => 'RSquared' ], [undef,5, planepath => 'Rows,width=6', coordinate_type => 'DiffXY' ], [-5,undef, planepath => 'Rows,width=6', coordinate_type => 'DiffYX' ], [0,undef, planepath => 'Rows,width=6', coordinate_type => 'AbsDiff' ], [0,undef, planepath => 'Columns,height=6', coordinate_type => 'X' ], [0,5, planepath => 'Columns,height=6', coordinate_type => 'Y' ], [0,undef, planepath => 'Columns,height=6', coordinate_type => 'Sum' ], [0,undef, planepath => 'Columns,height=6', coordinate_type => 'Product' ], [0,undef, planepath => 'Columns,height=6', coordinate_type => 'Radius' ], [0,undef, planepath => 'Columns,height=6', coordinate_type => 'RSquared' ], [-5,undef, planepath => 'Columns,height=6', coordinate_type => 'DiffXY' ], [undef,5, planepath => 'Columns,height=6', coordinate_type => 'DiffYX' ], [0,undef, planepath => 'Columns,height=6', coordinate_type => 'AbsDiff' ], # step=0 vertical on Y axis only [0,0, planepath=>'PyramidRows,step=0', coordinate_type => 'X' ], [0,undef, planepath=>'PyramidRows,step=0', coordinate_type => 'Y' ], [0,undef, planepath=>'PyramidRows,step=0', coordinate_type => 'Sum' ], [0,0, planepath=>'PyramidRows,step=0', coordinate_type => 'Product' ], [0,undef, planepath=>'PyramidRows,step=0', coordinate_type => 'Radius' ], [0,undef, planepath=>'PyramidRows,step=0', coordinate_type => 'RSquared' ], [undef,0, planepath=>'PyramidRows,step=0', coordinate_type => 'DiffXY' ], [0,undef, planepath=>'PyramidRows,step=0', coordinate_type => 'DiffYX' ], [0,undef, planepath=>'PyramidRows,step=0', coordinate_type => 'AbsDiff' ], [0,undef, planepath=>'PyramidRows,step=1', coordinate_type => 'X' ], [0,undef, planepath=>'PyramidRows,step=1', coordinate_type => 'Y' ], [0,undef, planepath=>'PyramidRows,step=1', coordinate_type => 'Sum' ], [0,undef, planepath=>'PyramidRows,step=1', coordinate_type => 'Product' ], [0,undef, planepath=>'PyramidRows,step=1', coordinate_type => 'Radius' ], [0,undef, planepath=>'PyramidRows,step=1', coordinate_type => 'RSquared' ], [undef,0, planepath=>'PyramidRows,step=1', coordinate_type => 'DiffXY' ], [0,undef, planepath=>'PyramidRows,step=1', coordinate_type => 'DiffYX' ], [0,undef, planepath=>'PyramidRows,step=1', coordinate_type => 'AbsDiff' ], [undef,undef, planepath=>'PyramidRows,step=2', coordinate_type=>'X' ], [0,undef, planepath=>'PyramidRows,step=2', coordinate_type=>'Y' ], [0,undef, planepath=>'PyramidRows,step=2', coordinate_type=>'Sum' ], [undef,undef, planepath=>'PyramidRows,step=2', coordinate_type=>'Product' ], [0,undef, planepath=>'PyramidRows,step=2', coordinate_type=>'Radius' ], [0,undef, planepath=>'PyramidRows,step=2', coordinate_type=>'RSquared'], [undef,0, planepath=>'PyramidRows,step=2', coordinate_type=>'DiffXY' ], [0,undef, planepath=>'PyramidRows,step=2', coordinate_type=>'DiffYX' ], [0,undef, planepath=>'PyramidRows,step=2', coordinate_type=>'AbsDiff' ], [undef,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'X' ], [0,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'Y' ], [0,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'Sum' ], [undef,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'Product' ], [0,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'Radius' ], [0,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'RSquared' ], [undef,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'DiffXY' ], [undef,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'DiffYX' ], [0,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'AbsDiff' ], # Y <= X-1, so X-Y >= 1 # Y-X <= -1 [1,undef, planepath => 'SierpinskiCurve', coordinate_type => 'DiffXY' ], [undef,-1, planepath => 'SierpinskiCurve', coordinate_type => 'DiffYX' ], [1,undef, planepath => 'SierpinskiCurve', coordinate_type => 'AbsDiff' ], [0,undef, planepath => 'HIndexing', coordinate_type => 'X' ], [0,undef, planepath => 'HIndexing', coordinate_type => 'Y' ], [0,undef, planepath => 'HIndexing', coordinate_type => 'Sum' ], [0,undef, planepath => 'HIndexing', coordinate_type => 'Product' ], [0,undef, planepath => 'HIndexing', coordinate_type => 'Radius' ], [0,undef, planepath => 'HIndexing', coordinate_type => 'RSquared' ], [undef,0, planepath => 'HIndexing', coordinate_type => 'DiffXY' ], [0,undef, planepath => 'HIndexing', coordinate_type => 'DiffYX' ], [0,undef, planepath => 'HIndexing', coordinate_type => 'AbsDiff' ], # right line [0,undef, planepath=>'CellularRule,rule=16', coordinate_type=>'X' ], [0,undef, planepath=>'CellularRule,rule=16', coordinate_type=>'Y' ], [0,undef, planepath=>'CellularRule,rule=16', coordinate_type=>'Sum' ], [0,undef, planepath=>'CellularRule,rule=16', coordinate_type=>'Product' ], [0,undef, planepath=>'CellularRule,rule=16', coordinate_type=>'Radius' ], [0,undef, planepath=>'CellularRule,rule=16', coordinate_type=>'RSquared' ], [0,0, planepath=>'CellularRule,rule=16', coordinate_type=>'DiffXY' ], [0,0, planepath=>'CellularRule,rule=16', coordinate_type=>'DiffYX' ], [0,0, planepath=>'CellularRule,rule=16', coordinate_type=>'AbsDiff' ], # centre line Y axis only [0,0, planepath=>'CellularRule,rule=4', coordinate_type => 'X' ], [0,undef, planepath=>'CellularRule,rule=4', coordinate_type => 'Y' ], [0,undef, planepath=>'CellularRule,rule=4', coordinate_type => 'Sum' ], [0,0, planepath=>'CellularRule,rule=4', coordinate_type => 'Product' ], [0,undef, planepath=>'CellularRule,rule=4', coordinate_type => 'Radius' ], [0,undef, planepath=>'CellularRule,rule=4', coordinate_type => 'RSquared' ], [undef,0, planepath=>'CellularRule,rule=4', coordinate_type => 'DiffXY' ], [0,undef, planepath=>'CellularRule,rule=4', coordinate_type => 'DiffYX' ], [0,undef, planepath=>'CellularRule,rule=4', coordinate_type => 'AbsDiff' ], # left line [undef,0, planepath=>'CellularRule,rule=2', coordinate_type=>'X' ], [0,undef, planepath=>'CellularRule,rule=2', coordinate_type=>'Y' ], [0,0, planepath=>'CellularRule,rule=2', coordinate_type=>'Sum' ], [undef,0, planepath=>'CellularRule,rule=2', coordinate_type=>'Product' ], [0,undef, planepath=>'CellularRule,rule=2', coordinate_type=>'Radius' ], [0,undef, planepath=>'CellularRule,rule=2', coordinate_type=>'RSquared' ], [undef,0, planepath=>'CellularRule,rule=2', coordinate_type=>'DiffXY' ], [0,undef, planepath=>'CellularRule,rule=2', coordinate_type=>'DiffYX' ], [0,undef, planepath=>'CellularRule,rule=2', coordinate_type=>'AbsDiff' ], # left solid [undef,0, planepath=>'CellularRule,rule=206', coordinate_type=>'X' ], [0,undef, planepath=>'CellularRule,rule=206', coordinate_type=>'Y' ], [0,undef, planepath=>'CellularRule,rule=206', coordinate_type=>'Sum' ], [undef,0, planepath=>'CellularRule,rule=206', coordinate_type=>'Product' ], [0,undef, planepath=>'CellularRule,rule=206', coordinate_type=>'Radius' ], [0,undef, planepath=>'CellularRule,rule=206', coordinate_type=>'RSquared' ], [undef,0, planepath=>'CellularRule,rule=206', coordinate_type=>'DiffXY' ], [0,undef, planepath=>'CellularRule,rule=206', coordinate_type=>'DiffYX' ], [0,undef, planepath=>'CellularRule,rule=206', coordinate_type=>'AbsDiff' ], # odd solid [undef,undef, planepath=>'CellularRule,rule=50',coordinate_type=>'X' ], [0,undef, planepath=>'CellularRule,rule=50',coordinate_type=>'Y' ], [0,undef, planepath=>'CellularRule,rule=50',coordinate_type=>'Sum' ], [undef,undef, planepath=>'CellularRule,rule=50',coordinate_type=>'Product'], [0,undef, planepath=>'CellularRule,rule=50',coordinate_type=>'Radius' ], [0,undef, planepath=>'CellularRule,rule=50',coordinate_type=>'RSquared'], [undef,0, planepath=>'CellularRule,rule=50',coordinate_type=>'DiffXY' ], [0,undef, planepath=>'CellularRule,rule=50',coordinate_type=>'DiffYX' ], [0,undef, planepath=>'CellularRule,rule=50',coordinate_type=>'AbsDiff' ], ) { my ($want_min,$want_max, @parameters) = @$elem; ### @parameters ### $want_min ### $want_max my $seq = Math::NumSeq::PlanePathCoord->new (@parameters); ok ($seq->values_min, $want_min, "values_min() ".join(',',@parameters)); ok ($seq->values_max, $want_max, "values_max() ".join(',',@parameters)); } #------------------------------------------------------------------------------ # values_min(), values_max() by running values my @modules = ( # 'FourReplicate', # module list begin 'Corner', 'Corner,wider=1', 'Corner,wider=2', 'Corner,wider=5', 'Corner,wider=37', 'CornerAlternating', 'CornerAlternating,wider=1', 'CornerAlternating,wider=2', 'CornerAlternating,wider=5', 'CornerAlternating,wider=37', 'PeanoDiagonals', 'PeanoDiagonals,radix=2', 'PeanoDiagonals,radix=4', 'PeanoDiagonals,radix=5', 'PeanoDiagonals,radix=17', 'PeanoCurve', 'PeanoCurve,radix=2', 'PeanoCurve,radix=4', 'PeanoCurve,radix=5', 'PeanoCurve,radix=17', 'AlternateTerdragon', 'AlternateTerdragon,arms=2', 'AlternateTerdragon,arms=3', 'AlternateTerdragon,arms=4', 'AlternateTerdragon,arms=5', 'AlternateTerdragon,arms=6', 'VogelFloret', 'VogelFloret,rotation_type=sqrt2', 'VogelFloret,rotation_type=sqrt3', 'VogelFloret,rotation_type=sqrt5', 'SacksSpiral', 'TheodorusSpiral', 'ArchimedeanChords', 'MultipleRings,step=0', 'MultipleRings,ring_shape=polygon,step=0', 'MultipleRings,step=1', 'MultipleRings,ring_shape=polygon,step=1', 'MultipleRings,step=2', 'MultipleRings,ring_shape=polygon,step=2', 'MultipleRings,step=3', 'MultipleRings,step=5', 'MultipleRings,step=6', 'MultipleRings,step=7', 'MultipleRings,step=8', 'MultipleRings,step=37', 'MultipleRings,ring_shape=polygon,step=3', 'MultipleRings,ring_shape=polygon,step=4', 'MultipleRings,ring_shape=polygon,step=5', 'MultipleRings,ring_shape=polygon,step=6', 'MultipleRings,ring_shape=polygon,step=7', 'MultipleRings,ring_shape=polygon,step=8', 'MultipleRings,ring_shape=polygon,step=9', 'MultipleRings,ring_shape=polygon,step=10', 'MultipleRings,ring_shape=polygon,step=11', 'MultipleRings,ring_shape=polygon,step=12', 'MultipleRings,ring_shape=polygon,step=13', 'MultipleRings,ring_shape=polygon,step=14', 'MultipleRings,ring_shape=polygon,step=15', 'MultipleRings,ring_shape=polygon,step=16', 'MultipleRings,ring_shape=polygon,step=17', 'MultipleRings,ring_shape=polygon,step=18', 'MultipleRings,ring_shape=polygon,step=37', 'SquareSpiral', 'SquareSpiral,wider=1', 'SquareSpiral,wider=2', 'SquareSpiral,wider=3', 'SquareSpiral,wider=4', 'SquareSpiral,wider=5', 'SquareSpiral,wider=6', 'SquareSpiral,wider=37', 'SquareSpiral,n_start=37', 'SquareSpiral,n_start=37,wider=1', 'SquareSpiral,n_start=37,wider=2', 'SquareSpiral,n_start=37,wider=3', 'SquareSpiral,n_start=37,wider=4', 'SquareSpiral,n_start=37,wider=5', 'SquareSpiral,n_start=37,wider=6', 'SquareSpiral,n_start=37,wider=37', 'GreekKeySpiral', 'GreekKeySpiral,turns=0', 'GreekKeySpiral,turns=1', 'GreekKeySpiral,turns=3', 'GreekKeySpiral,turns=4', 'GreekKeySpiral,turns=5', 'GreekKeySpiral,turns=6', 'GreekKeySpiral,turns=7', 'GreekKeySpiral,turns=8', 'GreekKeySpiral,turns=37', 'ChanTree,k=2', 'ChanTree', 'ChanTree,k=4', 'ChanTree,k=5', 'ChanTree,k=6', 'ChanTree,k=7', 'ChanTree,k=2,n_start=1', 'ChanTree,n_start=1', 'ChanTree,k=4,n_start=1', 'ChanTree,k=5,n_start=1', 'Rows,width=1', 'Rows,width=2', 'Rows,width=3', 'Rows,width=4', 'Rows,width=6', 'Rows,width=15', 'Rows', 'Columns,height=1', 'Columns,height=2', 'Columns,height=3', 'Columns,height=4', 'Columns,height=6', 'Columns,height=15', 'Columns', 'TriangularHypot', 'TriangularHypot,points=odd', 'TriangularHypot,points=all', 'TriangularHypot,points=hex', 'TriangularHypot,points=hex_rotated', 'TriangularHypot,points=hex_centred', 'PythagoreanTree,tree_type=UMT', 'PythagoreanTree,tree_type=UMT,coordinates=AC', 'PythagoreanTree,tree_type=UMT,coordinates=BC', 'PythagoreanTree,tree_type=UMT,coordinates=PQ', 'PythagoreanTree,tree_type=UMT,coordinates=SM', 'PythagoreanTree,tree_type=UMT,coordinates=SC', 'PythagoreanTree,tree_type=UMT,coordinates=MC', 'PythagoreanTree', 'PythagoreanTree,coordinates=AC', 'PythagoreanTree,coordinates=BC', 'PythagoreanTree,coordinates=PQ', 'PythagoreanTree,coordinates=SM', 'PythagoreanTree,coordinates=SC', 'PythagoreanTree,coordinates=MC', 'PythagoreanTree,tree_type=FB', 'PythagoreanTree,tree_type=FB,coordinates=AC', 'PythagoreanTree,tree_type=FB,coordinates=BC', 'PythagoreanTree,tree_type=FB,coordinates=PQ', 'PythagoreanTree,tree_type=FB,coordinates=SM', 'PythagoreanTree,tree_type=FB,coordinates=SC', 'PythagoreanTree,tree_type=FB,coordinates=MC', 'LTiling', 'LTiling,L_fill=left', 'LTiling,L_fill=upper', 'LTiling,L_fill=ends', 'LTiling,L_fill=all', 'HilbertSides', 'HilbertCurve', 'HilbertSpiral', 'DekkingCurve', 'DekkingCurve,arms=2', 'DekkingCurve,arms=3', 'DekkingCurve,arms=4', 'DekkingCentres', 'UlamWarburton,parts=octant', 'UlamWarburton,parts=octant_up', 'UlamWarburton', 'UlamWarburton,parts=2', 'UlamWarburton,parts=1', 'UlamWarburtonQuarter', 'UlamWarburtonQuarter,parts=octant_up', 'UlamWarburtonQuarter,parts=octant', 'WythoffPreliminaryTriangle', 'WythoffArray', 'WythoffArray,x_start=1', 'WythoffArray,y_start=1', 'WythoffArray,x_start=1,y_start=1', 'WythoffArray,x_start=5,y_start=7', 'MPeaks', 'MPeaks,n_start=0', 'AztecDiamondRings', 'AztecDiamondRings,n_start=0', 'AnvilSpiral', 'AnvilSpiral,wider=1', 'AnvilSpiral,wider=2', 'AnvilSpiral,wider=9', 'AnvilSpiral,wider=17', 'AnvilSpiral,n_start=0', 'AnvilSpiral,wider=1,n_start=0', 'AnvilSpiral,wider=2,n_start=0', 'AnvilSpiral,wider=9,n_start=0', 'AnvilSpiral,wider=17,n_start=0', 'Diagonals', 'Diagonals,direction=up', # 'Diagonals,x_start=1', 'Diagonals,y_start=1', 'Diagonals,x_start=1,direction=up', 'Diagonals,y_start=1,direction=up', # 'Diagonals,x_start=-1', 'Diagonals,y_start=-1', 'Diagonals,x_start=-1,direction=up', 'Diagonals,y_start=-1,direction=up', # 'Diagonals,x_start=2', 'Diagonals,y_start=2', 'Diagonals,x_start=2,direction=up', 'Diagonals,y_start=2,direction=up', # 'Diagonals,x_start=-2', 'Diagonals,y_start=-2', 'Diagonals,x_start=-2,direction=up', 'Diagonals,y_start=-2,direction=up', # 'Diagonals,x_start=6', 'Diagonals,y_start=6', 'Diagonals,x_start=6,direction=up', 'Diagonals,y_start=6,direction=up', # 'Diagonals,x_start=-6', 'Diagonals,y_start=-6', 'Diagonals,x_start=-6,direction=up', 'Diagonals,y_start=-6,direction=up', # 'Diagonals,x_start=3,y_start=6', 'Diagonals,x_start=-3,y_start=0', 'Diagonals,x_start=0,y_start=-6', 'Diagonals,x_start=5,y_start=-2', 'Diagonals,x_start=-5,y_start=2', 'Diagonals,x_start=-5,y_start=2', 'Diagonals,x_start=-5,y_start=-2', 'Diagonals,x_start=3,y_start=-5', 'Diagonals,x_start=-3,y_start=5', 'Diagonals,x_start=-3,y_start=5', 'Diagonals,x_start=-3,y_start=-5', # 'Diagonals,x_start=3,y_start=6,direction=up', 'Diagonals,x_start=-3,y_start=0,direction=up', 'Diagonals,x_start=0,y_start=-6,direction=up', 'Diagonals,x_start=5,y_start=-2,direction=up', 'Diagonals,x_start=-5,y_start=2,direction=up', 'Diagonals,x_start=-5,y_start=2,direction=up', 'Diagonals,x_start=-5,y_start=-2,direction=up', 'Diagonals,x_start=3,y_start=-5,direction=up', 'Diagonals,x_start=-3,y_start=5,direction=up', 'Diagonals,x_start=-3,y_start=5,direction=up', 'Diagonals,x_start=-3,y_start=-5,direction=up', # 'Diagonals,x_start=20,y_start=10', # 'Diagonals,x_start=20,y_start=10 # 'Diagonals,x_start=3,y_start=6,direction=up', # 'Diagonals,x_start=3,y_start=-6,direction=up', # 'Diagonals,x_start=-3,y_start=6,direction=up', # 'Diagonals,x_start=-3,y_start=-6,direction=up', 'SierpinskiArrowhead', 'SierpinskiArrowhead,align=right', 'SierpinskiArrowhead,align=left', 'SierpinskiArrowhead,align=diagonal', 'SierpinskiArrowheadCentres', 'SierpinskiArrowheadCentres,align=right', 'SierpinskiArrowheadCentres,align=left', 'SierpinskiArrowheadCentres,align=diagonal', 'KochCurve', 'KochPeaks', 'KochSnowflakes', 'KochSquareflakes', 'KochSquareflakes,inward=>1', 'CellularRule,rule=84', # right 2 cell line 'CellularRule,rule=84,n_start=0', 'CellularRule,rule=84,n_start=37', 'CellularRule,rule=14', # left 2 cell line 'CellularRule,rule=14,n_start=0', 'CellularRule,rule=14,n_start=37', 'CellularRule,rule=20', # right 1,2 line 'CellularRule,rule=20,n_start=0', 'CellularRule,rule=20,n_start=37', 'CellularRule,rule=6', # left 1,2 line 'CellularRule,rule=6,n_start=0', 'CellularRule,rule=6,n_start=37', 'PyramidRows', 'PyramidRows,step=0', 'PyramidRows,step=1', 'PyramidRows,step=3', 'PyramidRows,step=4', 'PyramidRows,step=5', 'PyramidRows,step=6', 'PyramidRows,step=7', 'PyramidRows,step=37', 'PyramidRows,align=right', 'PyramidRows,align=right,step=0', 'PyramidRows,align=right,step=1', 'PyramidRows,align=right,step=3', 'PyramidRows,align=right,step=4', 'PyramidRows,align=right,step=5', 'PyramidRows,align=right,step=6', 'PyramidRows,align=right,step=7', 'PyramidRows,align=right,step=37', 'PyramidRows,align=left', 'PyramidRows,align=left,step=0', 'PyramidRows,align=left,step=1', 'PyramidRows,align=left,step=3', 'PyramidRows,align=left,step=4', 'PyramidRows,align=left,step=5', 'PyramidRows,align=left,step=6', 'PyramidRows,align=left,step=7', 'PyramidRows,align=left,step=37', 'OctagramSpiral', 'OctagramSpiral,n_start=0', 'OctagramSpiral,n_start=37', 'Staircase', 'Staircase,n_start=0', 'Staircase,n_start=37', 'StaircaseAlternating', 'StaircaseAlternating,n_start=0', 'StaircaseAlternating,n_start=37', 'StaircaseAlternating,end_type=square', 'StaircaseAlternating,end_type=square,n_start=0', 'StaircaseAlternating,end_type=square,n_start=37', 'R5DragonCurve', 'R5DragonCurve,arms=2', 'R5DragonCurve,arms=3', 'R5DragonCurve,arms=4', 'R5DragonMidpoint', 'R5DragonMidpoint,arms=2', 'R5DragonMidpoint,arms=3', 'R5DragonMidpoint,arms=4', 'PyramidSides', 'CornerReplicate', 'DragonCurve', 'DragonCurve,arms=2', 'DragonCurve,arms=3', 'DragonCurve,arms=4', 'DragonRounded', 'DragonRounded,arms=2', 'DragonRounded,arms=3', 'DragonRounded,arms=4', 'DragonMidpoint', 'DragonMidpoint,arms=2', 'DragonMidpoint,arms=3', 'DragonMidpoint,arms=4', 'TerdragonCurve', 'TerdragonCurve,arms=2', 'TerdragonCurve,arms=3', 'TerdragonCurve,arms=4', 'TerdragonCurve,arms=5', 'TerdragonCurve,arms=6', 'TerdragonRounded', 'TerdragonRounded,arms=2', 'TerdragonRounded,arms=3', 'TerdragonRounded,arms=4', 'TerdragonRounded,arms=5', 'TerdragonRounded,arms=6', 'TerdragonMidpoint', 'TerdragonMidpoint,arms=2', 'TerdragonMidpoint,arms=3', 'TerdragonMidpoint,arms=4', 'TerdragonMidpoint,arms=5', 'TerdragonMidpoint,arms=6', 'HexSpiral', 'HexSpiral,wider=1', 'HexSpiral,wider=2', 'HexSpiral,wider=3', 'HexSpiral,wider=4', 'HexSpiral,wider=5', 'HexSpiral,wider=37', 'HexSpiralSkewed', 'HexSpiralSkewed,wider=1', 'HexSpiralSkewed,wider=2', 'HexSpiralSkewed,wider=3', 'HexSpiralSkewed,wider=4', 'HexSpiralSkewed,wider=5', 'HexSpiralSkewed,wider=37', 'Hypot', 'Hypot,points=even', 'Hypot,points=odd', 'HypotOctant', 'HypotOctant,points=even', 'HypotOctant,points=odd', 'DiamondArms', 'SquareArms', 'HexArms', 'PentSpiral', 'PentSpiral,n_start=0', 'PentSpiral,n_start=37', 'PentSpiralSkewed', 'PentSpiralSkewed,n_start=0', 'PentSpiralSkewed,n_start=37', 'CellularRule,rule=16', # right line 'CellularRule,rule=16,n_start=0', 'CellularRule,rule=16,n_start=37', 'CellularRule,rule=24', # right line 'CellularRule,rule=48', # right line 'CellularRule,rule=2', # left line 'CellularRule,rule=2,n_start=0', 'CellularRule,rule=2,n_start=37', 'CellularRule,rule=10', # left line 'CellularRule,rule=34', # left line 'CellularRule,rule=4', # centre line 'CellularRule,rule=4,n_start=0', 'CellularRule,rule=4,n_start=37', 'CellularRule,rule=12', # centre line 'CellularRule,rule=36', # centre line 'CellularRule,rule=206', # left solid 'CellularRule,rule=206,n_start=0', 'CellularRule,rule=206,n_start=37', 'CellularRule,rule=18', # Sierpinski 'CellularRule,rule=18,n_start=0', 'CellularRule,rule=18,n_start=37', 'CellularRule,rule=60', 'CellularRule,rule=18,n_start=0', 'CellularRule,rule=18,n_start=37', 'CellularRule,rule=220', # right half solid 'CellularRule,rule=220,n_start=0', 'CellularRule,rule=220,n_start=37', 'CellularRule,rule=222', # solid 'CoprimeColumns', 'DivisibleColumns', 'DivisibleColumns,divisor_type=proper', 'FractionsTree', 'SierpinskiTriangle', 'SierpinskiTriangle,align=right', 'SierpinskiTriangle,align=left', 'SierpinskiTriangle,align=diagonal', 'SierpinskiTriangle,n_start=37', 'SierpinskiTriangle,n_start=37,align=right', 'SierpinskiTriangle,n_start=37,align=left', 'SierpinskiTriangle,n_start=37,align=diagonal', '*ToothpickUpist', '*HTree', 'FlowsnakeCentres', 'FlowsnakeCentres,arms=2', 'FlowsnakeCentres,arms=3', 'Flowsnake', 'Flowsnake,arms=2', 'Flowsnake,arms=3', 'ImaginaryBase', 'ImaginaryBase,radix=3', 'ImaginaryBase,radix=4', 'ImaginaryBase,radix=5', 'ImaginaryBase,radix=6', 'ImaginaryBase,radix=37', 'ImaginaryHalf', 'ImaginaryHalf,digit_order=XXY', 'ImaginaryHalf,digit_order=YXX', 'ImaginaryHalf,digit_order=XnXY', 'ImaginaryHalf,digit_order=XnYX', 'ImaginaryHalf,digit_order=YXnX', 'ImaginaryHalf,digit_order=XXY,radix=3', 'ImaginaryHalf,radix=37', 'ImaginaryHalf,radix=3', 'ImaginaryHalf,radix=4', 'ImaginaryHalf,radix=5', 'ImaginaryHalf,radix=6', 'FactorRationals', 'FactorRationals,sign_encoding=odd/even', 'FactorRationals,sign_encoding=negabinary', 'FactorRationals,sign_encoding=revbinary', 'FactorRationals,sign_encoding=spread', 'PowerArray', 'PowerArray,radix=3', 'PowerArray,radix=4', '*ToothpickTree', '*ToothpickTree,parts=1', '*ToothpickTree,parts=2', '*ToothpickTree,parts=3', '*ToothpickTree,parts=octant', '*ToothpickTree,parts=octant_up', '*ToothpickTree,parts=wedge', '*ToothpickReplicate', '*ToothpickReplicate,parts=1', '*ToothpickReplicate,parts=2', '*ToothpickReplicate,parts=3', '*LCornerReplicate', '*LCornerTree', '*LCornerTree,parts=3', '*LCornerTree,parts=2', '*LCornerTree,parts=1', '*LCornerTree,parts=octant', '*LCornerTree,parts=octant+1', '*LCornerTree,parts=octant_up', '*LCornerTree,parts=octant_up+1', '*LCornerTree,parts=wedge', '*LCornerTree,parts=wedge+1', '*LCornerTree,parts=diagonal-1', '*LCornerTree,parts=diagonal', 'ZOrderCurve', 'ZOrderCurve,radix=3', 'ZOrderCurve,radix=9', 'ZOrderCurve,radix=37', 'DiagonalRationals', 'DiagonalRationals,direction=up', 'HeptSpiralSkewed', 'HeptSpiralSkewed,n_start=0', 'HeptSpiralSkewed,n_start=37', '*OneOfEight,parts=wedge', '*OneOfEight,parts=octant_up', '*OneOfEight', '*OneOfEight,parts=4', '*OneOfEight,parts=1', '*OneOfEight,parts=octant', '*OneOfEight,parts=3mid', '*OneOfEight,parts=3side', '*ToothpickSpiral', '*ToothpickSpiral,n_start=0', '*ToothpickSpiral,n_start=37', 'ComplexPlus', 'ComplexPlus,realpart=2', 'ComplexPlus,realpart=3', 'ComplexPlus,realpart=4', 'ComplexPlus,realpart=5', 'PyramidSpiral', 'PyramidSpiral,n_start=0', 'PyramidSpiral,n_start=37', 'GrayCode,apply_type=TsF', 'GrayCode,apply_type=FsT', 'GrayCode,apply_type=Ts', 'GrayCode,apply_type=Fs', 'GrayCode,apply_type=sT', 'GrayCode,apply_type=sF', 'GrayCode,radix=3,apply_type=TsF', 'GrayCode,radix=3,apply_type=FsT', 'GrayCode,radix=3,apply_type=Ts', 'GrayCode,radix=3,apply_type=Fs', 'GrayCode,radix=3,apply_type=sT', 'GrayCode,radix=3,apply_type=sF', 'GrayCode,radix=3,gray_type=modular,apply_type=TsF', 'GrayCode,radix=3,gray_type=modular,apply_type=Ts', 'GrayCode,radix=3,gray_type=modular,apply_type=Fs', 'GrayCode,radix=3,gray_type=modular,apply_type=FsT', 'GrayCode,radix=3,gray_type=modular,apply_type=sT', 'GrayCode,radix=3,gray_type=modular,apply_type=sF', 'GrayCode,radix=4,apply_type=TsF', 'GrayCode,radix=4,apply_type=FsT', 'GrayCode,radix=4,apply_type=Ts', 'GrayCode,radix=4,apply_type=Fs', 'GrayCode,radix=4,apply_type=sT', 'GrayCode,radix=4,apply_type=sF', 'GrayCode,radix=4,gray_type=modular,apply_type=TsF', 'GrayCode,radix=4,gray_type=modular,apply_type=Ts', 'GrayCode,radix=4,gray_type=modular,apply_type=Fs', 'GrayCode,radix=4,gray_type=modular,apply_type=FsT', 'GrayCode,radix=4,gray_type=modular,apply_type=sT', 'GrayCode,radix=4,gray_type=modular,apply_type=sF', 'GrayCode,radix=5,apply_type=TsF', 'GrayCode,radix=5,apply_type=FsT', 'GrayCode,radix=5,apply_type=Ts', 'GrayCode,radix=5,apply_type=Fs', 'GrayCode,radix=5,apply_type=sT', 'GrayCode,radix=5,apply_type=sF', 'GrayCode,radix=5,gray_type=modular,apply_type=TsF', 'GrayCode,radix=5,gray_type=modular,apply_type=Ts', 'GrayCode,radix=5,gray_type=modular,apply_type=Fs', 'GrayCode,radix=5,gray_type=modular,apply_type=FsT', 'GrayCode,radix=5,gray_type=modular,apply_type=sT', 'GrayCode,radix=5,gray_type=modular,apply_type=sF', 'GrayCode,radix=6,apply_type=TsF', 'GrayCode,radix=6,apply_type=FsT', 'GrayCode,radix=6,apply_type=Ts', 'GrayCode,radix=6,apply_type=Fs', 'GrayCode,radix=6,apply_type=sT', 'GrayCode,radix=6,apply_type=sF', 'GrayCode,radix=6,gray_type=modular,apply_type=TsF', 'GrayCode,radix=6,gray_type=modular,apply_type=Ts', 'GrayCode,radix=6,gray_type=modular,apply_type=Fs', 'GrayCode,radix=6,gray_type=modular,apply_type=FsT', 'GrayCode,radix=6,gray_type=modular,apply_type=sT', 'GrayCode,radix=6,gray_type=modular,apply_type=sF', 'CellularRule', 'CellularRule,rule=0', # single cell 'CellularRule,rule=8', # single cell 'CellularRule,rule=32', # single cell 'CellularRule,rule=40', # single cell 'CellularRule,rule=64', # single cell 'CellularRule,rule=72', # single cell 'CellularRule,rule=96', # single cell 'CellularRule,rule=104', # single cell 'CellularRule,rule=128', # single cell 'CellularRule,rule=136', # single cell 'CellularRule,rule=160', # single cell 'CellularRule,rule=168', # single cell 'CellularRule,rule=192', # single cell 'CellularRule,rule=200', # single cell 'CellularRule,rule=224', # single cell 'CellularRule,rule=232', # single cell 'CellularRule,rule=50', # solid every second cell 'CellularRule,rule=50,n_start=0', 'CellularRule,rule=50,n_start=37', 'CellularRule,rule=58', # solid every second cell 'CellularRule54', 'CellularRule54,n_start=0', 'CellularRule54,n_start=37', 'CellularRule57', 'CellularRule57,n_start=0', 'CellularRule57,n_start=37', 'CellularRule57,mirror=1', 'CellularRule190,n_start=0', 'CellularRule190', 'CellularRule190', 'CellularRule190,mirror=1', 'CellularRule190,mirror=1,n_start=0', 'AlternatePaper', 'AlternatePaper,arms=2', 'AlternatePaper,arms=3', 'AlternatePaper,arms=4', 'AlternatePaper,arms=5', 'AlternatePaper,arms=6', 'AlternatePaper,arms=7', 'AlternatePaper,arms=8', 'AlternatePaperMidpoint', 'AlternatePaperMidpoint,arms=2', 'AlternatePaperMidpoint,arms=3', 'AlternatePaperMidpoint,arms=4', 'AlternatePaperMidpoint,arms=5', 'AlternatePaperMidpoint,arms=6', 'AlternatePaperMidpoint,arms=7', 'AlternatePaperMidpoint,arms=8', 'GosperReplicate', 'GosperSide', 'GosperIslands', 'CubicBase', 'KnightSpiral', 'DiagonalsAlternating', 'GcdRationals', 'GcdRationals,pairs_order=rows_reverse', 'GcdRationals,pairs_order=diagonals_down', 'GcdRationals,pairs_order=diagonals_up', 'CCurve', 'ComplexMinus', 'ComplexMinus,realpart=2', 'ComplexMinus,realpart=3', 'ComplexMinus,realpart=4', 'ComplexMinus,realpart=5', 'ComplexRevolving', 'SierpinskiCurve', 'SierpinskiCurve,arms=2', 'SierpinskiCurve,arms=3', 'SierpinskiCurve,diagonal_spacing=5', 'SierpinskiCurve,straight_spacing=5', 'SierpinskiCurve,diagonal_spacing=3,straight_spacing=7', 'SierpinskiCurve,diagonal_spacing=3,straight_spacing=7,arms=7', 'SierpinskiCurve,arms=4', 'SierpinskiCurve,arms=5', 'SierpinskiCurve,arms=6', 'SierpinskiCurve,arms=7', 'SierpinskiCurve,arms=8', 'TriangleSpiralSkewed', 'TriangleSpiralSkewed,n_start=37', 'TriangleSpiralSkewed,skew=right', 'TriangleSpiralSkewed,skew=right,n_start=37', 'TriangleSpiralSkewed,skew=up', 'TriangleSpiralSkewed,skew=up,n_start=37', 'TriangleSpiralSkewed,skew=down', 'TriangleSpiralSkewed,skew=down,n_start=37', 'DiagonalsOctant', 'DiagonalsOctant,direction=up', 'HIndexing', 'SierpinskiCurveStair', 'SierpinskiCurveStair,diagonal_length=2', 'SierpinskiCurveStair,diagonal_length=3', 'SierpinskiCurveStair,diagonal_length=4', 'SierpinskiCurveStair,arms=2', 'SierpinskiCurveStair,arms=3,diagonal_length=2', 'SierpinskiCurveStair,arms=4', 'SierpinskiCurveStair,arms=5', 'SierpinskiCurveStair,arms=6,diagonal_length=5', 'SierpinskiCurveStair,arms=7', 'SierpinskiCurveStair,arms=8', 'QuadricCurve', 'QuadricIslands', 'CfracDigits,radix=1', 'CfracDigits', 'CfracDigits,radix=3', 'CfracDigits,radix=4', 'CfracDigits,radix=37', 'RationalsTree,tree_type=L', 'RationalsTree,tree_type=HCS', 'RationalsTree', 'RationalsTree,tree_type=CW', 'RationalsTree,tree_type=AYT', 'RationalsTree,tree_type=Bird', 'RationalsTree,tree_type=Drib', 'WunderlichSerpentine,radix=2', 'WunderlichSerpentine', 'WunderlichSerpentine,serpentine_type=100_000_000', 'WunderlichSerpentine,serpentine_type=000_000_001', 'WunderlichSerpentine,radix=4', 'WunderlichSerpentine,radix=5,serpentine_type=coil', 'DigitGroups', 'DigitGroups,radix=3', 'DigitGroups,radix=4', 'DigitGroups,radix=5', 'DigitGroups,radix=37', 'QuintetReplicate', 'QuintetCurve', 'QuintetCurve,arms=2', 'QuintetCurve,arms=3', 'QuintetCurve,arms=4', 'QuintetCentres', 'QuintetCentres,arms=2', 'QuintetCentres,arms=3', 'QuintetCentres,arms=4', 'TriangleSpiral', 'TriangleSpiral,n_start=37', # 'File', 'PixelRings', 'FilledRings', 'CretanLabyrinth', 'AR2W2Curve', 'AR2W2Curve,start_shape=D2', 'AR2W2Curve,start_shape=B2', 'AR2W2Curve,start_shape=B1rev', 'AR2W2Curve,start_shape=D1rev', 'AR2W2Curve,start_shape=A2rev', 'BetaOmega', 'KochelCurve', 'CincoCurve', 'WunderlichMeander', 'FibonacciWordFractal', 'DiamondSpiral', 'SquareReplicate', # module list end # cellular 0 to 255 (map {("CellularRule,rule=$_", "CellularRule,rule=$_,n_start=0", "CellularRule,rule=$_,n_start=37")} 0..255), ); foreach (@modules) { s/^\*// } { require Math::NumSeq::PlanePathDelta; require Math::NumSeq::PlanePathTurn; require Math::NumSeq::PlanePathN; foreach my $mod (@modules) { next unless want_planepath($mod); my $bad = 0; foreach my $elem ( ['Math::NumSeq::PlanePathDelta','delta_type'], ['Math::NumSeq::PlanePathCoord','coordinate_type'], ['Math::NumSeq::PlanePathTurn','turn_type'], ['Math::NumSeq::PlanePathN','line_type'], ) { my ($class, $pname) = @$elem; foreach my $param (@{$class->parameter_info_hash ->{$pname}->{'choices'}}) { next unless want_coordinate($param); MyTestHelpers::diag ("$mod $param"); ### $mod ### $param my $seq = $class->new (planepath => $mod, $pname => $param); my $planepath_object = $seq->{'planepath_object'}; ### planepath_object: ref $planepath_object my $i_start = $seq->i_start; if (! defined $i_start) { die "Oops, i_start=undef"; } my $characteristic_integer = $seq->characteristic('integer') || 0; my $saw_characteristic_integer = 1; my $saw_characteristic_integer_at = ''; my $saw_values_min = 999999999; my $saw_values_max = -999999999; my $saw_values_min_at = 'sentinel'; my $saw_values_max_at = 'sentinel'; my $saw_increasing = 1; my $saw_non_decreasing = 1; my $saw_increasing_at = '[default]'; my $saw_non_decreasing_at = '[default]'; my $prev_value; my $count = 0; my $i_limit = 800; if ($mod =~ /Vogel|Theod|Archim/ && $param =~ /axis|[XY]_neg|diagonal/i) { $i_limit = 20; } if ($mod =~ /Hypot|PixelRings|FilledRings/ && $param =~ /axis|[XY]_neg|diagonal/i) { $i_limit = 50; } if ($mod =~ /CellularRule/ && $param =~ /axis|[XY]_neg|diagonal/i) { $i_limit = 80; } my $i_end = $i_start + $i_limit; ### $i_limit my @i_extra; if (my $delta_type = $seq->{'delta_type'}) { foreach my $m ('min','max') { if (my $coderef = $planepath_object->can("_NumSeq_Delta_${delta_type}_${m}_n")) { push @i_extra, $planepath_object->$coderef(); } } } foreach my $i ($i_start .. $i_end, @i_extra) { my $value = $seq->ith($i); ### $i ### $value next if ! defined $value; $count++; if ($saw_characteristic_integer) { if ($value != int($value)) { $saw_characteristic_integer = 0; $saw_characteristic_integer_at = "i=$i value=$value"; } } if ($value < $saw_values_min) { $saw_values_min = $value; if (my ($x,$y) = $seq->{'planepath_object'}->n_to_xy($i)) { $saw_values_min_at = "i=$i xy=$x,$y"; } else { $saw_values_min_at = "i=$i"; } } if ($value > $saw_values_max) { $saw_values_max = $value; $saw_values_max_at = "i=$i"; } # ### $value # ### $prev_value if (defined $prev_value) { if (abs($value - $prev_value) < 0.0000001) { $prev_value = $value; } if ($value <= $prev_value && ! is_nan($prev_value) && ! ($value==pos_infinity() && $prev_value==pos_infinity())) { # ### not increasing ... if ($saw_increasing) { $saw_increasing = 0; $saw_increasing_at = "i=$i value=$value prev_value=$prev_value"; } if ($value < $prev_value) { if ($saw_non_decreasing) { $saw_non_decreasing = 0; $saw_non_decreasing_at = "i=$i"; } } } } $prev_value = $value; } ### $count next if $count == 0; ### $saw_values_min ### $saw_values_min_at ### $saw_values_max ### $saw_values_max_at my $values_min = $seq->values_min; my $values_max = $seq->values_max; if (! defined $values_min) { if ($saw_values_min >= -3 && $count >= 3) { MyTestHelpers::diag ("$mod $param values_min=undef vs saw_values_min=$saw_values_min apparent lower bound at $saw_values_min_at"); } $values_min = $saw_values_min; } if (! defined $values_max) { if ($saw_values_max <= 3 && $count >= 3) { MyTestHelpers::diag ("$mod $param values_max=undef vs saw_values_max=$saw_values_max apparent upper bound at $saw_values_max_at"); } $values_max = $saw_values_max; } if (my $coderef = $planepath_object->can("_NumSeq_${param}_max_is_supremum")) { if ($planepath_object->$coderef) { if ($saw_values_max == $values_max) { MyTestHelpers::diag ("$mod $param values_max=$values_max vs saw_values_max=$saw_values_max at $saw_values_max_at supposed to be supremum only"); MyTestHelpers::diag (" (planepath_object ",ref $seq->{'planepath_object'},")"); $bad++; } if ($saw_values_max < $values_max) { $saw_values_max = $values_max; $saw_values_max_at = 'supremum'; } } } if (my $coderef = $planepath_object->can("_NumSeq_${param}_min_is_infimum")) { if ($planepath_object->$coderef()) { if ($saw_values_min == $values_min) { MyTestHelpers::diag ("$mod $param values_min=$values_min vs saw_values_min=$saw_values_min at $saw_values_min_at supposed to be infimum only"); MyTestHelpers::diag (" (planepath_object ",ref $seq->{'planepath_object'},")"); } if ($saw_values_min > $values_min) { $saw_values_min = $values_min; $saw_values_min_at = 'infimum'; } } } # these come arbitrarily close to dX==dY, in general, probably if (($mod eq 'MultipleRings,step=2' || $mod eq 'MultipleRings,step=3' || $mod eq 'MultipleRings,step=5' || $mod eq 'MultipleRings,step=7' || $mod eq 'MultipleRings,step=37' ) && $param eq 'AbsDiff' && $saw_values_min > 0 && $saw_values_min < 0.3) { $saw_values_min = 0; $saw_values_min_at = 'override'; } # supremum +/- 1 without ever actually reaching if (($mod eq 'MultipleRings' ) && ($param eq 'dX' || $param eq 'dY' )) { $saw_values_min = -1; $saw_values_min_at = 'override'; } # if (($mod eq 'MultipleRings,step=1' # || $mod eq 'MultipleRings,step=2' # || $mod eq 'MultipleRings,step=3' # || $mod eq 'MultipleRings,step=4' # || $mod eq 'MultipleRings,step=5' # || $mod eq 'MultipleRings,step=6' # || $mod eq 'MultipleRings' # ) # && ($param eq 'dX' # || $param eq 'dY' # || $param eq 'Dist' # )) { # my ($step) = ($mod =~ /MultipleRings,step=(\d+)/); # $step ||= 6; # if (-$saw_values_min > 2*PI()/$step*0.85 # && -$saw_values_min < 2*PI()/$step) { # $saw_values_min = -2*PI() / $step; # $saw_values_min_at = 'override'; # } # if ($saw_values_max > 2*PI()/$step*0.85 # && $saw_values_max < 2*PI()/$step) { # $saw_values_max = 2*PI() / $step; # $saw_values_max_at = 'override'; # } # } if (($mod eq 'MultipleRings,step=7' || $mod eq 'MultipleRings,step=8' ) && ($param eq 'dY' )) { if (-$saw_values_min > 0.9 && -$saw_values_min < 1) { $saw_values_min = -1; $saw_values_min_at = 'override'; } if ($saw_values_max > 0.9 && $saw_values_max < 1) { $saw_values_max = 1; $saw_values_max_at = 'override'; } } if (($mod eq 'MultipleRings,step=7' || $mod eq 'MultipleRings,step=8' ) && ($param eq 'dX' )) { if (-$saw_values_min > 0.9 && -$saw_values_min < 1) { $saw_values_min = -1; $saw_values_min_at = 'override'; } } # approach 360 without ever actually reaching if (($mod eq 'SacksSpiral' || $mod eq 'TheodorusSpiral' || $mod eq 'Hypot' || $mod eq 'MultipleRings,step=8' || $mod eq 'MultipleRings,step=37' ) && ($param eq 'Dir4' ) && $saw_values_max > 3.7 && $saw_values_max < 4 ) { $saw_values_max = 4; $saw_values_max_at = 'override'; } if (($mod eq 'SacksSpiral' || $mod eq 'TheodorusSpiral' || $mod eq 'Hypot' || $mod eq 'MultipleRings,step=8' || $mod eq 'MultipleRings,step=37' ) && ($param eq 'TDir6' ) && $saw_values_max > 5.55 && $saw_values_max < 6) { $saw_values_max = 6; $saw_values_max_at = 'override'; } # approach 0 without ever actually reaching if (($mod eq 'MultipleRings,step=8' || $mod eq 'MultipleRings,step=37' ) && ($param eq 'Dir4' )) { $saw_values_min = 0; $saw_values_min_at = 'override'; } if (($mod eq 'MultipleRings,step=8' || $mod eq 'MultipleRings,step=37' ) && ($param eq 'TDir6' )) { $saw_values_min = 0; $saw_values_min_at = 'override'; } # not enough values to see these decreasing if (($mod eq 'SquareSpiral,wider=37' ) && ($param eq 'dY')) { $saw_values_min = -1; $saw_values_min_at = 'override'; } if (($mod eq 'SquareSpiral,wider=37' ) && ($param eq 'Dir4')) { $saw_values_max = 3; $saw_values_max_at = 'override'; } if (($mod eq 'SquareSpiral,wider=37' ) && ($param eq 'TDir6')) { $saw_values_max = 4.5; $saw_values_max_at = 'override'; } # not enough values to see near supremum if (($mod eq 'ZOrderCurve,radix=37' ) && ($param eq 'Dir4' || $param eq 'TDir6' )) { $saw_values_max = $values_max; $saw_values_max_at = 'override'; } # Turn4 maximum is at N=radix*radix-1 if (($mod eq 'ZOrderCurve,radix=37' && $param eq 'Turn4' && $i_end < 37*37-1 )) { $saw_values_max = $values_max; $saw_values_max_at = 'override'; } # Turn4 maximum is at N=8191 if (($mod eq 'LCornerReplicate' && $param eq 'Turn4' && $i_end < 8191 )) { $saw_values_max = $values_max; $saw_values_max_at = 'override'; } if (abs ($values_min - $saw_values_min) > 0.001) { MyTestHelpers::diag ("$mod $param values_min=$values_min vs saw_values_min=$saw_values_min at $saw_values_min_at (to i_end=$i_end)"); MyTestHelpers::diag (" (planepath_object ",ref $seq->{'planepath_object'},")"); $bad++; } if (abs ($values_max - $saw_values_max) > 0.001) { MyTestHelpers::diag ("$mod $param values_max=$values_max vs saw_values_max=$saw_values_max at $saw_values_max_at (to i_end=$i_end)"); MyTestHelpers::diag (" (planepath_object ",ref $seq->{'planepath_object'},")"); $bad++; } #------------------- my $increasing = $seq->characteristic('increasing'); my $non_decreasing = $seq->characteristic('non_decreasing'); $increasing ||= 0; $non_decreasing ||= 0; # not enough values to see these decreasing if ($mod eq 'DigitGroups,radix=37' && $param eq 'Radius' && $i_end < 37*37) { $saw_characteristic_integer = 0; } # not enough values to see these decreasing if (($mod eq 'ZOrderCurve,radix=9' || $mod eq 'ZOrderCurve,radix=37' || $mod eq 'PeanoCurve,radix=17' || $mod eq 'DigitGroups,radix=37' || $mod eq 'SquareSpiral,wider=37' || $mod eq 'HexSpiral,wider=37' || $mod eq 'HexSpiralSkewed,wider=37' || $mod eq 'ComplexPlus,realpart=2' || $mod eq 'ComplexPlus,realpart=3' || $mod eq 'ComplexPlus,realpart=4' || $mod eq 'ComplexPlus,realpart=5' || $mod eq 'ComplexMinus,realpart=3' || $mod eq 'ComplexMinus,realpart=4' || $mod eq 'ComplexMinus,realpart=5' ) && ($param eq 'Y' || $param eq 'Product')) { $saw_increasing_at = 'override'; $saw_increasing = 0; $saw_non_decreasing = 0; } # not enough values to see these decreasing if (($mod eq 'ComplexPlus,realpart=2' || $mod eq 'ComplexPlus,realpart=3' || $mod eq 'ComplexPlus,realpart=4' || $mod eq 'ComplexPlus,realpart=5' || $mod eq 'ComplexMinus,realpart=5' || $mod eq 'TerdragonMidpoint' || $mod eq 'TerdragonMidpoint,arms=2' || $mod eq 'TerdragonMidpoint,arms=3' || $mod eq 'TerdragonCurve' || $mod eq 'TerdragonCurve,arms=2' || $mod eq 'TerdragonCurve,arms=3' || $mod eq 'TerdragonRounded' || $mod eq 'Flowsnake' || $mod eq 'Flowsnake,arms=2' || $mod eq 'FlowsnakeCentres' || $mod eq 'FlowsnakeCentres,arms=2' || $mod eq 'GosperSide' || $mod eq 'GosperIslands' || $mod eq 'QuintetCentres' || $mod eq 'QuintetCentres,arms=2' || $mod eq 'QuintetCentres,arms=3' ) && ($param eq 'X_axis' || $param eq 'Y_axis' || $param eq 'X_neg' || $param eq 'Y_neg' || $param =~ /Diagonal/ )) { $saw_increasing = 0; $saw_increasing_at = 'override'; $saw_non_decreasing = 0; } if ($mod eq 'QuintetCurve' && $i_end < 5938 # first decrease && $param eq 'Diagonal_SE') { $saw_increasing = 0; $saw_increasing_at = 'override'; $saw_non_decreasing = 0; } if ($mod eq 'QuintetCentres' && $i_end < 5931 # first decreasing && $param eq 'Diagonal_SE') { $saw_increasing = 0; $saw_increasing_at = 'override'; $saw_non_decreasing = 0; } if ($mod eq 'ImaginaryBase,radix=37' && $i_end < 1369 # N of first Y coordinate decrease && $param eq 'Y') { $saw_increasing = 0; $saw_increasing_at = 'override'; $saw_non_decreasing = 0; } # if ($mod eq 'ImaginaryBase,radix=37' # $param eq 'Diagonal_NW' # || $param eq 'Diagonal_NW' # || $param eq 'Diagonal_SS' # || $param eq 'Diagonal_SE') # && $i_end < 74) { # $saw_increasing = 0; # $saw_increasing_at = 'override'; # $saw_non_decreasing = 0; # } if ($mod eq 'ImaginaryHalf,radix=37' && $i_end < 1369 # N of first Y coordinate decrease && $param eq 'Y') { $saw_increasing = 0; $saw_increasing_at = 'override'; $saw_non_decreasing = 0; } if ($mod eq 'ImaginaryHalf,radix=37' && $i_end < 99974 # first decrease && $param eq 'Diagonal') { $saw_increasing = 0; $saw_increasing_at = 'override'; $saw_non_decreasing = 0; } if ($mod eq 'ImaginaryHalf,radix=37' && $i_end < 2702 # first decreasing && $param eq 'Diagonal_NW') { $saw_increasing = 0; $saw_increasing_at = 'override'; $saw_non_decreasing = 0; } # not enough values to see these decreasing if (($mod eq 'DigitGroups,radix=37' ) && ($param eq 'X_axis' || $param eq 'Y_axis' )) { $saw_increasing = 0; $saw_increasing_at = 'override'; $saw_non_decreasing = 0; } # not enough values to see these decreasing if (($mod eq 'PeanoCurve,radix=2' || $mod eq 'PeanoCurve,radix=4' || $mod eq 'PeanoCurve,radix=5' || $mod eq 'PeanoCurve,radix=17' ) && ($param eq 'Diagonal' )) { $saw_increasing = 0; $saw_increasing_at = 'override'; $saw_non_decreasing = 0; } if (($mod eq 'SquareSpiral,wider=37' ) && ($param eq 'Dir4' || $param eq 'TDir6')) { $saw_non_decreasing = 0; } if ($count > 1 && $increasing ne $saw_increasing) { MyTestHelpers::diag ("$mod $param increasing=$increasing vs saw_increasing=$saw_increasing at $saw_increasing_at (to i_end=$i_end)"); MyTestHelpers::diag (" (planepath_object ",ref $seq->{'planepath_object'},")"); $bad++; } if ($count > 1 && $non_decreasing ne $saw_non_decreasing) { MyTestHelpers::diag ("$mod $param non_decreasing=$non_decreasing vs saw_non_decreasing=$saw_non_decreasing at $saw_non_decreasing_at (to i_end=$i_end)"); MyTestHelpers::diag (" (planepath_object ",ref $seq->{'planepath_object'},")"); $bad++; } if ($characteristic_integer != $saw_characteristic_integer) { MyTestHelpers::diag ("$mod $param characteristic_integer=$characteristic_integer vs saw_characteristic_integer=$saw_characteristic_integer at $saw_characteristic_integer_at"); MyTestHelpers::diag (" (planepath_object ",ref $seq->{'planepath_object'},")"); $bad++; } } } ok ($bad, 0); } } #------------------------------------------------------------------------------ sub is_nan { my ($x) = @_; return !($x==$x); } exit 0; Math-PlanePath-129/xt/slow/AlternatePaper-slow.t0000644000175000017500000001555612451431554017423 0ustar gggg#!/usr/bin/perl -w # Copyright 2014, 2015 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min','max'; use Test; plan tests => 87; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use lib 'xt'; use MyOEIS; use Memoize; # uncomment this to run the ### lines # use Smart::Comments; use Math::PlanePath::AlternatePaper; my $path = Math::PlanePath::AlternatePaper->new; #------------------------------------------------------------------------------ # right boundary N { my $bad = 0; foreach my $arms (1 .. 8) { my $path = Math::PlanePath::AlternatePaper->new (arms => $arms); my $i = 0; foreach my $n (0 .. 4**6-1) { my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n + $arms); my $want_pred = path_xyxy_is_right_boundary($path, $x1,$y1, $x2,$y2) ? 1 : 0; my $got_pred = $path->_UNDOCUMENTED__n_segment_is_right_boundary($n) ? 1 : 0; unless ($want_pred == $got_pred) { MyTestHelpers::diag ("oops, _UNDOCUMENTED__n_segment_is_right_boundary() arms=$arms n=$n pred traverse=$want_pred method=$got_pred"); last if $bad++ > 10; } } } ok ($bad, 0); } # Return true if line segment $x1,$y1 to $x2,$y2 is on the right boundary. # Assumes a square grid and every enclosed unit square has all 4 sides. sub path_xyxy_is_right_boundary { my ($path, $x1,$y1, $x2,$y2) = @_; ### path_xyxy_is_right_boundary() ... my $dx = $x2-$x1; my $dy = $y2-$y1; ($dx,$dy) = ($dy,-$dx); # rotate -90 ### one: "$x1,$y1 to ".($x1+$dx).",".($y1+$dy) ### two: "$x2,$y2 to ".($x2+$dx).",".($y2+$dy) return (! defined $path->xyxy_to_n_either ($x1,$y1, $x1+$dx,$y1+$dy) || ! defined $path->xyxy_to_n_either ($x2,$y2, $x2+$dx,$y2+$dy) || ! defined $path->xyxy_to_n_either ($x1+$dx,$y1+$dy, $x2+$dx,$y2+$dy)); } #------------------------------------------------------------------------------ # left boundary N { my $bad = 0; foreach my $arms (4 .. 8) { my $path = Math::PlanePath::AlternatePaper->new (arms => $arms); my $i = 0; foreach my $n (0 .. 4**6-1) { my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n + $arms); my $want_pred = path_xyxy_is_left_boundary($path, $x1,$y1, $x2,$y2) ? 1 : 0; my $got_pred = $path->_UNDOCUMENTED__n_segment_is_left_boundary($n) ? 1 : 0; unless ($want_pred == $got_pred) { MyTestHelpers::diag ("oops, _UNDOCUMENTED__n_segment_is_left_boundary() arms=$arms n=$n pred traverse=$want_pred method=$got_pred"); last if $bad++ > 10; } } } ok ($bad, 0); } # Return true if line segment $x1,$y1 to $x2,$y2 is on the left boundary. # Assumes a square grid and every enclosed unit square has all 4 sides. sub path_xyxy_is_left_boundary { my ($path, $x1,$y1, $x2,$y2) = @_; my $dx = $x2-$x1; my $dy = $y2-$y1; ($dx,$dy) = (-$dy,$dx); # rotate +90 return (! defined ($path->xyxy_to_n_either ($x1,$y1, $x1+$dx,$y1+$dy)) || ! defined ($path->xyxy_to_n_either ($x2,$y2, $x2+$dx,$y2+$dy)) || ! defined ($path->xyxy_to_n_either ($x1+$dx,$y1+$dy, $x2+$dx,$y2+$dy))); } #------------------------------------------------------------------------------ # boundary lengths sub B_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit); return scalar(@$points); } memoize('B_from_path'); sub L_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit, side => 'left'); return scalar(@$points) - 1; } memoize('L_from_path'); sub R_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit, side => 'right'); return scalar(@$points) - 1; } BEGIN { memoize('R_from_path'); } #------------------------------------------------------------------------------ # B boundary { # _UNDOCUMENTED_level_to_line_boundary() # is sum left and right foreach my $k (0 .. 14) { my $got = $path->_UNDOCUMENTED_level_to_line_boundary($k); my $want = ($path->_UNDOCUMENTED_level_to_right_line_boundary($k) + $path->_UNDOCUMENTED_level_to_left_line_boundary($k)); ok ($got, $want, "boundary sum k=$k"); } } { # _UNDOCUMENTED_level_to_line_boundary() foreach my $k (0 .. 14) { my $got = $path->_UNDOCUMENTED_level_to_line_boundary($k); my $want = B_from_path($path,$k); ok ($got, $want, "_UNDOCUMENTED_level_to_line_boundary() k=$k"); } } #------------------------------------------------------------------------------ # L { # _UNDOCUMENTED_level_to_left_line_boundary() foreach my $k (0 .. 14) { my $got = $path->_UNDOCUMENTED_level_to_left_line_boundary($k); my $want = L_from_path($path,$k); ok ($got, $want, "_UNDOCUMENTED_level_to_left_line_boundary() k=$k"); } } #------------------------------------------------------------------------------ # R { # _UNDOCUMENTED_level_to_right_line_boundary() foreach my $k (0 .. 14) { my $got = $path->_UNDOCUMENTED_level_to_right_line_boundary($k); my $want = R_from_path($path,$k); ok ($got, $want, "_UNDOCUMENTED_level_to_right_line_boundary() k=$k"); } } #------------------------------------------------------------------------------ # convex hull area { require Math::Geometry::Planar; my @points; my $n = $path->n_start; foreach my $k (0 .. 14) { my $n_end = 2**$k; while ($n <= $n_end) { push @points, [ $path->n_to_xy($n) ]; $n++; } my ($want_area, $want_boundary); if ($k == 0) { # N=0 to N=1 $want_area = 0; } else { my $polygon = Math::Geometry::Planar->new; $polygon->points([@points]); if (@points > 3) { $polygon = $polygon->convexhull2; ### convex: $polygon } $want_area = $polygon->area; } my $got_area = $path->_UNDOCUMENTED_level_to_hull_area($k); ok ($got_area, $want_area, "k=$k"); } } sub to_sqrt2_parts { my ($x) = @_; if (! defined $x) { return $x; } foreach my $b (0 .. int($x)) { my $a = $x - $b*sqrt(2); my $a_int = int($a+.5); if (abs($a - $a_int) < 0.00000001) { return $a_int, $b; } } return (undef,undef); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/slow/GcdRationals-slow.t0000644000175000017500000000717612136177164017071 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min','max'; use Test; plan tests => 637; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::GcdRationals; my @pairs_order_choices = @{Math::PlanePath::GcdRationals->parameter_info_hash ->{'pairs_order'}->{'choices'}}; #------------------------------------------------------------------------------ # rect_to_n_range() my $bad = 0; my $y_min = 1; my $y_max = 50; my $x_min = 1; my $x_max = 50; foreach my $pairs_order (@pairs_order_choices) { my $path = Math::PlanePath::GcdRationals->new (pairs_order => $pairs_order); my $n_start = $path->n_start; my $report = sub { MyTestHelpers::diag("$pairs_order ",@_); $bad++; }; my %data; my $data_count; foreach my $x ($x_min .. $x_max) { foreach my $y ($y_min .. $y_max) { my $n = $path->xy_to_n ($x, $y); $data{$y}{$x} = $n; $data_count += defined $n; } } MyTestHelpers::diag("$pairs_order data_count ",$data_count); foreach my $y1 ($y_min .. $y_max) { foreach my $y2 ($y1 .. $y_max) { foreach my $x1 ($x_min .. $x_max) { my $min; my $max; foreach my $x2 ($x1 .. $x_max) { my @col = map {$data{$_}{$x2}} $y1 .. $y2; @col = grep {defined} @col; $min = min (grep {defined} $min, @col); $max = max (grep {defined} $max, @col); my $want_min = (defined $min ? $min : 1); my $want_max = (defined $max ? $max : 0); ### @col ### rect: "$x1,$y1 $x2,$y2 expect N=$want_min..$want_max" my ($got_min, $got_max) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); defined $got_min or &$report ("rect_to_n_range($x1,$y1, $x2,$y2) got_min undef"); defined $got_max or &$report ("rect_to_n_range($x1,$y1, $x2,$y2) got_max undef"); $got_min >= $n_start or &$report ("rect_to_n_range() got_min=$got_min is before n_start=$n_start"); if (! defined $min || ! defined $max) { next; # outside } unless ($got_min <= $want_min) { ### $x1 ### $y1 ### $x2 ### $y2 ### got: $path->rect_to_n_range ($x1,$y1, $x2,$y2) ### $want_min ### $want_max ### $got_min ### $got_max ### @col ### $data &$report ("rect_to_n_range($x1,$y1, $x2,$y2) bad min got_min=$got_min want_min=$want_min".(defined $min ? '' : '[nomin]') ); } unless ($got_max >= $want_max) { &$report ("rect_to_n_range($x1,$y1, $x2,$y2 ) bad max got $got_max want $want_max".(defined $max ? '' : '[nomax]')); } } } } } } ok ($bad, 0); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/slow/CellularRule-slow.t0000644000175000017500000001057613246363043017104 0ustar gggg#!/usr/bin/perl -w # Copyright 2014, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min','max'; use Test; plan tests => 637; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::CellularRule; #------------------------------------------------------------------------------ # rules_are_equiv() sub paths_are_equiv { my ($path1, $path2) = @_; foreach my $y (0 .. 6) { foreach my $x (-$y .. $y) { if ((!! $path1->xy_is_visited($x,$y)) != (!! $path2->xy_is_visited($x,$y))) { return 0; } } } return 1; } foreach my $rule1 (0 .. 255) { my $path1 = Math::PlanePath::CellularRule->new (rule => $rule1); foreach my $rule2 (0 .. 255) { my $path2 = Math::PlanePath::CellularRule->new (rule => $rule2); my $got = Math::PlanePath::CellularRule->_NOTWORKING__rules_are_equiv($rule1,$rule2) ? 1 : 0; my $want = paths_are_equiv($path1,$path2); ok ($got, $want, "rules_are_equiv($rule1,$rule2)"); if ($got != $want) { MyTestHelpers::diag(path_str($path1)); MyTestHelpers::diag(path_str($path2)); } } } #------------------------------------------------------------------------------ # rule_to_mirror() sub paths_are_mirror { my ($path1, $path2) = @_; foreach my $y (0 .. 6) { foreach my $x (-$y .. $y) { if ((!!$path1->xy_is_visited($x,$y)) != (!!$path2->xy_is_visited(-$x,$y))) { return 0; } } } return 1; } foreach my $rule (0 .. 255) { my $mirror_rule = Math::PlanePath::CellularRule->_UNDOCUMENTED__rule_to_mirror($rule); my $path1 = Math::PlanePath::CellularRule->new (rule => $rule); my $path2 = Math::PlanePath::CellularRule->new (rule => $mirror_rule); my $are_mirror = paths_are_mirror($path1,$path2); ok ($are_mirror, 1, "rule_to_mirror() rule=$rule got_rule=$mirror_rule"); if (! $are_mirror) { MyTestHelpers::diag(path_str($path1)); MyTestHelpers::diag(path_str($path2)); } } #------------------------------------------------------------------------------ # rule_is_finite() sub path_is_finite { my ($path) = @_; foreach my $y (4 .. 6) { foreach my $x (-$y .. $y) { if ($path->xy_is_visited($x,$y)) { return 0; } } } return 1; } foreach my $rule (0 .. 255) { my $path = Math::PlanePath::CellularRule->new (rule => $rule); my $got = Math::PlanePath::CellularRule->_UNDOCUMENTED__rule_is_finite($rule) ? 1 : 0; my $want = path_is_finite($path) ? 1 : 0; ok ($got, $want, "rule_is_finite() rule=$rule"); if ($got != $want) { MyTestHelpers::diag (path_str($path)); } } #------------------------------------------------------------------------------ # rule_is_symmetric() sub path_is_symmetric { my ($path) = @_; foreach my $y (1 .. 8) { foreach my $x (1 .. $y) { if ((!!$path->xy_is_visited($x,$y)) != (!!$path->xy_is_visited(-$x,$y))) { return 0; } } } return 1; } foreach my $rule (0 .. 255) { my $path = Math::PlanePath::CellularRule->new (rule => $rule); my $got_symmetric = Math::PlanePath::CellularRule->_NOTWORKING__rule_is_symmetric($rule) ? 1 : 0; my $want_symmetric = path_is_symmetric($path) ? 1 : 0; ok ($got_symmetric, $want_symmetric, "rule_is_symmetric() rule=$rule"); if ($got_symmetric != $want_symmetric) { MyTestHelpers::diag (path_str($path)); } } sub path_str { my ($path) = @_; my $str = ''; foreach my $y (reverse 0 .. 6) { $str .= "$y "; foreach my $x (-6 .. 6) { $str .= $path->xy_is_visited($x,$y) ? ' *' : ' '; } if ($y == 6) { $str .= " rule=$path->{'rule'} = ".sprintf('%08b',$path->{'rule'}); } $str .= "\n"; } return $str; } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/slow/CCurve-slow.t0000644000175000017500000001110213036540301015651 0ustar gggg#!/usr/bin/perl -w # Copyright 2014, 2016, 2017 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min','max'; use Test; plan tests => 87; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use lib 'xt'; use MyOEIS; use Memoize; # uncomment this to run the ### lines # use Smart::Comments; use Math::PlanePath::CCurve 124; # v.124 for n_to_n_list() my $path = Math::PlanePath::CCurve->new; #------------------------------------------------------------------------------ # convex hull { require Math::Geometry::Planar; my @points; my $n = $path->n_start; foreach my $k (0 .. 14) { my $n_end = 2**$k; while ($n <= $n_end) { push @points, [ $path->n_to_xy($n) ]; $n++; } my ($want_area, $want_boundary); if ($k == 0) { # N=0 to N=1 $want_area = 0; $want_boundary = 2; } else { my $polygon = Math::Geometry::Planar->new; $polygon->points([@points]); if (@points > 3) { $polygon = $polygon->convexhull2; ### convex: $polygon } $want_area = $polygon->area; $want_boundary = $polygon->perimeter; } my ($want_a,$want_b) = to_sqrt2_parts($want_boundary); my $got_boundary = $path->_UNDOCUMENTED_level_to_hull_boundary($k); my ($got_a,$got_b) = $path->_UNDOCUMENTED_level_to_hull_boundary_sqrt2($k); ok ($got_a, $want_a, "k=$k"); ok ($got_b, $want_b, "k=$k"); ok (abs($got_boundary - $want_boundary) < 0.00001, 1); my $got_area = $path->_UNDOCUMENTED_level_to_hull_area($k); ok ($got_area, $want_area, "k=$k"); } } sub to_sqrt2_parts { my ($x) = @_; if (! defined $x) { return $x; } foreach my $b (0 .. int($x)) { my $a = $x - $b*sqrt(2); my $a_int = int($a+.5); if (abs($a - $a_int) < 0.00000001) { return $a_int, $b; } } return (undef,undef); } #------------------------------------------------------------------------------ # boundary lengths sub B_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit); return scalar(@$points); } memoize('B_from_path'); sub L_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit, side => 'left'); return scalar(@$points) - 1; } memoize('L_from_path'); sub R_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit, side => 'right'); return scalar(@$points) - 1; } memoize('R_from_path'); # R[k] = 2*R[k-1] + R[k-2] - 4*R[k-3] + 2*R[k-4] sub R_recurrence { my ($recurrence, $k) = @_; if ($k <= 0) { return 1; } if ($k == 1) { return 2; } if ($k == 2) { return 4; } if ($k == 3) { return 8; } return (2*R_recurrence($k-4) - 4*R_recurrence($k-3) + R_recurrence($k-2) + 2*R_recurrence($k-1)); } memoize('R_from_path'); #------------------------------------------------------------------------------ # R { # POD samples my @want = (1, 2, 4, 8, 14, 24, 38, 60, 90, 136, 198, 292, 418); foreach my $k (0 .. $#want) { my $got = R_from_path($path,$k); my $want = $want[$k]; ok ($got,$want); } } { # recurrence my @want = (1, 2, 4, 8, 14, 24, 38, 60, 90, 136, 198, 292, 418); foreach my $k (0 .. $#want) { my $got = R_from_path($path,$k); my $want = $want[$k]; ok ($got,$want); } } #------------------------------------------------------------------------------ # claimed in the pod N overlaps always have different count 1-bits mod 4 { foreach my $n (0 .. 100_000) { my @n_list = $path->n_to_n_list($n); my @seen; foreach my $n (@n_list) { my $c = count_1_bits($n) % 4; if ($seen[$c]++) { die; } } } ok (1,1); } sub count_1_bits { my ($n) = @_; my $count = 0; while ($n) { $count += ($n & 1); $n >>= 1; } return $count; } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/GrayCode-oseq.t0000644000175000017500000002176013704764672015216 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BigInt; use Math::Prime::XS 0.23 'is_prime'; # version 0.23 fix for 1928099 use Test; plan tests => 12; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::GrayCode; use Math::PlanePath::Diagonals; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use Math::PlanePath::Diagonals; # GP-DEFINE read("my-oeis.gp"); sub to_binary_gray { my ($n, $radix) = @_; my $digits = [ digit_split_lowtohigh($n,2) ]; Math::PlanePath::GrayCode::_digits_to_gray_reflected($digits,2); return digit_join_lowtohigh($digits,2); } #------------------------------------------------------------------------------ # A195467 -- rows of Gray permutations by bit widths # (for a time it had been array by anti-diagonals something) # n bits is A062383(n-1) different rows, next strictly higher power of 2 MyOEIS::compare_values (anum => 'A195467', func => sub { my ($count) = @_; my @got; for (my $bits = 0; @got < $count; $bits++) { my @row = (0 .. 2**(2**$bits) - 1); foreach (1 .. 2**$bits) { push @got, @row; @row = map {to_binary_gray($_)} @row; } } $#got = $count-1; return \@got; }); #------------------------------------------------------------------------------ # A048641 - binary Gray cumulative sum MyOEIS::compare_values (anum => 'A048641', func => sub { my ($count) = @_; my @got; my $cumulative = 0; for (my $n = 0; @got < $count; $n++) { $cumulative += to_binary_gray($n); push @got, $cumulative; } return \@got; }); #------------------------------------------------------------------------------ # A048644 - binary Gray cumulative sum difference from triangular(n) MyOEIS::compare_values (anum => 'A048644', func => sub { my ($count) = @_; my @got; my $cumulative = 0; for (my $n = 0; @got < $count; $n++) { $cumulative += to_binary_gray($n); push @got, $cumulative - triangular($n); } return \@got; }); sub triangular { my ($n) = @_; return $n*($n+1)/2; } #------------------------------------------------------------------------------ # A048642 - binary gray cumulative product MyOEIS::compare_values (anum => 'A048642', func => sub { my ($count) = @_; my @got; my $product = Math::BigInt->new(1); for (my $n = 0; @got < $count; $n++) { $product *= (to_binary_gray($n) || 1); push @got, $product; } return \@got; }); #------------------------------------------------------------------------------ # A048643 - binary gray cumulative product, diff to factorial(n) MyOEIS::compare_values (anum => 'A048643', func => sub { my ($count) = @_; my @got; my $factorial = Math::BigInt->new(1); my $product = Math::BigInt->new(1); for (my $n = 0; @got < $count; $n++) { $product *= (to_binary_gray($n) || 1); $factorial *= ($n||1); push @got, $product - $factorial; } return \@got; }); #------------------------------------------------------------------------------ # A143329 - Gray(prime(n)) which is prime too MyOEIS::compare_values (anum => 'A143329', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { next unless is_prime($n); my $gray = to_binary_gray($n); next unless is_prime($gray); push @got, $gray; } return \@got; }); #------------------------------------------------------------------------------ # A143292 - binary Gray of primes MyOEIS::compare_values (anum => 'A143292', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { next unless is_prime($n); push @got, to_binary_gray($n); } return \@got; }); #------------------------------------------------------------------------------ # A005811 - count 1 bits in Gray(n), is num runs MyOEIS::compare_values (anum => 'A005811', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my $gray = to_binary_gray($n); push @got, count_1_bits($gray); } return \@got; }); sub count_1_bits { my ($n) = @_; my $count = 0; while ($n) { $count += ($n & 1); $n >>= 1; } return $count; } #------------------------------------------------------------------------------ # A173318 - cumulative count 1 bits in gray(n) ie. of A005811 MyOEIS::compare_values (anum => 'A173318', func => sub { my ($count) = @_; my @got; my $cumulative = 0; for (my $n = 0; @got < $count; $n++) { $cumulative += count_1_bits(to_binary_gray($n)); push @got, $cumulative; } return \@got; }); #------------------------------------------------------------------------------ # A099891 -- triangle cumulative XOR Grays # 0, # 1, 1, # 3, 2, 3, # 2, 1, 3, 0, # 6, 4, 5, 6, 6, # 7, 1, 5, 0, 6, 0, # 5, 2, 3, 6, 6, 0, 0, # 4, 1, 3, 0, 6, 0, 0, 0, # 12, 8, 9,10,10,12,12,12,12,... # first column Gray codes, then pairs xored by # A # B A^B # binomial mod 2 count of whether each Gray net odd or even into an entry MyOEIS::compare_values (anum => 'A099891', func => sub { my ($count) = @_; my @got; my @array; for (my $y = 0; @got < $count; $y++) { my $gray = to_binary_gray($y); push @array, [ $gray ]; for (my $x = 1; $x <= $y; $x++) { $array[$y][$x] = $array[$y-1][$x-1] ^ $array[$y][$x-1]; } for (my $x = 0; $x <= $y && @got < $count; $x++) { push @got, $array[$y][$x]; } } return \@got; }); # GP-DEFINE LowOneBit(n) = 1<>1); # GP-Test my(v=OEIS_samples("A003188")); \ # GP-Test v == vector(#v,n,n--; Gray(n)) /* OFFSET=0 */ # GP-DEFINE A099891(n,k) = my(B=0); for(i=0,k, if(binomial(k,i)%2, B=bitxor(B,Gray(n-i)))); B; # GP-Test my(v=OEIS_samples("A099891"),l=List()); \ # GP-Test for(n=0,#v, for(k=0,n, if(#l>=#v,break(2)); listput(l,A099891(n,k)))); \ # GP-Test v == Vec(l) #-- # second column k=1 # GP-Test /* my formula */ \ # GP-Test vector(256,n, A099891(n,1)) == \ # GP-Test vector(256,n, A006519(n)) # GP-Test vector(256,n, A099891(n,1)) == \ # GP-Test vector(256,n, bitxor(Gray(n),Gray(n-1))) # GP-Test vector(256,n, A099891(n,1)) == \ # GP-Test vector(256,n, bitxor(bitxor(n,n-1), bitxor(n,n-1)>>1)) # GP-Test /* bitxor(n,n-1) changed bits by increment */ \ # GP-Test vector(256,n, bitxor(n,n-1)) == \ # GP-Test vector(256,n, 2*LowOneBit(n)-1) # GP-Test vector(256,n, bitxor(n,n-1)>>1) == \ # GP-Test vector(256,n, LowOneBit(n)-1) # # GP-Test vector(256,n, A099891(n,1)) == \ # GP-Test vector(256,n, Gray(2*LowOneBit(n)-1)) # # GP-Test my(v=OEIS_samples("A099892")); v == vector(#v,n,n--; A099891(n,n)) # Sierpinski triangle patterns of each bit, until 0s past column 2^k # # for(n=0,64, for(k=0,n, printf(" %2d",A099891(n,k))); print()); # for(n=0,20, for(k=0,n, printf(" %d",A099891(n,k)%2)); print()); # for(n=0,64, for(k=0,n, printf(" %s",if(bitand(A099891(n,k),8),8,"."))); print()); # vector(20,n,n++; A099891(n,n-1)) # not in OEIS: 2, 3, 6, 6, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 24, 24, 0, 0, 0, 0 # vector(20,n,n++; A099891(n,2)) # not in OEIS: 3, 3, 5, 5, 3, 3, 9, 9, 3, 3, 5, 5, 3, 3, 17, 17, 3, 3, 5, 5 #------------------------------------------------------------------------------ # A064706 - binary Gray twice # which is n XOR n>>2 MyOEIS::compare_values (anum => 'A064706', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { push @got, to_binary_gray(to_binary_gray($n)); } return \@got; }); #------------------------------------------------------------------------------ # A055975 - binary Gray increments MyOEIS::compare_values (anum => 'A055975', func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { push @got, to_binary_gray($n+1) - to_binary_gray($n); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/DragonCurve-more.t0000644000175000017500000000533413475604706015726 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2018, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min', 'max'; use Test; plan tests => 28; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::DragonCurve; #------------------------------------------------------------------------------ # Lmin,Lmax Wmin,Wmax claimed in the pod { my $path = Math::PlanePath::DragonCurve->new; my $xmax = 0; my $xmin = 0; my $ymax = 0; my $ymin = 0; my $n = 0; foreach my $level (2, 4, 8, 10, 12, 14, 16) { my $k = $level / 2; my $Nlevel = 2**$level; for ( ; $n <= $Nlevel; $n++) { my ($x,$y) = $path->n_to_xy($n); $xmax = max ($xmax, $x); $xmin = min ($xmin, $x); $ymax = max ($ymax, $y); $ymin = min ($ymin, $y); } my $Lmax = $ymax; my $Lmin = $ymin; my $Wmax = $xmax; my $Wmin = $xmin; foreach (2 .. $k) { ( $Lmax, $Lmin, $Wmax, $Wmin) = (-$Wmin, -$Wmax, $Lmax, $Lmin); # rotate -90 } my $calc_Lmax = calc_Lmax($k); my $calc_Lmin = calc_Lmin($k); my $calc_Wmax = calc_Wmax($k); my $calc_Wmin = calc_Wmin($k); ok ($calc_Lmax, $Lmax, "Lmax k=$k"); ok ($calc_Lmin, $Lmin, "Lmin k=$k"); ok ($calc_Wmax, $Wmax, "Wmax k=$k"); ok ($calc_Wmin, $Wmin, "Wmin k=$k"); } } sub calc_Lmax { my ($k) = @_; # Lmax = (7*2^k - 4)/6 if k even # (7*2^k - 2)/6 if k odd if ($k & 1) { return (7*2**$k - 2) / 6; } else { return (7*2**$k - 4) / 6; } } sub calc_Lmin { my ($k) = @_; # Lmin = - (2^k - 1)/3 if k even # - (2^k - 2)/3 if k odd if ($k & 1) { return - (2**$k - 2) / 3; } else { return - (2**$k - 1) / 3; } } sub calc_Wmax { my ($k) = @_; # Wmax = (2*2^k - 1) / 3 if k even # (2*2^k - 2) / 3 if k odd if ($k & 1) { return (2*2**$k - 1) / 3; } else { return (2*2**$k - 2) / 3; } } sub calc_Wmin { my ($k) = @_; return calc_Lmin($k); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/xt/0-Test-Pod.t0000755000175000017500000000175111655356337014347 0ustar gggg#!/usr/bin/perl -w # 0-Test-Pod.t -- run Test::Pod if available # Copyright 2009, 2010, 2011 Kevin Ryde # 0-Test-Pod.t is shared by several distributions. # # 0-Test-Pod.t 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, or (at your option) any later # version. # # 0-Test-Pod.t 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 file. If not, see . use 5.004; use strict; use Test::More; # all_pod_files_ok() is new in Test::Pod 1.00 # eval 'use Test::Pod 1.00; 1' or plan skip_all => "due to Test::Pod 1.00 not available -- $@"; Test::Pod::all_pod_files_ok(); exit 0; Math-PlanePath-129/xt/oeis-xrefs.t0000755000175000017500000001676713774704253014652 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2015, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Check that OEIS A-numbers listed in lib/Math/PlanePath/Foo.pm files have # code exercising them in one of the xt/oeis/*-oeis.t scripts. # # Check that A-numbers are not duplicated among the .pm files, since that's # often a cut-and-paste mistake. # # Check that A-numbers are not duplicated within an xt/oeis/*-oeis.t script, # since normally only need to exercise a claimed path sequence once. Except # often that's not true since the same sequence can arise in separate ways. # But for now demand duplication is either disguised there or explicitly # listed here. # use 5.005; use strict; use FindBin; use ExtUtils::Manifest; use File::Spec; use File::Slurp; use Test::More; # new in 5.6, so unless got it separately with 5.005 use List::Util 'uniqstr'; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; plan tests => 1; my $toplevel_dir = File::Spec->catdir ($FindBin::Bin, File::Spec->updir); my $manifest_filename = File::Spec->catfile ($toplevel_dir, 'MANIFEST'); my $manifest = ExtUtils::Manifest::maniread ($manifest_filename); my $bad = 0; my $anum_re = qr/A\d{6,7}/; my %allow_POD_duplicates = (Corner => {A000290 => 1, # with different wider A002378 => 1, # with different wider A005563 => 1, # with different wider A014206 => 1, # with different wider A028552 => 1, # with different wider }, DiamondSpiral => {A001105 => 1, # different n_start }, CornerReplicate => {A139351 => 1, # in text and seq list }, CoprimeColumns => {A002088 => 1, # different n_start }, TerdragonCurve => {A057083 => 1, # two uses }, AlternatePaper => {A062880 => 1, # arms=1 and arms=2 }, ); my %allow_checked_not_in_POD = (Corner => {A000007 => 1, # left turn 1 at N=0 only A063524 => 1, # left turn 1 at N=1 only A185012 => 1, # left turn 1 at N=2 only }, TriangleSpiralSkewed => {A081274 => 1, # duplicate of A038764 }, DragonCurve => {A059841 => 1, # 1,0 repeating not interesting A000035 => 1, # 0,1 repeating }, DiagonalRationals => {A060837 => 1, # checked in FactorRationals-oeis.t }, ); #------------------------------------------------------------------------------ # Entries like 'ZOrderCurve' => [ 'A000001', 'A000002', ... ] my %path_seq_anums; foreach my $seq_filename ('lib/Math/NumSeq/PlanePathCoord.pm', 'lib/Math/NumSeq/PlanePathN.pm', 'lib/Math/NumSeq/PlanePathDelta.pm', 'lib/Math/NumSeq/PlanePathTurn.pm', ) { open my $fh, '<', $seq_filename or die "Cannot open $seq_filename"; while (<$fh>) { if (/^\s*# OEIS-(Catalogue|Other):\s+($anum_re)([^#]+)/) { my $anum = $2; my @args = split /\s/, $3; my %args = map { split /=/, $_, 2 } @args; ### %args my $planepath_and_args = $args{'planepath'} || die "Oops, no planepath parameter"; my ($planepath, @planepath_args) = split /,/, $planepath_and_args; push @{$path_seq_anums{$planepath}}, $anum; } } } foreach (values %path_seq_anums) { @$_ = uniqstr(@$_); } #------------------------------------------------------------------------------ my @module_filenames = grep {m{^lib/Math/PlanePath/[^/]+\.pm$}} keys %$manifest; @module_filenames = sort @module_filenames; diag "module count ",scalar(@module_filenames); my @path_names = map {m{([^/]+)\.pm$} or die "Oops, unmatched module filename $_"; $1} @module_filenames; sub path_pod_anums { my ($planepath_name) = @_; my $filename = "lib/Math/PlanePath/$planepath_name.pm"; open my $fh, '<', $filename or die "Oops, cannot open module filename $filename"; my @ret; while (<$fh>) { if (/^ +($anum_re)/) { push @ret, $1; } } return @ret; } sub path_checked_anums { my ($planepath_name) = @_; return (path_xt_anums ($planepath_name), @{$path_seq_anums{$planepath_name} || []}); } sub path_xt_anums { my ($planepath_name) = @_; my @ret; my %seen; foreach my $filename (File::Spec->catfile('xt','oeis',"$planepath_name-oeis.t"), File::Spec->catfile('xt',"$planepath_name-hog.t")) { open my $fh, '<', $filename or next; while (<$fh>) { my $anum; # if (/^[^#]*\$anum = '($anum_re)'/mg) { if (/^[^#]*'($anum_re)'/mg) { $anum = $1; } elsif (/^[^#]*anum => '($anum_re)'/mg) { $anum = $1; } else { next; } push @ret, $anum; if ($seen{$anum}) { print "$filename:$.: duplicate check, previous at line $seen{$anum}\n"; print "$filename:$seen{$anum}: ... previous here\n"; } else { $seen{$anum} = $.; } } } return @ret; } # From among the argument strings, return those which appear more than once. sub str_duplicates { my %seen; return map {$seen{$_}++ == 1 ? ($_) : ()} @_; } foreach my $planepath_name (@path_names) { my @pod_anums = path_pod_anums ($planepath_name); my @checked_anums = path_checked_anums ($planepath_name); my %pod_anums = map {$_=>1} @pod_anums; my %checked_anums = map {$_=>1} @checked_anums; foreach my $anum (str_duplicates(@pod_anums)) { next if $allow_POD_duplicates{$planepath_name}->{$anum}; diag "Math::PlanePath::$planepath_name $anum duplicated within POD"; } @pod_anums = uniqstr(@pod_anums); foreach my $anum (str_duplicates(@checked_anums)) { next if $anum eq 'A000012'; # all ones next if $anum eq 'A000027'; # 1,2,3 naturals next if $anum eq 'A005408'; # odd 2n+1 diag "Math::PlanePath::$planepath_name $anum checked and also catalogued"; } @checked_anums = uniqstr(@checked_anums); diag ""; foreach my $anum (@pod_anums) { next if $anum eq 'A191689'; # CCurve fractal dimension if (! exists $checked_anums{$anum}) { diag "Math::PlanePath::$planepath_name $anum in POD, not checked"; } } foreach my $anum (@checked_anums) { next if $anum eq 'A000004'; # all zeros next if $anum eq 'A000012'; # all ones next if $anum eq 'A001477'; # integers 0,1,2,3 next if $anum eq 'A001489'; # negative integers 0,-1,-2,-3 next if $anum eq 'A000035'; # 0,1 reps next if $anum eq 'A059841'; # 1,0 reps next if $allow_checked_not_in_POD{$planepath_name}->{$anum}; if (! exists $pod_anums{$anum}) { diag "Math::PlanePath::$planepath_name $anum checked, not in POD"; } } } is ($bad, 0); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/Makefile.PL0000755000175000017500000000525213524205127013670 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use ExtUtils::MakeMaker; WriteMakefile (NAME => 'Math::PlanePath', ABSTRACT => 'Mathematical paths through the 2-D plane.', VERSION_FROM => 'lib/Math/PlanePath.pm', PREREQ_PM => { 'Math::Libm' => 0, # for hypot() mainly 'List::Util' => 0, 'constant' => '1.02', # 1.02 for leading underscore 'constant::defer' => 5, # v.5 for Perl 5.6 fixes }, TEST_REQUIRES => { 'Test' => 0, }, AUTHOR => 'Kevin Ryde ', LICENSE => 'gpl_3', SIGN => 1, MIN_PERL_VERSION => '5.004', META_MERGE => { 'meta-spec' => { version => 2 }, resources => { homepage => 'http://user42.tuxfamily.org/math-planepath/index.html', license => 'http://www.gnu.org/licenses/gpl.html', }, no_index => { directory=>['devel','xt'], # these are in Math-PlanePath-Toothpick but added to by # Math::NumSeq::PlanePathCoord here package => [ 'Math::PlanePath::ToothpickTree', 'Math::PlanePath::ToothpickReplicate', 'Math::PlanePath::ToothpickUpist', 'Math::PlanePath::LCornerTree', 'Math::PlanePath::LCornerReplicate', 'Math::PlanePath::OneOfEight', ], }, prereqs => { test => { suggests => { # have "make test" do as much as possible 'Data::Float' => 0, 'Math::BigInt' => 0, 'Math::BigInt::Lite' => 0, 'Math::BigFloat' => '1.993', 'Math::BigRat' => 0, 'Number::Fraction' => 0, }, }, }, }, ); Math-PlanePath-129/examples/0002755000175000017500000000000014001441522013517 5ustar ggggMath-PlanePath-129/examples/knights-oeis.pl0000755000175000017500000000356012041154023016464 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl knights-oeis.pl # # This spot of code prints sequence A068608 of Sloane's On-Line Encyclopedia # of Integer Sequences # # http://oeis.org/A068608 # # which is the infinite knight's tour path of Math::PlanePath::KnightSpiral # with the X,Y positions numbered according to the SquareSpiral and thus # giving an integer sequence # # 1, 10, 3, 16, 19, 22, 9, 12, 15, 18, 7, 24, 11, 14, ... # # All points in the first quadrant are reached by both paths, so this is a # permutation of the integers. # # There's eight variations on the sequence. 2 directions clockwise and # anti-clockwise and 4 sides to start from relative to the side the square # spiral numbering starts from. # # A068608 # A068609 # A068610 # A068611 # A068612 # A068613 # A068614 # A068615 # use 5.004; use strict; use Math::PlanePath::KnightSpiral; use Math::PlanePath::SquareSpiral; my $knights = Math::PlanePath::KnightSpiral->new; my $square = Math::PlanePath::SquareSpiral->new; foreach my $n ($knights->n_start .. 20) { my ($x, $y) = $knights->n_to_xy ($n); my $sq_n = $square->xy_to_n ($x, $y); print "$sq_n, "; } print "...\n"; exit 0; Math-PlanePath-129/examples/a023531.l0000644000175000017500000000547613574274301014621 0ustar gggg; Copyright 2019 Kevin Ryde ; ; This file is part of Math-PlanePath. ; ; Math-PlanePath 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, or (at your option) any later ; version. ; ; Math-PlanePath 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 Math-PlanePath. If not, see . ; a023531.l -- Triangle Spiral by A023531 Turns. ; Kevin Ryde, December 2019 ; ; The turn sequence of Math::PlanePath::TriangleSpiral is OEIS A240025. ; This file is a lightly massaged copy of my upload there. ; Usage: xfractint type=lsystem lfile=a023531.l lname=TriangleSpiral params=9 ; ; Or interactively, the usual key "t", choose type lsystem, "F6" files, ; "F6" again the current directory, choose a023531.l, etc. ; ; "lname" can be TriangleSpiral or TriangleSpiral2 which are the ; variations below. Interactively, "t" and choose type lsystem ; (again) goes to the available L-systems in the current file. ; ; "params=9" is the expansion level (order). This is the number of ; sides in the spiral here. Interactively, key "z" changes just the ; order. ; The symbol string generated is like ; ; S F T + F F T + F F F T + F F F F T + F F F F F T + ; a(n) = 1 0 1 0 0 1 0 0 0 1 0 0 0 0 1 ; n = 0 1 2 3 4 5 6 7 8 9 10 13 14 ; ; F is draw forward. ; Turn a(n) is after each F, and is either "+" for a(n)=1 turn, or ; nothing for a(n)=0 which is no turn. ; T is a non-drawing symbol. It precedes each "+" and its expansion ; increases the length of the preceding run of Fs which are a(n)=0s ; and which are the preceding side. ; ; The morphism given in the comments in A023531 has 1->0,1 which here ; would be a rule like "+ = F+". But Fractint doesn't allow rewrite ; of "+", hence T before each + to achieve the same result. TriangleSpiral { Angle 3 ; 120 degrees Axiom S S = SFT+ T = FT } ; A little variation can be made by putting the T before each run of ; Fs instead of after. The symbol string generated is then like ; ; S T F + T F F + T F F F + T F F F F + T F F F F F + ; ; T is still used to increase the length of the Fs, but the Fs following it. ; In this form, T is also at the start of the string which makes it a ; little less like the morphism 1->0,1. TriangleSpiral2 { Angle 3 ; 120 degrees Axiom S S = STF+ T = TF } ; Local variables: ; compile-command: "xfractint type=lsystem lfile=a023531.l lname=TriangleSpiral params=9" ; End: Math-PlanePath-129/examples/cellular-rules.pl0000755000175000017500000000636112041153426017023 0ustar gggg#!/usr/bin/perl -w # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl cellular-rules.pl # # Print the patterns from the CellularRule paths with "*"s. # Rules with the same output are listed together. # # Implementation: # # Points are plotted by looping $n until its $y coordinate is beyond the # desired maximum rows. @rows is an array of strings of length 2*size+1 # spaces each in which "*"s are applied to plot points. # # Another way to plot would be to loop over $x,$y for the desired rectangle # and look at $n=$path->xy_to_n($x,$y) to see which cells have defined($n). # Characters could be appended or join(map{}) to make an output $str in that # case. Going by $n should be fastest for sparse patterns, though # CellularRule is not blindingly quick either way. # # See Cellular::Automata::Wolfram for the same but with more options and a # graphics file output. # use 5.004; use strict; use Math::PlanePath::CellularRule; my $numrows = 15; # size of each printout my %seen; my $count = 0; my $mirror_count = 0; my $finite_count = 0; my @strs; my @rules_list; my @mirror_of; foreach my $rule (0 .. 255) { my $path = Math::PlanePath::CellularRule->new (rule => $rule); my @rows = (' ' x (2*$numrows+1)) x ($numrows+1); # strings of spaces for (my $n = $path->n_start; ; $n++) { my ($x,$y) = $path->n_to_xy($n) or last; # some patterns are only finitely many N values last if $y > $numrows; # stop at $numrows+1 many rows substr($rows[$y], $x+$numrows, 1) = '*'; } @rows = reverse @rows; # print rows going up the page my $str = join("\n",@rows); # string of all rows my $seen_rule = $seen{$str}; # possible previous rule giving this $str if (defined $seen_rule) { # $str is a repeat of an output already seen, note this $rule with that $rules_list[$seen_rule] .= ",$rule"; next; } my $mirror_str = join("\n", map {scalar(reverse)} @rows); my $mirror_rule = $seen{$mirror_str}; if (defined $mirror_rule) { $mirror_of[$mirror_rule] = " (mirror image is rule $rule)"; $mirror_of[$rule] = " (mirror image of rule $mirror_rule)"; $mirror_count++; } $strs[$rule] = $str; $rules_list[$rule] = $rule; $seen{$str} = $rule; $count++; if ($rows[0] =~ /^ *$/) { $finite_count++; } } foreach my $rule (0 .. 255) { my $str = $strs[$rule] || next; print "rule=$rules_list[$rule]", $mirror_of[$rule]||'', "\n"; print "\n$strs[$rule]\n\n"; } my $unmirrored_count = $count - $mirror_count; print "Total $count different rule patterns\n"; print "$mirror_count are mirror images of another\n"; print "$finite_count stop after a few cells\n"; exit 0; Math-PlanePath-129/examples/koch-svg.pl0000755000175000017500000000531012041154170015577 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl koch-svg.pl >output.svg # perl koch-svg.pl LEVEL >output.svg # # Print SVG format graphics to standard output for a Koch snowflake curve of # given LEVEL fineness. The default level is 4. # # The range of N values to plot follows the formulas in the # Math::PlanePath::KochSnowflakes module POD. # # The svg output size is a fixed 300x300, but of course the point of svg is # that it can be resized by a graphics viewer program. use 5.006; use strict; use warnings; use List::Util 'min'; use Math::PlanePath::KochSnowflakes; my $path = Math::PlanePath::KochSnowflakes->new; my $level = $ARGV[0] || 4; my $width = 300; my $height = 300; # use the svg transform="translate()" to centre the origin in the viewport, # but don't use its scale() to shrink the path X,Y coordinates, just in case # the factor 1/4^level becomes very small my $xcentre = $width / 2; my $ycentre = $height / 2; print <<"HERE"; Koch Snowflake level $level HERE # factor to make equilateral triangles from the integer Y out of KochSnowflakes my $y_equilateral = sqrt(3); my $path_width = 2 * 3**$level; my $path_height = 2 * (2/3) * 3**$level * $y_equilateral; my $scale = 0.9 * min ($width / $path_width, $height / $path_height); my $linewidth = 1/$level; # N range for $level, per KochSnowflakes POD my $n_lo = 4**$level; my $n_hi = 4**($level+1) - 1; my $points = ''; foreach my $n ($n_lo .. $n_hi) { my ($x, $y) = $path->n_to_xy($n); $x *= $scale; $y *= $scale; $y *= $y_equilateral; $points .= "\n $x, $y"; } print <<"HERE" HERE Math-PlanePath-129/examples/ulam-spiral-xpm.pl0000755000175000017500000000600512041155744017120 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl ulam-spiral-xpm.pl >/tmp/foo.xpm # write image file # xzgv /tmp/foo.xpm # view file # # This is a bit of fun drawing Ulam's spiral of primes in the SquareSpiral # path. The output is XPM format (which is plain text) and any good image # viewer program should display it. # # Optional args # # perl ulam-spiral-xpm.pl SIZE # or # perl ulam-spiral-xpm.pl SIZE SCALE # # make the image SIZExSIZE pixels, and SCALE to expand each point to a # SCALExSCALE square instead of a single pixel. # use 5.004; use strict; use Math::PlanePath::SquareSpiral; my $size = 200; my $scale = 1; if (@ARGV >= 2) { $scale = $ARGV[1]; } if (@ARGV >= 1) { $size = $ARGV[0]; } my $path = Math::PlanePath::SquareSpiral->new; my $x_origin = int($size / 2); my $y_origin = int($size / 2); my ($n_lo, $n_hi) = $path->rect_to_n_range (-$x_origin, -$y_origin, -$x_origin+$size, -$y_origin+$size); # Find the prime numbers 2 to $n_hi by sieve of Eratosthenes. # Could also use Math::Prime::TiedArray or Math::Prime::XS. # my @primes = (0, # 0 0, # 1 1, # 2 prime 1, # 3 prime (0,1) x ($n_hi/2)); # rest alternately even/odd my $i = 3; foreach my $i (3 .. int(sqrt($n_hi)) + 1) { next unless $primes[$i]; foreach (my $j = 2*$i; $j <= $n_hi; $j += $i) { $primes[$j] = 0; } } # Draw the primes into an array of rows strings. # my @rows = (' ' x $size) x $size; foreach my $n ($n_lo .. $n_hi) { next unless $primes[$n]; my ($x, $y) = $path->n_to_xy ($n); $x = $x + $x_origin; $y = $y_origin - $y; # inverted # $n_hi is an over-estimate in general, check x,y actually in desired size if ($x >= 0 && $x < $size && $y >= 0 && $y < $size) { substr ($rows[$y], $x,1) = '*'; } } # Expand @rows points by $scale, horizontally and vertically. # if ($scale > 1) { foreach (@rows) { s{(.)}{$1 x $scale}eg; # expand horizontally } @rows = map { ($_) x $scale} @rows; # expand vertically $size *= $scale; } # XPM format is easy to print. # Output is about 1 byte per pixel. # print <<"HERE"; /* XPM */ static char *ulam_spiral_xpm_pl[] = { "$size $size 2 1", " c black", "* c white", HERE foreach my $row (@rows) { print "\"$row\",\n"; } print "};\n"; exit 0; Math-PlanePath-129/examples/sacks-xpm.pl0000755000175000017500000000362712041155624016002 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl sacks-xpm.pl >/tmp/foo.xpm # write image file # xgzv /tmp/foo.xpm # view file # # This spot of code generates a big .xpm file showing all points of the # SacksSpiral. XPM is a text format and can be generated quite easily as # row strings. Use a graphics viewer program to look at it. # use 5.004; use strict; use POSIX (); use Math::PlanePath::SacksSpiral; my $width = 800; my $height = 600; my $spacing = 10; my $path = Math::PlanePath::SacksSpiral->new; my $x_origin = int($width / 2); my $y_origin = int($height / 2); my $n_max = ($x_origin/$spacing+2)**2 + ($y_origin/$spacing+2)**2; my @rows = (' ' x $width) x $height; foreach my $n ($path->n_start .. $n_max) { my ($x, $y) = $path->n_to_xy ($n); $x *= $spacing; $y *= $spacing; $x = $x + $x_origin; $y = $y_origin - $y; # inverted $x = POSIX::floor ($x + 0.5); # round $y = POSIX::floor ($y + 0.5); if ($x >= 0 && $x < $width && $y >= 0 && $y < $height) { substr ($rows[$y], $x,1) = '*'; } } print <<"HERE"; /* XPM */ static char *sacks_xpm_pl[] = { "$width $height 2 1", " c black", "* c white", HERE foreach my $row (@rows) { print "\"$row\",\n"; } print "};\n"; exit 0; Math-PlanePath-129/examples/hilbert-path.pl0000755000175000017500000000503212041154004016436 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl hilbert-lines.pl # # This is a bit of fun printing the HilbertCurve path in ascii. It follows # the terminal width if you've got Term::Size, otherwise 79x23. # # Enough curve is drawn to fill the whole output size, clipped when the path # goes outside the output bounds. You could instead stop at say # # $n_hi = 2**6; # # to see just a square portion of the curve. # # The $scale variable spaces out the points. 3 apart is good, or tighten it # up to 2 to fit more on the screen. # # The output has Y increasing down the screen. It could be instead printed # up the screen in the final output by going $y from $height-1 down to 0. # use 5.004; use strict; use Math::PlanePath::HilbertCurve; my $width = 79; my $height = 23; my $scale = 3; if (eval { require Term::Size }) { my ($w, $h) = Term::Size::chars(); if ($w) { $width = $w - 1; } if ($h) { $height = $h - 1; } } my $x = 0; my $y = 0; my %grid; # write $char at $x,$y in %grid sub plot { my ($char) = @_; if ($x < $width && $y < $height) { $grid{$x}{$y} = $char; } } # at the origin 0,0 plot('+'); my $path = Math::PlanePath::HilbertCurve->new; my $path_width = int($width / $scale) + 1; my $path_height = int($height / $scale) + 1; my ($n_lo, $n_hi) = $path->rect_to_n_range (0,0, $path_width,$path_height); foreach my $n (1 .. $n_hi) { my ($next_x, $next_y) = $path->n_to_xy ($n); $next_x *= $scale; $next_y *= $scale; while ($x > $next_x) { # draw to left $x--; plot ('-'); } while ($x < $next_x) { # draw to right $x++; plot ('-'); } while ($y > $next_y) { # draw up $y--; plot ('|'); } while ($y < $next_y) { # draw down $y++; plot ('|'); } plot ('+'); } foreach my $y (0 .. $height-1) { foreach my $x (0 .. $width-1) { print $grid{$x}{$y} || ' '; } print "\n"; } exit 0; Math-PlanePath-129/examples/rationals-tree.pl0000755000175000017500000001067512136175114017027 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl rationals-tree.pl # # Print the RationalsTree paths in tree form. # use 5.004; use strict; use List::Util 'max'; use Math::PlanePath::RationalsTree; use Math::PlanePath::FractionsTree; sub print_as_fractions { my ($path) = @_; my $n = $path->n_start; foreach (1) { my ($x,$y) = $path->n_to_xy($n++); print centre("$x/$y",64); } print "\n"; print " /------------- -------------\\\n"; foreach (1 .. 2) { my ($x,$y) = $path->n_to_xy($n++); print centre("$x/$y",32); } print "\n"; print " /---- ----\\ /---- ----\\\n"; foreach (1 .. 4) { my ($x,$y) = $path->n_to_xy($n++); print centre("$x/$y",16); } print "\n"; print " / \\ / \\ / \\ / \\\n"; foreach (1 .. 8) { my ($x,$y) = $path->n_to_xy($n++); print centre("$x/$y",8); } print "\n"; print " / \\ / \\ / \\ / \\ / \\ / \\ / \\ / \\\n"; foreach (16 .. 31) { my ($x,$y) = $path->n_to_xy($n++); print centre("$x/$y",4); } print "\n"; print "\n"; } sub centre { my ($str, $width) = @_; my $extra = max (0, $width - length($str)); my $left = int($extra/2); my $right = $extra - $left; return ' 'x$left . $str . ' 'x$right; } sub xy_to_cfrac_str { my ($x,$y) = @_; my @quotients; while ($x > 0 && $y > 0) { push @quotients, int($x/$y); $x %= $y; ($x,$y) = ($y,$x); } return "[".join(',',@quotients)."]"; } sub print_as_cfracs { my ($path) = @_; my $n = $path->n_start; foreach (1) { my ($x,$y) = $path->n_to_xy($n++); print centre(xy_to_cfrac_str($x,$y), 72); } print "\n"; print " /--------------- ---------------\\\n"; foreach (1 .. 2) { my ($x,$y) = $path->n_to_xy($n++); print centre(xy_to_cfrac_str($x,$y), 36); } print "\n"; print " /----- -----\\ /----- -----\\\n"; foreach (1 .. 4) { my ($x,$y) = $path->n_to_xy($n++); print centre(xy_to_cfrac_str($x,$y), 18); } print "\n"; print " / \\ / \\ / \\ / \\\n"; foreach (1 .. 8) { my ($x,$y) = $path->n_to_xy($n++); print centre(xy_to_cfrac_str($x,$y), 9); } print "\n"; print "\n"; } #------------------------------------------------------------------------------ my $rationals_type_arrayref = Math::PlanePath::RationalsTree->parameter_info_hash()->{'tree_type'}->{'choices'}; my $fractions_type_arrayref = Math::PlanePath::FractionsTree->parameter_info_hash()->{'tree_type'}->{'choices'}; print "RationalsTree\n"; print "-------------\n\n"; foreach my $tree_type (@$rationals_type_arrayref) { print "$tree_type tree\n"; my $path = Math::PlanePath::RationalsTree->new (tree_type => $tree_type); print_as_fractions ($path); } print "\n"; print "FractionsTree\n"; print "-------------\n\n"; foreach my $tree_type (@$fractions_type_arrayref) { print "$tree_type tree\n"; my $path = Math::PlanePath::FractionsTree->new (tree_type => $tree_type); print_as_fractions ($path); } print "\n"; print "-----------------------------------------------------------------------\n"; print "Or written as continued fraction quotients.\n"; print "\n"; print "RationalsTree\n"; print "-------------\n\n"; foreach my $tree_type (@$rationals_type_arrayref) { print "$tree_type tree\n"; my $path = Math::PlanePath::RationalsTree->new (tree_type => $tree_type); print_as_cfracs ($path); } print "\n"; print "FractionsTree\n"; print "-------------\n\n"; foreach my $tree_type (@$fractions_type_arrayref) { print "$tree_type tree\n"; my $path = Math::PlanePath::FractionsTree->new (tree_type => $tree_type); print_as_cfracs ($path); } exit 0; Math-PlanePath-129/examples/cretan-walls.pl0000755000175000017500000000436011746612502016467 0ustar gggg#!/usr/bin/perl -w # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl cretan-walls.pl # # This is a bit of fun carving out the CretanLabyrinth from a solid block of # "*"s, thus leaving those "*"s representing the walls of the labyrinth. # # The $spacing variable is how widely to spread the path, for thicker walls. # The $width,$height sizes are chosen to make a whole 4-way cycle. # # The way the arms align means the entrance to the labyrinth is at the # bottom right corner. In real labyrinths its usual to omit the lower right # bit of wall so the entrance is in the middle of the right side. # use 5.004; use strict; use Math::PlanePath::CretanLabyrinth; my $spacing = 2; my $width = $spacing * 14 - 1; my $height = $spacing * 16 - 1; my $path = Math::PlanePath::CretanLabyrinth->new; my $x_origin = int($width / 2) + $spacing; my $y_origin = int($height / 2); my @rows = ('*' x $width) x $height; # array of strings sub plot { my ($x,$y,$char) = @_; if ($x >= 0 && $x < $width && $y >= 0 && $y < $height) { substr($rows[$y], $x, 1) = $char; } } my ($n_lo, $n_hi) = $path->rect_to_n_range (-$x_origin,-$y_origin, $x_origin,$y_origin); my $x = $x_origin; my $y = $y_origin; plot($x,$y,'_'); foreach my $n ($n_lo+1 .. $n_hi) { my ($next_x, $next_y) = $path->n_to_xy ($n); $next_x *= $spacing; $next_y *= $spacing; $next_x += $x_origin; $next_y += $y_origin; while ($x != $next_x) { $x -= ($x <=> $next_x); plot($x,$y,' '); } while ($y != $next_y) { $y -= ($y <=> $next_y); plot($x,$y,' '); } } foreach my $row (reverse @rows) { print "$row\n"; } exit 0; Math-PlanePath-129/examples/hilbert-oeis.pl0000755000175000017500000000425612066001433016454 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl hilbert-oeis.pl # # This spot of code prints sequence A163359 of Sloane's On-Line Encyclopedia # of Integer Sequences # # http://oeis.org/A163359 # # which is the Hilbert curve N values which occur on squares numbered # diagonally in the style of Math::PlanePath::Diagonals, # # 0, 3, 1, 4, 2, 14, 5, 7, 13, 15, 58, 6, 8, 12, 16, 59, ... # # All points in the first quadrant are reached by both paths, so this is a # re-ordering or the non-negative integers. # # In the code there's a double transpose going on. A163359 is conceived as # the Hilbert starting downwards and the diagonals numbered from the X axis, # but the HilbertCurve code goes to the right first and the Diagonals module # numbers from the Y axis. The effect is the same, ie. that the first # Hilbert step is the opposite axis as the diagonals are numbered from. # # Diagonals option direction=>up could be added to transpose $x,$y to make # the first Hilbert step the same axis as the diagonal numbering. Doing so # would give sequence A163357. # use 5.004; use strict; use Math::PlanePath::HilbertCurve; use Math::PlanePath::Diagonals; my $hilbert = Math::PlanePath::HilbertCurve->new; my $diagonal = Math::PlanePath::Diagonals->new; print "A163359: "; foreach my $n ($diagonal->n_start .. 19) { my ($x, $y) = $diagonal->n_to_xy ($n); # X,Y points by diagonals my $hilbert_n = $hilbert->xy_to_n ($x, $y); # hilbert N at those points print "$hilbert_n, "; } print "...\n"; exit 0; Math-PlanePath-129/examples/other/0002755000175000017500000000000014001441522014640 5ustar ggggMath-PlanePath-129/examples/other/sierpinski-triangle.m40000644000175000017500000000243512241344134021074 0ustar ggggdivert(-1) # Copyright 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: m4 sierpinski-triangle.m4 # # Plot points of the Sierpinski triangle using a bitwise-and to decide # whether a given X,Y point should be a "*" or a space. # # forloop(varname, start,end, body) # Expand body with varname successively define()ed to integers "start" to # "end" inclusive. "start" to "end" can go either increasing or decreasing. define(`forloop', `define(`$1',$2)$4`'dnl ifelse($2,$3,,`forloop(`$1',eval($2 + 2*($2 < $3) - 1), $3, `$4')')') divert`'dnl forloop(`y',15,0, `forloop(`i',0,y,` ')dnl indent y many spaces forloop(`x',0,15, `ifelse(eval(x&y),0,` *',` ')') ') Math-PlanePath-129/examples/other/sierpinski-triangle-text.gnuplot0000755000175000017500000000332012062333616023227 0ustar gggg#!/usr/bin/gnuplot # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: gnuplot sierpinski-triangle-text.gnuplot # # Print the Sierpinski triangle pattern with spaces and stars using # bitwise-and to decide whether or not to plot each X,Y. # # * # * * # * * # * * * * # * * # * * * * # * * * * # * * * * * * * * # # Return a space or star string to print at x,y. # Must have x=0 && ((y+x)%2)==0 && ((y+x)&(y-x))==0 ? "*" : " ") # Return a string which is row y of the triangle from character # position x through to the right hand end x==y, inclusive. row(x,y) = (x<=y ? char(x,y).row(x+1,y) : "\n") # Return a string of stars, spaces and newlines which is the # Sierpinski triangle rows from y to limit, inclusive. # The first row is y=0. triangle(y,limit) = (y <= limit ? row(-limit,y).triangle(y+1,limit) : "") # Print rows 0 to 15, which is the order 4. print triangle(0,15) exit Math-PlanePath-129/examples/other/dragon-curve.logo0000755000175000017500000000554512335526416020146 0ustar gggg#!/usr/bin/ucblogo ; Copyright 2012, 2013, 2014 Kevin Ryde ; ; This file is part of Math-PlanePath. ; ; Math-PlanePath 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, or (at your option) any later ; version. ; ; Math-PlanePath 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 Math-PlanePath. If not, see . ; Usage: ucblogo dragon-curve-turns.logo ; ; Plot the dragon curve using bit-twiddling to turn the turtle left or ; right, as described for example in "Turn" of ; Math::PlanePath::DragonCurve and variously elsewhere. ; ; The commented out "dragon.chamfer 256" is an alternative plot with ; the corners rounded off to help see the shape. ; ; ; See also: ; ; Mark Horney, "Fractals I: Making Recursion Visible", Logo Exchange, ; Volume 9, number 1, September 1990, pages 23-29. ; Mark Horney, "Fractals II: Representation, Logo Exchange, Volume 9, ; number 2, October 1990, pages 26-29. ; http://el.media.mit.edu/logo-foundation/pubs/nlx.html ; http://el.media.mit.edu/logo-foundation/pubs/nlx/v9/Vol9No1.pdf ; http://el.media.mit.edu/logo-foundation/pubs/nlx/v9/Vol9No2.pdf ; Return the bit above the lowest 1-bit in :n. ; If :n = binary "...z100..00" then the return is "z000..00". ; Eg. n=22 is binary 10110 the lowest 1-bit is the "...1." and the return is ; bit above that "..1.," which is 4. to bit.above.lowest.1bit :n output bitand :n (1 + (bitxor :n (:n - 1))) end ; Return angle +90 or -90 for dragon curve turn at point :n. ; The curve is reckoned as starting from n=0 so the first turn is at n=1. to dragon.turn.angle :n output ifelse (bit.above.lowest.1bit :n) = 0 [90] [-90] end ; Draw :steps many segments of the dragon curve. to dragon :steps localmake "step.len 12 ; length of each step repeat :steps [ forward :step.len left dragon.turn.angle repcount ; repcount = 1 to :steps inclusive ] end ; Draw :steps many segments of the dragon curve, with corners chamfered ; off with little 45-degree diagonals. ; Done this way the vertices don't touch. to dragon.chamfer :steps localmake "step.len 12 ; length of each step localmake "straight.frac 0.5 ; fraction of the step to go straight localmake "straight.len :step.len * :straight.frac localmake "diagonal.len (:step.len - :straight.len) * sqrt(1/2) repeat :steps [ localmake "turn (dragon.turn.angle repcount)/2 ; +45 or -45 forward :straight.len left :turn forward :diagonal.len left :turn ] end dragon 256 ; dragon.chamfer 256 Math-PlanePath-129/examples/other/fibonacci-word-fractal.logo0000755000175000017500000000435112335737560022050 0ustar gggg#!/usr/bin/ucblogo ; Copyright 2014 Kevin Ryde ; ; This file is part of Math-PlanePath. ; ; Math-PlanePath 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, or (at your option) any later ; version. ; ; Math-PlanePath 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 Math-PlanePath. If not, see . ; Usage: ucblogo fibonacci-word-fractal.logo ; ; Draw the Fibonacci word fractal. fibonacci.word.fractal draws any ; given number of steps. The self-similar nature of the pattern is ; best seen by making it a Fibonacci number, hence 377 below. ; ; The turns are based on the Fibonacci word values which are 0 or 1. ; Those values are calculated by the least significant bit of the ; fibbinary values. Fibbinary values are integers which have no "11" ; adjacent 1-bits. They're iterated by some bit twiddling. ; Return the low 1-bits of :n ; For example if n = binary 10110111 = 183 ; then return binary 111 = 7 to low.ones :n output ashift (bitxor :n (:n+1)) -1 end ; :fibbinary should be a fibbinary value ; return the next larger fibbinary value to fibbinary.next :fibbinary localmake "filled bitor :fibbinary (ashift :fibbinary -1) localmake "mask low.ones :filled output (bitor :fibbinary :mask) + 1 end to fibonacci.word.fractal :steps localmake "step.length 5 ; length of each step localmake "fibbinary 0 repeat :steps [ forward :step.length if (bitand 1 :fibbinary) = 0 [ ifelse (bitand repcount 1) = 1 [right 90] [left 90] ] make "fibbinary fibbinary.next :fibbinary ] end setheading 0 ; initial line North fibonacci.word.fractal 377 ;------------------------------------------------------------------------------ ; Print the fibbinary values as iterated by fibbinary.next. ; ; make "fibbinary 0 ; repeat 20 [ ; print :fibbinary ; make "fibbinary fibbinary.next :fibbinary ; ] Math-PlanePath-129/examples/other/sierpinski-triangle-replicate.gnuplot0000755000175000017500000000324712041164144024216 0ustar gggg#!/usr/bin/gnuplot # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: gnuplot sierpinski-triangle-replicate.gnuplot # # Plot points of the Sierpinski triangle by replicating sub-parts of # the pattern according to parameter t in ternary. # # The alignment relative to the Y axis can be changed by what # digit_to_x() does. For example to plot half, # # digit_to_x(d) = (d<2 ? 0 : 1) # # triangle_x(n) and triangle_y(n) return X,Y coordinates for the # Sierpinski triangle point number n, for integer n. triangle_x(n) = (n > 0 ? 2*triangle_x(int(n/3)) + digit_to_x(int(n)%3) : 0) triangle_y(n) = (n > 0 ? 2*triangle_y(int(n/3)) + digit_to_y(int(n)%3) : 0) digit_to_x(d) = (d==0 ? 0 : d==1 ? -1 : 1) digit_to_y(d) = (d==0 ? 0 : 1) # Plot the Sierpinski triangle to "level" many replications. # "trange" and "samples" are chosen so the parameter t runs through # integers t=0 to 3**level-1, inclusive. # level=6 set trange [0:3**level-1] set samples 3**level set parametric set key off plot triangle_x(t), triangle_y(t) with points pause 100Math-PlanePath-129/examples/other/dragon-curve.m40000644000175000017500000001373012221425116017503 0ustar ggggdivert(-1) # Copyright 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: m4 dragon.m4 # # This is a bit of fun generating the dragon curve with the predicate # algorithms of xy_is_visited() from DragonMidpoint and DragonCurve. The # output is generated row by row and and column by column with no image # array or storage. # # The macros which return a pair of values x,y expand to an unquoted 123,456 # which is suitable as arguments to a further macro. The quoting is slack # because the values are always integers and so won't suffer unwanted macro # expansion. # 0,1 Vertex and segment x,y numbering. # | # | Segments are numbered as if a # |s=0,1 square grid turned anti-clockwise # | by 45 degrees. # | # -1,0 -------- 0,0 -------- 1,0 vertex_to_seg_east(x,y) returns # s=-1,1 | s=0,0 the segment x,y to the East, # | so vertex_to_seg_east(0,0) is 0,0 # | # |s=-1,0 vertex_to_seg_west(x,y) returns # | the segment x,y to the West, # 0,-1 so vertex_to_seg_west(0,0) is -1,1 # define(`vertex_to_seg_east', `eval($1 + $2), eval($2 - $1)') define(`vertex_to_seg_west', `eval($1 + $2 - 1), eval($2 - $1 + 1)') define(`vertex_to_seg_south', `eval($1 + $2 - 1), eval($2 - $1)') # Some past BSD m4 didn't have "&" operator, so mod2(n) using % instead. # mod2() returns 0,1 even if "%" gives -1 for negative odds. # define(`mod2', `ifelse(eval($1 % 2),0,0,1)') # seg_to_even(x,y) returns x,y moved to an "even" position by subtracting an # offset in a way which suits the segment predicate test. # # seg_offset_y(x,y) is a repeating pattern # # | 1,1,0,0 # | 1,1,0,0 # | 0,0,1,1 # | 0,0,1,1 # +--------- # # seg_offset_x(x,y) is the same but offset by 1 in x,y # # | 0,1,1,0 # | 1,0,0,1 # | 1,0,0,1 # | 0,1,1,0 # +--------- # # Incidentally these offset values also give n which is the segment number # along the curve. "x_offset XOR y_offset" is 0,1 and is a bit of n from # low to high. # define(`seg_offset_y', `mod2(eval(($1 >> 1) + ($2 >> 1)))') define(`seg_offset_x', `seg_offset_y(eval($1+1), eval($2+1))') define(`seg_to_even', `eval($1 - seg_offset_x($1,$2)), eval($2 - seg_offset_y($1,$2))'); # xy_div_iplus1(x,y) returns x,y divided by complex number i+1. # So (x+i*y)/(i+1) which means newx = (x+y)/2, newy = (y-x)/2. # Must have x,y "even", meaning x+y even, so newx and newy are integers. # define(`xy_div_iplus1', `eval(($1 + $2)/2), eval(($2 - $1)/2)') # seg_is_final(x,y) returns 1 if x,y is one of the final four points. # On these four points xy_div_iplus1(seg_to_even(x,y)) returns x,y # unchanged, so the seg_pred() recursion does not reduce any further. # # .. | .. # final | final y=+1 # final | final y=0 # -------+-------- # .. | .. # x=-1 x=0 # define(`seg_is_final', `eval(($1==-1 || $1==0) && ($2==1 || $2==0))') # seg_pred(x,y) returns 1 if segment x,y is on the dragon curve. # If the final point reached is 0,0 then the original x,y was on the curve. # (If a different final point then x,y was one of four rotated copies of the # curve.) # define(`seg_pred', `ifelse(seg_is_final($1,$2), 1, `eval($1==0 && $2==0)', `seg_pred(xy_div_iplus1(seg_to_even($1,$2)))')') # vertex_pred(x,y) returns 1 if point x,y is on the dragon curve. # The curve always turns left or right at a vertex, it never crosses itself, # so if a vertex is visited then either the segment to the east or to the # west must have been traversed. Prefer ifelse() for the two checks since # eval() || operator is not a short-circuit. # define(`vertex_pred', `ifelse(seg_pred(vertex_to_seg_east($1,$2)),1,1, `seg_pred(vertex_to_seg_west($1,$2))')') # forloop(varname, start,end, body) # Expand body with varname successively define()ed to integers "start" to # "end" inclusive. "start" to "end" can go either increasing or decreasing. # define(`forloop', `define(`$1',$2)$4`'dnl ifelse($2,$3,,`forloop(`$1',eval($2 + 2*($2 < $3) - 1), $3, `$4')')') #---------------------------------------------------------------------------- # dragon01(xmin,xmax, ymin,ymax) prints an array of 0s and 1s which are the # vertex_pred() values. `y' runs from ymax down to ymin so that y # coordinate increases up the screen. # define(`dragon01', `forloop(`y',$4,$3, `forloop(`x',$1,$2, `vertex_pred(x,y)') ')') # dragon_ascii(xmin,xmax, ymin,ymax) prints an ascii art dragon curve. # Each y value results in two output lines. The first has "+" vertices and # "--" horizontals. The second has "|" verticals. # define(`dragon_ascii', `forloop(`y',$4,$3, `forloop(`x',$1,$2, `ifelse(vertex_pred(x,y),1, `+', ` ')dnl ifelse(seg_pred(vertex_to_seg_east(x,y)), 1, `--', ` ')') forloop(`x',$1,$2, `ifelse(seg_pred(vertex_to_seg_south(x,y)), 1, `| ', ` ')') ')') #-------------------------------------------------------------------------- divert`'dnl # 0s and 1s directly from vertex_pred(). # dragon01(-7,23, dnl X range -11,10) dnl Y range # ASCII art lines. # dragon_ascii(-6,5, dnl X range -10,2) dnl Y range Math-PlanePath-129/examples/other/sierpinski-triangle-bitand.gnuplot0000755000175000017500000000264612177346233023524 0ustar gggg#!/usr/bin/gnuplot # Copyright 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: gnuplot sierpinski-triangle-replicate.gnuplot # # Plot points of the Sierpinski triangle by a bitwise-and to decide # whether a given X,Y point should be plotted. Points not wanted are # suppressed by returning NaN. level=6 size=2**level # Return X,Y grid coordinates ranging X=0 to size-1 and Y=0 to size-1, # as t ranges 0 to size*size-1. x(t) = int(t) % size y(t) = int(t / size) # Return true if the X,Y coordinates at t are wanted for the # Sierpinski triangle. want(t) = ((x(t) & y(t)) == 0) triangle_x(t) = (want(t) ? x(t) : NaN) triangle_y(t) = (want(t) ? y(t) : NaN) set parametric set trange [0:size*size-1] set samples size*size set key off plot triangle_x(t),triangle_y(t) with points pause 100 Math-PlanePath-129/examples/other/flowsnake-ascii-small.gp0000644000175000017500000001000112544112624021353 0ustar gggg\\ Copyright 2015 Kevin Ryde \\ This file is part of Math-PlanePath. \\ \\ Math-PlanePath 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, or (at your option) any later \\ version. \\ \\ Math-PlanePath 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 Math-PlanePath. If not, see . \\ This is a bit of fun drawing the flowsnake in ascii art for \\ http://codegolf.stackexchange.com/questions/50521/ascii-art-of-the-day-2-flow-snakes \\ ____ \\ ____ \__ \ \\ \__ \__/ / __ \\ __/ ____ \ \ \ ____ \\ / __ \__ \ \/ / __ \__ \ \\ ____ \ \ \__/ / __ \/ / __/ / __ \\ ____ \__ \ \/ ____ \/ / __/ / __ \ \ \ \\ \__ \__/ / __ \__ \__/ / __ \ \ \ \/ \\ __/ ____ \ \ \__/ ____ \ \ \ \/ / __ \\ / __ \__ \ \/ ____ \__ \ \/ / __ \/ / \\ \ \ \__/ / __ \__ \__/ / __ \ \ \__/ \\ \/ ____ \/ / __/ ____ \ \ \ \/ ____ \\ \__ \__/ / __ \__ \ \/ / __ \__ \ \\ __/ ____ \ \ \__/ / __ \/ / __/ / __ \\ / __ \__ \ \/ ____ \/ / __/ / __ \/ / \\ \/ / __/ / __ \__ \__/ / __ \/ / __/ \\ __/ / __ \ \ \__/ ____ \ \ \__/ / __ \\ / __ \ \ \ \/ ____ \__ \ \/ ____ \/ / \\ \ \ \ \/ / __ \__ \__/ / __ \__ \__/ \\ \/ / __ \/ / __/ ____ \ \ \__/ \\ \ \ \__/ / __ \__ \ \/ \\ \/ \ \ \__/ / __ \\ \/ ____ \/ / \\ \__ \__/ \\ __/ \\ \\ Each hexagon of the flowsnake is 2 characters and a line segment does \\ across its corners either by __, / or \. The loop goes over x,y and \\ calculates which of these to show at each location. Only moderate \\ attempts at minimizing. \\ \\ The code expresses a complex number z in base b=2+w and digits 0, 1, w^2, \\ ..., w^5, where w=e^(2pi/6) sixth root of unity. Those digits are kept \\ just as a distinguishing 1 to 7 then taken high to low for net rotation. \\ \\ This is in the style of Ed Shouten's http://80386.nl/projects/flowsnake/ \\ (xytoi) but only for net rotation, not making digits into an "N" index \\ along the path. \\ \\ The extents calculated are relative to an origin 0 at the centre of the \\ shape (not the start of the curve as in Math::PlanePath::Flowsnake). The \\ vecmin()/vecmax() calculate with centre of the little hexagons. Segments \\ other than the start and end are always / or \ and so go only to that \\ centre. But if the curve start or end are the maximum or minimum then \\ they are the whole hexagon so a +1 is needed. This only occurs for k=0 \\ for X minimum and k<3 for the X maximum. \\ \\ Pari has "quads" like sqrt(-3) builtin but the same can be done with real \\ and imaginary parts separately. k=3; { S = quadgen(-12); \\ sqrt(-3) w = (1 + S)/2; \\ sixth root of unity b = 2 + w; \\ base \\ base b low digit position under 2*Re+4*Im mod 7 index P = [0, w^2, 1, w, w^4, w^3, w^5]; \\ rotation state table T = 7*[0,0,1,0,0,1,2, 1,2,1,0,1,1,2, 2,2,2,0,0,1,2]; C = ["_","_", " ","\\", "/"," "]; \\ extents X = 2*sum(i=0,k-1, vecmax(real(b^i*P))); Y = 2*sum(i=0,k-1, vecmax(imag(b^i*P))); for(y = -Y, Y, for(x = -X+!!k, X+(k<3), \\ adjusted when endpoint is X limit z = (x- (o = (x+y)%2) - y*S)/2; v = vector(k,i, z = (z - P[ d = (2*real(z) + 4*imag(z)) % 7 + 1 ])/b; d); print1( C[if(z,3, r = 0; forstep(i=#v,1, -1, r = T[r+v[i]];); r%5 + o + 1)]) ); \\ r=0,7,14 mod 5 is 0,2,4 print()) } Math-PlanePath-129/examples/other/dragon-curve.el0000644000175000017500000000771212241340154017566 0ustar gggg;; Copyright 2012, 2013 Kevin Ryde ;; ;; This file is part of Math-PlanePath. ;; ;; Math-PlanePath 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, or (at your option) any later ;; version. ;; ;; Math-PlanePath 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 Math-PlanePath. If not, see . ;; Usage: M-x load-file dragon-curve.el ;; ;; And thereafter M-x dragon-picture. ;; (unless (fboundp 'ignore-errors) (require 'cl)) ;; Emacs 22 and earlier `ignore-errors' (defun dragon-ensure-line-above () "If point is in the first line of the buffer then insert a new line above." (when (= (line-beginning-position) (point-min)) (save-excursion (goto-char (point-min)) (insert "\n")))) (defun dragon-ensure-column-left () "If point is in the first column then insert a new column to the left. This is designed for use in `picture-mode'." (when (zerop (current-column)) (save-excursion (goto-char (point-min)) (insert " ") (while (= 0 (forward-line 1)) (insert " "))) (picture-forward-column 1))) (defun dragon-insert-char (char len) "Insert CHAR repeated LEN many times. After each CHAR move point in the current `picture-mode' direction (per `picture-set-motion' etc). This is the same as `picture-insert' except in column 0 or row 0 a new row or column is inserted to make room, with existing buffer contents shifted down or right." (dotimes (i len) (dragon-ensure-line-above) (dragon-ensure-column-left) (picture-insert char 1))) (defun dragon-bit-above-lowest-0bit (n) "Return the bit above the lowest 0-bit in N. For example N=43 binary \"101011\" has lowest 0-bit at \"...0..\" and the bit above that is \"..1...\" so return 8 which is that bit." (logand n (1+ (logxor n (1+ n))))) (defun dragon-next-turn-right-p (n) "Return non-nil if the dragon curve should turn right after segment N. Segments are numbered from N=0 for the first, so calling with N=0 is whether to turn right at the end of that N=0 segment." (zerop (dragon-bit-above-lowest-0bit n))) (defun dragon-picture (len step) "Draw the dragon curve in a *dragon* buffer. LEN is the number of segments of the curve to draw. STEP is the length of each segment, in characters. Any LEN can be given but a power-of-2 such as 256 shows the self-similar nature of the curve. If STEP >= 2 then the segments are lines using \"-\" or \"|\" characters (`picture-rectangle-h' and `picture-rectangle-v'). If STEP=1 then only \"+\" corners. There's a `sit-for' delay in the drawing loop to draw the curve progressively on screen." (interactive (list (read-number "Length of curve " 256) (read-number "Each step size " 3))) (unless (>= step 1) (error "Step length must be >= 1")) (switch-to-buffer "*dragon*") (erase-buffer) (setq truncate-lines t) (ignore-errors ;; ignore error if already in picture-mode (picture-mode)) (dotimes (n len) ;; n=0 to len-1, inclusive (dragon-insert-char ?+ 1) ;; corner char (dragon-insert-char (if (zerop picture-vertical-step) picture-rectangle-h picture-rectangle-v) (1- step)) ;; line chars (if (dragon-next-turn-right-p n) ;; turn right (picture-set-motion (- picture-horizontal-step) picture-vertical-step) ;; turn left (picture-set-motion picture-horizontal-step (- picture-vertical-step))) ;; delay to display the drawing progressively (sit-for .01)) (picture-insert ?+ 1) ;; endpoint (picture-mode-exit) (goto-char (point-min))) (dragon-picture 128 2) Math-PlanePath-129/examples/other/dragon-recursive.gri0000644000175000017500000000644312217673641020647 0ustar gggg# Copyright 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . `Draw Dragon [ from .x1. .y1. to .x2. .y2. [level .level.] ]' Draw a dragon curve going from .x1. .y1. to .x2. .y2. with recursion depth .level. The total number of line segments for the recursion is 2^level. level=0 is a straight line from x1,y1 to x2,y2. The default for x1,y1 and x2,y2 is to draw horizontally from 0,0 to 1,0. { new .x1. .y1. .x2. .y2. .level. .x1. = \.word3. .y1. = \.word4. .x2. = \.word6. .y2. = \.word7. .level. = \.word9. if {rpn \.words. 5 >=} .x2. = 1 .y2. = 0 end if if {rpn \.words. 7 >=} .level. = 6 end if if {rpn 0 .level. <=} draw line from .x1. .y1. to .x2. .y2. else .level. = {rpn .level. 1 -} # xmid,ymid is half way between x1,y1 and x2,y2 and up at # right angles away. # # xmid,ymid xmid = (x1+x2 + y2-y1)/2 # ^ ^ ymid = (x1-x2 + y1+y2)/2 # / . \ # / . \ # x1,y1 ........... x2,y2 # new .xmid. .ymid. .xmid. = {rpn .x1. .x2. + .y2. .y1. - + 2 /} .ymid. = {rpn .x1. .x2. - .y1. .y2. + + 2 /} # The recursion is a level-1 dragon from x1,y1 to the midpoint # and the same from x2,y2 to the midpoint (the latter # effectively being a revered dragon.) # Draw Dragon from .x1. .y1. to .xmid. .ymid. level .level. Draw Dragon from .x2. .y2. to .xmid. .ymid. level .level. delete .xmid. .ymid. end if delete .x1. .y1. .x2. .y2. .level. } # Dragon curve from 0,0 to 1,0 extends out by 1/3 at the ends, so # extents -0.5 to +1.5 for a bit of margin. The Y extent is the same # size 2 to make the graph square. set x axis -0.5 1.5 .25 set y axis -1 1 .25 Draw Dragon #Draw Dragon from 0 0 to 1 0 level 10 # x1,y1 to x2,y2 # dx = x2-x1 # dy = y2-y1 # xmid = x1 + dx/2 - dy/2 # = x1 + (x2-x1 - (y2-y1))/2 # = (2*x1 + x2-x1 -y2+y1)/2 # = (2*x1 + x2-x1 - y2+y1) / 2 # = (x1+x2 + y1-y2)/2 # ymid = y1 + dy/2 + dx/2 # = (2*y1 + dy + dx)/2 # = (2*y1 + y2-y1 + x2-x1) / 2 # = (y1+y2 + x2-x1) / 2 # xmid = x1 + dx/2 + dy/2 # = x1 + (x2-x1 + y2-y1)/2 # = (x1+x2 + y2-y1)/2 # ymid = y1 + dy/2 - dx/2 # = (2*y1 + y2-y1 + x1-x2) / 2 # = (y1+y2 + x1-x2) / 2 # show " line " .x1. " " .y1. " to " .x2. " " .y2. # show .x1. " " .y1. " to " .x2. " " .y2. " mid " .xmid. " " .ymid. # show "second " .x1. " " .y1. " to " .x2. " " .y2. " mid " .xmid. " " .ymid. # show "level " .level. Math-PlanePath-129/examples/other/dragon-pgf-plain.tex0000644000175000017500000000466512246063147020534 0ustar gggg%% Copyright 2013 Kevin Ryde %% %% This file is part of Math-PlanePath. %% %% Math-PlanePath 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, or (at your option) any later %% version. %% %% Math-PlanePath 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 Math-PlanePath. If not, see . %% Usage: tex dragon-pgf-latex.tex %% xdvi dragon-pgf-latex.dvi %% %% This a dragon curve drawn with the PGF lindenmayersystems library. %% %% http://sourceforge.net/projects/pgf/ %% %% The PGF manual includes examles of Koch snowflake, Hilbert curve and %% Sierpinski arrowhead. In the ``spy'' library section there's some %% magnifications of the Koch and of a quadric curve too. %% %% In the rule here \symbol{S} is a second drawing symbol. It draws a %% line segment the same as F, but the two different symbols let the %% rules distinguish odd and even position line segments. %% %% F and S are always in pairs, F first and S second, F_S_F_S_F_S_F_S. %% At each even position F expands to a left bend, ie with a "+" turn. %% At each odd position S expands to a right bend, ie with a "-". %% This is the "successive approximation" method for generating the %% curve where each line segment is replaced by a bend to the left or %% right according as it's at an even or odd position. %% %% The sequence of + and - turns resulting in the expansion follows %% the "bit above lowest 1-bit" rule. This works essentially because %% the bit above obeys an expansion rule %% %% if k even %% bitabovelowest1bit(2k) = bitabovelowest1bit(k) %% bitabovelowest1bit(2k+1) = 0 # the "+" in F -> F+S %% %% if k odd %% bitabovelowest1bit(2k) = bitabovelowest1bit(k) %% bitabovelowest1bit(2k+1) = 1 # the "-" in S -> F-S %% \input tikz.tex \usetikzlibrary{lindenmayersystems} \pgfdeclarelindenmayersystem{Dragon curve}{ \symbol{S}{\pgflsystemdrawforward} \rule{F -> F+S} \rule{S -> F-S} } \tikzpicture \draw [lindenmayer system={Dragon curve, step=10pt, axiom=F, order=8}] lindenmayer system; \endtikzpicture \bye Math-PlanePath-129/examples/other/sierpinski-triangle-text.logo0000755000175000017500000000304012062333621022472 0ustar gggg#!/usr/bin/ucblogo ; Copyright 2012 Kevin Ryde ; This file is part of Math-PlanePath. ; ; Math-PlanePath 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, or (at your option) any later ; version. ; ; Math-PlanePath 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 Math-PlanePath. If not, see . ; Usage: ucblogo sierpinski-triangle-text.logo ; ; Print the Sierpinski triangle pattern in text with spaces and stars, ; using BITAND to decide whether to plot at a given X,Y or not. ; ; :limit determines the padding at the left, and within that limit the ; range of :y to print is arbitrary. ; Print rows of the triangle from 0 to :limit inclusive. ; ; * ; * * ; * * ; * * * * ; * * ; * * * * ; * * * * ; * * * * * * * * ; make "limit 15 for [y 0 :limit] [ for [x -:limit :y] [ type ifelse (and :y+:x >= 0 ; blank left of triangle (remainder :y+:x 2) = 0 ; only "even" squares (bitand :y+:x :y-:x) = 0 ; Sierpinski bit test ) ["*] ["| |] ; star or space ] print [] ] Math-PlanePath-129/examples/other/dragon-pgf-latex.tex0000644000175000017500000000334112246063234020531 0ustar gggg%% Copyright 2013 Kevin Ryde %% %% This file is part of Math-PlanePath. %% %% Math-PlanePath 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, or (at your option) any later %% version. %% %% Math-PlanePath 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 Math-PlanePath. If not, see . %% Usage: latex dragon-pgf-latex.tex %% xdvi dragon-pgf-latex.dvi %% See dragon-pgf-plain.tex for more comments. The F,S here behave %% the same as there. %% %% The rule here is a 45-degree variation which keeps the net %% direction unchanged after expansion. This means the curve endpoint %% remains in a fixed direction horizontal no matter what expansion %% level is applied. %% %% Does Mandelbrot's book ``Fractal Geometry of Nature'' have an %% expansion like this, but maybe with just a single drawing symbol? \documentclass{article} \usepackage{tikz} \usetikzlibrary{lindenmayersystems} \begin{document} \pgfdeclarelindenmayersystem{Dragon curve}{ \symbol{S}{\pgflsystemdrawforward} \rule{F -> -F++S-} \rule{S -> +F--S+} } \foreach \i in {1,...,8} { \hbox{ order=\i \hspace{.5em} \begin{tikzpicture}[baseline=0pt] \draw [lindenmayer system={Dragon curve, step=10pt,angle=45, axiom=F, order=\i}] lindenmayer system; \end{tikzpicture} \hspace{1em} } \vspace{.5ex} } \end{document} Math-PlanePath-129/examples/other/dragon-curve.gnuplot0000755000175000017500000000554712041161321020660 0ustar gggg#!/usr/bin/gnuplot # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: gnuplot dragon-curve.gnuplot # # Draw the dragon curve by calculating an X,Y position for each # point n. The plot is in "parametric" mode with t running integers # 0 to n inclusive. # Return the position of the highest 1-bit in n. # The least significant bit is position 0. # For example n=13 is binary "1101" and the high bit is pos=3. # If n==0 then the return is 0. # Arranging the test as n>=2 avoids infinite recursion if n==NaN (any # comparison involving NaN is always false). # high_bit_pos(n) = (n>=2 ? 1+high_bit_pos(int(n/2)) : 0) # Return 0 or 1 for the bit at position "pos" in n. # pos==0 is the least significant bit. # bit(n,pos) = int(n / 2**pos) & 1 # dragon(n) returns a complex number which is the position of the # dragon curve at integer point "n". n=0 is the first point and is at # the origin {0,0}. Then n=1 is at {1,0} which is x=1,y=0, etc. If n # is not an integer then the point returned is for int(n). # # The calculation goes by bits of n from high to low. Gnuplot doesn't # have iteration in functions, but can go recursively from # pos=high_bit_pos(n) down to pos=0, inclusive. # # mul() rotates by +90 degrees (complex "i") at bit transitions 0->1 # or 1->0. add() is a vector (i+1)**pos for each 1-bit, but turned by # factor "i" when in a "reversed" section of curve, which is when the # bit above is also a 1-bit. # dragon(n) = dragon_by_bits(n, high_bit_pos(n)) dragon_by_bits(n,pos) \ = (pos>=0 ? add(n,pos) + mul(n,pos)*dragon_by_bits(n,pos-1) : 0) add(n,pos) = (bit(n,pos) ? (bit(n,pos+1) ? {0,1} * {1,1}**pos \ : {1,1}**pos) \ : 0) mul(n,pos) = (bit(n,pos) == bit(n,pos+1) ? 1 : {0,1}) # Plot the dragon curve from 0 to "length" with line segments. # "trange" and "samples" are set so the parameter t runs through # integers t=0 to t=length inclusive. # # Any trange works, it doesn't have to start at 0. But must have # enough "samples" that all integers t in the range are visited, # otherwise vertices in the curve would be missed. # length=256 set trange [0:length] set samples length+1 set parametric set key off plot real(dragon(t)),imag(dragon(t)) with lines Math-PlanePath-129/examples/square-numbers.pl0000755000175000017500000000332512041155563017042 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl square-numbers.pl # # Print the SquareSpiral numbers in a grid like # # 37 36 35 34 33 32 31 # 38 17 16 15 14 13 30 # 39 18 5 4 3 12 29 # 40 19 6 1 2 11 28 # 41 20 7 8 9 10 27 # 42 21 22 23 24 25 26 # 43 44 45 46 47 ... # # See numbers.pl for a more sophisticated program. use 5.004; use strict; use List::Util 'min', 'max'; use Math::PlanePath::SquareSpiral; my $n_max = 115; my $path = Math::PlanePath::SquareSpiral->new; my %rows; my $x_min = 0; my $x_max = 0; my $y_min = 0; my $y_max = 0; foreach my $n ($path->n_start .. $n_max) { my ($x, $y) = $path->n_to_xy ($n); $rows{$x}{$y} = $n; $x_min = min($x_min, $x); $x_max = max($x_max, $x); $y_min = min($y_min, $y); $y_max = max($y_max, $y); } my $cellwidth = length($n_max) + 2; foreach my $y (reverse $y_min .. $y_max) { foreach my $x ($x_min .. $x_max) { printf ('%*s', $cellwidth, $rows{$x}{$y} || ''); } print "\n"; } exit 0; Math-PlanePath-129/examples/numbers.pl0000755000175000017500000004575613774317626015600 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2018, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl numbers.pl CLASS... # perl numbers.pl all # # Print the given path CLASS or classes as N numbers in a grid. Eg. # # perl numbers.pl SquareSpiral DiamondSpiral # # Parameters to the class can be given as # # perl numbers.pl SquareSpiral,wider=4 # # With option "all" print all classes and a selection of their parameters, # per the table in the code below # # perl numbers.pl all # # See square-numbers.pl for a simpler program designed just for the # SquareSpiral. The code here tries to adapt itself to the tty width and # stops when the width of the numbers to be displayed would be wider than # the tty. # # Stopping when N goes outside the tty means that just the first say 99 or # so N values will be shown. There's often other bigger N within the X,Y # grid region, but the first few N show how the path begins, without # clogging up the output. # # The origin 0,0 is kept in the middle of the output, horizontally, to help # see how much is on each side and to make multiple paths printed line up # such as the "all" option. Vertically only as many rows as necessary are # printed. # # Paths with fractional X,Y positions like SacksSpiral or VogelFloret are # rounded to character positions. There's some hard-coded fudge factors to # try to make them come out nicely. # # When an X,Y position is visited more than once multiple N's are shown with # a comma like "9,24". This can happen for example in the DragonCurve where # points are visited twice, or when rounding gives the same X,Y for a few # initial points such as in KochSquareflakes. # use 5.004; use strict; use POSIX (); use List::Util 'min', 'max'; my $width = 79; my $height = 23; # use Term::Size if available # chars() can return 0 for unknown size, ignore that if (eval { require Term::Size }) { my ($term_width, $term_height) = Term::Size::chars(); if ($term_width) { $width = $term_width - 1; } if ($term_height) { $height = $term_height - 1; } } if (! @ARGV) { push @ARGV, 'HexSpiral'; # default class to print if no args } my @all_classes = ('SquareSpiral', 'SquareSpiral,wider=9', 'DiamondSpiral', 'PentSpiral', 'PentSpiralSkewed', 'HexSpiral', 'HexSpiral,wider=3', 'HexSpiralSkewed', 'HexSpiralSkewed,wider=5', 'HeptSpiralSkewed', 'AnvilSpiral', 'AnvilSpiral,wider=3', 'OctagramSpiral', 'PyramidSpiral', 'PyramidRows', 'PyramidRows,step=5', 'PyramidRows,align=right', 'PyramidRows,align=left,step=4', 'PyramidSides', 'CellularRule,rule=30', 'CellularRule,rule=73', 'CellularRule54', 'CellularRule57', 'CellularRule57,mirror=1', 'CellularRule190', 'CellularRule190,mirror=1', 'TriangleSpiral', 'TriangleSpiralSkewed', 'TriangleSpiralSkewed,skew=right', 'TriangleSpiralSkewed,skew=up', 'TriangleSpiralSkewed,skew=down', 'Diagonals', 'Diagonals,direction=up', 'DiagonalsAlternating', 'DiagonalsOctant', 'DiagonalsOctant,direction=up', 'Staircase', 'StaircaseAlternating', 'StaircaseAlternating,end_type=square', 'Corner', 'Corner,wider=5', 'CornerAlternating', 'CornerAlternating,wider=5', 'KnightSpiral', 'CretanLabyrinth', 'SquareArms', 'DiamondArms', 'HexArms', 'GreekKeySpiral', 'GreekKeySpiral,turns=4', 'GreekKeySpiral,turns=1', 'AztecDiamondRings', 'MPeaks', 'SacksSpiral', 'VogelFloret', 'ArchimedeanChords', 'TheodorusSpiral', 'MultipleRings', 'MultipleRings,step=14', 'PixelRings', 'FilledRings', 'Hypot', 'Hypot,points=even', 'Hypot,points=odd', 'HypotOctant', 'HypotOctant,points=even', 'HypotOctant,points=odd', 'TriangularHypot', 'TriangularHypot,points=odd', 'TriangularHypot,points=all', 'TriangularHypot,points=hex', 'TriangularHypot,points=hex_rotated', 'TriangularHypot,points=hex_centred', 'Rows', 'Columns', 'UlamWarburton', 'UlamWarburton,parts=2', 'UlamWarburton,parts=1', 'UlamWarburton,parts=octant', 'UlamWarburton,parts=octant_up', 'UlamWarburtonQuarter', 'UlamWarburtonQuarter,parts=octant', 'UlamWarburtonQuarter,parts=octant_up', 'PeanoCurve', 'PeanoCurve,radix=5', 'PeanoDiagonals', 'PeanoDiagonals,radix=5', 'WunderlichSerpentine', 'WunderlichSerpentine,serpentine_type=coil', 'WunderlichSerpentine,radix=5,serpentine_type=01001_01110_01000_11111_00010', 'WunderlichMeander', 'HilbertCurve', 'HilbertSides', 'HilbertSpiral', 'ZOrderCurve', 'ZOrderCurve,radix=5', 'GrayCode', 'GrayCode,apply_type=Ts', 'GrayCode,radix=4', 'BetaOmega', 'AR2W2Curve', 'AR2W2Curve,start_shape=D2', 'AR2W2Curve,start_shape=B2', 'AR2W2Curve,start_shape=B1rev', 'AR2W2Curve,start_shape=D1rev', 'AR2W2Curve,start_shape=A2rev', 'KochelCurve', 'DekkingCurve', 'DekkingCurve,arms=2', 'DekkingCurve,arms=3', 'DekkingCurve,arms=4', 'DekkingCentres', 'CincoCurve', 'ImaginaryBase', 'ImaginaryBase,radix=4', 'ImaginaryHalf', 'ImaginaryHalf,radix=4', 'ImaginaryHalf,digit_order=XXY', 'ImaginaryHalf,digit_order=YXX', 'ImaginaryHalf,digit_order=XnXY', 'ImaginaryHalf,digit_order=XnYX', 'ImaginaryHalf,digit_order=YXnX', 'CubicBase', 'CubicBase,radix=4', 'SquareReplicate', 'SquareReplicate,numbering_type=rotate-4', 'SquareReplicate,numbering_type=rotate-8', 'CornerReplicate', 'LTiling', 'LTiling,L_fill=ends', 'LTiling,L_fill=all', 'DigitGroups', 'FibonacciWordFractal', 'Flowsnake', 'Flowsnake,arms=3', 'FlowsnakeCentres', 'FlowsnakeCentres,arms=3', 'GosperReplicate', 'GosperReplicate,numbering_type=rotate', 'GosperIslands', 'GosperSide', 'QuintetCurve', 'QuintetCurve,arms=4', 'QuintetCentres', 'QuintetReplicate', 'QuintetReplicate,numbering_type=rotate', 'KochCurve', 'KochPeaks', 'KochSnowflakes', 'KochSquareflakes', 'KochSquareflakes,inward=1', 'QuadricCurve', 'QuadricIslands', 'SierpinskiCurve', 'SierpinskiCurve,arms=8', 'SierpinskiCurveStair', 'SierpinskiCurveStair,arms=2', 'SierpinskiCurveStair,diagonal_length=4', 'HIndexing', 'SierpinskiTriangle', 'SierpinskiTriangle,align=right', 'SierpinskiTriangle,align=left', 'SierpinskiTriangle,align=diagonal', 'SierpinskiArrowhead', 'SierpinskiArrowhead,align=right', 'SierpinskiArrowhead,align=left', 'SierpinskiArrowhead,align=diagonal', 'SierpinskiArrowheadCentres', 'SierpinskiArrowheadCentres,align=right', 'SierpinskiArrowheadCentres,align=left', 'SierpinskiArrowheadCentres,align=diagonal', 'DragonCurve', 'DragonCurve,arms=4', 'DragonRounded', 'DragonRounded,arms=4', 'DragonMidpoint', 'DragonMidpoint,arms=2', 'DragonMidpoint,arms=3', 'DragonMidpoint,arms=4', 'AlternatePaper', 'AlternatePaper,arms=2', 'AlternatePaper,arms=8', 'AlternatePaperMidpoint', 'AlternatePaperMidpoint,arms=2', 'AlternatePaperMidpoint,arms=8', 'CCurve', 'TerdragonCurve', 'TerdragonCurve,arms=6', 'TerdragonRounded', 'TerdragonRounded,arms=6', 'TerdragonMidpoint', 'TerdragonMidpoint,arms=6', 'AlternateTerdragon', 'AlternateTerdragon,arms=6', 'R5DragonCurve', 'R5DragonCurve,arms=4', 'R5DragonMidpoint', 'R5DragonMidpoint,arms=2', 'R5DragonMidpoint,arms=3', 'R5DragonMidpoint,arms=4', 'ComplexPlus', 'ComplexPlus,realpart=2', 'ComplexMinus', 'ComplexMinus,realpart=2', 'ComplexRevolving', 'PythagoreanTree,tree_type=UAD', 'PythagoreanTree,tree_type=UAD,coordinates=AC', 'PythagoreanTree,tree_type=UAD,coordinates=BC', 'PythagoreanTree,tree_type=UAD,coordinates=PQ', 'PythagoreanTree,tree_type=UAD,coordinates=SM', 'PythagoreanTree,tree_type=UAD,coordinates=SC', 'PythagoreanTree,tree_type=UAD,coordinates=MC', 'PythagoreanTree,tree_type=FB', 'PythagoreanTree,tree_type=FB,coordinates=AC', 'PythagoreanTree,tree_type=FB,coordinates=BC', 'PythagoreanTree,tree_type=FB,coordinates=PQ', 'PythagoreanTree,tree_type=FB,coordinates=SM', 'PythagoreanTree,tree_type=FB,coordinates=SC', 'PythagoreanTree,tree_type=FB,coordinates=MC', 'PythagoreanTree,tree_type=UMT', 'PythagoreanTree,tree_type=UMT,coordinates=AC', 'PythagoreanTree,tree_type=UMT,coordinates=BC', 'PythagoreanTree,tree_type=UMT,coordinates=PQ', 'PythagoreanTree,tree_type=UMT,coordinates=SM', 'PythagoreanTree,tree_type=UMT,coordinates=SC', 'PythagoreanTree,tree_type=UMT,coordinates=MC', 'DiagonalRationals', 'DiagonalRationals,direction=up', 'CoprimeColumns', 'FactorRationals', 'GcdRationals', 'GcdRationals,pairs_order=rows_reverse', 'GcdRationals,pairs_order=diagonals_down', 'GcdRationals,pairs_order=diagonals_up', 'RationalsTree,tree_type=SB', 'RationalsTree,tree_type=CW', 'RationalsTree,tree_type=AYT', 'RationalsTree,tree_type=HCS', 'RationalsTree,tree_type=Bird', 'RationalsTree,tree_type=Drib', 'RationalsTree,tree_type=L', 'FractionsTree', 'ChanTree', 'ChanTree,k=4', 'ChanTree,k=5', 'ChanTree,k=7', 'ChanTree,k=8', 'CfracDigits', 'CfracDigits,radix=3', 'CfracDigits,radix=4', 'CfracDigits,radix=1', 'DivisibleColumns', 'DivisibleColumns,divisor_type=proper', 'WythoffArray', 'WythoffPreliminaryTriangle', 'PowerArray', 'PowerArray,radix=3', 'PowerArray,radix=4', # in separate Math-PlanePath-Toothpick '*ToothpickTree', '*ToothpickTree,parts=3', '*ToothpickTree,parts=2', '*ToothpickTree,parts=1', '*ToothpickTree,parts=octant', '*ToothpickTree,parts=octant_up', '*ToothpickTree,parts=wedge', '*ToothpickReplicate', '*ToothpickReplicate,parts=3', '*ToothpickReplicate,parts=2', '*ToothpickReplicate,parts=1', '*ToothpickUpist', '*ToothpickSpiral', '*LCornerTree', '*LCornerTree,parts=3', '*LCornerTree,parts=2', '*LCornerTree,parts=1', '*LCornerTree,parts=octant', '*LCornerTree,parts=octant+1', '*LCornerTree,parts=octant_up', '*LCornerTree,parts=octant_up+1', '*LCornerTree,parts=wedge', '*LCornerTree,parts=wedge+1', '*LCornerTree,parts=diagonal', '*LCornerTree,parts=diagonal-1', '*LCornerReplicate', '*OneOfEight', '*OneOfEight,parts=4', '*OneOfEight,parts=1', '*OneOfEight,parts=octant', '*OneOfEight,parts=octant_up', '*OneOfEight,parts=3mid', '*OneOfEight,parts=3side', '*OneOfEight,parts=wedge', '*HTree', ); # expand arg "all" to full list @ARGV = map {$_ eq 'all' ? @all_classes : $_} @ARGV; my $separator = ''; foreach my $class (@ARGV) { print $separator; $separator = "\n"; print_class ($class); } sub print_class { my ($name) = @_; # secret leading "*Foo" means print if available my $if_available = ($name =~ s/^\*//); my $class = $name; unless ($class =~ /::/) { $class = "Math::PlanePath::$class"; } ($class, my @parameters) = split /\s*,\s*/, $class; $class =~ /^[a-z_][:a-z_0-9]*$/i or die "Bad class name: $class"; if (! eval "require $class") { if ($if_available) { next; } else { die $@; } } @parameters = map { /(.*?)=(.*)/ or die "Missing value for parameter \"$_\""; $1,$2 } @parameters; my %rows; my $x_min = 0; my $x_max = 0; my $y_min = 0; my $y_max = 0; my $cellwidth = 1; my $path = $class->new (width => POSIX::ceil($width / 4), height => POSIX::ceil($height / 2), @parameters); my $x_limit_lo; my $x_limit_hi; if ($path->x_negative) { my $w_cells = int ($width / $cellwidth); my $half = int(($w_cells - 1) / 2); $x_limit_lo = -$half; $x_limit_hi = +$half; } else { my $w_cells = int ($width / $cellwidth); $x_limit_lo = 0; $x_limit_hi = $w_cells - 1; } my $y_limit_lo = 0; my $y_limit_hi = $height-1; if ($path->y_negative) { my $half = int(($height-1)/2); $y_limit_lo = -$half; $y_limit_hi = +$half; } my $n_start = $path->n_start; my $n = $n_start; for ($n = $n_start; $n <= 999; $n++) { my ($x, $y) = $path->n_to_xy ($n); # stretch these out for better resolution if ($class =~ /Sacks/) { $x *= 1.5; $y *= 2; } if ($class =~ /Archimedean/) { $x *= 2; $y *= 3; } if ($class =~ /Theodorus|MultipleRings/) { $x *= 2; $y *= 2; } if ($class =~ /Vogel/) { $x *= 2; $y *= 3.5; } # nearest integers $x = POSIX::floor ($x + 0.5); $y = POSIX::floor ($y + 0.5); my $cell = $rows{$x}{$y}; if (defined $cell) { $cell .= ','; } $cell .= $n; my $new_cellwidth = max ($cellwidth, length($cell) + 1); my $new_x_limit_lo; my $new_x_limit_hi; if ($path->x_negative) { my $w_cells = int ($width / $new_cellwidth); my $half = int(($w_cells - 1) / 2); $new_x_limit_lo = -$half; $new_x_limit_hi = +$half; } else { my $w_cells = int ($width / $new_cellwidth); $new_x_limit_lo = 0; $new_x_limit_hi = $w_cells - 1; } my $new_x_min = min($x_min, $x); my $new_x_max = max($x_max, $x); my $new_y_min = min($y_min, $y); my $new_y_max = max($y_max, $y); if ($new_x_min < $new_x_limit_lo || $new_x_max > $new_x_limit_hi || $new_y_min < $y_limit_lo || $new_y_max > $y_limit_hi) { last; } $rows{$x}{$y} = $cell; $cellwidth = $new_cellwidth; $x_limit_lo = $new_x_limit_lo; $x_limit_hi = $new_x_limit_hi; $x_min = $new_x_min; $x_max = $new_x_max; $y_min = $new_y_min; $y_max = $new_y_max; } $n--; # the last N actually plotted print "$name N=$n_start to N=$n\n\n"; foreach my $y (reverse $y_min .. $y_max) { foreach my $x ($x_limit_lo .. $x_limit_hi) { my $cell = $rows{$x}{$y}; if (! defined $cell) { $cell = ''; } printf ('%*s', $cellwidth, $cell); } print "\n"; } } exit 0; Math-PlanePath-129/examples/c-curve-wx.pl0000755000175000017500000007110613734026674016105 0ustar gggg#!/usr/bin/perl -w # Copyright 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl c-curve-wx.pl # # This is a WxWidgets GUI drawing the C curve and some variations. It's a # little rough but can pan and zoom, and rolling the expansion level # expansion level in the toolbar is an interesting way to see to see the # curve or curves develop. # # Segments are drawn either as lines or triangles. Triangles are on the # side of expansion of each segment. When multiple copies of the curve are # selected they're in different colours. (Though presently when line # segments overlap only one colour is shown.) # # The default is to draw one copy of the curve. The toolbar control can # select various combinations of adjacent curves. The names are supposed to # be suggestive, but it can help to decrease to curve level 0 to see each as # a single segment. # # Drawing is done with Math::PlanePath::CCurve and # Geometry::AffineTransform. The drawing is not particularly efficient # since it runs through all segments, even those which are off-screen. The # drawing is piece-wise in an idle loop, so you can change or move without # waiting for it to finish. # # Some of the drawing options can be set initially from the command line. # See the usage message print below or run --help. use 5.008; use strict; use warnings; use FindBin; use Getopt::Long; use Geometry::AffineTransform; use List::Util 'min','max'; use Math::Libm 'M_PI', 'hypot'; use Math::PlanePath::CCurve; use Time::HiRes; use POSIX (); use Wx; use Wx::Event; # uncomment this to run the ### lines # use Smart::Comments; our $VERSION = 129; my $level = 5; my $scale = 1; my $x_offset = 0; my $y_offset = 0; my $window_initial_width; my $initial_window_height; my $window_initial_fullscreen; my @types_list = ( # curve goes East so x=0,y=0 to x=1,y=0 # optional $rotate*90 degrees (anti-clockwise) { name => '1', copies => [ { x => 0, y => 0 } ], }, { name => 'Part', copies => [ { x => 0, y => 0 } ], min_x => -.1, max_x => 1.1, min_y => -.1, max_y => .7, }, { name => '2 Pair', # 0,0 -----> 1,0 # 0,0 <----- 1,0 copies => [ { x => 0, y => 0 }, { x => 1, y => 0, rotate => 2 } ], }, { name => '2 Line', copies => [ { x => 0, y => 0 }, { x => -1, y => 0 } ], }, { name => '2 Arms', copies => [ { x => 0, y => 0 }, { x => 0, y => 0, rotate => 2 } ], }, # { name => '2 Above', # copies => [ { x => 0, y => 0 }, # { x => 0, y => 1 } ], # }, { name => '4 Pinwheel', copies => [ { x => 0, y => 0 }, { x => 0, y => 0, rotate => 1 }, { x => 0, y => 0, rotate => 2 }, { x => 0, y => 0, rotate => 3 }, ], }, { name => '4 Square Inward', # 0,0 -----> 1,0 # ^ | # | v # 0,-1 <----- 1,-1 copies => [ { x => 0, y => 0 }, { x => 0, y => -1, rotate => 1 }, { x => 1, y => -1, rotate => 2 }, { x => 1, y => 0, rotate => 3 }, ], }, { name => '4 Square Outward', # 0,1 <----- 1,1 # | ^ # v | # 0,0 -----> 1,0 copies => [ { x => 0, y => 0 }, { x => 1, y => 0, rotate => 1 }, { x => 1, y => 1, rotate => 2 }, { x => 0, y => 1, rotate => 3 }, ], }, { name => '4 Pairs', # 0,0 -----> 1,0 -----> 2,0 # 0,0 <----- 1,0 <----- 2,0 copies => [ { x => 0, y => 0 }, { x => 1, y => 0 }, { x => 2, y => 0, rotate => 2 }, { x => 1, y => 0, rotate => 2 }, ], }, { name => '8 Cross', copies => [ { x => 0, y => 0 }, { x => 0, y => 0, rotate => 1 }, { x => 0, y => 0, rotate => 2 }, { x => 0, y => 0, rotate => 3 }, { x => 1, y => 0, rotate => 2 }, { x => -1, y => 0 }, { x => 0, y => -1, rotate => 1 }, { x => 0, y => 1, rotate => 3 }, ], }, { name => '8 Square', copies => [ { x => 0, y => 0 }, # 4 inward { x => 1, y => 0, rotate => 1 }, { x => 1, y => 1, rotate => 2 }, { x => 0, y => 1, rotate => 3 }, { x => 0, y => 1 }, # 4 outward { x => 0, y => 0, rotate => 1 }, { x => 1, y => 0, rotate => 2 }, { x => 1, y => 1, rotate => 3 },, ], }, { name => '16', copies => [ { x => 0, y => 0 }, { x => 0, y => 0, rotate => 1 }, { x => 0, y => 0, rotate => 2 }, { x => 0, y => 0, rotate => 3 }, { x => 1, y => 0, rotate => 2 }, { x => -1, y => 0 }, { x => 0, y => -1, rotate => 1 }, { x => 0, y => 1, rotate => 3 }, { x => -1, y => 1 }, { x => 0, y => 1 }, { x => 1, y => 1, rotate => 3 }, # down { x => 1, y => 0, rotate => 3 }, { x => 1, y => -1, rotate => 2 }, # bottom { x => 0, y => -1, rotate => 2 }, { x => -1, y => -1, rotate => 1 }, # up { x => -1, y => 0, rotate => 1 }, ] }, { name => '24 Clipped', copies => [ { x => -1, y => 0 }, { x => 0, y => 0 }, { x => 1, y => 0 }, { x => -1, y => 1 }, { x => 0, y => 1 }, { x => 1, y => 1 }, { x => 0, y => 0, rotate => 2 }, { x => 1, y => 0, rotate => 2 }, { x => 2, y => 0, rotate => 2 }, { x => 0, y => 1, rotate => 2 }, { x => 1, y => 1, rotate => 2 }, { x => 2, y => 1, rotate => 2 }, { x => 0, y => -1, rotate => 1 }, { x => 0, y => 0, rotate => 1 }, { x => 0, y => 1, rotate => 1 }, { x => 1, y => -1, rotate => 1 }, { x => 1, y => 0, rotate => 1 }, { x => 1, y => 1, rotate => 1 }, { x => 0, y => 0, rotate => 3 }, { x => 0, y => 1, rotate => 3 }, { x => 0, y => 2, rotate => 3 }, { x => 1, y => 0, rotate => 3 }, { x => 1, y => 1, rotate => 3 }, { x => 1, y => 2, rotate => 3 }, ], min_x => -0.1, max_x => 1.1, min_y => -0.1, max_y => 1.1, clip_min_x => 0, clip_max_x => 1, clip_min_y => 0, clip_max_y => 1, }, { name => '24', copies => [ { x => -1, y => 0 }, { x => 0, y => 0 }, { x => 1, y => 0 }, { x => -1, y => 1 }, { x => 0, y => 1 }, { x => 1, y => 1 }, { x => 0, y => 0, rotate => 2 }, { x => 1, y => 0, rotate => 2 }, { x => 2, y => 0, rotate => 2 }, { x => 0, y => 1, rotate => 2 }, { x => 1, y => 1, rotate => 2 }, { x => 2, y => 1, rotate => 2 }, { x => 0, y => -1, rotate => 1 }, { x => 0, y => 0, rotate => 1 }, { x => 0, y => 1, rotate => 1 }, { x => 1, y => -1, rotate => 1 }, { x => 1, y => 0, rotate => 1 }, { x => 1, y => 1, rotate => 1 }, { x => 0, y => 0, rotate => 3 }, { x => 0, y => 1, rotate => 3 }, { x => 0, y => 2, rotate => 3 }, { x => 1, y => 0, rotate => 3 }, { x => 1, y => 1, rotate => 3 }, { x => 1, y => 2, rotate => 3 }, ], }, { name => 'Half', copies => [ { x => 0, y => 0 } ], clip_min_x => -2, clip_max_x => .5, # clip second half clip_min_y => -1, clip_max_y => 2, }, ); my %types_hash = map { $_->{'name'} => $_ } @types_list; my @type_names = map {$_->{'name'}} @types_list; my $type = $types_list[0]->{'name'}; my @figure_names = ('Arrows','Triangles','Lines'); my $figure = $figure_names[0]; Getopt::Long::Configure ('no_ignore_case', 'bundling'); if (! Getopt::Long::GetOptions ('help|?' => sub { print "$FindBin::Script [--options]\n --version print program version --display DISPLAY X display to use --level N expansion level --geometry WIDTHxHEIGHT window size --fullscreen full screen window --initial=1 initial centre cell value "; exit 0; }, 'version' => sub { print "$FindBin::Script version $VERSION\n"; exit 0; }, 'level=i' => \$level, 'geometry=s' => sub { my ($opt, $str) = @_; $str =~ /^(\d+)x(\d+)$/ or die "Unrecognised --geometry \"$str\""; $window_initial_width = $1; $initial_window_height = $2; }, 'fullscreen' => \$window_initial_fullscreen, )) { exit 1; } my $path = Math::PlanePath::CCurve->new; my @colours; my @brushes; my @pens; my $brush_black; { package MyApp; use base 'Wx::App'; sub OnInit { my ($self) = @_; # $self->SUPER::OnInit(); foreach my $r (255/4, 255*2/4, 255) { foreach my $g (255/4, 255*2/4, 255) { foreach my $b (255/4, 255*2/4, 255) { my $colour = Wx::Colour->new ($r, $g, $b); push @colours, $colour; my $brush = Wx::Brush->new ($colour, Wx::wxSOLID()); push @brushes, $brush; my $pen = Wx::Pen->new ($colour, 1, Wx::wxSOLID()); push @pens, $pen; } } } $brush_black = Wx::Brush->new (Wx::wxBLACK, Wx::wxSOLID()); return 1; } } my $app = MyApp->new; $app->SetAppName($FindBin::Script); use constant FULLSCREEN_HIDE_BITS => (Wx::wxFULLSCREEN_NOBORDER() | Wx::wxFULLSCREEN_NOCAPTION()); my $main = Wx::Frame->new(undef, # parent Wx::wxID_ANY(), # ID $FindBin::Script); # title $main->SetIcon (Wx::GetWxPerlIcon()); use constant ZOOM_IN_ID => Wx::wxID_HIGHEST() + 1; use constant ZOOM_OUT_ID => Wx::wxID_HIGHEST() + 2; my $accel_table = Wx::AcceleratorTable->new ([Wx::wxACCEL_NORMAL(), Wx::WXK_NUMPAD_ADD(), ZOOM_IN_ID], [Wx::wxACCEL_CTRL(), Wx::WXK_NUMPAD_ADD(), ZOOM_IN_ID], [Wx::wxACCEL_CTRL(), 'd', ZOOM_IN_ID], [Wx::wxACCEL_NORMAL(), 'd', ZOOM_IN_ID], [Wx::wxACCEL_NORMAL(), 'D', ZOOM_IN_ID], [Wx::wxACCEL_NORMAL(), Wx::WXK_NUMPAD_SUBTRACT(), ZOOM_OUT_ID], [Wx::wxACCEL_CTRL(), Wx::WXK_NUMPAD_SUBTRACT(), ZOOM_OUT_ID]); $main->SetAcceleratorTable ($accel_table); ### $accel_table my $menubar = Wx::MenuBar->new; $main->SetMenuBar ($menubar); if (! defined $window_initial_width) { my $screen_size = Wx::GetDisplaySize(); $main->SetSize (Wx::Size->new ($screen_size->GetWidth * 0.8, $screen_size->GetHeight * 0.8)); } my $draw = Wx::Window->new ($main, # parent Wx::wxID_ANY(), # ID Wx::wxDefaultPosition(), Wx::wxDefaultSize()); $draw->SetBackgroundColour (Wx::wxBLACK()); Wx::Event::EVT_PAINT ($draw, \&OnPaint); Wx::Event::EVT_SIZE ($draw, \&OnSize); Wx::Event::EVT_IDLE ($draw, \&OnIdle); Wx::Event::EVT_MOUSEWHEEL ($draw, \&OnMouseWheel); Wx::Event::EVT_LEFT_DOWN ($draw, \&OnLeftDown); Wx::Event::EVT_MOTION ($draw, \&OnMotion); Wx::Event::EVT_ENTER_WINDOW ($draw, \&OnMotion); Wx::Event::EVT_KEY_DOWN ($draw, \&OnKey); $draw->SetExtraStyle($draw->GetExtraStyle | Wx::wxWS_EX_PROCESS_IDLE()); if (defined $window_initial_width) { $draw->SetSize(Wx::Size->new($window_initial_width,$initial_window_height)); } { my $menu = Wx::Menu->new; $menubar->Append ($menu, '&File'); # $menu->Append (Wx::wxID_PRINT(), # '', # Wx::GetTranslation('Print the image.')); # Wx::Event::EVT_MENU ($main, Wx::wxID_PRINT(), 'print_image'); # # $menu->Append (Wx::wxID_PREVIEW(), # '', # Wx::GetTranslation('Preview image print.')); # Wx::Event::EVT_MENU ($main, Wx::wxID_PREVIEW(), 'print_preview'); # # $menu->Append (Wx::wxID_PRINT_SETUP(), # Wx::GetTranslation('Print &Setup'), # Wx::GetTranslation('Setup page print.')); # Wx::Event::EVT_MENU ($main, Wx::wxID_PRINT_SETUP(), 'print_setup'); $menu->Append(Wx::wxID_EXIT(), '', 'Exit the program'); Wx::Event::EVT_MENU ($main, Wx::wxID_EXIT(), sub { my ($main, $event) = @_; $main->Close; }); } { my $menu = Wx::Menu->new; $menubar->Append ($menu, '&View'); { my $item = $menu->Append (Wx::wxID_ANY(), "&Fullscreen\tCtrl-F", "Toggle full screen or normal window (use accelerator Ctrl-F to return from fullscreen).", Wx::wxITEM_CHECK()); Wx::Event::EVT_MENU ($main, $item, sub { my ($self, $event) = @_; ### Wx-Main toggle_fullscreen() ... $main->ShowFullScreen (! $main->IsFullScreen, FULLSCREEN_HIDE_BITS); } ); Wx::Event::EVT_UPDATE_UI($main, $item, sub { my ($main, $event) = @_; ### Wx-Main _update_ui_fullscreen_menuitem: "@_" # though if FULLSCREEN_HIDE_BITS hides the # menubar then the item won't be seen when # checked ... $item->Check ($main->IsFullScreen); }); } { $menu->Append (ZOOM_IN_ID, "Zoom &In\tCtrl-+", Wx::GetTranslation('Zoom in.')); Wx::Event::EVT_MENU ($main, ZOOM_IN_ID, \&zoom_in); } { $menu->Append (ZOOM_OUT_ID, "Zoom &Out\tCtrl--", Wx::GetTranslation('Zoom out.')); Wx::Event::EVT_MENU ($main, ZOOM_OUT_ID, \&zoom_out); } { my $item = $menu->Append (Wx::wxID_ANY(), "&Centre\tCtrl-C", Wx::GetTranslation('Centre display in window.')); Wx::Event::EVT_MENU ($main, $item, sub { $x_offset = 0; $y_offset = 0; }); } } my $toolbar = $main->CreateToolBar; { { my $choice = Wx::Choice->new ($toolbar, Wx::wxID_ANY(), Wx::wxDefaultPosition(), Wx::wxDefaultSize(), \@type_names); $choice->SetSelection(0); $toolbar->AddControl($choice); $toolbar->SetToolShortHelp ($choice->GetId, 'The display type.'); Wx::Event::EVT_CHOICE ($main, $choice, sub { my ($main, $event) = @_; $type = $type_names[$choice->GetSelection]; ### $type $draw->Refresh; }); } { my $spin = Wx::SpinCtrl->new ($toolbar, Wx::wxID_ANY(), $level, # initial value Wx::wxDefaultPosition(), Wx::Size->new(40,-1), Wx::wxSP_ARROW_KEYS(), 0, # min POSIX::INT_MAX(), # max $level); # initial $toolbar->AddControl($spin); $toolbar->SetToolShortHelp ($spin->GetId, 'Expansion level.'); Wx::Event::EVT_SPINCTRL ($main, $spin, sub { my ($main, $event) = @_; $level = $spin->GetValue; $draw->Refresh; }); } { my $choice = Wx::Choice->new ($toolbar, Wx::wxID_ANY(), Wx::wxDefaultPosition(), Wx::wxDefaultSize(), \@figure_names); $choice->SetSelection(0); $toolbar->AddControl($choice); $toolbar->SetToolShortHelp ($choice->GetId, 'The figure to draw at each point.'); Wx::Event::EVT_CHOICE ($main, $choice, sub { my ($main, $event) = @_; $figure = $figure_names[$choice->GetSelection]; $draw->Refresh; }); } } #------------------------------------------------------------------------------ # Keyboard sub zoom_in { $scale *= 1.5; # $x_offset *= 1.5; # $y_offset *= 1.5; $draw->Refresh; } sub zoom_out { $scale /= 1.5; # $x_offset /= 1.5; # $y_offset /= 1.5; $draw->Refresh; } # $event is a wxMouseEvent sub OnKey { my ($draw, $event) = @_; ### Draw OnLeftDown() ... my $keycode = $event->GetKeyCode; ### $keycode # if ($keycode == Wx::WXK_NUMPAD_ADD()) { # zoom_in(); # } elsif ($keycode == Wx::WXK_NUMPAD_SUBTRACT()) { # zoom_out(); # } } #------------------------------------------------------------------------------ # mouse wheel scroll sub OnMouseWheel { my ($draw, $event) = @_; ### OnMouseWheel() .. # "Control" by page, otherwise by step my $frac = ($event->ControlDown ? 0.9 : 0.1) * $event->GetWheelRotation / $event->GetWheelDelta; # "Shift" horizontally, otherwise vertically my $size = $draw->GetClientSize; if ($event->ShiftDown) { $x_offset += int($size->GetWidth * $frac); } else { $y_offset += int($size->GetHeight * $frac); } $draw->Refresh; } #------------------------------------------------------------------------------ # mouse drag # $drag_x,$drag_y are the X,Y position where dragging started. # If dragging is not in progress then $drag_x is undef. my ($drag_x, $drag_y); # $event is a wxMouseEvent sub OnLeftDown { my ($draw, $event) = @_; ### Draw OnLeftDown() ... $drag_x = $event->GetX; $drag_y = $event->GetY; $event->Skip(1); # propagate to other processing } sub OnMotion { my ($draw, $event) = @_; ### Draw OnMotion() ... if ($event->Dragging) { if (defined $drag_x) { ### drag ... my $x = $event->GetX; my $y = $event->GetY; $x_offset += $x - $drag_x; $y_offset += $y - $drag_y; $drag_x = $x; $drag_y = $y; $draw->Refresh; } } } #------------------------------------------------------------------------------ # drawing sub TopStart { my ($k) = @_; return (2**$k + ($k%2==0 ? -1 : 1))/3; } sub TopEnd { my ($k) = @_; return TopStart($k+1); } sub OnSize { my ($draw, $event) = @_; $draw->Refresh; } # $idle_drawing is a coderef which is setup by OnPaint() to draw more of the # curves. If it doesn't finish the drawing then it does a ->RequestMore() # to go again when next idle (which might be immediately). my $idle_drawing; sub OnPaint { my ($draw, $event) = @_; ### Drawing OnPaint(): $event ### foreground: $draw->GetForegroundColour->GetAsString(4) ### background: $draw->GetBackgroundColour->GetAsString(4) my $busy = Wx::BusyCursor->new; my $dc = Wx::PaintDC->new ($draw); { my $brush = $dc->GetBackground; $brush->SetColour ($draw->GetBackgroundColour); $dc->SetBackground ($brush); $dc->Clear; } # $brush->SetColour (Wx::wxWHITE); # $brush->SetStyle (Wx::wxSOLID()); # $dc->SetBrush ($brush); # # $dc->DrawRectangle (20,20,100,100); my $colour = Wx::wxGREEN(); { my $pen = $dc->GetPen; $pen->SetColour($colour); $dc->SetPen($pen); } my $brush = $dc->GetBrush; $brush->SetColour ($colour); $brush->SetStyle (Wx::wxSOLID()); $dc->SetBrush ($brush); my ($width,$height) = $dc->GetSizeWH; ### $width ### $height my ($n_lo, $n_hi); if ($type eq 'Part') { $n_lo = TopStart($level); $n_hi = TopEnd($level); } else { ($n_lo, $n_hi) = $path->level_to_n_range($level); } my ($x_lo,$y_lo) = $path->n_to_xy($n_lo); my ($x_hi,$y_hi) = $path->n_to_xy($n_hi); my ($dx,$dy) = ($x_hi-$x_lo, $y_hi-$y_lo); my $len = hypot($dx,$dy); my $angle = atan2($dy,$dx) * 180 / M_PI(); # dx,dy plus 180deg ### $angle ### $len my $to01 = Geometry::AffineTransform->new; $to01->translate(-$x_lo, -$y_lo); $to01->rotate(- $angle); if ($len) { $to01->scale(1/$len, 1/$len); } my $t = $types_hash{$type}; ### $t my $min_x = $t->{'min_x'}; my $min_y = $t->{'min_y'}; my $max_x = $t->{'max_x'}; my $max_y = $t->{'max_y'}; if (! defined $min_x) { $min_x = 0; $min_y = 0; $max_x = 0; $max_y = 0; foreach my $copy (@{$t->{'copies'}}) { my $this_min_x = -.5; my $this_max_x = 1.5; my $this_min_y = -1; my $this_max_y = .25; foreach (1 .. ($copy->{'rotate'} || 0)) { ($this_max_y, $this_min_x, $this_max_x, $this_min_y) = ($this_max_x, -$this_max_y, -$this_min_y, $this_min_x); } $this_min_x += $copy->{'x'}; $this_max_x += $copy->{'x'}; $this_min_y += $copy->{'y'}; $this_max_y += $copy->{'y'}; ### this extents: "X $this_min_x to $this_max_x Y $this_min_y to $this_max_y" $min_x = min($min_x, $this_min_x); $min_y = min($min_y, $this_min_y); $max_x = max($max_x, $this_max_x); $max_y = max($max_y, $this_max_y); } } ### extents: "X $min_x to $max_x Y $min_y to $max_y" # min_x ----------- 0 ---- max_x # ^ # mid = (max+min)/2 my $extent_x = $max_x - $min_x; my $extent_y = $max_y - $min_y; ### $extent_x ### $extent_y my $affine = Geometry::AffineTransform->new; $affine->translate(- ($min_x + $max_x)/2, # extent midpoints - ($min_y + $max_y)/2); my $extent_scale = min($width/$extent_x, $height/$extent_y) * .9; $affine->scale($extent_scale, $extent_scale); # shrink ### $extent_scale $affine->scale(1, -1); # Y upwards $affine->scale($scale, $scale); $affine->scale(-1,-1); # rotate 180 $affine->translate($width/2, $height/2); # 0,0 at centre $affine->translate($x_offset, $y_offset); my ($prev_x,$prev_y) = $to01->transform($x_lo,$y_lo); ### origin: "$prev_x, $prev_y" undef $dc; my $bitmap = Wx::Bitmap->new ($width, $height); my $scale = 0.5; # $scale = sqrt(3)/2; my $iterations = 100; my $n = $n_lo+1; $idle_drawing = sub { my ($event) = @_; ### idle_drawing: $event my $time = Time::HiRes::time(); # my $client_dc = Wx::ClientDC->new($draw); # my $dc = Wx::BufferedDC->new($client_dc, $bitmap); my $dc = Wx::ClientDC->new($draw); my $remaining = $iterations; for ( ; $n <= $n_hi; $n++) { if ($remaining-- < 0) { # each took time/iterations, want to take .25 sec so # new_iterations = .25/(time/iterations) # new_iterations = iterations * .25/time my $time = Time::HiRes::time() - $time; $iterations = int(($iterations+1) * .25/$time); # print "$iterations cf time $time\n"; if ($event) { $event->RequestMore(1); } return; } my ($x,$y) = $path->n_to_xy($n); ($x,$y) = $to01->transform($x,$y); ### point: "$x, $y" my $c = 0; foreach my $copy (@{$t->{'copies'}}) { $c++; my $x = $x; my $y = $y; my $prev_x = $prev_x; my $prev_y = $prev_y; if ($copy->{'invert'}) { $y = -$y; $prev_y = -$prev_y; } if (my $r = $copy->{'rotate'}) { foreach (1 .. $r) { ($x,$y) = (-$y,$x); # rotate +90 ($prev_x, $prev_y) = (-$prev_y, $prev_x); # rotate +90 } } $x += $copy->{'x'}; $y += $copy->{'y'}; $prev_x += $copy->{'x'}; $prev_y += $copy->{'y'}; my $dx = $x - $prev_x; my $dy = $y - $prev_y; my $mx = ($x + $prev_x)/2; # midpoint prev to this my $my = ($y + $prev_y)/2; if (defined $t->{'clip_min_x'}) { my $cx = $mx - $dy * $scale * .5; my $cy = $my + $dx * $scale * .5; if ($cx < $t->{'clip_min_x'} || $cx > $t->{'clip_max_x'} || $cy < $t->{'clip_min_y'} || $cy > $t->{'clip_max_y'}) { next; } } $mx += $dy * $scale; # dx,dy turned right -90deg $my -= $dx * $scale; # for triangle top ($prev_x,$prev_y) = $affine->transform($prev_x,$prev_y); ($mx, $my) = $affine->transform($mx,$my); ($x,$y) = $affine->transform($x,$y); ### screen: "$prev_x, $prev_y to $x, $y" if (xy_in_rect($x,$y, 0,0,$width,$height) || xy_in_rect($prev_x,$prev_y, 0,0,$width,$height) || xy_in_rect($mx,$my, 0,0,$width,$height)) { if ($figure eq 'Triangles') { $dc->SetBrush ($brushes[$c]); $dc->SetPen ($pens[$c]); $dc->DrawPolygon ([ Wx::Point->new($prev_x, $prev_y), Wx::Point->new($mx, $my), Wx::Point->new($x, $y), ], 0,0); } elsif ($figure eq 'Arrows') { $dx = $x - $prev_x; $dy = $y - $prev_y; $prev_x += $dx*.1; # shorten $prev_y += $dy*.1; $x -= $dx*.1; $y -= $dy*.1; my $rx = -$dy; # to the right my $ry = $dx; $prev_x += $rx*.05; # gap between overlapping segments $prev_y += $ry*.05; $x += $rx*.05; $y += $ry*.05; $dc->SetPen ($pens[$c]); $dc->DrawLines ([ Wx::Point->new($prev_x, $prev_y), Wx::Point->new($x, $y), Wx::Point->new($x - $dx*.25 + $rx*.12, # arrow harpoon $y - $dy*.25 + $ry*.12), ], 0,0); } else { # $figure eq 'Lines' $dc->SetPen ($pens[$c]); $dc->DrawLine ($prev_x,$prev_y, $x,$y); } } } # after all copies ($prev_x,$prev_y) = ($x,$y); } if ($type eq 'square') { $dc->SetBrush ($brushes[0]); $dc->SetPen ($pens[0]); my ($x1,$y1) = $affine->transform(-$y_hi,$x_hi); my ($x2,$y2) = $affine->transform($x_hi,$y_hi); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } $dc->DrawRectangle (0,0, $width,$y1-5); $dc->DrawRectangle (0,0, $x1-5, $height); $dc->DrawRectangle ($x2+5,0, $width, $height); $dc->DrawRectangle (0,$y2+5, $width,$height); } undef $idle_drawing; }; $idle_drawing->(); } sub OnIdle { my ($draw, $event) = @_; ### draw OnIdle(): $event if ($idle_drawing) { $idle_drawing->($event); } } sub xy_in_rect { my ($x,$y, $x1,$y1, $x2,$y2) = @_; return (($x >= $x1 && $x <= $x2) && ($y >= $y1 && $y <= $y2)); } ### $accel_table $draw->SetFocus; if ($window_initial_fullscreen) { $main->ShowFullScreen(1, FULLSCREEN_HIDE_BITS); } else { $main->Show; } $app->MainLoop; exit 0; Math-PlanePath-129/devel/0002755000175000017500000000000014001441522013000 5ustar ggggMath-PlanePath-129/devel/r5.pl0000644000175000017500000000470311507022742013674 0ustar gggg#!/usr/bin/perl -w # Copyright 2010 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use POSIX (); use List::Util 'min', 'max'; # uncomment this to run the ### lines use Smart::Comments; my $width = 79; my $height = 23; my @turn_right = (0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0); sub xturn_right { my ($n) = @_; return $turn_right[$n-1]; } sub turn_right { my ($n) = @_; while (($n % 5) == 0) { $n = int($n/5); } return ($n % 5) > 2; } { my %rows; my $x_min = 0; my $x_max = 0; my $y_min = 0; my $y_max = 0; my $cellwidth = 1; my $xd = 1; my $yd = 0; my $x = 0; my $y = 0; my $n = 1; foreach my $n (1 .. 500) { $x += $xd; $y += $yd; my $cell = $rows{$x}{$y}; if ($cell) { $cell .= '/'; } $cell .= $n; $rows{$x}{$y} = $cell; $cellwidth = max ($cellwidth, length($cell)+1); ### draw: "$x,$y $cell" $x_min = min ($x_min, $x); $x_max = max ($x_max, $x); $y_min = min ($y_min, $y); $y_max = max ($y_max, $y); my $turn_right = turn_right($n) // last; if ($turn_right) { ### right: "$xd,$yd -> $yd,@{[-$xd]}" ($xd,$yd) = ($yd,-$xd); } else { ### left: "$xd,$yd -> @{[-$yd]},$xd" ($xd,$yd) = (-$yd,$xd); } } ### $x_min ### $x_max ### $y_min ### $y_max foreach my $y (reverse $y_min .. $y_max) { foreach my $x ($x_min .. $x_max) { printf ('%*s', $cellwidth, $rows{$x}{$y} || ''); } print "\n"; } exit 0; } { foreach my $n (1 .. 50) { print turn($n),","; } print "\n"; exit 0; } Math-PlanePath-129/devel/sierpinski-arrowhead.pl0000644000175000017500000002225713233741061017504 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::PlanePath::SierpinskiArrowhead; # uncomment this to run the ### lines use Smart::Comments; { # turn # A189706 = ternary lowest non-1 and its position # A189707 positions of 0s, A189708 positions of 1s # A156595 = ternary lowest non-2 and its position # GP-DEFINE select_first_n(f,n) = { # GP-DEFINE my(l=List([]), i=0); # GP-DEFINE while(#l sub { require MyFLAT; require FLAT::Regex; return FLAT::Regex->new ('((0|1|2)* 0 | []) 1(11)* | (0|1|2)* 2(11)*')->as_dfa ->MyFLAT::minimize ->MyFLAT::set_name("A189707_ternary0"); }; use constant::defer A189708_ternary_flat => sub { require MyFLAT; require FLAT::Regex; return FLAT::Regex->new ('((0|1|2)* 0 | []) (11)* | (0|1|2)* 2 1(11)*')->as_dfa ->MyFLAT::minimize ->MyFLAT::set_name("A189708_ternary0"); }; use constant::defer ternary_any_flat => sub { require MyFLAT; require FLAT::Regex; return FLAT::Regex->new ('(0|1|2)*')->as_dfa ->MyFLAT::minimize ->MyFLAT::set_name("ternary any"); }; A189707_ternary_flat()->union(A189708_ternary_flat())->as_dfa ->equals(ternary_any_flat()) or die; # MyFLAT::FLAT_show_breadth(A189707_ternary_flat(),3); # MyFLAT::FLAT_show_breadth(A189708_ternary_flat(),3); # A189708_ternary_flat()->MyFLAT::reverse->MyFLAT::minimize->MyFLAT::view; # left = even+even or odd+odd my $f = FLAT::Regex->new ('(0|2)* (1 (0|2)* 1 (0|2)*)* (1|2) (00)* | (0|2)* 1 (0|2)* (1 (0|2)* 1 (0|2)*)* (1|2) 0(00)* ')->as_dfa ->MyFLAT::minimize; $f->MyFLAT::view; $f->MyFLAT::reverse->MyFLAT::minimize->MyFLAT::view; require Math::NumSeq::PlanePathTurn; require Math::BaseCnv; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SierpinskiArrowhead', turn_type => 'Left'); foreach (1 .. 400) { my ($i, $value) = $seq->next; my $i3 = Math::BaseCnv::cnv($i,10,3); my $calc = $f->contains($i3) ? 1 : 0; my $diff = ($value == $calc ? "" : " ***"); print "$i $i3 $value $calc$diff\n"; } exit 0; } { # turn sequence require Math::NumSeq::PlanePathTurn; require Math::BaseCnv; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SierpinskiArrowhead', turn_type => 'Left'); foreach (1 .. 400) { my ($i, $value) = $seq->next; my $i3 = Math::BaseCnv::cnv($i,10,3); # my $calc = calc_turnleft($i); my $calc = WORKING__calc_turnleft($i); my $diff = ($value == $calc ? "" : " ***"); print "$i $i3 $value $calc$diff\n"; } sub calc_turnleft { # not working my ($n) = @_; my $ret = 1; my $flip = 0; while ($n && ($n % 9) == 0) { $n = int($n/9); } if ($n) { my $digit = $n % 9; my $flip = ($digit == 0 || $digit == 1 # 01 # || $digit == 3 # 10 || $digit == 5 # 12 || $digit == 6 # 20 || $digit == 7 # 21 ); $ret ^= $flip; $n = int($n/9); } while ($n) { my $digit = $n % 9; my $flip = ($digit == 1 # 01 || $digit == 3 # 10 || $digit == 5 # 12 || $digit == 7 # 21 ); $ret ^= $flip; $n = int($n/9); } return $ret; } # GP-DEFINE CountLowZeros(n) = valuation(n,3); # vector(20,n, CountLowZeros(n)) # GP-DEFINE CountLowTwos(n) = my(ret=0); while(n%3==2, n\=3;ret++); ret; # GP-DEFINE CountLowOnes(n) = my(ret=0); while(n%3==1, n\=3;ret++); ret; # GP-DEFINE CountOnes(n) = vecsum(apply(d->d==1,digits(n,3))); # vector(20,n,n--; CountOnes(n)) # GP-DEFINE LowestNonZero(n) = { # GP-DEFINE if(n<1,error("LowestNonZero() is for n>=1")); \ # GP-DEFINE (n / 3^valuation(n,3)) % 3; # GP-DEFINE } # GP-DEFINE LowestNonOne(n) = while((n%3)==1,n=n\3); n%3; # GP-DEFINE LowestNonTwo(n) = while((n%3)==2,n=n\3); n%3; # GP-DEFINE CountOnesExceptLowestNonZero(n) = { # GP-DEFINE while(n && n%3==0, n/=3); # GP-DEFINE CountOnes(n\3); # GP-DEFINE } # vector(20,n,n--; CountOnes(n)) # GP-DEFINE turn_left(n) = ! turn_right(n); # GP-DEFINE turn_right(n) = (CountOnes(n) + LowestNonZero(n) + CountLowZeros(n)) % 2; # GP-DEFINE turn_right(n) = (CountOnesExceptLowestNonZero(n) + CountLowZeros(n)) % 2; # vector(20,n, turn_left(n)) # vector(22,n, turn_right(n)) # vector(15,n, turn_left(n)-turn_right(n)) # not in OEIS: 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1 # not in OEIS: 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0,1,1 # not in OEIS: 1, 1, -1, -1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, 1 # at odd and even positions # vector(15,n, turn_left(2*n)-turn_right(2*n)) # vector(18,n, turn_left(2*n-1)-turn_right(2*n-1)) # not in OEIS: 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, -1, -1, 1, -1, 1 # not in OEIS: 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1, 1, -1, -1, 1, 1, -1 # GP-Test vector(1000,m,m--; ((LowestNonOne(m)==0)+CountLowOnes(m))%2) == \ # GP-Test vector(1000,m, turn_left(2*m-1)) # GP-Test vector(1000,m,m--; ((LowestNonOne(m)==2)+CountLowOnes(m))%2) == \ # GP-Test vector(1000,m,m--; turn_right(2*m+1)) # GP-Test vector(1000,m,m--; ((LowestNonTwo(m)==0)+CountLowTwos(m))%2) == \ # GP-Test vector(1000,m, turn_left(2*m)) # GP-Test vector(1000,m,m--; (LowestNonTwo(m)+CountLowTwos(m))%2) == \ # GP-Test vector(1000,m, turn_right(2*m)) # GP-Test vector(1000,m, (LowestNonZero(m)+CountLowZeros(m))%2) == \ # GP-Test vector(1000,m, turn_left(2*m)) # GP-Test vector(1000,n, (n + LowestNonZero(n) + CountLowZeros(n))%2) == \ # GP-Test vector(1000,n, turn_right(n)) # vector(25,n, (1+LowestNonZero(n) + CountLowZeros(n))%2) # is A189706 with index change low-2s -> low-0s # ternary # [ count 1 digits ] [1 or 2] [ count low 0 digits ] # vector(10,k, (3^k)%2) # vector(10,k, (2*3^k)%2) sub WORKING__calc_turnleft { # works my ($n) = @_; my $ret = 1; while ($n && ($n % 3) == 0) { $ret ^= 1; # flip for trailing 0s $n = int($n/3); } $n = int($n/3); # skip lowest non-0 while ($n) { if (($n % 3) == 1) { # flip for all 1s $ret ^= 1; } $n = int($n/3); } return $ret; } sub count_digits { my ($n) = @_; my $count = 0; while ($n) { $count++; $n = int($n/3); } return $count; } sub count_1_digits { my ($n) = @_; my $count = 0; while ($n) { $count += (($n % 3) == 1); $n = int($n/3); } return $count; } exit 0; } { # direction sequence # 9-17 = mirror image horizontally 3-dir # 18-26 = dir+2 require Math::NumSeq::PlanePathDelta; require Math::BaseCnv; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => 'SierpinskiArrowhead', delta_type => 'TDir6'); foreach (1 .. 3**4+1) { my ($i, $value) = $seq->next; # $value %= 6; my $i3 = Math::BaseCnv::cnv($i,10,3); my $calc = calc_dir6($i); print "$i $i3 $value $calc\n"; } sub calc_dir6 { # works my ($n) = @_; my $dir = 1; while ($n) { if (($n % 9) == 0) { } elsif (($n % 9) == 1) { $dir = 3 - $dir; } elsif (($n % 9) == 2) { $dir = $dir + 2; } elsif (($n % 9) == 3) { $dir = 3 - $dir; } elsif (($n % 9) == 4) { } elsif (($n % 9) == 5) { $dir = 1 - $dir; } elsif (($n % 9) == 6) { $dir = $dir - 2; } elsif (($n % 9) == 7) { $dir = 1 - $dir; } elsif (($n % 9) == 8) { } $n = int($n/9); } return $dir % 6; } sub Xcalc_dir6 { # works my ($n) = @_; my $dir = 1; while ($n) { if (($n % 3) == 0) { } if (($n % 3) == 1) { # mirror $dir = 3 - $dir; } if (($n % 3) == 2) { $dir = $dir + 2; } $n = int($n/3); if (($n % 3) == 0) { } if (($n % 3) == 1) { # mirror $dir = 3 - $dir; } if (($n % 3) == 2) { $dir = $dir - 2; } $n = int($n/3); } return $dir % 6; } exit 0; } Math-PlanePath-129/devel/koch-squareflakes.pl0000644000175000017500000001174012375744415016771 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use Math::PlanePath::KochSquareflakes; # uncomment this to run the ### lines # use Smart::Comments; { # area # # start diag[0] = 0 # start straight[0] = 4 # diag[n+1] = 2*straight[n] + 2*diag[n] # straight[n+1] = 2*straight[n] + 2*diag[n] # # require Math::Geometry::Planar; my $path = Math::PlanePath::KochSquareflakes->new; my @values; my $prev_a_log = 0; my $prev_len_log = 0; foreach my $level (1 .. 7) { my $n_level = (4**($level+1) - 1) / 3; my $n_end = $n_level + 4**$level; my @points; foreach my $n ($n_level .. $n_end) { my ($x,$y) = $path->n_to_xy($n); push @points, [$x,$y]; } ### @points my $polygon = Math::Geometry::Planar->new; $polygon->points(\@points); my $a = $polygon->area; my $len = $polygon->perimeter; my $a_log = log($a); my $len_log = log($len); my $d_a_log = $a_log - $prev_a_log; my $d_len_log = $len_log - $prev_len_log; my $f = $d_a_log / $d_len_log; my $formula = area_by_formula($level); print "$level $a $formula\n"; # print "$level $d_len_log $d_a_log $f\n"; push @values, $a; $prev_a_log = $a_log; $prev_len_log = $len_log; } shift @values; require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; sub area_by_formula { my ($n) = @_; return (9**$n - 4**$n)/5; # return (4 * (9**$n - 4**$n)/5 + 16**$n); } } { # max extents of a single side # horiz: 1, 4, 14, 48, 164, 560, 1912, 6528, 22288, 76096, 259808, 887040 # A007070 a(n+1) = 4*a(n) - 2*a(n-1), starting 1,4 # # diag: 1, 3, 10, 34, 116, 396, 1352, 4616, 15760, 53808, 183712, 627232 # A007052 a(n+1) = 4*a(n) - 2*a(n-1), starting 1,3 # A007070 max horiz dist from ring start pos 4,14,48,164 side width # A206374 N of the max position 2,9,37,149 corner # A003480 X of the max position 2,7,24,82 last # A007052 max vert dist from ring start pos 3,10,34,116 height # A072261 N of the max Y position 7,29,117,469 Y axis # A007052 Y of the max position 3,10,34,116 my $path = Math::PlanePath::KochSquareflakes->new; my @values; my $coord = 1; foreach my $level (1 .. 8) { my $nstart = (4**($level+1) - 1) / 3; my $nend = $nstart + 4**$level; my @start = $path->n_to_xy($nstart); my $max_offset = 0; my $max_offset_n = $nstart; my $max_offset_c = $start[$coord]; foreach my $n ($nstart .. $nend) { my @this = $path->n_to_xy($n); my $offset = abs ($this[$coord] - $start[$coord]); if ($offset > $max_offset) { $max_offset = $offset; $max_offset_n = $n; $max_offset_c = $this[$coord]; } } push @values, $max_offset; print "level $level start=$start[$coord] max offset $max_offset at N=$max_offset_n (of $nstart to $nend) Y=$max_offset_c\n"; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; } { # X or Y coordinate of first point of ring # X or Y coord: 1, 2,7,24,82,280, # A003480 1,2,7 OFFSET=0 # A020727 2,7 # # cf A006012 same recurrence, start 1,2 my $path = Math::PlanePath::KochSquareflakes->new; my @values; foreach my $level (1 .. 12) { my $nstart = (4**($level+1) - 1) / 3; my ($x,$y) = $path->n_to_xy($nstart); push @values, -$y; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; } { # Xstart power # Xstart = b^level # b = Xstart^(1/level) # # D = P^2-4Q = 4^2-4*-2 = 24 # sqrt(24) = 4.898979485566356196394568149 # my $path = Math::PlanePath::KochSquareflakes->new; my $prev = 1; foreach my $level (1 .. 12) { my $nstart = (4**($level+1) - 1) / 3; my ($xstart,$ystart) = $path->n_to_xy($nstart); $xstart = -$xstart; my $f = $xstart / $prev; # my $b = $xstart ** (1/($level+1)); print "level=$level xstart=$xstart f=$f\n"; $prev = $xstart; } print "\n"; exit 0; } { my @horiz = (1); my @diag = (1); foreach my $i (0 .. 10) { $horiz[$i+1] = 2*$horiz[$i] + 2*$diag[$i]; $diag[$i+1] = $horiz[$i] + 2*$diag[$i]; $i++; } print "horiz: ",join(', ',@horiz),"\n"; print "diag: ",join(', ',@diag),"\n"; exit 0; } Math-PlanePath-129/devel/zorder.pl0000644000175000017500000000341013735570133014654 0ustar gggg#!/usr/bin/perl -w # Copyright 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'sum'; use Math::BaseCnv 'cnv'; use Math::PlanePath; use Math::PlanePath::ZOrderCurve; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; # uncomment this to run the ### lines use Smart::Comments; { # Numbers Samples # A(0,0), A(0,1), A(1,0), A(0,2), A(1,1), A(2,0), ... my $path = Math::PlanePath::ZOrderCurve->new (radix => 3); { print "%e X="; foreach my $x (0 .. 8) { printf "%d ", $x; } print "\n"; print '%e +', '-' x 40, "\n"; foreach my $y (0 .. 8) { print "%e ", ($y==0 ? "Y=" : " "), "$y | "; foreach my $x (0 .. 8) { last if $x+$y > 8; my $n = $path->xy_to_n($x,$y); printf "%2d, ", $n; } print "\n"; } } { foreach my $y (0 .. 8) { print "%e "; foreach my $x (0 .. 8) { last if $x+$y > 8; my $n = $path->xy_to_n($x,$y); printf "%2d, ", $n; } print "\n"; } } exit 0; } Math-PlanePath-129/devel/cubic-base.pl0000644000175000017500000000534212003406621015335 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use List::MoreUtils; use POSIX 'floor'; use Math::Libm 'M_PI', 'hypot'; use List::Util 'min', 'max'; use Math::BaseCnv 'cnv'; use lib 'xt'; # uncomment this to run the ### lines use Smart::Comments; { # smallest hypot in each level require Math::PlanePath::CubicBase; require Math::NumSeq::PlanePathDelta; my $tdir6_func = \&Math::NumSeq::PlanePathDelta::_delta_func_TDir6; my $radix = 2; my $path = Math::PlanePath::CubicBase->new (radix => $radix); foreach my $level (1 .. 30) { my $n_lo = $radix ** ($level-1); my $n_hi = $radix ** $level - 1; my $n = $n_lo; my $min_h = $path->n_to_rsquared($n); my @min_n = ($n); for ($n++; $n < $n_hi; $n++) { my $h = $path->n_to_rsquared($n); if ($h < $min_h) { @min_n = ($n); $min_h = $h; } elsif ($h == $min_h) { push @min_n, $n; } } print "level=$level\n"; # print " n=${n_lo}to$n_hi\n"; print " min_h=$min_h\n"; foreach my $n (@min_n) { my $nr = cnv($n,10,$radix); my ($x,$y) = $path->n_to_xy($n); my $xr = cnv($x,10,$radix); my $yr = cnv($y,10,$radix); my $tdir6 = $tdir6_func->(0,0,$x,$y); print " n=$n $nr xy=$x,$y $xr,$yr tdir6=$tdir6 \n"; } } exit 0; sub path_n_to_trsquared { my ($path,$n) = @_; my ($x,$y) = $path->n_to_xy($n); return $x*$x+3*$y*$y; } } { # Dir4 maximum require Math::PlanePath::CubicBase; require Math::NumSeq::PlanePathDelta; require Math::BigInt; my $path = Math::PlanePath::CubicBase->new; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => 'CubicBase', delta_type => 'Dir4'); my $dir4_max = 0; foreach my $level (0 .. 600) { my $n = Math::BigInt->new(2)**$level - 1; my $dir4 = $seq->ith($n); if (1 || $dir4 > $dir4_max) { $dir4_max = $dir4; my ($dx,$dy) = $path->n_to_dxdy($n); printf "%3d %2b,\n %2b %8.6f\n", $n, abs($dx),abs($dy), $dir4; } } exit 0; } Math-PlanePath-129/devel/pixel-rings.pl0000644000175000017500000001511713244155473015620 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use POSIX (); use List::Util 'min', 'max'; # uncomment this to run the ### lines use Smart::Comments; { # vs spectrum my $prev = 0; my $diff_total = 0; my $diff_count = 0; my $prev_count = 0; my $prev_sq = 0; foreach my $r (1 .. 6000) { my $count = image_count($r) / 4; my $dcount = $count - $prev_count - 1; my $xfrac = (1 + sqrt(8*($r+0)**2-1))/4; # my $x = (2 + sqrt(8*($r+0)**2-4))/4; my $y = int($xfrac+.5); my $x = int($xfrac); my $extra = (($y-1)**2 + ($y+.5)**2) < $r*$r; $extra = ($x==$y); # && (($x^$y^1)&1); my $sq = $y + $y-1 + $extra; my $dsq = $sq - $prev_sq; my $star = ($dsq != $dcount ? "***" : ""); # printf "%2d dc=%3d dsq=%4.2f %s\n", $r, $dcount,$dsq, $star; $star = (int($sq) != $count ? "***" : ""); printf "%2d c=%3d sq=%4.2f x=%4.2f,y=$y %s\n", $r, $count,$sq,$x, $star; $prev_count = $count; $prev_sq = $sq; } exit 0; sub floor_half { my ($n) = @_; return int(2*$n)/2; } } { my $r = 5; my $w = 2*$r+1; require Image::Base::Text; my $image = Image::Base::Text->new (-width => $w, -height => $w); $image->ellipse (0,0, $w-1,$w-1, 'x'); my $str = $image->save_string; print $str; exit 0; } { # wider ellipse() overlaps, near centre mostly my %image_coords; my $offset = 100; my $i; { package MyImageCoords; require Image::Base; use vars '@ISA'; @ISA = ('Image::Base'); sub new { my $class = shift; return bless {@_}, $class; } sub xy { my ($self, $x, $y, $colour) = @_; my $key = "$x,$y"; if ($image_coords{$key}) { $image_coords{$key} .= ','; } $image_coords{$key} .= $i; } } my $width = 500; my $height = 494; my $image = MyImageCoords->new (-width => $width, -height => $height); for ($i = 0; $i < min($width,$height)/2; $i++) { $image->ellipse ($i,$i, $width-1-$i,$height-1-$i, $i % 10); } foreach my $coord (keys %image_coords) { if ($image_coords{$coord} =~ /,/) { print "$coord i=$image_coords{$coord}\n"; } } exit 0; } { # wider ellipse() require Image::Base::Text; my $width = 40; my $height = 10; my $image = Image::Base::Text->new (-width => $width, -height => $height); for (my $i = 0; $i < min($width,$height)/2; $i++) { $image->ellipse ($i,$i, $width-1-$i,$height-1-$i, $i % 10); } $image->save('/dev/stdout'); exit 0; } { # average diff step 4*sqrt(2) require Image::Base::Text; my $prev = 0; my $diff_total = 0; my $diff_count = 0; foreach my $r (1 .. 1000) { my $count = image_count($r); my $diff = $count - $prev; # printf "%2d %3d %2d\n", $r, $count, $diff; $prev = $count; $diff_total += $diff; $diff_count++; } my $avg = $diff_total/$diff_count; my $sqavg = $avg*$avg; print "diff average $avg squared $sqavg\n"; exit 0; } { # vs int(sqrt(2)) require Image::Base::Text; my $prev = 0; my $diff_total = 0; my $diff_count = 0; my $prev_count = 0; my $prev_sq = 0; foreach my $r (1 .. 300) { my $count = image_count($r) / 4; my $dcount = $count - $prev_count - 1; my $sq = int(sqrt(2) * ($r+3)); my $dsq = $sq - $prev_sq - 1; my $star = ($dsq != $dcount ? "***" : ""); printf "%2d %3d %3d %s\n", $r, $dcount,$dsq, $star; $prev_count = $count; $prev_sq = $sq; } exit 0; } { # vs int(sqrt(2)) my $prev = 0; my $diff_total = 0; my $diff_count = 0; foreach my $r (1 .. 500) { my $count = image_count($r); my $sq = 4*int(sqrt(2) * ($r+1)); my $star = ($sq != $count ? "***" : ""); printf "%2d %3d %3d %s\n", $r, $count,$sq, $star; } exit 0; } my $width = 79; my $height = 23; my @rows; my @x; my @y; foreach my $r (0 .. 39) { my $rr = $r * $r; # E(x,y) = x^2*r^2 + y^2*r^2 - r^2*r^2 # # Initially, # d1 = E(x-1/2,y+1) # = (x-1/2)^2*r^2 + (y+1)^2*r^2 - r^2*r^2 # which for x=r,y=0 is # = r^2 - r^2*r + r^2/4 # = (r + 5/4) * r^2 # my $x = $r; my $y = 0; my $d = ($x-.5)**2 * $rr + ($y+1)**2 * $rr - $rr*$rr; my $count = 0; while ($x >= $y) { ### at: "$x,$y" ### assert: $d == ($x-.5)**2 * $rr + ($y+1)**2 * $rr - $rr*$rr push @x, $x; push @y, $y; $rows[$y]->[$x] = ($r%10); $count++; if( $d < 0 ) { $d += $rr * (2*$y + 3); ++$y; } else { $d += $rr * (2*$y - 2*$x + 5); ++$y; --$x; } } my $c = int (2*3.14159*$r/8 + .5); printf "%2d %2d %2d %s\n", $r, $count, $c, ($count!=$c ? "**" : ""); } foreach my $row (reverse @rows) { if ($row) { foreach my $char (@$row) { print ' ', $char // ' '; } } print "\n"; } { require Math::PlanePath::PixelRings; my $path = Math::PlanePath::PixelRings->new (wider => 0, # step => 0, ); ### range: $path->rect_to_n_range (0,0, 0,0) exit 0; } { # search OEIS require Image::Base::Text; my @count4; my @count; my @diffs4; my @diffs; my @diffs0; my $prev_count = 0; foreach my $r (1 .. 50) { my $count = image_count($r); push @count4, $count; push @count, $count/4; my $diff = $count - $prev_count; push @diffs4, $diff; push @diffs, $diff/4; push @diffs0, $diff/4 - 1; $prev_count = $count; } print "count4: ", join(',', @count4), "\n"; print "count: ", join(',', @count), "\n"; print "diffs4: ", join(',', @diffs4), "\n"; print "diffs: ", join(',', @diffs), "\n"; print "diffs0: ", join(',', @diffs0), "\n"; exit 0; } sub image_count { my ($r) = @_; my $w = 2*$r+1; require Image::Base::Text; my $image = Image::Base::Text->new (-width => $w, -height => $w); $image->ellipse (0,0, $w-1,$w-1, 'x'); my $str = $image->save_string; my $count = ($str =~ tr/x/x/); return $count; } Math-PlanePath-129/devel/fibonacci-word.logo0000644000175000017500000000270712335325716016572 0ustar gggg#!/usr/bin/ucblogo ;; Copyright 2012, 2014 Kevin Ryde ;; ;; This file is part of Math-PlanePath. ;; ;; Math-PlanePath 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, or (at your option) any later ;; version. ;; ;; Math-PlanePath 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 Math-PlanePath. If not, see . ;; hexagons overlapping much but slowly expanding to fibbinary.next :n localmake "filled bitor :n (lshift :n -1) localmake "mask lshift (bitxor :filled (:filled + 1)) -1 output (bitor :n :mask) + 1 end ; to print.binary :n ; do.while [ ; type bitand :n 1 ; make "n lshift :n -1 ; ] [:n <> 0] ; print " ; end ; make "n 0 ; for [i 0 21 1] [ ; print "n ; print :n ; print.binary :n ; make "n fibbinary.next :n ; ] to fib.hex :steps ; right 90 ; left 45 ; penup ; back 300 ; right 90 ; pendown localmake "step.len 10 localmake "n 0 for [i 0 :steps 1] [ forward :step.len if (bitand :n 1)=0 [left 60] [right 60] make "n fibbinary.next :n ] end fib.hex 210000Math-PlanePath-129/devel/theodorus.pl0000644000175000017500000002562712040145574015374 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.006; use strict; use warnings; use POSIX 'floor', 'fmod'; use Math::Trig 'pi', 'atan'; use Math::BigFloat try => 'GMP'; use Math::Libm 'hypot'; use Math::PlanePath::TheodorusSpiral; use Smart::Comments; { # Euler summation # kparse_from_string("TREE_a * (TREE_b / TREE_c)"); # my $pattern = Math::Symbolic::Custom::Pattern->new($formula); use Math::Symbolic::Custom::Transformation; my $trafo = Math::Symbolic::Custom::Transformation::Group->new (',', 'TREE_a * (TREE_b / TREE_c)' => '(TREE_a * TREE_b) / TREE_c', 'TREE_a * (TREE_b + TREE_c)' => 'TREE_a * TREE_b + TREE_a * TREE_c', '(TREE_b + TREE_c) * TREE_a' => 'TREE_b * TREE_a + TREE_c * TREE_a', # '(TREE_a / TREE_b) / TREE_c' => 'TREE_a / (TREE_b * TREE_c)', '(TREE_a / TREE_b) / (TREE_c / TREE_d)' => '(TREE_a * TREE_d) / (TREE_b * TREE_c)', '1 - TREE_a / TREE_b' => '(TREE_b - TREE_a) / TREE_b', 'TREE_a / TREE_b + TREE_c' => '(TREE_a + TREE_b * TREE_c) / TREE_b', '(TREE_a / TREE_b) * TREE_c' => '(TREE_a * TREE_c) / TREE_b', 'TREE_a - (TREE_b + TREE_c)' => 'TREE_a - TREE_b - TREE_c', '(TREE_a - TREE_b) - TREE_c' => 'TREE_a - TREE_b - TREE_c', ); sub simplify { my $tree = shift; ### simplify(): "$tree" ### traf: ($trafo->apply_recursive($tree)//'').'' return $trafo->apply_recursive($tree) || $tree; # if (my $m = $pattern->match($tree)) { # $m = $m->{'trees'}; # ### trees: $m # ### return: ($m->{'a'} * $m->{'b'}) / $m->{'c'} # return ($m->{'a'} * $m->{'b'}) / $m->{'c'}; # } else { # ### no match # return $tree; # } } __PACKAGE__->register(); } require Math::Symbolic; require Math::Symbolic::Derivative; { my $t = Math::Symbolic->parse_from_string('1/(x^2+1)'); $t = Math::Symbolic::Derivative::total_derivative($t, 'x'); $t = $t->simplify; print "$t\n"; exit 0; } { my $a = Math::Symbolic->parse_from_string( '(x+y)/(1-x*y)' ); my $z = Math::Symbolic->parse_from_string( 'z' ); my $t = ($a + $z) / (1 - $a*$z); $t = $t->simplify; print $t; exit 0; } } { my $path = Math::PlanePath::TheodorusSpiral->new; my $prev_x = 0; my $prev_y = 0; #for (my $n = 10; $n < 100000000; $n = int($n * 1.2)) { foreach my $n (2000, 2010, 2020, 2010, 2000, 2010, 2000, 2010) { my ($x,$y) = $path->n_to_xy($n); my $rsq = $x*$x+$y*$y; my $dx = $x - $prev_x; my $dy = $y - $prev_y; my $dxy_dist = hypot($dx,$dy); printf "%d %.2f,%.2f %.2f %.4f\n", $n, $x,$y, $rsq, $dxy_dist; ($prev_x, $prev_y) = ($x,$y); } exit 0; } sub integral { my ($x) = @_; print "log ", log(1+$x*$x), " at x=$x\n"; return $x * atan($x) - 0.5 * log (1 + $x*$x); } print "integral 0 = ", integral(0), "\n"; print "integral 1 = ", integral(1)/(2*pi()), "\n"; print "atan 1 = ", atan(1)/(2*pi()), "\n"; sub est { my ($n) = @_; my $k = $n-1; if ($k == 0) { return 0; } my $K = 2.1577829966; my $root = sqrt($k); my $a = 2*pi()*pi(); my $radians; $radians = integral(1/$root); # - integral(0); # $radians = ($k+1)*atan(1/$root) + $root - 1/($root*$k); return $radians / (2*pi()); # $radians = 2*$root; # return $radians / (2*pi()); # # $radians = $root - atan($root) + $k*atan(1/$root); # return $radians / (2*pi()); # # return $k / $a; # revolutions # return $k / pi(); # # return 2*$root / $a; # $radians = 2*sqrt($k+1) + $K + 1/(6*sqrt($k+1)); # plus O(n^(-3/2)) # return 0.5 * $a * ($k * sqrt(1+$k*$k) + log($k + sqrt(1+$k*$k))) / $k; # return $root + ($k+1)*atan(1/$root); } print "est 1 = ", est(1), "\n"; print "est 2 = ", est(2), "\n"; { require Math::Polynomial; open OUT, '>', '/tmp/theodorus.data' or die; my @n; my @theta; my $total = 0; foreach my $n (2 .. 120) { my $inc = Math::Trig::atan(1/sqrt($n-1)) / (2*pi()); # revs $total += $inc; my $est = est($n); my $diff = $total - $est; # $diff = 1/$diff; if ($n > 50) { push @n, $n-51; push @theta, $diff; print OUT "$n $diff\n"; } print "$n $inc $total $est $diff\n"; } print "\n"; Math::BigFloat->accuracy(500); my $p = Math::Polynomial->new; # (Math::BigFloat->new(0)); $p = $p->interpolate(\@n, \@theta); foreach my $i (0 .. $p->degree) { print "$i ",$p->coeff($i),"\n"; } # $p->string_config({ fold_sign => 1, # variable => 'n' }); # print "theta = $p\n"; close OUT or die; system "xterm -e 'gnuplot = $next) { $next++; my $diff = $n - $prev_n; my $diff_diff = $diff - $prev_diff; $total_diff_diff += $diff_diff; $count_diff_diff++; print "$n +$diff +$diff_diff $total\n"; if ($next >= 1000) { last; } $prev_n = $n; $prev_diff = $diff; } } my $avg = $total_diff_diff / $count_diff_diff; print "average $avg\n"; print "\n"; exit 0; } { my $c2 = 2.15778; my $t1 = 1.8600250; my $t2 = 0.43916457; my $z32 = 2.6123753486; my $tn1 = 2*$t1 - 2*$t2 - $z32; my $n = 1; my $x = 1; my $y = 0; while ($n < 10000) { my $r = sqrt($n); # before increment ($x, $y) = ($x - $y/$r, $y + $x/$r); $n++; $r = sqrt($n); # after increment my $theta = atan2($y,$x); if ($theta < 0) { $theta += 2*pi(); } my $root; $root = 2*sqrt($n) - $c2; # $root += .01/$r; # $root = -atan(sqrt($n)) + $n*atan(1/sqrt($n)) + sqrt($n); # $root = atan(1/sqrt($n)) - pi()/2 + $n*atan(1/sqrt($n)) + sqrt($n); $root = 2*sqrt($n) + 1/sqrt($n) - $c2 # - 1/($n*sqrt($n))/3 # + 1/($n*$n*sqrt($n))/5 # - 1/($n*$n*sqrt($n))/7 # + 1/($n*$n*$n*sqrt($n))/9 ; # $root = -pi()/4 + Arctan($r); # foreach my $k (2 .. 1000000) { # $root += atan(1/sqrt($k)) - atan(1/sqrt($k + $r*$r - 1)); # # $root += atan( ($r*$r - 1) / ( ($k + $r*$r)*sqrt($k) + ($k+1)*sqrt($k+$r*$r-1))); # } # $root = -pi()/2 + Arctan($r) + $t1 *$r*$r/2 + ($tn1 - $t1)*$r**2/8; $root = fmod ($root, 2*pi()); my $d = $root - $theta; $d = fmod ($d + pi(), 2*pi()) - pi(); # printf "%10.6f %10.6f %23.20f\n", $theta, $root, $d; printf "%23.20f\n", $d; } exit 0; } { my $t1 = 0; foreach my $k (1 .. 100) { $t1 += 1 / (sqrt($k) * ($k+1)); printf "%10.6f\n", $t1; } exit 0; } sub Arctan { my ($r) = @_; return pi()/2 - atan(1/$r); } { Math::BigFloat->accuracy(200); my $bx = Math::BigFloat->new(1); my $by = Math::BigFloat->new(0); my $x = 1; my $y = 0; my $n = 1; my @n = ($n); my @x = ($x); my @y = ($y); my $count = 0; my $prev_n = 0; my $prev_d = 0; my @dd; while ($n++ < 10000000) { my $r = hypot($x,$y); my $py = $y; ($x, $y) = ($x - $y/$r, $y + $x/$r); if ($py < 0 && $y >= 0) { my $d = $n-$prev_n; my $dd = $d-$prev_d; push @dd, $dd; printf "%5d +%4d +%3d %7.3f %10.6f %10.6f\n", $n, $d, $dd, # (sqrt($n)-1.07)/pi(), sqrt($n), $x, $y; $prev_n = $n; $prev_d = $d; if (++$count >= 10) { push @n, $n; push @x, $x; push @y, $y; $count = 0; } } } print "average dd ", List::Util::sum(@dd)/scalar(@dd),"\n"; # require Data::Dumper; # print Data::Dumper->new([\@n],['n'])->Indent(1)->Dump; # print Data::Dumper->new([\@x],['x'])->Indent(1)->Dump; # print Data::Dumper->new([\@y],['y'])->Indent(1)->Dump; # require Math::Polynomial; # my $p = Math::Polynomial->new(0); # $p = $p->interpolate([ 1 .. @nc ], \@nc); # $p->string_config({ fold_sign => 1, # variable => 'd' }); # print "N = $p\n"; exit 0; } { Math::BigFloat->accuracy(200); my $bx = Math::BigFloat->new(1); my $by = Math::BigFloat->new(0); my $x = 1; my $y = 0; my $n = 1; while ($n++ < 10000) { my $r = hypot($x,$y); ($x, $y) = ($x - $y/$r, $y + $x/$r); my $br = sqrt($bx*$bx + $by*$by); ($bx, $by) = ($bx - $by/$br, $by + $bx/$br); } my $ex = "$bx" + 0; my $ey = "$by" + 0; printf "%10.6f %10.6f %23.20f\n", $ex, $x, $ex - $x; exit 0; } Math-PlanePath-129/devel/exe-atan2.c0000644000175000017500000000347212005653477014754 0ustar gggg/* Copyright 2011, 2012 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . */ #include #include #include #include void dump (double d) { union { double d; unsigned char byte[8]; } u; u.d = d; printf ("%02X %02X %02X %02X %02X %02X %02X %02X\n", u.byte[0], u.byte[1], u.byte[2], u.byte[3], u.byte[4], u.byte[5], u.byte[6], u.byte[7]); } static const double double_ulong_max_plus_1 = ((double) ((ULONG_MAX >> 1)+1)) * 2.0; static const double double_ull_max_plus_1 = ((double) ((ULLONG_MAX >> 1)+1)) * 2.0; int main (void) { volatile double zero = 0; volatile double negzero = -zero; dump (zero); dump (negzero); printf ("%la %la\n", zero,negzero); printf ("%la\n", atan2(zero,zero)); printf ("%la\n", atan2(negzero,zero)); printf ("\n"); printf ("%la\n", atan2(zero,negzero)); printf ("%la\n", atan2(negzero,negzero)); printf ("\n"); printf ("ulong %la ", double_ulong_max_plus_1); dump (double_ulong_max_plus_1); printf (" %lf\n", double_ulong_max_plus_1); printf ("ull %la ", double_ull_max_plus_1); dump (double_ull_max_plus_1); printf (" %lf\n", double_ull_max_plus_1); exit (0); } Math-PlanePath-129/devel/sierpinski-triangle.gnuplot0000644000175000017500000000663612041164262020413 0ustar gggg#!/usr/bin/gnuplot # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . #------------------------------------------------------------------------------ set xrange [0:16]; set yrange [0:16] set key off set samples 256 splot (int(x)&int(y))==0 ? 1 : NaN with points pause 100 #------------------------------------------------------------------------------ triangle_x(n) = (n > 0 \ ? 2*triangle_x(int(n/3)) + digit_to_x(int(n)%3) \ : 0) triangle_y(n) = (n > 0 \ ? 2*triangle_y(int(n/3)) + digit_to_y(int(n)%3) \ : 0) digit_to_x(d) = (d==0 ? 0 : d==1 ? -1 : 1) digit_to_y(d) = (d==0 ? 0 : 1) # Plot the Sierpinski triangle to "level" many replications. # trange and samples are chosen so that the parameter t runs through # integers 0 to 3**level-1 inclusive. # level=6 set trange [0:3**level-1] # set samples 3**level # making t integers set parametric set key off plot triangle_x(t), triangle_y(t) with points pause 100 #------------------------------------------------------------------------------ # 0 0 0 # 1 -1 1 # 2 1 -1 # n%3 >= # triangle(n) = (n > 0 \ # ? 2*triangle(int(n/3)) + (int(n)%3==0 ? {0,0} \ # : int(n)%3==1 ? {-1,1} \ # : {1,1}) \ # : 0) # level=6 # set trange [0:3**level-1] # set samples 3**level # set parametric # set key off # plot real(triangle(t)), imag(triangle(t)) with points # # pause 100 # # #------------------------------------------------------------------------------ # root = cos(pi*2/3) + {0,1}*sin(pi*2/3) # # print root**0 # print root**1 # print root**2 # # # triangle(n) = (n > 0 \ # # ? (1+2*triangle(int(n/3)))*root**(int(n)%3) \ # # : 0) # # # left = cos(pi*2/3) + {0,1}*sin(pi*2/3) # # right = cos(pi*1/3) + {0,1}*sin(pi*1/3) # left = {-1,1} # right = {1,1} # # # t_to_x(t,size) = int(t / size) # t_to_y(t,size) = (int(t) % size) # # t_to_pyramid_x(t,size) = t_to_x(t,size) - t_to_y(t,size) # t_to_pyramid_y(t,size) = t_to_x(t,size) + t_to_y(t,size) # # sierpinski_x(t,size) = \ # (t_to_x(t,size) & t_to_y(t,size) \ # ? NaN \ # : t_to_pyramid_x(t,size)) # sierpinski_y(t,size) = \ # (t_to_x(t,size) & t_to_y(t,size) \ # ? NaN \ # : t_to_pyramid_y(t,size)) # # size=50 # set trange [0:size*size-1] # set samples size*size # set parametric # set key off # plot sierpinski_x(t,size), sierpinski_y(t,size) with points # # pause 100Math-PlanePath-129/devel/sierpinski-arrowhead-stars.pl0000644000175000017500000000263411612663016020635 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use POSIX (); use Math::PlanePath::SierpinskiArrowhead; # uncomment this to run the ### lines use Smart::Comments; { my $path = Math::PlanePath::SierpinskiArrowhead->new; my @rows = ((' ' x 79) x 64); foreach my $n (0 .. 3 * 3**4) { my ($x, $y) = $path->n_to_xy ($n); $x += 32; substr ($rows[$y], $x,1, '*'); } local $,="\n"; print reverse @rows; exit 0; } { my @rows = ((' ' x 64) x 32); foreach my $p (0 .. 31) { foreach my $q (0 .. 31) { next if ($p & $q); my $x = $p-$q; my $y = $p+$q; next if ($y >= @rows); $x += 32; substr ($rows[$y], $x,1, '*'); } } local $,="\n"; print reverse @rows; exit 0; } Math-PlanePath-129/devel/quintet.pl0000644000175000017500000001360713147425727015056 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2017 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.006; use strict; use warnings; use Math::Libm 'M_PI', 'hypot'; { # QuintetCurve turn sequence require Math::NumSeq::PlanePathTurn; { # turn # not in OEIS: -1,1,1,0,-1,0,-1,-1,1,1,-1,1,1,0,0,-1,1,1,0,0,0,-1,-1,1,-1,-1,1,1,0,0,0,-1,-1,1,0,0,-1,-1,1,-1,-1,1,1,0,1,0,-1,-1,1,1 my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'QuintetCurve', turn_type => 'LSR'); foreach (1 .. 50) { my ($i,$value) = $seq->next; print "$value,"; } print "\n"; } { # Left = lowest non-0 is 1,5,6 # not in OEIS: 0,1,1,0,0,0,0,0,1,1,0,1,1,0,0,0,1,1,0,0,0,0,0,1,0,0,1,1,0,0,0,0,0,1,0,0,0,0,1,0,0,1,1,0,1,0,0,0,1,1, my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'QuintetCurve', turn_type => 'Left'); foreach (1 .. 50) { my ($i,$value) = $seq->next; print "$value,"; } print "\n"; } exit 0; } { require Math::PlanePath::QuintetCurve; require Math::PlanePath::QuintetCentres; my $f = Math::PlanePath::QuintetCurve->new (arms=>4); my $c = Math::PlanePath::QuintetCentres->new (arms=>4); my $width = 5; my %saw; my $n_end = 5**($width-1) * $f->arms_count; foreach my $n (0 .. $n_end) { my ($x,$y) = $f->n_to_xy($n); my $cn = $c->xy_to_n($x,$y) // -1; my $cr = $c->xy_to_n($x+1,$y) // -1; my $cur = $c->xy_to_n($x+1,$y+1) // -1; my $cu = $c->xy_to_n($x, $y+1) // -1; # <----- my $cul = $c->xy_to_n($x-1,$y+1) // -1; # <----- my $cl = $c->xy_to_n($x-1,$y) // -1; # <----- my $cdl = $c->xy_to_n($x-1,$y-1) // -1; my $cd = $c->xy_to_n($x, $y-1) // -1; my $cdr = $c->xy_to_n($x+1,$y-1) // -1; if ($n == $cn) { $saw{'n'} = 0; } if ($n == $cr) { $saw{'r'} = 1; } if ($n == $cur) { $saw{'ur'} = 2; } if ($n == $cu) { $saw{'u'} = 3; } if ($n == $cul) { $saw{'ul'} = 4; } if ($n == $cl) { $saw{'l'} = 5; } if ($n == $cdl) { $saw{'dl'} = 6; } if ($n == $cd) { $saw{'d'} = 7; } if ($n == $cdr) { $saw{'dr'} = 8; } unless ($n == $cn || $n == $cr || $n == $cur || $n == $cu || $n == $cul || $n == $cl || $n == $cdl || $n == $cd || $n == $cdr) { die "$n"; } # print "$n5 $cn5 $ch5 $cw5 $cu5 $bad\n"; } my $saw = join(',', sort {$saw{$a}<=>$saw{$b}} keys %saw); print "$saw to n_end=$n_end\n"; exit 0; } { require Math::BaseCnv; require Math::PlanePath::QuintetCurve; require Math::PlanePath::QuintetCentres; my $f = Math::PlanePath::QuintetCurve->new; my $c = Math::PlanePath::QuintetCentres->new; my $width = 5; my %saw; foreach my $n (0 .. 5**($width-1)) { my $n5 = sprintf '%*s', $width, Math::BaseCnv::cnv($n,10,5); my ($x,$y) = $f->n_to_xy($n); my $cn = $c->xy_to_n($x,$y) || -1; my $cn5 = sprintf '%*s', $width, Math::BaseCnv::cnv($cn,10,5); my $rx = $x + 1; my $ry = $y; my $cr = $c->xy_to_n($rx,$ry) || -1; my $cr5 = sprintf '%*s', $width, Math::BaseCnv::cnv($cr,10,5); my $urx = $x + 1; my $ury = $y + 1; my $cur = $c->xy_to_n($urx,$ury) || -1; my $cur5 = sprintf '%*s', $width, Math::BaseCnv::cnv($cur,10,5); my $ux = $x; my $uy = $y + 1; my $cu = $c->xy_to_n($ux,$uy) || -1; my $cu5 = sprintf '%*s', $width, Math::BaseCnv::cnv($cu,10,5); my $ulx = $x - 1; my $uly = $y + 1; my $cul = $c->xy_to_n($ulx,$uly) || -1; my $cul5 = sprintf '%*s', $width, Math::BaseCnv::cnv($cul,10,5); my $lx = $x - 1; my $ly = $y; my $cl = $c->xy_to_n($lx,$ly) || -1; my $cl5 = sprintf '%*s', $width, Math::BaseCnv::cnv($cl,10,5); my $dlx = $x - 1; my $dly = $y - 1; my $cdl = $c->xy_to_n($dlx,$dly) || -1; my $cdl5 = sprintf '%*s', $width, Math::BaseCnv::cnv($cdl,10,5); my $dx = $x; my $dy = $y - 1; my $cd = $c->xy_to_n($dx,$dy) || -1; my $cd5 = sprintf '%*s', $width, Math::BaseCnv::cnv($cd,10,5); my $drx = $x + 1; my $dry = $y - 1; my $cdr = $c->xy_to_n($drx,$dry) || -1; my $cdr5 = sprintf '%*s', $width, Math::BaseCnv::cnv($cdr,10,5); if ($n == $cn) { $saw{'n'} = 0; } if ($n == $cr) { $saw{'r'} = 1; } if ($n == $cur) { $saw{'ur'} = 2; } if ($n == $cu) { $saw{'u'} = 3; } if ($n == $cul) { $saw{'ul'} = 4; } if ($n == $cl) { $saw{'l'} = 5; } if ($n == $cdl) { $saw{'dl'} = 6; } if ($n == $cd) { $saw{'d'} = 7; } if ($n == $cdr) { $saw{'dr'} = 8; } my $bad = ($n == $cn || $n == $cr || $n == $cur || $n == $cu || $n == $cul || $n == $cl || $n == $cdl || $n == $cd || $n == $cdr ? '' : ' ******'); # print "$n5 $cn5 $ch5 $cw5 $cu5 $bad\n"; } my $saw = join(',', sort {$saw{$a}<=>$saw{$b}} keys %saw); print "$saw\n"; exit 0; } { my $x = 1; my $y = 0; for (my $level = 1; $level < 20; $level++) { # (x+iy)*(2+i) ($x,$y) = (2*$x - $y, $x + 2*$y); if (abs($x) >= abs($y)) { $x -= ($x<=>0); } else { $y -= ($y<=>0); } print "$level $x,$y\n"; } exit 0; } Math-PlanePath-129/devel/r5-dragon.pl0000644000175000017500000007646012435205200015146 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use Math::BaseCnv; use List::MoreUtils; use POSIX 'floor'; use Math::Libm 'M_PI', 'hypot'; use List::Util 'min', 'max'; use Math::PlanePath::R5DragonCurve; use Math::BigInt try => 'GMP'; use Math::BigFloat; use lib 'devel/lib'; use lib 'xt'; use MyOEIS; # uncomment this to run the ### lines # use Smart::Comments; { # partial fractions require Math::Polynomial; Math::Polynomial->string_config({ascending=>1}); # x^3/((x-1)*(2*x-1)*(x^2-x+1)) require Math::Complex; my $b = Math::Complex->make(1,1); my @numerators = MyOEIS::polynomial_partial_fractions (Math::Polynomial->new(Math::Complex->make( 3, -2), Math::Complex->make(-10, 1), Math::Complex->make( 11, 5), Math::Complex->make( 2, -24), Math::Complex->make(-18, 18), Math::Complex->make( 8, -16), Math::Complex->make( 16, -32), ), # numerator # (( 16 - 32*I)*x^6 # + ( 8 - 16*I)*x^5 # + (-18 + 18*I)*x^4 # + ( 2 - 24*I)*x^3 # + ( 11 + 5*I)*x^2 # + (-10 + I)*x # + ( 3 - 2*I)) Math::Polynomial->new(1,-$b), # 1-b*x Math::Polynomial->new(1,1)**2, # 1+x Math::Polynomial->new(1,-2,2)**2, # 1 - 2x + 2x^2 Math::Polynomial->new(1, -$b, -2*$b**3), # 1 - b*x - 2*b^3*x^3 ); print "@numerators\n"; # my @numerators = MyOEIS::polynomial_partial_fractions # (Math::Polynomial->new(0,0,0,3), # numerator x^3 # Math::Polynomial->new(1,-1), # 1-x # Math::Polynomial->new(1,-2), # 1-2x # Math::Polynomial->new(1, -1, 1), # 1 - x + x^2 # ); # print "@numerators\n"; # my @numerators = MyOEIS::polynomial_partial_fractions # (1640 * Math::Polynomial->new(1), # numerator 1 # Math::Polynomial->new(1,-1), # 1-x # Math::Polynomial->new(1, 1), # 1+x # Math::Polynomial->new(1, -2, 2), # 1 - 2*x + 2*x^2 # ); # print "@numerators\n"; # use Math::BigRat; # my $o = Math::BigRat->new(1); # $o = 1; # my @numerators = MyOEIS::polynomial_partial_fractions # (1640 * Math::Polynomial->new(0*$o/10, 65*$o/10, 18*$o/10, 13*$o/10), # numerator 13*x^2 + 18*x + 65 # Math::Polynomial->new(25*$o, 6*$o, 1*$o), # (25 + 6*x + x^2) # Math::Polynomial->new(1*$o, -5*$o), # * (1-5*x) # Math::Polynomial->new(1*$o, -1*$o)); # (1-x) # print "@numerators\n"; # dragon dir N touching next level # p = (1-2*x^3)/(1-2*x-x^3+2*x^4) # (1-2*x^3)/((1-2*x)*(1-x)*(1+x+x^2)) * 21 == (18+12*x)/(1+x+x^2) + 3/(1-2*x) # p*21 == ((3 + 6*x + 12*x^2)/(1-x^3) + 3/(1-2*x) # p*21 == (-4 -5*x)/(1+x+x^2) + 7/(1-x) + 18/(1-2*x) # my @numerators = MyOEIS::polynomial_partial_fractions # (21 * Math::Polynomial->new(1,0,0,-2), # numerator 1-2*x^3 # Math::Polynomial->new(1,0,0,-1), # 1-x^3 # # Math::Polynomial->new(1,1,1), # 1+x+x^2 # # Math::Polynomial->new(1,-1), # 1-x # Math::Polynomial->new(1,-2)); # 1-2x # print "@numerators\n"; # # dragon JA[k] area # # x^4/ ((1 - x - 2*x^3)*(1-x)*(1-2*x)) # my @numerators = MyOEIS::polynomial_partial_fractions # (Math::Polynomial->new(1), # numerator # Math::Polynomial->new(1,-1,0,-2), # 1-x-2*x^3 # Math::Polynomial->new(1,-1)); # 1-x # print "@numerators\n"; # # dragon A[k] area # # x^4/ ((1 - x - 2*x^3)*(1-x)*(1-2*x)) # my @numerators = MyOEIS::polynomial_partial_fractions # (Math::Polynomial->new(2), # numerator # Math::Polynomial->new(1,-1,0,-2), # 1-x-2*x^3 # Math::Polynomial->new(1,-2), # 1-2*x # Math::Polynomial->new(1,-1)); # 1-x # print "@numerators\n"; # # dragon B[k]=R[k+1] total boundary # # (4 + 2 x + 4 x^2)/(1-x-2*x^3) + (-2)/(1-x) # my @numerators = MyOEIS::polynomial_partial_fractions # (Math::Polynomial->new(2,0,2), # numerator reduced 2*x + 2*x^3 # Math::Polynomial->new(1,-1,0,-2), # 1-x-2*x^3 # Math::Polynomial->new(1,-1)); # 1-x # print "@numerators\n"; # # dragon R right boundary # my @numerators = MyOEIS::polynomial_partial_fractions # (Math::Polynomial->new(1,0,1,0,2), # Math::Polynomial->new(1,-1,0,-2), # Math::Polynomial->new(1,-1)); # print "@numerators\n"; exit 0; } { # convex hull # hull 8 new vertices require Math::Geometry::Planar; my $points = [ [0,0], [1,0], [0,0] ]; $points = [ [Math::BigInt->new(0), Math::BigInt->new(0)], [Math::BigInt->new(1), Math::BigInt->new(0)], [Math::BigInt->new(0), Math::BigInt->new(0)] ]; my $end_x = Math::BigInt->new(1); my $end_y = Math::BigInt->new(0); my $path = Math::PlanePath::R5DragonCurve->new; my $num_points_prev = 0; for (my $k = Math::BigInt->new(0); $k < 40; $k++) { my $angle = 0; # Math::BigFloat->new($end_y)->batan2(Math::BigFloat->new($end_x), 10); my $num_points = scalar(@$points); my $num_points_diff = $num_points - $num_points_prev; print "k=$k end=$end_x,$end_y a=$angle $num_points diff=$num_points_diff\n"; my @new_points = @$points; { my $p = Math::Geometry::Planar->new; $p->points(points_copy($points)); $p->move (-$end_y, $end_x); push @new_points, @{$p->points}; ### move 1: $p->points } { my $p = Math::Geometry::Planar->new; $p->points(points_copy($points)); $p->move (2*-$end_y, 2*$end_x); push @new_points, @{$p->points}; ### move 2: $p->points } { my $p = Math::Geometry::Planar->new; $p->points(points_copy($points)); planar_rotate_plus90($p); push @new_points, @{$p->points}; ### rot: $p->points } { my $p = Math::Geometry::Planar->new; $p->points(points_copy($points)); planar_rotate_plus90($p); $p->move ($end_x + -$end_y, $end_y + $end_x); push @new_points, @{$p->points}; ### rot move: $p->points } my $p = Math::Geometry::Planar->new; $p->points(\@new_points); $p = $p->convexhull2; $points = $p->points; ($end_x,$end_y) = ($end_x - 2*$end_y, $end_y + 2*$end_x); $num_points_prev = $num_points; my ($x,$y) = $path->n_to_xy(5**($k+1)); ### $end_y ### $y $x == $end_x or die; $y == $end_y or die; } exit 0; sub planar_rotate_plus90 { my ($planar) = @_; my $points = $planar->points; foreach my $p (@$points) { ($p->[0],$p->[1]) = (- $p->[1], $p->[0]); } return $planar; } sub points_copy { my ($points) = @_; return [ map {[$_->[0],$_->[1]]} @$points ]; } # { # my $pl = Math::Geometry::Planar->new; # $pl->points($points); # $pl->rotate(- atan2(2,1)); # $pl->scale(1/sqrt(5)); # $points = $pl->points; # } } { # extents h->4/5 w->2/5 # 1/sqrt(5) # *--* 1/5 + 4/5 = 1 # 2/sqrt(5) | / 1 # |/ # * # my $h = 0; my $w = 0; my $sum = 0; foreach my $k (0 .. 20) { print "$h $w $sum\n"; $sum += (3/5)**$k; $h /= sqrt(5); $w /= sqrt(5); my $s = 1/sqrt(5); my $add = $s * 2/sqrt(5); ($h, $w) = ($h*2/sqrt(5) + $w*1/sqrt(5) + $add, $h*2/sqrt(5) + $w*1/sqrt(5)); } exit 0; } { # min/max for level # radial extent # # dist0to5 = sqrt(1*1+2*2) = sqrt(5) # # 4-->5 # ^ # | # 3<--2 # ^ # | # 0-->1 # # Rlevel = sqrt(5)^level + Rprev # = sqrt(5) + sqrt(5)^2 + ... + sqrt(5)^(level-1) + sqrt(5)^level # if level # = sqrt(5) + sqrt(5)^2 + sqrt(5)*sqrt(5)^2 + ... # = sqrt(5) + (1+sqrt(5))*5^1 + (1+sqrt(5))*5^2 + ... # = sqrt(5) + (1+sqrt(5))* [ 5^1 + 5^2 + ... ] # = sqrt(5) + (1+sqrt(5))* (5^k - 1)/4 # <= 5^k # Rlevel^2 <= 5^level require Math::BaseCnv; require Math::PlanePath::R5DragonCurve; my $path = Math::PlanePath::R5DragonCurve->new; my $prev_min = 1; my $prev_max = 1; for (my $level = 1; $level < 10; $level++) { my $n_start = 5**($level-1); my $n_end = 5**$level; my $min_hypot = 128*$n_end*$n_end; my $min_x = 0; my $min_y = 0; my $min_pos = ''; my $max_hypot = 0; my $max_x = 0; my $max_y = 0; my $max_pos = ''; print "level $level n=$n_start .. $n_end\n"; foreach my $n ($n_start .. $n_end) { my ($x,$y) = $path->n_to_xy($n); my $h = $x*$x + $y*$y; if ($h < $min_hypot) { $min_hypot = $h; $min_pos = "$x,$y"; } if ($h > $max_hypot) { $max_hypot = $h; $max_pos = "$x,$y"; } } # print " min $min_hypot at $min_x,$min_y\n"; # print " max $max_hypot at $max_x,$max_y\n"; { my $factor = $min_hypot / $prev_min; my $min_hypot_5 = Math::BaseCnv::cnv($min_hypot,10,5); print " min r^2 $min_hypot ${min_hypot_5}[5] at $min_pos factor $factor\n"; } { my $factor = $max_hypot / $prev_max; my $max_hypot_5 = Math::BaseCnv::cnv($max_hypot,10,5); print " max r^2 $max_hypot ${max_hypot_5}[5]) at $max_pos factor $factor\n"; } $prev_min = $min_hypot; $prev_max = $max_hypot; } exit 0; } { # boundary length between arms = 2*3^k # # *---1 length=6 # | # 2 *---*---* # | | | | # *---* 0---* # # T[0] = 2 # T[k+1] = R[k] + T[k] + U[k] # T[k+1] = 4*3^k + T[k] # i=k-1 # T[k] = 2 + sum 4*3^i # i=0 # = 2 + 4*(3^k - 1)/(3-1) # = 2 + 2*(3^k - 1) # = 2*3^k my $arms = 2; my $path = Math::PlanePath::R5DragonCurve->new (arms => $arms); my @values; foreach my $k (0 .. 8) { my $n_limit = $arms * 5**$k + $arms-1; my $n_from = $n_limit-1; my $n_to = $n_limit; print "k=$k n_limit=$n_limit\n"; my $points = MyOEIS::path_boundary_points_ft ($path, $n_limit, $path->n_to_xy($n_from), $path->n_to_xy($n_to), side => 'right', ); if (@$points < 10) { foreach my $p (@$points) { print " $p->[0],$p->[1]"; } print "\n"; } my $length = scalar(@$points) - 1; print " length $length\n"; push @values, $length; } shift @values; print join(',',@values),"\n"; Math::OEIS::Grep->search(array => \@values); exit 0; } { # right boundary N my $path = Math::PlanePath::R5DragonCurve->new; my %non_values; my %n_values; my @n_values; my @values; foreach my $k (3){ my $n_limit = 5**$k; print "k=$k n_limit=$n_limit\n"; foreach my $n (0 .. $n_limit-1) { $non_values{$n} = 1; } my $points = MyOEIS::path_boundary_points ($path, $n_limit, side => 'right', ); ### $points for (my $i = 0; $i+1 <= $#$points; $i++) { my ($x,$y) = @{$points->[$i]}; my ($x2,$y2) = @{$points->[$i+1]}; # my @n_list = $path->xy_to_n_list($x,$y); my @n_list = path_xyxy_to_n($path, $x,$y, $x2,$y2); foreach my $n (@n_list) { delete $non_values{$n}; if ($n <= $n_limit) { $n_values{$n} = 1; } my $n5 = Math::BaseCnv::cnv($n,10,5); my $pred = $path->_UNDOCUMENTED__n_segment_is_right_boundary($n); my $diff = $pred ? '' : ' ***'; if ($k <= 4) { print "$n $n5$diff\n"; } } } @n_values = keys %n_values; @n_values = sort {$a<=>$b} @n_values; my @non_values = keys %non_values; @non_values = sort {$a<=>$b} @non_values; my $count = scalar(@n_values); print "count $count\n"; # push @values, $count; @values = @n_values; if ($k <= 4) { foreach my $n (@non_values) { my $pred = $path->_UNDOCUMENTED__n_segment_is_right_boundary($n); my $diff = $pred ? ' ***' : ''; my $n5 = Math::BaseCnv::cnv($n,10,5); print "non $n $n5$diff\n"; } } # @values = @non_values; # print "func "; # foreach my $i (0 .. $count-1) { # my $n = $path->_UNDOCUMENTED__right_boundary_i_to_n($i); # my $n5 = Math::BaseCnv::cnv($n,10,5); # print "$n,"; # } # print "\n"; print "vals "; foreach my $i (0 .. $count-1) { my $n = $values[$i]; my $n5 = Math::BaseCnv::cnv($n,10,5); print "$n,"; } print "\n"; } @values = MyOEIS::first_differences(@values); shift @values; shift @values; shift @values; print join(',',@values),"\n"; Math::OEIS::Grep->search(array => \@values); exit 0; sub path_xyxy_to_n { my ($path, $x1,$y1, $x2,$y2) = @_; ### path_xyxy_to_n(): "$x1,$y1, $x2,$y2" my @n_list = $path->xy_to_n_list($x1,$y1); ### @n_list my $arms = $path->arms_count; foreach my $n (@n_list) { my ($x,$y) = $path->n_to_xy($n + $arms); if ($x == $x2 && $y == $y2) { return $n; } } return; } } { my $C = sub { my ($k) = @_; return 3**$k - $k; # A024024 }; my $E = sub { my ($k) = @_; return 3**$k + $k; # A104743 }; my @values = map { $E->($_) } 0 .. 10; print join(',',@values),"\n"; Math::OEIS::Grep->search(array => \@values); exit; } { # left boundary N my $path = Math::PlanePath::R5DragonCurve->new; my %non_values; my %n_values; my @n_values; my @values; foreach my $k (2) { my $n_limit = 3*5**$k; print "k=$k n_limit=$n_limit\n"; foreach my $n (0 .. $n_limit-1) { $non_values{$n} = 1; } my $points = MyOEIS::path_boundary_points ($path, $n_limit, side => 'left', ); @$points = reverse @$points; # for left ### $points for (my $i = 0; $i+1 <= $#$points; $i++) { my ($x,$y) = @{$points->[$i]}; my ($x2,$y2) = @{$points->[$i+1]}; # my @n_list = $path->xy_to_n_list($x,$y); my @n_list = path_xyxy_to_n($path, $x,$y, $x2,$y2); foreach my $n (@n_list) { delete $non_values{$n}; if ($n <= $n_limit) { $n_values{$n} = 1; } my $n5 = Math::BaseCnv::cnv($n,10,5); my $pred = $path->_UNDOCUMENTED__n_segment_is_left_boundary($n); my $diff = $pred ? '' : ' ***'; if ($k <= 4) { print "$n $n5$diff\n"; } } } @n_values = keys %n_values; @n_values = sort {$a<=>$b} @n_values; my @non_values = keys %non_values; @non_values = sort {$a<=>$b} @non_values; my $count = scalar(@n_values); print "count $count\n"; # push @values, $count; @values = @n_values; if ($k <= 4) { foreach my $n (@non_values) { my $pred = $path->_UNDOCUMENTED__n_segment_is_left_boundary($n); my $diff = $pred ? ' ***' : ''; my $n5 = Math::BaseCnv::cnv($n,10,5); print "non $n $n5$diff\n"; } } # @values = @non_values; # print "func "; # foreach my $i (0 .. $count-1) { # my $n = $path->_UNDOCUMENTED__left_boundary_i_to_n($i); # my $n5 = Math::BaseCnv::cnv($n,10,5); # print "$n,"; # } # print "\n"; print "vals "; foreach my $i (0 .. $count-1) { my $n = $values[$i]; my $n5 = Math::BaseCnv::cnv($n,10,5); print "$n5,"; } print "\n"; } # @values = MyOEIS::first_differences(@values); shift @values; shift @values; shift @values; print join(',',@values),"\n"; Math::OEIS::Grep->search(array => \@values); exit 0; } { # recurrence # v3 = a*v0 + b*v1 + c*v2 # [v0 v1 v2] [a] [v3] # [v1 v2 v3] [b] = [v4] # [v2 v3 v4] [c] [v5] # [a] [v0 v1 v2] -1 [v1] # [b] = [v1 v2 v3] * [v2] # [c] [v2 v3 v4] [v3] $|=1; my @array = ( 54,90,150,250,422,714,1206,2042,3462 ); # @array = (); # foreach my $k (5 .. 10) { # push @array, R_formula(2*$k+1); # } # require MyOEIS; # my $path = Math::PlanePath::R5DragonCurve->new; # foreach my $k (0 .. 30) { # my $value = MyOEIS::path_boundary_length($path, 5**$k, # # side => 'left', # ); # last if $value > 10_000; # push @array, $value; # print "$value,"; # } print "\n"; array_to_recurrence_pari(\@array); print "\n"; my @recurr = array_to_recurrence(\@array); print join(', ',@recurr),"\n"; exit 0; sub array_to_recurrence_pari { my ($aref) = @_; my $order = int(scalar(@array)/2); # 2*order-1 = @array-1 my $str = "m=["; foreach my $i (0 .. $order-1) { if ($i) { $str .= "; " } foreach my $j (0 .. $order-1) { if ($j) { $str .= "," } $str .= $aref->[$i+$j]; } } $str .= "]\n"; $str .= "v=["; foreach my $i ($order .. 2*$order-1) { if ($i > $order) { $str .= ";" } $str .= $aref->[$i]; } $str .= "];"; $str .= "(m^-1)*v\n"; print $str; require IPC::Run; IPC::Run::run(['gp'],'<',\$str); } sub array_to_recurrence { my ($aref) = @_; # 2*order-1 = @array-1 my $order = int(scalar(@array)/2); require Math::Matrix; my $m = Math::Matrix->new(map {[ map { $array[$_] } $_ .. $_+$order-1 ]} 0 .. $order-1); print $m; print $m->determinant,"\n"; my $v = Math::Matrix->new(map {[ $array[$_] ]} $order .. 2*$order-1); print $v; $m = $m->invert; print $m; $v = $m*$v; print $v; return (map {$v->[$_][0]} reverse 0 .. $order-1); } } { # at N=29 require Math::NumSeq::PlanePathDelta; require Math::PlanePath::R5DragonMidpoint; my $path = Math::PlanePath::R5DragonMidpoint->new; my $n = 29; my ($x,$y) = $path->n_to_xy($n); my ($dx,$dy) = $path->n_to_dxdy($n); my $tradius = Math::NumSeq::PlanePathCoord::_path_n_to_tradius($path,$n); my $next_tradius = Math::NumSeq::PlanePathCoord::_path_n_to_tradius($path,$n + $path->arms_count); my $dtradius = Math::NumSeq::PlanePathDelta::_path_n_to_dtradius($path,$n); print "$n x=$x,y=$y $dx,$dy dtradius=$dtradius\n"; print " tradius $tradius to $next_tradius\n"; exit 0; } { # first South step dY=-1 on Y axis require Math::PlanePath::R5DragonMidpoint; my $path = Math::PlanePath::R5DragonMidpoint->new; require Math::NumSeq::PlanePathDelta; my $seq = Math::NumSeq::PlanePathDelta->new (path => $path); my @values; my $n = 0; OUTER: for ( ; ; $n++) { my ($x,$y) = $path->n_to_xy($n); my ($dx,$dy) = $path->n_to_dxdy($n); if ($x == 0 && $dx == 0 && $dy == -($y < 0 ? -1 : 1)) { my $tradius = Math::NumSeq::PlanePathCoord::_path_n_to_tradius($path,$n); my $next_tradius = Math::NumSeq::PlanePathCoord::_path_n_to_tradius($path,$n + $path->arms_count); my $dtradius = Math::NumSeq::PlanePathDelta::_path_n_to_dtradius($path,$n); print "$n $x,$y $dx,$dy dtradius=$dtradius\n"; print " tradius $tradius to $next_tradius\n"; push @values, $n; last OUTER if @values > 20; } } print join(',',@values),"\n"; Math::OEIS::Grep->search(array => \@values); exit 0; } { # any South step dY=-1 on Y axis # use Math::BigInt try => 'GMP'; # use Math::BigFloat; require Math::PlanePath::R5DragonMidpoint; my $path = Math::PlanePath::R5DragonMidpoint->new; require Math::NumSeq::PlanePathDelta; my $seq = Math::NumSeq::PlanePathDelta->new (path => $path); my @values; my $x = 0; my $y = 0; # $x = Math::BigFloat->new($x); # $y = Math::BigFloat->new($y); OUTER: for ( ; ; $y++) { ### y: "$y" foreach my $sign (1,-1) { ### at: "$x, $y sign=$sign" if (defined (my $n = $path->xy_to_n($x,$y))) { my ($dx,$dy) = $path->n_to_dxdy($n); ### dxdy: "$dx, $dy" if ($dx == 0 && $dy == $sign) { my $tradius = Math::NumSeq::PlanePathCoord::_path_n_to_tradius($path,$n); my $next_tradius = Math::NumSeq::PlanePathCoord::_path_n_to_tradius($path,$n + $path->arms_count); my $dtradius = Math::NumSeq::PlanePathDelta::_path_n_to_dtradius($path,$n); print "$n $x,$y $dx,$dy dtradius=$dtradius\n"; print " tradius $tradius to $next_tradius\n"; push @values, $y; last OUTER if @values > 20; } } $y = -$y; } } print join(',',@values),"\n"; require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; } { # boundary join 4,13,40,121,364 # A003462 (3^n - 1)/2. require Math::PlanePath::R5DragonCurve; my $path = Math::PlanePath::R5DragonCurve->new; my @values; $| = 1; foreach my $exp (2 .. 6) { my $t_lo = 5**$exp; my $t_hi = 2*5**$exp - 1; my $count = 0; foreach my $n (0 .. $t_lo-1) { my ($x,$y) = $path->n_to_xy($n); my @n_list = $path->xy_to_n_list($x,$y); if (@n_list >= 2 && $n_list[0] < $t_lo && $n_list[1] >= $t_lo && $n_list[1] < $t_hi) { $count++; } } push @values, $count; print "$count,"; } print "\n"; require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; } { # overlaps require Math::PlanePath::R5DragonCurve; require Math::BaseCnv; my $path = Math::PlanePath::R5DragonCurve->new; my $width = 5; foreach my $n (0 .. 5**($width-1)) { my ($x,$y) = $path->n_to_xy($n); my @n_list = $path->xy_to_n_list($x,$y); next unless @n_list >= 2; if ($n_list[1] == $n) { ($n_list[0],$n_list[1]) = ($n_list[1],$n_list[0]); } my $n_list = join(',',@n_list); my @n5_list = map { sprintf '%*s', $width, Math::BaseCnv::cnv($_,10,5) } @n_list; print "$n5_list[0] $n5_list[1] ($n_list)\n"; } exit 0; } { # tiling require Image::Base::Text; require Math::PlanePath::R5DragonCurve; my $path = Math::PlanePath::R5DragonCurve->new; my $width = 37; my $height = 21; my $image = Image::Base::Text->new (-width => $width, -height => $height); my $xscale = 3; my $yscale = 2; my $w2 = int(($width+1)/2); my $h2 = int($height/2); $w2 -= $w2 % $xscale; $h2 -= $h2 % $yscale; my $affine = sub { my ($x,$y) = @_; return ($x*$xscale + $w2, -$y*$yscale + $h2); }; my ($n_lo, $n_hi) = $path->rect_to_n_range(-$w2/$xscale, -$h2/$yscale, $w2/$xscale, $h2/$yscale); print "n to $n_hi\n"; foreach my $n ($n_lo .. $n_hi) { next if ($n % 5) == 2; my ($x,$y) = $path->n_to_xy($n); my ($next_x,$next_y) = $path->n_to_xy($n+1); foreach (1 .. 4) { $image->line ($affine->($x,$y), $affine->($next_x,$next_y), ($x==$next_x ? '|' : '-')); $image->xy ($affine->($x,$y), '+'); $image->xy ($affine->($next_x,$next_y), '+'); ($x,$y) = (-$y,$x); # rotate +90 ($next_x,$next_y) = (-$next_y,$next_x); # rotate +90 } } $image->xy ($affine->(0,0), 'o'); foreach my $x (0 .. $width-1) { foreach my $y (0 .. $height-1) { next unless $image->xy($x,$y) eq '+'; if ($x > 0 && $image->xy($x-1,$y) eq ' ') { $image->xy($x,$y, '|'); } elsif ($x < $width-1 && $image->xy($x+1,$y) eq ' ') { $image->xy($x,$y, '|'); } elsif ($y > 0 && $image->xy($x,$y-1) eq ' ') { $image->xy($x,$y, '-'); } elsif ($y < $height-1 && $image->xy($x,$y+1) eq ' ') { $image->xy($x,$y, '-'); } } } $image->save('/dev/stdout'); exit 0; } { # area recurrence foreach my $i (0 .. 10) { print recurrence($i),","; } print "\n"; print "wrong(): "; foreach my $i (0 .. 10) { print wrong($i),","; } print "\n"; print "recurrence_area815(): "; foreach my $i (0 .. 10) { print recurrence_area815($i),","; } print "\n"; print "recurrence_area43(): "; foreach my $i (0 .. 10) { print recurrence_area43($i),","; } print "\n"; print "formula_pow(): "; foreach my $i (0 .. 10) { print formula_pow($i),","; } print "\n"; print "recurrence_areaSU(): "; foreach my $i (0 .. 10) { print recurrence_areaSU($i),","; } print "\n"; print "recurrence_area2S(): "; foreach my $i (0 .. 10) { print recurrence_area2S($i),","; } print "\n"; exit 0; # A[n+1] = 4*A[n] - 3*A[n-1] + 4*5^(n-1) # - A[n+1] + 4*A[n] + 4*5^(n-1) = 3*A[n-1] # 3*A[n-1] = - A[n+1] + 4*A[n] + 4*5^(n-1) # 3*A[n-2] = - A[n] + 4*A[n-1] + 4*5^(n-2) # D[n+1] = 4*A[n] - 3*A[n-1] + 4*5^(n-1) # - (4*A[n-1] - 3*A[n-2] + 4*5^(n-2)) # = 4*A[n] - 3*A[n-1] + 4*5^(n-1) # - 4*A[n-1] + 3*A[n-2] - 4*5^(n-2)) # = 4*A[n] - 3*A[n-1] + 4*5^(n-1) # - 4*A[n-1] - A[n] + 4*A[n-1] + 4*5^(n-2) - 4*5^(n-2)) # = 4*A[n] - 3*A[n-1] + 4*5^(n-1) # - A[n] # D[n+1] = 4*A[n] - 3*A[n-1] + 4*5^(n-1) # - A[n] # D[n+1] = 3*A[n] - 3*A[n-1] + 4*5^(n-1) # D[n+1] = 3*D[n] + 4*5^(n-1) # = 4*A[n] - 7*A[n-1] + 3*A[n-2] + (4*5-4)*5^(n-2) # = 4*A[n] - 7*A[n-1] + 3*A[n-2] + 16*5^(n-2) # = 4*A[n] - 7*A[n-1] + A[n] + 4*A[n-1] + 4*5^(n-2) + 16*5^(n-2) # = 3*A[n] - 3*A[n-1] + 20*5^(n-2) # 4*A[n] - 12*A[n-1] + 4 - 4*5^(n-1) = 0 ?? sub wrong { my ($n) = @_; if ($n <= 0) { return 0; } if ($n == 1) { return 0; } return 4*wrong($n-1) + 4*5**($n-2); } # A[n] = (5^k - 2*3^k + 1)/2 sub formula_pow { my ($n) = @_; return (5**$n - 2*3**$n + 1) / 2; } sub recurrence_area43 { my ($n) = @_; if ($n <= 0) { return 0; } if ($n == 1) { return 0; } return 4*recurrence_area43($n-1) - 3*recurrence_area43($n-2) + 4*5**($n-2); } # A[n+1] = 8*A[n] - 15*A[n-1] + 4 sub recurrence_area815 { my ($n) = @_; if ($n <= 0) { return 0; } if ($n == 1) { return 0; } return 8*recurrence_area815($n-1) - 15*recurrence_area815($n-2) + 4; } sub recurrence { my ($n) = @_; if ($n <= 0) { return 0; } if ($n == 1) { return 2; } return 8*recurrence($n-1) - 15*recurrence($n-2) + 2; } sub recurrence_area2S { my ($n) = @_; return 2*recurrence_S($n+1); } sub recurrence_areaSU { my ($n) = @_; return 4*recurrence_S($n) + 2*recurrence_U($n); } sub recurrence_S { my ($n) = @_; if ($n <= 0) { return 0; } if ($n == 1) { return 0; } return 2*recurrence_S($n-1) + recurrence_U($n-1); } sub recurrence_U { my ($n) = @_; if ($n <= 0) { return 0; } if ($n == 1) { return 0; } return recurrence_S($n-1) + 2*recurrence_U($n-1) + 2*5**($n-2); } # A(n)=a(n)*2 # A(n)/2 = 8*A(n-1)/2 - 15*A(n-2)/2 + 2 # A(n) = 8*A(n-1) - 15*A(n-2) + 4 } { # arm xy modulus require Math::PlanePath::R5DragonMidpoint; my $path = Math::PlanePath::R5DragonMidpoint->new (arms => 4); my %dxdy_to_digit; my %seen; for (my $n = 0; $n < 6125; $n++) { my $digit = $n % 5; foreach my $arm (0 .. 3) { my ($x,$y) = $path->n_to_xy(4*$n+$arm); my $nb = int($n/5); my ($xb,$yb) = $path->n_to_xy(4*$nb+$arm); # (x+iy)*(1+2i) = x-2y + 2x+y ($xb,$yb) = ($xb-2*$yb, 2*$xb+$yb); my $dx = $xb - $x; my $dy = $yb - $y; my $dxdy = "$dx,$dy"; my $show = "${dxdy}[$digit]"; $seen{$x}{$y} = $show; if ($dxdy eq '0,0') { } # if (defined $dxdy_to_digit{$dxdy} && $dxdy_to_digit{$dxdy} != $digit) { # die; # } $dxdy_to_digit{$dxdy} = $digit; } } foreach my $y (reverse -45 .. 45) { foreach my $x (-5 .. 5) { printf " %9s", $seen{$x}{$y}//'e' } print "\n"; } ### %dxdy_to_digit exit 0; } { # Midpoint xy to n require Math::PlanePath::DragonMidpoint; require Math::BaseCnv; my @yx_adj_x = ([0,1,1,0], [1,0,0,1], [1,0,0,1], [0,1,1,0]); my @yx_adj_y = ([0,0,1,1], [0,0,1,1], [1,1,0,0], [1,1,0,0]); sub xy_to_n { my ($self, $x,$y) = @_; my $n = ($x * 0 * $y) + 0; # inherit bignum 0 my $npow = $n + 1; # inherit bignum 1 while (($x != 0 && $x != -1) || ($y != 0 && $y != 1)) { # my $ax = ((($x+1) ^ ($y+1)) >> 1) & 1; # my $ay = (($x^$y) >> 1) & 1; # ### assert: $ax == - $yx_adj_x[$y%4]->[$x%4] # ### assert: $ay == - $yx_adj_y[$y%4]->[$x%4] my $y4 = $y % 4; my $x4 = $x % 4; my $ax = $yx_adj_x[$y4]->[$x4]; my $ay = $yx_adj_y[$y4]->[$x4]; ### at: "$x,$y n=$n axy=$ax,$ay bit=".($ax^$ay) if ($ax^$ay) { $n += $npow; } $npow *= 2; $x -= $ax; $y -= $ay; ### assert: ($x+$y)%2 == 0 ($x,$y) = (($x+$y)/2, # rotate -45 and divide sqrt(2) ($y-$x)/2); } ### final: "xy=$x,$y" my $arm; if ($x == 0) { if ($y) { $arm = 1; ### flip ... $n = $npow-1-$n; } else { # $y == 1 $arm = 0; } } else { # $x == -1 if ($y) { $arm = 2; } else { $arm = 3; ### flip ... $n = $npow-1-$n; } } ### $arm my $arms_count = $self->arms_count; if ($arm > $arms_count) { return undef; } return $n * $arms_count + $arm; } foreach my $arms (4,3,1,2) { ### $arms my $path = Math::PlanePath::DragonMidpoint->new (arms => $arms); for (my $n = 0; $n < 50; $n++) { my ($x,$y) = $path->n_to_xy($n) or next; my $rn = xy_to_n($path,$x,$y); my $good = ''; if (defined $rn && $rn == $n) { $good .= "good N"; } my $n2 = Math::BaseCnv::cnv($n,10,2); my $rn2 = Math::BaseCnv::cnv($rn,10,2); printf "n=%d xy=%d,%d got rn=%d %s\n", $n,$x,$y, $rn, $good; } } exit 0; } { # 2i+1 powers my $x = 1; my $y = 0; foreach (1 .. 10) { ($x,$y) = ($x - 2*$y, $y + 2*$x); print "$x $y\n"; } exit 0; } { # turn sequence require Math::NumSeq::PlanePathTurn; my @want = (0); foreach (1 .. 5) { @want = map { $_ ? (0,0,1,1,1) : (0,0,1,1,0) } @want; } my @got; foreach my $i (1 .. @want) { push @got, calc_n_turn($i); } # my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'R5DragonCurve', # turn_type => 'Right'); # while (@got < @want) { # my ($i,$value) = $seq->next; # push @got, $value; # } my $got = join(',',@got); my $want = join(',',@want); print "$got\n"; print "$want\n"; if ($got ne $want) { die; } exit 0; # return 0 for left, 1 for right sub calc_n_turn { my ($n) = @_; $n or die; for (;;) { if (my $digit = $n % 5) { return ($digit >= 3 ? 1 : 0); } $n = int($n/5); } } } Math-PlanePath-129/devel/greek-key.pl0000644000175000017500000000561111774517323015242 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use POSIX 'floor'; use List::Util 'min', 'max'; use Math::PlanePath::GreekKeySpiral; # uncomment this to run the ### lines use Smart::Comments; { { package Math::PlanePath::GreekKeySpiral; sub new { my $self = shift->SUPER::new (@_); my $turns = $self->{'turns'}; if (! defined $turns) { $turns = 2; } elsif ($turns < 0) { } $self->{'turns'} = $turns; $self->{'centre_x'} = int($turns/2); $self->{'centre_y'} = int(($turns+1)/2); $self->{'midpoint'} = ($turns+1)*$turns/2; return $self; } } sub _n_part_to_xy { my ($self, $n) = @_; ### _n_part_to_xy(): $n # if ($rot & 2) { # $y = -$y; # } # if ($d & 1) { # $x = -$x; # } # # my $d = int((sqrt(-8*$n-7) + 1) / 2); # $x = $n; # $y = 0; # } elsif (($n -= 1) < 0) { # ### centre ... # $x = + $n; # $y = $self->{'centre_y'}; # $rot = $self->{'turns'}; # } else { # $rot = $d; # $x = $n; # $y = 0; # } } my $turns = 6; my $self = Math::PlanePath::GreekKeySpiral->new (turns => $turns); ### $self foreach my $n (# 20 .. ($turns+1)**2 0, 6, 11, 15, 18, 20, 21, 21.25, 21.75, 22, 23, 25, 28, 32, 37, 43, 49 ) { my $nn = $n; my $n = $n; my $rot = $self->{'turns'}; my $centre_x = $self->{'centre_x'}; my $centre_y = $self->{'centre_y'}; if (($n -= $self->{'midpoint'}) <= 0) { $n = -$n; $rot += 0; $centre_x += 1; } elsif ($n < 1) { $rot -= 1; $centre_x += 1; } else { $n -= 1; $rot += 2; } my $d = int((sqrt(8*$n + 1) + 1) / 2); $n -= $d*($d-1)/2; my $half = int($d/2); my $x = $half - $n; my $y = $n*0 - $half; if (($d % 4) == 2) { $x -= 1; } if (($d % 4) == 3) { $y -= 1; } $rot -= $d; if ($rot & 2) { $x = -$x; $y = -$y; } if ($rot & 1) { ($x,$y) = (-$y,$x); } $x += $centre_x; $y += $centre_y; $rot &= 3; print "$nn $d,$n,rot=$rot $x,$y\n"; } exit 0; } Math-PlanePath-129/devel/vogel.pl0000644000175000017500000002316112067770710014470 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use POSIX 'fmod'; use List::Util 'min', 'max'; use Math::Libm 'M_PI', 'hypot'; use Math::Trig 'pi'; use POSIX; use Smart::Comments; use constant PHI => (1 + sqrt(5)) / 2; { require Math::PlanePath::VogelFloret; my $width = 79; my $height = 21; my $x_factor = 1.4; my $y_factor = 2; my $n_hi = 99; require Math::NumSeq::OEIS; my $seq = Math::NumSeq::OEIS->new(anum => 'A000201'); print_class('Math::PlanePath::VogelFloret'); require Math::NumSeq::FibonacciWord; $seq = Math::NumSeq::FibonacciWord->new; $y_factor = 1.2; $n_hi = 73; print_class('Math::PlanePath::VogelFloret'); sub print_class { my ($name) = @_; # secret leading "*Foo" means print if available my $if_available = ($name =~ s/^\*//); my $class = $name; unless ($class =~ /::/) { $class = "Math::PlanePath::$class"; } ($class, my @parameters) = split /\s*,\s*/, $class; $class =~ /^[a-z_][:a-z_0-9]*$/i or die "Bad class name: $class"; if (! eval "require $class") { if ($if_available) { next; } else { die $@; } } @parameters = map { /(.*?)=(.*)/ or die "Missing value for parameter \"$_\""; $1,$2 } @parameters; my %rows; my $x_min = 0; my $x_max = 0; my $y_min = 0; my $y_max = 0; my $cellwidth = 1; my $path = $class->new (width => POSIX::ceil($width / 4), height => POSIX::ceil($height / 2), @parameters); my $x_limit_lo; my $x_limit_hi; if ($path->x_negative) { my $w_cells = int ($width / $cellwidth); my $half = int(($w_cells - 1) / 2); $x_limit_lo = -$half; $x_limit_hi = +$half; } else { my $w_cells = int ($width / $cellwidth); $x_limit_lo = 0; $x_limit_hi = $w_cells - 1; } my $y_limit_lo = 0; my $y_limit_hi = $height-1; if ($path->y_negative) { my $half = int(($height-1)/2); $y_limit_lo = -$half; $y_limit_hi = +$half; } my $is_01 = $seq->characteristic('smaller'); ### seq: ref $seq ### $is_01 $rows{0}{0} = '.'; my $n_start = $path->n_start; my $n = $n_start; for (;;) { my ($x, $y) = $path->n_to_xy ($n); # stretch these out for better resolution if ($class =~ /Sacks/) { $x *= 1.5; $y *= 2; } if ($class =~ /Archimedean/) { $x *= 2; $y *= 3; } if ($class =~ /Theodorus|MultipleRings/) { $x *= 2; $y *= 2; } if ($class =~ /Vogel/) { $x *= $x_factor; $y *= $y_factor; } # nearest integers $x = POSIX::floor ($x + 0.5); $y = POSIX::floor ($y + 0.5); my $cell = $rows{$x}{$y}; if (defined $cell) { $cell .= ','; } if ($is_01) { $cell .= $seq->ith($n); } else { $cell .= $n; } my $new_cellwidth = max ($cellwidth, length($cell) + 1); my $new_x_limit_lo; my $new_x_limit_hi; if ($path->x_negative) { my $w_cells = int ($width / $new_cellwidth); my $half = int(($w_cells - 1) / 2); $new_x_limit_lo = -$half; $new_x_limit_hi = +$half; } else { my $w_cells = int ($width / $new_cellwidth); $new_x_limit_lo = 0; $new_x_limit_hi = $w_cells - 1; } my $new_x_min = min($x_min, $x); my $new_x_max = max($x_max, $x); my $new_y_min = min($y_min, $y); my $new_y_max = max($y_max, $y); if ($new_x_min < $new_x_limit_lo || $new_x_max > $new_x_limit_hi || $new_y_min < $y_limit_lo || $new_y_max > $y_limit_hi) { last; } $rows{$x}{$y} = $cell; $cellwidth = $new_cellwidth; $x_limit_lo = $new_x_limit_lo; $x_limit_hi = $new_x_limit_hi; $x_min = $new_x_min; $x_max = $new_x_max; $y_min = $new_y_min; $y_max = $new_y_max; if ($is_01) { $n++; } else { (my $i, $n) = $seq->next; } last if $n > $n_hi; } $n--; # the last N actually plotted print "$name N=$n_start to N=$n\n\n"; foreach my $y (reverse $y_min .. $y_max) { foreach my $x ($x_limit_lo .. $x_limit_hi) { my $cell = $rows{$x}{$y}; if (! defined $cell) { $cell = ''; } printf ('%*s', $cellwidth, $cell); } print "\n"; } } exit 0; } sub cont { my $ret = pop; while (@_) { $ret = (pop @_) + 1/$ret; } return $ret; } ### phi: cont(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1) { # use constant ROTATION => M_PI-3; # use constant ROTATION => PHI; #use constant ROTATION => sqrt(37); use constant ROTATION => cont(1 .. 20); my $margin = 0.999; # use constant K => 6; # use constant ROTATION => (K + sqrt(4+K*K)) / 2; print "ROTATION ",ROTATION,"\n"; my @n; my @r; my @x; my @y; my $prev_d = 5; my $min_d = 5; my $min_n1 = 0; my $min_n2 = 0; my $min_x2 = 0; my $min_y2 = 0; for (my $n = 1; $n < 100_000_000; $n++) { my $r = sqrt($n); my $theta = $n * ROTATION() * 2*pi(); # radians my $x = $r * cos($theta); my $y = $r * sin($theta); foreach my $i (0 .. $#n) { my $d = hypot ($x-$x[$i], $y-$y[$i]); if ($d < $min_d) { $min_d = $d; $min_n1 = $n[$i]; $min_n2 = $n; $min_x2 = $x; $min_y2 = $y; if ($min_d / $prev_d < $margin) { $prev_d = $min_d; print "$min_n1 $min_n2 $min_d ", 1/$min_d, "\n"; print " x=$min_x2 y=$min_y2\n"; } } } push @n, $n; push @r, $r; push @x, $x; push @y, $y; if ((my $r_lo = sqrt($n) - 1.2 * $min_d) > 0) { while (@n > 1) { if ($r[0] >= $r_lo) { last; } shift @r; shift @n; shift @x; shift @y; } } } print "$min_n1 $min_n2 $min_d ", 1/$min_d, "\n"; print " x=$min_x2 y=$min_y2\n"; exit 0; } { my $x = 3; foreach (1 .. 100) { $x = 1 / (1 + $x); } } # { # # 609 631 0.624053229799566 1.60242740883046 # # 2 7 1.47062247517163 0.679984167849259 # # use constant ROTATION => M_PI-3; # my @x; # my @y; # foreach my $n (1 .. 20000) { # my $r = sqrt($n); # # my $theta = 2 * $n; # radians # my $theta = $n * ROTATION() * 2*pi(); # radians # push @x, $r * cos($theta); # push @y, $r * sin($theta); # } # # ### @x # my $min_d = 999; # my $min_i = 0; # my $min_j = 0; # my $min_xi = 0; # my $min_yi = 0; # foreach my $i (0 .. $#x-1) { # my $xi = $x[$i]; # my $yi = $y[$i]; # foreach my $j ($i+1 .. $#x) { # my $d = hypot ($xi-$x[$j], $yi-$y[$j]); # if ($d < $min_d) { # $min_d = $d; # $min_i = $i; # $min_j = $j; # $min_xi = $xi; # $min_yi = $yi; # } # } # } # print "$min_i $min_j $min_d ", 1/$min_d, "\n"; # print " x=$min_xi y=$min_yi\n"; # exit 0; # } # { # require Math::PlanePath::VogelFloret; # use constant FACTOR => do { # my @c = map { # my $n = $_; # my $r = sqrt($n); # my $revs = $n / (PHI * PHI); # my $theta = $revs * 2*M_PI(); # ### $n # ### $r # ### $revs # ### $theta # ($r*cos($theta), $r*sin($theta)) # } 1, 4; # ### @c # ### hypot: hypot ($c[0]-$c[2], $c[1]-$c[3]) # 1 / hypot ($c[0]-$c[2], $c[1]-$c[3]) # }; # ### FACTOR: FACTOR() # # print "FACTOR ", FACTOR(), "\n"; # # print "FACTOR ", Math::PlanePath::VogelFloret::FACTOR(), "\n"; # exit 0; # } { foreach my $i (0 .. 20) { my $f = PHI**$i/sqrt(5); my $rem = fmod($f,PHI); printf "%11.5f %6.5f\n", $f, $rem; } exit 0; } { foreach my $n (18239,19459,25271,28465,31282,35552,43249,74592,88622, 101898,107155,116682) { my $theta = $n / (PHI * PHI); # 1==full circle printf "%6d %.2f\n", $n, $theta; } exit 0; } foreach my $i (2 .. 5000) { my $rem = fmod ($i, PHI*PHI); if ($rem > 0.5) { $rem = $rem - 1; } if (abs($rem) < 0.02) { printf "%4d %6.3f %s\n", $i,$rem,factorize($i); } } sub factorize { my ($n) = @_; my @factors; foreach my $f (2 .. int(sqrt($n)+1)) { if (($n % $f) == 0) { push @factors, $f; $n /= $f; while (($n % $f) == 0) { $n /= $f; } } } return join ('*',@factors); } exit 0; # pi => { rotation_factor => M_PI() - 3, # rfactor => 2, # # ever closer ? # # 298252 298365 0.146295611059244 6.83547505464836 # # x=-142.771526420416 y=527.239311170539 # }, # # BEGIN { # # foreach my $info (rotation_types()) { # # my $rot = $info->{'rotation_factor'}; # # my $n1 = $info->{'closest_Ns'}->[0]; # # my $r1 = sqrt($n1); # # my $t1 = $n1 * $rot * 2*M_PI(); # # my $x1 = cos ($t1); # # my $y1 = sin ($t1); # # # # my $r2 = sqrt($n2); # # my $t2 = $n2 * $rot * 2*M_PI(); # # my $x2 = cos ($t2); # # my $y2 = sin ($t2); # # # # $info->{'rfactor'} = 1 / hypot ($x1-$x2, $y1-$y2); # # } # # } Math-PlanePath-129/devel/beta-omega.pl0000644000175000017500000000402012507664322015346 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2015 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::PlanePath::Base::Digits 'round_down_pow'; # uncomment this to run the ### lines use Smart::Comments; use Math::PlanePath::BetaOmega; use Math::PlanePath::KochCurve; { require Math::BaseCnv; my $path = Math::PlanePath::BetaOmega->new; my @values; foreach my $x (0 .. 64) { my $n = $path->xy_to_n($x,0); my $n2 = Math::BaseCnv::cnv($n,10,4); printf "%8s\n", $n2; push @values, $n; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, verbose=>1); exit 0; } { require Math::BaseCnv; my $path = Math::PlanePath::BetaOmega->new; foreach my $n (0 .. 64) { my $n4 = sprintf '%3s', Math::BaseCnv::cnv($n,10,4); my ($x,$y) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); my $dx = $x2-$x; my $dy = $y2-$y; print "$n4 $dx,$dy\n"; } exit 0; } { require Math::PlanePath::KochCurve; foreach my $y (reverse -16 .. 22) { my $y1 = $y; my $y2 = $y; { if ($y2 > 0) { # eg y=5 gives 3*5 = 15 $y2 *= 3; } else { # eg y=-2 gives 1-3*-2 = 7 $y2 = 1-3*$y1; } my ($ylen, $ylevel) = round_down_pow($y2,2); ($ylen, $ylevel) = Math::PlanePath::BetaOmega::_y_round_down_len_level($y); print "$y $y2 $ylevel $ylen\n"; } } exit 0; } Math-PlanePath-129/devel/peano.l0000644000175000017500000000322113717340646014275 0ustar gggg; Copyright 2019, 2020 Kevin Ryde ; ; This file is part of Math-PlanePath. ; ; Math-PlanePath 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, or (at your option) any later ; version. ; ; Math-PlanePath 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 Math-PlanePath. If not, see . ; from http://mathworld.wolfram.com/HilbertCurve.html PeanoMathworld { Angle 4 ; 90 degrees Axiom X X = XFYFX+F+YFXFY-F-XFYFX Y = YFXFY-F-XFYFX+F+YFXFY } PeanoDiagonal3 { Angle 4 ; 90 degrees Axiom FX X = X-FY+FX++ Y = Y+FX-FY++ } PeanoDiagonal3f { Angle 4 ; 45 degrees Axiom FX X = Y+FX-FY ; to be applied an even number of times Y = X-FY+FX } PeanoDiagonal3fr { Angle 8 ; 45 degrees Axiom FX X = Y+F+FX-F-FY ; to be applied an even number of times Y = X-F-FY+F+FX } ; cf ; /usr/share/xfractint/lsystem/fractint.l ; Peano1 ; Segment replacement in the manner of Mandelbrot, which is ; Peano's unit square shape, but not Peano's form as it doesn't ; transpose alternate segments. ; Peano2 ; Sierpinski curve. ; Peano3 ; Peano S shape midpoints. ; Local variables: ; compile-command: "xfractint type=lsystem lfile=peano.l lname=PeanoDiagonal3 params=4" ; End: Math-PlanePath-129/devel/sierpinski-arrowhead-centres.pl0000644000175000017500000000516113234174611021142 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::PlanePath::SierpinskiArrowheadCentres; # uncomment this to run the ### lines use Smart::Comments; { # turn sequence require Math::NumSeq::PlanePathTurn; require Math::BaseCnv; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SierpinskiArrowheadCentres', turn_type => 'TTurn6n'); for (my $n = 1; $n <= 400; $n += 1) { # for (my $n = 9; $n <= 400; $n += 9) { # for (my $n = 3; $n <= 400; $n += 3) { my $value = $seq->ith($n); my $n3 = Math::BaseCnv::cnv($n,10,3); # my $calc = calc_turnleft($n); my $calc = calc_turn6n($n); my $diff = ($value == $calc ? "" : " ***"); printf "%3d %5s %2d %2d%s\n", $n, $n3, $value, $calc, $diff; } sub calc_turn6n { my ($n) = @_; { my $flip = ($n%2 ? 1 : -1); if (($n%3)==1) { return 2*$flip; } my $ret = 0; if (($n%3)==0) { ($ret,$flip) = ($flip,$ret); $n--; } do { ($ret,$flip) = ($flip,$ret); $n = int($n/3); } while (($n%3)==2); if (($n % 3) == 1) { ($ret,$flip) = ($flip,$ret); } return $ret; } { my $flip = ($n%2 ? 1 : -1); if (($n%3)==1) { return 2*$flip; } my $ret = 0; if (($n%3)==2) { ($ret,$flip) = ($flip,$ret); $n++; } do { ($ret,$flip) = ($flip,$ret); $n = int($n/3); } while ($n && ($n%3)==0); if (($n % 3) == 1) { ($ret,$flip) = ($flip,$ret); } return $ret; } { my $flip = ($n%2 ? 1 : -1); if (($n%3)==1) { return 2*$flip; } my $ret = 0; my $low = $n % 3; # low 0s or 2s do { ($ret,$flip) = ($flip,$ret); $n = int($n/3); } while ($n && ($n%3)==$low); if (($n % 3) == 1) { ($ret,$flip) = ($flip,$ret); } return $ret; } } exit 0; } Math-PlanePath-129/devel/flowsnake.pl0000644000175000017500000005500513544612445015350 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2017, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use FindBin; use Math::Libm 'M_PI', 'hypot'; use Math::BaseCnv 'cnv'; use Math::PlanePath;; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; $|=1; # uncomment this to run the ### lines # use Smart::Comments; { require Math::NumSeq::PlanePathDelta; require Math::PlanePath::FlowsnakeCentres; my $class = 'Math::PlanePath::FlowsnakeCentres'; my $path = $class->new; my $seq = Math::NumSeq::PlanePathDelta->new (planepath_object=>$path, delta_type => 'TDir6'); # Centres N to turn by path my $path_n_to_tturn6 = sub { my ($n) = @_; if ($n < 1) { return undef; } my $turn6 = ($seq->ith($n) - $seq->ith($n-1)) % 6; if ($turn6 > 3) { $turn6 -= 6; } return $turn6; }; # Centres N to Turn by digits my $calc_n_to_tturn6; $calc_n_to_tturn6 = sub { # not working my ($n) = @_; if ($n < 1) { return undef; } my $z = 0; while ($n % 7 == 0) { $n /= 7; $n == int($n) or die; $z++; } my $t = $n % 7; $n = ($n-$t)/7; while ($n % 7 == 3) { $n = ($n-3)/7; $n == int($n) or die; } my $r = $n % 7; if ($r == 1 || $r == 2 || $r == 6) { if ($t == 1) { return 1; } if ($t == 2) { return 1; } if ($t == 3) { return 2; } if ($t == 4) { return -1; } if ($t == 5) { return -2; } if ($t == 6) { return ($r == 1 ? 1 : 0); } } else { if ($t == 1) { return ($z ? 0 : 2); } if ($t == 2) { return ($z ? 0 : 1); } if ($t == 3) { return ($z ? -1 : -2); } if ($t == 4) { return -1; } if ($t == 5) { return -1; } if ($t == 6) { return ($z ? 0 : 1); } } die "oops t=$t"; }; { for (my $n = 1; $n < 7**3; $n+=1) { my $n7 = cnv($n,10,7); my $by_path = $path_n_to_tturn6->($n); my $calc = $calc_n_to_tturn6->($n); my $diff = ($by_path != $calc ? ' ***' : ''); print "$n [$n7] $by_path $calc$diff\n"; } exit 0; } exit 0; } { # islands convex hull require Math::PlanePath::GosperIslands; require Math::Geometry::Planar; my $path = Math::PlanePath::GosperIslands->new; my @values; foreach my $k (0 .. 9) { my $n_lo = 3**($k+1) - 2; my $n_hi = 3**($k+2) - 2 - 1; ### $n_lo ### $n_hi my $points = [ map{[$path->n_to_xy($_)]} $n_lo .. $n_hi ]; my $planar = Math::Geometry::Planar->new; $planar->points($points); if (@$points > 4) { $planar = $planar->convexhull2; $points = $planar->points; } my $area = $planar->area / 6; my $whole_area = 7**$k; my $f = $area / $whole_area; my $num_points = scalar(@$points); print "k=$k hull points $num_points area $area cf $whole_area ratio $f\n"; push @values,$area; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, verbose=>1); exit 0; } { # ascii by path my $k = 3; require Math::PlanePath::Flowsnake; my $path = Math::PlanePath::Flowsnake->new; my ($n_lo, $n_hi) = $path->level_to_n_range($k); foreach my $y (reverse -2 .. 25) { my $x = -20; if ($y % 2) { print " "; $x++; } X: for ( ; $x <= 28; $x+=2) { ### at: "$x, $y" { my $n = $path->xyxy_to_n_either($x,$y, $x+2,$y); if (defined $n && $n < $n_hi) { print "__"; ### horiz ... next X; } } { my $n = $path->xyxy_to_n_either($x,$y, $x+1,$y+1); if (defined $n && $n < $n_hi) { print "/ "; next X; } } { my $n = $path->xyxy_to_n_either($x+2,$y, $x+1,$y+1); if (defined $n && $n < $n_hi) { print " \\"; next X; } } ### none ... print ".."; } print "\n"; } exit 0; } { require Math::BaseCnv; push @INC, "$FindBin::Bin/../../dragon/tools"; require MyFSM; my @digit_to_rot = (0, 0, 1, 0, 0, 1, 2); my @digit_permute = (0, 2, 4, 6, 1, 3, 5); my %table; foreach my $digit (0 .. 6) { foreach my $rot (0 .. 2) { my $p = $digit; foreach (1 .. $rot) { $p = $digit_permute[$p]; } my $new_rot = ($rot + $digit_to_rot[$p]) % 3; $table{$rot}->{$digit} = $new_rot; print "$new_rot, "; } print "\n"; } my $fsm = MyFSM->new(table => \%table, initial => 0, accepting => { 0=>0, 1=>1, 2=>2 }, ); { my $width = 2; foreach my $n (0 .. 7**$width-1) { my $n7 = sprintf '%0*s', $width, Math::BaseCnv::cnv($n,10,7); my @v = split //,$n7; # print $fsm->traverse(\@v); print @v," ",$fsm->traverse(\@v),"\n"; } print "\n"; # exit 0; } my $hf = $fsm; print "traverse ", $fsm->traverse([0,2]), "\n"; $fsm->view; $fsm = $fsm->reverse; $fsm->simplify; $fsm->view; print "reverse\n"; foreach my $digit (0 .. 6) { foreach my $state ($fsm->sorted_states) { my $new_state = $fsm->{'table'}->{$state}->{$digit}; my $ns = $new_state; if ($ns eq 'identity') { $ns = 0; } $ns =~ s/,.*//; print $ns,", "; } print "\n"; } print "traverse ", $fsm->traverse([2,0]), "\n"; my $width = 2; foreach my $n (0 .. 7**$width-1) { my $n7 = sprintf '%0*s', $width, Math::BaseCnv::cnv($n,10,7); my @v = split //,$n7; my $h = $hf->traverse(\@v); my $l = $fsm->traverse([ reverse @v ]); if ($l eq 'identity') { $l = 0; } $l =~ s/,.*//; if ($h ne $l) { print join(', ',@v)," h=$h l=$l\n"; } } exit 0; } { # rect_to_n_range require Math::PlanePath::Flowsnake; my $path = Math::PlanePath::Flowsnake->new; my ($n_lo,$n_hi) = $path->rect_to_n_range(0,0, 31.5,31.5); ### $n_lo ### $n_hi foreach my $n (973 .. 1000000) { my ($x,$y) = $path->n_to_xy($n); if ($x >= 0 && $x <= 31.5 && $y >= 0 && $y <= 31.5) { print "$n $x,$y\n"; } } exit 0; } { require Math::NumSeq::PlanePathDelta; require Math::PlanePath::Flowsnake; my $class = 'Math::PlanePath::Flowsnake'; my $path = $class->new; my $seq = Math::NumSeq::PlanePathDelta->new (planepath_object=>$path, delta_type => 'TDir6'); my $path_n_to_tturn6 = sub { my ($n) = @_; if ($n < 1) { return undef; } my $turn6 = $seq->ith($n) - $seq->ith($n-1); if ($turn6 > 3) { $turn6 -= 6; } return $turn6; }; # N to Turn by recurrence my $calc_n_to_tturn6 = sub { # not working my ($n) = @_; if ($n < 1) { return undef; } if ($n % 49 == 0) { return calc_n_to_tturn6($n/7); } if (int($n/7) % 7 == 3) { # "_3_" return calc_n_to_tturn6(($n%7) + int($n/49)); } return path_n_to_tturn6($n); if ($n == 1) { return 1; } if ($n == 2) { return 2; } if ($n == 3) { return -1; } if ($n == 4) { return -2; } if ($n == 5) { return 0; } if ($n == 6) { return -1; } my @digits = digit_split_lowtohigh($n,7); my $high = pop @digits; if ($digits[-1]) { } $n = digit_join_lowtohigh(\@digits,7,0); if ($n == 0) { return 0; } return calc_n_to_tturn6($n); }; { for (my $n = 1; $n < 7**3; $n+=1) { my $value = $path_n_to_tturn6->($n); my $calc = $calc_n_to_tturn6->($n); my $diff = ($value != $calc ? ' ***' : ''); print "$n $value $calc$diff\n"; } exit 0; } exit 0; } { # N to Dir6 -- working for integers require Math::PlanePath::Flowsnake; require Math::NumSeq::PlanePathDelta; my @next_state = (0,7,7,0,0,0,7, 0,7,7,7,0,0,7); my @tdir6 = (0,1,3,2,0,0,-1, -1,0,0,2,3,1,0); sub n_to_totalturn6 { my ($self, $n) = @_; unless ($n >= 0) { return undef; } my $state = 0; my $tdir6 = 0; foreach my $digit (reverse digit_split_lowtohigh($n,7)) { $state += $digit; $tdir6 += $tdir6[$state]; $state = $next_state[$state]; } return $tdir6 % 6; } { my $class = 'Math::PlanePath::Flowsnake'; my $path = $class->new; my $seq = Math::NumSeq::PlanePathDelta->new (planepath=>'Flowsnake', delta_type => 'TDir6'); for (my $n = 0; $n < 7**3; $n+=1) { my $value = $seq->ith($n); my $tdir6 = n_to_totalturn6($path,$n) % 6; my $diff = ($value != $tdir6 ? ' ***' : ''); print "$n $value $tdir6$diff\n"; } exit 0; } # sub _digit_lowest { # my ($n, $radix) = @_; # my $digit; # for (;;) { # last if ($digit = ($n % 7)); # $n /= 7; # last unless $n; # } # # if ($digit < 1_000_000) { # # $digit = "$digit"; # # } # return $digit; # } } { # N to Turn6 -- working for integers require Math::PlanePath::Flowsnake; require Math::NumSeq::PlanePathDelta; my $class = 'Math::PlanePath::Flowsnake'; my $path = $class->new; my $seq = Math::NumSeq::PlanePathDelta->new (planepath=>'Flowsnake', delta_type => 'TDir6'); for (my $n = 1; $n < 7**4; $n+=1) { my $value = ($seq->ith($n) - $seq->ith($n-1)) % 6; $value += 2; # range -2 to +2 $value %= 6; $value -= 2; my $turn = $path->_WORKING_BUT_SECRET__n_to_turn6($n); my $diff = ($value != $turn ? ' ***' : ''); print "$n $value $turn$diff\n"; die if $value != $turn; } exit 0; } { require Math::PlanePath::Flowsnake; require Math::PlanePath::FlowsnakeCentres; my $f = Math::PlanePath::Flowsnake->new (arms => 2); my $c = Math::PlanePath::FlowsnakeCentres->new (arms => 2); my $width = 5; my %saw; foreach my $n (0 .. 7**($width-1)) { my ($x,$y) = $f->n_to_xy($n); my $cn = $c->xy_to_n($x,$y) // -1; my $cr = $c->xy_to_n($x+2, $y) // -1; my $ch = $c->xy_to_n($x+1,$y+1) // -1; my $cw = $c->xy_to_n($x-1,$y+1) // -1; my $cl = $c->xy_to_n($x-2,$y) // -1; # <------ my $cu = $c->xy_to_n($x-1,$y-1) // -1; # <------3 my $cz = $c->xy_to_n($x+1,$y-1) // -1; if ($n == $cn) { $saw{'n'} = 0; } if ($n == $cr) { $saw{'r'} = 1; } if ($n == $ch) { $saw{'h'} = 2; } if ($n == $cw) { $saw{'w'} = 3; } if ($n == $cl) { $saw{'l'} = 4; } if ($n == $cu) { $saw{'u'} = 5; } if ($n == $cz) { $saw{'z'} = 6; } unless (($n == $cn) || ($n == $cr) || ($n == $ch) || ($n == $cw) || ($n == $cl) || ($n == $cu) || ($n == $cz)) { die "no match $n: $cn,$cr,$ch,$cw,$cl,$cu,$cz"; } } my $saw = join(',', sort {$saw{$a}<=>$saw{$b}} keys %saw); print "$saw\n"; exit 0; } { require Math::PlanePath::Flowsnake; require Math::PlanePath::FlowsnakeCentres; say Math::PlanePath::Flowsnake->isa('Math::PlanePath::FlowsnakeCentres'); say Math::PlanePath::FlowsnakeCentres->isa('Math::PlanePath::Flowsnake'); say Math::PlanePath::Flowsnake->can('xy_to_n'); say Math::PlanePath::FlowsnakeCentres->can('xy_to_n'); exit 0; } { require Math::BaseCnv; require Math::PlanePath::Flowsnake; require Math::PlanePath::FlowsnakeCentres; my $c = Math::PlanePath::Flowsnake->new; my $f = Math::PlanePath::FlowsnakeCentres->new; my $width = 5; my %saw; foreach my $n (0 .. 7**($width-1)) { my $n7 = sprintf '%*s', $width, Math::BaseCnv::cnv($n,10,7); my ($x,$y) = $f->n_to_xy($n); my $cn = $c->xy_to_n($x,$y) || -1; my $cn7 = sprintf '%*s', $width, Math::BaseCnv::cnv($cn,10,7); my $rx = $x + 1; my $ry = $y + 1; my $cr = $c->xy_to_n($rx,$ry) || -1; my $cr7 = sprintf '%*s', $width, Math::BaseCnv::cnv($cr,10,7); my $hx = $x + 1; my $hy = $y + 1; my $ch = $c->xy_to_n($hx,$hy) || -1; my $ch7 = sprintf '%*s', $width, Math::BaseCnv::cnv($ch,10,7); my $wx = $x - 1; my $wy = $y + 1; my $cw = $c->xy_to_n($wx,$wy) || -1; my $cw7 = sprintf '%*s', $width, Math::BaseCnv::cnv($cw,10,7); my $lx = $x - 2; my $ly = $y; my $cl = $c->xy_to_n($lx,$ly) || -1; my $cl7 = sprintf '%*s', $width, Math::BaseCnv::cnv($cl,10,7); my $ux = $x - 1; my $uy = $y - 1; my $cu = $c->xy_to_n($ux,$uy) || -1; my $cu7 = sprintf '%*s', $width, Math::BaseCnv::cnv($cu,10,7); my $zx = $x + 1; my $zy = $y - 1; my $cz = $c->xy_to_n($zx,$zy) || -1; my $cz7 = sprintf '%*s', $width, Math::BaseCnv::cnv($cz,10,7); if ($n == $cn) { $saw{'n'} = 0; } if ($n == $cr) { $saw{'r'} = 1; } if ($n == $ch) { $saw{'h'} = 2; } if ($n == $cw) { $saw{'w'} = 3; } if ($n == $cl) { $saw{'l'} = 4; } if ($n == $cu) { $saw{'u'} = 5; } if ($n == $cz) { $saw{'z'} = 6; } my $bad = ($n == $cn || $n == $cr || $n == $ch || $n == $cw || $n == $cl || $n == $cu || $n == $cz ? '' : ' ******'); # print "$n7 $cn7 $ch7 $cw7 $cu7 $bad\n"; } my $saw = join(',', sort {$saw{$a}<=>$saw{$b}} keys %saw); print "$saw\n"; exit 0; } { require Math::BaseCnv; require Math::PlanePath::Flowsnake; my $path = Math::PlanePath::Flowsnake->new; foreach my $y (reverse -5 .. 40) { printf "%3d ", $y; foreach my $x (-20 .. 15) { my $n = $path->xy_to_n($x,$y); if (! defined $n) { print " "; next; } my $nh = $n - ($n%7); my ($hx,$hy) = $path->n_to_xy($nh); my $pos = '?'; if ($hy > $y) { $pos = 'T'; } elsif ($hx > $x) { $pos = '.'; } else { $pos = '*'; $pos = $n%7; } print "$pos "; } print "\n"; } exit 0; } { require Math::BaseCnv; require Math::PlanePath::Flowsnake; require Math::PlanePath::FlowsnakeCentres; my $f = Math::PlanePath::Flowsnake->new; my $c = Math::PlanePath::FlowsnakeCentres->new; my $width = 5; foreach my $n (0 .. 7**($width-1)) { my $n7 = sprintf '%*s', $width, Math::BaseCnv::cnv($n,10,7); my ($x,$y) = $f->n_to_xy($n); my $cn = $c->xy_to_n($x,$y) || 0; my $cn7 = sprintf '%*s', $width, Math::BaseCnv::cnv($cn,10,7); my $m = ($x + 2*$y) % 7; if ($m == 2) { # 2,0 = 2 $x -= 2; } elsif ($m == 5) { # 3,1 = 3+2*1 = 5 $x -= 3; $y -= 1; } elsif ($m == 3) { # 1,1 = 1+2 = 3 $x -= 1; $y -= 1; } elsif ($m == 4) { # 0,2 = 0+2*2 = 4 $y -= 2; } elsif ($m == 6) { # 2,2 = 2+2*2 = 6 $x -= 2; $y -= 2; } elsif ($m == 1) { # 4,2 = 4+2*2 = 8 = 1 $x -= 4; $y -= 2; } my $mn = $c->xy_to_n($x,$y) || 0; my $mn7 = sprintf '%*s', $width, Math::BaseCnv::cnv($mn,10,7); my $nh = $n - ($n%7); my $mh = $mn - ($mn%7); my $diff = ($nh == $mh ? "" : " **"); print "$n7 $mn7 $cn7$diff\n"; } exit 0; } { # xy_to_n require Math::PlanePath::Flowsnake; require Math::PlanePath::FlowsnakeCentres; my $path = Math::PlanePath::FlowsnakeCentres->new; my $k = 4000; my ($n_lo,$n_hi) = $path->rect_to_n_range(-$k,-$k, $k,$k); print "$n_lo, $n_hi\n"; exit 0; } { # xy_to_n require Math::PlanePath::Flowsnake; require Math::PlanePath::FlowsnakeCentres; my $path = Math::PlanePath::FlowsnakeCentres->new; my $y = 0; for (my $x = 6; $x >= -5; $x-=2) { $x -= ($x^$y)&1; my $n = $path->xy_to_n($x,$y); print "$x,$y ",($n//'undef'),"\n"; } exit 0; } { # modulo require Math::PlanePath::Flowsnake; my $path = Math::PlanePath::Flowsnake->new; for (my $n = 0; $n <= 49; $n++) { if (($n % 7) == 0) { print "\n"; } my ($x,$y) = $path->n_to_xy($n); my $c = $x + 2*$y; my $m = $c % 7; print "$n $x,$y $c $m\n"; } exit 0; } { require Math::PlanePath::Flowsnake; my $path = Math::PlanePath::Flowsnake->new; for (my $n = 0; $n <= 49; $n+=7) { my ($x,$y) = $path->n_to_xy($n); my ($rx,$ry) = ((3*$y + 5*$x) / 14, (5*$y - $x) / 14); print "$n $x,$y $rx,$ry\n"; } exit 0; } { # radius require Math::PlanePath::Flowsnake; my $path = Math::PlanePath::Flowsnake->new; my $prev_max = 1; for (my $level = 1; $level < 10; $level++) { print "level $level\n"; my ($x2,$y2) = $path->n_to_xy(2 * 7**($level-1)); my ($x3,$y3) = $path->n_to_xy(3 * 7**($level-1)); my $cx = ($x2+$x3)/2; my $cy = ($y2+$y3)/2; my $max_hypot = 0; my $max_pos = ''; foreach my $n (0 .. 7**$level - 1) { my ($x,$y) = $path->n_to_xy($n); my $h = ($x-$cx)**2 + 3*($y-$cy); if ($h > $max_hypot) { $max_hypot = $h; $max_pos = "$x,$y"; } } my $factor = $max_hypot / $prev_max; $prev_max = $max_hypot; print " cx=$cx,cy=$cy max $max_hypot at $max_pos factor $factor\n"; } exit 0; } { require Math::PlanePath::Flowsnake; my $path = Math::PlanePath::Flowsnake->new; my $prev_max = 1; for (my $level = 1; $level < 10; $level++) { my $n_start = 0; my $n_end = 7**$level - 1; my $min_hypot = $n_end; my $min_x = 0; my $min_y = 0; my $max_hypot = 0; my $max_pos = ''; print "level $level\n"; my ($xend,$yend) = $path->n_to_xy(7**($level-1)); print " end $xend,$yend\n"; $yend *= sqrt(3); my $cx = -$yend; # rotate +90 my $cy = $xend; print " rot90 $cx, $cy\n"; # $cx *= sqrt(3/4) * .5; # $cy *= sqrt(3/4) * .5; $cx *= 1.5; $cy *= 1.5; print " scale $cx, $cy\n"; $cx += $xend; $cy += $yend; print " offset to $cx, $cy\n"; $cy /= sqrt(3); printf " centre %.1f, %.1f\n", $cx,$cy; foreach my $n ($n_start .. $n_end) { my ($x,$y) = $path->n_to_xy($n); my $h = ($cx-$x)**2 + 3*($cy-$y)**2; if ($h > $max_hypot) { $max_hypot = $h; $max_pos = "$x,$y"; } # if ($h < $min_hypot) { # $min_hypot = $h; # $min_x = $x; # $min_y = $y; # } } # print " min $min_hypot at $min_x,$min_y\n"; my $factor = $max_hypot / $prev_max; print " max $max_hypot at $max_pos factor $factor\n"; $prev_max = $max_hypot; } exit 0; } { # diameter require Math::PlanePath::Flowsnake; my $path = Math::PlanePath::Flowsnake->new; my $prev_max = 1; for (my $level = 1; $level < 10; $level++) { print "level $level\n"; my $n_start = 0; my $n_end = 7**$level - 1; my ($xend,$yend) = $path->n_to_xy($n_end); print " end $xend,$yend\n"; my @x; my @y; foreach my $n ($n_start .. $n_end) { my ($x,$y) = $path->n_to_xy($n); push @x, $x; push @y, $y; } my $max_hypot = 0; my $max_pos = ''; my ($cx,$cy); foreach my $i (0 .. $#x-1) { foreach my $j (1 .. $#x) { my $h = ($x[$i]-$x[$j])**2 + 3*($y[$i]-$y[$j]); if ($h > $max_hypot) { $max_hypot = $h; $max_pos = "$x[$i],$y[$i], $x[$j],$y[$j]"; $cx = ($x[$i] + $x[$j]) / 2; $cy = ($y[$i] + $y[$j]) / 2; } } } my $factor = $max_hypot / $prev_max; print " max $max_hypot at $max_pos factor $factor\n"; $prev_max = $max_hypot; } exit 0; } { require Math::PlanePath::GosperIslands; my $path = Math::PlanePath::GosperIslands->new; foreach my $level (0 .. 20) { my $n_start = 3**($level+1) - 2; my $n_end = 3**($level+2) - 2 - 1; my ($prev_x) = $path->n_to_xy($n_start); foreach my $n ($n_start .. $n_end) { my ($x,$y) = $path->n_to_xy($n); # if ($y == 0 && $x > 0) { # print "level $level x=$x y=$y n=$n\n"; # } if (($prev_x>0) != ($x>0) && $y > 0) { print "level $level x=$x y=$y n=$n\n"; } $prev_x = $x; } print "\n"; } exit 0; } sub hij_to_xy { my ($h, $i, $j) = @_; return ($h*2 + $i - $j, $i+$j); } { # y<0 at n=8598 x=-79,y=-1 require Math::PlanePath::Flowsnake; my $path = Math::PlanePath::Flowsnake->new; for (my $n = 3; ; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($y == 0) { print "zero n=$n $x,$y\n"; } if ($y < 0) { print "yneg n=$n $x,$y\n"; exit 0; } # if ($y < 0 && $x >= 0) { # print "yneg n=$n $x,$y\n"; # exit 0; # } } exit 0; } { { my $sh = 1; my $si = 0; my $sj = 0; my $n = 1; foreach my $level (1 .. 20) { $n *= 7; ($sh, $si, $sj) = (2*$sh - $sj, 2*$si + $sh, 2*$sj + $si); my ($x, $y) = hij_to_xy($sh,$si,$sj); $n = sprintf ("%f",$n); print "$level $n $sh,$si,$sj $x,$y\n"; } } exit 0; } our $level; my $n = 0; my $x = 0; my $y = 0; my %seen; my @row; my $x_offset = 8; my $dir = 0; sub step { $dir %= 6; print "$n $x, $y dir=$dir\n"; my $key = "$x,$y"; if (defined $seen{$key}) { print "repeat $x, $y from $seen{$key}\n"; } $seen{"$x,$y"} = $n; if ($y >= 0) { $row[$y]->[$x+$x_offset] = $n; } if ($dir == 0) { $x += 2; } elsif ($dir == 1) { $x++, $y++; } elsif ($dir == 2) { $x--, $y++; } elsif ($dir == 3) { $x -= 2; } elsif ($dir == 4) { $x--, $y--; } elsif ($dir == 5) { $x++, $y--; } else { die; } $n++; } sub forward { if ($level == 1) { step (); return; } local $level = $level-1; forward(); $dir++; # 0 backward(); $dir += 2; # 1 backward(); $dir--; # 2 forward(); $dir -= 2; # 3 forward(); # 4 forward(); $dir--; # 5 backward(); $dir++; # 6 } sub backward { my ($dir) = @_; if ($level == 1) { step (); return; } print "backward\n"; local $level = $level-1; $dir += 2; forward(); forward(); $dir--; # 5 forward(); $dir--; # 5 forward(); $dir--; # 5 backward(); $dir--; # 5 backward(); $dir--; # 5 forward(); $dir--; # 5 } $level = 3; forward (2); foreach my $y (reverse 0 .. $#row) { my $aref = $row[$y]; foreach my $x (0 .. $#$aref) { printf ('%*s', 3, (defined $aref->[$x] ? $aref->[$x] : '')); } print "\n"; } Math-PlanePath-129/devel/diagonals.pl0000644000175000017500000000643712157255652015327 0ustar gggg#!/usr/bin/perl -w # Copyright 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min', 'max'; # uncomment this to run the ### lines use Smart::Comments; use Math::PlanePath::Diagonals; use Math::NumSeq::PlanePathDelta; { my $dir = 'up'; foreach my $y_start (reverse -7 .. 7) { printf "Ystart=%2d", $y_start; foreach my $x_start (-7 .. 7) { my $seq = Math::NumSeq::PlanePathDelta->new (planepath => "Diagonals,x_start=$x_start,y_start=$y_start,direction=$dir", delta_type => 'dSumAbs'); printf " %3d", $seq->values_max; } print "\n"; } print "\n"; foreach my $y_start (reverse -7 .. 7) { printf "Ystart=%2d", $y_start; foreach my $x_start (-7 .. 7) { my $max = dsumabs_max($x_start,$y_start); my $seq = Math::NumSeq::PlanePathDelta->new (planepath => "Diagonals,x_start=$x_start,y_start=$y_start,direction=$dir", delta_type => 'dSumAbs'); my $diff = ($seq->values_max == $max ? ' ' : '*'); printf "%3d%s", $max, $diff; } print "\n"; } print "\n"; foreach my $y_start (reverse -7 .. 7) { printf "Ystart=%2d", $y_start; foreach my $x_start (-7 .. 7) { my $seq = Math::NumSeq::PlanePathDelta->new (planepath => "Diagonals,x_start=$x_start,y_start=$y_start,direction=$dir", delta_type => 'dSumAbs'); printf " %3d", $seq->values_min; } print "\n"; } print "\n"; foreach my $y_start (reverse -7 .. 7) { printf "Ystart=%2d ", $y_start; foreach my $x_start (-7 .. 7) { my $min = dsumabs_min($x_start,$y_start); my $seq = Math::NumSeq::PlanePathDelta->new (planepath => "Diagonals,x_start=$x_start,y_start=$y_start,direction=$dir", delta_type => 'dSumAbs'); my $diff = ($seq->values_min == $min ? ' ' : '*'); printf "%3d%s", $min, $diff; } print "\n"; } print "\n"; exit 0; sub dsumabs_min { my ($x_start, $y_start) = @_; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => "Diagonals,x_start=$x_start,y_start=$y_start,direction=$dir", delta_type => 'dSumAbs'); my $i_start = $seq->i_start; my $min = $seq->ith($i_start); foreach my $i ($i_start .. 500) { $min = min($min, $seq->ith($i)); } return $min; } sub dsumabs_max { my ($x_start, $y_start) = @_; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => "Diagonals,x_start=$x_start,y_start=$y_start,direction=$dir", delta_type => 'dSumAbs'); my $i_start = $seq->i_start; my $max = $seq->ith($i_start); foreach my $i ($i_start .. 500) { $max = max($max, $seq->ith($i)); } return $max; } } Math-PlanePath-129/devel/ulam-warburton.pl0000644000175000017500000001324112400225034016312 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use warnings; # uncomment this to run the ### lines #use Smart::Comments; { # depth_to_n() require Math::PlanePath::UlamWarburton; my $path = Math::PlanePath::UlamWarburton->new(parts=>'octant'); for (my $depth = 0; $depth < 35; $depth++) { my $n = $path->tree_depth_to_n($depth); my ($x,$y) = $path->n_to_xy($n); my $rn = $path->xy_to_n($x,$y); my $diff = $rn - $n; print "$depth $n $x,$y $diff\n"; } exit 0; } { # n_to_depth() 1 6 16 # 2 7,8 17,18 # 14 3 9,10 19,20 # 15 10 13 20 4,5 11,12,13,14,15 # 5 8 12 18 # 1 2 3 4 6 7 9 11 16 17 19 # -------------------------- # 0 1 2 3 4 5 6 7 8 require Math::PlanePath::UlamWarburton; my $path = Math::PlanePath::UlamWarburton->new(parts=>'octant'); for (my $n = 1; $n <= 35; $n++) { my $depth = $path->tree_n_to_depth($n); print "$n $depth\n"; } exit 0; } { # height # my $class = 'Math::PlanePath::UlamWarburton'; # my $class = 'Math::PlanePath::UlamWarburtonQuarter'; # my $class = 'Math::PlanePath::ToothpickUpist'; my $class = 'Math::PlanePath::LCornerTree'; eval "require $class"; require Math::BaseCnv; my $path = $class->new (parts => 1); my $prev_depth = 0; for (my $n = $path->n_start;; $n++) { my $depth = $path->tree_n_to_depth($n); my $n_depth = $path->tree_depth_to_n($depth); if ($depth != $prev_depth) { print "\n"; last if $depth > 65; $prev_depth = $depth; } my $calc_height = $path->tree_n_to_subheight($n); my $search_height = path_tree_n_to_subheight_by_search($path,$n); my $n3 = Math::BaseCnv::cnv($n - $n_depth, 10,3); $search_height //= 'undef'; $calc_height //= 'undef'; my $diff = ($search_height eq $calc_height ? '' : ' ***'); printf "%2d %2d %3s %5s %5s%s\n", $depth, $n, $n3, $search_height, $calc_height, $diff; } exit 0; sub path_tree_n_to_subheight_by_search { my ($self, $n) = @_; my @n = ($n); my $height = 0; for (;;) { @n = map {$self->tree_n_children($_)} @n or return $height; $height++; if (@n > 400 || $height > 70) { return undef; # presumed infinite } } } } { # number of children require Math::PlanePath::UlamWarburton; require Math::PlanePath::UlamWarburtonQuarter; # my $path = Math::PlanePath::UlamWarburton->new; my $path = Math::PlanePath::UlamWarburtonQuarter->new; my $prev_depth = 0; for (my $n = $path->n_start; ; $n++) { my $depth = $path->tree_n_to_depth($n); if ($depth != $prev_depth) { $prev_depth = $depth; print "\n"; last if $depth > 40; } my $num_children = $path->tree_n_num_children($n); print "$num_children,"; } print "\n"; exit 0; } # turn on u(0) = 1 # u(1) = 1 # u(n) = 4 * 3^ones(n-1) - 1 # where ones(x) = number of 1 bits A000120 # { my @yx; sub count_around { my ($x,$y) = @_; return ((!! $yx[$y+1][$x]) + (!! $yx[$y][$x+1]) + ($x > 0 && (!! $yx[$y][$x-1])) + ($y > 0 && (!! $yx[$y-1][$x]))); } my (@turn_x,@turn_y); sub turn_on { my ($x,$y) = @_; ### turn_on(): "$x,$y" if (! $yx[$y][$x] && count_around($x,$y) == 1) { push @turn_x, $x; push @turn_y, $y; } } my $print_grid = 1; my $cumulative = 1; my @lchar = ('a' .. 'z'); $yx[0][0] = $lchar[0]; for my $level (1 .. 20) { print "\n"; printf "level %d %b\n", $level, $level; if ($print_grid) { foreach my $row (reverse @yx) { foreach my $cell (@$row) { print ' ', (defined $cell #&& ($cell eq 'p' || $cell eq 'o') ? $cell : ' '); } print "\n"; } print "\n"; } { my $count = 0; foreach my $row (reverse @yx) { foreach my $cell (@$row) { $count += defined $cell; } } print "total $count\n"; } foreach my $y (0 .. $#yx) { my $row = $yx[$y]; foreach my $x (0 .. $#$row) { $yx[$y][$x] or next; ### cell: $yx[$y][$x] turn_on ($x, $y+1); turn_on ($x+1, $y); if ($x > 0) { turn_on ($x-1, $y); } if ($y > 0) { turn_on ($x, $y-1); } } } print "extra ",scalar(@turn_x),"\n"; my %seen_turn; for (my $i = 0; $i < @turn_x; ) { my $key = "$turn_x[$i],$turn_y[$i]"; if ($seen_turn{$key}) { splice @turn_x,$i,1; splice @turn_y,$i,1; } else { $seen_turn{$key} = 1; $i++; } } my $e = 4*(scalar(@turn_x)-2)+4; $cumulative += $e; print "extra $e cumulative $cumulative\n"; ### @turn_x ### @turn_y while (@turn_x) { $yx[pop @turn_y][pop @turn_x] = ($lchar[$level]||'z'); } ### @yx } exit 0; } Math-PlanePath-129/devel/corner-replicate.pl0000644000175000017500000000226112157300664016605 0ustar gggg#!/usr/bin/perl -w # Copyright 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min', 'max'; # uncomment this to run the ### lines use Smart::Comments; use Math::PlanePath::CornerReplicate; { my $path = Math::PlanePath::CornerReplicate->new; foreach my $n (0x0FFF, 0x1FFF, 0x2FFF, 0x3FFF) { my ($x,$y) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); my $dsum = ($x2+$y2) - ($x+$y); printf "%4X to %4X %2X,%2X to %2X,%2X dSum=%d\n", $n,$n+1, $x,$y, $x2,$y2, $dsum; } exit 0; } Math-PlanePath-129/devel/terdragon.pl0000644000175000017500000012242113234165452015336 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min', 'max'; use Math::PlanePath::TerdragonCurve; use Math::PlanePath; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use List::Pairwise; use Math::BaseCnv; use lib 'xt'; use MyOEIS; # uncomment this to run the ### lines use Smart::Comments; # # skip low zeros # # 1 left # # 2 right # ones(n) - ones(n+1) # 1*3^k left # 2*3^k right { # arms=6 sample points for the POD require Math::PlanePath::TerdragonCurve; my $path = Math::PlanePath::TerdragonCurve->new (arms => 6); my $show = sub { my ($x,$y) = @_; my @n_list = $path->xy_to_n_list($x,$y); [join(',',@n_list)]; }; print " \\ / \\ / \\ / \\ / --- @{$show->(-1,1)} ---------------- @{$show->(1,1)} --- / \\ / \\ \\ / \\ / \\ / \\ / \\ / \\ / --- @{$show->(-2,0)} ------------- @{$show->(0,0)} -------------- @{$show->(2,0)} --- / \\ / \\ / \\ / \\ / \\ / \\ \\ / \\ / --- @{$show->(-1,-1)} ---------------- @{$show->(1,-1)} --- / \\ / \\ / \\ / \\ "; exit 0; } { require Math::PlanePath::TerdragonMidpoint; # my $path = Math::PlanePath::TerdragonMidpoint->new; my $path = Math::PlanePath::TerdragonCurve->new; require POSIX; my @n_list = $path->xy_to_n_list(POSIX::DBL_MAX(),POSIX::DBL_MAX()); ### @n_list exit 0; } { # A062756 == 1-abs(A229215) mod 3 # A062756(n) = vecsum(apply(d->d==1,digits(n,3))); # A229215(n) = [1,-3,-2,-1,3,2][(A062756(n-1) % 6)+1]; # A229215(n) = [1,2,3,-1,-2,-3][(-A062756(n-1) % 6)+1]; # vector(20,n,n--; A062756(n)) # vector(20,n, A229215(n)) # A229215(n) = (digits(n,3)) # A229215 # 1, -3, 1, -3, -2, -3, 1, -3, 1, -3, -2, -3, -2, -1, -2, -3, -2, -3, 1, require Math::NumSeq::OEIS; my $A062756 = Math::NumSeq::OEIS->new(anum=>'A062756'); my $A229215 = Math::NumSeq::OEIS->new(anum=>'A229215'); my @map = (1,2,3,-1,-2,-3); for (;;) { my ($i1,$value1) = $A062756->next or last; my ($i2,$value2) = $A229215->next or last; # $value1 %= 3; # $value2 = (1 - abs($value2)) % 3; $value1 = $map[-$value1 % 6]; print "i=$i1 $value1 $value2\n"; $value1 == $value2 or die; } exit 0; } { # some variations # cf A106154 terdragon 6 something # A105499 terdragon permute something # 1->{2,1,2}, 2->{1,3,1}, 3->{3,2,3}. # 212323212131212131212323212323131323212323212323 # * * 3 2 # \ / \ \ / # *---* -1 ---*--- 1 # \ / \ # *---* -2 -3 # # A062756 # 0, 1, 0, 1, 2, 1, 0, 1, 0, 1, 2, 1, 2, 3, 2, 1, 2, 1, 0, 1, 0, 1, 2, 1, # 1,2,3 = 0,1,2 # -1,-2,-3 = 3,4,5 my @map123 = (undef, 0,1,2, 5,4,3); require Math::NumSeq::OEIS; my $seq; $seq = Math::NumSeq::OEIS->new(anum=>'A105969'); $seq = Math::NumSeq::OEIS->new(anum=>'A106154'); $seq = Math::NumSeq::OEIS->new(anum=>'A229215'); require Language::Logo; my $lo = Logo->new(update => 2, port => 8200 + (time % 100)); my $draw; # $lo->command("seth 135; backward 200; seth 90"); $lo->command("pendown; hideturtle"); my $angle = 0; while (my ($i,$value) = $seq->next) { last if $i > 3**3; $value = $map123[$value]; $angle = $value*120; # $angle = 90-$angle; $angle += 90; $lo->command("seth $angle; forward 13"); } $lo->disconnect("Finished..."); exit 0; } { # powers (1+w)^k # w^2 = -1+w # (a+bw)*(1+w) = a+bw + aw+bw^2 # = a + bw + aw - b + bw # = (a-b) + (a+2b)w # a+bw = (a+b) + bw^2 my $a = 1; my $b = 0; my @values; for (1 .. 30) { push @values, -($a+$b); ($a,$b) = ($a-$b, $a+2*$b); } for (1 .. 20) { print "$_\n"; Math::OEIS::Grep->search(array=>\@values); } exit 0; } { # mixed ternary grep my @values; foreach my $n (1 .. 3*2**3) { my @digits = Math::PlanePath::TerdragonCurve::_digit_split_mix23_lowtohigh($n); push @values, digit_join_lowtohigh(\@digits,3); } print join(',',@values),"\n"; Math::OEIS::Grep->search(array => \@values); exit 0; } =head2 Left Boundary Turn Sequence The left boundary turn sequence is Lt(i) = / if i == 1 mod 3 then turn -120 (right) | otherwise | let b = bit above lowest 1-bit of i-floor((i+1)/3) | if b = 0 then turn 0 (straight ahead) \ if b = 1 then turn +120 (left) = 1, 0, 0, 1, -1, 0, 1, 0, -1, 1, -1, 0, 1, 0, 0, 1, -1, -1, ... starting i=1, multiple of 120 degrees The sequence can be calculated in a similar way to the right boundary, but from an initial V part since the "0" and "2" points are on the left boundary (and "1" is not). 2 Vrev \ \ 0-----1 This expands as 2 * initial \ / \ Vtrev[0] = 1 \ / \ Rtrev[0] = empty a-----1 \ Vtrev[1] = Vtrev[0], 0, Rtrev[0] \ = 1, 0 (at "*" and "a") 0-----* Vtrev[k+1] = Vtrev[k], 0, Rtrev[k] Rtrev[k+1] = Vtrev[k], 1, Rtrev[k] The R and V parts are the same on the left, but are to be taken in reverse. The left side 0 to 2 is the same V shape as on the right (by symmetry), but the points are in reverse. =head2 Right and Left Turn Matching =cut { # segments by direction # A092236, A135254, A133474 # A057083 half term, offset from 3^k, A103312 similar require Math::PlanePath::TerdragonCurve; my $path = Math::PlanePath::TerdragonCurve->new; my %count; my %count_arrays; my $n = 0; my @dxdy_strs = List::Pairwise::mapp {"$a,$b"} $path->_UNDOCUMENTED__dxdy_list; my $width = 36; foreach my $k (12 .. 23) { my $n_end = 3**$k * 0; for ( ; $n < $n_end; $n++) { my ($dx,$dy) = $path->n_to_dxdy($n); $count{"$dx,$dy"}++; } # printf "k=%2d ", $k; # foreach my $dxdy (@dxdy_strs) { # my $a = $count{$dxdy} || 0; # my $aref = ($count_arrays{$dxdy} ||= []); # push @$aref, $a; # # my $ar = Math::BaseCnv::cnv($a,10,3); # printf " %18s", $ar; # } # print "\n"; printf "k=%2d ", $k; foreach my $dxdy (@dxdy_strs) { my $a = _UNDOCUMENTED__level_to_segments_dxdy($path, $k, split(/,/, $dxdy)); my $ar = Math::BaseCnv::cnv($a,10,3); printf " %*s", $width, $ar; } print "\n"; print " "; foreach my $dxdy (@dxdy_strs) { my $a = _UNDOCUMENTED__level_to_segments_dxdy_2($path, $k, split(/,/, $dxdy)); my $ar = Math::BaseCnv::cnv($a,10,3); printf " %*s", $width, $ar; } print "\n"; print "\n"; } my $trim = 1; foreach my $dxdy (@dxdy_strs) { my $aref = $count_arrays{$dxdy} || []; splice @$aref, 0, $trim; # @$aref = MyOEIS::first_differences(@$aref); print "$dxdy\n"; print "is ", join(',',@$aref),"\n"; Math::OEIS::Grep->search (array => \@$aref, name => $dxdy); } sub _UNDOCUMENTED__level_to_segments_dxdy { my ($self, $level, $dx,$dy) = @_; my $a = 1; my $b = 0; my $c = 0; for (1 .. $level) { ($a,$b,$c) = (2*$a + $c, 2*$b + $a, 2*$c + $b); } if ($dx == 2 && $dy == 0) { return $a; } if ($dx == -1) { if ($dy == 1) { return $b; } if ($dy == -1) { return $c; } } return undef; } BEGIN { my @dir3_to_offset = (0,8,4); my @table = (2,1,1, 0,-1,-1, -2,-1,-1, 0,1,1); sub _UNDOCUMENTED__level_to_segments_dxdy_2 { my ($self, $level, $dx,$dy) = @_; my $ret = _dxdy_to_dir3($dx,$dy); if (! defined $ret) { return undef; } $ret = $table[($dir3_to_offset[$ret] + $level) % 12]; $level -= 1; if ($ret) { $ret *= 3**int($level/2); } return 3**$level + $ret; } } sub _dxdy_to_dir3 { my ($dx,$dy) = @_; if ($dx == 2 && $dy == 0) { return 0; } if ($dx == -1) { if ($dy == 1) { return 1; } if ($dy == -1) { return 2; } } return undef; } # print "\n"; # foreach my $k (0 .. $#a) { # my $h = int($k/2); # printf "%3d,", $d[$k]; # } # print "\n"; exit 0; } { # left boundary N # left_boundary_n_pred(14); # ### exit 0 my $path = Math::PlanePath::TerdragonCurve->new; my %non_values; my %n_values; my @n_values; my @values; foreach my $k (4){ print "k=$k\n"; my $n_limit = 2*3**$k; foreach my $n (0 .. $n_limit-1) { $non_values{$n} = 1; } my $points = MyOEIS::path_boundary_points ($path, $n_limit, lattice_type => 'triangular', side => 'left', ); @$points = reverse @$points; # for left ### $points for (my $i = 0; $i+1 <= $#$points; $i++) { my ($x,$y) = @{$points->[$i]}; my ($x2,$y2) = @{$points->[$i+1]}; # my @n_list = $path->xy_to_n_list($x,$y); my @n_list = path_xyxy_to_n($path, $x,$y, $x2,$y2); foreach my $n (@n_list) { delete $non_values{$n}; if ($n <= $n_limit) { $n_values{$n} = 1; } my $n3 = Math::BaseCnv::cnv($n,10,3); my $pred = $path->_UNDOCUMENTED__n_segment_is_left_boundary($n); my $diff = $pred ? '' : ' ***'; if ($k <= 4) { print "$n $n3$diff\n"; } } } @n_values = keys %n_values; @n_values = sort {$a<=>$b} @n_values; my @non_values = keys %non_values; @non_values = sort {$a<=>$b} @non_values; my $count = scalar(@n_values); print "count $count\n"; # push @values, $count; @values = @n_values; if ($k <= 4) { foreach my $n (@non_values) { my $pred = $path->_UNDOCUMENTED__n_segment_is_left_boundary($n); my $diff = $pred ? ' ***' : ''; my $n3 = Math::BaseCnv::cnv($n,10,3); print "non $n $n3$diff\n"; } } # @values = @non_values; print "func "; foreach my $i (0 .. $count-1) { my $n = $path->_UNDOCUMENTED__left_boundary_i_to_n($i); my $n3 = Math::BaseCnv::cnv($n,10,3); print "$n,"; } print "\n"; print "vals "; foreach my $i (0 .. $count-1) { my $n = $values[$i]; my $n3 = Math::BaseCnv::cnv($n,10,3); print "$n3,"; } print "\n"; } # @values = MyOEIS::first_differences(@values); # shift @values; # shift @values; # shift @values; print join(',',@values),"\n"; Math::OEIS::Grep->search(array => \@values); exit 0; } { # right boundary N # $path->_UNDOCUMENTED__n_segment_is_right_boundary(14); # ### exit 0 my $path = Math::PlanePath::TerdragonCurve->new; my %non_values; my %n_values; my @n_values; my @values; foreach my $k (4){ print "k=$k\n"; my $n_limit = 3**$k; foreach my $n (0 .. $n_limit-1) { $non_values{$n} = 1; } my $points = MyOEIS::path_boundary_points ($path, $n_limit, lattice_type => 'triangular', side => 'right', ); # $points = points_2of3($points); for (my $i = 0; $i+1 <= $#$points; $i++) { my ($x,$y) = @{$points->[$i]}; my ($x2,$y2) = @{$points->[$i+1]}; # my @n_list = $path->xy_to_n_list($x,$y); my @n_list = path_xyxy_to_n($path, $x,$y, $x2,$y2); foreach my $n (@n_list) { delete $non_values{$n}; if ($n <= $n_limit) { $n_values{$n} = 1; } my $n3 = Math::BaseCnv::cnv($n,10,3); my $pred = $path->_UNDOCUMENTED__n_segment_is_right_boundary($n); my $diff = $pred ? '' : ' ***'; if ($k <= 4) { print "$n $n3$diff\n"; } } } @n_values = keys %n_values; @n_values = sort {$a<=>$b} @n_values; my @non_values = keys %non_values; @non_values = sort {$a<=>$b} @non_values; my $count = scalar(@n_values); print "count $count\n"; # push @values, $count; @values = @n_values; if ($k <= 4) { foreach my $n (@non_values) { my $pred = $path->_UNDOCUMENTED__n_segment_is_right_boundary($n); my $diff = $pred ? ' ***' : ''; my $n3 = Math::BaseCnv::cnv($n,10,3); print "non $n $n3$diff\n"; } } # @values = @non_values; print "func "; foreach my $i (0 .. $count-1) { my $n = $path->_UNDOCUMENTED__right_boundary_i_to_n($i); my $n3 = Math::BaseCnv::cnv($n,10,3); print "$n3,"; } print "\n"; print "vals "; foreach my $i (0 .. $count-1) { my $n = $values[$i]; my $n3 = Math::BaseCnv::cnv($n,10,3); print "$n,"; } print "\n"; } # @values = MyOEIS::first_differences(@values); # shift @values; # shift @values; # shift @values; print join(',',@values),"\n"; Math::OEIS::Grep->search(array => \@values); exit 0; sub path_xyxy_to_n { my ($path, $x1,$y1, $x2,$y2) = @_; ### path_xyxy_to_n(): "$x1,$y1, $x2,$y2" my @n_list = $path->xy_to_n_list($x1,$y1); ### @n_list my $arms = $path->arms_count; foreach my $n (@n_list) { my ($x,$y) = $path->n_to_xy($n + $arms); if ($x == $x2 && $y == $y2) { return $n; } } return; } } { =head2 Boundary Straight 2s 1 x straight Right j=2 010 left j == 2 mod 8 j=3 11 straight i == 3 mod 12 j= 1100 straight trailing 0s >= 2 j= 1101 left 2 x straight Right i=9 j=6 110 i=10 j=7 111 even ...110 so j == 6 mod 8 odd ...111 i == 9 mod 12 i=21 +12 i=22 +12 Left odd even N and N+1 both bit-above-low-1 = 1 both straight 2m-1 2m odd must be ...11 odd+1 x100 must be ...1100 so odd 1011 is 11 mod 16 =cut # A083575 length=1 # 2^(k-2) - 1 length=2 # 2^(k-3) length=3 # # 3*2^(k-1) - 2*(2^(k-2) - 1) - 3*2^(k-3) # = 12*2^(k-3) - 4*2^(k-3) + 1 - 3*2^(k-3) # = 5*2^(k-3) + 1 # require Math::NumSeq::PlanePathTurn; my $path = Math::PlanePath::TerdragonCurve->new; my $seq = Math::NumSeq::PlanePathTurn->new(planepath_object => $path, turn_type => 'LSR'); my @values; foreach my $k (1 .. 12) { print "k=$k\n"; my $points = MyOEIS::path_boundary_points ($path, 3**$k, lattice_type => 'triangular', side => 'right', ); my $run = 0; my @count = (0,0,0); for (my $i = 0; $i+2 <= $#$points; $i++) { my $tturn6 = points_to_tturn6($points->[$i], $points->[$i+1], $points->[$i+2]); if ($tturn6 == 0) { $run++; } else { $count[$run]++; $run = 0; } } print "$count[0] $count[1] $count[2]\n"; push @values, $count[0]; } shift @values; shift @values; Math::OEIS::Grep->search(array => \@values); exit 0; } =head2 Boundary Isolated Triangles When the boundary visits a point twice it does so by enclosing a single unit triangle. This is seen for example in the turn sequence diagram above where turns 5 and 8 are at the same point and the turns go -1, 1, 1, -1 to enclose a single unit triangle. \ 7 Rt(7)=1 \ / \ \8/ \ *-----6 Rt(6)=1 \5 Rt(5)=-1 \ \ * * / \ / \ / \ / \ \ *-----*-----* \ / \ / \ \ / \ / \ * *-----* \ \ \ =cut { # shortcut boundary length = 2^k area = 2*3^(k-1) # # *-----* # \ # \ # *-----* # my $path = Math::PlanePath::TerdragonCurve->new; my @values; foreach my $k (1 .. 7) { print "k=$k\n"; my $points = MyOEIS::path_boundary_points ($path, 3**$k, lattice_type => 'triangular', # side => 'right', ); $points = points_2of3($points); # points_shortcut_triangular($points); if (@$points < 10) { print join(" ", map{"$_->[0],$_->[1]"} @$points),"\n"; } my $length = scalar(@$points) - 0; require Math::Geometry::Planar; my $polygon = Math::Geometry::Planar->new; $polygon->points($points); my $area = $polygon->area; print " shortcut boundary $length area $area\n"; push @values, $area; } Math::OEIS::Grep->search(array => \@values); exit 0; sub points_2of3 { my ($points) = @_; my @ret; foreach my $i (0 .. $#$points) { if ($i % 3 != 2) { push @ret, $points->[$i]; } } return \@ret; } sub points_shortcut_triangular { my ($points) = @_; my $print = (@$points < 20); my $i = 0; while ($i+2 <= $#$points) { my $tturn6 = points_to_tturn6($points->[$i], $points->[$i+1], $points->[$i+2]); if ($tturn6 == 4) { splice @$points, $i+1, 1; if ($print) { print " delete point ",$i+1,"\n"; } } else { if ($print) { print " keep point ",$i+1,"\n"; } $i++; } # my $p1 = $points->[$i]; # my $p2 = $points->[$i+2]; # if (abs($p1->[0] - $p2->[0]) + abs($p1->[1] - $p2->[1]) == 2) { # splice @$points, $i+1, 1; # if ($print) { print " delete point ",$i+1,"\n"; } # } else { # if ($print) { print " keep point ",$i+1,"\n"; } # $i++; # } } } } { # shortcut turn sequence, is dragon turn sequence by 60 degrees # my $path = Math::PlanePath::TerdragonCurve->new; my @values; foreach my $k (1 .. 7) { print "k=$k\n"; my $points = MyOEIS::path_boundary_points ($path, 3**$k, lattice_type => 'triangular', side => 'right', ); points_shortcut_triangular($points); for (my $i = 0; $i+2 <= $#$points; $i++) { my $tturn6 = points_to_tturn6($points->[$i], $points->[$i+1], $points->[$i+2]); print "$tturn6"; if ($k == 5) { push @values, ($tturn6 == 1 ? 1 : $tturn6 == 5 ? -1 : die); } } print "\n"; } Math::OEIS::Grep->search(array => \@values); exit 0; } { # boundary turn sequence # 26----27 0 to 8 2 4 2 0 4 # \ 9 to 26 2 2 4 0 0 4 # \ 27 2 2 4 2 0 4 0 2 4 0 0 4 # 22 81 2 2 4 2 0 4 2 2 4 0 0 4 0 2 4 2 0 4 0 2 4 0 0 4 # \ 2 2 4 2 0 4 2 2 4 0 0 4 2 2 4 2 0 4 0 2 4 0 0 4 0 2 4 2 0 4 2 2 4 0 0 4 0 2 4 2 0 4 0 2 4 0 0 4 # \ # 12 10 # / \ / \ # / \ / \ # 18 13-----8-----9 Rlen = 1, 3*2^(k-1) # \ / \ / \ V Vlen = 2, 3*2^(k-1) # \ / \ / \ # 17 6----7,4 R -> R,2,V R[1] = 2,4 # \ / \ R V -> R,0,V V[1] = 0,4 # \ / \ # 5,2----3 R[2] = 2,4 2 0,4 # \ V V[2] = 2,4 0 0,4 # \ # 0-----1 bit above lowest 1 like dragon # R # # R[k+1] my $side = 'left'; my (@R, @V); if ($side eq 'right') { @R = (''); @V = ('4'); } else { @R = (''); @V = ('2'); } # 2 4 0 0 turn = ternary lowest non-zero 1=left 2=right # 2 0 4 1 1 # 2 2 4 10 2 # 0 0 4 11 10 # 2 2 4 100 11 # 2 0 4 101 12 # 0 2 4 110 20 # 0 0 4 111 21 # 2 2 4 1000 22 # 2 0 4 100 # 2 2 4 101 # 0 0 4 102 # 0 2 4 110 # 2 0 4 111 # 0 2 4 112 # 0 0 4 120 # 2 2 4 121 # 2 0 4 122 # 2 2 4 200 # 0 0 4 201 # 2 2 4 # 2 0 4 # 0 2 4 # 0 0 4 # 0 2 4 # 2 0 4 # 2 2 4 # 0 0 4 # 0 2 4 # 2 0 4 # 0 2 4 # 0 0 4 sub Tt_to_tturn6 { if ($side eq 'right') { goto &Rt_to_tturn6; } else { goto &Lt_to_tturn6; } } sub Rt_to_tturn6 { my ($i) = @_; { if ($i % 3 == 2) { return 4; } my $j = $i - int($i/3); return (bit_above_lowest_zero($j) ? 0 : 2); } { my $mod = _divrem_mutate($i, 3); if ($mod == 2) { return 4; } if ($mod == 1) { return ($i % 2 ? 0 : 2); } do { $mod = _divrem_mutate($i, 2); } while ($mod == 0); $mod = _divrem_mutate($i, 2); return ($mod % 2 ? 0 : 2); } } # i=0 # i=1 2 # i=2 j=1 # i=3 j=2 # i=4 2 # i=5 j=3 # i=6 j=4 # i=7 2 # i=8 j=5 # i=9 j=6 sub Lt_to_tturn6 { my ($i) = @_; { if ($i % 3 == 1) { return 2; } my $j = $i - int(($i+1)/3); # print "i=$i j=$j\n"; return (bit_above_lowest_one($j) ? 4 : 0); } } sub bit_above_lowest_one { my ($n) = @_; for (;;) { if (! $n || ($n % 2) != 0) { last; } $n = int($n/2); } $n = int($n/2); return ($n % 2); } sub bit_above_lowest_zero { my ($n) = @_; for (;;) { if (($n % 2) == 0) { last; } $n = int($n/2); } $n = int($n/2); return ($n % 2); } my @dir6_to_dx = (2, 1,-1,-2, -1, 1); my @dir6_to_dy = (0, 1, 1, 0, -1,-1); my $path = Math::PlanePath::TerdragonCurve->new; require Math::NumSeq::PlanePathTurn; require Math::NumSeq::PlanePathDelta; foreach my $k (1 .. 7) { print "k=$k\n"; if ($side eq 'right') { $R[$k] = $R[$k-1] . '2' . $V[$k-1]; $V[$k] = $R[$k-1] . '0' . $V[$k-1]; } else { $V[$k] = $V[$k-1] . '0' . $R[$k-1]; $R[$k] = $V[$k-1] . '4' . $R[$k-1]; } my $n_limit = ($side eq 'right' ? 3**$k : 2*3**$k); my $points = MyOEIS::path_boundary_points ($path, $n_limit, lattice_type => 'triangular', side => $side); if ($side eq 'left') { @$points = reverse @$points; } if (@$points < 20) { print "points"; foreach my $p (@$points) { print " $p->[0],$p->[1]"; } print "\n"; } my @values; foreach my $i (1 .. $#$points - 1) { my $tturn6 = points_to_tturn6($points->[$i-1], $points->[$i], $points->[$i+1]); # if ($tturn6 > 3) { $tturn6 -= 6; } # my $dir6 = Math::NumSeq::PlanePathDelta::_delta_func_TDir6($dx,$dy); # if ($dir6 > 3) { $dir6 -= 6; } push @values, $tturn6; } # { # my @new_values; # for (my $i = 2; $i <= $#values; $i += 3) { # push @new_values, $values[$i] / 2; # } # @values = @new_values; # } Math::OEIS::Grep->search(array => \@values); my $v = join('',@values); print "p $v\n"; if ($side eq 'right') { print "R $R[$k]\n"; if ($v ne $R[$k]) { print " wrong\n"; } } else { print "V $V[$k]\n"; if ($v ne $V[$k]) { print " wrong\n"; } } my $f = join('', map {Tt_to_tturn6($_)} 1 .. scalar(@values)); print "f $f\n"; if ($v ne $f) { print " wrong\n"; } } foreach my $i (1 .. 18) { my $tturn6 = Tt_to_tturn6($i); my $pn = ($tturn6 == 2 ? 1 : $tturn6 == 0 ? 0 : $tturn6 == 4 ? -1 : die); print "$pn, "; } print "\n"; exit 0; sub points_to_tturn6 { my ($p1,$p2,$p3) = @_; my ($x1,$y1) = @$p1; my ($x2,$y2) = @$p2; my ($x3,$y3) = @$p3; my $dx = $x2-$x1; my $dy = $y2-$y1; my $next_dx = $x3-$x2; my $next_dy = $y3-$y2; require Math::NumSeq::PlanePathTurn; return Math::NumSeq::PlanePathTurn::_turn_func_TTurn6($dx,$dy, $next_dx,$next_dy); } } { # dRadius range my $n = 118088; require Math::PlanePath::TerdragonMidpoint; my $path = Math::PlanePath::TerdragonMidpoint->new; my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); print "$x1,$y1 $x2,$y2\n"; exit 0; } { # A+Yw A=X-Y require Math::BaseCnv; my $path = Math::PlanePath::TerdragonCurve->new; my $dx_min = 0; my $dx_max = 0; foreach my $n (1 .. 3**10) { my ($dx,$dy) = $path->n_to_dxdy($n); if ($dx == 299) { my $n3 = Math::BaseCnv::cnv($n,10,3); printf "%3d %s\n", $n, $n3; } $dx_min = min($dx_min,$dx); $dx_max = max($dx_max,$dx); } print "$dx_min $dx_max\n"; exit 0; } { # A+Yw A=X-Y require Math::BaseCnv; my $path = Math::PlanePath::TerdragonCurve->new; my @values; foreach my $n (1 .. 3**6) { my @n_list = $path->n_to_n_list($n); if (@n_list == 1) { push @values, $n; } if (@n_list == 1 && $n == $n_list[0]) { my $n3 = Math::BaseCnv::cnv($n,10,3); printf "%3d %s\n", $n, $n3; } } print join(',',@values),"\n"; Math::OEIS::Grep->search(array=>\@values); exit 0; } { # A+Yw A=X-Y my $path = Math::PlanePath::TerdragonCurve->new; my @values; foreach my $n (1 .. 20) { my ($x,$y) = $path->n_to_xy($n); push @values, ($x-$y); } Math::OEIS::Grep->search(array=>\@values); exit 0; } { # TerdragonCurve direction away from a point require Image::Base::Text; my $arms = 6; my $path = Math::PlanePath::TerdragonCurve->new (arms => $arms); my $width = 78; my $height = 40; my $x_lo = -$width/2; my $y_lo = -$height/2; my $x_hi = $x_lo + $width - 1; my $y_hi = $y_lo + $height - 1; my $image = Image::Base::Text->new (-width => $width, -height => $height); my $plot = sub { my ($x,$y,$char) = @_; $x -= $x_lo; $y -= $y_lo; return if $x < 0 || $y < 0 || $x >= $width || $y >= $height; $image->xy ($x,$height-1-$y,$char); }; my ($n_lo, $n_hi) = $path->rect_to_n_range($x_lo-2,$y_lo-2, $x_hi+2,$y_hi+2); print "n_hi $n_hi\n"; for my $n (0 .. $n_hi) { my $arm = $n % $arms; my ($x,$y) = $path->n_to_xy($n); next if $x < $x_lo || $y < $y_lo || $x > $x_hi || $y > $y_hi; my ($nx,$ny) = $path->n_to_xy($n + $arms); my $dir = dxdy_to_dir6($nx-$x,$ny-$y); if ($dir == 2) { $plot->($x, $y, $dir); } } $plot->(0,0, '+'); $image->save('/dev/stdout'); exit 0; } { # TerdragonCurve xy_to_n offsets to Midpoint require Math::PlanePath::TerdragonMidpoint; my $arms = 6; my $curve = Math::PlanePath::TerdragonCurve->new (arms => $arms); my $midpoint = Math::PlanePath::TerdragonMidpoint->new (arms => $arms); my %seen; for my $n (0 .. 1000) { my ($x,$y) = $curve->n_to_xy($n); $x *= 2; $y *= 2; for my $dx (-2 .. 2) { for my $dy (-1 .. 1) { my $m = $midpoint->xy_to_n($x+$dx,$y+$dy) // next; if ($m == $n) { $seen{"$dx,$dy"} = 1; } } } } ### %seen exit 0; } { # TerdragonCurve xy cf Midpoint require Image::Base::Text; require Math::PlanePath::TerdragonMidpoint; my $arms = 6; my $curve = Math::PlanePath::TerdragonCurve->new (arms => $arms); my $midpoint = Math::PlanePath::TerdragonMidpoint->new (arms => $arms); my $width = 50; my $height = 30; my $x_lo = -$width/2; my $y_lo = -$height/2; my $x_hi = $x_lo + $width - 1; my $y_hi = $y_lo + $height - 1; my $image = Image::Base::Text->new (-width => $width, -height => $height); my $plot = sub { my ($x,$y,$char) = @_; $x -= $x_lo; $y -= $y_lo; return if $x < 0 || $y < 0 || $x >= $width || $y >= $height; $image->xy ($x,$height-1-$y,$char); }; my ($n_lo, $n_hi) = $curve->rect_to_n_range($x_lo-2,$y_lo-2, $x_hi+2,$y_hi+2); print "n_hi $n_hi\n"; for my $y ($y_lo .. $y_hi) { for my $x ($x_lo .. $x_hi) { my $n = $curve->xy_to_n($x,$y) // next; my $arm = $n % $arms; my ($nx,$ny) = $curve->n_to_xy($n + $arms); my $dir = dxdy_to_dir6($nx-$x,$ny-$y); $plot->($x, $y, $dir); } } $plot->(0,0, '+'); $image->save('/dev/stdout'); exit 0; } { # TerdragonMidpoint xy absolute direction require Image::Base::Text; require Math::PlanePath::TerdragonMidpoint; my $arms = 6; my $path = Math::PlanePath::TerdragonMidpoint->new (arms => $arms); my $width = 50; my $height = 30; my $x_lo = -$width/2; my $y_lo = -$height/2; my $x_hi = $x_lo + $width - 1; my $y_hi = $y_lo + $height - 1; my $image = Image::Base::Text->new (-width => $width, -height => $height); my $plot = sub { my ($x,$y,$char) = @_; $x -= $x_lo; $y -= $y_lo; return if $x < 0 || $y < 0 || $x >= $width || $y >= $height; $image->xy ($x,$height-1-$y,$char); }; my ($n_lo, $n_hi) = $path->rect_to_n_range($x_lo-2,$y_lo-2, $x_hi+2,$y_hi+2); print "n_hi $n_hi\n"; for my $n (0 .. $n_hi) { my $arm = $n % $arms; my ($x,$y) = $path->n_to_xy($n); # if (($n % $arms) == 1) { # $x += 1; # $y += 1; # } next if $x < $x_lo || $y < $y_lo || $x > $x_hi || $y > $y_hi; my ($nx,$ny) = $path->n_to_xy($n + $arms); # if (($n % $arms) == 1) { # $nx += 1; # $ny += 1; # } # if ($nx == $x+1) { # $image->xy($x,$y,$n&3); # } # if ($ny == $y+1) { # $image->xy($x,$y,$n&3); # } # if ($ny == $y) { # } my $show; my $dir = dxdy_to_dir6($nx-$x,$ny-$y); my $digit = (($x + 3*$y) + 0) % 3; my $d9 = ((2*$x + $y) + 0) % 9; my $c = ($x+$y)/2; my $flow = sprintf "%X", ($x + 3*$y) % 12; my $prev_dir = -1; if ($n >= $arms) { my ($px,$py) = $path->n_to_xy($n - $arms); $prev_dir = dxdy_to_dir6($x-$px,$y-$py); } foreach my $r (0,1,2) { $flow = ($r == 0 ? '-' : $r == 1 ? '/' : '\\'); if ($arm & 1) { if (($digit == 0 || $digit == 1) && (($dir%3) == $r)) { $show = $flow; } if (($digit == 2) && (($prev_dir%3) == $r)) { $show = $flow; } } else { if (($digit == 0 || $digit == 2) && (($dir%3) == $r)) { $show = $flow; } if (($digit == 1) && (($prev_dir%3) == $r)) { $show = $flow; } } } if (! defined $show) { $show = '.'; } # if ($digit == 1) { # if ($dir == 0 || $dir == 3) { # $show = $dir; # $show = 'x'; # } # } # if ($digit == 2) { # if ($dir == 0 || $dir == 3) { # $show = $prev_dir; # $show = 'x'; # } # } # if ($digit == 0) { # $show = 'x'; # } my $mod = (int($n/$arms) % 3); # if (($arm == 0 && $mod == 0) # || ($arm == 1 && $mod == 2) # || ($arm == 2 && $mod == 0) # || ($arm == 3 && $mod == 2) # || ($arm == 4 && $mod == 0) # || ($arm == 5 && $mod == 2)) { # # $show = '0'; # # $show = $digit; # if ($n < 3*$arms) { # print "n=$n $x,$y mod=$mod\n"; # } # } # if (($arm == 0 && $mod == 1) # || ($arm == 1 && $mod == 1) # || ($arm == 2 && $mod == 1) # || ($arm == 3 && $mod == 1) # || ($arm == 4 && $mod == 1) # || ($arm == 5 && $mod == 1)) { # # $show = '1'; # } # if (($arm == 0 && $mod == 2) # || ($arm == 1 && $mod == 0) # || ($arm == 2 && $mod == 2) # || ($arm == 3 && $mod == 0) # || ($arm == 4 && $mod == 2) # || ($arm == 5 && $mod == 0)) { # # $show = '2'; # } if (defined $show) { $plot->($x, $y, $show); } # if ($dir == 0) { # $image->xy($x-$x_lo,$y-$y_lo, $dir); # } } # $plot->(0,0, '+'); $image->save('/dev/stdout'); exit 0; } { require Math::PlanePath::TerdragonMidpoint; my $path = Math::PlanePath::TerdragonMidpoint->new; $path->xy_to_n(5,3); exit 0; } { # TerdragonMidpoint modulo require Math::PlanePath::TerdragonMidpoint; my $arms = 2; my $path = Math::PlanePath::TerdragonMidpoint->new (arms => $arms); for my $n (0 .. 3**4) { my $arm = $n % $arms; my $mod = (int($n/$arms) % 3); my ($x,$y) = $path->n_to_xy($n); my $digit = (($x + 3*$y) + 0) % 3; print "n=$n $x,$y mod=$mod k=$digit\n"; } exit 0; } { # cumulative turn +/- 1 list require Math::BaseCnv; my $path = Math::PlanePath::TerdragonCurve->new; my $cumulative = 0; for (my $n = $path->n_start + 1; $n < 35; $n++) { my $n3 = Math::BaseCnv::cnv($n,10,3); my $turn = calc_n_turn ($n); # my $turn = path_n_turn($path, $n); if ($turn == 2) { $turn = -1 } $cumulative += $turn; printf "%3s %4s %d\n", $n, $n3, $cumulative; } print "\n"; exit 0; } { # cumulative turn +/- 1 my $path = Math::PlanePath::TerdragonCurve->new; my $cumulative = 0; my $max = 0; my $min = 0; for (my $n = $path->n_start + 1; $n < 35; $n++) { my $turn = calc_n_turn ($n); # my $turn = path_n_turn($path, $n); if ($turn == 2) { $turn = -1 } $cumulative += $turn; $max = max($cumulative,$max); $min = min($cumulative,$min); print "$cumulative,"; } print "\n"; print "min $min max $max\n"; exit 0; sub calc_n_turn { my ($n) = @_; die if $n == 0; while (($n % 3) == 0) { $n = int($n/3); # skip low 0s } return ($n % 3); # next digit is the turn } } { # turn my $path = Math::PlanePath::TerdragonCurve->new; my $n = $path->n_start; # my ($n0_x, $n0_y) = $path->n_to_xy ($n); # $n++; # my ($prev_x, $prev_y) = $path->n_to_xy ($n); # my ($prev_dx, $prev_dy) = ($prev_x - $n0_x, $prev_y - $n0_y); # my $prev_dir = dxdy_to_dir ($prev_dx, $prev_dy); $n++; my $pow = 3; for ( ; $n < 128; $n++) { # my ($x, $y) = $path->n_to_xy ($n); # my $dx = $x - $prev_x; # my $dy = $y - $prev_y; # my $dir = dxdy_to_dir ($dx, $dy); # my $turn = ($dir - $prev_dir) % 3; # # $prev_dir = $dir; # ($prev_x,$prev_y) = ($x,$y); my $turn = path_n_turn($path, $n); my $azeros = digit_above_low_zeros($n); my $azx = ($azeros == $turn ? '' : '*'); # my $aones = digit_above_low_ones($n-1); # if ($aones==0) { $aones=1 } # elsif ($aones==1) { $aones=0 } # elsif ($aones==2) { $aones=2 } # my $aox = ($aones == $turn ? '' : '*'); # # my $atwos = digit_above_low_twos($n-2); # if ($atwos==0) { $atwos=1 } # elsif ($atwos==1) { $atwos=2 } # elsif ($atwos==2) { $atwos=0 } # my $atx = ($atwos == $turn ? '' : '*'); # # my $lzero = digit_above_low_zeros($n); # my $lone = digit_above_lowest_one($n); # my $ltwo = digit_above_lowest_two($n); # print "$n $turn ones $aones$aox twos $atwos$atx zeros $azeros${azx}[$lzero] $lone $ltwo\n"; print "$n $turn zeros got=$azeros ${azx}\n"; } print "\n"; exit 0; sub digit_above_low_zeros { my ($n) = @_; if ($n == 0) { return 0; } while (($n % 3) == 0) { $n = int($n/3); } return ($n % 3); } sub path_n_turn { my ($path, $n) = @_; my $prev_dir = path_n_dir ($path, $n-1); my $dir = path_n_dir ($path, $n); return ($dir - $prev_dir) % 3; } sub path_n_dir { my ($path, $n) = @_; my ($prev_x, $prev_y) = $path->n_to_xy ($n); my ($x, $y) = $path->n_to_xy ($n+1); return dxdy_to_dir($x - $prev_x, $y - $prev_y); } } { # min/max for level require Math::BaseCnv; my $path = Math::PlanePath::TerdragonCurve->new; my $prev_min = 1; my $prev_max = 1; for (my $level = 1; $level < 25; $level++) { my $n_start = 3**($level-1); my $n_end = 3**$level; my $min_hypot = 128*$n_end*$n_end; my $min_x = 0; my $min_y = 0; my $min_pos = ''; my $max_hypot = 0; my $max_x = 0; my $max_y = 0; my $max_pos = ''; print "level $level n=$n_start .. $n_end\n"; foreach my $n ($n_start .. $n_end) { my ($x,$y) = $path->n_to_xy($n); my $h = $x*$x + 3*$y*$y; if ($h < $min_hypot) { $min_hypot = $h; $min_pos = "$x,$y"; } if ($h > $max_hypot) { $max_hypot = $h; $max_pos = "$x,$y"; } } # print " min $min_hypot at $min_x,$min_y\n"; # print " max $max_hypot at $max_x,$max_y\n"; { my $factor = $min_hypot / $prev_min; my $min_hypot3 = Math::BaseCnv::cnv($min_hypot,10,3); print " min h= $min_hypot [$min_hypot3] at $min_pos factor $factor\n"; my $calc = (4/3/3) * 2.9**$level; print " cf $calc\n"; } # { # my $factor = $max_hypot / $prev_max; # my $max_hypot3 = Math::BaseCnv::cnv($max_hypot,10,3); # print " max h= $max_hypot [$max_hypot3] at $max_pos factor $factor\n"; # # my $calc = 4 * 3**($level*.9) * 4**($level*.1); # # print " cf $calc\n"; # } $prev_min = $min_hypot; $prev_max = $max_hypot; } exit 0; } { # turn my $path = Math::PlanePath::TerdragonCurve->new; my $n = $path->n_start; my ($n0_x, $n0_y) = $path->n_to_xy ($n); $n++; my ($prev_x, $prev_y) = $path->n_to_xy ($n); my ($prev_dx, $prev_dy) = ($prev_x - $n0_x, $prev_y - $n0_y); my $prev_dir = dxdy_to_dir ($prev_dx, $prev_dy); $n++; my $pow = 3; for ( ; $n < 128; $n++) { my ($x, $y) = $path->n_to_xy ($n); my $dx = ($x - $prev_x); my $dy = ($y - $prev_y); my $dir = dxdy_to_dir ($dx, $dy); my $turn = ($dir - $prev_dir) % 3; $prev_dir = $dir; ($prev_x,$prev_y) = ($x,$y); print "$turn"; if ($n-1 == $pow) { $pow *= 3; print "\n"; } } print "\n"; exit 0; } sub path_to_dir6 { my ($path,$n) = @_; my ($x,$y) = $path->n_to_xy($n); my ($nx,$ny) = $path->n_to_xy($n + $path->arms_count); return dxdy_to_dir6($nx-$x,$ny-$y); } sub dxdy_to_dir6 { my ($dx,$dy) = @_; if ($dy == 0) { if ($dx == 2) { return 0; } if ($dx == -2) { return 3; } } if ($dy == 1) { if ($dx == 1) { return 1; } if ($dx == -1) { return 2; } } if ($dy == -1) { if ($dx == 1) { return 5; } if ($dx == -1) { return 4; } } die "unrecognised $dx,$dy"; } # per KochCurve.t sub dxdy_to_dir3 { my ($dx,$dy) = @_; if ($dy == 0) { if ($dx == 2) { return 0/2; } # if ($dx == -2) { return 3; } } if ($dy == 1) { # if ($dx == 1) { return 1; } if ($dx == -1) { return 2/2; } } if ($dy == -1) { # if ($dx == 1) { return 5; } if ($dx == -1) { return 4/2; } } die "unrecognised $dx,$dy"; } sub digit_above_low_ones { my ($n) = @_; if ($n == 0) { return 0; } while (($n % 3) == 1) { $n = int($n/3); } return ($n % 3); } sub digit_above_low_twos { my ($n) = @_; if ($n == 0) { return 0; } while (($n % 3) == 2) { $n = int($n/3); } return ($n % 3); } sub digit_above_lowest_zero { my ($n) = @_; for (;;) { if (($n % 3) == 0) { last; } $n = int($n/3); } $n = int($n/3); return ($n % 3); } sub digit_above_lowest_one { my ($n) = @_; for (;;) { if (! $n || ($n % 3) != 0) { last; } $n = int($n/3); } $n = int($n/3); return ($n % 3); } sub digit_above_lowest_two { my ($n) = @_; for (;;) { if (! $n || ($n % 3) != 0) { last; } $n = int($n/3); } $n = int($n/3); return ($n % 3); } #------------------------------------------------------------------------------ # Old xy_to_n_list based on TerdragonMidpoint::xy_to_n # maximum extent -- no, not quite right # # .----* # \ # *----. # # Two triangle heights, so # rnext = 2 * r * sqrt(3)/2 # = r * sqrt(3) # rsquared_next = 3 * rsquared # Initial X=2,Y=0 is rsquared=4 # then X=3,Y=1 is 3*3+3*1*1 = 9+3 = 12 = 4*3 # then X=3,Y=3 is 3*3+3*3*3 = 9+3 = 36 = 4*3^2 # # my @try_dx = (2, 1, -1, -2, -1, 1); # my @try_dy = (0, 1, 1, 0, -1, -1); # # my $xm = 2*$x; # doubled out # my $ym = 2*$y; # foreach my $i (0 .. $#try_dx) { # my $t = $self->Math::PlanePath::TerdragonMidpoint::xy_to_n # ($xm+$try_dx[$i], $ym+$try_dy[$i]); # # ### try: ($xm+$try_dx[$i]).",".($ym+$try_dy[$i]) # ### $t # # next unless defined $t; # # # function call here to get our n_to_xy(), not the overridden method # # when in TerdragonRounded or other subclass # my ($tx,$ty) = n_to_xy($self,$t) # or next; # # if ($tx == $x && $ty == $y) { # ### found: $t # if (@n_list && $t < $n_list[0]) { # unshift @n_list, $t; # } elsif (@n_list && $t < $n_list[-1]) { # splice @n_list, -1,0, $t; # } else { # push @n_list, $t; # } # if (@n_list == 3) { # return @n_list; # } # } # } # return @n_list; Math-PlanePath-129/devel/cellular-rule-oeis.pl0000644000175000017500000000440612611264071017053 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2015 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use HTML::Entities::Interpolate; use List::Util; use URI::Escape; use Tie::IxHash; use Math::BigInt; use Math::PlanePath::CellularRule; # uncomment this to run the ### lines #use Smart::Comments; { # greps my %done; tie %done, 'Tie::IxHash'; foreach my $rule (0 .. 255) { my $path = Math::PlanePath::CellularRule->new(rule=>$rule); my @values; # { # # 0/1 cells # Y01: foreach my $y (0 .. 10) { # foreach my $x (-$y .. $y) { # if (defined ($path->xy_to_n($x,$y))) { # push @values, 1; # } else { # push @values, 0; # } # last Y01 if (@values > 100); # } # } # } { # bignum rows my $base = 10; # 2 or 10 Y01: foreach my $y (0 .. 20) { my $n = ''; foreach my $x (-$y .. $y) { $n .= defined $path->xy_to_n($x,$y) ? '1' : '0'; } $n =~ s/^0+//; if ($n eq '') { $n = 0; } if ($base == 10) { Math::BigInt->new("0b$n"); } push @values, $n; } } my $values = join(',',@values); $done{$values} .= ",$rule"; } foreach my $values (keys %done) { my $name = $done{$values}; $name =~ s/^,//; $name = "rule=".$name; print "$name\n"; print "values $values\n"; my @values = split /,/, $values; require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, name => $name, verbose => 0, ); } exit 0; } Math-PlanePath-129/devel/sierpinski-triangle.pl0000644000175000017500000002672712536646441017356 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min', 'max'; use Math::PlanePath::SierpinskiTriangle; use Math::PlanePath; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; # # # # # # # # # # # # # # # 8 14 # 7 10 11 12 13 # 6 8 9 # 5 6 7 # 4 5 # 3 3 4 # 2 2 # 1 1 # 0 0 # { # number of children my $path = Math::PlanePath::SierpinskiTriangle->new; for (my $n = $path->n_start; $n < 180; $n++) { my @n_children = $path->tree_n_children($n); my $num_children = scalar(@n_children); print "$num_children,"; print "\n" if path_tree_n_is_depth_end($path,$n); } print "\n"; exit 0; sub path_tree_n_is_depth_end { my ($path, $n) = @_; my $depth = $path->tree_n_to_depth($n); return defined($depth) && $n == $path->tree_depth_to_n_end($depth); } } { # Pascal's triangle as a graph my $max_row = 4; require Graph::Easy; require Math::BigInt; my $graph = Graph::Easy->new; foreach my $row (0 .. $max_row) { foreach my $col (0 .. $row) { my $n = Math::BigInt->new($row)->bnok($col); next unless $n % 2; $graph->add_vertex("$row,$col=$n"); next if $row >= $max_row; my $row2 = $row + 1; foreach my $col2 ($col, $col+1) { my $n2 = Math::BigInt->new($row2)->bnok($col2); ### consider: "$row2,$col2=$n2" next unless $n2 % 2; $graph->add_edge("$row,$col=$n", "$row2,$col2=$n2"); } } } print $graph->as_ascii(); exit 0; } { # 41 81 # 33 34 35 36 37 38 39 40 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 15 # 29 30 31 32 57 58 59 60 61 62 63 64 14 # 25 26 27 28 49 50 51 52 53 54 55 56 13 # 23 24 45 46 47 48 12 # 19 20 21 22 37 38 39 40 41 42 43 44 11 # 17 18 33 34 35 36 10 # 15 16 29 30 31 32 9 # 14 8 27 28 # 10 11 12 13 7 19 20 21 22 23 24 25 26 # 8 9 6 15 16 17 18 # 6 7 5 11 12 13 14 # 5 4 9 10 # 3 4 3 5 6 7 8 # 2 2 3 4 # 1 1 1 2 # 0 <- Y=0 0 # # 0,1,2,3,3, 4,5,5 Math::PlanePath::SierpinskiTriangle::_n0_to_depthbits(81,'all'); my $parts = 'left'; foreach my $n (0 .. 41) { my ($depthbits, $ndepth, $nwidth) = Math::PlanePath::SierpinskiTriangle::_n0_to_depthbits($n,$parts); my $depth = digit_join_lowtohigh ($depthbits, 2); print "n=$n depth= $depth ndepth= $ndepth\n"; } exit 0; } { # centroid # X = 0 midpoint # Y = (2^n - 2)/3 # I = (4*12^n-3^n)/3 * 24/9 # = 8/9 * (4*12^n-3^n) # = 8/3 * 3^k * (4*4^k - 1)/3 my $path = Math::PlanePath::SierpinskiTriangle->new; my @values; foreach my $level (0 .. 7) { my ($n_lo, $n_hi) = $path->level_to_n_range($level); my $gx = 0; my $gy = 0; my $count = 0; foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); $gx += $x; $gy += $y; $count++; } $gx = to_bigrat($gx); $gy = to_bigrat($gy); $gx /= $count; $gy /= $count; my $I = 0; foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); $I += ($x - $gx)**2 + ($y - $gy)**2; } $I /= 3**$level; push @values, $I*9/24; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, verbose => 1); exit 0; sub to_bigrat { my ($n) = @_; require Math::BigRat; return Math::BigRat->new($n); # return $n; } } { # Pascal's triangle require Math::BigInt; my @array; my $rows = 10; my $width = 0; foreach my $y (0 .. $rows) { foreach my $x (0 .. $y) { my $n = Math::BigInt->new($y); my $k = Math::BigInt->new($x); $n->bnok($k); my $str = "$n"; $array[$x][$y] = $str; $width = max($width,length($str)); } } $width += 2; if ($width & 1) { $width++; } # $width |= 1; foreach my $y (0 .. $rows) { print ' ' x (($rows-$y) * int($width/2)); foreach my $x (0 .. $y) { my $value = $array[$x][$y]; unless ($value & 1) { $value = ''; } printf "%*s", $width, $value; } print "\n"; } exit 0; } { # NumSiblings run lengths # lowest 1-bit of pos k # NumChildren run lengths # is same lowest 1-bit if NumChildren=0 leaf coalesced with NumChildren=1 my $path = Math::PlanePath::SierpinskiTriangle->new (align => 'diagonal'); require Math::NumSeq::PlanePathCoord; my $seq = Math::NumSeq::PlanePathCoord->new (planepath_object => $path, # coordinate_type => 'NumChildren', coordinate_type => 'NumSiblings', ); my $prev = 0; my $run = 1; for (my $n = $path->n_start+1; $n < 500; $n++) { my ($i,$value) = $seq->next; $value = 1-$value; # if ($value == 1) { $value = 0; } # if ($value == $prev) { # $run++; # } else { # print "$run,"; # $run = 1; # $prev = $value; # } # printf "%4b %d\n", $i, $value; print "$value,"; } print "\n"; exit 0; sub path_tree_n_num_siblings { my ($path, $n) = @_; $n = $path->tree_n_parent($n); return (defined $n ? $path->tree_n_num_children($n) - 1 # not including self : 0); # any tree root considered to have no siblings } } { # height use constant _INFINITY => do { my $x = 999; foreach (1 .. 20) { $x *= $x; } $x; }; my $path = Math::PlanePath::SierpinskiTriangle->new (align => 'diagonal'); require Math::NumSeq::PlanePathCoord; my $seq = Math::NumSeq::PlanePathCoord->new (planepath_object => $path, coordinate_type => 'SubHeight'); for (my $n = $path->n_start; $n < 500; $n++) { my ($x,$y) = $path->n_to_xy($n); my $s = $seq->ith($n); # my $c = $path->_UNTESTED__NumSeq__tree_n_to_leaflen($n); my $c = n_to_subheight($n); if (! defined $c) { $c = _INFINITY; } my $diff = ($s == $c ? '' : ' ***'); print "$x,$y $s $c$diff\n"; } print "\n"; exit 0; sub n_to_subheight { my ($n) = @_; # this one correct based on diagonal X,Y bits my ($x,$y) = $path->n_to_xy($n); if ($x == 0 || $y == 0) { return _INFINITY(); } my $mx = ($x ^ ($x-1)) >> 1; my $my = ($y ^ ($y-1)) >> 1; return max ($mx - ($y & $mx), $my - ($x & $my)); # Must stretch out $n remainder to make X. # my ($depthbits, $ndepth, $nwidth) = Math::PlanePath::SierpinskiTriangle::_n0_to_depthbits($n); # $n -= $ndepth; # X # my $y = digit_join_lowtohigh ($depthbits, 2, $n*0) - $n; # # if ($n == 0 || $y == 0) { # return undef; # } # my $mx = ($n ^ ($n-1)) >> 1; # my $my = ($y ^ ($y-1)) >> 1; # return max ($mx - ($y & $mx), # $my - ($n & $my)); # my $h = high_bit($y); # my $m = ($h<<1)-1; # return $y ^ $m; # # return count_0_bits($y); # - count_0_bits($x); } sub high_bit { my ($n) = @_; my $bit = 1; while ($bit <= $n) { $bit <<= 1; } return $bit >> 1; } sub count_0_bits { my ($n) = @_; my $count = 0; while ($n) { $count += ($n & 1) ^ 1; $n >>= 1; } return $count; } sub count_1_bits { my ($n) = @_; my $count = 0; while ($n) { $count += ($n & 1); $n >>= 1; } return $count; } } { # number of children in replicate style my $levels = 5; my $height = 2**$levels; sub replicate_n_to_xy { my ($n) = @_; my $zero = $n * 0; my @xpos_bits; my @xneg_bits; my @y_bits; foreach my $ndigit (digit_split_lowtohigh($n,3)) { if ($ndigit == 0) { push @xpos_bits, 0; push @xneg_bits, 0; push @y_bits, 0; } elsif ($ndigit == 1) { push @xpos_bits, 0; push @xneg_bits, 1; push @y_bits, 1; } else { push @xpos_bits, 1; push @xneg_bits, 0; push @y_bits, 1; } } return (digit_join_lowtohigh(\@xpos_bits, 2, $zero) - digit_join_lowtohigh(\@xneg_bits, 2, $zero), digit_join_lowtohigh(\@y_bits, 2, $zero)); } # xxx0 = 2 low digit 0 then num children = 2 # xxx0111 = 1 \ low digit != 0 then all low non-zeros must be same # xxx0222 = 1 / # other = 0 otherwise num children = 0 sub replicate_tree_n_num_children { my ($n) = @_; $n = int($n); my $low_digit = _divrem_mutate($n,3); if ($low_digit == 0) { return 2; } while (my $digit = _divrem_mutate($n,3)) { if ($digit != $low_digit) { return 0; } } return 1; } my $path = Math::PlanePath::SierpinskiTriangle->new; my %grid; for (my $n = 0; $n < 3**$levels; $n++) { my ($x,$y) = replicate_n_to_xy($n); my $path_num_children = path_xy_num_children($path,$x,$y); my $repl_num_children = replicate_tree_n_num_children($n); if ($path_num_children != $repl_num_children) { print "$x,$y $path_num_children $repl_num_children\n"; exit 1; } $grid{$x}{$y} = $repl_num_children; } foreach my $y (0 .. $height) { foreach my $x (-$height .. $y) { print $grid{$x}{$y} // ' '; } print "\n"; } exit 0; sub path_xy_num_children { my ($path, $x,$y) = @_; my $n = $path->xy_to_n($x,$y); return (defined $n ? $path->tree_n_num_children($n) : undef); } } { my $path = Math::PlanePath::SierpinskiTriangle->new; foreach my $y (0 .. 10) { foreach my $x (-$y .. $y) { if ($path->xy_to_n($x,$y)) { print "1,"; } else { print "0,"; } } } print "\n"; exit 0; } Math-PlanePath-129/devel/fibonacci-word.pl0000644000175000017500000001631712150501071016230 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; # uncomment this to run the ### lines use Smart::Comments; { # Knot overlapping points # 0,1, 4,16,68,288,1220,5168 # /4 1,4,17,72,305,1292 = A001076 a(n) = 4a(n-1) + a(n-2) # denom continued fract converg to sqrt(5), 4-Fibonacci # each next = this*4 + prev require Math::PlanePath::FibonacciWordKnott; require Math::BaseCnv; require Math::NumSeq::BalancedBinary; my $path = Math::PlanePath::FibonacciWordKnott->new; my %seen; my %diffs; require Tie::IxHash; tie %diffs, 'Tie::IxHash'; foreach my $n ($path->n_start .. 10000) { my ($x,$y) = $path->n_to_xy($n); if (my $p = $seen{$x,$y}) { my $d = $n - $p; # print "$x,$y $p $n diff $d\n"; $diffs{$d} ||= 1; } $seen{$x,$y} = $n; } my $bal = Math::NumSeq::BalancedBinary->new; foreach my $d (keys %diffs) { my $b = Math::BaseCnv::cnv($d,10,2); my $z = $bal->ith($d); $z = Math::BaseCnv::cnv($z,10,2); print "$d bin=$b zeck=$z\n"; } exit 0; } { # Dense Fibonacci Word turns require Math::NumSeq::FibonacciWord; require Image::Base::Text; my $image = Image::Base::Text->new (-width => 79, -height => 40); my $foreground = '*'; my $doubleground = '+'; # require Image::Base::GD; # $image = Image::Base::GD->new (-width => 200, -height => 200); # $image->rectangle (0,0, 200,200, 'black'); # $foreground = 'white'; # $doubleground = 'red'; my $seq = Math::NumSeq::FibonacciWord->new (fibonacci_word_type => 'dense'); my $dx = 1; my $dy = 0; my $x = 1; my $y = 1; my $transpose = 1; my $char = sub { if ($transpose) { if (($image->xy($y,$x)//' ') eq $foreground) { $image->xy ($y,$x, $doubleground); } else { $image->xy ($y,$x, $foreground); } } else { if (($image->xy($x,$y)//' ') eq $foreground) { $image->xy ($x,$y, $doubleground); } else { $image->xy ($x,$y, $foreground); } } }; my $draw = sub { &$char ($x,$y); $x += $dx; $y += $dy; &$char ($x,$y); $x += $dx; $y += $dy; # &$char ($x,$y); # $x += $dx; # $y += $dy; }; my $natural = sub { my ($value) = @_; &$draw(); if ($value == 1) { ($dx,$dy) = (-$dy,$dx); } elsif ($value == 2) { ($dx,$dy) = ($dy,-$dx); } }; my $apply; $apply = sub { # dfw natural, rot +45 my ($i, $value) = $seq->next; &$natural($value); }; # # plus, rot -45 # $apply = sub { # my ($i, $value) = $seq->next; # if ($value == 0) { # # empty # } else { # &$natural($value); # } # }; # $x += 20; # $y += 20; $apply = sub { # standard my ($i, $value) = $seq->next; if ($value == 0) { &$natural(1); &$natural(2); } elsif ($value == 1) { &$natural(1); &$natural(0); } else { &$natural(0); &$natural(2); } }; # $x += 2; # $y += int ($image->get('-height') / 2); # $apply = sub { # # rot pi/5 = 36deg curly # my ($i, $value) = $seq->next; # if ($value == 0) { # &$natural(2); # &$natural(1); # } elsif ($value == 1) { # &$natural(0); # &$natural(2); # } else { # &$natural(1); # &$natural(0); # } # }; # $x += 20; # $y += 20; $apply = sub { # expanded my ($i, $value) = $seq->next; if ($value == 0) { &$natural(0); &$natural(1); &$natural(0); &$natural(2); } elsif ($value == 1) { &$natural(0); &$natural(1); &$natural(0); } else { &$natural(0); &$natural(0); &$natural(2); } }; $apply = sub { # Ron Knott my ($i, $value) = $seq->next; if ($value == 0) { &$natural(1); &$natural(2); } else { &$natural($value); } }; print "$x,$y\n"; for (1 .. 2000) { &$apply(); } # $image->save('/tmp/x.png'); # system('xzgv /tmp/x.png'); my $lines = $image->save_string; my @lines = split /\n/, $lines; $, = "\n"; print reverse @lines; exit 0; } { my @xend = (0,0,1); my @yend = (0,1,1); my $f0 = 1; my $f1 = 2; my $level = 1; my $transpose = 0; my $rot = 0; ### at: "$xend[-1],$xend[-1] for $f1" foreach (1 .. 20) { ($f1,$f0) = ($f1+$f0,$f1); my $six = $level % 6; $transpose ^= 1; my ($x,$y); if (($level % 6) == 0) { $x = $yend[-2]; # T $y = $xend[-2]; } elsif (($level % 6) == 1) { $x = $yend[-2]; # -90 $y = - $xend[-2]; } elsif (($level % 6) == 2) { $x = $xend[-2]; # T -90 $y = - $yend[-2]; } elsif (($level % 6) == 3) { ### T $x = $yend[-2]; # T $y = $xend[-2]; } elsif (($level % 6) == 4) { $x = - $yend[-2]; # +90 $y = $xend[-2]; } elsif (($level % 6) == 5) { $x = - $xend[-2]; # T +90 $y = $yend[-2]; } push @xend, $xend[-1] + $x; push @yend, $yend[-1] + $y; ### new: ($level%6)." add $x,$y for $xend[-1],$yend[-1] for $f1" $level++; } exit 0; } { my @xend = (0, 1); my @yend = (1, 1); my $f0 = 1; my $f1 = 2; foreach (1 .. 10) { { ($f1,$f0) = ($f1+$f0,$f1); my ($nx,$ny) = ($xend[-1] + $yend[-2], $yend[-1] + $xend[-2]); # T push @xend, $nx; push @yend, $ny; ### new 1: "$nx, $ny for $f1" } { ($f1,$f0) = ($f1+$f0,$f1); my ($nx,$ny) = ($xend[-1] + $xend[-2], $yend[-1] - $yend[-2]); # T ... push @xend, $nx; push @yend, $ny; ### new 2: "$nx, $ny for $f1" } { ($f1,$f0) = ($f1+$f0,$f1); my ($nx,$ny) = ($xend[-1] + $yend[-2], $yend[-1] + $xend[-2]); # T push @xend, $nx; push @yend, $ny; ### new 3: "$nx, $ny for $f1" } { ($f1,$f0) = ($f1+$f0,$f1); my ($nx,$ny) = ($xend[-1] + $yend[-2], $yend[-1] + $xend[-2]); # T push @xend, $nx; push @yend, $ny; ### new 1b: "$nx, $ny for $f1" } { ($f1,$f0) = ($f1+$f0,$f1); my ($nx,$ny) = ($xend[-1] - $xend[-2], $yend[-1] + $yend[-2]); # T +90 push @xend, $nx; push @yend, $ny; ### new 2b: "$nx, $ny for $f1" } { ($f1,$f0) = ($f1+$f0,$f1); my ($nx,$ny) = ($xend[-1] + $yend[-2], $yend[-1] + $xend[-2]); # T push @xend, $nx; push @yend, $ny; ### new 1c: "$nx, $ny for $f1" } { ($f1,$f0) = ($f1+$f0,$f1); my ($nx,$ny) = ($xend[-1] + $yend[-2], $yend[-1] - $xend[-2]); # rot -90 push @xend, $nx; push @yend, $ny; ### new 2c: "$nx, $ny for $f1" } } exit 0; } Math-PlanePath-129/devel/bignums.pl0000644000175000017500000001131013774214764015021 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2016, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min'; use POSIX (); $|=1; # uncomment this to run the ### lines use Smart::Comments; { # current List::Util min() and other funcs run overloads, # early versions cast to UV or NV or something use Math::BigInt; my $x = Math::BigInt->new(2)**256 + 1; my $y = Math::BigInt->new(2)**256; $x!=$y or die; $x = MyNum->new(101); $y = MyNum->new(100); print "cmp ",$x <=> $y,"\n"; print "call min\n"; my $m = min($x,$y); ### $m if ($m==$x) { print "is x\n"; } if ($m==$y) { print "is y\n"; } exit 0; { package MyNum; use overload '<=>' => \&spaceship, fallback => 1; sub new { my ($class, $n) = @_; ### MyNum: $n return bless { num => $n }, $class; } sub spaceship { my ($self, $other) = @_; ### spaceship ### $self ### $other return $self->{'num'} <=> $other->{'num'}; } } } { use Math::BigInt; my $b = Math::BigInt->new('463168356949264781694283940034751631414441068130246010011683834461379591405565'); $b->bsqrt; print "$b\n"; my $f = Math::BigRat->new('463168356949264781694283940034751631414441068130246010011683834461379591405565'); ### $f print " = $f\n"; $f->bsqrt; print "$f\n"; my $n = Math::BigRat->new('57896044618658097711785492504343953926805133516280751251460479307672448925696'); $n -= 1; my $r = 8*$n + 5; ### $r print " = $r\n"; $r = sqrt(int($r)); print "$r\n"; exit 0; } { print int(sqrt(24)); exit 0; } { use Math::BigRat; my $f = Math::BigRat->new('-1/2'); ### $f my $int = int($f); ### $f ### $int my $result = ($int == 0); print $result ? "yes\n" : "no\n"; exit 0; } { use Math::BigFloat; Math::BigFloat->accuracy(10); # significant digits print int(Math::BigFloat->new('64.5')),"\n"; exit 0; } # my $inf = 2**99999; # my $nan = $inf/$inf; # print "$inf, $nan","\n"; # print $nan==$nan,"\n"; # print $nan<=>0,"\n"; # print 0<=>$nan,"\n"; { use Math::BigFloat; Math::BigFloat->accuracy(15); my $n = Math::BigFloat->new(1); $n->accuracy(50); $n->batan2(.00000000, 100); print "$n\n"; exit 0; } { use Math::BigFloat; my $n = Math::BigFloat->new('1.234567892345678923456789'); $n->accuracy(15); # my $pi = $n->bpi(undef); # my $pi = Math::BigFloat->bpi; $n = Math::BigFloat->new(1); print "$n\n"; $n->accuracy(10); my $pi = $n->batan2(.0000001); print "$pi\n"; exit 0; } { use Math::BigFloat; # Math::BigFloat->precision(5); # Math::BigFloat->precision(-5); Math::BigFloat->accuracy(13); # my $n = Math::BigFloat->new('123456789.987654321'); my $n = Math::BigFloat->bpi(50); print "$n\n"; exit 0; } { use Math::BigFloat; my $n = Math::BigFloat->new(1234); ### accuracy: $n->accuracy() ### precision: $n->precision() my $global_accuracy = Math::BigFloat->accuracy(); my $global_precision = Math::BigFloat->precision(); ### $global_accuracy ### $global_precision my $global_div_scale = Math::BigFloat->div_scale(); ### $global_div_scale Math::BigFloat->div_scale(500); $global_div_scale = Math::BigFloat->div_scale(); ### $global_div_scale ### div_scale: $n->div_scale $n = Math::BigFloat->new(1234); ### div_scale: $n->div_scale exit 0; } { require Math::Complex; my $c = Math::Complex->new(123); ### $c print $c,"\n"; print $c * 0,"\n";; ### int: int($c) print int($c),"\n";; exit 0; } { require Math::BigRat; use Math::BigFloat; Math::BigFloat->precision(2000); # digits right of decimal point Math::BigFloat->accuracy(2000); { my $x = Math::BigRat->new('1/2') ** 512; print "$x\n"; my $r = sqrt($x); print "$r\n"; print $r*$r,"\n"; # my $r = 8*$x-3; # print "$r\n"; } exit 0; { my $x = Math::BigInt->new(2) ** 128 - 1; print "$x\n"; my $r = 8*$x-3; print "$r\n"; } { my $x = Math::BigRat->new('100000000000000000000'.('0'x200)); $x = $x*$x-1; print "$x\n"; my $r = sqrt($x); print "$r\n"; $r = int($r); print "$r\n"; } } Math-PlanePath-129/devel/tree.pl0000644000175000017500000001403111765112630014302 0ustar gggg#!/usr/bin/perl -w # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.006; use strict; use warnings; use POSIX qw(floor ceil); use List::Util qw(min max); use Module::Load; use App::MathImage::LinesTree; # uncomment this to run the ### lines #use Smart::Comments; { my $path_class; require Math::PlanePath::Hypot; require Math::PlanePath::HypotOctant; require Math::PlanePath::PythagoreanTree; require Math::PlanePath::GreekKeySpiral; require Math::PlanePath::PixelRings; require Math::PlanePath::TriangularHypot; require Math::PlanePath::Diagonals; require Math::PlanePath::SquareArms; require Math::PlanePath::CellularRule54; require Math::PlanePath::SquareReplicate; require Math::PlanePath::KochSquareflakes; require Math::PlanePath::SierpinskiTriangle; require Math::PlanePath::DivisibleColumns; require Math::PlanePath::DiamondSpiral; require Math::PlanePath::DigitGroups; require Math::PlanePath::DekkingCurve; require Math::PlanePath::DekkingStraight; require Math::PlanePath::HilbertCurve; require Math::PlanePath::SierpinskiArrowheadCentres; require Math::PlanePath::SquareSpiral; require Math::PlanePath::PentSpiral; require Math::PlanePath::PentSpiralSkewed; require Math::PlanePath::HexArms; require Math::PlanePath::TriangleSpiral; require Math::PlanePath::TriangleSpiralSkewed; require Math::PlanePath::KochelCurve; require Math::PlanePath::MPeaks; require Math::PlanePath::CincoCurve; require Math::PlanePath::DiagonalRationals; require Math::PlanePath::FactorRationals; require Math::PlanePath::VogelFloret; require Math::PlanePath::CellularRule; require Math::PlanePath::ComplexPlus; require Math::PlanePath::AnvilSpiral; require Math::PlanePath::CellularRule57; require Math::PlanePath::CretanLabyrinth; require Math::PlanePath::PeanoHalf; require Math::PlanePath::StaircaseAlternating; require Math::PlanePath::SierpinskiCurveStair; require Math::PlanePath::AztecDiamondRings; require Math::PlanePath::PyramidRows; require Math::PlanePath::MultipleRings; require Math::PlanePath::SacksSpiral; require Math::PlanePath::TheodorusSpiral; require Math::PlanePath::FilledRings; require Math::PlanePath::ImaginaryHalf; require Math::PlanePath::MooreSpiral; require Math::PlanePath::QuintetSide; require Math::PlanePath::PeanoRounded; require Math::PlanePath::GosperSide; $path_class = 'Math::PlanePath::ComplexMinus'; $path_class = 'Math::PlanePath::QuadricCurve'; $path_class = 'Math::PlanePath::QuintetReplicate'; $path_class = 'Math::PlanePath::SierpinskiCurve'; $path_class = 'Math::PlanePath::LTiling'; $path_class = 'Math::PlanePath::ImaginaryHalf'; $path_class = 'Math::PlanePath::ImaginaryBase'; $path_class = 'Math::PlanePath::TerdragonCurve'; $path_class = 'Math::PlanePath::TerdragonMidpoint'; $path_class = 'Math::PlanePath::TerdragonRounded'; $path_class = 'Math::PlanePath::DragonCurve'; $path_class = 'Math::PlanePath::SierpinskiArrowhead'; $path_class = 'Math::PlanePath::DragonMidpoint'; $path_class = 'Math::PlanePath::QuintetCentres'; $path_class = 'Math::PlanePath::QuintetCurve'; $path_class = 'Math::PlanePath::GosperReplicate'; $path_class = 'Math::PlanePath::HIndexing'; $path_class = 'Math::PlanePath::CornerReplicate'; $path_class = 'Math::PlanePath::WunderlichMeander'; $path_class = 'Math::PlanePath::ComplexRevolving'; $path_class = 'Math::PlanePath::AlternatePaper'; $path_class = 'Math::PlanePath::WunderlichSerpentine'; $path_class = 'Math::PlanePath::PeanoCurve'; $path_class = 'Math::PlanePath::Flowsnake'; $path_class = 'Math::PlanePath::FlowsnakeCentres'; $path_class = 'Math::PlanePath::FractionsTree'; $path_class = 'Math::PlanePath::RationalsTree'; $path_class = 'Math::PlanePath::GrayCode'; $path_class = 'Math::PlanePath::CubicBase'; $path_class = 'Math::PlanePath::R5DragonCurve'; $path_class = 'Math::PlanePath::R5DragonMidpoint'; $path_class = 'Math::PlanePath::HilbertSpiral'; $path_class = 'Math::PlanePath::BetaOmega'; $path_class = 'Math::PlanePath::AR2W2Curve'; $path_class = 'Math::PlanePath::CCurve'; $path_class = 'Math::PlanePath::GcdRationals'; $path_class = 'Math::PlanePath::DiagonalsOctant'; $path_class = 'Math::PlanePath::KochSnowflakes'; $path_class = 'Math::PlanePath::GosperIslands'; $path_class = 'Math::PlanePath::Corner'; $path_class = 'Math::PlanePath::KochCurve'; $path_class = 'Math::PlanePath::QuadricIslands'; $path_class = 'Math::PlanePath::KochPeaks'; $path_class = 'Math::PlanePath::UlamWarburton'; $path_class = 'Math::PlanePath::DragonRounded'; Module::Load::load($path_class); my $path = $path_class->new ( ); ### $path my ($prev_x, $prev_y); my %seen; my $n_start = $path->n_start; my $arms_count = $path->arms_count; print "n_start $n_start arms_count $arms_count ",ref($path),"\n"; for (my $i = $n_start+0; $i <= 32; $i+=1) { #for (my $i = $n_start; $i <= $n_start + 800000; $i=POSIX::ceil($i*2.01+1)) { my @n_children = $path->MathImage__tree_n_children($i); my $n_children = join(', ', @n_children); my $iwidth = ($i == int($i) ? 0 : 2); printf "%.*f %s\n", $iwidth,$i, $n_children; foreach my $n_child (@n_children) { my $n_parent = $path->MathImage__tree_n_parent($n_child); if (! defined $n_parent || $n_parent != $i) { $n_parent //= 'undef'; print " oops child=$n_child, parent=$n_parent\n"; } } } exit 0; } Math-PlanePath-129/devel/multiple-rings.pl0000644000175000017500000003552012601460724016324 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2015 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::Libm 'hypot'; use Math::Trig 'pi','tan'; use Math::PlanePath::MultipleRings; # uncomment this to run the ### lines use Smart::Comments; { my $path = Math::PlanePath::MultipleRings->new (step => 8, ring_shape => 'polygon'); my $n = 10; my ($prev_dx,$prev_dy) = $path->n_to_dxdy($n - 1) or die; my ($dx,$dy) = $path->n_to_dxdy($n) or die; my $LSR = $dy*$prev_dx - $dx*$prev_dy; ### $LSR if (abs($LSR) < 1e-10) { $LSR = 0; } $LSR = ($LSR <=> 0); # 1,undef,-1 print "path_n_to_LSR dxdy $prev_dx,$prev_dy then $dx,$dy is LSR=$LSR\n"; exit 0; } { require Math::NumSeq::PlanePathDelta; foreach my $step (3 .. 10) { print "$step\n"; my $path = Math::PlanePath::MultipleRings->new (step => $step, ring_shape => 'polygon'); foreach my $n (0 .. $step-1) { my ($dx,$dy) = $path->n_to_dxdy($n+$path->n_start); my $dir4 = Math::NumSeq::PlanePathDelta::_delta_func_Dir4($dx,$dy); printf "%2d %6.3f,%6.3f %6.3f\n", $n, $dx,$dy, $dir4; } # my $m = int((3*$step-3)/4); # $m = int((2*$step-4)/4); my $m = 2*$step - 2 + ($step%2); my ($cx,$cy) = Math::PlanePath::MultipleRings::_circlefrac_to_xy (1, $m, 2*$step, pi()); # $cx = -$cx; my $dir4 = Math::NumSeq::PlanePathDelta::_delta_func_Dir4($cx,$cy); print "$m $cx, $cy $dir4\n"; print "\n"; } exit 0; } { foreach my $step (0 .. 10) { my $path = Math::PlanePath::MultipleRings->new (step => $step, ring_shape => 'polygon'); for (my $n = $path->n_start; $n < 10; $n++) { my ($x, $y) = $path->n_to_xy($n); my $g = gcd($x,$y); printf "%2d %6.3f,%6.3f %.8g\n", $n, $x,$y, $g; } print "\n"; } use POSIX 'fmod'; sub gcd { my ($x,$y) = @_; $x = abs($x); $y = abs($y); unless ($x > 0) { return $y; } # if (is_infinite($x)) { return $x; } # if (is_infinite($y)) { return $y; } if ($y > $x) { $y = fmod($y,$x); } for (;;) { ### gcd at: "x=$x y=$y" if ($y == 0) { return $x; # gcd(x,0)=x } if ($y < 0.0001) { return 0.00001; } ($x,$y) = ($y, fmod($x,$y)); } } exit 0; } { require Math::BigFloat; # Math::BigFloat->precision(-3); my $n = Math::BigFloat->new(4); # $n->accuracy(5); $n->precision(-3); my $pi = Math::PlanePath::MultipleRings::_pi($n); print "$pi\n"; exit 0; } { my $pi = pi(); my $offset = 0.0; foreach my $step (3,4,5,6,7,8) { my $path = Math::PlanePath::MultipleRings->new (step => $step, ring_shape => 'polygon'); my $d = 1; my $n0base = Math::PlanePath::MultipleRings::_d_to_n0base($path,$d); my $next_n0base = Math::PlanePath::MultipleRings::_d_to_n0base($path,$d+10); my ($pbase, $pinc); if ($step > 6) { $pbase = 0; $pinc = Math::PlanePath::MultipleRings::_numsides_to_r($step,$pi); } else { $pbase = Math::PlanePath::MultipleRings::_numsides_to_r($step,$pi); $pinc = 1/cos($pi/$step); } print "step=$step pbase=$pbase pinc=$pinc\n"; for (my $n = $n0base+$path->n_start; $n < $next_n0base; $n += 1.0) { my ($x, $y) = $path->n_to_xy($n); my $revn = $path->xy_to_n($x-$offset,$y) // 'undef'; my $r = hypot ($x, $y); my $theta_frac = Math::PlanePath::MultipleRings::_xy_to_angle_frac($x,$y); $theta_frac -= int($theta_frac*$step) / $step; # modulo 1/step my $alpha = 2*$pi/$step; my $theta = 2*$pi * $theta_frac; ### $r ### x=r*cos(theta): $r*cos($theta) ### y=r*sin(theta): $r*sin($theta) my $p = $r*cos($theta) + $r*sin($theta) * sin($alpha/2)/cos($alpha/2); $d = ($p - $pbase) / $pinc + 1; printf "%5.1f thetafrac=%.4f r=%.4f p=%.4f d=%.2f revn=%s\n", $n, $theta_frac, $r, $p, $d, $revn; if ($n==int($n) && (! defined $revn || $revn != $n)) { print "\n"; die "oops, revn=$revn != n=$n"; } } print "\n"; } exit 0; } { # dir_minimum_dxdy() position require Math::PlanePath::MultipleRings; require Math::NumSeq::PlanePathDelta; foreach my $step (3 .. 100) { my $path = Math::PlanePath::MultipleRings->new (step => $step, ring_shape => 'polygon'); my $min_dir4 = 99; my $min_n = 1; my $max_dir4 = 0; my $max_n = 1; foreach my $n (1 .. $step) { my ($dx,$dy) = $path->n_to_dxdy($n); my $dir4 = Math::NumSeq::PlanePathDelta::_delta_func_Dir4($dx,$dy); if ($dir4 > $max_dir4) { $max_dir4 = $dir4; $max_n = $n; } if ($dir4 < $min_dir4) { $min_dir4 = $dir4; $min_n = $n; } } my $min_diff = $step - $min_n; my $max_diff = $step - $max_n; print "$step min N=$min_n $min_diff max N=$max_n $max_diff\n"; } exit 0; } { # Dir4 minimum, maximum require Math::PlanePath::MultipleRings; foreach my $step (3 .. 20) { my $path = Math::PlanePath::MultipleRings->new (step => $step, ring_shape => 'polygon'); my $min = $path->dir4_minimum(); my $max = $path->dir4_maximum(); my $den = 2*$step; $min *= $den; $max *= $den; my $md = 4*$den - $max; print "$step $min $max($md) / $den\n"; } exit 0; } { # polygon pack my $poly = 5; # w/c = tan(angle/2) # w = c*tan(angle/2) # (c/row)^2 + (c-prev)^2 = 1 # 1/row^2 * c^2 + (c^2 - 2cp + p^2) = 1 # 1/row^2 * c^2 + c^2 - 2cp + p^2 - 1 = 0 # (1/row^2 + 1) * c^2 - 2p*c + (p^2 - 1) = 0 # A = (1 + 1/row^2) # B = -2p # C = (p^2-1) # c = (2p + sqrt(4p^2 - 4*(p^2+1)*(1 + 1/row^2))) / (2*(1 + 1/row^2)) # d = c-prev # c = d+prev # ((d+prev)/row)^2 + d^2 = 1 # (d^2+2dp+p^2)/row^2 + d^2 = 1 # d^2/row^2 + 2p/row^2 * d + p^2/row^2 + d^2 - 1 = 0 # (1+1/row^2)*d^2 + 2p/row^2 * d + (p^2/row^2 - 1) = 0 # A = (1+1/row^2) # B = 2p/row^2 # C = (p^2/row^2 - 1) my $angle_frac = 1/$poly; my $angle_degrees = $angle_frac * 360; my $angle_radians = 2*pi * $angle_frac; my $slope = 1/cos($angle_radians/2); # e = slope*c my $tan = tan($angle_radians/2); print "angle $angle_degrees slope $slope tan=$tan\n"; my @c = (0); my @e = (0); my @points_on_row; my $delta_minimum = 1/$slope; my $delta_minimum_hypot = hypot($delta_minimum, $delta_minimum*$tan); print "delta_minimum = $delta_minimum (hypot $delta_minimum_hypot)\n"; # tan a/2 = 0.5/c # c = 0.5 / tan(a/2) my $c = 0.5 / tan($angle_radians/2); my $e = $c * $slope; $c[1] = $c; $e[1] = $e; my $w = $c*$tan; print "row=1 initial c=$c e=$e w=$w\n"; { my $delta_equil = sqrt(3)/2; my $delta_side = cos($angle_radians/2); print " delta equil=$delta_equil side=$delta_side\n"; if ($delta_equil > $delta_side) { $c += $delta_equil; $w = $c*$tan; print "row=2 equilateral to c=$c w=$w\n"; } else { $c += $delta_side; $w = $c*$tan; print "row=2 side to c=$c w=$w\n"; } } $e = $c * $slope; $c[2] = $c; $e[2] = $e; # for (my $row = 3; $row < 27; $row += 2) { # my $p = $c; # # # # (p - (row-2)/row * c)^2 + (c-p)^2 = 1 # # # p^2 - 2*rf*p*c + rf^2*c^2 + c^2 - 2cp + p^2 - 1 = 0 # # # rf^2*c^2 + c^2 - 2*rf*p*c - 2*p*c + p^2 + p^2 - 1 = 0 # # # (rf^2 + 1)*c^2 + (- 2*rf*p - 2*p)*c + (p^2 + p^2 - 1) = 0 # # # (rf^2 + 1)*c^2 + -2*p*(rf+1)*c + (p^2 + p^2 - 1) = 0 # # # # # my $rf = ($row-2)/$row; # # my $A = ($rf^2 + 1); # # my $B = -2*$rf*$p - 2*$p; # # my $C = (2*$p**2 - 1); # # print "A=$A B=$B C=$C\n"; # # my $next_c; # # my $delta; # # if ($B*$B - 4*$A*$C >= 0) { # # $next_c = (-$B + sqrt($B*$B - 4*$A*$C))/(2*$A); # # $delta = $next_c - $c; # # } else { # # $delta = .7; # # $next_c = $c + $delta; # # # # my $side = ($c - $rf*$next_c); # # my $h = hypot($side, $delta); # # print " h=$h\n"; # # } # # # delta of i=0 j=1 # # # # (p - (row-2)/row * c)^2 + d^2 = 1 # # (p - rf*(p+d))^2 + d^2 = 1 # # (p - rf*p - rf*d))^2 + d^2 = 1 # # (-p + rf*p + rf*d))^2 + d^2 = 1 # # (rf*d -p + rf*p)^2 + d^2 = 1 # # (rf*d + (rf-1)p)^2 + d^2 = 1 # # rf^2*d^2 + 2*rf*(rf-1)*p * d + (rf-1)^2*p^2 + d^2 - 1 = 0 # # (rf^2+1)*d^2 + rf*(rf-1)*p * d + ((rf-1)^2*p^2 - 1) = 0 # # # my $rf = ($row-2)/$row; # $rf = ($row+1 -2)/($row+1); # my $A = $rf**2 + 1; # my $B = 2*$rf*($rf-1)*$p; # my $C = ($rf-1)**2 * $p**2 - 1; # my $delta; # if ($B*$B - 4*$A*$C >= 0) { # $delta = (-$B + sqrt($B*$B - 4*$A*$C))/(2*$A); # } else { # print "discrim: ",$B*$B - 4*$A*$C,"\n"; # $delta = 0; # } # # # delta of i=0 j=0 # # (c - p)^2 + d^2 = 1 # # # if ($delta < $delta_minimum+.0) { # print " side minimum $delta < $delta_minimum\n"; # $delta = $delta_minimum; # } # my $next_c = $delta + $c; # # # # my $A = (1 + ($tan/$row)**2); # # my $B = -2*$c; # # my $C = ($c**2 - 1); # # my $next_c = (-$B + sqrt($B*$B - 4*$A*$C))/(2*$A); # # my $delta = $next_c - $c; # # # # $A = (1 + ($tan/$row)**2); # # $B = 2*$c/$row**2; # # $C = ($c**2/$row**2 - 1); # # my $delta_2 = 0; # (-$B + sqrt($B*$B - 4*$A*$C))/(2*$A); # # printf "row=$row delta=%.5f=%.5f next_c=%.5f\n", $delta, $delta_2, $next_c; # printf "row=$row delta=%.5f next_c=%.5f\n", $delta, $next_c; # # $c[$row] = $c + $delta; # $c[$row+1] = $c + 2*$delta; # # $e[$row] = $c[$row] * $slope; # $e[$row+1] = $c[$row+1] * $slope; # # $c += 2*$delta; # } for (my $row = 3; $row < 138; $row++) { my $p = $c; # # (p - (row-2)/row * c)^2 + (c-p)^2 = 1 # # p^2 - 2*rf*p*c + rf^2*c^2 + c^2 - 2cp + p^2 - 1 = 0 # # rf^2*c^2 + c^2 - 2*rf*p*c - 2*p*c + p^2 + p^2 - 1 = 0 # # (rf^2 + 1)*c^2 + (- 2*rf*p - 2*p)*c + (p^2 + p^2 - 1) = 0 # # (rf^2 + 1)*c^2 + -2*p*(rf+1)*c + (p^2 + p^2 - 1) = 0 # # # my $rf = ($row-2)/$row; # my $A = ($rf^2 + 1); # my $B = -2*$rf*$p - 2*$p; # my $C = (2*$p**2 - 1); # print "A=$A B=$B C=$C\n"; # my $next_c; # my $delta; # if ($B*$B - 4*$A*$C >= 0) { # $next_c = (-$B + sqrt($B*$B - 4*$A*$C))/(2*$A); # $delta = $next_c - $c; # } else { # $delta = .7; # $next_c = $c + $delta; # # my $side = ($c - $rf*$next_c); # my $h = hypot($side, $delta); # print " h=$h\n"; # } # delta of i=0 j=1 # # (p*tan - (row-2)/row * tan*c)^2 + d^2 = 1 # tt*(p - rf*(p+d))^2 + d^2 = 1 # tt*(p - rf*p - rf*d)^2 + d^2 = 1 # tt*(-p + rf*p + rf*d)^2 + d^2-1 = 0 # tt*(rf*d -p + rf*p)^2 + d^2-1 = 0 # tt*(rf*d + (rf-1)p)^2 + d^2-1 = 0 # tt*rf^2*d^2 + tt*2*rf*(rf-1)*p * d + tt*(rf-1)^2*p^2 + d^2 - 1 = 0 # (tt*rf^2+1)*d^2 + tt*rf*(rf-1)*p * d + (tt*(rf-1)^2*p^2 - 1) = 0 # # print " rf ",($row-2),"/$row\n"; my $rf = ($row-2)/($row); my $A = $tan**2 * $rf**2 + 1; my $B = $tan**2 * 2*$rf*($rf-1)*$p; my $C = $tan**2 * ($rf-1)**2 * $p**2 - 1; my $delta; if ($B*$B - 4*$A*$C >= 0) { $delta = (-$B + sqrt($B*$B - 4*$A*$C))/(2*$A); my $next_c = $delta + $c; my $pw = $p * $tan; my $next_w = $next_c * $tan; my $rem = $pw - $next_w*($row-2)/$row; my $h = hypot ($delta, $rem); # print " h^2=$h pw=$pw nw=$next_w rem=$rem\n"; } else { print "discrim: ",$B*$B - 4*$A*$C,"\n"; my $w = $p*$tan / $row; print " at d=0 w=$w\n"; $delta = 0; } # delta of i=0 j=0 # (c - p)^2 + d^2 = 1 # if ($delta < $delta_minimum+.0) { print " side minimum $delta < $delta_minimum\n"; $delta = $delta_minimum; } my $next_c = $delta + $c; printf "row=$row delta=%.5f next_c=%.5f\n", $delta, $next_c; $c += $delta; $c[$row] = $c; $e[$row] = $c[$row] * $slope; } # print "c ",join(', ',@c),"\n"; # print "e ",join(', ',@e),"\n"; my (@x,@y); foreach my $row (1 .. $#c) { my $x1 = $e[$row]; my $y1 = 0; my ($x2,$y2) = Math::Trig::cylindrical_to_cartesian($e[$row], $angle_radians, 0); my $dx = $x2-$x1; my $dy = $y2-$y1; foreach my $p (0 .. $row) { $x[$row][$p] = $x1 + $dx*$p/$row; $y[$row][$p] = $y1 + $dy*$p/$row; } # print "row=$row x ",join(', ',@{$x[$row]}),"\n"; } foreach my $row (1 .. $#c-1) { print "\n"; my $min_dist = 9999; my $min_dist_at_i = -1; my $min_dist_at_j = -1; foreach my $i (0 .. $row) { foreach my $j (0 .. $row+1) { my $dist = hypot($x[$row][$i] - $x[$row+1][$j], $y[$row][$i] - $y[$row+1][$j]); if ($dist < $min_dist) { # print " dist=$dist at i=$i j=$j\n"; $min_dist = $dist; $min_dist_at_i = $i; $min_dist_at_j = $j; } } } if ($min_dist_at_i > $row/2) { $min_dist_at_i = $row - $min_dist_at_i; $min_dist_at_j = $row+1 - $min_dist_at_j; } print "row=$row min_dist=$min_dist at i=$min_dist_at_i j=$min_dist_at_j\n"; my $zdist = hypot($x[$row][0] - $x[$row+1][0], $y[$row][0] - $y[$row+1][0]); my $odist = hypot($x[$row][0] - $x[$row+1][1], $y[$row][0] - $y[$row+1][1]); print " zdist=$zdist odist=$odist\n"; } open OUT, '>', '/tmp/multiple-rings.tmp' or die; foreach my $row (1 .. $#c-1) { foreach my $i (0 .. $row) { print OUT "$x[$row][$i], $y[$row][$i]\n"; } } close OUT or die; system ('math-image --wx --path=File,filename=/tmp/multiple-rings.tmp --all --scale=25 --figure=ring'); exit 0; } { # max dx require Math::PlanePath::MultipleRings; my $path = Math::PlanePath::MultipleRings->new (step => 37); my $n = $path->n_start; my $dx_max = 0; my ($prev_x, $prev_y) = $path->n_to_xy($n++); foreach (1 .. 1000000) { my ($x, $y) = $path->n_to_xy($n++); my $dx = $y - $prev_y; if ($dx > $dx_max) { print "$n $dx\n"; $dx_max = $dx; } $prev_x = $x; $prev_y = $y; } exit 0; } Math-PlanePath-129/devel/gray.pl0000644000175000017500000002704213662151350014313 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2015, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::BaseCnv 'cnv'; use Math::Prime::XS 0.23 'is_prime'; # version 0.23 fix for 1928099 use Math::PlanePath::GrayCode; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; $|=1; # uncomment this to run the ### lines # use Smart::Comments; { # binary Gray twice cf base 4 foreach my $n (0 .. 16) { my $b = to_gray_reflected($n,2); my $b2 = to_gray_reflected($b,2); my $fr = to_gray_reflected($n,4); my $fm = to_gray_modular($n,4); printf "%5d %5d %5d\n", cnv($b2,10,4), cnv($fr,10,4), cnv($fm,10,4); } exit 0; } { # F. J. Budden and T. M. Sporton, "Some Unsolved Problems on Binary Codes", # Mathematics in School, volume 11, number 3, May 1982, pages 26-28. # http://www.jstor.org/stable/30213735 # 14,32,50,114 # 2* of # A295921 (n+2) * 2^(n-2) + 1 # maximal cliques in folded cube graph n # folded cube = merge antipodals # # 6,5 10mins len 50 my $N = 6; my $MD = 5; my @last = map {[-99]} 0 .. $N; ### @last my @seq = (-1); my @values = (0); my %values = (0 => 1); my $limit = 2**$N; my @flip = map {1<<$_} 0 .. $N-1; ### @flip my $new_value; my @max_seq; my @max_values; for (;;) { ### at: join('',@seq).' last '.join(',',map {$_->[-1]} @last).' values '.join(',',@values) if (0) { foreach my $i (0 .. $N-1) { my $s1 = join(',',@{$last[$i]}); my $s2 = join(',',-99,grep {$seq[$_]==$i} 0 .. $#seq-1); unless ($s1 eq $s2) { print "i=$i\n"; print " $s1\n"; print " $s2\n"; die; } } my @stepped_values = (0); foreach my $i (0 .. $#seq-1) { push @stepped_values, $stepped_values[-1] ^ $flip[$seq[$i]]; } my $s1 = join(',',@stepped_values); my $s2 = join(',',@values); unless ($s1 eq $s2) { print " $s1\n"; print " $s2\n"; die; } } my $this = ++$seq[-1]; if ($this >= $N) { ### backtrack ... pop @seq; last unless @seq; pop @{$last[$seq[-1]]}; undef $values{pop @values}; next; } my $dist = scalar(@seq) - $last[$this]->[-1]; ### $dist if ($dist > $MD && !$values{$new_value = $values[-1] ^ $flip[$this]}) { ### descend to: $this $values{$new_value} = 1; push @values, $new_value; if (@seq > @max_seq) { @max_seq = @seq; @max_values = @values; print "new high ",scalar(@max_values),"\n"; } if (@values == $limit) { print "found $limit\n"; print " ",join('',@seq),"\n"; last; } push @{$last[$this]}, $#seq; push @seq, -1; } } my $max = scalar(@max_values); print "max $max seq ",join('',@max_seq),"\n"; foreach my $i (0 .. $#max_values) { printf " %0*b %s\n", $N, $max_values[$i], $max_seq[$i] // '[none]'; } exit 0; } { my $from = from_gray(2**8-1,2); require Math::BaseCnv; print Math::BaseCnv::cnv($from,10,2),"\n"; exit 0; } { # turn Left # 1,1,0,0,1,1,1, # left at N=1,2 then 180 at N=3 # 7to8 # N=2,3,4 same Y # parity of A065883 require Math::NumSeq::PlanePathTurn; my $planepath; $planepath = "GrayCode"; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => $planepath, turn_type => 'LSR'); my $path = $seq->{'planepath_object'}; for (1 .. 60) { my ($n, $turn) = $seq->next; # next if $value; my ($x,$y) = $path->n_to_xy($n); my ($dx,$dy) = $path->n_to_dxdy($n); my $calc = calc_left_turn($n); print "$n $x,$y $turn $calc dxdy=$dx,$dy\n"; # printf "%d,", $value; # printf " i-1 gray %6b\n",to_gray($n-1,2); # printf " i gray %6b\n",to_gray($n,2); # printf " i+1 gray %6b\n",to_gray($n+1,2); } print "\n"; exit 0; sub calc_left_turn { my ($n) = @_; return count_low_0_bits(($n+1)>>1) % 2 ? 0 : 1; } sub count_low_1_bits { my ($n) = @_; my $count = 0; while ($n % 2) { $count++; $n = int($n/2); } return $count; } sub count_low_0_bits { my ($n) = @_; if ($n == 0) { die; } my $count = 0; until ($n % 2) { $count++; $n /= 2; } return $count; } } { # cf GRS require Math::NumSeq::GolayRudinShapiro; require Math::NumSeq::DigitCount; my $seq = Math::NumSeq::GolayRudinShapiro->new; my $dc = Math::NumSeq::DigitCount->new (radix => 2); for (my $n = 0; $n < 2000; $n++) { my $grs = $seq->ith($n); my $gray = from_binary_gray($n); my $gbit = $dc->ith($gray) & 1; printf "%3d %2d %2d\n", $n, $grs, $gbit; } exit 0; } { # X,Y,Diagonal values foreach my $apply_type ('TsF','Ts','sT','sF') { print "$apply_type\n"; my $path = Math::PlanePath::GrayCode->new (apply_type => $apply_type); foreach my $i (0 .. 40) { my $nx = $path->xy_to_n(0,$i); printf "%d %d %b\n", $i, $nx, $nx; } } exit 0; } { # path sameness require Tie::IxHash; my @apply_types = ('TsF','Ts','Fs','FsT','sT','sF'); my @gray_types = ('reflected', 'modular', ); for (my $radix = 2; $radix <= 10; $radix++) { print "radix $radix\n"; my %xy; tie %xy, 'Tie::IxHash'; foreach my $apply_type (@apply_types) { foreach my $gray_type (@gray_types) { my $path = Math::PlanePath::GrayCode->new (radix => $radix, apply_type => $apply_type, gray_type => $gray_type); my $str = ''; foreach my $n (0 .. $radix ** 4) { my ($x,$y) = $path->n_to_xy($n); $str .= " $x,$y"; } push @{$xy{$str}}, "$apply_type,$gray_type"; } } my @distinct; foreach my $aref (values %xy) { if (@$aref > 1) { print " same: ",join(' ',@$aref),"\n"; } else { push @distinct, @$aref; } } print " distinct: ",join(' ',@distinct),"\n"; } exit 0; } { # to_gray() same as from_gray() in some radices for (my $radix = 2; $radix < 20; $radix++) { my $result = "same"; for (my $n = 0; $n < 2000; $n++) { my $to = to_gray($n,$radix); my $from = from_gray($n,$radix); if ($to != $from) { $result = "different"; last; } } print "radix=$radix to/from $result\n"; } exit 0; sub to_gray { my ($n, $radix) = @_; my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_to_gray_reflected($digits,$radix); return digit_join_lowtohigh($digits,$radix); } sub from_gray { my ($n, $radix) = @_; my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_from_gray_reflected($digits,$radix); return digit_join_lowtohigh($digits,$radix); } } { for (my $n = 0; $n < 2000; $n++) { next unless is_prime($n); my $gray = to_binary_gray($n); next unless is_prime($gray); printf "%3d %3d\n", $n, $gray; } exit 0; sub to_binary_gray { my ($n) = @_; my $digits = [ digit_split_lowtohigh($n,2) ]; Math::PlanePath::GrayCode::_digits_to_gray_reflected($digits,2); return digit_join_lowtohigh($digits,2); } } { my $radix = 10; my $num = 3; my $width = length($radix)*2*$num; foreach my $i (0 .. $radix ** $num - 1) { my $i_digits = [ digit_split_lowtohigh($i,$radix) ]; my @gray_digits = @$i_digits; my $gray_digits = \@gray_digits; Math::PlanePath::GrayCode::_digits_to_gray_reflected($gray_digits,$radix); # Math::PlanePath::GrayCode::_digits_to_gray_modular($gray_digits,$radix); my @rev_digits = @gray_digits; my $rev_digits = \@rev_digits; Math::PlanePath::GrayCode::_digits_from_gray_reflected($rev_digits,$radix); # Math::PlanePath::GrayCode::_digits_from_gray_modular($rev_digits,$radix); my $i_str = join(',', reverse @$i_digits); my $gray_str = join(',', reverse @$gray_digits); my $rev_str = join(',', reverse @$rev_digits); my $diff = ($i_str eq $rev_str ? '' : ' ***'); printf "%*s %*s %*s%s\n", $width,$i_str, $width,$gray_str, $width,$rev_str, $diff; } exit 0; } { foreach my $i (0 .. 32) { printf "%05b %05b\n", $i, from_binary_gray($i); } sub from_binary_gray { my ($n) = @_; my @digits; while ($n) { push @digits, $n & 1; $n >>= 1; } my $xor = 0; my $ret = 0; while (@digits) { my $digit = pop @digits; $ret <<= 1; $ret |= $digit^$xor; $xor ^= $digit; } return $ret; } exit 0; } # integer modular # 000 000 # 001 001 # 002 002 # 010 012 # 011 010 # 012 011 # 020 021 # 021 022 # 022 020 # integer reflected # 000 000 # 001 001 # 002 002 # 010 012 # 011 011 # 012 010 # 020 020 # 021 021 # 022 022 # 100 122 # 101 121 # 102 120 # 110 110 # 111 111 # 112 112 # 120 102 # 121 101 # 122 100 # # 200 200 # A128173 ternary reverse # 0, 000 # 1, 001 # 2, 002 # 5, 012 # 4, 011 # 3, 010 # 6, 020 # 7, 021 # 8, 022 # 17, 122 # 16, 121 # 15, 120 # 12, 110 # 13, 111 # 14, 112 # 11, 102 # 10, 101 # 9, 100 # 18, 200 # A105530 ternary cyclic # 0, 000 # 1, 001 # 2, 002 # 5, 012 # 3, 010 # 4, 011 # 7, 021 # 8, 022 # 6, 020 # 15, 120 # 16, 121 # 17, 122 # 11, 102 # 9, 100 # 10, 101 # 13, 111 # 14, 112 # 12, 110 # 21, 210 # 22, 211 # sub _to_gray { my ($n) = @_; ### _to_gray(): $n return ($n >> 1) ^ $n; } sub _from_gray { my ($n) = @_; ### _from_gray(): $n my $shift = 1; for (;;) { my $xor = ($n >> $shift) || return $n; $n ^= $xor; $shift *= 2; } # my @digits; # while ($n) { # push @digits, $n & 1; # $n >>= 1; # } # my $xor = 0; # my $ret = 0; # while (@digits) { # my $digit = pop @digits; # $ret <<= 1; # $ret |= $digit^$xor; # $xor ^= $digit; # } # return $ret; } sub to_gray_reflected { my ($n, $radix) = @_; my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_to_gray_reflected($digits,$radix); return digit_join_lowtohigh($digits,$radix); } sub from_gray_reflected { my ($n, $radix) = @_; my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_from_gray_reflected($digits,$radix); return digit_join_lowtohigh($digits,$radix); } sub to_gray_modular { my ($n, $radix) = @_; my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_to_gray_modular($digits,$radix); return digit_join_lowtohigh($digits,$radix); } sub from_gray_modular { my ($n, $radix) = @_; my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_from_gray_modular($digits,$radix); return digit_join_lowtohigh($digits,$radix); } Math-PlanePath-129/devel/quadric.pl0000644000175000017500000001160013147425677015010 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2017 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; { # QuadricCurve turn sequence require Math::NumSeq::PlanePathTurn; { # turn # not in OEIS: 1,-1,-1,0,1,1,-1,1,1,-1,-1,0,1,1,-1,-1,1,-1,-1,0,1,1,-1,-1,1,-1,-1,0,1,1,-1,0,1,-1,-1,0,1,1,-1,1,1,-1,-1,0,1,1,-1,1,1,-1, # not A168181 = abs values non-multiples of 8 are L or R # nor other abs matches my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'QuadricCurve', turn_type => 'LSR'); foreach (1 .. 50) { my ($i,$value) = $seq->next; print "$value,"; } print "\n"; } { # Left = lowest non-0 is 1,5,6 # not in OEIS: 1,0,0,0,1,1,0,1,1,0,0,0,1,1,0,0,1,0,0,0,1,1,0,0,1,0,0,0,1,1,0,0,1,0,0,0,1,1,0,1,1,0,0,0,1,1,0,1,1,0 my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'QuadricCurve', turn_type => 'Left'); foreach (1 .. 50) { my ($i,$value) = $seq->next; print "$value,"; } print "\n"; } exit 0; } { # QuadricIslands X negative axis N increasing require Math::PlanePath::QuadricIslands; my $path = Math::PlanePath::QuadricIslands->new; my $prev_n = 0; for (my $x = 0; $x > -1000000000; $x--) { my $n = $path->xy_to_n($x,0) // next; if ($n < $prev_n) { print "decrease N at X=$x N=$n prev_N=$prev_n\n"; } $prev_n = $n; } exit 0; } { # min/max for level require Math::PlanePath::QuadricIslands; my $path = Math::PlanePath::QuadricIslands->new; my $prev_min = 1; my $prev_max = 1; for (my $level = 1; $level < 25; $level++) { my $n_start = (4*8**$level + 3)/7; my $n_end = (4*8**($level+1) + 3)/7 - 1; $n_end = $n_start + 8**$level; my $min_width = $n_start ** 2; my $min_pos = ''; my $max_width = 0; my $max_pos = ''; print "level $level n=$n_start .. $n_end\n"; foreach my $n ($n_start .. $n_end) { my ($x,$y) = $path->n_to_xy($n); #my $w = -$y-$x/2; my $w = abs($y); if ($w > $max_width) { $max_width = $w; $max_pos = "$x,$y n=$n (oct ".sprintf('%o',$n).")"; } if ($w < $min_width) { $min_width = $w; $min_pos = "$x,$y n=$n (oct ".sprintf('%o',$n).")"; } } { my $factor = $max_width / $prev_max; print " max width $max_width oct ".sprintf('%o',$max_width)." at $max_pos factor $factor\n"; } { my $factor = $min_width / ($prev_min||1); print " min width $min_width oct ".sprintf('%o',$min_width)." at $min_pos factor $factor\n"; } { my $formula = (2*4**($level-1) + 1) / 3; print " cf min formula $formula\n"; } { my $formula = (10*4**($level-1) - 1) / 3; print " cf max formula $formula\n"; } $prev_max = $max_width; $prev_min = $min_width; } exit 0; } { # min/max for level require Math::PlanePath::QuadricCurve; my $path = Math::PlanePath::QuadricCurve->new; my $prev_min = 1; my $prev_max = 1; for (my $level = 1; $level < 25; $level++) { my $n_start = 8**($level-1); my $n_end = 8**$level; my $max_width = 0; my $max_pos = ''; my $min_width; my $min_pos = ''; print "level $level n=$n_start .. $n_end\n"; foreach my $n ($n_start .. $n_end) { my ($x,$y) = $path->n_to_xy($n); $x -= 4**$level / 2; # for Rings $y -= 4**$level / 2; # for Rings my $w = -2*$y-$x; #my $w = -$y-$x/2; if ($w > $max_width) { $max_width = $w; $max_pos = "$x,$y n=$n (oct ".sprintf('%o',$n).")"; } if (! defined $min_width || $w < $min_width) { $min_width = $w; $min_pos = "$x,$y n=$n (oct ".sprintf('%o',$n).")"; } } # print " max $max_width at $max_x,$max_y\n"; my $factor = $max_width / $prev_max; print " min width $min_width oct ".sprintf('%o',$min_width)." at $min_pos factor $factor\n"; # print " max width $max_width oct ".sprintf('%o',$max_width)." at $max_pos factor $factor\n"; # print " cf formula ",(10*4**($level-1) - 1)/3,"\n"; # print " cf formula ",2* (4**($level-0) - 1)/3,"\n"; print " cf formula ",2*4**($level-1),"\n"; $prev_max = $max_width; } exit 0; } Math-PlanePath-129/devel/pictures.tex0000644000175000017500000007364313731324345015407 0ustar gggg% Copyright 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde % %; whizzy section \documentclass{article} \usepackage[T1]{fontenc} % T1 for accents, before babel \usepackage{amsmath} \allowdisplaybreaks \usepackage{needspace} \usepackage{gensymb} % for \degree \usepackage{hyphenat} % for \hyp hyphenation of words with - \usepackage[pdfusetitle, pdflang={en}, % RFC3066 style ISO639 ]{hyperref} \renewcommand\figureautorefname{figure} % lower case \usepackage[all]{hypcap} % figure links to top of figure \usepackage{mathtools} % for \mathclap and showonlyrefs \mathtoolsset{showonlyrefs=true,showmanualtags=true} \usepackage{amsthm} \usepackage{tikz} \usetikzlibrary{arrows.meta} % for Latex arrows To[length etc \usetikzlibrary{bending} % for arrow [bend] \usetikzlibrary{calc} % for ($(...)$) coordinate calculations \usetikzlibrary{decorations} % for [decoration=] \usetikzlibrary{decorations.pathreplacing} % for decoration=brace \usetikzlibrary{shapes} % for shape aspect=1 \tikzset{font=\small, % same as text >=Latex} % arrowhead style % must be capital Latex for [harpoon] half arrows %------------------------------------------------------------------------------ % personal preferences \hypersetup{ pdfborderstyle={/W 0}, % no border on hyperlinks } % these must be after \begin{document} to take effect, hence \AtBeginDocument \AtBeginDocument{% \setlength\abovedisplayskip{.7\baselineskip} \setlength\belowdisplayskip{.7\baselineskip} \setlength\abovedisplayshortskip{.5\baselineskip} \setlength\belowdisplayshortskip{.5\baselineskip} } % less space after "plain" style \end{theorem} etc \makeatletter \g@addto@macro\th@plain{\thm@postskip=1\baselineskip} \makeatother %------------------------------------------------------------------------------ % Generic Macros % GP-DEFINE default(strictargs,1); \newcommand\MySlash{\slash\hspace{0pt}} \newcommand\MyTightDots{.\kern.1em.\kern.1em.} %------------------------------------------------------------------------------ \begin{document} %------------------------------------------------------------------------------ \section{Peano Diagonals} \begin{center} \begin{tikzpicture} [scale=.8, my grey/.style={black!30}, ] \fill (0.1,0.1) circle (.1); \draw[my grey] (0.0,0.0) -- (0.1,0.1); \draw[->] (0.1,0.1) -- (0.9,0.9); \fill (1.1,0.9) circle (.1); \draw[my grey] (0.9,0.9) -- (1.1,0.9); \draw[->] (1.1,0.9) -- (1.9,0.1); \fill (2.1,0.1) circle (.1); \draw[my grey] (1.9,0.1) -- (2.1,0.1); \draw[->] (2.1,0.1) -- (2.9,0.9); \fill (3.1,0.9) circle (.1); \draw[my grey] (2.9,0.9) -- (3.1,0.9); \draw[->] (3.1,0.9) -- (3.9,0.1); \fill (3.9,1.1) circle (.1); \draw[my grey] (3.9,0.1) -- (3.9,1.1); \draw[->] (3.9,1.1) -- (3.1,1.9); \fill (2.9,1.9) circle (.1); \draw[my grey] (3.1,1.9) -- (2.9,1.9); \draw[->] (2.9,1.9) -- (2.1,1.1); \fill (1.9,1.1) circle (.1); \draw[my grey] (2.1,1.1) -- (1.9,1.1); \draw[->] (1.9,1.1) -- (1.1,1.9); \fill (0.9,1.9) circle (.1); \draw[my grey] (1.1,1.9) -- (0.9,1.9); \draw[->] (0.9,1.9) -- (0.1,1.1); \fill (0.1,2.1) circle (.1); \draw[my grey] (0.1,1.1) -- (0.1,2.1); \draw[->] (0.1,2.1) -- (0.9,2.9); \fill (1.1,2.9) circle (.1); \draw[my grey] (0.9,2.9) -- (1.1,2.9); \draw[->] (1.1,2.9) -- (1.9,2.1); \fill (2.1,2.1) circle (.1); \draw[my grey] (1.9,2.1) -- (2.1,2.1); \draw[->] (2.1,2.1) -- (2.9,2.9); \fill (3.1,2.9) circle (.1); \draw[my grey] (2.9,2.9) -- (3.1,2.9); \draw[->] (3.1,2.9) -- (3.9,2.1); \fill (3.9,3.1) circle (.1); \draw[my grey] (3.9,2.1) -- (3.9,3.1); \draw[->] (3.9,3.1) -- (3.1,3.9); \fill (2.9,3.9) circle (.1); \draw[my grey] (3.1,3.9) -- (2.9,3.9); \draw[->] (2.9,3.9) -- (2.1,3.1); \fill (1.9,3.1) circle (.1); \draw[my grey] (2.1,3.1) -- (1.9,3.1); \draw[->] (1.9,3.1) -- (1.1,3.9); \fill (0.9,3.9) circle (.1); \draw[my grey] (1.1,3.9) -- (0.9,3.9); \draw[->] (0.9,3.9) -- (0.1,3.1); \fill (4.1,3.9) circle (.1); \draw[my grey] (0.1,3.1) -- (4.1,3.9); \draw[->] (4.1,3.9) -- (4.9,3.1); \fill (5.1,3.1) circle (.1); \draw[my grey] (4.9,3.1) -- (5.1,3.1); \draw[->] (5.1,3.1) -- (5.9,3.9); \fill (6.1,3.9) circle (.1); \draw[my grey] (5.9,3.9) -- (6.1,3.9); \draw[->] (6.1,3.9) -- (6.9,3.1); \fill (7.1,3.1) circle (.1); \draw[my grey] (6.9,3.1) -- (7.1,3.1); \draw[->] (7.1,3.1) -- (7.9,3.9); \fill (7.9,2.9) circle (.1); \draw[my grey] (7.9,3.9) -- (7.9,2.9); \draw[->] (7.9,2.9) -- (7.1,2.1); \fill (6.9,2.1) circle (.1); \draw[my grey] (7.1,2.1) -- (6.9,2.1); \draw[->] (6.9,2.1) -- (6.1,2.9); \fill (5.9,2.9) circle (.1); \draw[my grey] (6.1,2.9) -- (5.9,2.9); \draw[->] (5.9,2.9) -- (5.1,2.1); \fill (4.9,2.1) circle (.1); \draw[my grey] (5.1,2.1) -- (4.9,2.1); \draw[->] (4.9,2.1) -- (4.1,2.9); \fill (4.1,1.9) circle (.1); \draw[my grey] (4.1,2.9) -- (4.1,1.9); \draw[->] (4.1,1.9) -- (4.9,1.1); \fill (5.1,1.1) circle (.1); \draw[my grey] (4.9,1.1) -- (5.1,1.1); \draw[->] (5.1,1.1) -- (5.9,1.9); \fill (6.1,1.9) circle (.1); \draw[my grey] (5.9,1.9) -- (6.1,1.9); \draw[->] (6.1,1.9) -- (6.9,1.1); \fill (7.1,1.1) circle (.1); \draw[my grey] (6.9,1.1) -- (7.1,1.1); \draw[->] (7.1,1.1) -- (7.9,1.9); \fill (7.9,0.9) circle (.1); \draw[my grey] (7.9,1.9) -- (7.9,0.9); \draw[->] (7.9,0.9) -- (7.1,0.1); \fill (6.9,0.1) circle (.1); \draw[my grey] (7.1,0.1) -- (6.9,0.1); \draw[->] (6.9,0.1) -- (6.1,0.9); \fill (5.9,0.9) circle (.1); \draw[my grey] (6.1,0.9) -- (5.9,0.9); \draw[->] (5.9,0.9) -- (5.1,0.1); \fill (4.9,0.1) circle (.1); \draw[my grey] (5.1,0.1) -- (4.9,0.1); \draw[->] (4.9,0.1) -- (4.1,0.9); \fill (8.1,0.1) circle (.1); \draw[my grey] (4.1,0.9) -- (8.1,0.1); \draw[->] (8.1,0.1) -- (8.9,0.9); \fill (9.1,0.9) circle (.1); \draw[my grey] (8.9,0.9) -- (9.1,0.9); \draw[->] (9.1,0.9) -- (9.9,0.1); \fill (10.1,0.1) circle (.1); \draw[my grey] (9.9,0.1) -- (10.1,0.1); \draw[->] (10.1,0.1) -- (10.9,0.9); \fill (11.1,0.9) circle (.1); \draw[my grey] (10.9,0.9) -- (11.1,0.9); \draw[->] (11.1,0.9) -- (11.9,0.1); \fill (11.9,1.1) circle (.1); \draw[my grey] (11.9,0.1) -- (11.9,1.1); \draw[->] (11.9,1.1) -- (11.1,1.9); \fill (10.9,1.9) circle (.1); \draw[my grey] (11.1,1.9) -- (10.9,1.9); \draw[->] (10.9,1.9) -- (10.1,1.1); \fill (9.9,1.1) circle (.1); \draw[my grey] (10.1,1.1) -- (9.9,1.1); \draw[->] (9.9,1.1) -- (9.1,1.9); \fill (8.9,1.9) circle (.1); \draw[my grey] (9.1,1.9) -- (8.9,1.9); \draw[->] (8.9,1.9) -- (8.1,1.1); \fill (8.1,2.1) circle (.1); \draw[my grey] (8.1,1.1) -- (8.1,2.1); \draw[->] (8.1,2.1) -- (8.9,2.9); \fill (9.1,2.9) circle (.1); \draw[my grey] (8.9,2.9) -- (9.1,2.9); \draw[->] (9.1,2.9) -- (9.9,2.1); \fill (10.1,2.1) circle (.1); \draw[my grey] (9.9,2.1) -- (10.1,2.1); \draw[->] (10.1,2.1) -- (10.9,2.9); \fill (11.1,2.9) circle (.1); \draw[my grey] (10.9,2.9) -- (11.1,2.9); \draw[->] (11.1,2.9) -- (11.9,2.1); \fill (11.9,3.1) circle (.1); \draw[my grey] (11.9,2.1) -- (11.9,3.1); \draw[->] (11.9,3.1) -- (11.1,3.9); \fill (10.9,3.9) circle (.1); \draw[my grey] (11.1,3.9) -- (10.9,3.9); \draw[->] (10.9,3.9) -- (10.1,3.1); \fill (9.9,3.1) circle (.1); \draw[my grey] (10.1,3.1) -- (9.9,3.1); \draw[->] (9.9,3.1) -- (9.1,3.9); \fill (8.9,3.9) circle (.1); \draw[my grey] (9.1,3.9) -- (8.9,3.9); \draw[->] (8.9,3.9) -- (8.1,3.1); \fill (12.1,3.9) circle (.1); \draw[my grey] (8.1,3.1) -- (12.1,3.9); \draw[->] (12.1,3.9) -- (12.9,3.1); \fill (13.1,3.1) circle (.1); \draw[my grey] (12.9,3.1) -- (13.1,3.1); \draw[->] (13.1,3.1) -- (13.9,3.9); \fill (14.1,3.9) circle (.1); \draw[my grey] (13.9,3.9) -- (14.1,3.9); \draw[->] (14.1,3.9) -- (14.9,3.1); \fill (15.1,3.1) circle (.1); \draw[my grey] (14.9,3.1) -- (15.1,3.1); \draw[->] (15.1,3.1) -- (15.9,3.9); \fill (15.9,2.9) circle (.1); \draw[my grey] (15.9,3.9) -- (15.9,2.9); \draw[->] (15.9,2.9) -- (15.1,2.1); \fill (14.9,2.1) circle (.1); \draw[my grey] (15.1,2.1) -- (14.9,2.1); \draw[->] (14.9,2.1) -- (14.1,2.9); \fill (13.9,2.9) circle (.1); \draw[my grey] (14.1,2.9) -- (13.9,2.9); \draw[->] (13.9,2.9) -- (13.1,2.1); \fill (12.9,2.1) circle (.1); \draw[my grey] (13.1,2.1) -- (12.9,2.1); \draw[->] (12.9,2.1) -- (12.1,2.9); \fill (12.1,1.9) circle (.1); \draw[my grey] (12.1,2.9) -- (12.1,1.9); \draw[->] (12.1,1.9) -- (12.9,1.1); \fill (13.1,1.1) circle (.1); \draw[my grey] (12.9,1.1) -- (13.1,1.1); \draw[->] (13.1,1.1) -- (13.9,1.9); \fill (14.1,1.9) circle (.1); \draw[my grey] (13.9,1.9) -- (14.1,1.9); \draw[->] (14.1,1.9) -- (14.9,1.1); \fill (15.1,1.1) circle (.1); \draw[my grey] (14.9,1.1) -- (15.1,1.1); \draw[->] (15.1,1.1) -- (15.9,1.9); \fill (15.9,0.9) circle (.1); \draw[my grey] (15.9,1.9) -- (15.9,0.9); \draw[->] (15.9,0.9) -- (15.1,0.1); \fill (14.9,0.1) circle (.1); \draw[my grey] (15.1,0.1) -- (14.9,0.1); \draw[->] (14.9,0.1) -- (14.1,0.9); \fill (13.9,0.9) circle (.1); \draw[my grey] (14.1,0.9) -- (13.9,0.9); \draw[->] (13.9,0.9) -- (13.1,0.1); \fill (12.9,0.1) circle (.1); \draw[my grey] (13.1,0.1) -- (12.9,0.1); \draw[->] (12.9,0.1) -- (12.1,0.9); \fill (15.9,4.1) circle (.1); \draw[my grey] (12.1,0.9) -- (15.9,4.1); \draw[->] (15.9,4.1) -- (15.1,4.9); \fill (14.9,4.9) circle (.1); \draw[my grey] (15.1,4.9) -- (14.9,4.9); \draw[->] (14.9,4.9) -- (14.1,4.1); \fill (13.9,4.1) circle (.1); \draw[my grey] (14.1,4.1) -- (13.9,4.1); \draw[->] (13.9,4.1) -- (13.1,4.9); \fill (12.9,4.9) circle (.1); \draw[my grey] (13.1,4.9) -- (12.9,4.9); \draw[->] (12.9,4.9) -- (12.1,4.1); \fill (12.1,5.1) circle (.1); \draw[my grey] (12.1,4.1) -- (12.1,5.1); \draw[->] (12.1,5.1) -- (12.9,5.9); \fill (13.1,5.9) circle (.1); \draw[my grey] (12.9,5.9) -- (13.1,5.9); \draw[->] (13.1,5.9) -- (13.9,5.1); \fill (14.1,5.1) circle (.1); \draw[my grey] (13.9,5.1) -- (14.1,5.1); \draw[->] (14.1,5.1) -- (14.9,5.9); \fill (15.1,5.9) circle (.1); \draw[my grey] (14.9,5.9) -- (15.1,5.9); \draw[->] (15.1,5.9) -- (15.9,5.1); \fill (15.9,6.1) circle (.1); \draw[my grey] (15.9,5.1) -- (15.9,6.1); \draw[->] (15.9,6.1) -- (15.1,6.9); \fill (14.9,6.9) circle (.1); \draw[my grey] (15.1,6.9) -- (14.9,6.9); \draw[->] (14.9,6.9) -- (14.1,6.1); \fill (13.9,6.1) circle (.1); \draw[my grey] (14.1,6.1) -- (13.9,6.1); \draw[->] (13.9,6.1) -- (13.1,6.9); \fill (12.9,6.9) circle (.1); \draw[my grey] (13.1,6.9) -- (12.9,6.9); \draw[->] (12.9,6.9) -- (12.1,6.1); \fill (12.1,7.1) circle (.1); \draw[my grey] (12.1,6.1) -- (12.1,7.1); \draw[->] (12.1,7.1) -- (12.9,7.9); \fill (13.1,7.9) circle (.1); \draw[my grey] (12.9,7.9) -- (13.1,7.9); \draw[->] (13.1,7.9) -- (13.9,7.1); \fill (14.1,7.1) circle (.1); \draw[my grey] (13.9,7.1) -- (14.1,7.1); \draw[->] (14.1,7.1) -- (14.9,7.9); \fill (15.1,7.9) circle (.1); \draw[my grey] (14.9,7.9) -- (15.1,7.9); \draw[->] (15.1,7.9) -- (15.9,7.1); \fill (11.9,7.9) circle (.1); \draw[my grey] (15.9,7.1) -- (11.9,7.9); \draw[->] (11.9,7.9) -- (11.1,7.1); \fill (10.9,7.1) circle (.1); \draw[my grey] (11.1,7.1) -- (10.9,7.1); \draw[->] (10.9,7.1) -- (10.1,7.9); \fill (9.9,7.9) circle (.1); \draw[my grey] (10.1,7.9) -- (9.9,7.9); \draw[->] (9.9,7.9) -- (9.1,7.1); \fill (8.9,7.1) circle (.1); \draw[my grey] (9.1,7.1) -- (8.9,7.1); \draw[->] (8.9,7.1) -- (8.1,7.9); \fill (8.1,6.9) circle (.1); \draw[my grey] (8.1,7.9) -- (8.1,6.9); \draw[->] (8.1,6.9) -- (8.9,6.1); \fill (9.1,6.1) circle (.1); \draw[my grey] (8.9,6.1) -- (9.1,6.1); \draw[->] (9.1,6.1) -- (9.9,6.9); \fill (10.1,6.9) circle (.1); \draw[my grey] (9.9,6.9) -- (10.1,6.9); \draw[->] (10.1,6.9) -- (10.9,6.1); \fill (11.1,6.1) circle (.1); \draw[my grey] (10.9,6.1) -- (11.1,6.1); \draw[->] (11.1,6.1) -- (11.9,6.9); \fill (11.9,5.9) circle (.1); \draw[my grey] (11.9,6.9) -- (11.9,5.9); \draw[->] (11.9,5.9) -- (11.1,5.1); \fill (10.9,5.1) circle (.1); \draw[my grey] (11.1,5.1) -- (10.9,5.1); \draw[->] (10.9,5.1) -- (10.1,5.9); \fill (9.9,5.9) circle (.1); \draw[my grey] (10.1,5.9) -- (9.9,5.9); \draw[->] (9.9,5.9) -- (9.1,5.1); \fill (8.9,5.1) circle (.1); \draw[my grey] (9.1,5.1) -- (8.9,5.1); \draw[->] (8.9,5.1) -- (8.1,5.9); \fill (8.1,4.9) circle (.1); \draw[my grey] (8.1,5.9) -- (8.1,4.9); \draw[->] (8.1,4.9) -- (8.9,4.1); \fill (9.1,4.1) circle (.1); \draw[my grey] (8.9,4.1) -- (9.1,4.1); \draw[->] (9.1,4.1) -- (9.9,4.9); \fill (10.1,4.9) circle (.1); \draw[my grey] (9.9,4.9) -- (10.1,4.9); \draw[->] (10.1,4.9) -- (10.9,4.1); \fill (11.1,4.1) circle (.1); \draw[my grey] (10.9,4.1) -- (11.1,4.1); \draw[->] (11.1,4.1) -- (11.9,4.9); \fill (7.9,4.1) circle (.1); \draw[my grey] (11.9,4.9) -- (7.9,4.1); \draw[->] (7.9,4.1) -- (7.1,4.9); \fill (6.9,4.9) circle (.1); \draw[my grey] (7.1,4.9) -- (6.9,4.9); \draw[->] (6.9,4.9) -- (6.1,4.1); \fill (5.9,4.1) circle (.1); \draw[my grey] (6.1,4.1) -- (5.9,4.1); \draw[->] (5.9,4.1) -- (5.1,4.9); \fill (4.9,4.9) circle (.1); \draw[my grey] (5.1,4.9) -- (4.9,4.9); \draw[->] (4.9,4.9) -- (4.1,4.1); \fill (4.1,5.1) circle (.1); \draw[my grey] (4.1,4.1) -- (4.1,5.1); \draw[->] (4.1,5.1) -- (4.9,5.9); \fill (5.1,5.9) circle (.1); \draw[my grey] (4.9,5.9) -- (5.1,5.9); \draw[->] (5.1,5.9) -- (5.9,5.1); \fill (6.1,5.1) circle (.1); \draw[my grey] (5.9,5.1) -- (6.1,5.1); \draw[->] (6.1,5.1) -- (6.9,5.9); \fill (7.1,5.9) circle (.1); \draw[my grey] (6.9,5.9) -- (7.1,5.9); \draw[->] (7.1,5.9) -- (7.9,5.1); \fill (7.9,6.1) circle (.1); \draw[my grey] (7.9,5.1) -- (7.9,6.1); \draw[->] (7.9,6.1) -- (7.1,6.9); \fill (6.9,6.9) circle (.1); \draw[my grey] (7.1,6.9) -- (6.9,6.9); \draw[->] (6.9,6.9) -- (6.1,6.1); \fill (5.9,6.1) circle (.1); \draw[my grey] (6.1,6.1) -- (5.9,6.1); \draw[->] (5.9,6.1) -- (5.1,6.9); \fill (4.9,6.9) circle (.1); \draw[my grey] (5.1,6.9) -- (4.9,6.9); \draw[->] (4.9,6.9) -- (4.1,6.1); \fill (4.1,7.1) circle (.1); \draw[my grey] (4.1,6.1) -- (4.1,7.1); \draw[->] (4.1,7.1) -- (4.9,7.9); \fill (5.1,7.9) circle (.1); \draw[my grey] (4.9,7.9) -- (5.1,7.9); \draw[->] (5.1,7.9) -- (5.9,7.1); \fill (6.1,7.1) circle (.1); \draw[my grey] (5.9,7.1) -- (6.1,7.1); \draw[->] (6.1,7.1) -- (6.9,7.9); \fill (7.1,7.9) circle (.1); \draw[my grey] (6.9,7.9) -- (7.1,7.9); \draw[->] (7.1,7.9) -- (7.9,7.1); \fill (3.9,7.9) circle (.1); \draw[my grey] (7.9,7.1) -- (3.9,7.9); \draw[->] (3.9,7.9) -- (3.1,7.1); \fill (2.9,7.1) circle (.1); \draw[my grey] (3.1,7.1) -- (2.9,7.1); \draw[->] (2.9,7.1) -- (2.1,7.9); \fill (1.9,7.9) circle (.1); \draw[my grey] (2.1,7.9) -- (1.9,7.9); \draw[->] (1.9,7.9) -- (1.1,7.1); \fill (0.9,7.1) circle (.1); \draw[my grey] (1.1,7.1) -- (0.9,7.1); \draw[->] (0.9,7.1) -- (0.1,7.9); \fill (0.1,6.9) circle (.1); \draw[my grey] (0.1,7.9) -- (0.1,6.9); \draw[->] (0.1,6.9) -- (0.9,6.1); \fill (1.1,6.1) circle (.1); \draw[my grey] (0.9,6.1) -- (1.1,6.1); \draw[->] (1.1,6.1) -- (1.9,6.9); \fill (2.1,6.9) circle (.1); \draw[my grey] (1.9,6.9) -- (2.1,6.9); \draw[->] (2.1,6.9) -- (2.9,6.1); \fill (3.1,6.1) circle (.1); \draw[my grey] (2.9,6.1) -- (3.1,6.1); \draw[->] (3.1,6.1) -- (3.9,6.9); \fill (3.9,5.9) circle (.1); \draw[my grey] (3.9,6.9) -- (3.9,5.9); \draw[->] (3.9,5.9) -- (3.1,5.1); \fill (2.9,5.1) circle (.1); \draw[my grey] (3.1,5.1) -- (2.9,5.1); \draw[->] (2.9,5.1) -- (2.1,5.9); \fill (1.9,5.9) circle (.1); \draw[my grey] (2.1,5.9) -- (1.9,5.9); \draw[->] (1.9,5.9) -- (1.1,5.1); \fill (0.9,5.1) circle (.1); \draw[my grey] (1.1,5.1) -- (0.9,5.1); \draw[->] (0.9,5.1) -- (0.1,5.9); \fill (0.1,4.9) circle (.1); \draw[my grey] (0.1,5.9) -- (0.1,4.9); \draw[->] (0.1,4.9) -- (0.9,4.1); \fill (1.1,4.1) circle (.1); \draw[my grey] (0.9,4.1) -- (1.1,4.1); \draw[->] (1.1,4.1) -- (1.9,4.9); \fill (2.1,4.9) circle (.1); \draw[my grey] (1.9,4.9) -- (2.1,4.9); \draw[->] (2.1,4.9) -- (2.9,4.1); \fill (3.1,4.1) circle (.1); \draw[my grey] (2.9,4.1) -- (3.1,4.1); \draw[->] (3.1,4.1) -- (3.9,4.9); \fill (0.1,8.1) circle (.1); \draw[my grey] (3.9,4.9) -- (0.1,8.1); \draw[->] (0.1,8.1) -- (0.9,8.9); \fill (1.1,8.9) circle (.1); \draw[my grey] (0.9,8.9) -- (1.1,8.9); \draw[->] (1.1,8.9) -- (1.9,8.1); \fill (2.1,8.1) circle (.1); \draw[my grey] (1.9,8.1) -- (2.1,8.1); \draw[->] (2.1,8.1) -- (2.9,8.9); \fill (3.1,8.9) circle (.1); \draw[my grey] (2.9,8.9) -- (3.1,8.9); \draw[->] (3.1,8.9) -- (3.9,8.1); \fill (3.9,9.1) circle (.1); \draw[my grey] (3.9,8.1) -- (3.9,9.1); \draw[->] (3.9,9.1) -- (3.1,9.9); \fill (2.9,9.9) circle (.1); \draw[my grey] (3.1,9.9) -- (2.9,9.9); \draw[->] (2.9,9.9) -- (2.1,9.1); \fill (1.9,9.1) circle (.1); \draw[my grey] (2.1,9.1) -- (1.9,9.1); \draw[->] (1.9,9.1) -- (1.1,9.9); \fill (0.9,9.9) circle (.1); \draw[my grey] (1.1,9.9) -- (0.9,9.9); \draw[->] (0.9,9.9) -- (0.1,9.1); \fill (0.1,10.1) circle (.1); \draw[my grey] (0.1,9.1) -- (0.1,10.1); \draw[->] (0.1,10.1) -- (0.9,10.9); \fill (1.1,10.9) circle (.1); \draw[my grey] (0.9,10.9) -- (1.1,10.9); \draw[->] (1.1,10.9) -- (1.9,10.1); \fill (2.1,10.1) circle (.1); \draw[my grey] (1.9,10.1) -- (2.1,10.1); \draw[->] (2.1,10.1) -- (2.9,10.9); \fill (3.1,10.9) circle (.1); \draw[my grey] (2.9,10.9) -- (3.1,10.9); \draw[->] (3.1,10.9) -- (3.9,10.1); \fill (3.9,11.1) circle (.1); \draw[my grey] (3.9,10.1) -- (3.9,11.1); \draw[->] (3.9,11.1) -- (3.1,11.9); \fill (2.9,11.9) circle (.1); \draw[my grey] (3.1,11.9) -- (2.9,11.9); \draw[->] (2.9,11.9) -- (2.1,11.1); \fill (1.9,11.1) circle (.1); \draw[my grey] (2.1,11.1) -- (1.9,11.1); \draw[->] (1.9,11.1) -- (1.1,11.9); \fill (0.9,11.9) circle (.1); \draw[my grey] (1.1,11.9) -- (0.9,11.9); \draw[->] (0.9,11.9) -- (0.1,11.1); \fill (4.1,11.9) circle (.1); \draw[my grey] (0.1,11.1) -- (4.1,11.9); \draw[->] (4.1,11.9) -- (4.9,11.1); \fill (5.1,11.1) circle (.1); \draw[my grey] (4.9,11.1) -- (5.1,11.1); \draw[->] (5.1,11.1) -- (5.9,11.9); \fill (6.1,11.9) circle (.1); \draw[my grey] (5.9,11.9) -- (6.1,11.9); \draw[->] (6.1,11.9) -- (6.9,11.1); \fill (7.1,11.1) circle (.1); \draw[my grey] (6.9,11.1) -- (7.1,11.1); \draw[->] (7.1,11.1) -- (7.9,11.9); \fill (7.9,10.9) circle (.1); \draw[my grey] (7.9,11.9) -- (7.9,10.9); \draw[->] (7.9,10.9) -- (7.1,10.1); \fill (6.9,10.1) circle (.1); \draw[my grey] (7.1,10.1) -- (6.9,10.1); \draw[->] (6.9,10.1) -- (6.1,10.9); \fill (5.9,10.9) circle (.1); \draw[my grey] (6.1,10.9) -- (5.9,10.9); \draw[->] (5.9,10.9) -- (5.1,10.1); \fill (4.9,10.1) circle (.1); \draw[my grey] (5.1,10.1) -- (4.9,10.1); \draw[->] (4.9,10.1) -- (4.1,10.9); \fill (4.1,9.9) circle (.1); \draw[my grey] (4.1,10.9) -- (4.1,9.9); \draw[->] (4.1,9.9) -- (4.9,9.1); \fill (5.1,9.1) circle (.1); \draw[my grey] (4.9,9.1) -- (5.1,9.1); \draw[->] (5.1,9.1) -- (5.9,9.9); \fill (6.1,9.9) circle (.1); \draw[my grey] (5.9,9.9) -- (6.1,9.9); \draw[->] (6.1,9.9) -- (6.9,9.1); \fill (7.1,9.1) circle (.1); \draw[my grey] (6.9,9.1) -- (7.1,9.1); \draw[->] (7.1,9.1) -- (7.9,9.9); \fill (7.9,8.9) circle (.1); \draw[my grey] (7.9,9.9) -- (7.9,8.9); \draw[->] (7.9,8.9) -- (7.1,8.1); \fill (6.9,8.1) circle (.1); \draw[my grey] (7.1,8.1) -- (6.9,8.1); \draw[->] (6.9,8.1) -- (6.1,8.9); \fill (5.9,8.9) circle (.1); \draw[my grey] (6.1,8.9) -- (5.9,8.9); \draw[->] (5.9,8.9) -- (5.1,8.1); \fill (4.9,8.1) circle (.1); \draw[my grey] (5.1,8.1) -- (4.9,8.1); \draw[->] (4.9,8.1) -- (4.1,8.9); \fill (8.1,8.1) circle (.1); \draw[my grey] (4.1,8.9) -- (8.1,8.1); \draw[->] (8.1,8.1) -- (8.9,8.9); \fill (9.1,8.9) circle (.1); \draw[my grey] (8.9,8.9) -- (9.1,8.9); \draw[->] (9.1,8.9) -- (9.9,8.1); \fill (10.1,8.1) circle (.1); \draw[my grey] (9.9,8.1) -- (10.1,8.1); \draw[->] (10.1,8.1) -- (10.9,8.9); \fill (11.1,8.9) circle (.1); \draw[my grey] (10.9,8.9) -- (11.1,8.9); \draw[->] (11.1,8.9) -- (11.9,8.1); \fill (11.9,9.1) circle (.1); \draw[my grey] (11.9,8.1) -- (11.9,9.1); \draw[->] (11.9,9.1) -- (11.1,9.9); \fill (10.9,9.9) circle (.1); \draw[my grey] (11.1,9.9) -- (10.9,9.9); \draw[->] (10.9,9.9) -- (10.1,9.1); \fill (9.9,9.1) circle (.1); \draw[my grey] (10.1,9.1) -- (9.9,9.1); \draw[->] (9.9,9.1) -- (9.1,9.9); \fill (8.9,9.9) circle (.1); \draw[my grey] (9.1,9.9) -- (8.9,9.9); \draw[->] (8.9,9.9) -- (8.1,9.1); \fill (8.1,10.1) circle (.1); \draw[my grey] (8.1,9.1) -- (8.1,10.1); \draw[->] (8.1,10.1) -- (8.9,10.9); \fill (9.1,10.9) circle (.1); \draw[my grey] (8.9,10.9) -- (9.1,10.9); \draw[->] (9.1,10.9) -- (9.9,10.1); \fill (10.1,10.1) circle (.1); \draw[my grey] (9.9,10.1) -- (10.1,10.1); \draw[->] (10.1,10.1) -- (10.9,10.9); \fill (11.1,10.9) circle (.1); \draw[my grey] (10.9,10.9) -- (11.1,10.9); \draw[->] (11.1,10.9) -- (11.9,10.1); \fill (11.9,11.1) circle (.1); \draw[my grey] (11.9,10.1) -- (11.9,11.1); \draw[->] (11.9,11.1) -- (11.1,11.9); \fill (10.9,11.9) circle (.1); \draw[my grey] (11.1,11.9) -- (10.9,11.9); \draw[->] (10.9,11.9) -- (10.1,11.1); \fill (9.9,11.1) circle (.1); \draw[my grey] (10.1,11.1) -- (9.9,11.1); \draw[->] (9.9,11.1) -- (9.1,11.9); \fill (8.9,11.9) circle (.1); \draw[my grey] (9.1,11.9) -- (8.9,11.9); \draw[->] (8.9,11.9) -- (8.1,11.1); \fill (12.1,11.9) circle (.1); \draw[my grey] (8.1,11.1) -- (12.1,11.9); \draw[->] (12.1,11.9) -- (12.9,11.1); \fill (13.1,11.1) circle (.1); \draw[my grey] (12.9,11.1) -- (13.1,11.1); \draw[->] (13.1,11.1) -- (13.9,11.9); \fill (14.1,11.9) circle (.1); \draw[my grey] (13.9,11.9) -- (14.1,11.9); \draw[->] (14.1,11.9) -- (14.9,11.1); \fill (15.1,11.1) circle (.1); \draw[my grey] (14.9,11.1) -- (15.1,11.1); \draw[->] (15.1,11.1) -- (15.9,11.9); \fill (15.9,10.9) circle (.1); \draw[my grey] (15.9,11.9) -- (15.9,10.9); \draw[->] (15.9,10.9) -- (15.1,10.1); \fill (14.9,10.1) circle (.1); \draw[my grey] (15.1,10.1) -- (14.9,10.1); \draw[->] (14.9,10.1) -- (14.1,10.9); \fill (13.9,10.9) circle (.1); \draw[my grey] (14.1,10.9) -- (13.9,10.9); \draw[->] (13.9,10.9) -- (13.1,10.1); \fill (12.9,10.1) circle (.1); \draw[my grey] (13.1,10.1) -- (12.9,10.1); \draw[->] (12.9,10.1) -- (12.1,10.9); \fill (12.1,9.9) circle (.1); \draw[my grey] (12.1,10.9) -- (12.1,9.9); \draw[->] (12.1,9.9) -- (12.9,9.1); \fill (13.1,9.1) circle (.1); \draw[my grey] (12.9,9.1) -- (13.1,9.1); \draw[->] (13.1,9.1) -- (13.9,9.9); \fill (14.1,9.9) circle (.1); \draw[my grey] (13.9,9.9) -- (14.1,9.9); \draw[->] (14.1,9.9) -- (14.9,9.1); \fill (15.1,9.1) circle (.1); \draw[my grey] (14.9,9.1) -- (15.1,9.1); \draw[->] (15.1,9.1) -- (15.9,9.9); \fill (15.9,8.9) circle (.1); \draw[my grey] (15.9,9.9) -- (15.9,8.9); \draw[->] (15.9,8.9) -- (15.1,8.1); \fill (14.9,8.1) circle (.1); \draw[my grey] (15.1,8.1) -- (14.9,8.1); \draw[->] (14.9,8.1) -- (14.1,8.9); \fill (13.9,8.9) circle (.1); \draw[my grey] (14.1,8.9) -- (13.9,8.9); \draw[->] (13.9,8.9) -- (13.1,8.1); \fill (12.9,8.1) circle (.1); \draw[my grey] (13.1,8.1) -- (12.9,8.1); \draw[->] (12.9,8.1) -- (12.1,8.9); \fill (15.9,12.1) circle (.1); \draw[my grey] (12.1,8.9) -- (15.9,12.1); \draw[->] (15.9,12.1) -- (15.1,12.9); \fill (14.9,12.9) circle (.1); \draw[my grey] (15.1,12.9) -- (14.9,12.9); \draw[->] (14.9,12.9) -- (14.1,12.1); \fill (13.9,12.1) circle (.1); \draw[my grey] (14.1,12.1) -- (13.9,12.1); \draw[->] (13.9,12.1) -- (13.1,12.9); \fill (12.9,12.9) circle (.1); \draw[my grey] (13.1,12.9) -- (12.9,12.9); \draw[->] (12.9,12.9) -- (12.1,12.1); \fill (12.1,13.1) circle (.1); \draw[my grey] (12.1,12.1) -- (12.1,13.1); \draw[->] (12.1,13.1) -- (12.9,13.9); \fill (13.1,13.9) circle (.1); \draw[my grey] (12.9,13.9) -- (13.1,13.9); \draw[->] (13.1,13.9) -- (13.9,13.1); \fill (14.1,13.1) circle (.1); \draw[my grey] (13.9,13.1) -- (14.1,13.1); \draw[->] (14.1,13.1) -- (14.9,13.9); \fill (15.1,13.9) circle (.1); \draw[my grey] (14.9,13.9) -- (15.1,13.9); \draw[->] (15.1,13.9) -- (15.9,13.1); \fill (15.9,14.1) circle (.1); \draw[my grey] (15.9,13.1) -- (15.9,14.1); \draw[->] (15.9,14.1) -- (15.1,14.9); \fill (14.9,14.9) circle (.1); \draw[my grey] (15.1,14.9) -- (14.9,14.9); \draw[->] (14.9,14.9) -- (14.1,14.1); \fill (13.9,14.1) circle (.1); \draw[my grey] (14.1,14.1) -- (13.9,14.1); \draw[->] (13.9,14.1) -- (13.1,14.9); \fill (12.9,14.9) circle (.1); \draw[my grey] (13.1,14.9) -- (12.9,14.9); \draw[->] (12.9,14.9) -- (12.1,14.1); \fill (12.1,15.1) circle (.1); \draw[my grey] (12.1,14.1) -- (12.1,15.1); \draw[->] (12.1,15.1) -- (12.9,15.9); \fill (13.1,15.9) circle (.1); \draw[my grey] (12.9,15.9) -- (13.1,15.9); \draw[->] (13.1,15.9) -- (13.9,15.1); \fill (14.1,15.1) circle (.1); \draw[my grey] (13.9,15.1) -- (14.1,15.1); \draw[->] (14.1,15.1) -- (14.9,15.9); \fill (15.1,15.9) circle (.1); \draw[my grey] (14.9,15.9) -- (15.1,15.9); \draw[->] (15.1,15.9) -- (15.9,15.1); \fill (11.9,15.9) circle (.1); \draw[my grey] (15.9,15.1) -- (11.9,15.9); \draw[->] (11.9,15.9) -- (11.1,15.1); \fill (10.9,15.1) circle (.1); \draw[my grey] (11.1,15.1) -- (10.9,15.1); \draw[->] (10.9,15.1) -- (10.1,15.9); \fill (9.9,15.9) circle (.1); \draw[my grey] (10.1,15.9) -- (9.9,15.9); \draw[->] (9.9,15.9) -- (9.1,15.1); \fill (8.9,15.1) circle (.1); \draw[my grey] (9.1,15.1) -- (8.9,15.1); \draw[->] (8.9,15.1) -- (8.1,15.9); \fill (8.1,14.9) circle (.1); \draw[my grey] (8.1,15.9) -- (8.1,14.9); \draw[->] (8.1,14.9) -- (8.9,14.1); \fill (9.1,14.1) circle (.1); \draw[my grey] (8.9,14.1) -- (9.1,14.1); \draw[->] (9.1,14.1) -- (9.9,14.9); \fill (10.1,14.9) circle (.1); \draw[my grey] (9.9,14.9) -- (10.1,14.9); \draw[->] (10.1,14.9) -- (10.9,14.1); \fill (11.1,14.1) circle (.1); \draw[my grey] (10.9,14.1) -- (11.1,14.1); \draw[->] (11.1,14.1) -- (11.9,14.9); \fill (11.9,13.9) circle (.1); \draw[my grey] (11.9,14.9) -- (11.9,13.9); \draw[->] (11.9,13.9) -- (11.1,13.1); \fill (10.9,13.1) circle (.1); \draw[my grey] (11.1,13.1) -- (10.9,13.1); \draw[->] (10.9,13.1) -- (10.1,13.9); \fill (9.9,13.9) circle (.1); \draw[my grey] (10.1,13.9) -- (9.9,13.9); \draw[->] (9.9,13.9) -- (9.1,13.1); \fill (8.9,13.1) circle (.1); \draw[my grey] (9.1,13.1) -- (8.9,13.1); \draw[->] (8.9,13.1) -- (8.1,13.9); \fill (8.1,12.9) circle (.1); \draw[my grey] (8.1,13.9) -- (8.1,12.9); \draw[->] (8.1,12.9) -- (8.9,12.1); \fill (9.1,12.1) circle (.1); \draw[my grey] (8.9,12.1) -- (9.1,12.1); \draw[->] (9.1,12.1) -- (9.9,12.9); \fill (10.1,12.9) circle (.1); \draw[my grey] (9.9,12.9) -- (10.1,12.9); \draw[->] (10.1,12.9) -- (10.9,12.1); \fill (11.1,12.1) circle (.1); \draw[my grey] (10.9,12.1) -- (11.1,12.1); \draw[->] (11.1,12.1) -- (11.9,12.9); \fill (7.9,12.1) circle (.1); \draw[my grey] (11.9,12.9) -- (7.9,12.1); \draw[->] (7.9,12.1) -- (7.1,12.9); \fill (6.9,12.9) circle (.1); \draw[my grey] (7.1,12.9) -- (6.9,12.9); \draw[->] (6.9,12.9) -- (6.1,12.1); \fill (5.9,12.1) circle (.1); \draw[my grey] (6.1,12.1) -- (5.9,12.1); \draw[->] (5.9,12.1) -- (5.1,12.9); \fill (4.9,12.9) circle (.1); \draw[my grey] (5.1,12.9) -- (4.9,12.9); \draw[->] (4.9,12.9) -- (4.1,12.1); \fill (4.1,13.1) circle (.1); \draw[my grey] (4.1,12.1) -- (4.1,13.1); \draw[->] (4.1,13.1) -- (4.9,13.9); \fill (5.1,13.9) circle (.1); \draw[my grey] (4.9,13.9) -- (5.1,13.9); \draw[->] (5.1,13.9) -- (5.9,13.1); \fill (6.1,13.1) circle (.1); \draw[my grey] (5.9,13.1) -- (6.1,13.1); \draw[->] (6.1,13.1) -- (6.9,13.9); \fill (7.1,13.9) circle (.1); \draw[my grey] (6.9,13.9) -- (7.1,13.9); \draw[->] (7.1,13.9) -- (7.9,13.1); \fill (7.9,14.1) circle (.1); \draw[my grey] (7.9,13.1) -- (7.9,14.1); \draw[->] (7.9,14.1) -- (7.1,14.9); \fill (6.9,14.9) circle (.1); \draw[my grey] (7.1,14.9) -- (6.9,14.9); \draw[->] (6.9,14.9) -- (6.1,14.1); \fill (5.9,14.1) circle (.1); \draw[my grey] (6.1,14.1) -- (5.9,14.1); \draw[->] (5.9,14.1) -- (5.1,14.9); \fill (4.9,14.9) circle (.1); \draw[my grey] (5.1,14.9) -- (4.9,14.9); \draw[->] (4.9,14.9) -- (4.1,14.1); \fill (4.1,15.1) circle (.1); \draw[my grey] (4.1,14.1) -- (4.1,15.1); \draw[->] (4.1,15.1) -- (4.9,15.9); \fill (5.1,15.9) circle (.1); \draw[my grey] (4.9,15.9) -- (5.1,15.9); \draw[->] (5.1,15.9) -- (5.9,15.1); \fill (6.1,15.1) circle (.1); \draw[my grey] (5.9,15.1) -- (6.1,15.1); \draw[->] (6.1,15.1) -- (6.9,15.9); \fill (7.1,15.9) circle (.1); \draw[my grey] (6.9,15.9) -- (7.1,15.9); \draw[->] (7.1,15.9) -- (7.9,15.1); \fill (3.9,15.9) circle (.1); \draw[my grey] (7.9,15.1) -- (3.9,15.9); \draw[->] (3.9,15.9) -- (3.1,15.1); \fill (2.9,15.1) circle (.1); \draw[my grey] (3.1,15.1) -- (2.9,15.1); \draw[->] (2.9,15.1) -- (2.1,15.9); \fill (1.9,15.9) circle (.1); \draw[my grey] (2.1,15.9) -- (1.9,15.9); \draw[->] (1.9,15.9) -- (1.1,15.1); \fill (0.9,15.1) circle (.1); \draw[my grey] (1.1,15.1) -- (0.9,15.1); \draw[->] (0.9,15.1) -- (0.1,15.9); \fill (0.1,14.9) circle (.1); \draw[my grey] (0.1,15.9) -- (0.1,14.9); \draw[->] (0.1,14.9) -- (0.9,14.1); \fill (1.1,14.1) circle (.1); \draw[my grey] (0.9,14.1) -- (1.1,14.1); \draw[->] (1.1,14.1) -- (1.9,14.9); \fill (2.1,14.9) circle (.1); \draw[my grey] (1.9,14.9) -- (2.1,14.9); \draw[->] (2.1,14.9) -- (2.9,14.1); \fill (3.1,14.1) circle (.1); \draw[my grey] (2.9,14.1) -- (3.1,14.1); \draw[->] (3.1,14.1) -- (3.9,14.9); \fill (3.9,13.9) circle (.1); \draw[my grey] (3.9,14.9) -- (3.9,13.9); \draw[->] (3.9,13.9) -- (3.1,13.1); \fill (2.9,13.1) circle (.1); \draw[my grey] (3.1,13.1) -- (2.9,13.1); \draw[->] (2.9,13.1) -- (2.1,13.9); \fill (1.9,13.9) circle (.1); \draw[my grey] (2.1,13.9) -- (1.9,13.9); \draw[->] (1.9,13.9) -- (1.1,13.1); \fill (0.9,13.1) circle (.1); \draw[my grey] (1.1,13.1) -- (0.9,13.1); \draw[->] (0.9,13.1) -- (0.1,13.9); \fill (0.1,12.9) circle (.1); \draw[my grey] (0.1,13.9) -- (0.1,12.9); \draw[->] (0.1,12.9) -- (0.9,12.1); \fill (1.1,12.1) circle (.1); \draw[my grey] (0.9,12.1) -- (1.1,12.1); \draw[->] (1.1,12.1) -- (1.9,12.9); \fill (2.1,12.9) circle (.1); \draw[my grey] (1.9,12.9) -- (2.1,12.9); \draw[->] (2.1,12.9) -- (2.9,12.1); \fill (3.1,12.1) circle (.1); \draw[my grey] (2.9,12.1) -- (3.1,12.1); \draw[->] (3.1,12.1) -- (3.9,12.9); \end{tikzpicture} \end{center} %------------------------------------------------------------------------------ \end{document} % Local variables: % compile-command: "latexmk -file-line-error -pdf pictures.tex" % End: Math-PlanePath-129/devel/dragon.pl0000644000175000017500000027131113031323420014610 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2016 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use List::MoreUtils; use POSIX 'floor'; use Math::BaseCnv; use Math::Libm 'M_PI', 'hypot', 'cbrt'; use List::Util 'min', 'max', 'sum'; use Math::PlanePath::DragonCurve; use Math::PlanePath::Base::Digits 'round_down_pow'; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::KochCurve; *_digit_join_hightolow = \&Math::PlanePath::KochCurve::_digit_join_hightolow; use lib 'xt'; use MyOEIS; use Memoize; # uncomment this to run the ### lines # use Smart::Comments; # A003229 area left side extra over doubling a(n) = a(n-1) + 2*a(n-3). # A003230 area enclosed Expansion of 1/((1-x)*(1-2*x)*(1-x-2*x^3)). # A003476 right boundary squares a(n) = a(n-1) + 2a(n-3). # A003477 area of connected blob Expansion of 1/((1-2x)(1+x^2)(1-x-2x^3)). # A003478 area on left side Expansion of 1/(1-2x)(1-x-2x^3 ). # A003479 join area Expansion of 1/((1-x)*(1-x-2*x^3)). # A203175 left boundary squares # A227036 # A077949 join area increments { # right boundary N my $path = Math::PlanePath::DragonCurve->new; my %non_values; my %n_values; my @n_values; my @values; foreach my $k (5) { my $n_limit = 2**$k; print "k=$k n_limit=$n_limit\n"; foreach my $n (0 .. $n_limit-1) { $non_values{$n} = 1; } my $points = MyOEIS::path_boundary_points ($path, $n_limit, side => 'right', ); ### $points for (my $i = 0; $i+1 <= $#$points; $i++) { my ($x,$y) = @{$points->[$i]}; my ($x2,$y2) = @{$points->[$i+1]}; # my @n_list = $path->xy_to_n_list($x,$y); my @n_list = path_xyxy_to_n($path, $x,$y, $x2,$y2); foreach my $n (@n_list) { delete $non_values{$n}; if ($n <= $n_limit) { $n_values{$n} = 1; } my $n2 = Math::BaseCnv::cnv($n,10,2); my $pred = $path->_UNDOCUMENTED__n_segment_is_right_boundary($n); my $diff = $pred ? '' : ' ***'; if ($k <= 5 || $diff) { print "$n $n2$diff\n"; } } } @n_values = keys %n_values; @n_values = sort {$a<=>$b} @n_values; my @non_values = keys %non_values; @non_values = sort {$a<=>$b} @non_values; my $count = scalar(@n_values); print "count $count\n"; # push @values, $count; @values = @n_values; foreach my $n (@non_values) { my $pred = $path->_UNDOCUMENTED__n_segment_is_right_boundary($n); my $diff = $pred ? ' ***' : ''; my $n2 = Math::BaseCnv::cnv($n,10,2); if ($k <= 5 || $diff) { print "non $n $n2$diff\n"; } } # @values = @non_values; # print "func "; # foreach my $i (0 .. $count-1) { # my $n = $path->_UNDOCUMENTED__right_boundary_i_to_n($i); # my $n2 = Math::BaseCnv::cnv($n,10,2); # print "$n,"; # } # print "\n"; print "vals "; foreach my $i (0 .. $count-1) { my $n = $values[$i]; my $n2 = Math::BaseCnv::cnv($n,10,2); print "$n,"; } print "\n"; } # @values = MyOEIS::first_differences(@values); splice @values,0,16; # shift @values; # shift @values; print join(',',@values),"\n"; require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, verbose=>1); exit 0; sub path_xyxy_to_n { my ($path, $x1,$y1, $x2,$y2) = @_; ### path_xyxy_to_n(): "$x1,$y1, $x2,$y2" my @n_list = $path->xy_to_n_list($x1,$y1); ### @n_list my $arms = $path->arms_count; foreach my $n (@n_list) { my ($x,$y) = $path->n_to_xy($n + $arms); if ($x == $x2 && $y == $y2) { return $n; } } return; } } { # Midpoint tiling, PNG require Math::PlanePath::DragonMidpoint; require Image::Base::Text; require Image::Base::PNGwriter; my $scale = 4; my $arms = 1; my $path = Math::PlanePath::DragonMidpoint->new (arms => $arms); # my $width = 78; # my $height = 48; # my $xoffset = $width/2; # my $yoffset = $height/2; # my $image = Image::Base::Text->new (-width => $width, # -height => $height); my $width = 1000; my $height = 800; my $xoffset = $width/2; my $yoffset = $height/2; my $image = Image::Base::PNGwriter->new (-width => $width, -height => $height); my $colour = '#00FF00'; my ($nlo,$nhi) = $path->rect_to_n_range(-$xoffset,-$yoffset, $xoffset,$yoffset); $nhi = 16384*2; print "nhi $nhi\n"; for (my $n = 0; $n <= $nhi; $n++) { # next if int($n/$arms) % 2; next unless int($n/$arms) % 2; my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+$arms); $x1 *= $scale; $y1 *= $scale; $x2 *= $scale; $y2 *= $scale; $x1 += $xoffset; $x2 += $xoffset; $y1 += $yoffset; $y2 += $yoffset; $image->line($x1,$y1,$x2,$y2,$colour); } # $image->save('/dev/stdout'); $image->save('/tmp/x.png'); system('xzgv /tmp/x.png'); exit 0; } { # DragonMidpoint abs(dY) sequence # A073089 n=N+2 value = lowbit(N) XOR bit-above-lowest-zero(N) # dX = 1 - A073089 inverse require Math::NumSeq::PlanePathDelta; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => 'DragonMidpoint', delta_type => 'dY'); my @values; foreach (0 .. 64) { my ($i,$value) = $seq->next; my $p = $i+2; # while ($p && ! ($p&1)) { # $p/=2; # } my $v = calc_n_midpoint_vert($i+1); printf "%d %d %7b\n", abs($value), $v, $p; push @values, 1-abs($value); } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, verbose => 1); exit 0; } { # DragonMidpoint abs(dY) sequence require Math::PlanePath::DragonMidpoint; my $path = Math::PlanePath::DragonMidpoint->new; foreach my $n (0 .. 64) { my ($x,$y) = $path->n_to_xy($n); my ($nx,$ny) = $path->n_to_xy($n+1); if ($nx == $x) { my $p = $n+2; # while ($p && ! ($p&1)) { # $p/=2; # } my $v = calc_n_midpoint_vert($n); printf "%d %7b\n", $v, $p; } } exit 0; sub calc_n_midpoint_vert { my ($n) = @_; if ($n < 0) { return 0; } my $vert = ($n & 1); my $right = calc_n_turn($n); return ((($vert && !$right) || (!$vert && $right)) ? 0 : 1); } # return 0 for left, 1 for right sub calc_n_turn { my ($n) = @_; my ($mask,$z); $mask = $n & -$n; # lowest 1 bit, 000100..00 $z = $n & ($mask << 1); # the bit above it my $turn = ($z == 0 ? 0 : 1); # printf "%b %b %b %d\n", $n,$mask, $z, $turn; return $turn; } } { # direction which curve enters and leaves an X axis point # all 4 arms # require Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new (arms => 4); my $width = 30; my ($n_lo, $n_hi) = $path->rect_to_n_range(0,0,$width+2,0); my (@enter, @leave); print "n_hi $n_hi\n"; for my $n (0 .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); if ($y == 0 && $x >= 0) { { my ($nx,$ny) = $path->n_to_xy($n+4); if ($ny > $y) { $leave[$x] .= 'u'; } if ($ny < $y) { $leave[$x] .= 'd'; } if ($nx > $x) { $leave[$x] .= 'r'; } if ($nx < $x) { $leave[$x] .= 'l'; } } if ($n >= 4) { my ($px,$py) = $path->n_to_xy($n-4); if ($y > $py) { $enter[$x] .= 'u'; } if ($y < $py) { $enter[$x] .= 'd'; } if ($x > $px) { $enter[$x] .= 'r'; } if ($x < $px) { $enter[$x] .= 'l'; } } } } foreach my $x (0 .. $width) { print "$x ",sort_str($enter[$x])," ",sort_str($leave[$x]),"\n"; } sub sort_str { my ($str) = @_; if (! defined $str) { return '-'; } return join ('', sort split //, $str); } exit 0; } { # repeat/unrepeat 0,1 require Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new; foreach my $n (0 .. 256) { my @n_list = $path->n_to_n_list($n); my $num = scalar(@n_list) - 1; print "$num,"; } exit 0; } { # repeat points require Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new; my %seen; my %first; foreach my $n (0 .. 2**10 - 1) { my ($x, $y) = $path->n_to_xy ($n); my @n_list = $path->xy_to_n_list($x,$y); next unless $n_list[0] == $n; next unless @n_list >= 2; my $dn = abs($n_list[0] - $n_list[1]); ++$seen{$dn}; $first{$dn} ||= "$x,$y"; } foreach my $dn (sort {$a<=>$b} keys %seen) { my $dn2 = sprintf '%b', $dn; print "dN=${dn}[$dn2] first at $first{$dn} count $seen{$dn}\n"; } my @seen = sort {$a<=>$b} keys %seen; print join(',',@seen),"\n"; foreach (@seen) { $_ /= 4; } print join(',',@seen),"\n"; exit 0; } { # unrepeated points require Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new; foreach my $n (0 .. 256) { my @n_list = $path->n_to_n_list($n); next unless @n_list == 1; #printf "%9b\n", $n; print "$n,"; } exit 0; } { # area left side = first differences my $path = Math::PlanePath::DragonCurve->new; my $prev = 0; $| = 1; foreach my $k (0 .. 15) { my $a = A_from_path($path,$k); my $al = A_from_path($path,$k) - $prev; print "$al, "; $prev = $a; } print "\n"; exit 0; } { # boundary squares # k=0 k=1 * k=2 # | # left=1 * left=1 *---* left=2 # right=1 | right=2 | right=3 # *---* *---* *---* # my $path = Math::PlanePath::DragonCurve->new; foreach my $side ('left', 'right', ) { my @values; foreach my $k ( # 1, 0 .. 10 ) { my $n_limit = 2**$k; # print "k=$k n_limit=$n_limit\n"; my $points = MyOEIS::path_boundary_points ($path, $n_limit, side => $side, ); # if ($side eq 'left') { # @$points = reverse @$points; # } my %seen; my $count_edges = 0; my $count_squares = 0; foreach my $i (1 .. $#$points) { my $p1 = $points->[$i-1]; my $p2 = $points->[$i]; my ($x1,$y1) = @$p1; my ($x2,$y2) = @$p2; ### edge: "$x1,$y1 to $x2,$y2" my $dx = $x2-$x1; my $dy = $y2-$y1; my $sx = 2*$x1 + ($dx + $dy); my $sy = 2*$y1 + ($dy - $dx); ### square: "$sx,$sy" $count_edges++; if (! $seen{"$sx,$sy"}++) { $count_squares++; } } print "k=$k edges=$count_edges squares=$count_squares\n"; push @values, $count_squares; } # shift @values; shift @values; shift @values; shift @values; require Math::OEIS::Grep; Math::OEIS::Grep->search(array=>\@values); } exit 0; } { # convex hull iterations # require Math::Geometry::Planar; my $points = [ [0,0], [1,0], [1,1] ]; my $nx = 1; my $ny = 1; foreach my $k (1 .. 20) { ($nx,$ny) = ($nx-$ny, $ny+$nx); # add rotate +90 my $num_points = scalar(@$points); print "k=$k nxy=$nx,$ny count=$num_points\n"; my @new_points = @$points; foreach my $p (@$points) { my ($x,$y) = @$p; ($x,$y) = ($y,-$x); # rotate -90 $x += $nx; $y += $ny; print " $x,$y"; push @new_points, [ $nx + $x, $ny + $y ]; } print "\n"; $points = \@new_points; # foreach my $i (0 .. $#new_points) { # my $p = $new_points[$i]; # my ($x,$y) = @$p; # } my $planar = Math::Geometry::Planar->new; $planar->points($points); $planar = $planar->convexhull2; $points = $planar->points; next if @$points < 10; my $max_i = 0; my $max_p = $points->[0]; foreach my $j (1 .. $#$points) { if ($points->[$j]->[0] > $max_p->[0] || ($points->[$j]->[0] == $max_p->[0] && $points->[$j]->[1] < $max_p->[1])) { $max_i = $j; $max_p = $points->[$j]; } } $points = points_sort_by_dir($points, [$nx,$ny]); foreach my $i (0 .. $#$points) { my $p = $points->[$i - $max_i]; my ($x,$y) = @$p; print " $x,$y"; } print "\n"; } exit 0; sub points_sort_by_dir { my ($points, $point_start) = @_; ### $points require Math::NumSeq::PlanePathDelta; my $start = Math::NumSeq::PlanePathDelta::_dxdy_to_dir4(@$point_start) + 0; return [ sort { my $a_dir = (Math::NumSeq::PlanePathDelta::_dxdy_to_dir4(@$a) + $start) % 4; my $b_dir = (Math::NumSeq::PlanePathDelta::_dxdy_to_dir4(@$b) + $start) % 4; $a_dir <=> $b_dir } @$points ]; } } { # mean X,Y # at 2/5 - 1/5*i relative to endpoint require Math::Complex; my $path = Math::PlanePath::DragonCurve->new; my @values; foreach my $k (0 .. 30) { my ($n_start, $n_end) = $path->level_to_n_range($k); my $x_total = 0; my $y_total = 0; foreach my $n ($n_start .. $n_end) { my ($x,$y) = $path->n_to_xy($n); $x_total += $x; $y_total += $y; } my ($x_end,$y_end) = $path->n_to_xy($n_end); $x_total -= $x_end/2; $y_total -= $y_end/2; my $total = 2**$k; my $x = $x_total / $total; my $y = $y_total / $total; my $f = Math::Complex->make($x,$y); my $rot = Math::Complex::root(1,8,$k); my $div = Math::Complex->make($x_end,$y_end); my $fr = $f / $div; print "k=$k X=$x_total Y=$y_total x=$x y=$y\n"; print " f=$f rot=$rot div=$div $fr\n"; print " fr=$fr\n"; push @values, $y_total; } shift @values; shift @values; shift @values; shift @values; require Math::OEIS::Grep; Math::OEIS::Grep->search(array=>\@values); exit 0; } { # mean X,Y by replication # # 1/2 --- 1/2 = .5, 0 # # 1/2 X = (1+1/2) / 2 = 3/4 # | Y = 1/2 / 2 = 1/4 # 1/2 --- 1 my $fx = 0; my $fy = 0; for my $k (0 .. 40) { my ($ax,$ay) = (($fx + $fy)/sqrt(2), # rotate -45 ($fy - $fx)/sqrt(2)); my ($bx,$by) = ((-$fx + $fy)/sqrt(2) + 1, # rotate -135 (-$fy - $fx)/sqrt(2)); print "$fx $fy $ax $ay $bx $by\n"; ($fx,$fy) = ($ax/2 + $bx/2, $ay/2 + $by/2); } exit 0; } { # fractal convex hull Benedek and Panzone # perimeter 4.12927310015371 # = (9 + sqrt(13) + sqrt(26) + 5*sqrt(2)) / 6 # require Math::BigRat; require Math::Geometry::Planar; my $polygon = Math::Geometry::Planar->new; my $points = [ [Math::BigRat->new('2/3')->copy, Math::BigRat->new('2/3')->copy], [Math::BigRat->new('0')->copy, Math::BigRat->new('2/3')->copy], [Math::BigRat->new('-1/3')->copy, Math::BigRat->new('1/3')->copy], [Math::BigRat->new('-1/3')->copy, Math::BigRat->new('0')->copy], [Math::BigRat->new('-1/6')->copy, Math::BigRat->new('-1/6')->copy], [Math::BigRat->new('2/3')->copy, Math::BigRat->new('-1/3')->copy], [Math::BigRat->new('1')->copy, Math::BigRat->new('-1/3')->copy], [Math::BigRat->new('7/6')->copy, Math::BigRat->new('-1/6')->copy], [Math::BigRat->new('7/6')->copy, Math::BigRat->new('0')->copy], [Math::BigRat->new('5/6')->copy, Math::BigRat->new('3/6')->copy], ]; $polygon->points($points); print "area = ",$polygon->area,"\n"; print "perimeter = ",$polygon->perimeter,"\n"; my %root; foreach my $i (0 .. $#$points) { my $hsquared = ($points->[$i]->[0] - $points->[$i-1]->[0])**2 + ($points->[$i]->[1] - $points->[$i-1]->[1])**2; $hsquared *= 36; my $root = square_free_part($hsquared); my $factor = sqrt($hsquared / $root); $root{$root} ||= 0; $root{$root} += $factor; print "$hsquared $root $factor\n"; } foreach my $root (keys %root) { print "$root{$root} * sqrt($root)\n"; } print "\nminrectangle\n"; my $minrect = $polygon->minrectangle; my $p = $minrect->points; print "$p->[0]->[0],$p->[0]->[1] $p->[1]->[0],$p->[1]->[1] $p->[2]->[0],$p->[2]->[1] $p->[3]->[0],$p->[3]->[1]\n"; print "area = ",$minrect->area,"\n"; print "perimeter = ",$minrect->perimeter,"\n"; exit 0; sub square_free_part { my ($n) = @_; my $ret = 1; for (my $p = 2; $p <= $n; $p++) { while ($n % ($p*$p) == 0) { $n /= ($p*$p); } if ($n % $p == 0) { $ret *= $p; $n /= $p; } } return $ret; } } { # (i-1)^k use lib 'xt'; require MyOEIS; require Math::Complex; my $b = Math::Complex->make(-1,1); my $c = Math::Complex->make(1); my @values = (0,0,0); foreach (0 .. 160) { push @values, $c->Re; $c *= $b; } print join(',',@values),"\n"; Math::OEIS::Grep->search(array=>\@values); print "\n"; exit 0; } { # L,R,T,U,V by path boundary require MyOEIS; $| = 1; # L my $path = Math::PlanePath::DragonCurve->new; foreach my $part ('B','A','L','R','T','U','V') { print "$part "; my $name = "${part}_from_path"; my $coderef = __PACKAGE__->can($name) || die $name; my @values; foreach my $k (0 .. 14) { my $value = $coderef->($path,$k); push @values, $value; print "$value,"; # if ($value < 10) { print "\n",join(' ',map{join(',',@$_)} @$points),"\n"; } } print "\n"; shift @values; shift @values; shift @values; shift @values; shift @values; Math::OEIS::Grep->search (array => \@values, name => $part); print "\n"; } exit 0; sub A_from_path { my ($path, $k) = @_; return MyOEIS::path_enclosed_area($path, 2**$k); } sub B_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit); return scalar(@$points); } sub L_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit, side => 'left'); return scalar(@$points) - 1; } sub R_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my $points = MyOEIS::path_boundary_points($path, $n_limit, side => 'right'); return scalar(@$points) - 1; } sub T_from_path { my ($path, $k) = @_; # 2 to 4 my $n_limit = 2**$k; my ($x,$y) = $path->n_to_xy(2*$n_limit); my ($to_x,$to_y) = $path->n_to_xy(4*$n_limit); my $points = MyOEIS::path_boundary_points_ft($path, 4*$n_limit, $x,$y, $to_x,$to_y, dir => 2); return scalar(@$points) - 1; } sub U_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my ($x,$y) = $path->n_to_xy(3*$n_limit); my ($to_x,$to_y) = $path->n_to_xy(0); my $points = MyOEIS::path_boundary_points_ft($path, 4*$n_limit, $x,$y, $to_x,$to_y, dir => 1); return scalar(@$points) - 1; } sub V_from_path { my ($path, $k) = @_; my $n_limit = 2**$k; my ($x,$y) = $path->n_to_xy(6*$n_limit); my ($to_x,$to_y) = $path->n_to_xy(3*$n_limit); my $points = MyOEIS::path_boundary_points_ft($path, 8*$n_limit, $x,$y, $to_x,$to_y, dir => 0); return scalar(@$points) - 1; } } { # drawing with Language::Logo require Language::Logo; require Math::NumSeq::PlanePathTurn; my $lo = Logo->new(update => 20, port => 8200 + (time % 100)); my $len = 20; my $level = 4; if (0) { my $seq = Math::NumSeq::PlanePathTurn->new(planepath=>'DragonCurve', turn_type => 'Right'); my $angle = 60; $lo->command("pendown"); $lo->command("color green"); $lo->command("right 90"); foreach my $n (0 .. 2**$level) { my ($i,$value) = $seq->next; my $turn_angle = ($value ? $angle : -$angle); $lo->command("forward $len; right $turn_angle"); } } { my $seq = Math::NumSeq::PlanePathTurn->new(planepath=>'TerdragonCurve', turn_type => 'Right'); my $angle = 120; $lo->command("penup"); $lo->command("setxy 400 200"); $lo->command("seth 90"); $lo->command("color red"); $lo->command("pendown"); foreach my $n (0 .. 3**$level-1) { my ($i,$value) = $seq->next; my $turn_angle = ($value ? $angle : -$angle); $lo->command("forward $len; right $turn_angle"); } $lo->command("home"); $lo->command("hideturtle"); } $lo->disconnect("Finished..."); exit 0; } { # arms=2 boundary # math-image --path=DragonCurve,arms=4 --expression='i<=67?i:0' --output=numbers_dash --size=50x80 # 5 # | # 6 --- 0,1,2,3 --- 4 # | # 7 my $path = Math::PlanePath::DragonCurve->new (arms=>4); sub Ba2_from_path { my ($path, $k) = @_; my ($n_start, $n_end) = $path->level_to_n_range($k); my $points = MyOEIS::path_boundary_points($path, $n_end); print join(" ", map{"$_->[0],$_->[1]"} @$points),"\n"; return scalar(@$points); } sub Aa2_from_path { my ($path, $k) = @_; my ($n_start, $n_end) = $path->level_to_n_range($k); return MyOEIS::path_enclosed_area($path, $n_end); } foreach my $k (1) { print "$k ",Ba2_from_path($path,$k),"\n"; # ," ",Aa2_from_path($path,$k) } exit 0; } { # poly trial division require Math::Polynomial; Math::Polynomial->string_config({ ascending => 1, fold_sign => 1 }); my $p; $p = Math::Polynomial->new(1,-4,5,-4,6,-4); # dragon area denom $p = Math::Polynomial->new(2,-5,3,-4,5); # dragon visited $p = Math::Polynomial->new(1,-3,-1,-5); # ComplexMinus r=2 boundary $p = Math::Polynomial->new(6, -4,, 2, -8); # DragonMidpoint boundary $p = Math::Polynomial->new(1,2,0,-1,1,0,2,4,-1); # C curve e $p = Math::Polynomial->new(2,2,4,8,2,4); # Ba2 gf print "$p\n"; foreach my $a (-15 .. 15) { foreach my $b (1 .. 15) { next if $a == 0 && $b == 0; next if abs($a) == 1 && $b == 0; my $d = Math::Polynomial->new($a,$b); my ($q,$r) = $p->divmod($d); if ($r == 0 && poly_is_integer($q)) { print "/ $d = $q rem $r\n"; $p = $q; } } } foreach my $a (-15 .. 15) { foreach my $b (-15 .. 15) { foreach my $c (1 .. 15) { next if $a == 0 && $b == 0 && $c == 0; next if abs($a) == 1 && $b == 0 && $c == 0; my $d = Math::Polynomial->new($a,$b,$c); my ($q,$r) = $p->divmod($d); if ($r == 0 && poly_is_integer($q)) { print "/ $d = $q rem $r\n"; $p = $q; } } } } print "final $p\n"; exit 0; sub poly_is_integer { my ($p) = @_; foreach my $coeff ($p->coefficients) { unless ($coeff == int($coeff)) { return 0; } } return 1; } } { my $path = Math::PlanePath::DragonCurve->new; sub level_to_join_area { my ($level) = @_; { if ($level == 0) { return 0; } if ($level == 1) { return 0; } if ($level == 2) { return 0; } if ($level == 3) { return 1; } my $j0 = 0; my $j1 = 0; my $j2 = 0; my $j3 = 1; foreach (4 .. $level) { ($j3,$j2,$j1,$j0) = (2*$j3 - $j2 + 2*$j1 - 2*$j0, $j3, $j2, $j1); } return $j3; } return ($path->_UNDOCUMENTED_level_to_right_line_boundary($level+1) - $path->_UNDOCUMENTED_level_to_left_line_boundary($level+1)) / 4; return ($path->_UNDOCUMENTED_level_to_line_boundary($level) / 2 - $path->_UNDOCUMENTED_level_to_line_boundary($level+1) / 4); return ($path->_UNDOCUMENTED_level_to_enclosed_area($level+1) - 2*$path->_UNDOCUMENTED_level_to_enclosed_area($level)); } sub level_to_join_points_by_formula { my ($level) = @_; { if ($level == 0) { return 1; } if ($level == 1) { return 1; } if ($level == 2) { return 1; } if ($level == 3) { return 2; } my $j0 = 1; my $j1 = 1; my $j2 = 1; my $j3 = 2; foreach (4 .. $level) { ($j3,$j2,$j1,$j0) = (2*$j3 - $j2 + 2*$j1 - 2*$j0, $j3, $j2, $j1); } return $j3; } return level_to_join_area($level) + 1; } my @values; my $prev_visited = 0; foreach my $k (0 .. 11) { my $n_end = 2**$k; # my %seen; # foreach my $n (0 .. $n_end) { # my ($x,$y) = $path->n_to_xy($n); # $seen{"$x,$y"}++; # } my $u = $path->_UNDOCUMENTED_level_to_u_left_line_boundary($k); my $ru = $path->_UNDOCUMENTED_level_to_u_right_line_boundary($k); my $bu = $path->_UNDOCUMENTED_level_to_u_line_boundary($k); my $ja = level_to_join_area($k); my $join_points = path_level_to_join_points($path,$k); my $join_area = $join_points - 1; my $j = level_to_join_points_by_formula($k); my $da = level_to_denclosed($k); my $area = $path->_UNDOCUMENTED_level_to_enclosed_area($k); my $area_next = $path->_UNDOCUMENTED_level_to_enclosed_area($k+1); my $darea = $area_next - $area; my $v = $path->_UNDOCUMENTED_level_to_visited($k); my $visited = $v; # MyOEIS::path_n_to_visited($path,$n_end); my $dvisited = $visited - $prev_visited; my $singles = 0 && MyOEIS::path_n_to_singles($path, $n_end-1); my $doubles = 0 && MyOEIS::path_n_to_doubles($path, $n_end-1); print "$k join=$join_points,$j da=$area_next-$area=$da $visited $v\n"; push @values, ($dvisited-1)/2; $prev_visited = $visited; # dvisited = 2,1,2,4,7,13,25,47,89,171,329,635,1233,2403,4697 # dvisited-1 = 1,0,1,3,6,12,24,46,88,170,328,634,1232,2402,4696 # (dvisited-1)/2 = 0.5,0,0.5,1.5, 3,6,12,23,44,85,164,317 # (dvisited-1)/2 differs from A001630 tetranacci at k=11 } print join(',',@values),"\n"; shift @values; shift @values; shift @values; shift @values; shift @values; Math::OEIS::Grep->search(array => \@values); exit 0; sub level_to_denclosed { my ($k) = @_; return ($path->_UNDOCUMENTED_level_to_enclosed_area($k+1) - $path->_UNDOCUMENTED_level_to_enclosed_area($k)); } sub path_level_to_join_points { my ($path, $k) = @_; my $n_level = 2**$k; my $join; foreach my $n ($n_level .. 2*$n_level) { foreach my $n ($path->xy_to_n_list($path->n_to_xy($n))) { $join += ($n <= $n_level); } } return $join; } } { # singles positions my $path = Math::PlanePath::DragonCurve->new; foreach my $k (0 .. 6) { my $n_end = 2**$k; foreach my $n (0 .. $n_end) { my @n_list = $path->n_to_n_list($n); if (@n_list == 1 || (@n_list == 2 && $n_list[1] > $n_end)) { # my $n = $n ^ ($n >> 1); my $str = sprintf "%8b", $n; my $match = ($str =~ /0101|0001/ ? ' ****' : ''); print "$str $match\n"; } } print "\n"; } exit 0; } { # root of x^3 - x^2 - 2 # real root D^(1/3) + (1/9)*D^(-1/3) + 1/3 = 1.6956207695598620 # where D=28/27 + (1/9)*sqrt(29*3) = 28/27 + sqrt(29/27) use constant D => 28/27 + sqrt(29/27); use constant REAL_ROOT => D**(1/3) + (1/9)*D**(-1/3) + 1/3; print "REAL_ROOT: ",REAL_ROOT,"\n"; # x^3 - x^2 - 2 # x = y+1/3 # y^3 - 1/3*y - 56/27 = 0 # y^3 + p*y + q = 0 # p=-1/3; q=-56/27 # p^3/27 + q^2/4 = 29/27 # q/2 = 28/27 # y=a-b # a^3 - 3*b*a^2 + 3*b^2*a + p*a + -b^3 - p*b + q = 0 # a^3 - b^3 - a(3*b*a - p) + b(3*b*a - p) + q = 0 # a^3 - b^3 + (b-a)(3*b*a - p) + q = 0 # a^3 - b^3 + (a-b)(-3*b*a + p) + q = 0 # take -3*b*a + p = 0 so p = 3ab # a^3 - b^3 + q = 0 # 27a^6 - (3ab)^3 + 27a^3q = 0 times (3a)^3 # 27a^6 - p^3 + 27a^3*q = 0 # 27a^6 + 27a^3*q - p^3 = 0 quadratic in a^3 # A = 27; B = 27*q; C = -p^3 # a^3 = (-27*q +/- sqrt((27*q)^2 - 4*27*-p^3) ) / 2*27 # = -q/2 +/- sqrt((27*q)^2 - 4*27*-p^3)/2*27 # = -q/2 +/- sqrt(q^2/4 - -p^3/27) # a^3 = -q/2 +/- sqrt(q^2/4 + p^3/27) # # 27*a^3*b^3 = p^3 # b^3 = p^3/27*a^3 # b^3 = p^3 / (-q/2 +/- sqrt(q^2/4 + p^3/27)) # b^3 = p^3 * (-q/2 -/+ sqrt(q^2/4 + p^3/27)) # / 27*((-q/2)^2 - (q^2/4 + p^3/27)) # / 27*(q^2/4 - q^2/4 - p^3/27) # / - p^3 # b^3 = q/2 +/- sqrt(q^2/4 + p^3/27) my $p = -1/3; my $q = -56/27; my $a3 = -$q/2 + sqrt($q**2/4 + $p**3/27); print "a^3 $a3\n"; my $a3poly = nearly_zero(27*($a3**2) + 27*$a3*$q - $p**3); print "a^3 poly: $a3poly\n"; my $b3 = $q/2 + sqrt($q**2/4 + $p**3/27); my $b3p = $p**3 / (27*$a3); print "b^3 $b3 $b3p\n"; my $a = cbrt($a3); my $b = cbrt($b3); print "a $a b $b\n"; print "a-b ",$a-$b,"\n"; my $y = cbrt(-$q/2 + sqrt($p**3/27 + $q**2/4)) - cbrt($q/2 + sqrt($p**3/27 + $q**2/4)); print "y $y\n"; my $ypoly = nearly_zero($y**3 - 1/3*$y - 56/27); print "y poly $ypoly\n"; my $x = $y+1/3; print "x $x\n"; my $xpoly = nearly_zero($x**3 - $x&&2 - 2); print "x poly $xpoly\n"; # y = cbrt(28/27 + sqrt(29/27)) + cbrt(28/27 - sqrt(29/27)) # x = 1/3 + cbrt(28/27 + sqrt(29/27)) + cbrt(28/27 - sqrt(29/27)) my $yf = cbrt(28/27 + sqrt(29/27)) + cbrt(28/27 - sqrt(29/27)); my $xf = 1/3 + cbrt(28/27 + sqrt(29/27)) + cbrt(28/27 - sqrt(29/27)); print "yf $yf\n"; print "xf $xf\n"; # cbrt(x)=(x^(1/3)) # f = 1/3 + cbrt(28/27 + sqrt(29/27)) + cbrt(28/27 - sqrt(29/27)) # (x^3 - x^2 - 2)/(x-f) # x^3 - x^2 - 2 quot = x^2 # - x^3 + x^2*f # = (-1+f)x^2 - 2 quot = x^2 + (-1+f)x # - (-1+f)x^2 + (-1+f)fx # = (-1+f)fx - 2 quot = x^2 + (-1+f)x + (-1+f)f # - (-1+f)fx + (-1+f)ff # = 0 since (-1+f)ff = f^3-f^2 = 2 # # (x^2 + (-1+f)*x + (-1+f)*f)*(x-f) + f^3-f^2-2 # = x^3 - x^2 - 2 # # x^2 + (f-1)*x + f*(f-1) # xb = (1-f + sqrt((f-1)^2 - 4f(f-1)))/2 # = (1-f + sqrt(f^2-2f+1 - 4f^2 +4f))/2 # xb = (1-f + sqrt(-3*f^2 + 2*f + 1))/2 # xb = (1-f + sqrt((3*f+1)*(-f+1)))/2 # xb^3 - xb^2 - 2 require Math::Complex; my $f = Math::Complex->new($x); my $xb = (1-$f + sqrt(-3*$f*$f + 2*$f + 1))/2; my $xc = (1-$f - sqrt(-3*$f*$f + 2*$f + 1))/2; print "xb $xb\n"; print "xc $xc\n"; my $xbpoly = ($xb**3 - $xb**2 - 2); my $xcpoly = ($xc**3 - $xc**2 - 2); print "xb poly $xbpoly\n"; print "xc poly $xcpoly\n"; # y^3 - 1/3*y - 56/27 = 0 # f^3 - 1/3*f - 56/27 # f = cbrt(28/27 + sqrt(29/27)) + cbrt(28/27 - sqrt(29/27)) # y^3 - 1/3*y - 56/27 - (y^2 + f*y + f^2 - 1/3)*(y-f) -(f^3-1/3*f-56/27) # y^2 + f*y + f^2-1/3 # yb = (-f + sqrt(f^2 - 4*(f^2-1/3)))/2 # = (-f + sqrt(f^2 - 4*f^2 + 4/3))/2 # yb = (-f + sqrt(-3*f^2 + 4/3))/2 # yb^3 - 1/3*yb - 56/27 $f = Math::Complex->new($y); my $yb = (-$f + sqrt(-3*$f*$f + 4/3))/2; my $yc = (-$f - sqrt(-3*$f*$f + 4/3))/2; print "yb $yb\n"; print "yc $yc\n"; my $ybpoly = nearly_zero($yb**3 - 1/3*$yb - 56/27); my $ycpoly = nearly_zero($yc**3 - 1/3*$yc - 56/27); print "yb poly $ybpoly\n"; print "yc poly $ycpoly\n"; # f^2 = (cbrt(28/27 + sqrt(29/27)) + cbrt(28/27 - sqrt(29/27)))^2 # = cbrt(28/27 + sqrt(29/27))^2 # + cbrt(28/27 - sqrt(29/27))^2 # + cbrt(28/27 + sqrt(29/27)) * cbrt(28/27 - sqrt(29/27)) # cbrt( (28/27 + sqrt(29/27))*(28/27 - sqrt(29/27)) ) # cbrt( (28/27)^2 - 29/27 ) exit 0; sub nearly_zero { my ($x) = @_; if (abs($x) < 1e-12) { return 0; } else { return $x; } } } { # 3 8 area=2 boundary=8 right # count=9 0,0 1,0 1,1 0,1 0,2 -1,2 -1,1 -2,1 -2,2 # 4 16 area=4 boundary=16 right # 5 32 area=9 boundary=28 right # 6 64 area=20 boundary=48 right # 7 128 area=43 boundary=84 right # 8 256 area=92 boundary=144 right # 9 512 area=195 boundary=244 right # 10 1024 area=408 boundary=416 right # 11 2048 area=847 boundary=708 right # 12 4096 area=1748 boundary=1200 right # 13 8192 area=3587 boundary=2036 right # 3 8 area=2 boundary=8 left # count=9 -2,2 -2,1 -1,1 -1,2 0,2 0,1 1,1 1,0 0,0 # 4 16 area=3 boundary=12 left # 5 32 area=5 boundary=20 left # 6 64 area=9 boundary=36 left # 7 128 area=15 boundary=60 left # 8 256 area=25 boundary=100 left # 9 512 area=43 boundary=172 left # 10 1024 area=73 boundary=292 left # 11 2048 area=123 boundary=492 left # 12 4096 area=209 boundary=836 left # 13 8192 area=355 boundary=1420 left # Left boundary/2 # A203175 a(n) = a(n-1) + 2*a(n-3) # Right boundary # A227036 = whole boundary # because R[k+1] = R[k]+L[k] = B[k-1] my $B_by_power = sub { my ($k) = @_; return 3.6 * REAL_ROOT ** $k; }; my ($R,$L,$T,$U,$V); $R = sub { my ($k) = @_; die if $k < 0; if ($k == 0) { return 1; } { if ($k == 1) { return 2; } if ($k == 2) { return 4; } if ($k == 3) { return 8; } if ($k == 4) { return 16; } # R[k+4] = 2*R[k+3] -R[k+2] + 2*R[k+1] - 2*R[k] ok return 2*$R->($k-1) - $R->($k-2) + 2*$R->($k-3) - 2*$R->($k-4); return $R->($k-1) - $R->($k-1) + $R->($k-2) + $R->($k-1) - $R->($k-2) + $R->($k-3) + $R->($k-1)-$R->($k-2) - $R->($k-4) + $R->($k-3)-$R->($k-4); return 2*$R->($k-1) - $R->($k-2) + 2*$R->($k-3) - 2*$R->($k-4); } return $R->($k-1) + $L->($k-1); }; $R = Memoize::memoize($R); $L = sub { my ($k) = @_; die if $k < 0; if ($k == 0) { return 1; } { if ($k == 1) { return 2; } if ($k == 2) { return 4; } if ($k == 3) { return 8; } # L[k+3] = L[k+2] + 2*L[k] ok return $L->($k-1) + 2*$L->($k-3); # L[k+3]-R[k+1] = L[k+2]-R[k] + L[k] ok return $R->($k-2) + $L->($k-1) - $R->($k-3) + $L->($k-3); } { if ($k == 1) { return 2; } return $R->($k-2) + $U->($k-2); } return $T->($k-1); }; $L = Memoize::memoize($L); $T = sub { my ($k) = @_; die if $k < 0; if ($k == 0) { return 2; } return $R->($k-1) + $U->($k-1); }; $T = Memoize::memoize($T); $U = sub { my ($k) = @_; die if $k < 0; if ($k == 0) { return 3; } # return $U->($k-1) + $L->($k-1); return $U->($k-1) + $V->($k-1); }; $U = Memoize::memoize($U); my $U2 = sub { my ($k) = @_; die if $k < 0; if ($k == 0) { return 3; } { if ($k == 1) { return 6; } if ($k == 2) { return 8; } if ($k == 3) { return 12; } if ($k == 4) { return 20; } # U[k+4] = 2*U[k+3] -U[k+2] + 2*U[k+1] - 2*U[k] ok return 2*$U->($k-1) - $U->($k-2) + 2*$U->($k-3) - 2*$U->($k-4); } # return $U->($k-1) + $L->($k-1); return $U->($k-1) + $V->($k-1); }; $U2 = Memoize::memoize($U2); my $U_from_LsubR = sub { my ($k) = @_; die if $k < 0; return $L->($k+2) - $R->($k); }; $V = sub { my ($k) = @_; if ($k == 0) { return 3; } return $T->($k-1); }; $V = Memoize::memoize($V); my $B = sub { my ($k) = @_; return $R->($k) + $L->($k); }; $B = Memoize::memoize($B); my $A = sub { my ($k) = @_; if ($k < 1) { return 0; } return 2**($k-1) - $B->($k)/4; }; foreach my $k (0 .. 20) { print $A->($k),", "; } print "\n"; my $path = Math::PlanePath::DragonCurve->new; my $prev_dl = 0; my $prev_ddl = 0; foreach my $k (0 .. 24) { # my $p = MyOEIS::path_boundary_length($path, 2**$k); # my $b = $B->($k); # my $r = $R->($k); # my $l = $L->($k); # my $t = $T->($k); # my $u = $U->($k); # my $u2 = $U2->($k); # my $u_lr = $U_from_LsubR->($k); # my $v = $V->($k); # print "$k $p $b R=$r L=$l T=$t U=$u,$u2,$u_lr V=$v\n"; # my $dl = $L->($k+1) - $L->($k); # my $ddl = $dl - $prev_dl; # printf "%28b\n", $ddl-$prev_ddl; # $prev_dl = $dl; # $prev_ddl = $ddl; my $b = $B->($k); my $best = $B_by_power->($k); my $f = $b/$best; print "$b $best $f\n"; } exit 0; } { # LLRR variation my $reverse = sub { my ($str) = @_; $str = reverse $str; $str =~ tr/+-/-+/; return $str; }; my $str = 'F'; while (length($str) < 8192) { $str = $str . '+' . $reverse->($str); # unfold left $str = $str . '+' . $reverse->($str); # unfold left $str = $str . '-' . $reverse->($str); # unfold right $str = $str . '-' . $reverse->($str); # unfold right } require Language::Logo; my $lo = Logo->new(update => 2, port => 8200 + (time % 100)); my $draw; $lo->command("right 45; backward 200; seth 90"); $lo->command("pendown; hideturtle"); my %char_to_command = (F => 'forward 5', '+' => 'left 90', '-' => 'right 90', ); foreach my $char (split //, $str) { ### $char $lo->command($char_to_command{$char}); } $lo->disconnect("Finished..."); exit 0; exit 0; } # { # [0,1,S 1,1,SW 1,0,W 0,0,- ]); # [1,1,SW 0,1,S 0,0,- 1,0,W ], # # [1,0,W 0,0,- 0,1,S 1,1,SW ], # my @yx_adj_x = ([0,0,- 1,0,W 1,1,SW 0,1,S ], # } { # visited 0,1 my $path = Math::PlanePath::DragonCurve->new; foreach my $y (reverse -16 .. 16) { foreach my $x (-32 .. 32) { print $path->xy_is_visited($x,$y) ? 1 : 0; } print "\n"; } exit 0; } { foreach my $arms (1 .. 4) { my $path = Math::PlanePath::DragonCurve->new (arms => $arms); foreach my $x (-50 .. 50) { foreach my $y (-50 .. 50) { my $v = !! $path->xy_is_visited($x,$y); my $n = defined($path->xy_to_n($x,$y)); $v == $n || die "arms=$arms x=$x,y=$y"; } } } exit 0; } { my @m = ([0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]); foreach my $arms (1 .. 4) { my $path = Math::PlanePath::DragonCurve->new (arms => $arms); foreach my $x (-50 .. 50) { foreach my $y (-50 .. 50) { next if $x == 0 && $y == 0; my $xm = $x+$y; my $ym = $y-$x; my $a1 = Math::PlanePath::DragonMidpoint::_xy_to_arm($xm,$ym); my $a2 = Math::PlanePath::DragonMidpoint::_xy_to_arm($xm-1,$ym+1); $m[$a1]->[$a2] = 1; } } } foreach my $i (0 .. $#m) { my $aref = $m[$i]; print "$i ",@$aref,"\n"; } exit 0; } { require Devel::TimeThis; require Math::PlanePath::DragonMidpoint; foreach my $arms (1 .. 4) { my $path = Math::PlanePath::DragonCurve->new (arms => $arms); { my $t = Devel::TimeThis->new("xy_is_visited() arms=$arms"); foreach my $x (0 .. 50) { foreach my $y (0 .. 50) { $path->xy_is_visited($x,$y); } } } { my $t = Devel::TimeThis->new("xy_to_n() arms=$arms"); foreach my $x (0 .. 50) { foreach my $y (0 .. 50) { $path->xy_to_n($x,$y); } } } } exit 0; } { # Dir4 is count_runs_1bits() require Math::NumSeq::PlanePathDelta; my $path = Math::PlanePath::DragonCurve->new; my $dir4_seq = Math::NumSeq::PlanePathDelta->new (planepath_object => $path, delta_type => 'Dir4'); foreach my $n (0 .. 64) { my $d = $dir4_seq->ith($n); my $c = count_runs_1bits($n*2+1) % 4; printf "%2d %d %d\n", $n, $d, $c; } my $n = 0b1100111101; print join(',',$path->n_to_dxdy($n)),"\n"; exit 0; } { # drawing two towards centre segment order my @values; print "\n"; my $draw; $draw = sub { my ($from, $to) = @_; my $mid = ($from + $to) / 2; if ($mid != int($mid)) { push @values, min($from,$to); } else { $draw->($from,$mid); $draw->($to,$mid); } }; $draw->(0, 64); print join(',',@values),"\n"; my %seen; foreach my $value (@values) { if ($seen{$value}++) { print "duplicate $value\n"; } } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); foreach my $i (0 .. $#values) { printf "%2d %7b\n", $i, $values[$i]; } exit 0; } { # drawing two towards centre with Language::Logo require Language::Logo; require Math::NumSeq::PlanePathTurn; my $lo = Logo->new(update => 20, port => 8200 + (time % 100)); my $draw; $lo->command("backward 130; hideturtle"); $draw = sub { my ($level, $length) = @_; if (--$level < 0) { $lo->command("pendown; forward $length; penup; backward $length"); return; } my $sidelen = $length / sqrt(2); $lo->command("right 45"); $draw->($level,$sidelen); $lo->command("left 45"); $lo->command("penup; forward $length"); $lo->command("right 135"); $draw->($level,$sidelen); $lo->command("left 135"); $lo->command("penup; backward $length"); }; $draw->(8, 300); $lo->disconnect("Finished..."); exit 0; } # { # # X,Y recurrence n = 2^k + rem # # X+iY(n) = (i+1)^k + (i+1)^k + # my $w = 8; # my $path = Math::PlanePath::DragonCurve->new; # foreach my $n (0 .. 1000) { # my ($x,$y) = $path->n_to_xy($n); # # } # exit 0; # sub high_bit { my ($n) = @_; my $bit = 1; while ($bit <= $n) { $bit <<= 1; } return $bit >> 1; } # } { # d(2n) = d(n)*(i+1) # d(2n+1) = d(2n) + 1-(transitions(2*$n) % 4) # 2n to 2n+1 is always horizontal # transitions(2n) is always even since return to 0 at the low end # # X(2n-1) \ = X(n) # X(2n) / # X(2n+1) \ = X(2n) + (-1) ** count_runs_1bits($n) # X(2n+2) / # # X(2n-1) \ = X(n) # X(2n) / # X(2n+1) \ = X(2n) + (-1) ** count_runs_1bits($n) # X(2n+2) / # X(n) = cumulative dx = (-1) ** count_runs_1bits(2n) # Y(n) = cumulative dy = (-1) ** count_runs_1bits(2n+1) # Dragon delta = bisection of count runs 1s # Alternate delta = bisection of count even runs 1s { require Math::NumSeq::OEIS; my $seq = Math::NumSeq::OEIS->new(anum=>'A005811'); # num runs my @array; sub A005811 { my ($i) = @_; while ($#array < $i) { my ($i,$value) = $seq->next; $array[$i] = $value; } return $array[$i]; } } my $path = Math::PlanePath::DragonCurve->new; foreach my $n (0 .. 32) { my ($x,$y) = $path->n_to_xy(2*$n+1); my ($x1,$y1) = $path->n_to_xy(2*$n+2); my $dx = $x1-$x; my $dy = $y1-$y; # my $transitions = transitions(2*$n); # my $c = 1 - (A005811(2*$n) % 4); # my $c = 1 - 2*(count_runs_1bits(2*$n) % 2); # my $c = (count_runs_1bits($n)%2 ? -1 : 1); # my $c = 2-(transitions(2*$n+1) % 4); # Y # my $c = (-1) ** count_runs_1bits(2*$n); # X my $c = - (-1) ** count_runs_1bits(2*$n+1); # Y printf "%6b %2d,%2d %d\n", $n, $dx,$dy, $c; } print "\n"; exit 0; } { # Recurrence high to low. # d(2^k + rem) = (i+1)^(k+1) - i*d(2^k-rem) # = (i+1) * (i+1)^k - i*d(2^k-rem) # = (i+1)^k + i*(i+1)^k - i*d(2^k-rem) # = (i+1)^k + i*((i+1)^k - d(2^k-rem)) require Math::Complex; # print mirror_across_k(Math::Complex->make(2,0),3); # exit 0; my $path = Math::PlanePath::DragonCurve->new; foreach my $n (0 .. 32) { my ($x,$y) = $path->n_to_xy($n); my $p = Math::Complex->make($x,$y); my $d = calc_d_by_high($n); printf "%6b %8s %8s %s\n", $n, $p,$d, $p-$d; } print "\n"; exit 0; sub calc_d_by_high { my ($n) = @_; if ($n == 0) { return 0; } my $k = high_bit_pos($n); my $pow = 1<<$k; my $rem = $n - $pow; ### $k ### $rem if ($rem == 0) { return i_plus_1_pow($k); } else { return i_plus_1_pow($k+1) + Math::Complex->make(0,-1) * calc_d_by_high($pow-$rem); } } sub high_bit_pos { my ($n) = @_; die "high_bit_pos $n" if $n <= 0; my $bit = 1; my $pos = 0; while ($n > 1) { $n >>= 1; $pos++; } return $pos; } sub i_plus_1_pow { my ($k) = @_; my $b = Math::Complex->make(1,1); my $c = Math::Complex->make(1); for (1 .. $k) { $c *= $b; } return $c; } # # no, not symmetric lengthwise # return i_plus_1_pow($k) # + Math::Complex->make(0,1) * mirror_across_k(calc_d_by_high($rem), # 4-$k); sub mirror_across_k { my ($c,$k) = @_; $k %= 8; $c *= i_plus_1_pow(8-$k); # ### c: "$c" $c = ~$c; # conjugate # ### conj: "$c" $c *= i_plus_1_pow($k); # ### mult: "$c" $c /= 16; # i_plus_1_pow(8) == 16 # ### ret: "$c" return $c; } } { # total turn = count 0<->1 transitions of N bits sub count_runs_1bits { my ($n) = @_; my $count = 0; for (;;) { last unless $n; while ($n % 2 == 0) { $n/=2; } $count++; while ($n % 2 == 1) { $n-=1; $n/=2; } } return $count; } # return how many places there are where n bits change 0<->1 sub transitions { my ($n) = @_; my $count = 0; while ($n) { $count += (($n & 3) == 1 || ($n & 3) == 2); $n >>= 1; } return $count } sub transitions2 { my ($n) = @_; my $m = low_ones_mask($n); $n ^= $m; # zap to zeros my $count = ($m!=0); while ($n) { ### assert: ($n&1)==0 $m = low_zeros_mask($n); $n |= $m; # fill to ones $count++; $m = low_ones_mask($n); $n ^= $m; # zap to zeros $count++; last unless $n; } return $count } sub transitions3 { my ($n) = @_; my $count = 0; return count_1_bits($n^($n>>1)); } sub low_zeros_mask { my ($n) = @_; die if $n == 0; return ($n ^ ($n-1)) >> 1; } ### assert: low_zeros_mask(1)==0 ### assert: low_zeros_mask(2)==1 ### assert: low_zeros_mask(3)==0 ### assert: low_zeros_mask(4)==3 ### assert: low_zeros_mask(12)==3 ### assert: low_zeros_mask(10)==1 sub low_ones_mask { my ($n) = @_; return ($n ^ ($n+1)) >> 1; } ### assert: low_ones_mask(1)==1 ### assert: low_ones_mask(2)==0 ### assert: low_ones_mask(3)==3 ### assert: low_ones_mask(5)==1 sub count_1_bits { my ($n) = @_; my $count = 0; while ($n) { $count += ($n&1); $n >>= 1; } return $count; } my $path = Math::PlanePath::DragonCurve->new; require Math::NumSeq::PlanePathDelta; my $dir4_seq = Math::NumSeq::PlanePathDelta->new (planepath_object => $path, delta_type => 'Dir4'); require Math::NumSeq::PlanePathTurn; my $turn_seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my $total_turn = 0; for (my $n = 0; $n < 16; ) { my $t = transitions($n); my $t2 = transitions2($n); my $t3 = transitions3($n); my $good = ($t == $t2 && $t2 == $t3 && $t == $total_turn ? 'good' : ''); my $dir4 = $dir4_seq->ith($n); my ($x,$y) = $path->n_to_xy($n); my $turn = $turn_seq->ith($n+1); printf "%2d xy=%2d,%2d d=%d total=%d turn=%+d %d,%d,%d %s\n", $n,$x,$y, $dir4, $total_turn, $turn, $t,$t2,$t3, $good; $total_turn += $turn; $n++; } exit 0; } { # X,Y recursion my $w = 8; my $path = Math::PlanePath::DragonCurve->new; foreach my $offset (0 .. $w-1) { my $n = $path->n_start + $offset; foreach (1 .. 10) { my ($x,$y) = $path->n_to_xy($n); print "$x "; $n += $w; } print "\n"; } exit 0; } { # Midpoint tiling, text lines require Math::PlanePath::DragonMidpoint; require Image::Base::Text; my $scale = 1; my $arms = 4; my $path = Math::PlanePath::DragonMidpoint->new (arms => $arms); my $width = 64; my $height = 32; my $xoffset = $width/2; my $yoffset = $height/2; my $image = Image::Base::Text->new (-width => $width, -height => $height); my ($nlo,$nhi) = $path->rect_to_n_range(-$xoffset,-$yoffset, $xoffset,$yoffset); $nhi = 16384; print "nhi $nhi\n"; for (my $n = 0; $n <= $nhi; $n++) { # next if int($n/$arms) % 2; next unless int($n/$arms) % 2; my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+$arms); my $colour = ($x1 == $x2 ? '|' : '-'); $x1 *= $scale; $x2 *= $scale; $y1 *= $scale; $y2 *= $scale; $x1 += $xoffset; $x2 += $xoffset; $y1 += $yoffset; $y2 += $yoffset; $image->line($x1,$y1,$x2,$y2,$colour); } $image->save('/dev/stdout'); exit 0; } { # Midpoint tiling, text grid require Math::PlanePath::DragonMidpoint; require Image::Base::Text; my $scale = 2; my $arms = 4; my $path = Math::PlanePath::DragonMidpoint->new (arms => $arms); my $width = 64; my $height = 32; my $xoffset = $width/2 - 9; my $yoffset = $height/2 - 10; my $image = Image::Base::Text->new (-width => $width, -height => $height); my ($nlo,$nhi) = $path->rect_to_n_range(-$xoffset,-$yoffset, $xoffset,$yoffset); $nhi = 16384; print "nhi $nhi\n"; for (my $n = 0; $n <= $nhi; $n++) { # next if int($n/$arms) % 2; next unless int($n/$arms) % 2; my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+$arms); $y1 = -$y1; $y2 = -$y2; my $colour = ($x1 == $x2 ? '|' : '-'); ($x1,$x2) = (min($x1,$x2),max($x1,$x2)); ($y1,$y2) = (min($y1,$y2),max($y1,$y2)); $x1 *= $scale; $x2 *= $scale; $y1 *= $scale; $y2 *= $scale; $x1 -= $scale/2; $x2 += $scale/2; $y1 -= $scale/2; $y2 += $scale/2; $x1 += $xoffset; $x2 += $xoffset; $y1 += $yoffset; $y2 += $yoffset; ### rect: $x1,$y1,$x2,$y2 $image->rectangle($x1,$y1,$x2,$y2,'*'); } $image->save('/dev/stdout'); exit 0; } { # turn sequence by d(2n) etc require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new(planepath=>'DragonCurve', turn_type => 'Right'); foreach my $n (0 .. 16) { my $dn = dseq($n); my $turn = $seq->ith($n) // 'undef'; print "$n $turn $dn\n"; } exit 0; # Knuth vol 2 answer to 4.5.3 question 41, page 607 sub dseq { my ($n) = @_; for (;;) { if ($n == 0) { return 1; } if (($n % 2) == 0) { $n >>= 1; next; } if (($n % 4) == 1) { return 0; # bit above lowest 1-bit } if (($n % 4) == 3) { return 1; # bit above lowest 1-bit } } } } { # rect range exact my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); my @digit_to_rev = (0,5,0,5,undef, 5,0,5,0); my @min_digit_to_rot = (-1,1,1,-1,0, 0,1,-1,-1,1); sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### DragonCurve rect_to_n_range(): "$x1,$y1 $x2,$y2" my $xmax = int(max(abs($x1),abs($x2))); my $ymax = int(max(abs($y1),abs($y2))); my ($level_power, $level_max) = round_down_pow (($xmax*$xmax + $ymax*$ymax + 1) * 7, 2); ### $level_power ### $level_max if (is_infinite($level_max)) { return (0, $level_max); } my $zero = $x1 * 0 * $y1 * $x2 * $y2; my $initial_len = 2**$level_max; ### $initial_len my ($len, $rot, $x, $y); my $overlap = sub { my $extent = ($len == 1 ? 0 : 2*$len); ### overlap consider: "xy=$x,$y extent=$extent" return ($x + $extent >= $x1 && $x - $extent <= $x2 && $y + $extent >= $y1 && $y - $extent <= $y2); }; my $find_min = sub { my ($initial_rev, $extra_rot) = @_; ### find_min() ... ### $initial_rev ### $extra_rot $rot = $level_max + 1 + $extra_rot; $len = $initial_len; if ($initial_rev) { $rot += 2; $x = 2*$len * $dir4_to_dx[($rot+2)&3]; $y = 2*$len * $dir4_to_dy[($rot+2)&3]; } else { $x = $zero; $y = $zero; } my @digits = (-1); # high to low my $rev = $initial_rev; for (;;) { my $digit = ++$digits[-1]; ### min at: "digits=".join(',',@digits)." xy=$x,$y len=$len rot=".($rot&3)." rev=$rev" unless ($initial_rev) { my $nlo = _digit_join_hightolow ([@digits,(0)x($level_max-$#digits)], 4, $zero); my ($nx,$ny) = $self->n_to_xy($nlo); my ($nextx,$nexty) = $self->n_to_xy($nlo + $len*$len); ### nlo: "nlo=$nlo xy=$nx,$ny next xy=$nextx,$nexty" ### assert: $x == $nx ### assert: $y == $ny # ### assert: $nextx == $nx + ($dir4_to_dx[$rot&3] * $len) # ### assert: $nexty == $ny + ($dir4_to_dy[$rot&3] * $len) } $rot += $min_digit_to_rot[$digit+$rev]; ### $digit ### rot increment: $min_digit_to_rot[$digit+$rev]." to $rot" if ($digit > 3) { pop @digits; if (! @digits) { ### not found to level_max ... if ($x1 <= 0 && $x2 >= 0 && $y1 <= 0 && $y2 >= 0) { ### origin covered: 4**($level_max+1) return 4**$level_max; } else { return; } } $rev = (@digits < 2 ? $initial_rev : $digits[-2]&1 ? 5 : 0); ### past digit=3, backtrack ... $len *= 2; next; } if (&$overlap()) { if ($#digits >= $level_max) { ### yes overlap, found n_lo ... last; } ### yes overlap, descend ... ### apply rev: "digit=$digit rev=$rev xor=$digit_to_rev[$digit+$rev]" push @digits, -1; $rev = ($digit & 1 ? 5 : 0); $len /= 2; # { # my $state = 0; # foreach (@digits) { if ($_&1) { $state ^= 5 } } # ### assert: $rev == $state # } } else { ### no overlap, next digit ... $rot &= 3; $x += $dir4_to_dx[$rot] * $len; $y += $dir4_to_dy[$rot] * $len; } } ### digits: join(',',@digits) ### found n_lo: _digit_join_hightolow (\@digits, 4, $zero) return _digit_join_hightolow (\@digits, 4, $zero); }; my $arms = $self->{'arms'}; my @n_lo; foreach my $arm (0 .. $arms-1) { if (defined (my $n = &$find_min(0,$arm))) { push @n_lo, $n*$arms + $arm; } } if (! @n_lo) { return (1,0); # rectangle not visited by curve } my $n_top = 4 * $level_power * $level_power; ### $n_top my @n_hi; foreach my $arm (0 .. $arms-1) { if (defined (my $n = &$find_min(5,$arm))) { push @n_hi, ($n_top-$n)*$arms + $arm; } } return (min(@n_lo), max(@n_hi)); } my $path = Math::PlanePath::DragonCurve->new (arms => 4); foreach my $n (4 .. 1000) { my ($x,$y) = $path->n_to_xy($n); my @n_list = $path->xy_to_n_list($x,$y); my $want_lo = min(@n_list); my $want_hi = max(@n_list); my ($lo,$hi) = rect_to_n_range ($path, $x,$y, $x,$y); print "n=$n lo=$lo wantlo=$want_lo hi=$hi wanthi=$want_hi\n"; if ($lo != $want_lo) { die "n=$n lo=$lo wantlo=$want_lo"; } if ($hi != $want_hi) { die "n=$n hi=$hi wanthi=$want_hi"; } } exit 0; } { # level to ymax, xmin my $path = Math::PlanePath::DragonCurve->new; my $target = 4; my $xmin = 0; my $ymax = 0; for (my $n = 0; $n < 2**28; $n++) { my ($x,$y) = $path->n_to_xy($n); $xmin = min($x,$xmin); $ymax = max($y,$ymax); if ($n == $target) { printf "%7d %14b %14b\n", $n, -$xmin, $ymax; $target *= 2; } } exit 0; } { # upwards # 9----8 5---4 # | | | | # 10--11,7---6 3---2 # | | # 16 13---12 0---1 # | | # 15---14 # # # # 8-----> 4 # | ^ # | | # 16-----> v | # # # # 2*(4^2-1)/3 = 10 0b1010 # 4*(4^2-1)/3 = 20 0b10100 # # (2^3+1)/3 # (2^4-1)/3 # (2^5-2)/3 = 10 # (2^6-4)/3 = 20 # (2^7-2)/3 = 42 = 101010 # (2^8-4)/3 = 84 = 1010100 # # # new xmax = xmax or ymax # # new xmin = ymin-4 # # new ymax = ymax or -ymin or 2-xmin # # new ymin = ymin or -ymax or -xmax # # 16 # | # | # v # xmin seg 2 <---8 # | # | # v # --->4 xmax seg0 # # ymin seg 0 # # new xmax = len + -xmin # = len + -ymin # new xmin = - xmax # new ymax = 2len + (-ymin) only candidate # new ymin = -(ymax-len) # # xmax,xmin alternate # ymax-len,ymin alternate my $xmin = 0; my $xmax = 0; my $ymin = 0; my $ymax = 0; my $len = 1; my $exp = 8; print "level xmin xmax xsize | ymin ymax ysize\n"; for (0 .. $exp) { printf "%2d %-10s %-10s = %-10s | %-10s %-10s = %-10s\n", $_, to_bin($xmin),to_bin($xmax), to_bin(-$xmin+$xmax), to_bin($ymin),to_bin($ymax), to_bin(-$ymin+$ymax); my @xmax_candidates = ($ymax, # seg 0 across $len-$xmin, # seg 1 side <--- $len-$ymin, # seg 2 before <--- ); my $xmax_seg = max_index(@xmax_candidates); my $xmax_candstr = join(',',@xmax_candidates); my @xmin_candidates = ($ymin, # seg 0 before -($ymax-$len), # seg 2 across -$xmax, # seg 3 side <--- ); my $xmin_seg = min_index(@xmin_candidates); my $xmin_candstr = join(',',@xmin_candidates); my @ymin_candidates = (-$xmax, # seg 0 side <--- -($ymax-$len)); # seg 1 extend my $ymin_seg = min_index(@ymin_candidates); my $ymin_candstr = join(',',@ymin_candidates); print "$_ xmax ${xmax_seg}of$xmax_candstr xmin ${xmin_seg}of$xmin_candstr ymin ${ymin_seg}of$ymin_candstr\n"; ($xmax,$xmin, $ymax,$ymin) = ( # xmax max ($ymax, # seg 0 across $len-$xmin, # seg 1 side $len-$ymin, # seg 2 before ), # xmin min ($ymin, # seg 0 before $len-$ymax, # seg 2 across -$xmax, # seg 3 side ), # ymax 2*$len-$ymin, # seg 3 before # ymin min(-$xmax, # seg 0 side -($ymax-$len))); # seg 1 extend ### assert: $xmin <= 0 ### assert: $ymin <= 0 ### assert: $xmax >= 0 ### assert: $ymax >= 0 $len *= 2; } print 3*$xmin/$len+.001," / 3\n"; print 6*$xmax/$len+.001," / 6\n"; print 3*$ymin/$len+.001," / 3\n"; print 3*$ymax/$len+.001," / 3\n"; exit 0; sub min_index { my $min_value = $_[0]; my $ret = 0; foreach my $i (1 .. $#_) { my $next = $_[$i]; if ($next == $min_value) { $ret .= ",$i"; } elsif ($next < $min_value) { $ret = $i; $min_value = $next; } } return $ret; } sub max_index { ### max_index(): @_ my $max_value = $_[0]; my $ret = 0; foreach my $i (1 .. $#_) { my $next = $_[$i]; ### $next if ($next == $max_value) { ### append ... $ret .= ",$i"; } elsif ($next > $max_value) { ### new max ... $ret = $i; $max_value = $next; } } return $ret; } } # n_to_xy ... # { # # low to high # my $rev = 0; # my @rev; # foreach my $digit (reverse @digits) { # push @rev, $rev; # $rev ^= $digit; # } # ### @digits # my $x = 0; # my $y = 0; # my $dy = $rot & 1; # my $dx = ! $dy; # if ($rot & 2) { # $dx = -$dx; # $dy = -$dy; # } # $rev = 0; # foreach my $digit (@digits) { # ### at: "$x,$y dxdy=$dx,$dy" # my $rev = shift @rev; # if ($digit) { # if ($rev) { # ($x,$y) = (-$y,$x); # rotate +90 # } else { # ($x,$y) = ($y,-$x); # rotate -90 # } # $x += $dx; # $y += $dy; # $rev = $digit; # } # # multiply i+1, ie. (dx,dy) = (dx + i*dy)*(i+1) # ($dx,$dy) = ($dx-$dy, $dx+$dy); # } # ### final: "$x,$y dxdy=$dx,$dy" # return ($x,$y); # } { # inner rectangle touching # | | # 751-750 735-734 431- # # # # 382-383 # | # 380-385-384 # | # 379-386-387 # | # 376-377-388 # | # 375-374 371- # # 368 # # 367- # # 9-- 8 5-- 4 # | | # 10--11-- 6 3-- 2 190-191 # | | # 17--16 13--12 0-- 1 188-193-192 # | | | # 18--19- 22--23 187-194-195 # | | | # 20- 25--24 184-185-196 # | | # 26--27 46--47 94--95 183-182-179- # | | | | # 33--32 29- 44- 49--48 92- 97--96 108-113-176 # | | | | | # 34--35- 38- 43- 50--51 54- 91- 98--99 102-107-114-175- # | | | | | | | # 36--37 40--41 52- 57- 88--89-100-101 104-105 116 # | | # 58- 87--86- 83--82 # | | # 65--64 61- 76--77 80--81 129-128 # | | | # 66--67- 70- 75--74 130-131-134 # | | | # 68--69 72--73 132 require Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new; foreach my $k (0 .. 5) { my $level = 2*$k; my $Nlevel = 2**$level; print "k=$k level=$level Nlevel=$Nlevel\n"; # my $c1x = 2**$k - calc_Wmax($k); # <-- # my $c1y = 2**$k + calc_Wmin($k); # <-- # my $c2x = 2**($k+1) - calc_Wmax($k+1); # my $c2y = 2**($k+1) + calc_Wmin($k+1); # my $c3x = 2**($k+2) - calc_Wmax($k+2); # <-- # my $c3y = 2**($k+2) + calc_Wmin($k+2); # <-- my $c1x = calc_Wouter($k); # <-- my $c1y = calc_Louter($k); # <-- my $c2x = calc_Wouter($k+1); my $c2y = calc_Louter($k+1); my $c3x = calc_Wouter($k+2); # <-- my $c3y = calc_Louter($k+2); # <-- my $step_c2x = 2*$c1x - !($k&1); unless ($step_c2x == $c2x) { warn "step X $step_c2x != $c2x"; } my $step_c2y = 2*$c1y - ($k&1); unless ($step_c2y == $c2y) { warn "step Y $step_c2y != $c2y"; } my $step_c3x = 4 * $c1x - 2 + ($k&1); unless ($step_c3x == $c3x) { warn "step X $step_c3x != $c3x"; } my $step_c3y = 4 * $c1y - 1 - ($k & 1); unless ($step_c3y == $c3y) { warn "step Y $step_c3y != $c3y"; } unless ($c1y == $c2x) { warn "diff $c1y $c2x"; } unless ($c2y == $c3x) { warn "diff $c2y $c3x"; } my $xmax = $c1x; my $ymax = $c1y; my $xmin = -$c3x; my $ymin = -$c3y; print " C1 x=$xmax,y=$ymax C2 x=$c2x,y=$c2y C3 x=$c3x,y=$c3y\n"; print " out x=$xmin..$xmax y=$ymin..$ymax\n"; foreach (1 .. $k) { print " rotate\n"; ($xmax, # rotate +90 $ymax, $xmin, $ymin) = (-$ymin, $xmax, -$ymax, $xmin); } print " out x=$xmin..$xmax y=$ymin..$ymax\n"; my $in_xmax = $xmax - 1; my $in_xmin = $xmin + 1; my $in_ymax = $ymax - 1; my $in_ymin = $ymin + 1; print " in x=$in_xmin..$in_xmax y=$in_ymin..$in_ymax\n"; # inner edges, Nlevel or higher is bad foreach my $y ($in_ymax, $in_ymin) { foreach my $x ($in_xmin .. $in_xmax) { foreach my $n ($path->xy_to_n_list ($x, $y)) { if ($n >= $Nlevel) { print "$n $x,$y horiz ***\n"; } } } } # inner edges, Nlevel or higher is bad foreach my $x ($in_xmax, $in_xmin) { foreach my $y ($in_ymin .. $in_ymax) { foreach my $n ($path->xy_to_n_list ($x, $y)) { if ($n >= $Nlevel) { print "$n $x,$y vert ***\n"; } } } } # outer edges, Nlevel or higher touched my $touch = 0; foreach my $y ($ymax, $ymin) { foreach my $x ($xmin .. $xmax) { foreach my $n ($path->xy_to_n_list ($x, $y)) { if ($n >= $Nlevel) { $touch++; } } } } # inner edges, Nlevel or higher is bad foreach my $x ($xmax, $xmin) { foreach my $y ($ymin .. $ymax) { foreach my $n ($path->xy_to_n_list ($x, $y)) { if ($n >= $Nlevel) { $touch++; } } } } my $diff_touch = ($touch == 0 ? ' ***' : ''); print " touch $touch$diff_touch\n"; } exit 0; sub calc_Louter { my ($k) = @_; # Louter = 2^k - abs(Lmin) # = 2^k - (2^k - 1 - (k&1))/3 # = (3*2^k - (2^k - 1 - (k&1)))/3 # = (3*2^k - 2^k + 1 + (k&1))/3 # = (2*2^k + 1 + (k&1))/3 return (2*2**$k + 1 + ($k&1)) / 3; # return 2**$k + calc_Lmin($k); } sub calc_Wouter { my ($k) = @_; # Wouter = 2^k - Wmax # = 2^k - (2*2^k - 2 + (k&1)) / 3 # = (3*2^k - (2*2^k - 2 + (k&1))) / 3 # = (3*2^k - 2*2^k + 2 - (k&1)) / 3 # = (2^k + 2 - (k&1)) / 3 return (2**$k + 2 - ($k&1)) / 3; # return 2**$k - calc_Wmax($k); } sub calc_Lmax { my ($k) = @_; # Lmax = (7*2^k - 4)/6 if k even # (7*2^k - 2)/6 if k odd if ($k & 1) { return (7*2**$k - 2) / 6; } else { return (7*2**$k - 4) / 6; } } sub calc_Lmin { my ($k) = @_; # Lmin = - (2^k - 1)/3 if k even # - (2^k - 2)/3 if k odd # = - (2^k - 2 - (k&1))/3 if ($k & 1) { return - (2**$k - 2) / 3; } else { return - (2**$k - 1) / 3; } } sub calc_Wmax { my ($k) = @_; # Wmax = (2*2^k - 1) / 3 if k odd # (2*2^k - 2) / 3 if k even # = (2*2^k - 2 + (k&1)) / 3 if ($k & 1) { return (2*2**$k - 1) / 3; } else { return (2*2**$k - 2) / 3; } } sub calc_Wmin { my ($k) = @_; return calc_Lmin($k); } } { # inner Wmin/Wmax foreach my $k (0 .. 10) { my $wmax = calc_Wmax($k); my $wmin = calc_Wmin($k); my $submax = 2**$k - $wmax; my $submin = 2**$k + $wmin; printf "%2d %4d %4d %4d %4d\n", $k, abs($wmin), $wmax, $submax, $submin; # printf "%2d %8b %8b %8b %8b\n", # $k, abs($wmin), $wmax, $submax, $submin; } exit 0; } { # width,height extents require Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new; my @xend = (1); my @yend = (0); my @xmin = (0); my @xmax = (1); my @ymin = (0); my @ymax = (0); extend(); sub extend { my $xend = $xend[-1]; my $yend = $yend[-1]; ($xend,$yend) = ($xend-$yend, # rotate +45 $xend+$yend); push @xend, $xend; push @yend, $yend; my $xmax = $xmax[-1]; my $xmin = $xmin[-1]; my $ymax = $ymax[-1]; my $ymin = $ymin[-1]; ### assert: $xmax >= $xmin ### assert: $ymax >= $ymin # ### at: "end=$xend,$yend $xmin..$xmax $ymin..$ymax" push @xmax, max($xmax, $xend + $ymax); push @xmin, min($xmin, $xend + $ymin); push @ymax, max($ymax, $yend - $xmin); push @ymin, min($ymin, $yend - $xmax); } my $level = 0; my $n_level = 1; my $n = 0; my $xmin = 0; my $xmax = 0; my $ymin = 0; my $ymax = 0; my $prev_r = 1; for (;;) { my ($x,$y) = $path->n_to_xy($n); $xmin = min($xmin,$x); $xmax = max($xmax,$x); $ymin = min($ymin,$y); $ymax = max($ymax,$y); if ($n == $n_level) { my $width = $xmax - $xmin + 1; my $height = $ymax - $ymin + 1; my $r = ($width/2)**2 + ($height/2)**2; my $rf = $r / $prev_r; my $xmin2 = to_bin($xmin); my $ymin2 = to_bin($ymin); my $xmax2 = to_bin($xmax); my $ymax2 = to_bin($ymax); my $xrange= sprintf "%9s..%9s", $xmin2, $xmax2; my $yrange= sprintf "%9s..%9s", $ymin2, $ymax2; printf "%2d n=%-7d %19s %19s r=%.2f (%.3f)\n", $level, $n, $xrange, $yrange, $r, $rf; extend(); $xrange="$xmin[$level]..$xmax[$level]"; $yrange="$ymin[$level]..$ymax[$level]"; # printf " %9s %9s\n", # $xrange, $yrange; $level++; $n_level *= 2; $prev_r = $r; last if $level > 30; } $n++; } exit 0; sub to_bin { my ($n) = @_; return ($n < 0 ? '-' : '') . sprintf('%b', abs($n)); } } { # diagonal # # |---8 # | # v # 6<-- # | # | # 0 |---4 # | | # | v # |-->2 # # new xmax = ymax or -ymin or 2L-xmin # new xmin = ymin # new ymax = 2L-ymin # new ymin = -xmax or -ymax same my $xmax = 1; my $xmin = 0; my $ymax = 1; my $ymin = 0; my $len = 1; my $exp = 8; for (1 .. $exp) { printf "%2d %-18s %-18s %-18s %-18s\n", $_, to_bin($xmin),to_bin($xmax), to_bin($ymin),to_bin($ymax); ($xmax, $xmin, $ymax, $ymin) = (max($ymax, -$ymin, 2*$len-$xmin), min($ymin), 2*$len-$ymin, min(-$xmax,-$ymax)); ### assert: $xmin <= 0 ### assert: $ymin <= 0 ### assert: $xmax >= 0 ### assert: $ymax >= 0 $len *= 2; } print 3*$xmin/$len+.001," / 3\n"; print 6*$xmax/$len+.001," / 6\n"; print 3*$ymin/$len+.001," / 3\n"; print 3*$ymax/$len+.001," / 3\n"; } { # A073089 midpoint vertical/horizontal formula require Math::NumSeq::OEIS::File; my $A073089 = Math::NumSeq::OEIS::File->new (anum => 'A073089'); my $A014577 = Math::NumSeq::OEIS::File->new (anum => 'A014577'); # 0=left n=0 my $A014707 = Math::NumSeq::OEIS::File->new (anum => 'A014707'); # 1=left my $A038189 = Math::NumSeq::OEIS::File->new (anum => 'A038189'); my $A082410 = Math::NumSeq::OEIS::File->new (anum => 'A082410'); my $A000035 = Math::NumSeq::OEIS::File->new (anum => 'A000035'); # n mod 2 my $count = 0; foreach my $n (0 .. 1000) { my $got = $A073089->ith($n) // next; # works except for n=1 # my $turn = $A014707->ith($n-2) // next; # my $flip = $A000035->ith($n-2) // next; # my $calc = $turn ^ $flip; # works # my $turn = $A014577->ith($n-2) // next; # my $flip = $A000035->ith($n-2) // next; # my $calc = $turn ^ $flip ^ 1; # so A073089(n) = A082410(n) xor A000035(n) xor 1 my $turn = $A082410->ith($n) // next; my $flip = $A000035->ith($n) // next; my $calc = $turn ^ $flip ^ 1; if ($got != $calc) { print "wrong $n got=$got calc=$calc\n"; } $count++; } print "count $count\n"; exit 0; } { # doublings require Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new; my %seen; for (my $n = 0; $n < 2000; $n++) { my ($x,$y) = $path->n_to_xy($n); my $key = "$x,$y"; push @{$seen{$key}}, $n; if (@{$seen{$key}} == 2) { my @v2; my $aref = delete $seen{$key}; my $sum = 0; foreach my $v (@$aref) { $sum += $v; my $v2 = Math::BaseCnv::cnv($v,10,2); push @v2, $v2; printf "%4s %12s\n", $v, $v2; } printf "%4s %12b sum\n", $sum, $sum; my $diff = abs($aref->[0]-$aref->[1]); printf "%4s %12b diff\n", $diff, $diff; my $lenmatch = 0; foreach my $i (1 .. length($v2[0])) { my $want = substr ($v2[0], -$i); if ($v2[1] =~ /$want$/) { next; } else { $lenmatch = $i-1; last; last; } } my $zeros = ($v2[0] =~ /(0*)$/ && $1); my $lenzeros = length($zeros); my $same = ($lenmatch == $lenzeros+2 ? "same" : "diff"); print "low same $lenmatch zeros $lenzeros $same\n"; my $new = $aref->[0]; my $first_bit = my $bit = 2 * 2**$lenzeros; my $change = 0; while ($bit <= 2*$aref->[0]) { ### $bit ### $change if ($change) { $new ^= $bit; $change = ! ($aref->[0] & $bit); } else { $change = ($aref->[0] & $bit); } $bit *= 2; } my $new2 = Math::BaseCnv::cnv($new,10,2); if ($new != $aref->[1]) { print "flip wrong first $first_bit last $bit to $new $new2\n"; } print "\n"; } } exit 0; } { # xy absolute direction nsew require Math::PlanePath::DragonCurve; my @array; my $arms = 4; my $path = Math::PlanePath::DragonCurve->new (arms => $arms); my $width = 20; my $height = 20; my ($n_lo, $n_hi) = $path->rect_to_n_range(0,0,$width+2,$height+2); print "n_hi $n_hi\n"; for my $n (0 .. 20*$n_hi) { # next if ($n % 4) == 0; # next if ($n % 4) == 1; # next if ($n % 4) == 2; # next if ($n % 4) == 3; my ($x,$y) = $path->n_to_xy($n); next if $x < 0 || $y < 0 || $x > $width || $y > $height; my ($nx,$ny) = $path->n_to_xy($n+$arms); if ($ny == $y+1) { $array[$x][$y] .= ($n & 1 ? "n" : "N"); } if ($ny == $y-1) { $array[$x][$y] .= ($n & 1 ? "s" : "S"); } # if ($nx == $x+1) { # $array[$x][$y] .= "w"; # } # if ($nx == $x-1) { # $array[$x][$y] .= "e"; # } } foreach my $y (reverse 0 .. $height) { foreach my $x (0 .. $width) { my $v = $array[$x][$y]//''; $v = sort_str($v); printf "%3s", $v; } print "\n"; } exit 0; } { # xy absolute direction require Image::Base::Text; require Math::PlanePath::DragonCurve; my $arms = 1; my $path = Math::PlanePath::DragonCurve->new (arms => $arms); my $width = 20; my $height = 20; my $image = Image::Base::Text->new (-width => $width, -height => $height); my ($n_lo, $n_hi) = $path->rect_to_n_range(0,0,$width+2,$height+2); print "n_hi $n_hi\n"; for my $n (0 .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); next if $x < 0 || $y < 0 || $x >= $width || $y >= $height; my ($nx,$ny) = $path->n_to_xy($n+$arms); # if ($nx == $x+1) { # $image->xy($x,$y,$n&3); # } # if ($ny == $y+1) { # $image->xy($x,$y,$n&3); # } if ($ny == $y+1 || $ny == $y-1) { # $image->xy($x,$y,$n&3); $image->xy($x,$y,'|'); } if ($nx == $x+1 || $nx == $x-1) { # $image->xy($x,$y,$n&3); $image->xy($x,$y,'-'); } } $image->save('/dev/stdout'); exit 0; } { # Rounded and Midpoint equivalence table require Math::PlanePath::DragonRounded; require Math::PlanePath::DragonMidpoint; my @yx_rtom_dx; my @yx_rtom_dy; foreach my $arms (1 .. 4) { ### $arms my $rounded = Math::PlanePath::DragonRounded->new (arms => $arms); my $midpoint = Math::PlanePath::DragonMidpoint->new (arms => $arms); my %seen; foreach my $n (0 .. 5000) { my ($x,$y) = $rounded->n_to_xy($n) or next; my ($mx,$my) = $midpoint->n_to_xy($n); my $dx = ($x - floor($x/3)) - $mx; my $dy = ($y - floor($y/3)) - $my; if (defined $yx_rtom_dx[$y%6][$x%6] && $yx_rtom_dx[$y%6][$x%6] != $dx) { die "oops"; } if (defined $yx_rtom_dy[$y%6][$x%6] && $yx_rtom_dy[$y%6][$x%6] != $dy) { die "oops"; } $yx_rtom_dx[$y%6][$x%6] = $dx; $yx_rtom_dy[$y%6][$x%6] = $dy; } print_6x6(\@yx_rtom_dx); print_6x6(\@yx_rtom_dy); foreach my $n (0 .. 1000) { my ($x,$y) = $rounded->n_to_xy($n) or next; my $mx = $x-floor($x/3) - $yx_rtom_dx[$y%6][$x%6]; my $my = $y-floor($y/3) - $yx_rtom_dy[$y%6][$x%6]; my $m = $midpoint->xy_to_n($mx,$my); my $good = (defined $m && $n == $m ? "good" : "bad"); printf "n=%d xy=%d,%d -> mxy=%d,%d m=%s %s\n", $n, $x,$y, $mx,$my, $m//'undef', $good; } } exit 0; sub print_6x6 { my ($aref) = @_; foreach my $y (0 .. 5) { if ($y == 0) { print "[["; } else { print " ["; } foreach my $x (0 .. 5) { my $v = $aref->[$y][$x] // 'undef'; printf "%5s", $v; if ($x != 5) { print ", " } } if ($y == 5) { print "] ]\n"; } else { print "]\n"; } } } } { # Rounded and Midpoint equivalence checks require Math::PlanePath::DragonRounded; require Math::PlanePath::DragonMidpoint; my @yx_rtom_dx; my @yx_rtom_dy; foreach my $arms (1 .. 4) { print "\narms=$arms\n"; my $rounded = Math::PlanePath::DragonRounded->new (arms => $arms); my $midpoint = Math::PlanePath::DragonMidpoint->new (arms => $arms); foreach my $y (reverse -10 .. 10) { foreach my $x (-7 .. 7) { my $d = ''; my $n = $rounded->xy_to_n($x,$y); if (defined $n) { my ($mx,$my) = $midpoint->n_to_xy($n); my $dx = ($x - floor($x/3)) - $mx; my $dy = ($y - floor($y/3)) - $my; $d = "$dx,$dy"; } elsif ($x==0&&$y==0) { $d = '+'; } printf "%5s", $d; } print "\n"; } } exit 0; } { # A059125 "dragon-like" require MyOEIS; my ($drag_values) = MyOEIS::read_values('A014707'); my ($like_values) = MyOEIS::read_values('A059125'); my @diff = map {$drag_values->[$_] == $like_values->[$_] ? '_' : 'x' } 0 .. 80; print @{$drag_values}[0..70],"\n"; print @{$like_values}[0..70],"\n"; print @diff[0..70],"\n"; exit 0; } { # Curve xy to n by midpoint require Math::PlanePath::DragonCurve; require Math::PlanePath::DragonMidpoint; foreach my $arms (3) { ### $arms my $curve = Math::PlanePath::DragonCurve->new (arms => $arms); my $midpoint = Math::PlanePath::DragonMidpoint->new (arms => $arms); my %seen; for (my $n = 0; $n < 50; $n++) { my ($x,$y) = $curve->n_to_xy($n); my $list = ''; my $found = ''; DX: foreach my $dx (-1,0) { foreach my $dy (0,1) { # my ($x,$y) = ($x-$y,$x+$y); # rotate +45 and mul sqrt(2) my ($x,$y) = ($x+$y,$y-$x); # rotate -45 and mul sqrt(2) my $m = $midpoint->xy_to_n($x+$dx,$y+$dy) // next; $list .= " $m"; if ($m == $n) { $found = "$dx,$dy"; # last DX; } } } printf "n=%d xy=%d,%d got %s %s\n", $n,$x,$y, $found, $list; $seen{$found} = 1; } $,=' '; print sort keys %seen,"\n"; } exit 0; # (x+iy)*(i+1) = (x-y)+(x+y)i # +45 # (x+iy)*(-i+1) = (x+y)+(y-x)i # -45 } { # Midpoint xy to n require Math::PlanePath::DragonMidpoint; my @yx_adj_x = ([0,1,1,0], [1,0,0,1], [1,0,0,1], [0,1,1,0]); my @yx_adj_y = ([0,0,1,1], [0,0,1,1], [1,1,0,0], [1,1,0,0]); sub xy_to_n { my ($self, $x,$y) = @_; my $n = ($x * 0 * $y) + 0; # inherit bignum 0 my $npow = $n + 1; # inherit bignum 1 while (($x != 0 && $x != -1) || ($y != 0 && $y != 1)) { # my $ax = ((($x+1) ^ ($y+1)) >> 1) & 1; # my $ay = (($x^$y) >> 1) & 1; # ### assert: $ax == - $yx_adj_x[$y%4]->[$x%4] # ### assert: $ay == - $yx_adj_y[$y%4]->[$x%4] my $y4 = $y % 4; my $x4 = $x % 4; my $ax = $yx_adj_x[$y4]->[$x4]; my $ay = $yx_adj_y[$y4]->[$x4]; ### at: "$x,$y n=$n axy=$ax,$ay bit=".($ax^$ay) if ($ax^$ay) { $n += $npow; } $npow *= 2; $x -= $ax; $y -= $ay; ### assert: ($x+$y)%2 == 0 ($x,$y) = (($x+$y)/2, # rotate -45 and divide sqrt(2) ($y-$x)/2); } ### final: "xy=$x,$y" my $arm; if ($x == 0) { if ($y) { $arm = 1; ### flip ... $n = $npow-1-$n; } else { # $y == 1 $arm = 0; } } else { # $x == -1 if ($y) { $arm = 2; } else { $arm = 3; ### flip ... $n = $npow-1-$n; } } ### $arm my $arms_count = $self->arms_count; if ($arm > $arms_count) { return undef; } return $n * $arms_count + $arm; } foreach my $arms (4,3,1,2) { ### $arms my $path = Math::PlanePath::DragonMidpoint->new (arms => $arms); for (my $n = 0; $n < 50; $n++) { my ($x,$y) = $path->n_to_xy($n) or next; my $rn = xy_to_n($path,$x,$y); my $good = ''; if (defined $rn && $rn == $n) { $good .= "good N"; } my $n2 = Math::BaseCnv::cnv($n,10,2); my $rn2 = Math::BaseCnv::cnv($rn,10,2); printf "n=%d xy=%d,%d got rn=%d %s\n", $n,$x,$y, $rn, $good; } } exit 0; } { # xy modulus require Math::PlanePath::DragonMidpoint; my $path = Math::PlanePath::DragonMidpoint->new; my %seen; for (my $n = 0; $n < 1024; $n++) { my ($x,$y) = $path->n_to_xy($n) or next; my $k = ($x+$y) & 15; # $x &= 3; $y &= 3; $k = "$x,$y"; $seen{$k} = 1; } ### %seen exit 0; } { # arm xy modulus require Math::PlanePath::DragonMidpoint; my $path = Math::PlanePath::DragonMidpoint->new (arms => 4); my %seen; for (my $n = 0; $n < 1024; $n++) { my ($x,$y) = $path->n_to_xy($n) or next; $x &= 3; $y &= 3; $seen{$n&3}->{"$x,$y"} = 1; } ### %seen exit 0; } { # xy to n require Math::PlanePath::DragonMidpoint; my @yx_adj_x = ([0,-1,-1,0], [-1,0,0,-1], [-1,0,0,-1], [0,-1,-1,0]); my @yx_adj_y = ([0,0,-1,-1], [0,0,-1,-1], [-1,-1,0,0], [-1,-1,0,0]); my $path = Math::PlanePath::DragonMidpoint->new (); # (arms => 4); for (my $n = 0; $n < 50; $n++) { my ($x,$y) = $path->n_to_xy($n) or next; ($x,$y) = (-$y,$x+1); # rotate +90 # ($x,$y) = (-$x-1,-$y+1); # rotate 180 # my $rot = 1; # if ($rot & 2) { # $x -= 1; # } # if (($rot+1) & 2) { # # rot 1 or 2 # $y += 1; # } ### xy: "$n $x,$y adj ".$yx_adj_x[$y&3]->[$x&3]." ".$yx_adj_y[$y&3]->[$x&3] my $rx = $x; my $ry = $y; # if (((($x+1)>>1)&1) ^ ((($y-1)&2))) { # $rx--; # } # if (((($x-1)>>1)&1) ^ ((($y+1)&2))) { # $ry--; # } my $ax = ((($x+1) ^ ($y+1)) >> 1) & 1; my $ay = (($x^$y) >> 1) & 1; ### assert: $ax == - $yx_adj_x[$y&3]->[$x&3] ### assert: $ay == - $yx_adj_y[$y&3]->[$x&3] # $rx += $yx_adj_x[$y&3]->[$x&3]; # $ry += $yx_adj_y[$y&3]->[$x&3]; $rx -= $ax; $ry -= $ay; ($rx,$ry) = (($rx+$ry)/2, ($ry-$rx)/2); ### assert: $rx == int($rx) ### assert: $ry == int($ry) # my $arm = $n & 3; # my $nbit = ($path->arms_count == 4 ? ($n>>2)&1 : $n&1); # my $bit = $ax ^ $ay ^ ($arm&0) ^ (($arm>>1)&1); my $nbit = $n&1; my $bit = $ax ^ $ay; my $rn = $path->xy_to_n($ry-1,-$rx); # rotate -90 # my $rn = $path->xy_to_n(-$rx-1,-$ry+1); # rotate 180 my $good = ''; if (defined $rn && $rn == int($n/2)) { $good .= "good N"; } if ($nbit == $bit) { $good .= " good bit"; } my $n2 = Math::BaseCnv::cnv($n,10,2); my $rn2 = Math::BaseCnv::cnv($rn,10,2); printf "%d %d (%8s %8s) bit=%d,%d %d,%d %s\n", $n,$rn, $n2,$rn2, $nbit,$bit, $x,$y, $good; } exit 0; } { require Image::Base::Text; my $width = 79; my $height = 50; my $ox = $width/2; my $oy = $height/2; my $image = Image::Base::Text->new (-width => $width, -height => $height); require Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new; my $store = sub { my ($x,$y,$c) = @_; # $x *= 2; # $y *= 2; $x += $ox; $y += $oy; if ($x >= 0 && $y >= 0 && $x < $width && $y < $height) { my $o = $image->xy($x,$y); # if (defined $o && $o ne ' ' && $o ne $c) { # $c = '*'; # } $image->xy($x,$y,$c); } else { die "$x,$y"; } }; my ($x,$y); for my $n (0 .. 2**8) { ($x,$y) = $path->n_to_xy($n); # # (x+iy)/(i+1) = (x+iy)*(i-1)/2 = (-x-y)/2 + (x-y)/2 # if (($x+$y) % 2) { $x--; } # ($x,$y) = ((-$x-$y)/2, # ($x-$y)/2); # # # (x+iy)/(i+1) = (x+iy)*(i-1)/2 = (-x-y)/2 + (x-y)/2 # if (($x+$y) % 2) { $x--; } # ($x,$y) = ((-$x-$y)/2, # ($x-$y)/2); # ($x,$y) = (-$y,$x); # rotate +90 $y = -$y; $store->($x,$y,'*'); } $store->($x,$y,'+'); $store->(0,0,'o'); $image->save('/dev/stdout'); exit 0; } { # vs ComplexPlus require Math::PlanePath::DragonCurve; require Math::PlanePath::ComplexPlus; my $dragon = Math::PlanePath::DragonCurve->new; my $complex = Math::PlanePath::ComplexPlus->new; for (my $n = 0; $n < 50; $n++) { my ($x,$y) = $dragon->n_to_xy($n) or next; my $cn = $complex->xy_to_n($x,$y); my $n2 = Math::BaseCnv::cnv($n,10,2); my $cn2 = (defined $cn ? Math::BaseCnv::cnv($cn,10,2) : 'undef'); printf "%8s %8s %d,%d\n", $n2, $cn2, $x,$y; } exit 0; } { # turn require Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new; my $n = $path->n_start; my ($n0_x, $n0_y) = $path->n_to_xy ($n); $n++; my ($prev_x, $prev_y) = $path->n_to_xy ($n); my ($prev_dx, $prev_dy) = ($prev_x - $n0_x, $prev_y - $n0_y); $n++; my $pow = 4; for ( ; $n < 128; $n++) { my ($x, $y) = $path->n_to_xy ($n); my $dx = ($x - $prev_x); my $dy = ($y - $prev_y); my $turn; if ($prev_dx) { if ($dy == $prev_dx) { $turn = 0; # left } else { $turn = 1; # right } } else { if ($dx == $prev_dy) { $turn = 1; # right } else { $turn = 0; # left } } ($prev_dx,$prev_dy) = ($dx,$dy); ($prev_x,$prev_y) = ($x,$y); print "$turn"; if ($n-1 == $pow) { $pow *= 2; print "\n"; } } print "\n"; exit 0; } { # turn require Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new; my $n = 0; my ($n0_x, $n0_y) = $path->n_to_xy ($n); $n++; my ($prev_x, $prev_y) = $path->n_to_xy ($n); my ($prev_dx, $prev_dy) = ($prev_x - $n0_x, $prev_y - $n0_y); $n++; for ( ; $n < 40; $n++) { my ($x, $y) = $path->n_to_xy ($n); my $dx = ($x - $prev_x); my $dy = ($y - $prev_y); my $turn; if ($prev_dx) { if ($dy == $prev_dx) { $turn = 0; # left } else { $turn = 1; # right } } else { if ($dx == $prev_dy) { $turn = 1; # right } else { $turn = 0; # left } } ### $n ### $prev_dx ### $prev_dy ### $dx ### $dy # ### is: "$got[-1] at idx $#got" ($prev_dx,$prev_dy) = ($dx,$dy); ($prev_x,$prev_y) = ($x,$y); my $zero = bit_above_lowest_zero($n-1); my $one = bit_above_lowest_one($n-1); print "$n $turn $one $zero\n"; # if ($turn != $bit) { # die "n=$n got $turn bit $bit\n"; # } } print "n=$n ok\n"; sub bit_above_lowest_zero { my ($n) = @_; for (;;) { if (($n % 2) == 0) { last; } $n = int($n/2); } $n = int($n/2); return ($n % 2); } sub bit_above_lowest_one { my ($n) = @_; for (;;) { if (! $n || ($n % 2) != 0) { last; } $n = int($n/2); } $n = int($n/2); return ($n % 2); } exit 0; } { require Image::Base::Text; my $width = 132; my $height = 50; my $ox = $width/2; my $oy = $height/2; my $image = Image::Base::Text->new (-width => $width, -height => $height); require Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new; my $store = sub { my ($x,$y,$c) = @_; $x *= 2; $x += $ox; $y += $oy; if ($x >= 0 && $y >= 0 && $x < $width && $y < $height) { my $o = $image->xy($x,$y); # if (defined $o && $o ne ' ' && $o ne $c) { # $c = '*'; # } $image->xy($x,$y,$c); } else { die "$x,$y"; } }; my ($x,$y); for my $n (0 .. 2**9) { ($x,$y) = $path->n_to_xy($n); $y = -$y; $store->($x,$y,'*'); } $store->($x,$y,'+'); $store->(0,0,'+'); $image->save('/dev/stdout'); exit 0; } { # Midpoint fracs require Math::PlanePath::DragonMidpoint; my $path = Math::PlanePath::DragonMidpoint->new; for my $n (0 .. 64) { my $frac = .125; my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); my ($x,$y) = $path->n_to_xy($n+$frac); my $dx = $x2-$x1; my $dy = $y2-$y1; my $xm = $x1 + $frac*$dx; my $ym = $y1 + $frac*$dy; my $wrong = ''; if ($x != $xm) { $wrong .= " X"; } if ($y != $ym) { $wrong .= " Y"; } print "$n $dx,$dy $x, $y want $xm, $ym $wrong\n" } exit 0; } { # min/max for level require Math::PlanePath::DragonRounded; my $path = Math::PlanePath::DragonRounded->new; my $prev_min = 1; my $prev_max = 1; for (my $level = 1; $level < 25; $level++) { my $n_start = 2**($level-1); my $n_end = 2**$level; my $min_hypot = 128*$n_end*$n_end; my $min_x = 0; my $min_y = 0; my $min_pos = ''; my $max_hypot = 0; my $max_x = 0; my $max_y = 0; my $max_pos = ''; print "level $level n=$n_start .. $n_end\n"; foreach my $n ($n_start .. $n_end) { my ($x,$y) = $path->n_to_xy($n); my $h = $x*$x + $y*$y; if ($h < $min_hypot) { $min_hypot = $h; $min_pos = "$x,$y"; } if ($h > $max_hypot) { $max_hypot = $h; $max_pos = "$x,$y"; } } # print " min $min_hypot at $min_x,$min_y\n"; # print " max $max_hypot at $max_x,$max_y\n"; { my $factor = $min_hypot / $prev_min; print " min r^2 $min_hypot 0b".sprintf('%b',$min_hypot)." at $min_pos factor $factor\n"; } { my $factor = $max_hypot / $prev_max; print " max r^2 $max_hypot 0b".sprintf('%b',$max_hypot)." at $max_pos factor $factor\n"; } $prev_min = $min_hypot; $prev_max = $max_hypot; } exit 0; } { # points N=2^level require Math::PlanePath::DragonRounded; my $path = Math::PlanePath::DragonRounded->new; for my $n (0 .. 50) { my ($x,$y) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); my $dx = $x2 - $x; my $dy = $y2 - $y; my ($xm,$ym) = $path->n_to_xy($n+.5); # my $dir = 0; # for (my $bit = 1; ; ) { # $dir += ((($n ^ ($n>>1)) & $bit) != 0); # $bit <<= 1; # last if $bit > $n; # # $dir += 1; # } # $dir %= 4; $x += $dx/2; $y += $dy/2; print "$n $x,$y $xm,$ym\n"; } exit 0; } { # reverse checking require Math::PlanePath::DragonRounded; my $path = Math::PlanePath::DragonRounded->new; for my $n (1 .. 50000) { my ($x,$y) = $path->n_to_xy($n); my $rev = $path->xy_to_n($x,$y); if (! defined $rev || $rev != $n) { if (! defined $rev) { $rev = 'undef'; } print "$n $x,$y $rev\n"; } } exit 0; } { require Image::Base::Text; my $width = 78; my $height = 40; my $ox = $width/2; my $oy = $height/2; my $image = Image::Base::Text->new (-width => $width, -height => $height); require Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new; my $store = sub { my ($x,$y,$c) = @_; $x *= 2; $x += $ox; $y += $oy; if ($x >= 0 && $y >= 0 && $x < $width && $y < $height) { my $o = $image->xy($x,$y); if (defined $o && $o ne ' ' && $o ne $c) { $c = '.'; } $image->xy($x,$y,$c); } }; for my $n (0 .. 16*256) { my ($x,$y) = $path->n_to_xy($n); $y = -$y; { $store->($x,$y,'a'); } { $store->(-$y,$x,'b'); } { $store->(-$x,-$y,'c'); } { $store->($y,-$x,'d'); } } $image->xy($ox,$oy,'+'); $image->save('/dev/stdout'); exit 0; } { # points N=2^level require Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new; for my $level (0 .. 50) { my $n = 2**$level; my ($x,$y) = $path->n_to_xy($n); print "$level $n $x,$y\n"; } exit 0; } { # sx,sy my $sx = 1; my $sy = 0; for my $level (0 .. 50) { print "$level $sx,$sy\n"; ($sx,$sy) = ($sx - $sy, $sy + $sx); } exit 0; } Math-PlanePath-129/devel/interpolate.pl0000644000175000017500000001502512165377675015716 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use Math::BigRat; use Math::Polynomial 1; use Math::Polynomial::Horner; #use Devel::Comments; my_interpolate ([ 0, 1, 2, 3, 4 ], [ 0-0.5, 1-0.5, 4-0.5, 9-0.5, 16-0.5 ] ); # my_interpolate ([ 1, 2, 3 ], # [ 2, 9, 21 ] # ); # my_interpolate ([ reverse 0,1,2,3,4,5 ], # [ map {$_-16} 0,5,9,12,14,15 ] # ); exit 0; # [1,2,3,4],[1+4,12+4+8,35+4+8+8,70+4+8+8+8] # # step==0 # my_interpolate ([ 0, 1, 2, 3, 4 ], # [0.5, 0.5, 0.5, 0.5, 0.5 ]); # # step==1 # # 7 8 9 10 # # 4 5 6 # # 2 3 # # 1 # my_interpolate ([ 0, 1, 2, 3 ], # [0.5, 1.5, 3.5, 6.5 ]); # # step==2 # my_interpolate ([ 0, 1, 2, 3 ], # [0.5, 1.5, 4.5, 9.5 ]); # # step==3 # my_interpolate ([ 0, 1, 2, 3 ], # [0.5, 1.5, 5.5, 12.5 ]); # # step==4 # my_interpolate ([ 0, 1, 2, 3 ], # [0.5, 1.5, 6.5, 15.5 ]); # my_interpolate ([ 2, 3, 4, 5, 6, 7, 8, 9, 10 ], # [ 9, 25, 49, 81, 121, 169, 225, 289, 361 ] # ); exit 0; # N = a*s^2 + b*s + c # = a * (s^2 + b/a s + c/a) # # N/a = (s + b/2a)^2 - b^2/4a^2 + c/a # (s + b/2a)^2 = N/a + b^2/4a^2 - c/a # s+ b/2a = sqrt(4aN/4a^2 + b^2/4a^2 - 4ac/4a^2) # = 1/2a * sqrt(4aN + b^2 - 4ac) # # -b + sqrt(4aN + b2 - 4ac) # s = ------------------------ # 2a # my_interpolate ( [ 1, 2, 3, 4, 5], [ map {3*$_} 1,1+4,1+4+9,1+4+9+16,1+4+9+16+25 ], ); sub bigrat_to_decimal { my ($rat) = @_; if (is_pow2($rat->denominator)) { return $rat->as_float; } else { return $rat; } } sub is_pow2 { my ($n) = @_; while ($n > 1) { if ($n & 1) { return 0; } $n >>= 1; } return ($n == 1); } use constant my_string_config => (variable => '$d', times => '*', power => '**', fold_one => 1, fold_sign => 1, fold_sign_swap_end => 1, power_by_times => 1, ); # @string_config = ( # # power => '**', # # fold_one => 1, # # fold_sign => 1, # # fold_sign_swap_end => 1, # # power_by_times => 1, # ); sub my_interpolate { my ($xarray, $valarray) = @_; my $zero = 0; $zero = Math::BigRat->new(0); $xarray = [ map {Math::BigRat->new($_)} @$xarray ]; $valarray = [ map {Math::BigRat->new($_)} @$valarray ]; my $p = Math::Polynomial->new($zero); $p = $p->interpolate($xarray, $valarray); $p->string_config({ fold_sign => 1, variable => 'd' }); print "N = $p\n"; $p->string_config({ my_string_config() }); print " = $p\n"; $p->string_config({ my_string_config(), # convert_coeff => \&bigrat_to_decimal, }); print " = ",Math::Polynomial::Horner::as_string($p),"\n"; my $a = $p->coeff(2); return if $a == 0; my $b = $p->coeff(1); my $c = $p->coeff(0); my $x = -$b/(2*$a); my $y = 4*$a / ((2*$a) ** 2); my $z = ($b*$b-4*$a*$c) / ((2*$a) ** 2); print "d = $x + sqrt($y * \$n + $z)\n"; # return; my $s_to_n = sub { my ($s) = @_; return $p->evaluate($s); }; if (ref $x) { $x = $x->numify; $y = $y->numify; $z = $z->numify; } my $n_to_d = sub { my ($n) = @_; my $root = $y * $n + $z; if ($root < 0) { return 'neg sqrt'; } return ($x + sqrt($root)); }; # for (my $i = 0; $i < 100; $i += 0.5) { # printf "%4s d=%s\n", $i, $n_to_d->($i); # } exit 0; } # { # package Math::Polynomial; # sub interpolate { # my ($this, $xvalues, $yvalues) = @_; # if ( # !ref($xvalues) || !ref($yvalues) || @{$xvalues} != @{$yvalues} # ) { # croak 'usage: $q = $p->interpolate([$x1, $x2, ...], [$y1, $y2, ...])'; # } # return $this->new if !@{$xvalues}; # my @alpha = @{$yvalues}; # my $result = $this->new($alpha[0]); # my $aux = $result->monomial(0); # my $zero = $result->coeff_zero; # for (my $k=1; $k<=$#alpha; ++$k) { # for (my $j=$#alpha; $j>=$k; --$j) { # my $dx = $xvalues->[$j] - $xvalues->[$j-$k]; # croak 'x values not disjoint' if $zero == $dx; # ### dx: "$dx",ref $dx # $alpha[$j] = ($alpha[$j] - $alpha[$j-1]) / $dx; # } # $aux = $aux->mul_root($xvalues->[$k-1]); # $result += $aux->mul_const($alpha[$k]); # ### alpha: join(' ',map{"$_"}@alpha) # } # return $result; # } # } { my $f1 = 1.5; my $f2 = 4.5; my $f3 = 9.5; my $f4 = 16.5; foreach ($f1, $f2, $f3, $f4) { $_ = Math::BigRat->new($_); } my $a = $f4/2 - $f3 + $f2/2; my $b = $f4*-5/2 + $f3*6 - $f2*7/2; my $c = $f4*3 - $f3*8 + $f2*6; print "$a\n"; print "$b\n"; print "$c\n"; print "$a*\$s*\$s + $b*\$s + $c\n"; exit 0; } { my $subr = sub { my ($s) = @_; return 3*$s*$s - 4*$s + 2; # return 2*$s*$s - 2*$s + 2; # return $s*$s + .5; # return $s*$s - $s + 1; # return $s*($s+1)*.5 + 0.5; }; my $back = sub { my ($n) = @_; return (2 + sqrt(3*$n - 2)) / 3; # return .5 + sqrt(.5*$n-.75); # return sqrt ($n - .5); # return -.5 + sqrt(2*$n - .75); # return int((sqrt(4*$n-1) - 1) / 2); }; my $prev = 0; foreach (1..15) { my $this = $subr->($_); printf("%2d %.2f %.2f %.2f\n", $_, $this, $this-$prev,$back->($this)); $prev = $this; } for (my $n = 1; $n < 23; $n++) { printf "%.2f %.2f\n", $n,$back->($n); } exit 0; } Math-PlanePath-129/devel/gosper-islands-stars.pl0000644000175000017500000000233411777406713017445 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.005; use strict; use POSIX (); use Math::PlanePath::GosperIslands; # uncomment this to run the ### lines use Smart::Comments; { my $path = Math::PlanePath::GosperIslands->new; my @rows = ((' ' x 64) x 78); my $level = 3; my $n_start = 3**$level - 2; my $n_end = 3**($level+1) - 2 - 1; foreach my $n ($n_start .. $n_end) { my ($x, $y) = $path->n_to_xy ($n); # $x *= 2; $x+= 16; $y+= 16; substr ($rows[$y], $x,1, '*'); } local $,="\n"; print reverse @rows; exit 0; } Math-PlanePath-129/devel/koch-curve.pl0000644000175000017500000000426312252723363015422 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use List::Util 'sum'; use Math::PlanePath::KochCurve; { # A056832 All a(n) = 1 or 2; a(1) = 1; get next 2^k terms by repeating # first 2^k terms and changing last element so sum of first 2^(k+1) terms # is odd. # # Is lowest non-zero base4 digit(n) 1,3->a(n)=1 2->a(n)=2. # a(2^k) flips 1<->2 each time for low non-zero flipping 1<->2. # a(2^k) always flips because odd sum becomes even on duplicating. # my @a = (1); for my $i (1 .. 6) { push @a, @a; unless (sum(@a) & 1) { $a[-1] = 3-$a[-1]; # 2<->1 print "i=$i flip last\n"; } print @a,"\n"; } foreach my $i (1 .. 64) { my $d = base4_lowest_nonzero_digit($i); if ($d != 2) { $d = 1; } print $d; } print "\n"; exit 0; } sub base4_lowest_nonzero_digit { my ($n) = @_; while (($n & 3) == 0) { $n >>= 2; if ($n == 0) { die "oops, no nonzero digits at all"; } } return $n & 3; } sub base4_lowest_non3_digit { my ($n) = @_; while (($n & 3) == 3) { $n >>= 2; } return $n & 3; } { my $path = Math::PlanePath::KochCurve->new; foreach my $n (0 .. 16) { my ($x,$y) = $path->n_to_xy($n); my $rot = n_to_total_turn($n); print "$n $x,$y $rot\n"; } print "\n"; exit 0; sub n_to_total_turn { my ($n) = @_; my $rot = 0; while ($n) { if (($n % 4) == 1) { $rot++; } elsif (($n % 4) == 2) { $rot --; } $n = int($n/4); } return $rot; } } Math-PlanePath-129/devel/factor-rationals.pl0000644000175000017500000001773312236024533016625 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use List::Util 'min', 'max'; use Math::PlanePath::FactorRationals; # uncomment this to run the ### lines use Smart::Comments; { foreach my $n (1 .. 20) { print Math::PlanePath::FactorRationals::_pos_to_pn__negabinary($n),","; } exit 0; } { # different pos=49 numbers got=69 want=88, and more diff # N=50 = 5*5*2 my $path = Math::PlanePath::FactorRationals->new; foreach my $x (1 .. 50) { my $n = $path->xy_to_n(1,$x); print "$x $n\n"; } exit 0; } # Return ($good, $prime,$exp, $prime,$exp,...). # $good is true if a full factorization is found. # $good is false if cannot factorize because $n is too big or infinite. # # If $n==0 or $n==1 then there are no prime factors and the return is # $good=1 and an empty list of primes. # sub INPROGRESS_prime_factors_and_exps { my ($n) = @_; ### _prime_factors(): $n unless ($n >= 0) { return 0; } if (_is_infinite($n)) { return 0; } # if ($n <= 0xFFFF_FFFF) { # return (1, prime_factors($n)); # } my @ret; unless ($n % 2) { my $count = 0; do { $count++; $n /= 2; } until ($n % 2); push @ret, 2, $count; } # Stop at when prime $p reaches $limit and when no prime factor has been # found for the last 20 attempted $p. Stopping only after a run of no # factors found allows big primorials 2*3*5*7*13*... to be divided out. # If the divisions are making progress reducing $i then continue. # # Would like $p and $gap to count primes, not just odd numbers. Perhaps # a table of small primes. The first gap of 36 odds between primes # occurs at prime=31469. cf A000230 smallest prime p for gap 2n. my $limit = 10_000 / (_blog2_estimate($n) || 1); my $gap = 0; for (my $p = 3; $gap < 36 || $p <= $limit ; $p += 2) { if ($n % $p) { $gap++; } else { do { ### prime: $p $n /= $p; push @ret, $p; } until ($n % $p); if ($n <= 1) { ### all factors found ... return (1, @ret); } # if ($n < 0xFFFF_FFFF) { # ### remaining factors by XS ... # return (1, @ret, prime_factors($n)); # } $gap = 0; } } return 0; # factors too big } { my @primes = (2,3,5,7); sub _extend_primes { for (my $p = $primes[-1] + 2; ; $p += 2) { if (_is_prime($p)) { push @primes, $p; return; } } } sub _is_prime { my ($n) = @_; my $limit = int(sqrt($n)); for (my $i = 0; ; $i++) { if ($i > $#primes) { _extend_primes(); } my $prime = $primes[$i]; if ($n % $prime == 0) { return 0; } if ($prime > $limit) { return 1; } } } # $aref is an arrayref of prime exponents, [a,b,c,...] # Return their product 2**a * 3**b * 5**c * ... # sub _factors_join { my ($aref, $zero) = @_; ### _factors_join(): $aref my $n = $zero + 1; for (my $i = 0; $i <= $#$aref; $i++) { if ($i > $#primes) { _extend_primes(); } $n *= ($primes[$i] + $zero) ** $aref->[$i]; } ### join: $n return $n; } # Return an arrayref of prime exponents of $n. # Eg. [a,b,c,...] for $n == 2**a * 3**b * 5**c * ... sub _factors_split { my ($n) = @_; ### _factors_split(): $n my @ret; for (my $i = 0; $n > 1; $i++) { if ($i > 6541) { ### stop, primes too big ... return; } if ($i > $#primes) { _extend_primes(); } my $count = 0; while ($n % $primes[$i] == 0) { $n /= $primes[$i]; $count++; } push @ret, $count; } return \@ret; } # ### f: 2*3*3*5*19 # ### f: _factors_split(2*3*3*5*19) # ### f: _factors_join(_factors_split(2*3*3*5*19),0) # factor_coding => 'spread' # "spread" # if ($self->{'factor_coding'} eq 'spread') { # # N = 2^e1 * 3^e2 * 5^e3 * 7^e4 * 11^e5 * 13^e6 * 17^e7 # # X = 2^e1 * 3^e3 * 5^e5 * 7^e7, Y = 1 # # # # X = 2^e1 * 5^e5 e3=0,e7=0 # # Y = 3^e2 * 7^e4 # # # # X=1,0,1 # # Y=0,0,0 # # 22 = 1,0,0,0,1 # # num = 1,0,1 = 2*5 = 10 # # # my $xexps = _factors_split($x) # or return undef; # overflow # my $yexps = _factors_split($y) # or return undef; # overflow # ### $xexps # ### $yexps # # my @nexps; # my $denpos = -1; # to store first at $nexps[1] # while (@$xexps || @$yexps) { # my $xexp = shift @$xexps || 0; # my $yexp = shift @$yexps || 0; # ### @nexps # ### $xexp # ### $yexp # push @nexps, $xexp, 0; # if ($xexp) { # if ($yexp) { # ### X,Y common factor ... # return undef; # } # } else { # ### den store to: "denpos=".($denpos+2)." yexp=$yexp" # $nexps[$denpos+=2] = $yexp; # } # } # ### @nexps # return (_factors_join(\@nexps, $x*0*$y)); # # } els # if ($self->{'factor_coding'} eq 'spread') { # # N = 2^e1 * 3^e2 * 5^e3 * 7^e4 * 11^e5 * 13^e6 * 17^e7 # # X = 2^e1 * 3^e3 * 5^e5 * 7^e7, Y = 1 # # # # X = 2^e1 * 5^e5 e3=0,e7=0 # # Y = 3^e2 * 7^e4 # # # # 22 = 1,0,0,0,1 # # num = 1,0,1 = 2*5 = 10 # # den = 0 # # # my $nexps = _factors_split($n) # or return; # too big # ### $nexps # my @dens; # my (@xexps, @yexps); # while (@$nexps || @dens) { # my $exp = shift @$nexps; # if (@$nexps) { # push @dens, shift @$nexps; # } # # if ($exp) { # ### to num: $exp # push @xexps, $exp; # push @yexps, 0; # } else { # ### zero take den: $dens[0] # push @xexps, 0; # push @yexps, shift @dens; # } # } # ### @xexps # ### @yexps # return (_factors_join(\@xexps,$zero), # _factors_join(\@yexps,$zero)); # # } else } { # reversing binary, max factor=3 # 0 0 0 fac=0 # 1 1 1 fac=1 # 2 2 2 fac=1 # 3 -1 3 fac=3 # 4 4 4 fac= # 5 -3 5 fac= # 6 -2 6 fac=3 # 7 3 7 fac= # 8 8 8 fac= # 9 -7 9 fac= # 10 -6 10 fac= # 11 7 11 fac= # 12 -4 12 fac=3 # 13 5 13 fac= # 14 6 14 fac= # 15 -5 15 fac=3 # 16 16 16 fac= my $max_fac = 0; foreach my $n (0 .. 2**20) { my $pn = Math::PlanePath::FactorRationals::_pos_to_pn__revbinary($n); my $ninv = Math::PlanePath::FactorRationals::_pn_to_pos__revbinary($pn); my $fac = $n / abs($pn||1); if ($fac >= $max_fac) { $max_fac = $fac; } else { $fac = ''; } print "$n $pn $ninv fac=$fac\n"; die unless $ninv == $n; } print "\n"; exit 0; } { # negabinary, max factor approach 5 my %rev; my $max_fac = 0; foreach my $n (0 .. 2**20) { my $power = 1; my $nega = 0; for (my $bit = 1; $bit <= $n; $bit <<= 1) { if ($n & $bit) { $nega += $power; } $power *= -2; } my $fnega = Math::PlanePath::FactorRationals::_pos_to_pn__negabinary($n); my $ninv = Math::PlanePath::FactorRationals::_pn_to_pos__negabinary($nega); my $fac = -$n / ($nega||1); if ($fac > $max_fac) { $max_fac = $fac; print "$n $nega $fnega $ninv fac=$fac\n"; } else { $fac = ''; } $rev{$nega} = $n; } print "\n"; exit 0; foreach my $nega (sort {$a<=>$b} keys %rev) { my $n = $rev{$nega}; print "$nega $n\n"; } exit 0; } Math-PlanePath-129/devel/complex-minus.pl0000644000175000017500000010101012562515230016134 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.006; use strict; use warnings; use POSIX; use List::Util 'min', 'max'; use Math::BaseCnv; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh'; use Math::PlanePath::ComplexMinus; use lib 'xt'; use MyOEIS; # uncomment this to run the ### lines # use Smart::Comments; { # axis level sequence my $path = Math::PlanePath::ComplexMinus->new; my @dir_func = (sub { my ($i) = @_; ($i,0) }, # X sub { my ($i) = @_; (0,$i) }, # Y sub { my ($i) = @_; (-$i,0) }, # -X sub { my ($i) = @_; (0,-$i) }, # -Y sub { my ($i) = @_; ($i,$i) }, # NE sub { my ($i) = @_; (-$i,$i) }, # NW sub { my ($i) = @_; (-$i,-$i) }, # SW sub { my ($i) = @_; ($i,-$i) }, # SE ); my @values; foreach my $i (0 .. 10_000) { foreach my $dir (0 .. $#dir_func) { my ($x,$y) = $dir_func[$dir]->($i); my $n = $path->xy_to_n($x,$y); my $k = $path->n_to_level($n); if (! defined $values[$dir][-1] || $values[$dir][-1] != $k) { push @{$values[$dir]}, $k; } } } foreach my $dir (0 .. $#dir_func) { print "d=$dir: "; print join(', ',@{$values[$dir]}),"\n"; } print "\n"; exit 0; } { # Y axis and diagonal require Math::BaseCnv; require Math::NumSeq::PlanePathN; my $seq = Math::NumSeq::PlanePathN->new (planepath=> 'ComplexMinus', line_type => 'Y_axis'); my $seq_d = Math::NumSeq::PlanePathN->new (planepath=> 'ComplexMinus', line_type => 'Diagonal_SW'); my $radix = 2; foreach my $i (0 .. 30) { my ($i,$value) = $seq->next; my ($d_i,$d_value) = $seq_d->next; my $v2 = Math::BaseCnv::cnv($value,10,$radix); my $d_v2 = Math::BaseCnv::cnv($d_value,10,$radix); printf "%8d %20s %8d %20s\n", $value, $v2, $d_value, $d_v2; # $d_value == $value*2 or die; } print "\n"; exit 0; } { my $realpart = 1; my $path = Math::PlanePath::ComplexMinus->new (realpart => $realpart); { my $count = 0; for (my $n = $path->n_start; $n < 10_000_000; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($x == 0) { print "$n, "; last if $count++ > 15; } } print "\n"; } $,=', '; print sort({$a<=>$b} 064,067,060,063, 04,07, 00, 03),"\n"; print sort({$a<=>$b} 020,021,034,035, 00,01,014,015),"\n"; for (my $n = $path->n_start; $n < 10_000_000; $n++) { my ($x,$y) = $path->n_to_xy($n); my $want = ($x == 0 ? 1 : 0); my $got = $path->_UNDOCUMENTED__n_is_y_axis($n); if ($want != $got) { printf "%7d %7o want %d got %d\n", $n, $n, $want, $got; exit; } } exit 0; } { # Y axis require Math::BaseCnv; require Math::NumSeq::PlanePathN; my $seq = Math::NumSeq::PlanePathN->new (planepath=> 'ComplexMinus', line_type => 'Y_axis'); my $radix = 8; foreach my $i (0 .. 150) { my ($i,$value) = $seq->next; my $v2 = Math::BaseCnv::cnv($value,10,$radix); printf "%8d %20s\n", $value, $v2; } print "\n"; exit 0; } { # twindragon cf dragon # diff boundary = left # # 28 -> 50 2*28=56 require Math::PlanePath::DragonCurve; my $twindragon = Math::PlanePath::ComplexMinus->new; my $dragon = Math::PlanePath::DragonCurve->new; foreach my $k (0 .. 10) { my $t = $twindragon->_UNDOCUMENTED_level_to_figure_boundary($k); my $dt = $twindragon->_UNDOCUMENTED_level_to_figure_boundary($k) - $twindragon->_UNDOCUMENTED_level_to_figure_boundary($k-1); my $l = $dragon->_UNDOCUMENTED_level_to_left_line_boundary($k); my $r = $dragon->_UNDOCUMENTED_level_to_right_line_boundary($k); my $dr = $dragon->_UNDOCUMENTED_level_to_right_line_boundary($k) + 2*$dragon->_UNDOCUMENTED_level_to_right_line_boundary($k-1); $dr = 2*$r; print "$t dt=$dt $l $r $dr\n"; } exit 0; } { # A203181 nxk count endings # distinct 10,33,108,342,1096,3501,11199,35821 my @distinct; foreach my $k (2 .. 9) { print "k=$k\n"; my %counts; { my @mats = ([]); @mats = map {mat_extend($_,$k)} @mats; @mats = map {mat_extend($_,$k)} @mats; foreach my $m (@mats) { $counts{mat_end_to_str($m)}++; } } my $prev_distinct = 0; for (;;) { { my %new_counts; while (my ($str,$count) = each %counts) { foreach my $m (mat_extend(str_to_mat($str),$k)) { $new_counts{mat_end_to_str($m)} += $count; } } %counts = %new_counts; } my $distinct = scalar(keys %counts); print "distinct $distinct\n"; if ($distinct == $prev_distinct) { push @distinct, $distinct; last; } $prev_distinct = $distinct; } print "----------\n"; } print join(',',@distinct),"\n"; Math::OEIS::Grep->search(array=>\@distinct); exit 0; } { my %str_to_mat; sub str_to_mat { my ($str) = @_; return ($str_to_mat{$str} ||= [ map {[split //,$_]} split /;/, $str ]); } } { # A203181 nxk count # distinct 10,33,108 my $k = 2; # my @mats = ([[map {$_%2} 0 .. $k-1]]); my @mats = ([]); # @mats = [[1,2],[0,1]]; foreach my $y (0 .. 20) { ### loop for y: $y @mats = map {mat_extend($_,$k)} @mats; ### mats now: scalar(@mats) # printmats(@mats); # foreach my $m (@mats) { # print join(';',map{join('',@$_)}@$m),"\n"; # } my %count; foreach my $m (@mats) { my $e = mat_end_to_str($m); $count{$e}++; } my $distinct = scalar(keys %count); printf "yn=%2d count %d (distinct %d)\n", $y+1,scalar(@mats), $distinct; foreach my $e (sort keys %count) { print "$e $count{$e}\n"; } } exit 0; } sub mat_extend { my ($input_m,$k) = @_; my $y = scalar(@$input_m); my @mats = ($input_m); foreach my $x (0 .. $k-1) { my @new_mats; foreach my $m (@mats) { foreach my $digit (0, 1, 2) { ### consider: $m ### $y ### $x ### $digit if ($digit == 0) { next if $y >= 1 && $m->[$y-1]->[$x] == 0; # cannot 0 above next if $x >= 1 && $m->[$y]->[$x-1] == 0; # cannot 0 left } elsif ($digit == 1) { if ($y >= 1 && $m->[$y-1]->[$x] == 0) { # good, 0 above } elsif ($x >= 1 && $m->[$y]->[$x-1] == 0) { # good, 0 left } else { # bad next; } } else { # $digit == 2 if ($y >= 2 && $m->[$y-1]->[$x] == 1 # 1 above, and && $m->[$y-2]->[$x] == 0) { # 0 above # good } elsif ($x >= 2 && $m->[$y]->[$x-1] == 1 # 1 above, and && $m->[$y]->[$x-2] == 0) { # 0 above # good } else { # bad next; } } ### yes ... my $new_m = copymat($m); $new_m->[$y]->[$x] = $digit; push @new_mats, $new_m; } } @mats = @new_mats; } return @mats; } sub mat_end_to_str { my ($m) = @_; if (@$m >= 2) { return join('',@{$m->[-2]}) . ';' . join('',@{$m->[-1]}); } else { return join('',@{$m->[-1]}); } } sub printmats { foreach my $m (@_) { printaref($m); print "\n"; } } sub printaref { my ($m) = @_; foreach my $row (@$m) { print join('',@$row),"\n"; } } sub copymat { my ($m) = @_; return [ map {[@$_]} @$m ]; } { # 0,1 0,1 0,1 0,1 0,1 0,1 # 1,0 1,0 1,0 1,0 1,0 1,0 # 0,1 0,1 0,1 2,1 2,1 0,1 # 1,0 1,2 1,0 0,1 0,2 1,2 # 2,1 2,0 0,1 1,0 1,0 0,1 # A B C D E F G H I J # 0,1 1,0 1,0 0,1 2,1 2,1 1,2 0,2 2,0 1,2 # 1,0 0,1 2,1 1,2 0,1 0,2 2,0 1,0 0,1 0,1 # --- --- --- --- --- --- --- --- --- --- # 0,1=B 1,0=A 0,1=E 0,1=J 1,0=A 1,0=H 0,1=I 0,1=B 1,0=A 1,0=A # 2,1=C 1,2=D 0,2=F 2,0=G H=A I=B 2,1=C 1,2=D # 2*E E,G F=E H=A I=B J=E # # A -> B+C # B -> A+D B=I # C -> 2E # D -> E+G # E -> A E=F=H=J # G -> B # # 4,6,10,18,30,50,86,146,246,418,710,1202,2038,3458 require Math::Matrix; # A B C D E F G H I J my $m = Math::Matrix->new ([0,1,1,0,0,0,0,0,0,0], # A [1,0,0,1,0,0,0,0,0,0], # B [0,0,0,0,1,1,0,0,0,0], # C [0,0,0,0,0,0,1,0,0,1], # D [1,0,0,0,0,0,0,0,0,0], # E=J [0,0,0,0,0,0,0,1,0,0], # F [0,0,0,0,0,0,0,0,1,0], # G [0,1,1,0,0,0,0,0,0,0], # H=A [1,0,0,1,0,0,0,0,0,0], # I=B [1,0,0,0,0,0,0,0,0,0], # J ); # print "det ",$m->determinant,"\n"; # too slow =pod Pari m = [0,1,1,0,0,0,0,0,0,0; \ 1,0,0,1,0,0,0,0,0,0; \ 0,0,0,0,1,1,0,0,0,0; \ 0,0,0,0,0,0,1,0,0,1; \ 1,0,0,0,0,0,0,0,0,0; \ 0,0,0,0,0,0,0,1,0,0; \ 0,0,0,0,0,0,0,0,1,0; \ 0,1,1,0,0,0,0,0,0,0; \ 1,0,0,1,0,0,0,0,0,0; \ 1,0,0,0,0,0,0,0,0,0 ] =cut my $dot = Math::Matrix->new([1,1,1,1,1,1,1,1,1,1,1]); my $v = Math::Matrix->new([1,0,0,0,0,0,0,0,0,0,0]); foreach my $i (0 .. 6) { print "$i\n"; my $p = matrix_pow($m,$i); my $pv = $v*$p; print $pv->dot_product($dot),"\n"; matrix_print($pv); } # print $v,"\n"; #print $v*($m*$m),"\n"; # print "\nlast\n"; # # 3 2 1 1 # $v = Math::Matrix->new([1,2,2,1,2,1,0,0,0,1]); # my $pv = $v*$m; # print $pv->dot_product($dot),"\n"; # print vector_str($pv); # V*dot = total[i] # V*M*dot = total[i+1] # V*M^2*dot = total[i+2] # V*M^3*dot = total[i+3] # seek total[i+3] = total[i+2] # + 0*total[i+1] # + 2*total[i] # M^3 = M^2 + 2*I $v = Math::Matrix->new([1,0,0,0,0,0,0,0,0,0,0]); my $i = 2; $dot = $dot->transpose; my $t0 = ($v * matrix_pow($m,$i)) * $dot; my $t1 = ($v * matrix_pow($m,$i+1)) * $dot; my $t2 = ($v * matrix_pow($m,$i+2)) * $dot; my $t3 = ($v * matrix_pow($m,$i+3)) * $dot; print "$t0 $t1 $t2 $t3\n"; # my $d = matrix_pow($m,4) - (matrix_pow($m,3) + $m->multiply_scalar(2)); my $d = matrix_pow($m,4) - (matrix_pow($m,3) + $m->multiply_scalar(2)); matrix_print($d); print "\n"; # m^2*dot + 2*dot == m^3*dot # + $dot->multiply_scalar(2) { my $diff = $m*$m*$dot + $dot+$dot - $m*$m*$m*$dot; print "diff\n"; matrix_print($diff); print "\n"; } foreach my $exp (-1 .. 5) { my $diff = matrix_pow($m,$exp+2) + matrix_pow($m,$exp) + matrix_pow($m,$exp) - matrix_pow($m,$exp+3) ; print "diff\n"; matrix_print(($diff*$dot)->transpose); print "\n"; } # print "m\n"; matrix_print($m); print "\n"; # my $two = $m->multiply_scalar(2); # print "two\n"; matrix_print($two); print "\n"; # my $three = matrix_pow($m,3); # print "powthree\n"; matrix_print($three); print "\n"; # my $sum = $three + $two; # print "sum\n"; matrix_print($sum*$dot); print "\n"; # my $four = matrix_pow($m,4); # print "four\n"; matrix_print($four*$dot); print "\n"; # my $diff = $four*$dot - $sum*$dot; # print "four\n"; matrix_print($diff); print "\n"; exit 0; sub matrix_print { my ($m) = @_; my $len = 0; foreach my $row (@$m) { foreach my $value (@$row) { $len = max($len,length($value)); } } foreach my $row (@$m) { foreach my $value (@$row) { printf " %*s", $len, $value; } print "\n"; } } # sub vector_str { # my ($v) = @_; # my $str = "$v"; # $str =~ s{\.00000 *( |$)}{$1}g; # return $str; # } } { require Math::Matrix; my $m = Math::Matrix->new ([1,0,0], [0,0,0], [0,0,0]); print "det ",$m->determinant,"\n"; my $inv = $m->invert; print "inverse\n"; matrix_print($inv); print "\n"; my $prod = $m * $inv; print "prod\n"; matrix_print($prod); print "\n"; my $identity = $m->new_identity(3); my $wide = $m->concat($identity); print "wide\n"; matrix_print($wide); print "\n"; my $solve = $wide->solve; print "solve\n"; matrix_print($solve); print "\n"; exit 0; } { # print A203181 table require Math::NumSeq::OEIS; my $seq = Math::NumSeq::OEIS->new(anum=>'A203181'); my @table; my $len = 0; DD: for (my $d = 0; ; $d++) { foreach my $y (0 .. $d) { my ($i,$value) = $seq->next or last DD; push @{$table[$y]}, $value; $len = max($len,length($value)); } } $len++; print "len=$len\n"; $len = 15; foreach my $y (0 .. $#table) { my $aref = $table[$y]; foreach my $x (0 .. $#$aref) { last if $x > 3; my $value = $aref->[$x]; printf "%*d", $len, $value; } print "\n"; } exit 0; } { require Math::Matrix; my $m = Math::Matrix->new ([1,2,3], [0,0,0], [0,0,0], ); print matrix_pow($m,0); exit 0; } # m^(2k) = (m^2)^k # m^(2k+1) = (m^2)^k*m sub matrix_pow { my ($m, $exp, $swap) = @_; if ($swap) { # when called through "**" operator overload. die "Cannot raise scalar to matrix power"; } if ($exp != int($exp)) { die "Cannot raise matrix to non-integer power"; } if ($exp == 0) { my $size = @$m; if ($size != scalar(@{$m->[0]})) { # non-square matrix, no inverse and so no identity return undef; } return $m->new_identity($m->size); } if ($exp < 0) { $m = $m->invert; if (! defined $m) { return undef; } $exp = -$exp; } unless ($exp / 2 < $exp) { die "Cannot raise matrix to infinite power"; } # Result is $low * ($m ** $exp). # When $exp odd, ($m ** ($e+1)) = ($m**$e)*$m, so $low*=$m then $e even. # When $exp even, ($m ** (2*$k)) = ($m*$m) ** $k, so $m*=$m. # $low is undef if it's the identity matrix and so not needed yet. # If $exp is a power-of-2 then $low is never needed, just $m squared up. # Use $exp%2 rather than $exp&1 since that allows NV powers (NV can be a # 53-bit integer whereas UV might be only 32-bits). my $low; while ($exp > 1) { if ($exp % 2) { if (defined $low) { $low *= $m; } else { $low = $m; } $exp -= 1; } $m *= $m; $exp /= 2; } if (defined $low) { $m *= $low; } return $m; } { # neighbours across 2^k blocks my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); my @dir8_to_dx = (1, 1, 0,-1, -1, -1, 0, 1); my @dir8_to_dy = (0, 1, 1, 1, 0, -1, -1,-1); my $path = Math::PlanePath::ComplexMinus->new; my @values; my $prev_count = 0; foreach my $k (0 .. 13) { my $pow = 2**$k; my $count = 0; foreach my $n (2 .. $pow-1) { my ($x,$y) = $path->n_to_xy($n); # foreach my $i (0 .. $#dir4_to_dx) { foreach my $i (0, 2) { my $n2 = $path->xy_to_n($x+$dir4_to_dx[$i], $y+$dir4_to_dy[$i]); if (defined $n2 && $n2 >= $pow) { # num boundary $count++; last; } # if (defined $n2 && $n2 >= $pow && $n2 < 2*$pow) { # $count++; # last; # } } } my $value = ($count - $prev_count)/1; # my $value = $count/2; # my $value = $count; printf "%2d %4d %10b\n", $k, $value, $value; push @values, $value; $prev_count = $count; } shift @values; shift @values; print join(',',@values),"\n"; Math::OEIS::Grep->search(array=>\@values); exit 0; } { # counting all 4 directions, is boundary length # 2 * A003476 a(n) = a(n-1) + 2a(n-3). # 1, 2, 3, 5, 9, 15, 25, 43, 73, 123, 209, 355, # A203175 nX2 arrays 1, 1, 2, 4, 6, 10, 18, 30, 50, 86, 146, 246, 418, 710, # 1 immediately preceded by 0 to the left or above # 0 not immediately preceded by a 0 # 2 immediately preceded by 0 1 to the left or above # 4,6,10,18,30,50,86,146,246,418,710,1202,2038,3458 # # 30 = 18+2*6 # # A052537 2*A or 2*B or 2*C # n=4 a(4)=4 # 0,1 0,1 0,1 0,1 # 1,0 1,0 1,0 1,0 # 0,1 0,1 2,1 2,1 # 1,0 1,2 0,1 0,2 # [2] [2] [2] [1] = 7 # # n=5 a(4)=6 # 0,1 0,1 0,1 0,1 0,1 0,1 # 1,0 1,0 1,0 1,0 1,0 1,0 # 0,1 0,1 0,1 2,1 2,1 0,1 # 1,0 1,2 1,0 0,1 0,2 1,2 # 2,1 2,0 0,1 1,0 1,0 0,1 # [2] [?] [2] [2] [2] [2] = 10 # # 0,1 -> 1,0 later 1,2 # 0,2 -> 1,0 # 1,0 -> 0,1 2,1 # 1,2 -> 0,1 2,0 # 2,0 -> # 2,1 -> 0,1 0,2 # +---+---+ # | 0 1 | boundary[2^1] = 6 # +---+---+ # +---+---+ # | 2 3 | # +---+ +---+ # | 0 1 | # +---+---+ # (2n-1 0 2n ) (a) # (n^2-2n+2 0 (n-1)^2 ) (b) # (0 1 0 ) (c) # # inverse [ (n^2 - 2*n + 1)/(-n^2 - 1) -2*n/(-n^2 - 1) 0] # [ 0 0 1] # [(-n^2 + 2*n - 2)/(-n^2 - 1) (2*n - 1)/(-n^2 - 1) 0] # # c[k] = b[k-1] # a[k] = (2n-1)a[k-1] + 2n*c[k-1] # # m = [2*n-1,0,2*n; n^2-2*n+2,0,(n-1)^2; 0,1,0] # v = [n;n^2+1-n;1] so m*v transforms to new A,B,C # m^-1*v = [n ; 1; 1-n] # t=[0,0,0; 0,0,0; 1,1,1] # f=[0,1,0; 0,0,1; 1,0,0] # f*t=[0,0,0; 1,1,1; 0,0,0] # f^2*t=[1,1,1; 0,0,0; 0,0,0] # s=(t + f*t*m + f^2*t*m^2) # s*abc = l210 # s*m*abc = r*l210 # s*m*abc = r*s*abc # s*m = r*s # r = s*m*s^-1 # r=s*m*s^-1 = [ 2*n-1, n^2+1 - 2*n, n^2+1] # [1 0 0] # [0 1 0] # # (1 0 2) ( 0 1 0) r=1 initial (1) prev (1) # (1 0 0) ( 0 0 1) (1) (1) # (0 1 0) ( 1/2 -1/2 0) (1) (0) # m=[1,0,2;1,0,0;0,1,0] # # (3 0 4) (-1/5 4/5 0) r=2 initial (2) prev -2+4*3 = 2 # (2 0 1) ( 0 0 1) (3) = 1 # (0 1 0) ( 2/5 -3/5 0) (1) = -1 # m=[3,0,4;2,0,1;0,1,0] # 20 21 22 23 24 # 15 16 17 18 19 # 10 11 12 13 14 # 5 6 7 8 9 # 0 1 2 3 4 # 0 -> 4 # 5 -> 12 # 25 -> (5+8+5)*2 = 36 # l2 = 2*(norm # top # + r*(norm-1) # steps # + norm) # side # = 2*(norm + r*norm - r + norm) # = 2*(2*norm + r*norm - r) # = 2*((r+2)*norm - r) # = 2*((r+2)*norm - r-2 +2)) # = 2*((r+2)*norm - (r+2) +2)) # = 2*(r+2)*(norm-1) + 4 my $r = 2; my $norm = $r*$r+1; sub boundary_by_recurrence { my ($k) = @_; # my $l2 = 2*$r**3 + 4*$r**2 + 4; my $l2 = 2*($norm-1)*($r+2) + 4; my $l1 = 2*$norm + 2; my $l0 = 4; foreach (1 .. $k) { ($l2,$l1,$l0) = ((2*$r-1) * $l2 + ($norm - 2*$r) * $l1 + $norm * $l0, $l2, $l1); # ($l2,$l1,$l0) = ((2*$r-1)*$l2 # + ($r**2+1 - 2*$r)*$l1 # + ($r**2+1)*$l0, # # $l2, $l1); } return $l0; } sub abc_by_pow { my ($k) = @_; # my $a = 2*2; # my $b = 1*2; # my $c = -1*2; # my $a = $r*2; # my $b = ($norm-$r)*2; # my $c = 1*2; # my $a = 2 * $r / ($r*$r+1); # my $b = 2 * ($r*$r+1 - $r) / ($r*$r+1); # my $c = 2 * 1; my $a = 2*$r; my $b = 2; my $c = 2*(1-$r); foreach (1 .. $k) { ($a,$b,$c) = ((2*$r-1)*$a + 0 + 2*$r*$c, ($r*$r-2*$r+2)*$a + 0 + ($r-1)*($r-1)*$c, 0 + $b); } return ($a,$b,$c); } sub boundary_by_pow { my ($k) = @_; my ($a,$b,$c) = abc_by_pow($k); return 2*($a+$b+$c); } my @values; my $path = Math::PlanePath::ComplexMinus->new (realpart => $r); my $prev_len = 1; my $prev_ratio = 1; foreach my $k (1 .. 30) { my $pow = $norm**$k; my $len = 0; #path_boundary_length($path,$pow); my $len_by_pow = boundary_by_pow($k); my $len_by_rec = boundary_by_recurrence($k); my $ratio = $pow / $len_by_pow; my $f = 2* log($len_by_pow / $prev_len) / log($norm); printf "%2d %s %s %s %.6f\n", $k, $len, $len_by_pow, $len_by_rec, $f; my ($a,$b,$c) = abc_by_pow($k); push @values, $a; $prev_len = $len_by_pow; $prev_ratio = $ratio; } print "seek ",join(', ',@values),"\n"; Math::OEIS::Grep->search(array=>\@values); exit 0; } BEGIN { my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); sub path_boundary_length { my ($path, $n_below) = @_; ### $n_below my $boundary = 0; my %seen; my @pending_x = (0); my @pending_y = (0); while (@pending_x) { my $x = pop @pending_x; my $y = pop @pending_y; next if $seen{$x}{$y}; foreach my $i (0 .. $#dir4_to_dx) { my $ox = $x + $dir4_to_dx[$i]; my $oy = $y + $dir4_to_dy[$i]; ### consider: "$x,$y to $ox,$oy" my $n = $path->xy_to_n($ox,$oy); if ($n >= $n_below) { ### outside ... $boundary++; } else { ### inside ... push @pending_x, $ox; push @pending_y, $oy; } } $seen{$x}{$y} = 1; } return $boundary; } } { # min/max rectangle # # repeat at dx,dy require Math::BaseCnv; my $xmin = 0; my $xmax = 0; my $ymin = 0; my $ymax = 0; my $dx = 1; my $dy = 0; my $realpart = 2; my $norm = $realpart*$realpart + 1; printf "level xmin xmax xdiff | ymin ymax ydiff\n"; for (0 .. 22) { my $xminR = Math::BaseCnv::cnv($xmin,10,$norm); my $yminR = Math::BaseCnv::cnv($ymin,10,$norm); my $xmaxR = Math::BaseCnv::cnv($xmax,10,$norm); my $ymaxR = Math::BaseCnv::cnv($ymax,10,$norm); my $xdiff = $xmax - $xmin; my $ydiff = $ymax - $ymin; my $xdiffR = Math::BaseCnv::cnv($xdiff,10,$norm); my $ydiffR = Math::BaseCnv::cnv($ydiff,10,$norm); printf "%2d %11s %11s =%11s | %11s %11s =%11s\n", $_, $xminR,$xmaxR,$xdiffR, $yminR,$ymaxR,$ydiffR; $xmax = max ($xmax, $xmax + $dx*($norm-1)); $ymax = max ($ymax, $ymax + $dy*($norm-1)); $xmin = min ($xmin, $xmin + $dx*($norm-1)); $ymin = min ($ymin, $ymin + $dy*($norm-1)); ### assert: $xmin <= 0 ### assert: $ymin <= 0 ### assert: $xmax >= 0 ### assert: $ymax >= 0 # multiply i-r, ie. (dx,dy) = (dx + i*dy)*(i-$realpart) $dy = -$dy; ($dx,$dy) = ($dy - $realpart*$dx, $dx + $realpart*$dy); } # print 3*$xmin/$len+.001," / 3\n"; # print 6*$xmax/$len+.001," / 6\n"; # print 3*$ymin/$len+.001," / 3\n"; # print 3*$ymax/$len+.001," / 3\n"; exit 0; sub to_bin { my ($n) = @_; return ($n < 0 ? '-' : '') . sprintf('%b', abs($n)); } } { # min/max hypot for level $|=1; my $realpart = 2; my $norm = $realpart**2 + 1; my $path = Math::PlanePath::ComplexMinus->new (realpart => $realpart); my $prev_min = 1; my $prev_max = 1; for (my $level = 1; $level < 25; $level++) { my $n_start = $norm**($level-1); my $n_end = $norm**$level; my $min_hypot = POSIX::DBL_MAX(); my $min_x = 0; my $min_y = 0; my $min_pos = ''; my $max_hypot = 0; my $max_x = 0; my $max_y = 0; my $max_pos = ''; print "level $level n=$n_start .. $n_end\n"; foreach my $n ($n_start .. $n_end) { my ($x,$y) = $path->n_to_xy($n); my $h = $x*$x + $y*$y; if ($h < $min_hypot) { $min_hypot = $h; $min_pos = "$x,$y"; } if ($h > $max_hypot) { $max_hypot = $h; $max_pos = "$x,$y"; } } # print "$min_hypot,"; # print " min $min_hypot at $min_x,$min_y\n"; # print " max $max_hypot at $max_x,$max_y\n"; { my $factor = $min_hypot / $prev_min; print " min r^2 $min_hypot 0b".sprintf('%b',$min_hypot)." at $min_pos factor $factor\n"; print " cf formula ", 2**($level-7), "\n"; } # { # my $factor = $max_hypot / $prev_max; # print " max r^2 $max_hypot 0b".sprintf('%b',$max_hypot)." at $max_pos factor $factor\n"; # } $prev_min = $min_hypot; $prev_max = $max_hypot; } exit 0; } { # covered inner rect # depends on which coord extended first require Math::BaseCnv; $|=1; my $realpart = 1; my $norm = $realpart**2 + 1; my $path = Math::PlanePath::ComplexMinus->new (realpart => $realpart); my %seen; my $xmin = 0; my $xmax = 0; my $ymin = 0; my $ymax = 0; for (my $level = 1; $level < 25; $level++) { my $n_start = $norm**($level-1); my $n_end = $norm**$level - 1; foreach my $n ($n_start .. $n_end) { my ($x,$y) = $path->n_to_xy($n); $seen{"$x,$y"} = 1; $xmin = min ($xmin, $x); $xmax = max ($xmax, $x); $ymin = min ($ymin, $y); $ymax = max ($ymax, $y); } my $x1 = 0; my $y1 = 0; my $x2 = 0; my $y2 = 0; for (;;) { my $more = 0; { my $x = $x1-1; my $good = 1; foreach my $y ($y1 .. $y2) { if (! $seen{"$x,$y"}) { $good = 0; last; } } if ($good) { $more = 1; $x1 = $x; } } { my $x = $x2+1; my $good = 1; foreach my $y ($y1 .. $y2) { if (! $seen{"$x,$y"}) { $good = 0; last; } } if ($good) { $more = 1; $x2 = $x; } } { my $y = $y1-1; my $good = 1; foreach my $x ($x1 .. $x2) { if (! $seen{"$x,$y"}) { $good = 0; last; } } if ($good) { $more = 1; $y1 = $y; } } { my $y = $y2+1; my $good = 1; foreach my $x ($x1 .. $x2) { if (! $seen{"$x,$y"}) { $good = 0; last; } } if ($good) { $more = 1; $y2 = $y; } } last if ! $more; } printf "%2d %10s %10s %10s %10s\n", $level, Math::BaseCnv::cnv($x1,10,2), Math::BaseCnv::cnv($x2,10,2), Math::BaseCnv::cnv($y1,10,2), Math::BaseCnv::cnv($y2,10,2); } exit 0; } { # n=2^k bits require Math::BaseCnv; my $path = Math::PlanePath::ComplexMinus->new; foreach my $i (0 .. 16) { my $n = 2**$i; my ($x,$y) = $path->n_to_xy($n); my $x2 = Math::BaseCnv::cnv($x,10,2); my $y2 = Math::BaseCnv::cnv($y,10,2); printf "%#7X %12s %12s\n", $n, $x2, $y2; } print "\n"; # X axis bits require Math::BaseCnv; foreach my $x (0 .. 400) { my $n = $path->xy_to_n($x,0); my $w = int(log($n||1)/log(2)) + 2; my $n2 = Math::BaseCnv::cnv($n,10,2); print "x=$x n=$n = $n2\n"; for (my $bit = 1; $bit <= $n; $bit <<= 1) { if ($n & $bit) { my ($x,$y) = $path->n_to_xy($bit); my $x2 = Math::BaseCnv::cnv($x,10,2); my $y2 = Math::BaseCnv::cnv($y,10,2); printf " %#*X %*s %*s\n", $w, $bit, $w, $x2, $w, $y2; } } } print "\n"; exit 0; } { # X axis generating # hex 1 any X=0x1 or -1 # 2 never # C bits 4,8 together X=0x2 or -2 my @ns = (0, 1, 0xC, 0xD); my @xseen; foreach my $pos (1 .. 5) { push @ns, map {16*$_+0, 16*$_+1, 16*$_+0xC, 16*$_+0xD} @ns; } my $path = Math::PlanePath::ComplexMinus->new; require Set::IntSpan::Fast; my $set = Set::IntSpan::Fast->new; foreach my $n (@ns) { my ($x,$y) = $path->n_to_xy($n); $y == 0 or die "n=$n x=$x y=$y"; $set->add($x); } print "ok $#ns\n"; print "x span ",$set->as_string,"\n"; print "x card ",$set->cardinality,"\n"; exit 0; } { # n=2^k bits require Math::BaseCnv; my $path = Math::PlanePath::ComplexMinus->new; foreach my $i (0 .. 20) { my $n = 2**$i; my ($x,$y) = $path->n_to_xy($n); my $x2 = Math::BaseCnv::cnv($x,10,2); my $y2 = Math::BaseCnv::cnv($y,10,2); printf "%6X %20s %11s\n", $n, $x2, $y2; } print "\n"; exit 0; } { require Math::NumSeq::PlanePathDelta; my $seq = Math::NumSeq::PlanePathDelta->new (planepath=> 'ComplexMinus', delta_type => 'dX'); foreach my $i (0 .. 50) { my ($i,$value) = $seq->next; print "$value,"; } print "\n"; exit 0; } { # max Dir4 require Math::BaseCnv; print 4-atan2(2,1)/atan2(1,1)/2,"\n"; require Math::NumSeq::PlanePathDelta; my $realpart = 3; my $radix = $realpart*$realpart + 1; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => "ComplexPlus,realpart=$realpart", delta_type => 'Dir4'); my $dx_seq = Math::NumSeq::PlanePathDelta->new (planepath => "ComplexPlus,realpart=$realpart", delta_type => 'dX'); my $dy_seq = Math::NumSeq::PlanePathDelta->new (planepath => "ComplexPlus,realpart=$realpart", delta_type => 'dY'); my $max = 0; for (1 .. 1000000) { my ($i, $value) = $seq->next; # foreach my $k (1 .. 1000000) { # my $i = $radix ** (4*$k+3) - 1; # my $value = $seq->ith($i); if ($value > $max) { my $dx = $dx_seq->ith($i); my $dy = $dy_seq->ith($i); my $ri = Math::BaseCnv::cnv($i,10,$radix); my $rdx = Math::BaseCnv::cnv($dx,10,$radix); my $rdy = Math::BaseCnv::cnv($dy,10,$radix); my $f = $dy && $dx/$dy; printf "%d %s %.5f %s %s %.3f\n", $i, $ri, $value, $rdx,$rdy, $f; $max = $value; } } exit 0; } { # innermost points coverage require Math::BaseCnv; foreach my $realpart (1 .. 20) { my $norm = $realpart**2 + 1; my $path = Math::PlanePath::ComplexMinus->new (realpart => $realpart); my $n_max = 0; my $show = sub { my ($x,$y) = @_; my $n = $path->xy_to_n($x,$y); print "$x,$y n=$n\n"; if ($n > $n_max) { $n_max = $n; } }; $show->(1,0); $show->(1,1); $show->(0,1); $show->(-1,1); $show->(-1,0); $show->(-1,-1); $show->(0,-1); $show->(1,-1); my $n_max_base = to_base($n_max,$norm); my $n_max_log = log($n_max)/log($norm); print "n_max $n_max $n_max_base $n_max_log\n"; print "\n"; } exit 0; sub to_base { my ($n, $radix) = @_; my $ret = ''; do { my $digit = $n % $radix; $ret = "[$digit]$ret"; } while ($n = int($n/$radix)); return $ret; } } { require Math::PlanePath::ComplexPlus; require Math::BigInt; my $realpart = 10; my $norm = $realpart*$realpart + 1; ### $norm my $path = Math::PlanePath::ComplexPlus->new (realpart=>$realpart); my $prev_dist = 1; print sqrt($norm),"\n"; foreach my $level (1 .. 10) { my $n = Math::BigInt->new($norm) ** $level - 1; my ($x,$y) = $path->n_to_xy($n); my $radians = atan2($y,$x); my $degrees = $radians / 3.141592 * 180; my $dist = sqrt($x*$x+$y*$y); my $f = $dist / $prev_dist; printf "%2d %.2f %.4f %.2f\n", $level, $dist, $f, $degrees; $prev_dist = $dist; } exit 0; } { require Math::PlanePath::ComplexPlus; my $path = Math::PlanePath::ComplexPlus->new (realpart=>2); foreach my $i (0 .. 10) { { my $x = $i; my $y = 1; my $n = $path->xy_to_n($x,$y); if (! defined $n) { $n = 'undef'; } print "xy_to_n($x,$y) = $n\n"; } } foreach my $i (0 .. 10) { { my $n = $i; my ($x,$y) = $path->n_to_xy($n); print "n_to_xy($n) = $x,$y\n"; } } exit 0; } { my $count = 0; my $realpart = 5; my $norm = $realpart*$realpart+1; foreach my $x (-200 .. 200) { foreach my $y (-200 .. 200) { my $new_x = $x; my $neg_y = $x - $y*$realpart; my $digit = $neg_y % $norm; $new_x -= $digit; $neg_y -= $digit; next unless ($new_x*$realpart+$y)/$norm == $x; next unless -$neg_y/$norm == $y; print "$x,$y digit=$digit\n"; $count++; } } print "count $count\n"; exit 0; } Math-PlanePath-129/devel/wunderlich.pl0000644000175000017500000005375213774254145015536 0ustar gggg#!/usr/bin/perl -w # Copyright 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'sum'; use Math::BaseCnv 'cnv'; use Math::PlanePath; use Math::PlanePath::WunderlichSerpentine; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; # uncomment this to run the ### lines use Smart::Comments; { # Numbers Samples, transposed my $path = Math::PlanePath::WunderlichSerpentine->new; foreach my $y (reverse 0..8) { foreach my $x (0..8) { my ($x,$y) = ($y,$x); # transposed my $n = $path->xy_to_n($x,$y); printf "%2d ", $n; } print "\n"; } foreach my $n (0..9**3) { my ($x,$y) = $path->n_to_xy($n); print "$x," } print "\n"; { ! defined($path->xyxy_to_n_either(-1,1, 0,1)) or die; my $width = 4; foreach my $y (reverse 0..8) { foreach my $x (0..8) { my $bar = defined($path->xyxy_to_n_either($y,$x, $y+1,$x)) ? '|' : ''; printf "%*s", $width, $bar; } print "\n"; foreach my $x (0..8) { my $n = sprintf '%d', $path->xy_to_n($y,$x); my $dash = defined($path->xyxy_to_n_either($y,$x, $y,$x-1)) ? '-' : ' '; $dash x= $width-length($n); print $dash,$n; } print "\n"; } } exit 0; } { # WunderlichSerpentine # N=15 33 # yx y=3 x->0 yrev=1 xrev=0 # N=125 1331 my $n = 15; my $radix = 4; my $path = Math::PlanePath::PeanoDiagonals->new (radix => $radix); my ($x,$y) = $path->n_to_xy(15); ### xy: "$x, $y" ### $n ### cnv: cnv($n,10,$radix) exit 0; } { # PeanoDiagonals devel my $plain = Math::PlanePath::PeanoCurve->new (radix => 4); my $diag = Math::PlanePath::PeanoDiagonals->new (radix => 4); foreach my $n (0 .. 4**4) { my ($plain_x,$plain_y) = $plain->n_to_xy($n); my ($diag_x,$diag_y) = $diag->n_to_xy($n); printf "%6d %6d %d %d %3d %3d\n", $n, cnv($n,10,4), $diag_x-$plain_x, $diag_y-$plain_y, cnv($diag_x,10,4), cnv($diag_y,10,4); } exit 0; } # Uniform Grids # 4.1-O Wunderlich serpentine in diamond # bottom right between squares = Wunderlich Figure 3 # top left across diagonals = Mandelbrot page 62 # # 1.3-A Peano squares starting X direction { # PeanoDiagonals X axis # not in OEIS: 2,16,18,20,142,144,146,160,162,164,178,180,182,1276,1278 # half # not in OEIS: 1,8,9,10,71,72,73,80,81,82,89,90,91,638,639,640,647 # -----> <------ ------> # 3*9^k 6*9^k # base 9 digits 0,-2,2 # xx(n) = my(v=digits(n,3)); v=apply(d->if(d==0,-2,d==1,0,d==2,2), v); fromdigits(v,9); # vector(20,n,xx(n)) # Set(select(n->n>=0,vector(55,n,xx(n)))) == \ # [0,2,16,18,20,142,144,146,160,162,164,178,180,182,1276,1278] my $path = Math::PlanePath::PeanoDiagonals->new; foreach my $x (0 .. 81) { my $n = $path->xy_to_n($x,0) // next; my $n3 = cnv($n,10,3); my $n9 = cnv($n,10,9); print "n=$n $n3 $n9\n"; # print $n/2,","; } print "\n"; exit 0; } { # PeanoDiagonals other N my $path = Math::PlanePath::PeanoDiagonals->new; foreach my $n (1 .. 10) { my ($x,$y) = $path->n_to_xy($n); my @n_list = $path->xy_to_n_list($x,$y); @n_list <= 2 or die; my ($other) = grep {$_!=$n} @n_list; my $n3 = cnv($n,10,3); my $other3 = (defined $other ? cnv($other,10,3) : 'undef'); my $delta = (defined $other ? abs($other - $n) : undef); my $delta3 = (defined $delta ? cnv($delta,10,3) : 'undef'); my $by_func = PeanoDiagonals_other_n($n); my $by_func3 = (defined $by_func ? cnv($by_func,10,3) : 'undef'); $by_func //= 'undef'; my $diff = $other3 eq $by_func3 ? '' : ' ****'; print "n=$n $n3 other $other3 $by_func3$diff d=$delta3\n"; } print "\n"; exit 0; sub PeanoDiagonals_other_n { my ($n) = @_; ### PeanoDiagonals_other_n(): $n my @digits = digit_split_lowtohigh($n,3); my $c = 0; for (my $i = 0; $c>0 || $i <= $#digits; $i++) { $c += $digits[$i] || 0; my $d = $c % 3; ### at: "i=$i c=$c is d=$d" if ($d == 1) { $c += 4; $digits[$i] = _divrem_mutate($c,3); $c += $digits[++$i] || 0; $digits[$i] = _divrem_mutate($c,3); } elsif ($d == 2) { $c -= 4; $digits[$i] = _divrem_mutate($c,3); $c += $digits[++$i] || 0; $digits[$i] = _divrem_mutate($c,3); } else { $digits[$i] = _divrem_mutate($c,3); } } ### final: "c=$c digits ".join(',',@digits) if ($c < 0) { return undef; } $digits[scalar(@digits)] = $c; return digit_join_lowtohigh(\@digits,3); } } { my $path = Math::PlanePath::PeanoCurve->new; foreach my $x (0 .. 20) { print $path->xy_to_n($x,0),","; } print "\n"; foreach my $y (0 .. 20) { print $path->xy_to_n(0,$y),","; } print "\n"; exit 0; } { # Mephisto Waltz Picture require Image::Base::GD; my $size = 3**6; my $scale = 1; my $width = $size*$scale; my $height = $size*$scale; my $transform = sub { my ($x,$y) = @_; $x *= $scale; $y *= $scale; return ($x,$height-1-$y); }; my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); my $path = Math::PlanePath::PeanoCurve->new; my $image = Image::Base::GD->new (-height => $height, -width => $width); $image->rectangle(0,0, $width-1,$height-1, 'black'); require Math::NumSeq::MephistoWaltz; my $seq = Math::NumSeq::MephistoWaltz->new; foreach my $n (0 .. $size**2) { my ($x,$y) = $path->n_to_xy($n); my $value = $seq->ith($n); if ($value) { ($x,$y) = $transform->($x,$y); $image->rectangle($x,$y, $x+$scale-1, $y-($scale-1), 'white', 1); } } my $filename = '/tmp/mephisto-waltz.png'; $image->save($filename); require IPC::Run; IPC::Run::start(['xzgv',$filename],'&'); exit 0; } { # Cf Mandelbrot segment substitution # 2---3 # | | # / / # *---1 5-4 8---* # / / # | | # 6---7 # turn(n) = my(m=n/9^valuation(n,9)); [1, -1,-1,-1, 1, 1, 1, -1][m%9]; # turn(n) = my(m=n/3^valuation(n,3)); (-1)^((m%3)+(n%3!=0)); # vector(27,n,turn(n)) # not A216430 only middle match # vector(100,n,turn(3*n)) # vector(20,n,turn(n)) # vector(20,n,(turn(n)+1)/2) # vector(20,n,(1-turn(n))/2) exit 0; } { # PeanoDiagonals Turns Morphism # turn(3*n)) == -turn(n) # turn(3*n+1)) == -(-1)^n # turn(3*n+2)) == (-1)^n # X = end of even # Y = end of odd my %expand = (X => 'X -FY +FX +FY +FX -FY -FX -FY +FX', Y => 'Y +FX -FY -FX -FY +FX +FY +FX -FY'); %expand = (X => 'Y +FX -FY', # applied an even number of times Y => 'X -FY +FX'); %expand = (X => 'X -FY +FX ++', Y => 'Y +FX -FY ++'); my $str = 'FX'; foreach (1 .. 8) { $str =~ s{[XY]}{$expand{$&}}eg; } print substr($str,0,60),"\n"; $str =~ s/[XY ]//g; $str =~ s/(\+\+)+$//; $str =~ s{[-+]+}{pm_str_net($&)}eg; $str =~ s/[^-+]//g; print substr($str,0,27),"\n"; my $path = Math::PlanePath::PeanoDiagonals->new; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my $max = 0; my $by_path = ''; for (1 .. length($str)) { my ($i,$value) = $seq->next; my $c = $value > 0 ? '+' : '-'; if ($i < 27) { print $c; } $by_path .= $c; } print "\n"; $str eq $by_path or die; exit 0; sub pm_str_net { my ($str) = @_; my $net = 0; foreach my $c (split //, $str) { if ($c eq '+') { $net++; } elsif ($c eq '-') { $net--; } else { die $c; } } $net %= 4; if ($net == 1) { return '+'; } if ($net == 3) { return '-'; } die "net $net"; } } { # turn LSR # plain: # signed 0,1,1,0,-1,-1,0,0,0,0,-1,-1,0,1,1,0,0,0,0,1,1,0,-1,-1,0,1,1,0,-1,-1, # signed 0,-1,-1,0,1,1,0,0,0,0,1,1,0,-1,-1,0,0,0,0,-1,-1,0,1,1,0,-1,-1,0,1,1, # ones 0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0, # zeros 1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1,1,1, # diagturn(n) = my(v=digits(n,3)); sum(i=1,#v,v[i]!=1) my $radix = 4; my $path; $path = Math::PlanePath::PeanoDiagonals->new; $path = Math::PlanePath::PeanoCurve->new (radix => $radix); require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my $max = 0; for (1 .. 80) { my ($i,$value) = $seq->next; my $got = n_to_turn_LSR($i, $radix); $got = _UNDOCUMENTED__n_to_turn_LSR($path,$i); my $i3 = cnv($i,10,$radix); my $diff = $got==$value ? '' : ' ***'; printf "%2d %3s %d %d%s\n", $i,$i3, $value, $got, $diff; } print "signed "; $seq->rewind; for (1 .. 30) { my ($i,$value) = $seq->next; print $value,","; } print "\n"; print "signed "; $seq->rewind; for (1 .. 30) { my ($i,$value) = $seq->next; print -$value,","; } print "\n"; print "ones "; $seq->rewind; for (1 .. 30) { my ($i,$value) = $seq->next; print $value==1?1:0,","; } print "\n"; print "zeros "; $seq->rewind; for (1 .. 30) { my ($i,$value) = $seq->next; print $value==1?0:1,","; } print "\n"; exit 0; } { # Diagonals Pattern my $path = Math::PlanePath::PeanoDiagonals->new; $path->xy_to_n(0,0); $path->xy_to_n(2,0); # exit; my @slope; foreach my $n (0 .. 900) { my ($x,$y) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); my $dir = dxdy_to_dir8($x2-$x, $y2-$y); my $tx = $x+$x2; my $ty = $y+$y2; $slope[$tx]->[$ty] = $dir; if ($n < 10) { print "n=$n $x,$y to $x2,$y2 for $tx,$ty dir=$dir\n"; } } print "1,1 is $slope[1]->[1]\n"; foreach my $y (reverse 0 .. 27) { printf "y=%2d ", $y; # my $y = 2*$y+1; foreach my $x (0 .. 27) { # my $x = 2*$x+1; my $dir = $slope[$x]->[$y] // ''; printf '%3s', $dir; } print "\n"; } print " "; foreach my $x (0 .. 27) { printf '%3s', $x; } print "\n"; exit 0; # return 0..7 sub dxdy_to_dir8 { my ($dx, $dy) = @_; return atan2($dy,$dx) / atan2(1,1); if ($dx == 1) { if ($dy == 1) { return 1; } if ($dy == 0) { return 0; } if ($dy == -1) { return 7; } } if ($dx == 0) { if ($dy == 1) { return 2; } if ($dy == -1) { return 6; } } if ($dx == -1) { if ($dy == 1) { return 3; } if ($dy == 0) { return 4; } if ($dy == -1) { return 5; } } die 'oops'; } } # 8 60--61--62--63--64--65 78--79--80--... # | | | # 7 59--58--57 68--67--66 77--76--75 # | | | # 6 -1 54--55--56 69--70--71--72--73--74 # | # 5 -1 53--52--51 38--37--36--35--34--33 # | | | # 4 48--49--50 39--40--41 30--31--32 # | | | # 3 47--46--45--44--43--42 29--28--27 +1 # | # 2 6---7---8---9--10--11 24--25--26 +1 # | | | # 1 5---4---3 14--13--12 23--22--21 # | | | # Y=0 0---1---2 15--16--17--18--19--20 # 0 0 # +1 is low 0s to none # 1000 1001 # # 0 1 2 0 1 2 0 1 2 0 1 2 0 # \-/ \-/ \-/ \-/ # # GP-DEFINE A163536(n) = { # GP-DEFINE if(n%3==2,n++); # GP-DEFINE if(valuation(n,3)%2, 2-(n%2), 0); # GP-DEFINE } # my(v=OEIS_samples("A163536")); vector(#v,n, A163536(n)) == v # OEIS_samples("A163536") # vector(20,n, ceil(2*n/3)) # vector(20,n, valuation(n,3)%2) # GP-DEFINE A163536_b(n) = { # GP-DEFINE if(n%3==1,return(0)); # GP-DEFINE my(m=ceil(2*(n+1)/3)); # GP-DEFINE if(valuation(m\2,3)%2,0,2-(m\2)%2); # GP-DEFINE } # my(v=OEIS_samples("A163536")); vector(#v,n, A163536_b(n)) == v # vector(20,n, my(n=3*n-1, a=A163536(n)); if(a,-(-1)^a,0)) # vector(20,n, if(valuation(n,3)%2,0,-(-1)^n)) # for(n=1,27,my(n=n);print(n" "ceil(2*n/3)" "A163536(n)" "A163536_b(n))) # vector(20,n, A163536(n)) # vector(20,n, A163536(9*n)) # vector(20,n, A163536(81*n)) # # GP-DEFINE A163536_c(n) = { # GP-DEFINE if(n%3==1,return(0), # GP-DEFINE n%3==2,n++); # GP-DEFINE if(valuation(n,3)%2, 2-(n%2), 0); # GP-DEFINE } # my(v=OEIS_samples("A163536")); vector(#v,n, A163536_c(n)) == v # vector(20,n, A163536(n)) # # 5 4 2 10 # 8 6 0 10 # 11 8 2 10 # 14 10 1 10 # 17 12 0 10 # 20 14 1 10 # 23 16 2 10 # 26 18 1 10 # 29 20 2 10 # 32 22 1 10 # 35 24 0 10 # 38 26 1 10 # 41 28 2 10 # 44 30 0 10 # 47 32 2 10 # 50 34 1 10 # 53 36 2 10 # 56 38 1 10 # 59 40 2 10 # 62 42 0 10 # 65 44 2 10 # 68 46 1 10 # 71 48 0 10 # 74 50 1 10 # 77 52 2 10 # 80 54 0 10 # 83 56 2 10 # In odd bases, the parity of sum(@digits) is the parity of $n itself, # so no need for a full digit split (only examine the low end for low 0s). # sub _UNDOCUMENTED__n_to_turn_LSR { my ($self, $n) = @_; if ($n <= 0) { return undef; } my $radix = $self->{'radix'}; { my $r = $n % $radix; if ($r == $radix-1) { $n++; # ...222 and ...000 are same turns } elsif ($r != 0) { return 0; # straight ahead across rows, turn only at ends } } my $z = 1; until ($n % $radix) { # low 0s $z = !$z; $n /= $radix; } if ($z) { return 0; } # even number of low zeros return (($radix & 1 ? sum(digit_split_lowtohigh($n,$radix)) : $n) & 1 ? 1 : -1); } sub n_to_turn_LSR { my ($n,$radix) = @_; # { # if ($n % $radix != 0 # && $n % $radix != $radix-1) { # return 0; # } # # vector(20,n, ceil(2*n/3)) # # vector(20,n, floor((2*n+2)/3)) # $n = int((2*$n+2)/$radix); # } { if ($n % $radix == $radix-1) { $n++; } elsif ($n % $radix != 0) { return 0; } my @digits = digit_split_lowtohigh($n,$radix); my $turn = 1; while (@digits) { # low to high last if $digits[0]; $turn = -$turn; shift @digits; } if ($turn == 1) { return 0; } # even number of low zeros return (sum(@digits) & 1 ? -$turn : $turn); } { if ($n % $radix == $radix-1) { $n++; } elsif ($n % $radix != 0) { return 0; } my $low = 0; my $z = $n; while ($z % $radix == 0) { $low = 1-$low; $z /= $radix; } if ($low == 0) { return 0; # even num low 0s } return ($z % 2 ? 1 : -1); } { if ($n % $radix == $radix-1) { $n++; } while ($n % $radix**2 == 0) { $n /= $radix**2; } if ($n % $radix != 0) { return 0; } return diagonal_n_to_turn_LSR($n,$radix); } { my $turn = 1; my $turn2 = 1; my $m = $n; while ($m % $radix == $radix-1) { # odd low 2s is -1 $turn2 = -$turn2; $m = int($m/$radix); } my $z = $n; while ($z % $radix == 0) { # odd low 0s is -1 $turn = -$turn; $z /= $radix; } my $o = $n; if ($turn==$turn2) { return 0; } # return ($n % 2 ? 1 : -1); # my $opos = 0; # until ($o % 3 == 1) { # odd low 0s is -1 # $opos = 1-$opos; # $o = int($o/3); # } # if ($o==0) { return 0; } if ($n % 2) { # flip one or other $turn = -$turn; } else { $turn2 = -$turn2; } return ($turn+$turn2)/2; } { return (diagonal_n_to_turn_LSR($n,$radix) + diagonal_n_to_turn_LSR($n+1,$radix))/2; } } { # X=Y diagonal my $path = Math::PlanePath::PeanoCurve->new; foreach my $i (0 .. 20) { my $n = $path->xy_to_n($i,$i); printf "i=%3d %4s n=%3s %6s\n", $i,cnv($i,10,3), $n,cnv($n,10,3); } exit 0; } { # dx,dy on even radix require Math::BigInt; foreach my $radix (4, 2, 6, 8) { print "radix=$radix\n"; my $path = Math::PlanePath::PeanoCurve->new (radix => $radix); my $limit = 4000000000; { my %seen_dx; for my $len (0 .. 8) { for my $high (1 .. $radix-1) { my $n = Math::BigInt->new($high); foreach (1 .. $len) { $n *= $radix; $n += $radix-1; } my ($dx,$dy) = $path->n_to_dxdy($n); $dx = abs($dx); my ($x,$y) = $path->n_to_xy($n); my $xr = cnv($x,10,$radix); my $dr = cnv($dx,10,$radix); my $nr = cnv($n,10,$radix); print "N=$n [$nr] dx=$dx [$dr] x=[$xr]\n"; unless ($seen_dx{$dx}++) { } } } } { my %seen_dy; for my $len (0 .. 8) { for my $high (1 .. $radix-1) { my $n = Math::BigInt->new($high); foreach (1 .. $len) { $n *= $radix; $n += $radix-1; } my ($dx,$dy) = $path->n_to_dxdy($n); $dy = abs($dy); unless ($seen_dy{$dy}++) { my $dr = cnv($dy,10,$radix); my $nr = cnv($n,10,$radix); print "N=$n [$nr] dy=$dy [$dr]\n"; } } } } print "\n"; } exit 0; } { # abs(dY) = count low 2-digits, mod 2 # abs(dX) = opposite, 1-abs(dY) # x x # vertical when odd number of low 2s ..0222 # N+1 carry propagates to change ..1000 # y y # high y+1 complements x from 0->2 so X unchanged # Y becomes Y+1 02 -> 10, or if complement then Y-1 20 -> 12 # my $radix = 3; require Math::PlanePath::PeanoCurve; require Math::NumSeq::PlanePathDelta; require Math::NumSeq::DigitCountLow; require Math::BigInt; my $path = Math::PlanePath::PeanoCurve->new (radix => $radix); my $seq = Math::NumSeq::PlanePathDelta->new (planepath_object => $path, delta_type => 'AbsdX'); my $cnt = Math::NumSeq::DigitCountLow->new (radix => 3, digit => 2); foreach my $n (0 .. 40) { my ($dx,$dy) = $path->n_to_dxdy($n); my $absdx = abs($dx); my $absdy = abs($dy); my $c = $cnt->ith($n); my $by_c = $c & 1; my $diff = $absdy == $by_c ? '' : ' ***'; # my $n = $n+1; my $nr = cnv($n,10,$radix); printf "%3d %7s %2d,%2d low=%d%s\n", $n, $nr, abs($dx),abs($dy), $c, $diff; # print "$n,"; if ($absdx != 0) { } } exit 0; } { # Dir4 maximum my $radix = 6; require Math::PlanePath::PeanoCurve; require Math::NumSeq::PlanePathDelta; require Math::BigInt; my $path = Math::PlanePath::PeanoCurve->new (radix => $radix); my $seq = Math::NumSeq::PlanePathDelta->new (planepath_object => $path, delta_type => 'Dir4'); my $dir4_max = 0; foreach my $n (0 .. 600000) { # my $n = Math::BigInt->new(2)**$level - 1; my $dir4 = $seq->ith($n); if ($dir4 > $dir4_max) { $dir4_max = $dir4; my ($dx,$dy) = $path->n_to_dxdy($n); my $nr = cnv($n,10,$radix); printf "%7s %2b,\n %2b %8.6f\n", $nr, abs($dx),abs($dy), $dir4; } } exit 0; } { # axis increasing my $radix = 4; my $rsquared = $radix * $radix; my $re = '.' x $radix; require Math::NumSeq::PlanePathN; foreach my $line_type ('Y_axis', 'X_axis', 'Diagonal') { OUTER: foreach my $serpentine_num (0 .. 2**$rsquared-1) { my $serpentine_type = sprintf "%0*b", $rsquared, $serpentine_num; # $serpentine_type = reverse $serpentine_type; $serpentine_type =~ s/($re)/$1_/go; ### $serpentine_type my $seq = Math::NumSeq::PlanePathN->new ( planepath => "WunderlichSerpentine,radix=$radix,serpentine_type=$serpentine_type", line_type => $line_type, ); ### $seq # my $path = Math::NumSeq::PlanePathN->new # ( # e,radix=$radix,serpentine_type=$serpentine_type", # line_type => $line_type, # ); my $prev = -1; for (1 .. 1000) { my ($i, $value) = $seq->next; if ($value <= $prev) { # print "$line_type $serpentine_type decrease at i=$i value=$value cf prev=$prev\n"; # my $path = $seq->{'planepath_object'}; # my ($prev_x,$prev_y) = $path->n_to_xy($prev); # my ($x,$y) = $path->n_to_xy($value); # # print " N=$prev $prev_x,$prev_y N=$value $x,$y\n"; next OUTER; } $prev = $value; } print "$line_type $serpentine_type all increasing\n"; } } exit 0; } { # max Dir4 my $radix = 4; print 4-atan2(2,1)/atan2(1,1)/2,"\n"; require Math::NumSeq::PlanePathDelta; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => "PeanoCurve,radix=$radix", delta_type => 'Dir4'); my $dx_seq = Math::NumSeq::PlanePathDelta->new (planepath => "PeanoCurve,radix=$radix", delta_type => 'dX'); my $dy_seq = Math::NumSeq::PlanePathDelta->new (planepath => "PeanoCurve,radix=$radix", delta_type => 'dY'); my $max = 0; for (1 .. 10000000) { my ($i, $value) = $seq->next; # foreach my $k (1 .. 1000000) { # my $i = $radix ** (4*$k+3) - 1; # my $value = $seq->ith($i); if ($value > $max # || $i == 0b100011111 ) { my $dx = $dx_seq->ith($i); my $dy = $dy_seq->ith($i); my $ri = cnv($i,10,$radix); my $rdx = cnv($dx,10,$radix); my $rdy = cnv($dy,10,$radix); my $f = $dy ? $dx/$dy : -1; printf "%d %s %.5f %s %s %.3f\n", $i, $ri, $value, $rdx,$rdy, $f; $max = $value; } } exit 0; } Math-PlanePath-129/devel/cfrac-digits.pl0000644000175000017500000001400412155466372015713 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use POSIX 'floor'; use List::Util 'min', 'max'; use Math::PlanePath::CfracDigits; use Math::PlanePath::Base::Digits 'round_down_pow'; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::KochCurve; *_digit_join_hightolow = \&Math::PlanePath::KochCurve::_digit_join_hightolow; # 121313322 { require Math::PlanePath::CfracDigits; my $path = Math::PlanePath::CfracDigits->new; foreach my $n (0 .. 120) { my ($x,$y) = $path->n_to_xy($n); print "$x,"; } print "\n"; print "\n"; foreach my $n (0 .. 120) { my ($x,$y) = $path->n_to_xy($n); print "$y,"; } print "\n"; print "\n"; foreach my $n (0 .. 120) { my ($x,$y) = $path->n_to_xy($n); print "$x/$y, "; } print "\n"; print "\n"; exit 0; } { require Math::PlanePath::CfracDigits; require Number::Fraction; my $path = Math::PlanePath::CfracDigits->new (radix => 1); my $rat = Math::PlanePath::RationalsTree->new (tree_type => 'HCS'); my $nf = Number::Fraction->new(1,7); $nf = 1 / (4 + 1 / (2 + Number::Fraction->new(1,7))); print "$nf\n"; my $x = $nf->{num}; my $y = $nf->{den}; my $n = $path->xy_to_n($x,$y); printf "%5d %17b\n", $n, $n; $n = $rat->xy_to_n($y-$x,$x); printf "%5d %17b\n", $n, $n; exit 0; } { # +1 at low end to turn 1111 into 10000 require Math::PlanePath::CfracDigits; my $rat = Math::PlanePath::RationalsTree->new (tree_type => 'HCS'); my $cf = Math::PlanePath::CfracDigits->new (radix => 1); for (my $n = $rat->n_start; $n < 200; $n++) { my ($cx,$cy) = $cf->n_to_xy($n); # my ($rx,$ry) = $rat->n_to_xy($n); my $rn = $rat->xy_to_n($cy,$cx); printf "%d,%d %b %b\n", $cx,$cy, $n, $rn-1; } exit 0; } { # Fibonacci F[k]/F[k+1] require Math::NumSeq::Fibonacci; my $seq = Math::NumSeq::Fibonacci->new; my $radix = 3; my $path = Math::PlanePath::CfracDigits->new (radix => $radix); for (my $i = 1; $i < 20; $i++) { my $x = $seq->ith($i); my $y = $seq->ith($i+1); my $log = Math::PlanePath::CfracDigits::_log_phi_estimate($y); my $n = $path->xy_to_n($x,$y); # { # my @digits = ($radix+1) x ($i-2); # my $carry = 0; # foreach my $digit (@digits) { # low to high # if ($carry = (($digit += $carry) >= $radix)) { # modify array contents # $digit -= $radix; # } # } # if ($carry) { # push @digits, 1; # } # print join(',',@digits),"\n"; # } my @digits = ($radix+1) x ($i-2); my $d = Math::PlanePath::CfracDigits::_digit_join_1toR_destructive(\@digits,$radix+1,0); my $pow = ($radix+1)**$i; my ($nlo,$nhi) = $path->rect_to_n_range(0,0, $x,$y); print "$n $log $nhi $d $pow\n"; } exit 0; } { # range vs GcdRationals my $radix = 2; require Math::PlanePath::CfracDigits; require Math::PlanePath::GcdRationals; my $cf = Math::PlanePath::CfracDigits->new (radix => $radix); my $gc = Math::PlanePath::GcdRationals->new; foreach my $y (2 .. 1000) { my ($cf_nlo,$cf_nhi) = $cf->rect_to_n_range(0,0, 1,$y); my ($gc_nlo,$gc_nhi) = $gc->rect_to_n_range(0,0, $y,$y); my $flag = ''; if ($cf_nhi > $gc_nhi) { $flag = "*****"; } print "$y $cf_nhi $gc_nhi$flag\n"; } exit 0; } { # maximum N require Math::PlanePath::CfracDigits; my $radix = 6; my $path = Math::PlanePath::CfracDigits->new (radix => $radix); foreach my $y (2 .. 1000) { my $nmax = -1; my $xmax; foreach my $x (1 .. $y-1) { my $n = $path->xy_to_n($x,$y) // next; my $len = $n; # length_1toR($n); if ($len > $nmax) { $nmax = $len; $xmax = $x; # print " $xmax $nmax ",groups_string($n),"\n"; } } my ($nlo,$nhi) = $path->rect_to_n_range(0,0,1,$y); my $groups = groups_string($nmax); my $ysquared = ($radix+1) ** (_fib_log($y) - 1.5); # my $ysquared = ($radix+1) ** (log2($y)*2); # my $ysquared = int($y ** (5/2)); my $yfactor = sprintf '%.2f', $ysquared / ($nmax||1); my $flag = ''; if ($ysquared < $nmax) { $flag = "*****"; } print "$y x=$xmax n=$nmax $ysquared$flag $yfactor $groups\n"; my $log = Math::PlanePath::CfracDigits::_log_phi_estimate($y); $flag = ''; if ($nhi < $nmax) { $flag = "*****"; } print " nhi=$nhi$flag log=$log\n"; } exit 0; sub groups_string { my ($n) = @_; my @groups = Math::PlanePath::CfracDigits::_n_to_quotients($n,$radix); return join(',',reverse @groups); } sub length_1toR { my ($n) = @_; my @digits = Math::PlanePath::CfracDigits::_digit_split_1toR_lowtohigh($n,$radix); return scalar(@digits); } sub log2 { my ($x) = @_; return int(log($x)/log(2)); } sub _fib_log { my ($x) = @_; ### _fib_log(): $x my $f0 = ($x * 0); my $f1 = $f0 + 1; my $count = 0; while ($x > $f0) { $count++; ($f0,$f1) = ($f1,$f0+$f1); } return $count; } } { # minimum N in each row is at X=1 require Math::PlanePath::CfracDigits; my $path = Math::PlanePath::CfracDigits->new; foreach my $y (2 .. 1000) { my $nmin = 1e308; my $xmin; foreach my $x (1 .. $y-1) { my $n = $path->xy_to_n($x,$y) // next; if ($n < $nmin) { $nmin = $n; $xmin = $x; } } print "$y $xmin $nmin\n"; } exit 0; } Math-PlanePath-129/devel/pythagorean.pl0000644000175000017500000007205513675555252015713 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use List::Util 'min', 'max', 'sum'; use Math::Libm 'hypot'; use Math::PlanePath::PythagoreanTree; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_join_lowtohigh', 'digit_split_lowtohigh'; use Math::PlanePath::GcdRationals; *gcd = \&Math::PlanePath::GcdRationals::_gcd; $|=1; # uncomment this to run the ### lines # use Smart::Comments; { # A103605 equal perimeters area order # ~/OEIS/b103605.txt # ../../seq-pythagorean-perimeter/pythtripfind.txt # print " triple perimeter area gcd primitive prim_area\n"; print " triple perimeter area\n"; require Math::NumSeq::OEIS::File; my $seq = Math::NumSeq::OEIS::File->new(anum => 'A103605'); # my $seq = Math::NumSeq::OEIS::File->new(anum => 'A999999'); my $prev_perimeter = -1; my (@as,@bs,@cs,@perimeters,@areas,@elems); for (;;) { my ($i,$a) = $seq->next or last; my (undef,$b) = $seq->next or die "oops incomplete"; my (undef,$c) = $seq->next or die "oops incomplete"; $a**2 + $b**2 == $c**2 or die; my $perimeter = $a+$b+$c; my $area = $a*$b/2; push @elems, [$a,$b,$c,$perimeter,2*$area]; if (@perimeters >= 2 && $perimeter != $perimeters[-1]) { my $prev_dir; foreach my $i (0 .. $#as) { my $dir = ($i > 0 ? ($areas[$i] > $areas[$i-1] ? " inc" : " dec") : ''); my $g = gcd($as[$i], gcd($bs[$i],$cs[$i])); my $aprim = $as[$i]/$g; my $bprim = $bs[$i]/$g; my $cprim = $cs[$i]/$g; my $prim_area = $aprim*$bprim/2; printf "%14s %7s %8s%s%s\n", "$as[$i],$bs[$i],$cs[$i]", $perimeters[$i], $areas[$i], $dir, ($i >= 2 && $dir ne $prev_dir ? ' ****' : ''); # printf "%14s %7s %8s%-4s %4s %3d %14s %7s\n", # "$as[$i],$bs[$i],$cs[$i]", # $perimeters[$i], # $areas[$i], # $dir, # ($i >= 2 && $dir ne $prev_dir ? '****' : ''), # $g, # "$aprim,$bprim,$cprim", # $prim_area; $prev_dir = $dir; } print "\n"; } if (@perimeters && $perimeter != $perimeters[-1]) { @as = (); @bs = (); @cs = (); @perimeters = (); @areas = (); } push @as, $a; push @bs, $b; push @cs, $c; push @perimeters, $perimeter; push @areas, $area; } { @elems = sort {$a->[3] <=> $b->[3] || $a->[4] <=> $b->[4] } @elems; open my $fh, '>', '/tmp/b' or die; my $i = 1; foreach my $elem (@elems) { print $fh $i++," ",join(' ',@$elem)," \n"; } } exit 0; } { # A103605 perimeters then area # 5412 + 5635 + 7813 == 18860 # 2050 + 8280 + 8530 == 18860 # 5412 # 5635 # 7813 # A^2 + B^2 = C^2 # X^2 + Y^2 = Z^2 # A+B+C = X+Y+Z # A*B < X*Y equiv A < X ? # A+B+sqrt(A^2+B^2) = X+Y+sqrt(X^2+Y^2) my @ABC; my $max_perimeter = 5412 + 5635 + 7813; # $max_perimeter = 200; print "max_perimeter $max_perimeter\n"; for (my $A = 1; $A+$A+$A <= $max_perimeter; $A++) { ### $A print "$A ",scalar(@ABC),"\r"; for (my $B = $A; $A+$B+$B <= $max_perimeter; $B++) { ### $B my $C2 = $A*$A + $B*$B; my $C = int(sqrt($C2)); next unless $C*$C == $C2; my $perimeter = $A + $B + $C; last if $perimeter > $max_perimeter; push @ABC, [$A,$B,$C]; } } @ABC = sort { ABC_to_perimeter($a) <=> ABC_to_perimeter($b) || ABC_to_2area($a) <=> ABC_to_2area($b) } @ABC; { foreach my $i (0 .. $#ABC) { foreach my $j ($i+1 .. $#ABC) { last if ABC_to_perimeter($ABC[$i]) != ABC_to_perimeter($ABC[$j]); ABC_to_2area($ABC[$i]) != ABC_to_2area($ABC[$j]) or die; (ABC_to_2area($ABC[$i]) <=> ABC_to_2area($ABC[$j])) == ($ABC[$i]->[0] <=> $ABC[$j]->[0]) or die join(',',@{$ABC[$i]})," ",join(',',@{$ABC[$j]}); } } } { open my $fh, '>', '/tmp/p' or die; my $i = 1; foreach my $ABC (@ABC) { print $fh $i++," ",$ABC->[0]," \n"; print $fh $i++," ",$ABC->[1]," \n"; print $fh $i++," ",$ABC->[2]," \n"; } } { open my $fh, '>', '/tmp/q' or die; my $i = 1; foreach my $ABC (@ABC) { my $perimeter = ABC_to_perimeter($ABC); my $twoarea = ABC_to_2area($ABC); print $fh $i++," $ABC->[0] $ABC->[1] $ABC->[2] $perimeter $twoarea \n"; } } system("ls -l ~/OEIS/b103605.txt"); system("ls -l ../../seq-pythagorean-perimeter/pythtripfind.txt"); exit 0; sub ABC_to_perimeter { my ($aref) = @_; return sum(@$aref); } sub ABC_to_2area { my ($aref) = @_; return $aref->[0] * $aref->[1]; } } { # p,q LtoH vs HtoL { my $path = Math::PlanePath::PythagoreanTree->new ( tree_type => 'UAD', # tree_type => 'FB', coordinates => 'PQ', digit_order => 'HtoL', ); foreach my $n (12 .. 20) { my ($x,$y) = $path->n_to_xy($n); print "$x $y\n"; } } { my $path = Math::PlanePath::PythagoreanTree->new ( tree_type => 'UAD', # tree_type => 'FB', coordinates => 'PQ', digit_order => 'LtoH', ); foreach my $n (12 .. 20) { my ($x,$y) = $path->n_to_xy($n); print "$x $y\n"; } } exit 0; } { # powers foreach my $k (0 .. 41) { print 3**$k,"\n"; } exit 0; } { # repeated "U" or "M1" on initial P=2,Q=1 require Math::BaseCnv; my $path = Math::PlanePath::PythagoreanTree->new ( # tree_type => 'UAD', tree_type => 'FB', coordinates => 'PQ', ); foreach my $depth (0 .. 5) { my $n = $path->tree_depth_to_n($depth); my ($x,$y) = $path->n_to_xy($n); print "depth=$depth N=$n P=$x / Q=$y\n"; } exit 0; } { # X,Y list # PQ UAD # N=1 2 / 1 # # N=2 3 / 2 5,12 # N=3 5 / 2 21,20 # N=4 4 / 1 15,8 # # N=5 4 / 3 # N=6 8 / 3 # N=7 7 / 2 # N=8 8 / 5 # N=9 12 / 5 # N=10 9 / 2 # N=11 7 / 4 # N=12 9 / 4 # N=13 6 / 1 # PQ FB # N=1 2,1 # # N=2 3,2 # N=3 4,1 # N=4 4,3 # # N=5 5,4 # N=6 6,1 # N=7 6,5 # N=8 5,2 # N=9 8,3 # N=10 8,5 # N=11 7,6 # N=12 8,1 # N=13 8,7 require Math::PlanePath::PythagoreanTree; my $path = Math::PlanePath::PythagoreanTree->new ( # tree_type => 'UMT', tree_type => 'UAD', # tree_type => 'FB', coordinates => 'AB', # coordinates => 'PQ', # P>Q one odd other even ); my $n = $path->n_start; foreach my $level (0 .. 5) { foreach (1 .. 3**$level) { my ($x,$y) = $path->n_to_xy($n); # $x -= $y; my $flag = ''; if ($x <= $y) { $flag = ' ***'; } print "N=$n $x,$y$flag\n"; $n++; } print "\n"; } exit 0; } { # TMU parent/child foreach my $n (1 .. 40) { if (n_is_row_start($n)) { print "\n"; } my $n_str = n_to_pythagstr($n); my ($p,$q) = tmu_n_to_pq($n); my @pq_children = tmu_pq_children($p,$q); my ($p1,$q1,$p2,$q2,$p3,$q3) = @pq_children; print "$n = $n_str $p,$q children $p1,$q1 $p2,$q2 $p3,$q3\n"; while (@pq_children) { my $child_p = shift @pq_children; my $child_q = shift @pq_children; my ($parent_p,$parent_q) = tmu_pq_parent($child_p,$child_q); if ($parent_p != $p || $parent_q != $q) { print "oops\n"; } } } exit 0; sub tmu_pq_children { my ($p,$q) = @_; return ($p+3*$q, 2*$q, # T 2*$p, $p-$q, # M2 (2p, p-q) 2*$p-$q, $p); # "U" = (2p-q, p) } sub tmu_pq_parent { my ($p,$q) = @_; if ($p > 2*$q) { if ($p % 2) { # T 1 3 p -> p+3q p > 2q, p odd, q even # 0 2 q -> 2q det=2 # inverse 1 -3/2 # 0 1/2 $q /= 2; $p -= 3*$q; } else { # M2 2 0 p -> 2p p > 2q, p even, q odd # 1 -1 q -> p-q det=-2 # inverse -1 0 / -2 = 1/2 0 # -1 2 1/2 -1 $p /= 2; $q = $p - $q; } } else { # U 2 -1 p -> 2p-q 2q > p > q small p # 1 0 q -> p det=1 # inverse 0 1 = 0 -1 # -1 2 1 -2 ($p,$q) = ($q, 2*$q-$p); } return ($p,$q); } } { # 1^2 = 1 3^2 = 9 = 1 mod 4 # A^2 + B^2 = C^2 # 1 0 1 # 0 1 1 # A = 1 mod 4, B = 0 mod 4 # even 3mod4, any 1mod4 exit 0; } { my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'UMT', coordinates => 'PQ'); $path->xy_to_n(4,5); exit 0; } { # UAD to TMU my $uad = Math::PlanePath::PythagoreanTree->new (tree_type => 'UAD', coordinates => 'PQ'); foreach my $n (1 .. 40) { if (n_is_row_start($n)) { print "\n"; } my ($p,$q) = $uad->n_to_xy($n); my $umt_n = umt_pq_to_n($p,$q); my $umt_n_str = n_to_pythagstr($umt_n); my $n_str = n_to_pythagstr($n); print "$n = $n_str $p,$q UMT=$n $umt_n_str\n"; } exit 0; sub umt_pq_to_n { my ($p,$q) = @_; my @ndigits; while ($p > 2) { if ($p > 2*$q) { if ($p % 2) { $q /= 2; # T $p -= 3*$q; push @ndigits, 1; } else { $p /= 2; # M2 $q = $p - $q; push @ndigits, 2; } } else { ($p,$q) = ($q, 2*$q-$p); # U push @ndigits, 0; } } my $zero = $p*0*$q; return ((3+$zero)**scalar(@ndigits) + 1)/2 # tree_depth_to_n() + digit_join_lowtohigh(\@ndigits,3,$zero); # digits within this depth } } { # U = 2,-1,1,0 # A = 2,1, 1,0 # D = 1,2, 0,1 # M1 = 1,1, 0,2 # M2 = 2,0, 1,-1 # M3 = 2,0, 1,1 # p+2q = unchanged # p+q = odd # 2p or 2q = even # 2a+b>2c+d # ap+b>cp+d # # ap+b(p-1) > cp+d(p-1) # (c-1)p+b(p-1) > cp+d(p-1) # cp-p+b(p-1) > cp+d(p-1) # -p+b(p-1) > d(p-1) # b(p-1) > d(p-1)+p # b > d+p/(p-1) # b > d+1 # D A U # 1,2,0,1 2,-1,1,0 2,1,1,0 # # U 2 -1 p -> 2p-q 2q > p > q small p # 1 0 q -> p det=1 # # A 2 1 p -> 2p+q 3q > p > 2q mid p # 1 0 q -> p det=-1 # # D 1 2 p -> p+2q p > 3q big p # 0 1 q -> q det=1 # M1 M2 M3 # 1,1,0,2 2,0,1,-1 2,0,1,1 # # M1 1 1 p -> p+q p > 2q, p odd, q even # 0 2 q -> 2q det=2 # # M2 2 0 p -> 2p p > 2q, p even, q odd # 1 -1 q -> p-q det=-2 # # M3 2 0 p -> 2p 2q > p, small p, p even, q odd # 1 1 q -> p+q det=2 # U M2 # 1,3,0,2 2,-1,1,0 2,0,1,-1 # # # T 1 3 p -> p+3q p > 2q, p odd, q even # 0 2 q -> 2q det=2 # # M2 2 0 p -> 2p p > 2q, p even, q odd # 1 -1 q -> p-q det=-2 # # U 2 1 p -> 2p-q 2q > p > q small p # 1 0 q -> p det=-1 my $uad = Math::PlanePath::PythagoreanTree->new (tree_type => 'UAD', coordinates => 'PQ'); my $fb = Math::PlanePath::PythagoreanTree->new (tree_type => 'FB', coordinates => 'PQ'); my $len = 0; foreach my $n (1 .. 40) { if (n_is_row_start($n)) { print "\n"; } my ($p,$q) = tmu_n_to_pq($n); my $uad_n = n_to_pythagstr($uad->xy_to_n($p,$q)); my $fb_n = n_to_pythagstr($fb->xy_to_n($p,$q)); my $n_str = n_to_pythagstr($n); print "$n = $n_str $p,$q UAD N=$uad_n FB N=$fb_n\n"; } exit 0; sub n_is_row_start { my ($n) = @_; my ($pow, $exp) = round_down_pow (2*$n-1, 3); return ($n == ($pow+1)/2); } sub tmu_n_to_pq { my ($n) = @_; my $p = 2; my $q = 1; foreach my $digit (n_to_pythag_digits_lowtohigh($n)) { if ($digit == 0) { $p += 3*$q; # T $q *= 2; } elsif ($digit == 1) { $q = $p-$q; # (2p, p-q) M2 $p *= 2; } else { ($p,$q) = (2*$p-$q, $p); # "U" = (2p-q, p) } } return ($p,$q); } sub n_to_pythagstr { my ($n) = @_; if (! defined $n) { return '[undef]' } if ($n < 1) { return "($n)"; } my @digits = n_to_pythag_digits_lowtohigh($n); return '1.'.join('',reverse @digits); } # ($pow+1)/2 = row start # pow = 3^exp # N - rowstart + 3^exp = N - (pow+1)/2 + pow # = N - pow/2 - 1/2 + pow # = N + pow/2 - 1/2 # = N + (pow-1)/2 sub n_to_pythag_digits_lowtohigh { my ($n) = @_; my ($pow, $exp) = round_down_pow (2*$n-1, 3); my @digits = digit_split_lowtohigh($n + ($pow-1)/2,3); pop @digits; # high 1 return @digits; } } { # P,Q tables my $path = Math::PlanePath::PythagoreanTree->new(coordinates => 'PQ'); foreach my $n ($path->n_start .. $path->tree_depth_to_n_end(2)) { my ($p,$q) = $path->n_to_xy($n); print "$p,"; } print "\n"; foreach my $n ($path->n_start .. $path->tree_depth_to_n_end(2)) { my ($p,$q) = $path->n_to_xy($n); print "$q,"; } print "\n"; exit 0; } { require Devel::TimeThis; require Math::PlanePath::FractionsTree; my $path = Math::PlanePath::FractionsTree->new ( # tree_type => 'FB', # tree_type => 'UAD', # coordinates => 'BC', # coordinates => 'PQ', # P>Q one odd other even ); { my $t = Devel::TimeThis->new('xy_is_visited'); foreach my $x (0 .. 200) { foreach my $y (0 .. 200) { $path->xy_is_visited($x,$y); } } } { my $t = Devel::TimeThis->new('xy_to_n'); foreach my $x (0 .. 200) { foreach my $y (0 .. 200) { $path->xy_to_n($x,$y); } } } exit 0; } { # numbers in a grid require Math::PlanePath::PythagoreanTree; my $path = Math::PlanePath::PythagoreanTree->new ( # tree_type => 'FB', # tree_type => 'UAD', # coordinates => 'AB', coordinates => 'MC', ); my @rows; foreach my $n (1 .. 100000) { my ($orig_x,$orig_y) = $path->n_to_xy($n); my $x = $orig_x / 2; my $y = $orig_y / 4; next if $y > 25; next if $x > 80; print "$n $orig_x,$orig_y\n"; $rows[$y] ||= ' 'x80; substr($rows[$y],$x,length($n)) = $n; } for (my $y = $#rows; $y >= 0; $y--) { $rows[$y] ||= ''; $rows[$y] =~ s/ +$//; print $rows[$y],"\n"; } exit 0; } { # repeated "M1" as p,q matrix # P+(2^k-1)*Q, 2^k*Q # applied to P=2,Q=1 # 2+(2^k-1) = 2^k + 1, 2^k require Math::Matrix; my $u = Math::Matrix->new ([1,1], [0,2]); my $m = $u; foreach (1 .. 5) { print "$m\n"; $m *= $u; } exit 0; } { # repeated "U" as p,q matrix require Math::Matrix; my $u = Math::Matrix->new ([2,-1], [1,0]); my $m = $u; foreach (1 .. 5) { print "$m\n"; $m *= $u; } exit 0; } { # high bit 1 in ternary require Math::BaseCnv; for (my $n = 1; $n < 65536; $n *= 2) { my $n3 = Math::BaseCnv::cnv($n,10,3); my $n2 = Math::BaseCnv::cnv($n,10,2); printf "$n $n2 $n3\n"; } exit 0; } { # Fibonacci's method for primitive triples. # odd numbers 1,3,5,7,...,k being n terms n=(k+1)/2 with k square # sum 1+3+5+7+...+k = n^2 the gnomons around a square # a^2 = k = 2n-1 # b^2 = sum 1+3+5+...+k-2 = (n-1)^2 # c^2 = sum 1+3+5+...+k-2+k = n^2 # so a^2+b^2 = c^2 # (n-1)^2 + 2n-1 = n^2-2n+1 + 2n-1 = n^2 # # i=3 # o=2i-1=5 # k=o^2 = 5^2 = 25 # n=(k+1)/2 = (25+1)/2=13 # a=o = 5 # b = n-1 = 12 # # i=4 # o=2i-1=7 # k=o^2 = 7^2 = 49 # n=(k+1)/2 = (49+1)/2=25 # a=o = 7 # b = n-1 = 24 sub fibonacci_ab { my ($i) = @_; $i = 2*$i+1; # odd integer my $k = $i**2; # a^2 = k = odd square my $n = ($k+1)/2; return ($i, # a=sqrt(k) $n-1); # b=n-1 } require Math::PlanePath::PythagoreanTree; my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'FB'); foreach my $i (1 .. 30) { my ($a,$b) = fibonacci_ab($i); my $c = sqrt($a*$a+$b*$b); # my $n = $path->tree_depth_to_n($i-1); # my ($pa,$pb) = $path->n_to_xy($n); # print "$i $a,$b,$c $n $pa,$pb\n"; my $n = $path->xy_to_n($a,$b); my $depth = $path->tree_n_to_depth($n); print "$i $a,$b,$c $n depth=$depth\n"; } exit 0; } { # P,Q by rows require Math::BaseCnv; require Math::PlanePath::PythagoreanTree; my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); my $fb = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ', tree_type => 'FB'); my $level = 8; my $prev_depth = -1; for (my $n = $path->n_start; ; $n++) { my $depth = $path->tree_n_to_depth($n); last if $depth > 4; if ($depth != $prev_depth) { print "\n"; $prev_depth = $depth; } my ($x,$y) = $path->n_to_xy($n); printf " %2d/%-2d", $x,$y; my ($fx,$fy) = $fb->n_to_xy($n); printf " %2d/%-2d", $fx,$fy; my $fn = $path->xy_to_n($fx,$fy); print " ",n_to_treedigits_str($n); print " ",n_to_treedigits_str($fn); print "\n"; } exit 0; } { require Math::BigInt::Lite; my $x = Math::BigInt::Lite->new(3); my $y = Math::BigInt::Lite->new(4); Math::PlanePath::PythagoreanTree::_ab_to_pq($x,$y); exit 0; } { require Math::BigInt::Lite; my $x = Math::BigInt::Lite->new(3); my $low = $x & 1; ### $low exit 0; } { require Math::BigInt::Lite; my $x = Math::BigInt::Lite->new(3); my $y = Math::BigInt::Lite->new(4); ### $x ### $y my ($a, $b) = ($x,$y); ### _ab_to_pq(): "A=$a, B=$b" unless ($a >= 3 && $b >= 4 && ($a % 2) && !($b % 2)) { ### don't have A odd, B even ... return; } # This used to be $c=hypot($a,$b) and check $c==int($c), but libm hypot() # on Darwin 8.11.0 is somehow a couple of bits off being an integer, for # example hypot(57,176)==185 but a couple of bits out so $c!=int($c). # Would have thought hypot() ought to be exact on integer inputs and a # perfect square sum :-(. Check for a perfect square by multiplying back # instead. # my $c; { my $csquared = $a*$a + $b*$b; $c = int(sqrt($csquared)); ### $csquared ### $c unless ($c*$c == $csquared) { return; } } exit 0; } { require Math::BigInt::Lite; my $x = Math::BigInt::Lite->new(3); my $y = Math::BigInt::Lite->new(4); ### $x ### $y # my $csquared = $x*$x + $y*$y; # my $c = int(sqrt($csquared)); # ### $c # my $mod = $x%2; # $mod = $y%2; my $eq = ($x*$x == $y*$y); ### $eq # my $x = 3; # my $y = 4; # $x = Math::BigInt::Lite->new($x); # $y = Math::BigInt::Lite->new($y); # $mod = $x%2; # $mod = $y%2; unless ($x >= 3 && $y >= 4 && ($x % 2) && !($y % 2)) { ### don't have A odd, B even ... die; } # { # my $eq = ($c*$c == $csquared); # ### $eq # } exit 0; } { # P,Q continued fraction quotients require Math::BaseCnv; require Math::ContinuedFraction; require Math::PlanePath::PythagoreanTree; my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); my $level = 8; foreach my $n (1 .. 3**$level) { my ($x,$y) = $path->n_to_xy($n); my $cfrac = Math::ContinuedFraction->from_ratio($x,$y); my $cfrac_str = $cfrac->to_ascii; # my $nbits = Math::BaseCnv::cnv($n,10,3); my $nbits = n_to_treedigits_str($n); printf "%3d %7s %2d/%-2d %s\n", $n, $nbits, $x,$y, $cfrac_str; } exit 0; sub n_to_treedigits_str { my ($n) = @_; return "~".join('',n_to_treedigits($n)); } sub n_to_treedigits { my ($n) = @_; my ($len, $level) = round_down_pow (2*$n-1, 3); my @digits = digit_split_lowtohigh ($n - ($len+1)/2, 3); $#digits = $level-1; # pad to $level with undefs foreach (@digits) { $_ ||= 0 } return @digits; } } { require Math::PlanePath::PythagoreanTree; my $path = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ'); require Math::BigInt; # my ($n_lo,$n_hi) = $path->rect_to_n_range (1000,0, 1500,200); my ($n_lo,$n_hi) = $path->rect_to_n_range (Math::BigInt->new(1000),0, 1500,200); ### $n_hi ### n_hi: "$n_hi" exit 0; } { require Math::PlanePath::PythagoreanTree; # my $path = Math::PlanePath::PythagoreanTree->new # ( # # tree_type => 'FB', # tree_type => 'UAD', # coordinates => 'AB', # ); # my ($x,$y) = $path->n_to_xy(1121); # # exit 0; foreach my $k (1 .. 10) { print 3 * 2**$k + 1,"\n"; print 2**($k+2)+1,"\n"; } sub minpos { my $min = $_[0]; my $pos = 0; foreach my $i (1 .. $#_) { if ($_[$i] < $min) { $min = $_[$i]; $pos = 1; } } return $pos; } require Math::BaseCnv; require Math::PlanePath::PythagoreanTree; my $path = Math::PlanePath::PythagoreanTree->new ( # tree_type => 'UAD', tree_type => 'FB', # coordinates => 'AB', coordinates => 'PQ', ); my $n = 1; foreach my $level (1 .. 100) { my @x; my @y; print "level $level base n=$n\n"; my $base = $n; my ($min_x, $min_y) = $path->n_to_xy($n); my $min_x_n = $n; my $min_y_n = $n; foreach my $rem (0 .. 3**($level-1)-1) { my ($x,$y) = $path->n_to_xy($n); if ($x < $min_x) { $min_x = $x; $min_x_n = $n; } if ($y < $min_y) { $min_y = $y; $min_y_n = $n; } $n++; } my $min_x_rem = $min_x_n - $base; my $min_y_rem = $min_y_n - $base; my $min_x_rem_t = sprintf '%0*s', $level-1, Math::BaseCnv::cnv($min_x_rem,10,3); my $min_y_rem_t = sprintf '%0*s', $level-1, Math::BaseCnv::cnv($min_y_rem,10,3); print " minx=$min_x at n=$min_x_n rem=$min_x_rem [$min_x_rem_t]\n"; print " miny=$min_y at n=$min_y_n rem=$min_y_rem [$min_y_rem_t]\n"; local $,='..'; print $path->rect_to_n_range(0,0, $min_x,$min_y),"\n"; } exit 0; } { my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'UAD'); foreach my $level (1 .. 20) { # my $n = 3 ** $level; my $n = (3 ** $level - 1) / 2; my ($x,$y) = $path->n_to_xy($n); print "$x, $y\n"; } exit 0; } { # low zeros p=q+1 q=2^k my $p = 2; my $q = 1; ### initial ### $p ### $q foreach (1 .. 3) { ($p,$q) = (2*$p-$q, $p); ### $p ### $q } ($p,$q) = (2*$p+$q, $p); ### mid ### $p ### $q foreach (1 .. 3) { ($p,$q) = (2*$p-$q, $p); ### $p ### $q } exit 0; } { require Math::PlanePath::PythagoreanTree; my $path = Math::PlanePath::PythagoreanTree->new; my (undef, $n_hi) = $path->rect_to_n_range(0,0, 1000,1000); ### $n_hi my @count; foreach my $n (1 .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); my $z = hypot($x,$y); $count[$z]++; } my $total = 0; foreach my $i (1 .. $#count) { if ($count[$i]) { $total += $count[$i]; my $ratio = $total/$i; print "$i $total $ratio\n"; } } exit 0; } { require Math::PlanePath::PythagoreanTree; my $path = Math::PlanePath::PythagoreanTree->new; my $n = 1; foreach my $x (0 .. 10000) { foreach my $y (0 .. $x) { my $n = $path->xy_to_n($x,$y); next unless defined $n; my ($nx,$ny) = $path->n_to_xy($n); if ($nx != $x || $ny != $y) { ### $x ### $y ### $n ### $nx ### $ny } } } exit 0; } { my ($q,$p) = (21,46); print "$q / $p\n"; { my $a = $p*$p - $q*$q; my $b = 2*$p*$q; my $c = $p*$p + $q*$q; print "$a $b $c\n"; { require Math::BaseCnv; require Math::PlanePath::PythagoreanTree; my $path = Math::PlanePath::PythagoreanTree->new; my $n = 1; for ( ; $n < 3**11; $n++) { my ($x,$y) = $path->n_to_xy($n); if (($x == $a && $y == $b) || ($x == $b && $y == $a)) { print "n=$n\n"; last; } } my $level = 1; $n -= 2; while ($n >= 3**$level) { $n -= 3**$level; $level++; } my $remt = sprintf "%0*s", $level, Math::BaseCnv::cnv($n,10,3); print "level $level remainder $n [$remt]\n"; } } my $power = 1; my $rem = 0; foreach (1..8) { my $digit; if ($q & 1) { $p /= 2; if ($q > $p) { $q = $q - $p; $digit = 2; } else { $q = $p - $q; $digit = 1; } } else { $q /= 2; $p -= $q; $digit = 0; } print "$digit $q / $p\n"; $rem += $power * $digit; $power *= 3; last if $q == 1 && $p == 2; } print "digits $rem\n"; exit 0; } { # my ($a, $b, $c) = (39, 80, 89); my ($a, $b, $c) = (36,77,85); if (($a ^ $c) & 1) { ($a,$b) = ($b,$a); } print "$a $b $c\n"; my $p = sqrt (($a+$c)/2); my $q = $b/(2*$p); print "$p $q\n"; $a = $p*$p - $q*$q; $b = 2*$p*$q; $c = $p*$p + $q*$q; print "$a $b $c\n"; exit 0; } { require Math::Matrix; my $f = Math::Matrix->new ([2,0], [1,1]); my $g = Math::Matrix->new ([-1,1], [0,2]); my $h = Math::Matrix->new ([1,1], [0,2]); my $fi = $f->invert; print $fi,"\n"; my $gi = $g->invert; print $gi,"\n"; my $hi = $h->invert; print $hi,"\n"; exit 0; } { require Math::PlanePath::PythagoreanTree; my $path = Math::PlanePath::PythagoreanTree->new; my $n = 1; foreach my $i (1 .. 100) { my ($x,$y) = $path->n_to_xy($n); # print 2**($i),"\n"; # print 2*2**$i*(2**$i-1),"\n"; my $z = hypot($x,$y); printf "%3d %4d,%4d,%4d\n", $n, $x, $y, $z; $n += 3**$i; } exit 0; } { sub round_down_pow_3 { my ($n) = @_; my $p = 3 ** (int(log($n)/log(3))); return (3*$p <= $n ? 3*$p : $p > $n ? $p/3 : $p); } require Math::BaseCnv; # base = (range-1)/2 # range = 2*base + 1 # # newbase = ((2b+1)/3 - 1) / 2 # = (2b+1-3)/3 / 2 # = (2b-2)/2/3 # = (b-1)/3 # # deltarem = b-(b-1)/3 # = (3b-b+1)/3 # = (2b+1)/3 # foreach my $n (1 .. 32) { my $h = 2*($n-1)+1; my $level = int(log($h)/log(3)); $level--; my $range = 3**$level; my $base = ($range - 1)/2 + 1; my $rem = $n - $base; if ($rem < 0) { $rem += $range/3; $level--; $range /= 3; } if ($rem >= $range) { $rem -= $range; $level++; $range *= 3; } my $remt = Math::BaseCnv::cnv($rem,10,3); $remt = sprintf ("%0*s", $level, $remt); print "$n $h $level $range base=$base $rem $remt\n"; } exit 0; } { my $sum = 0; foreach my $k (0 .. 10) { $sum += 3**$k; my $f = (3**($k+1) - 1) / 2; print "$k $sum $f\n"; } exit 0; } { require Math::PlanePath::PythagoreanTree; my $path = Math::PlanePath::PythagoreanTree->new; my $x_limit = 500; my @max_n; foreach my $n (0 .. 500000) { my ($x,$y) = $path->n_to_xy($n); if ($x <= $x_limit) { $max_n[$x] = max($max_n[$x] || $n, $n); } } foreach my $x (0 .. $x_limit) { if ($max_n[$x]) { print "$x $max_n[$x]\n"; } } exit 0; } { require Math::PlanePath::PythagoreanTree; my $path = Math::PlanePath::PythagoreanTree->new; my $x_limit = 500; my @max_n; foreach my $n (0 .. 500000) { my ($x,$y) = $path->n_to_xy($n); if ($x <= $x_limit) { $max_n[$x] = max($max_n[$x] || $n, $n); } } foreach my $x (0 .. $x_limit) { if ($max_n[$x]) { print "$x $max_n[$x]\n"; } } exit 0; } { require Math::Matrix; my $u = Math::Matrix->new ([1,2,2], [-2,-1,-2], [2,2,3]); my $a = Math::Matrix->new ([1,2,2], [2,1,2], [2,2,3]); my $d = Math::Matrix->new ([-1,-2,-2], [2,1,2], [2,2,3]); my $ui = $u->invert; print $ui; exit 0; } { my (@x) = 3; my (@y) = 4; my (@z) = 5; for (1..3) { for my $i (0 .. $#x) { print "$x[$i], $y[$i], $z[$i] ",sqrt($x[$i]**2+$y[$i]**2),"\n"; } print "\n"; my @new_x; my @new_y; my @new_z; for my $i (0 .. $#x) { my $x = $x[$i]; my $y = $y[$i]; my $z = $z[$i]; push @new_x, $x - 2*$y + 2*$z; push @new_y, 2*$x - $y + 2*$z; push @new_z, 2*$x - 2*$y + 3*$z; push @new_x, $x + 2*$y + 2*$z; push @new_y, 2*$x + $y + 2*$z; push @new_z, 2*$x + 2*$y + 3*$z; push @new_x, - $x + 2*$y + 2*$z; push @new_y, -2*$x + $y + 2*$z; push @new_z, -2*$x + 2*$y + 3*$z; } @x = @new_x; @y = @new_y; @z = @new_z; } exit 0; } Math-PlanePath-129/devel/flowsnake-ascii.gp0000644000175000017500000002557212544112136016426 0ustar gggg\\ Copyright 2015 Kevin Ryde \\ This file is part of Math-PlanePath. \\ \\ Math-PlanePath 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, or (at your option) any later \\ version. \\ \\ Math-PlanePath 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 Math-PlanePath. If not, see . default(strictargs,1) sqrt3i = quadgen(4*-3); w = 1/2 + 1/2*sqrt3i; b = 2 + w; { \\ rot 1,8,15 through state table rot = [0,0,1, 0,0,1,2]; perm = Vecsmall([1,3,5,7,2,4,6]); v = [6, 6]; r = 0; forstep(i=#v,1, -1, d = (perm^r)[v[i]]; r = (r + rot[d]) % 3; print("new r="r); ); print(r); \\ table = 7*[0, 0, 1, 0, 0, 1, 2, 1, 2, 1, 0, 1, 1, 2, 2, 2, 2, 0, 0, 1, 2]; r = 0; forstep(i=#v,1, -1, r = table[r+v[i]]; print("new r="r); ); print(r/7); d=7;r=2; d = (perm^r)[d]; r = (r + rot[d]) % 3; print("d perm "d" to r="r); d=7;r=2*7; print("table entry ",r+d); r = table[r+d]; print("to r="r); quit; } { \\ rot high to low by state table rot = [0, 0, 1, 0, 0, 1, 2]; perm = Vecsmall([1,3,5,7,2,4,6]); table = vector(3*7,i,-1); for(r=0,2, for(d=1,7, dperm = (perm^r)[d]; new_r = (r + rot[dperm]) % 3; table[7*r + d] = new_r; print("table entry ",7*r+d," is ",7*new_r," for r="r" d="d" perm d="dperm); )); print(table); quit; } { \\ when b^k is an X maximum pos = [0, w^2, 1, w, w^4, w^3, w^5]; for(k=0,50, X = sum(i=0,k-1, vecmax(real(b^i*pos))); Xbk = real(b^(k-1) + 1); diff = abs(Xbk) - X; if(diff >= 0, angle = arg(b^k) *180/Pi; print("k="k" diff="diff" X="X" Xbk="Xbk" angle "angle), print("k="k" not")); ); print(); quit; } { \\ extents pos = [0, w^2, 1, w, w^4, w^3, w^5]; for(k=0,500, X = 2*sum(i=0,k-1, vecmax(real(b^i*pos))); Y = 2*sum(i=0,k-1, vecmax(imag(b^i*pos))); print1(X,","); ); print(); quit; } k=2; digit_to_rot = [0, 0, 1, 0, 0, 1, 2]; digit_permute_inv = [0, 4, 1, 5, 2, 6, 3]; digit_permute = [0, 2, 4, 6, 1, 3, 5]; digit_to_new_rot = matrix(3,7); print(digit_to_new_rot); { for(d=0,6, for(rot=0,2, my(p=d); for(j=1,rot, p=digit_permute[p+1]); new_rot = (rot+digit_to_rot[p+1]) % 3; digit_to_new_rot[rot+1,d+1] = new_rot; ); ); print("digit_to_new_rot"); for(d=0,6, for(rot=0,2, print1(digit_to_new_rot[rot+1,d+1],", ")); print()); print(digit_to_new_rot); print(); } z_to_low_digit(z) = 2*real(z) + 4*imag(z); digit_to_pos = [0, 1, w, w^2, w^3, w^4, w^5]; vector(#digit_to_pos,i, my(z=digit_to_pos[i]); z_to_low_digit(z)) vector(#digit_to_pos,i, my(z=digit_to_pos[i]); z_to_low_digit(z) % 7) digit_to_pos = [0, w^2, 1, w, w^4, w^3, w^5]; vector(#digit_to_pos,i, my(z=digit_to_pos[i]); z_to_low_digit(z) % 7) \\ 0 1 2 3 4 5 6 digit_to_reverse = [1, 0, 0, 0, 0, 0, 1]; z_to_digits(z) = { my(v = vector(k,i, my(d = z_to_low_digit(z) % 7); \\ print("z=",z," low ", d); z = (z - digit_to_pos[d+1]); \\ print("sub to "z); z /= b; d)); if(z,return(-1)); \\ my(rev=0); \\ forstep(i=#v,1, -1, \\ if(rev%2, v[i]=6-v[i]); \\ rev += digit_to_reverse[v[i]+1]); v; } vector(#digit_to_pos,i, my(z=digit_to_pos[i]); (3*imag(z) + real(z)) % 7) z_to_digits(0) print("z_to_digits(1) = ",z_to_digits(1)); z_to_digits(-1) z_to_digits(-w) z_to_digits(2) z_to_digits(b) { x_max=0; x_min=0; y_max=0; y_min=0; for(n=0,7^k-1, z = subst(Pol(apply((d)->digit_to_pos[d+1],digits(n,7))), 'x, b); \\ print("subst "z); x_min = min(x_min,real(z)); x_max = max(x_max,real(z)); y_min = min(y_min,imag(z)); y_max = max(y_max,imag(z))); print("extents X "x_min" "x_max" Y "y_min" "y_max); } { x_max=0; y_max=0; for(i=1,k-1, my(v = vector(6,d, b^i*w^d)); y_max += vecmax(apply(imag,v)); x_max += vecmax(apply(real,v))); x_min=-x_max; y_min=-y_min; print("extents X "x_min" "x_max" Y "y_min" "y_max); } { x_max = sum(i=0,k-1,vecmax(apply(real,vector(6,d, b^i*w^d)))); y_max = sum(i=0,k-1,vecmax(apply(imag,vector(6,d, b^i*w^d)))); x_min=-x_max; y_min=-y_min; print("extents X "x_min" "x_max" Y "y_min" "y_max); } { x_max = sum(i=0,k-1,vecmax(real(vector(6,d, b^i*w^d)))); y_max = sum(i=0,k-1,vecmax(imag(vector(6,d, b^i*w^d)))); x_min=-x_max; y_min=-y_min; print("extents X "x_min" "x_max" Y "y_min" "y_max); } \\ 0 1 2 3 4 5 6 digit_to_rot = [0, 0, 1, 0, 0, 1, 2]; digit_permute_inv = [0, 4, 1, 5, 2, 6, 3]; digit_permute = [0, 2, 4, 6, 1, 3, 5]; small = Vecsmall([1, 3, 5, 7, 2, 4, 6]); small*small \\ 1 2 3 4 5 6 7 small_to_rot = [0, "/ ", "__", 0, " \\", 1, 2]; print("permute twice ", vector(7,d, digit_permute[digit_permute[d]+1])); perform_rotation(v) = { \\ high to low my(rot = 0); forstep(i=#v,1, -1, rot = digit_to_new_rot[rot+1,v[i]+1]; ); return(rot); \\ low to high my(rot = 0); for(i=1,#v, rot = digit_to_new_rot[rot+1,v[i]+1]; ); return(rot); } { for(n=0,7^2-1, my(v=digits(n,7)); my(h = perform_rotation(v)); my(l = perform_rotation(Vecrev(v))); if(l!=h, print(v," h=",h," l=",l)); ); } { for(n=0,7^2-1, my(v=digits(n,7)); \\ print1(perform_rotation(v)); print(v," ",perform_rotation(v)); ); print(); } print(perform_rotation([0,6,2])) quit rot_to_chars = ["__", " \\", "/ "]; { forstep(y=2*y_max,-2*y_max, -1, if(y%2,print1("|")); for(x=-ceil(x_max),ceil(x_max), my(v = z_to_digits(x+(y%2)/2 + y/2*sqrt3i)); if(v==-1, print1(".."); next()); \\ my(d = prod(i=1,#v,small^digit_to_rot[v[i]+1],small)[2]); \\ print1(small_to_rot[d]); my(rot = perform_rotation(v)); print1(rot_to_chars[(rot%3)+1]); \\ my(rot = 0); \\ forstep(i=#v,1, -1, \\ my(d = v[i]); \\ for(j=1,rot, d=digit_permute[d+1]); \\ rot += digit_to_rot[d+1]); \\ print1(rot_to_chars[(rot%3)+1]); \\ print1(v[1]," "); \\print1(rot); ); print()); } quit default(strictargs,1) w = quadgen(-3); \\ sixth root of unity e^(I*Pi/3) digit_to_pos = [0, 1, w, w^2, w^3, w^4, w^5]; vector(#digit_to_pos,i, my(z=digit_to_pos[i]); (3*imag(z) + real(z)) % 7) digit_to_pos = [0, 1, w^2, w, w^4, w^5, w^3]; vector(#digit_to_pos,i, my(z=digit_to_pos[i]); (3*imag(z) + real(z)) % 7) k=2; z_to_digits(z) = { my(v = vector(k,i, my(d = (3*imag(z) + real(z)) % 7); z -= digit_to_pos[d+1]; d)); if(z,-1,v); } vector(#digit_to_pos,i, my(z=digit_to_pos[i]); (3*imag(z) + real(z)) % 7) z_to_digits(0) z_to_digits(1) z_to_digits(-1) z_to_digits(-w) \\ 0 1 2 3 4 5 6 digit_to_rot = [0, 1, 0, 0, 0, 2, 1]; rot_to_chars = ["__"," \\","/ "]; { forstep(y=2,-2, -1, if(y%2,print1("|")); for(x=-2,5, my(v = z_to_digits(x+floor(x/2) + y*w)); if(v==-1,print1(".."), my(rot = sum(i=1,#v, digit_to_rot[v[i]+1])); \\ print1(rot_to_chars[(rot%3)+1]); print1(v[1]," "); \\print1(rot); )); print()); } quit for(k=0,3,\\ ); char = Vecsmall("__.\\/..."); printf("%c",char[2*r+o+1]) \\----------------------------------------------------------------------------- \\ working for(k=0,3,\ { sqrt3i = quadgen(-12); \\ sqrt(-3) w = 1/2 + 1/2*sqrt3i; \\ sixth root of unity b = 2 + w; pos = [0, w^2, 1, w, w^4, w^3, w^5]; rot = [0, 0, 1, 0, 0, 1, 2]; perm = Vecsmall([1,3,5,7,2,4,6]); char = ["_","_", " ","\\", "/"," ", " "," "]; \\ extents X = 2*sum(i=0,k-1, vecmax(real(b^i*pos))); Y = 2*sum(i=0,k-1, vecmax(imag(b^i*pos))); for(y = -Y, Y, for(x = -X+(k>0), X+(k<3), \\ for(y = -Y, -Y+10, \\ for(x = -30, 170, o = (x+y)%2; z = (x-o - y*sqrt3i)/2; v = vector(k,i, d = (2*real(z) + 4*imag(z)) % 7 + 1; z = (z - pos[d]) / b; d); if(z, r = 3, r = 0; forstep(i=#v,1, -1, d = (perm^r)[v[i]]; r = (r + rot[d]) % 3)); print1(char[2*r+o+1])); print()) }\ ); quit \\----------------------------------------------------------------------------- \\ working { sqrt3i = quadgen(-12); \\ sqrt(-3) w = 1/2 + 1/2*sqrt3i; \\ sixth root of unity b = 2 + w; pos = [0, w^2, 1, w, w^4, w^3, w^5]; rot = [0, 0, 1, 0, 0, 1, 2]; perm = [1,2,3,4,5,6,7; 1,3,5,7,2,4,6; 1,5,2,6,3,7,4]; chars = ["__", ".\\", "/ ",".."]; \\ extents X = ceil(sum(i=0,k-1, vecmax(real(b^i*pos)))); Y = 2* sum(i=0,k-1, vecmax(imag(b^i*pos))); for(y = -Y, Y, if(y%2,print1(" ")); for(x = -X, X-(y%2), z = x+(y%2)/2 - y/2*sqrt3i; v = vector(k,i, d = (2*real(z) + 4*imag(z)) % 7 + 1; z = (z - pos[d]) / b; d); if(z, r = 3, r = 0; forstep(i=#v,1, -1, d = perm[r+1,v[i]]; r = (r + rot[d]) % 3)); print1(chars[r+1])); print()) } quit \\----------------------------------------------------------------------------- { sqrt3i = quadgen(-12); w = 1/2 + 1/2*sqrt3i; b = 2 + w; x_max = sum(i=0,k-1,vecmax(apply(real,vector(6,d, b^i*w^d)))); y_max = sum(i=0,k-1,vecmax(apply(imag,vector(6,d, b^i*w^d)))); digit_to_pos = [0, w^2, 1, w, w^4, w^3, w^5]; digit_to_rot = [0, 0, 1, 0, 0, 1, 2]; digit_permute = [1,2,3,4,5,6,7; 1,3,5,7,2,4,6; 1,5,2,6,3,7,4]; rot_to_chars = ["__", " \\", "/ "]; forstep(y=2*y_max,-2*y_max, -1, if(y%2,print1("|")); for(x=-ceil(x_max),ceil(x_max), z = x+(y%2)/2 + y/2*sqrt3i; v = vector(k,i, d = (2*real(z) + 4*imag(z)) % 7 + 1; z = (z - digit_to_pos[d]) / b; d); if(z, print1(".."); next()); rot = 0; forstep(i=#v,1, -1, d = digit_permute[rot+1,v[i]]; rot = (rot + digit_to_rot[d]) % 3); print1(rot_to_chars[rot%3+1])); print()); } \\ M = sum(i=0,k-1, \\ v = vector(6,d, b^i*w^d); \\ vecmax(real(v)) + vecmax(imag(v))*S); \\ X = ceil(real(M)); \\ Y = 2*imag(M); Math-PlanePath-129/devel/dekking-curve.pl0000644000175000017500000002122612766635264016124 0ustar gggg#!/usr/bin/perl -w # Copyright 2015, 2016 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use List::MoreUtils; use POSIX 'floor'; use Math::BaseCnv; use Math::Libm 'M_PI', 'hypot', 'cbrt'; use List::Util 'min', 'max', 'sum'; use Math::PlanePath::DekkingCurve; use Math::PlanePath::Base::Digits 'round_down_pow','digit_split_lowtohigh'; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::BaseCnv 'cnv'; # uncomment this to run the ### lines # use Smart::Comments; { # axis segments, print numbers # X my $path = Math::PlanePath::DekkingCurve->new; foreach my $x (0 .. 20) { print $path->_UNDOCUMENTED__xseg_is_traversed($x)?1:0,","; } print "\n"; foreach my $x (0 .. 50) { if ($path->_UNDOCUMENTED__xseg_is_traversed($x)) { print $x,","; } } print "\n"; # Y foreach my $y (0 .. 20) { print $path->_UNDOCUMENTED__yseg_is_traversed($y)?1:0,","; } print "\n"; foreach my $y (0 .. 50) { if ($path->_UNDOCUMENTED__yseg_is_traversed($y)) { print $y,","; } } print "\n"; print "union\n"; # 1,1,0,1,0,1,1,0,1,0,1,1,0,1,1,0,1,0,1,1,0 foreach my $i (0 .. 40) { print $path->_UNDOCUMENTED__xseg_is_traversed($i) || $path->_UNDOCUMENTED__yseg_is_traversed($i) ?1:0,","; } print "\n"; foreach my $i (0 .. 30) { if ($path->_UNDOCUMENTED__xseg_is_traversed($i) || $path->_UNDOCUMENTED__yseg_is_traversed($i)) { print $i,","; } } print "\n"; exit 0; } { # axis segments by FLAT use lib '../dragon/tools'; require MyFLAT; my $path = Math::PlanePath::DekkingCurve->new; my @xseg_state_table; my $S = 0; my $E = 1; my $N = 2; my $W = 3; $xseg_state_table[$S] = [$S,$S,$E,$N,$W]; $xseg_state_table[$E] = [$S,$S,$E,$N,$N]; $xseg_state_table[$N] = [$W,$S,$E,$N,$N]; $xseg_state_table[$W] = [$W,$S,$E,$N,$W]; my $xseg_flat = MyFLAT::aref_to_FLAT_DFA (\@xseg_state_table, name => 'xseg', accepting => 0); # South { my $bad = 0; foreach my $x (0 .. 1000) { my $str = cnv($x,10,5); my $by_path = $path->_UNDOCUMENTED__xseg_is_traversed($x) ?1:0; my $by_flat = $xseg_flat->contains($str) ?1:0; unless ($by_path == $by_flat) { print "X=$x [$str] $by_path $by_flat\n"; exit 1 if $bad++ > 10; } } } # zero # lowest non zero 1 at lowest, or 1 or 2 above my $xseg_regex = FLAT::Regex->new('0* | (0|1|2|3|4)* 1 | (0|1|2|3|4)* (1|2) 0* 0')->as_dfa; # MyFLAT::FLAT_show_breadth($xseg_flat,2); # MyFLAT::FLAT_show_breadth($xseg_regex,2); MyFLAT::FLAT_check_is_equal($xseg_flat, $xseg_regex); #------------------- my @yseg_state_table; $yseg_state_table[$S] = [$W,$N,$E,$S,$S]; $yseg_state_table[$E] = [$N,$N,$E,$S,$S]; $yseg_state_table[$N] = [$N,$N,$E,$S,$W]; $yseg_state_table[$W] = [$W,$N,$E,$S,$W]; my $yseg_flat = MyFLAT::aref_to_FLAT_DFA (\@xseg_state_table, name => 'yseg', accepting => $N); { my $bad = 0; foreach my $y (0 .. 1000) { my $str = cnv($y,10,5); my $by_path = $path->_UNDOCUMENTED__yseg_is_traversed($y) ?1:0; my $by_flat = $yseg_flat->contains($str) ?1:0; unless ($by_path == $by_flat) { print "Y=$y [$str] $by_path $by_flat\n"; exit 1 if $bad++ > 10; } } } # empty # lowest is 4 # after low digit, lowest non-zero is 1 or 2 my $yseg_regex = FLAT::Regex->new(' (0|1|2|3|4)* 3 | (0|1|2|3|4)* (2|3) 4* 4')->as_dfa; MyFLAT::FLAT_check_is_equal($yseg_flat, $yseg_regex); #------------------- # low 1 or 3 # low 0 then lowest non-0 is 1 or 2 # low 4 then lowest non-4 is 2 or 3 my $union = $xseg_flat->union($yseg_flat)->MyFLAT::minimize; $union->MyFLAT::view; my $union_LtoH = $union->MyFLAT::reverse->MyFLAT::minimize; $union_LtoH->MyFLAT::view; exit 0; } { # X leading diagonal segments my $path = Math::PlanePath::DekkingCentres->new; my @values; my $prev = -1; foreach my $i (0 .. 500) { my $n = $path->xyxy_to_n($i,$i, $i+1,$i+1); # forward # my $n = $path->xyxy_to_n($i+1,$i+1, $i,$i); # reverse if (defined $n) { my $i5 = Math::BaseCnv::cnv($i,10,5); print "$i [$i5] \n"; push @values, $i; } $prev = $n; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, verbose=>1); exit 0; } { # X negative axis N not increasing my $path = Math::PlanePath::DekkingCurve->new (arms => 3); my @values; my $prev = -1; foreach my $i (0 .. 500) { my $n = $path->xy_to_n(-$i,0); if ($n < $prev) { my $i5 = Math::BaseCnv::cnv($i,10,5); print "$i [$i5] \n"; push @values, $i; } $prev = $n; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, verbose=>1); exit 0; } { # X,Y axis points in common (none) my $path = Math::PlanePath::DekkingCurve->new; my @values; foreach my $i (0 .. 500) { my $nx = $path->xy_to_n($i,0); my $ny = $path->xy_to_n(0,$i); if (defined $nx && defined $ny) { my $i5 = Math::BaseCnv::cnv($i,10,5); print "$i5 \n"; push @values, $i; } } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, verbose=>1); exit 0; } { # Y axis points my %table = (S => ['W','N','E','S','S'], E => ['N','N','E','S','S'], N => ['N','N','E','S','W'], W => ['W','N','E','S','W']); sub yseg_to_side { my ($y) = @_; my $state = 'W'; my @digits = digit_split_lowtohigh($y,5); foreach my $digit (reverse @digits) { # high to low $state = $table{$state}->[$digit]; } return $state; } my $path = Math::PlanePath::DekkingCurve->new; my @values; foreach my $y (0 .. 500) { my $path_point_visit = defined($path->xy_to_n(0,$y)) ? 1 : 0; my $path_seg_visit = defined($path->xyxy_to_n_either(0,$y, 0,$y+1)) ? 1 : 0; my $side = yseg_to_side($y); my $prev_side = $y>0 && yseg_to_side($y-1); my $htol_visit = ($side eq 'S' || $side eq 'W' || $prev_side eq 'S' || $prev_side eq 'E' ? 1 : 0); my $htol_seg_visit = ($side eq 'S' ? 1 : 0); my $diff = ($path_seg_visit == $htol_seg_visit ? '' : ' ***'); my $y5 = Math::BaseCnv::cnv($y,10,5); print "$y5 $path_seg_visit ${htol_seg_visit}[$side] $diff\n"; if (defined $path_seg_visit) { push @values, $y; } } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, verbose=>1); exit 0; } { # X axis points # X # S -> S,S,E,N,W # E -> S,S,E,N,N # N -> W,S,E,N,N # W -> W,N,E,S,W my %table = (S => ['S','S','E','N','W'], E => ['S','S','E','N','N'], N => ['W','S','E','N','N'], W => ['W','S','E','N','W']); sub x_to_side { my ($x) = @_; my $state = 'S'; my @digits = digit_split_lowtohigh($x,5); foreach my $digit (reverse @digits) { # high to low $state = $table{$state}->[$digit]; } return $state; } my $path = Math::PlanePath::DekkingCurve->new; my @values; foreach my $x (0 .. 500) { my $path_point_visit = defined($path->xy_to_n($x,0)) ? 1 : 0; my $path_seg_visit = defined($path->xyxy_to_n_either($x,0, $x+1,0)) ? 1 : 0; my $side = x_to_side($x); my $prev_side = $x>0 && x_to_side($x-1); my $htol_visit = ($side eq 'S' || $side eq 'E' || $prev_side eq 'S' || $prev_side eq 'W' ? 1 : 0); my $htol_seg_visit = $path->_UNDOCUMENTED__xseg_is_traversed($x); my $diff = ($path_seg_visit == $htol_seg_visit ? '' : ' ***'); my $x5 = Math::BaseCnv::cnv($x,10,5); print "$x5 $path_seg_visit ${htol_visit}[$side] $diff\n"; if (defined $path_seg_visit) { push @values, $x; } } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, verbose=>1); exit 0; } Math-PlanePath-129/devel/staircase-alternating.pl0000644000175000017500000000175011717623507017642 0ustar gggg#!/usr/bin/perl -w # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::PlanePath::StaircaseAlternating; # uncomment this to run the ### lines use Smart::Comments; { my $path = Math::PlanePath::StaircaseAlternating->new (end_type => 'square'); my @nlohi = $path->rect_to_n_range (0,2, 2,4); ### @nlohi exit 0; } Math-PlanePath-129/devel/alternate-paper.pl0000644000175000017500000005732413577535340016455 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min', 'max'; use Math::Trig 'pi'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh'; use lib 'xt'; use MyOEIS; # uncomment this to run the ### lines # use Smart::Comments; =head2 Right Boundary Segment N The segment numbers which are the right boundary, being the X axis and notches there, are FIXME: N such that N+2 in base-4 has least significant digit any 0,1,2,3 above that only digits 0,2 = 0,1, 2,3,4,5, 14,15,16,17, 18,19,20,21, 62,63,64,65, ... =head2 Left Boundary Segment N The segment numbers which are the left boundary of the curve continued infinitely, being the stair-step diagonal, are FIXME: N such that N+1 in base-4 has least significant digit any 0,1,2,3 above that only digits 0,2 = 0,1,2, 7,8,9,10, 31,32,33,34, 39,40,41,42, 127,128,129,130, ... =cut { # Sum, would be an i_to_n_offset require Math::NumSeq::PlanePathCoord; my $seq = Math::NumSeq::PlanePathCoord->new (planepath => 'AlternatePaper', coordinate_type => 'Sum', i_start => 0, n_start => 1); foreach (1..6) { my ($i,$value) = $seq->next; print "$i $value\n"; } exit 0; } { # resistance # # 2---3 # | | # 0---1 4 # # vertices 5 # 4 # 4.000000000000000000000000000 # level=2 # vertices 14 # 28/5 # 5.600000000000000000000000000 # level=3 # vertices 44 # 32024446704/4479140261 # 7.149686064273931429806591627 # level=4 # vertices 152 # 6628233241945519690439003608662864691664896192990656/773186632952527929515144502921021371068970539201685 # 8.572617476112626473076554400 # # shortcut on X axis # 2---3 # | | 1 + 1/(1+1/3) = 1+3/4 # 0---1---4 # 1 # 1.000000000000000000000000000 # level=1 # vertices 5 # 7/4 # 1.750000000000000000000000000 # level=2 # vertices 14 # 73/26 # 2.807692307692307692307692308 # level=3 # vertices 44 # 2384213425/588046352 # 4.054465123184711126309308352 # level=4 # vertices 152 # 2071307229966623393952039649887056624274965452048209/386986144302228882053693423947791758105522022410048 # 5.352406695855682889687320523 # sub to_bigrat { my ($n) = @_; return $n; require Math::BigRat; return Math::BigRat->new($n); } my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); require Math::PlanePath::AlternatePaper; my $path = Math::PlanePath::AlternatePaper->new; foreach my $level (0 .. 9) { print "level=$level\n"; my %xy_to_index; my %xy_to_value; my $index = 0; my @rows; my $n_lo = 0; my $n_hi = 2*4**$level; foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); my $xy = "$x,$y"; if (! exists $xy_to_index{$xy}) { ### vertex: "$x,$y index=$index" $xy_to_index{$xy} = $index++; $xy_to_value{$xy} = ($n == $n_lo ? to_bigrat(-1) : $n == $n_hi ? to_bigrat(1) : to_bigrat(0)); } } foreach my $xy (keys %xy_to_index) { my @row = (to_bigrat(0)) x $index; $row[$index] = $xy_to_value{$xy}; my $i = $xy_to_index{$xy}; if ($i == 0) { $row[$i] = 1; $row[$index] = 0; } else { my ($x,$y) = split /,/, $xy; ### point: "$x,$y" foreach my $dir4 (0 .. $#dir4_to_dx) { my $dx = $dir4_to_dx[$dir4]; my $dy = $dir4_to_dy[$dir4]; my $x2 = $x+$dx; my $y2 = $y+$dy; my $n = $path->xyxy_to_n ($x,$y, $x2,$y2); if (defined $n && $n < $n_hi) { my $i2 = $xy_to_index{"$x2,$y2"}; ### edge: "$x,$y to $x2,$y2 $i to $i2" $row[$i]++; $row[$i2]--; } } } push @rows, \@row; } print "vertices $index\n"; ### @rows require Math::Matrix; my $m = Math::Matrix->new(@rows); # print $m; if (0) { my $s = $m->solve; # print $s; foreach my $i (0 .. $index-1) { print " ",$s->[$i][0],","; } print "\n"; my $V = $s->[0][0]; print int($V),"+",$V-int($V),"\n"; } { open my $fh, '>', '/tmp/x.gp' or die; mm_print_pari($m,$fh); print $fh "; s=matsolve(m,v); print(s[$index,1]);s[$index,1]+0.0\n"; close $fh; require IPC::Run; IPC::Run::run(['gp','--quiet'],'<','/tmp/x.gp'); } } exit 0; sub mm_print_pari { my ($m, $fh) = @_; my ($rows, $cols) = $m->size; print $fh "m=[\\\n"; my $semi = ''; foreach my $r (0 .. $rows-1) { print $fh $semi; $semi = ";\\\n"; my $comma = ''; foreach my $c (0 .. $cols-2) { print $fh $comma, $m->[$r][$c]; $comma = ','; } } print $fh "];\\\nv=["; $semi = ''; foreach my $r (0 .. $rows-1) { print $fh $semi, $m->[$r][$cols-1]; $semi = ';'; } print $fh "]"; } } { # left boundary require Math::PlanePath::AlternatePaper; my $path = Math::PlanePath::AlternatePaper->new; my @values; for (my $n = $path->n_start; @values < 30; $n++) { if ($path->_UNDOCUMENTED__n_segment_is_right_boundary($n)) { push @values, $n; } } print join(',',@values),"\n"; Math::OEIS::Grep->search(array=>\@values); exit; } { # base 4 reversal # 1000 0 # 111 1 # 110 10 # 101 11 # 100 100 # 11 101 # 10 110 # 1 111 # 0 1000 require Math::BaseCnv; require Math::PlanePath::AlternatePaper; my $path = Math::PlanePath::AlternatePaper->new; foreach my $i (0 .. 32) { my $nx = $path->xy_to_n($i,0); my $nxr = $path->xy_to_n(32-$i,0); printf "%6s ", Math::BaseCnv::cnv($nx, 10,4); printf "%6s ", Math::BaseCnv::cnv($nxr, 10,4); my $c = 3*$nx + 3*$nxr; printf "%6s ", Math::BaseCnv::cnv($c, 10,4); print "\n"; } print "\n"; exit 0; } { # N pairs in X=2^k columns # 8 | 128 # | | # 7 | 42---43/127 # | | | # 6 | 40---41/45--44/124 # | | | | # 5 | 34---35/39--38/46--47/123 # | | | | | # 4 | 32---33/53--36/52--37/49--48/112 # | | | | | | # 3 | 10---11/31--30/54--51/55--50/58--59/111 # | | | | | | | # 2 | 8----9/13--12/28--29/25--24/56--57/61--60/108 # | | | | | | | | # 1 | 2----3/7---6/14--15/27--26/18--19/23---22/62--63/107 # | | | | | | | | | # Y=0 | 0-----1 4-----5 16-----17 20-----21 64---.. # # * # / | \ # *---*---* # 2000-0 # 2000-1 # 2000-10 # 2000-11 # 2000-100 # 1000-1001 # # 0 1 10 11 100 101 110 111 1000 1001 1010 1011 1100 1101 1110 1111 10000 # X=8 # N=64 # left vert = 1000 - horiz # right vert = 2000 - horiz reverse # require Math::PlanePath::AlternatePaper; require Math::BaseCnv; my $path = Math::PlanePath::AlternatePaper->new; print "X "; foreach my $x (0 .. 16) { my $nx = $path->xy_to_n($x,0); print " ",Math::BaseCnv::cnv($nx, 10,4); } print "\n"; foreach my $k (0 .. 3) { my $x = 2**$k; my $x4 = Math::BaseCnv::cnv($x,10,4); print "k=$k x=$x [$x4]\n"; foreach my $y (reverse 0 .. $x) { printf " y=%2d", $y; my $nx = $path->xy_to_n($y,0); my $nxr = $path->xy_to_n($x-$y,0); my $nd = $path->xy_to_n($y,$y); my @n_list = $path->xy_to_n_list($x,$y); foreach my $n (@n_list) { printf " %3d[%6s]", $n, Math::BaseCnv::cnv($n,10,4); } my ($na,$nb) = @n_list; print " "; print " ",Math::BaseCnv::cnv(4**$k - $nx, 10,4); print " ",Math::BaseCnv::cnv(2*4**$k - $nxr, 10,4); print "\n"; } } exit 0; } { # revisit require Math::NumSeq::PlanePathCoord; my $seq = Math::NumSeq::PlanePathCoord->new (planepath => 'AlternatePaper', coordinate_type => 'Revisit'); foreach my $n (0 .. 4*4*4*64) { my $want = $seq->ith($n); my $got = n_to_revisit($n); my $diff = ($want == $got ? '' : ' ***'); print "$n $want $got$diff\n"; last if $diff; } sub n_to_revisit { my ($n) = @_; ### n_to_revisit(): $n my @digits = digit_split_lowtohigh($n,4); ### digits: join(',', reverse @digits) my $rev = 0; foreach my $digit (reverse @digits) { # high to low if ($rev) { $rev ^= ($digit == 0 || $digit == 2); } else { $rev ^= ($digit == 1 || $digit == 3); } } ### $rev my $h = 1; my $v = 1; my $d = 1; my $nonzero = 0; while (defined (my $digit = shift @digits)) { # low to high if ($rev) { $rev ^= ($digit == 0 || $digit == 2); } else { $rev ^= ($digit == 1 || $digit == 3); } ### at: "h=$h v=$v d=$d rev=$rev digit=$digit nonzero=$nonzero" if ($rev) { if ($digit == 0) { $h = 0; $d = 0; } elsif ($digit == 1) { if ($v) { ### return nonzero ... return $nonzero ? 1 : 0; } } elsif ($digit == 2) { if ($d) { ### return nonzero ... return $nonzero ? 1 : 0; } $h = 0; } else { # $digit == 3 $h = 0; } } else { # forward if ($digit == 0) { $v = 0; } elsif ($digit == 1) { if ($h) { return $nonzero ? 1 : 0; } $h = $v; $d = 0; } elsif ($digit == 2) { $h = 0; } else { # $digit == 3 if ($v || $d) { return $nonzero ? 1 : 0; } $v = $h; $h = 0; } } $nonzero ||= $digit; } ### at: "final h=$h v=$v d=$d rev=$rev" return 0; } sub Xn_to_revisit { my ($n) = @_; ### n_to_revisit(): $n my $h = 0; my $v = 0; my $d = 0; my @digits = reverse digit_split_lowtohigh($n,4); ### digits: join(',',@digits) while (@digits && $digits[-1] == 0) { pop @digits; # strip low zero digits } my $low = pop @digits || 0; my $rev = 0; while (defined (my $digit = shift @digits)) { ### at: "rev=$rev h=$h v=$v d=$d digit=$digit more=".scalar(@digits) if ($rev) { if ($digit == 0) { $v = 0; $d = 0; $rev ^= 1; # forward again } elsif ($digit == 1) { $v = ($low ? 1 : 0); } elsif ($digit == 2) { $h = 0; $d = ($low ? 1 : 0); $rev ^= 1; } else { # $digit == 3 $h = ($low ? 1 : 0); } } else { # forward if ($digit == 0) { $v = 0; } elsif ($digit == 1) { $v = ($low ? 1 : 0); $d = 0; $rev ^= 1; } elsif ($digit == 2) { $h = 0; } else { # $digit == 3 $h = ($low ? 1 : 0); $d = 1; $rev ^= 1; } } } ### at: "final rev=$rev h=$h v=$v d=$d" # return ($h || $v); # return ($h || $v || $d); if ($rev) { if ($low == 0) { return $h || $v; } elsif ($low == 1) { return $h; } elsif ($low == 2) { return $d; } else { # $digit == 3 return $v; } } else { if ($low == 0) { return $h || $d; } elsif ($low == 1) { return $h; } elsif ($low == 2) { return $d; } else { # $digit == 3 return $v; } } } exit 0; } { # total turn require Math::PlanePath::AlternatePaper; require Math::BaseCnv; my $path = Math::PlanePath::AlternatePaper->new; my $total = 0; my $bits_total = 0; my @values; for (my $n = 1; $n <= 32; $n++) { my $n2 = Math::BaseCnv::cnv($n,10,2); my $n4 = Math::BaseCnv::cnv($n,10,4); printf "%10s %10s %2d %2d\n", $n2, $n4, $total, $bits_total; # print "$total,"; push @values, $total; $bits_total = total_turn_by_bits($n); my $turn = path_n_turn ($path, $n); if ($turn == 1) { # left $total++; } elsif ($turn == 0) { # right $total--; } else { die; } } print join(',',@values),"\n"; Math::OEIS::Grep->search(array=>\@values); use Math::PlanePath; use Math::PlanePath::GrayCode; sub total_turn_by_bits { my ($n) = @_; my $bits = [ digit_split_lowtohigh($n,2) ]; my $rev = 0; my $total = 0; for (my $pos = $#$bits; $pos >= 0; $pos--) { # high bit to low bit my $bit = $bits->[$pos]; if ($rev) { if ($bit) { } else { if ($pos & 1) { $total--; } else { $total++; } $rev = 0; } } else { if ($bit) { if ($pos & 1) { $total--; } else { $total++; } $rev = 1; } else { } } } return $total; } exit 0; } { require Math::PlanePath::AlternatePaper; require Math::PlanePath::AlternatePaperMidpoint; my $paper = Math::PlanePath::AlternatePaper->new (arms => 8); my $midpoint = Math::PlanePath::AlternatePaperMidpoint->new (arms => 8); foreach my $n (0 .. 7) { my ($x1,$y1) = $paper->n_to_xy($n); my ($x2,$y2) = $paper->n_to_xy($n+8); my ($mx,$my) = $midpoint->n_to_xy($n); my $x = $x1+$x2; # midpoint*2 my $y = $y1+$y2; ($x,$y) = (($x+$y-1)/2, ($x-$y-1)/2); # rotate -45 and shift print "$n $x,$y $mx,$my\n"; } exit 0; } { # grid X,Y offset require Math::PlanePath::AlternatePaperMidpoint; my $path = Math::PlanePath::AlternatePaperMidpoint->new (arms => 8); my %dxdy_to_digit; my %seen; for (my $n = 0; $n < 4**4; $n++) { my $digit = $n % 4; foreach my $arm (0 .. 7) { my ($x,$y) = $path->n_to_xy(8*$n+$arm); my $nb = int($n/4); my ($xb,$yb) = $path->n_to_xy(8*$nb+$arm); $xb *= 2; $yb *= 2; my $dx = $xb - $x; my $dy = $yb - $y; my $dxdy = "$dx,$dy"; my $show = "${dxdy}[$digit]"; $seen{$x}{$y} = $show; if ($dxdy eq '0,0') { } $dxdy_to_digit{$dxdy} = $digit; } } foreach my $y (reverse -45 .. 45) { foreach my $x (-5 .. 5) { printf " %9s", $seen{$x}{$y}//'e' } print "\n"; } ### %dxdy_to_digit exit 0; } { # sum/sqrt(n) goes below pi/4 print "pi/4 ",pi/4,"\n"; require Math::PlanePath::AlternatePaper; my $path = Math::PlanePath::AlternatePaper->new; my $min = 999; for my $n (1 .. 102400) { my ($x,$y) = $path->n_to_xy($n); my $sum = $x+$y; my $frac = $sum/sqrt($n); # printf "%10s %.4f\n", "$n,$x,$y", $frac; $min = min($min,$frac); } print "min $min\n"; exit 0; } { # repeat points require Math::PlanePath::AlternatePaper; require Math::BaseCnv; my $path = Math::PlanePath::AlternatePaper->new; for my $nn (0 .. 1024) { my ($x,$y) = $path->n_to_xy($nn); next unless $y == 18; my ($n,$m) = $path->xy_to_n_list($x,$y); next unless ($n == $nn) && $m; my $diff = $m - $n; my $xor = $m ^ $n; my $n4 = Math::BaseCnv::cnv($n,10,4); my $m4 = Math::BaseCnv::cnv($m,10,4); my $diff4 = Math::BaseCnv::cnv($diff,10,4); my $xor4 = Math::BaseCnv::cnv($xor,10,4); printf "%10s %6s %6s %6s,%-6s\n", "$n,$x,$y", $n4, $m4, $diff4, $diff4; } exit 0; } { # dY require Math::PlanePath::AlternatePaper; require Math::BaseCnv; my $path = Math::PlanePath::AlternatePaper->new; for (my $n = 1; $n <= 64; $n += 2) { my $n2 = Math::BaseCnv::cnv($n,10,2); my $n4 = Math::BaseCnv::cnv($n,10,4); my $dy = path_n_dy ($path, $n); my $nhalf = $n>>1; my $grs_half = GRS($nhalf); my $calc_dy = $grs_half * (($nhalf&1) ? -1 : 1); my $diff = ($calc_dy == $dy ? '' : ' ****'); my $grs = GRS($n); printf "%10s %10s %2d %2d %2d%s\n", $n2, $n4, $dy, $grs, $calc_dy,$diff; } exit 0; sub GRS { my ($n) = @_; return (count_1_bits($n&($n>>1)) & 1 ? -1 : 1); } sub count_1_bits { my ($n) = @_; my $count = 0; while ($n) { $count += ($n & 1); $n >>= 1; } return $count; } } { # base4 X,Y axes and diagonal # diagonal base4 all twos require Math::PlanePath::AlternatePaper; require Math::BaseCnv; my $path = Math::PlanePath::AlternatePaper->new; for my $x (0 .. 40) { my $y; $y = 0; $y = $x; my $n = $path->xy_to_n($x,$y); my $n2 = Math::BaseCnv::cnv($n,10,2); my $n4 = Math::BaseCnv::cnv($n,10,4); printf "%14s %10s %4d %d,%d\n", $n2, $n4, $n,$x,$y; } exit 0; } { # dX require Math::PlanePath::AlternatePaper; require Math::BaseCnv; my $path = Math::PlanePath::AlternatePaper->new; for (my $n = 0; $n <= 64; $n += 2) { my $n2 = Math::BaseCnv::cnv($n,10,2); my $n4 = Math::BaseCnv::cnv($n,10,4); my ($dx,$dy) = $path->n_to_dxdy($n); my $grs = GRS($n); my $calc_dx = 0; my $diff = ($calc_dx == $dx ? '' : ' ****'); printf "%10s %10s %2d %2d %2d%s\n", $n2, $n4, $dx, $grs, $calc_dx,$diff; } exit 0; } { # plain rev # 0 0 0 -90 # 1 +90 1 0 # 2 0 2 +90 # 3 -90 3 0 # # dX ends even so plain, count 11 bits mod 2 # dY ends odd so rev, # dX,dY require Math::PlanePath::AlternatePaper; require Math::BaseCnv; my $path = Math::PlanePath::AlternatePaper->new; for (my $n = 0; $n <= 128; $n += 2) { my ($x,$y) = $path->n_to_xy($n); my ($next_x,$next_y) = $path->n_to_xy($n+1); my $dx = $next_x - $x; my $dy = - path_n_dy ($path,$n ^ 0xFFFF); my $n2 = Math::BaseCnv::cnv($n,10,2); my $n4 = Math::BaseCnv::cnv($n,10,4); printf "%10s %10s %2d,%2d\n", $n2, $n4, $dx,$dy; } exit 0; sub path_n_dx { my ($path,$n) = @_; my ($x,$y) = $path->n_to_xy($n); my ($next_x,$next_y) = $path->n_to_xy($n+1); return $next_x - $x; } sub path_n_dy { my ($path,$n) = @_; my ($x,$y) = $path->n_to_xy($n); my ($next_x,$next_y) = $path->n_to_xy($n+1); return $next_y - $y; } } # return 1 for left, 0 for right sub path_n_turn { my ($path, $n) = @_; my $prev_dir = path_n_dir ($path, $n-1); my $dir = path_n_dir ($path, $n); my $turn = ($dir - $prev_dir) % 4; if ($turn == 1) { return 1; } if ($turn == 3) { return 0; } die "Oops, unrecognised turn"; } # return 0,1,2,3 sub path_n_dir { my ($path, $n) = @_; my ($x,$y) = $path->n_to_xy($n); my ($next_x,$next_y) = $path->n_to_xy($n+1); return dxdy_to_dir4 ($next_x - $x, $next_y - $y); } # return 0,1,2,3, with Y reckoned increasing upwards sub dxdy_to_dir4 { my ($dx, $dy) = @_; if ($dx > 0) { return 0; } # east if ($dx < 0) { return 2; } # west if ($dy > 0) { return 1; } # north if ($dy < 0) { return 3; } # south } { # Old code for n_to_xy() with explicit rotation etc rather than state table. # # my @dir4_to_dx = (1,0,-1,0); # my @dir4_to_dy = (0,1,0,-1); # # my @arm_to_x = (0,0, 0,0, -1,-1, -1,-1); # my @arm_to_y = (0,0, 1,1, 1,1, 0,0); # # sub XXn_to_xy { # my ($self, $n) = @_; # ### AlternatePaper n_to_xy(): $n # # if ($n < 0) { return; } # if (is_infinite($n)) { return ($n, $n); } # # my $frac; # { # my $int = int($n); # $frac = $n - $int; # inherit possible BigFloat # $n = $int; # BigFloat int() gives BigInt, use that # } # ### $frac # # my $zero = ($n * 0); # inherit bignum 0 # # my $arm = _divrem_mutate ($n, $self->{'arms'}); # # my @bits = bit_split_lowtohigh($n); # if (scalar(@bits) & 1) { # push @bits, 0; # extra high to make even # } # # my @sx; # my @sy; # { # my $sy = $zero; # inherit BigInt # my $sx = $sy + 1; # inherit BigInt # ### $sx # ### $sy # # foreach (1 .. scalar(@bits)/2) { # push @sx, $sx; # push @sy, $sy; # # # (sx,sy) + rot+90(sx,sy) # ($sx,$sy) = ($sx - $sy, # $sy + $sx); # # push @sx, $sx; # push @sy, $sy; # # # (sx,sy) + rot-90(sx,sy) # ($sx,$sy) = ($sx + $sy, # $sy - $sx); # } # } # # ### @bits # ### @sx # ### @sy # ### assert: scalar(@sx) == scalar(@bits) # # my $rot = int($arm/2); # arm to initial rotation # my $rev = 0; # my $x = $zero; # my $y = $zero; # while (@bits) { # { # my $bit = pop @bits; # high to low # my $sx = pop @sx; # my $sy = pop @sy; # ### at: "$x,$y $bit side $sx,$sy" # ### $rot # # if ($rot & 2) { # ($sx,$sy) = (-$sx,-$sy); # } # if ($rot & 1) { # ($sx,$sy) = (-$sy,$sx); # } # # if ($rev) { # if ($bit) { # $x -= $sy; # $y += $sx; # ### rev add to: "$x,$y next is still rev" # } else { # $rot ++; # $rev = 0; # } # } else { # if ($bit) { # $rot ++; # $x += $sx; # $y += $sy; # $rev = 1; # ### add to: "$x,$y next is rev" # } # } # } # # @bits || last; # # { # my $bit = pop @bits; # my $sx = pop @sx; # my $sy = pop @sy; # ### at: "$x,$y $bit side $sx,$sy" # ### $rot # # if ($rot & 2) { # ($sx,$sy) = (-$sx,-$sy); # } # if ($rot & 1) { # ($sx,$sy) = (-$sy,$sx); # } # # if ($rev) { # if ($bit) { # $x += $sy; # $y -= $sx; # ### rev add to: "$x,$y next is still rev" # } else { # $rot --; # $rev = 0; # } # } else { # if ($bit) { # $rot --; # $x += $sx; # $y += $sy; # $rev = 1; # ### add to: "$x,$y next is rev" # } # } # } # } # # ### $rot # ### $rev # # if ($rev) { # $rot += 2; # ### rev change rot to: $rot # } # # if ($arm & 1) { # ($x,$y) = ($y,$x); # odd arms transpose # } # # $rot &= 3; # $x = $frac * $dir4_to_dx[$rot] + $x + $arm_to_x[$arm]; # $y = $frac * $dir4_to_dy[$rot] + $y + $arm_to_y[$arm]; # # ### final: "$x,$y" # return ($x,$y); # } exit 0; } Math-PlanePath-129/devel/square-spiral.pl0000644000175000017500000001136413675603432016150 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use Math::PlanePath::SquareSpiral; use Math::Prime::XS; $|=1; # uncomment this to run the ### lines #use Smart::Comments; { # A136626 num prime neighbours 8 directions my $path = Math::PlanePath::SquareSpiral->new; my @dir8_to_dx = (1,1, 0,-1, -1,-1, 0,1); my @dir8_to_dy = (0,1, 1,1, 0,-1, -1,-1); my $A136626 = sub { my ($n) = @_; my ($x,$y) = $path->n_to_xy($n); my $count = 0; if (Math::Prime::XS::is_prime($n)) { $count++; } # for A136627 foreach my $dir (0 .. 7) { my $sn = $path->xy_to_n ($x+$dir8_to_dx[$dir], $y+$dir8_to_dy[$dir]); if (Math::Prime::XS::is_prime($sn)) { $count++; } } return $count; }; my @seen; my $prev = 0; for (my $n = 1; ; $n++) { my $this = $n >> 14; if ($this != $prev) { print "$n\r"; $prev = $this; } my $count = $A136626->($n); if (!$seen[$count]++) { print "$count at $n\n"; } } exit 0; } { # A240025 L-system my %to; %to = (S => 'SFT+FT+', # SquareSpiral T => 'FT', F => 'F', '+' => '+'); %to = (S => 'STF+TF+', # SquareSpiral2 T => 'TF', F => 'F', '+' => '+'); my $str = 'S'; foreach (1 .. 7) { my $padded = $str; $padded =~ s/./$& /g; # spaces between symbols print "$padded\n"; $str =~ s{.}{$to{$&} // die}ge; } $str =~ s/F(?=[^+]*F)/F0/g; $str =~ s/F//g; $str =~ s/\+/1/g; $str =~ s/S/1/g; $str =~ s/T//g; print $str,"\n"; require Math::NumSeq::OEIS; my $seq = Math::NumSeq::OEIS->new (anum => 'A240025'); my $want = ''; while (length($want) < length($str)) { my ($i,$value) = $seq->next; $want .= $value; } $str eq $want or die "oops, different"; print "end\n"; exit 0; } { # cf A002620 quarter squares = floor(n^2/4) = (n^2-(n%2))/4 # concat(vector(10,n,[n^2,n*(n+1)])) # # vector(10,n, (n^2-(n%2))/4) # q=(n^2-(n%2))/4 # 4q = n^2 - (n%2) # n^2 = 4q + (n%2) # vector(20,n, ((n^2-(n%2))/4) % 4) # vector(20,n, n%2) # n*n = 0 or 1 mod 4 # q = n*(n+1) = n*n + n = 0 or 2 mod 4 # n^2 + n - q = 0 # q=12 # n = ( -1 + sqrt(4*q + 1) )/2 # # a(n) = issquare(n) || issquare(4*n+1); # vector(20,n, (n^2)%8) \\ 0,1, 4 # for(n=1,10, print(n*n" "n*(n+1))) # for(n=1,20, print(n, " ", (n*n)%4," ",(n*(n+1))%4 )) exit 0; } { my $path = Math::PlanePath::SquareSpiral->new; foreach my $n (1 .. 100) { my ($x,$y) = $path->n_to_xy($n); print "$x,"; } # v=[0,1,1,0,-1,-1,-1,0,1,2,2,2,2,1,0,-1,-2,-2,-2,-2,-2,-1,0,1,2,3,3,3,3,3,3,2,1,0,-1,-2,-3,-3,-3,-3,-3,-3,-3,-2,-1,0,1,2,3,4,4,4,4,4,4,4,4,3,2,1,0,-1,-2,-3,-4,-4,-4,-4,-4,-4,-4,-4,-4,-3,-2,-1,0,1,2,3,4,5,5,5,5,5,5,5,5,5,5,4,3,2,1,0,-1,-2,-3,-4]; # a(n) = if(n==1,'e,my(d=(2+sqrtint(4*n-7))\4); n--; n -= 4*d^2; \ # if(n>=0, if(n<=2*d, -d, n-3*d), if(n>=-2*d, -n-d, d))); # a(n) = my(d=(sqrtint(4*n-3)+1)\2); n -= d*d+1; \ # -(-1)^d * if(n>=0, d\2+1, d\2+n+1); # a(n) = my(d=ceil(sqrtint(n-1)/2)); n -= 4*d^2; \ # if(n<=0, if(n<=-2*d, d, 1-d-n), if(n<=2*d, -d, n-3*d-1)); # a(n) = n--; my(k=ceil(sqrtint(n)/2)); n -= 4*k^2; \ # if(n<0, if(n<-2*k, k, -k-n), if(n<2*k, -k, n-3*k)); # # a(n) = n--; my(m=sqrtint(n), k=ceil(m/2)); n -= 4*k^2; \ # if(n<0, if(n<-m, k, -k-n), if(n=1 # e = 1+sqrtint(4*n-7) # vector(20,n, (1+sqrtint(4*n-3))\2) # vector(20,n, my(d=(1+sqrtint(4*n-3))\2); n-d*(d-1)) exit 0; } { require Math::Prime::XS; my @primes = (0, Math::Prime::XS::sieve_primes (1000)); my $path = Math::PlanePath::SquareSpiral->new; foreach my $y (reverse -4 .. 4) { foreach my $x (-4 .. 4) { my $n = $path->xy_to_n($x,$y); my $p = $primes[$n] // ''; printf " %4d", $p; } print "\n"; } exit 0; } Math-PlanePath-129/devel/gosper-side.pl0000644000175000017500000000526312402275640015573 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.006; use strict; use warnings; use Math::Libm 'M_PI', 'hypot'; { # horizontals have count_1_digits == 0 mod 3 # Easts have count_1_digits == 0 mod 6 # require Math::PlanePath::GosperSide; require Math::BaseCnv; my $path = Math::PlanePath::GosperSide->new; foreach my $n (0 .. 500) { my ($dx,$dy) = $path->n_to_dxdy($n); my $n3 = Math::BaseCnv::cnv($n, 10, 3); # next if $n3 =~ /1/; next if $dy != 0; # next if $dx < 0; print "$n $n3 $dx $dy\n"; } exit 0; } { # minimum hypot beyond N=3^level # require Math::PlanePath::GosperSide; require Math::BaseCnv; my $path = Math::PlanePath::GosperSide->new; my $prev_min_hypot = 1; foreach my $level (0 .. 40) { my $n_level = 3**$level; my $min_n = $n_level; my ($x,$y) = $path->n_to_xy($min_n); my $min_hypot = hypot($x,sqrt(3)*$y); foreach my $n ($n_level .. 1.0001*$n_level) { my ($x,$y) = $path->n_to_xy($n); my $h = hypot($x,sqrt(3)*$y); if ($h < $min_hypot) { $min_n = $n; $min_hypot = $h; } } my $min_n3 = Math::BaseCnv::cnv($min_n, 10, 3); my $factor = $min_hypot / $prev_min_hypot; printf "%2d %8d %15s %9.2f %7.4f %7.4g\n", $level, $min_n, "[$min_n3]", $min_hypot, $factor, $factor-sqrt(7); $prev_min_hypot = $min_hypot; } exit 0; } { # growth of 3^level hypot # require Math::PlanePath::GosperSide; my $path = Math::PlanePath::GosperSide->new; my $prev_angle = 0; my $prev_dist = 0; foreach my $level (0 .. 20) { my ($x,$y) = $path->n_to_xy(3**$level); $y *= sqrt(3); my $angle = atan2($y,$x); $angle *= 180/M_PI(); if ($angle < 0) { $angle += 360; } my $delta_angle = $angle - $prev_angle; my $dist = log(hypot($x,$y)); my $delta_dist = $dist - $prev_dist; printf "%d %d,%d %.1f %+.3f %.3f %+.5f\n", $level, $x, $y, $angle, $delta_angle, $dist, $delta_dist; $prev_angle = $angle; $prev_dist = $dist; } exit 0; } Math-PlanePath-129/devel/number-fraction.pl0000644000175000017500000000331114001400740016421 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; # use lib '/so/perl/number-fraction/number-fraction/lib/'; use lib '/so/perl/number-fraction/'; use Number::Fraction; print "Number::Fraction version ",Number::Fraction->VERSION,"\n"; # uncomment this to run the ### lines use Smart::Comments; { # Number::Fraction 3.0.3 problem with plain scalar first compares print 123 < Number::Fraction->new(123) ? "yes\n" : "no\n"; print Number::Fraction->new(123) < 123 ? "yes\n" : "no\n"; print 123 == Number::Fraction->new(123) ? "yes\n" : "no\n"; print Number::Fraction->new(123) == 123 ? "yes\n" : "no\n"; exit 0; } { my $x = Number::Fraction->new('4/3'); my $y = Number::Fraction->new('2/1'); my $pow = $x ** $y; print "pow: $pow\n"; exit 0; } { my $x = Number::Fraction->new('0/2'); my $y = Number::Fraction->new('0/1'); my $eq = ($x == $y); print "equal: $eq\n"; exit 0; } { my $nf = Number::Fraction->new('4/-3'); print "$nf\n"; $nf = int($nf); print "$nf ",ref($nf),"\n"; exit 0; } Math-PlanePath-129/devel/bigint-lite.pl0000644000175000017500000000403612523324765015565 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2015 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Devel::TimeThis; # use Math::BigInt try => 'GMP'; use Math::BigInt::Lite; # uncomment this to run the ### lines use Smart::Comments; { # ->blog() my $base = 3; my $n = Math::BigInt::Lite->new(1); my $exp = $n->copy->blog($base); ### n: $n ### exp: $exp ### exp: ref $exp my $pow = (ref $n)->new(1)->blsft($exp,$base); ### pow: "$pow" ### pow: ref $pow exit 0; } { # log() my $n = Math::BigInt::Lite->new(1); my $exp = log($n); ### n: "$n" ### exp: "$exp" my $div = log(3); $exp /= $div; ### exp: "$exp" exit 0; } { # sprintf about 2x faster my $start = 0xFFFFFFF; my $end = $start + 0x10000; { my $t = Devel::TimeThis->new('sprintf'); foreach ($start .. $end) { my $n = $_; my @array = reverse split //, sprintf('%b',$n); } } { my $t = Devel::TimeThis->new('division'); foreach ($start .. $end) { my $n = $_; my @ret; do { my $digit = $n % 2; push @ret, $digit; $n = int(($n - $digit) / 2); } while ($n); } } exit 0; } { { my $t = Devel::TimeThis->new('main'); foreach (1 .. 10000) { Math::BigInt::Lite->newXX(123); } } { my $t = Devel::TimeThis->new('lite'); foreach (1 .. 10000) { Math::BigInt::Lite->new(123); } } exit 0; } Math-PlanePath-129/devel/wythoff-array.pl0000644000175000017500000002230212240271436016144 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::PlanePath::WythoffArray; use lib 't','xt'; # uncomment this to run the ### lines # use Smart::Comments; { # tree A230871 require Math::PlanePath::WythoffArray; my $wythoff = Math::PlanePath::WythoffArray->new (x_start => 1, y_start => 1); my @parent = (undef, 0); my @value = (0, 1); my @child_left = (1); my @child_right = (undef); my $value_seen = ''; { my @pending = (1); foreach (0 .. 13) { my @new_pending; while (@pending) { my $i = shift @pending; my $value = $value[$i] // die "oops no value at $i"; if ($value < 20000) { vec($value_seen,$value,1) = 1; } my $parent_i = $parent[$i]; my $parent_value = $value[$parent_i]; { my $left_value = $value + $parent_value; my $left_i = scalar(@value); $value[$left_i] = $left_value; $parent[$left_i] = $i; $child_left[$i] = $left_i; push @new_pending, $left_i; } { my $right_value = 3*$value - $parent_value; my $right_i = scalar(@value); $value[$right_i] = $right_value; $parent[$right_i] = $i; $child_right[$i] = $right_i; push @new_pending, $right_i; } } @pending = @new_pending; } } print "total nodes ",scalar(@value),"\n"; my @rows; { # by rows my @pending = (0); while (@pending) { my @new_pending; my @row; while (@pending) { my $i = shift @pending; if (defined $child_left[$i]) { push @new_pending, $child_left[$i]; } if (defined $child_right[$i]) { push @new_pending, $child_right[$i]; } my $value = $value[$i]; push @row, $value; if (@row < 20) { printf '%4d,', $value; } } print "\n"; @pending = @new_pending; push @rows, \@row; } } # print columns { foreach my $c (0 .. 20) { print "col c=$c: "; foreach my $r (0 .. 20) { if (defined (my $value = $rows[$r]->[$c])) { print "$value,"; } } print "\n"; } } my @wythoff_row; my @wythoff_step; my @triangle; { # wythoff row my $r = 0; my $c = 0; my %seen; my $print_c_limit = 300; for (;;) { my $v1 = $rows[$r]->[$c]; if (! defined $v1) { $r++; if ($c < $print_c_limit) { print "next row\n"; } next; } my $v2 = $rows[$r+1]->[$c]; if (! defined $v2) { last; } if ($v1 <= $v2) { print "smaller v1: $v1 $v2\n"; } $triangle[$v1][$v2] = 1; my ($x,$y,$step) = pair_to_wythoff_xy($v1,$v2); $x //= '[undef]'; $y //= '[undef]'; my $wv1 = $wythoff->xy_to_n($x,$y); my $wv2 = $wythoff->xy_to_n($x+1,$y); if ($c < $print_c_limit) { print "$c $v1,$v2 $x, $y $step is $wv1, $wv2\n"; } if ($c < 40) { push @wythoff_row, $y; push @wythoff_step, $step; } if (defined $seen{$y}) { print "seen $y at $seen{$y}\n"; } $seen{$y} = $c; $c++; } print "stop at column $c\n"; print "\n"; } { # print triangle foreach my $v1 (reverse 0 .. 80) { foreach my $v2 (0 .. 80) { print $triangle[$v1][$v2] ? '*' : ' '; } print "\n"; } } @wythoff_row = sort {$a<=>$b} @wythoff_row; foreach (1, 2) { print join(',',@wythoff_row),"\n"; { require Math::NumSeq::Fibbinary; my $fib = Math::NumSeq::Fibbinary->new; print join(',',map{sprintf '%b',$fib->ith($_)} @wythoff_row),"\n"; } foreach (@wythoff_row) { $_-- } print "\n"; } print "step: ",join(',',@wythoff_step),"\n"; require MyOEIS; MyOEIS::compare_values (anum => 'A230872', name => 'tree all values occurring', max_count => 700, func => sub { my ($count) = @_; my @got = (0); for (my $i = 0; @got < $count; $i++) { if (vec($value_seen,$i,1)) { push @got, $i; } } return \@got; }); MyOEIS::compare_values (anum => 'A230871', name => 'tree table', func => sub { my ($count) = @_; my @got; my $r = 0; my $c = 0; while (@got < $count) { my $row = $rows[$r] // last; if ($c > $#$row) { $r++; $c = 0; next; } push @got, $row->[$c]; $c++; } return \@got; }); exit 0; sub pair_to_wythoff_xy { my ($v1,$v2) = @_; foreach my $step (0 .. 500) { # use Smart::Comments; ### at: "seek $v1, $v2 step $_" if (my ($x,$y) = $wythoff->n_to_xy($v1)) { my $wv2 = $wythoff->xy_to_n($x+1,$y); if (defined $wv2 && $wv2 == $v2) { ### found: "pair $v1 $v2 at x=$x y=$x" return ($x,$y,$step); } } ($v1,$v2) = ($v2,$v1+$v2); } } } { # left-justified shift amount require Math::NumSeq::Fibbinary; my $fib = Math::NumSeq::Fibbinary->new; my $path = Math::PlanePath::WythoffArray->new; foreach my $y (0 .. 50) { my $a = $path->xy_to_n(0,$y); my $b = $path->xy_to_n(1,$y); my $count = 0; while ($a < $b) { ($a,$b) = ($b-$a,$a); $count++; } my $y_fib = sprintf '%b',$fib->ith($y); print "$y $y_fib $count\n"; # $count = ($count+1)/2; # print "$count,"; } exit 0; } { # Y*phi use constant PHI => (1 + sqrt(5)) / 2; my $path = Math::PlanePath::WythoffArray->new (y_start => 0); foreach my $y ($path->y_minimum .. 20) { my $n = $path->xy_to_n(0,$y); my $prod = int(PHI*PHI*$y + PHI); print "$y $n $prod\n"; } exit 0; } { # dual require Math::NumSeq::Fibbinary; my $seq = Math::NumSeq::Fibbinary->new; foreach my $value ( 1 .. 300, 1, # # 1,10 # 4, 6, 10, 16, 26, 42, 68, 110, 178, 288, 466 # 101,1001 # 7, 11, 18, 29, 47, 76, 123, 199, 322, 521, 843 # 1010,10100 # 9, 14, 23, 37, 60, 97, 157, 254, 411, 665, 1076, # 10001,100001 # 12, 19, 31, 50, 81, 131, 212, 343, 555, 898, 1453 # 10101,101001 ) { my $z = $seq->ith($value); printf "%3d %6b\n", $value, $z; } exit 0; } { # Fibbinary with even trailing 0s require Math::NumSeq::Fibbinary; require Math::NumSeq::DigitCountLow; my $seq = Math::NumSeq::Fibbinary->new; my $cnt = Math::NumSeq::DigitCountLow->new (radix => 2, digit => 0); my $e = 0; foreach (1 .. 40) { my ($i, $value) = $seq->next; my $c = $cnt->ith($value); my $str = ($c % 2 ? 'odd' : 'even'); my $ez = $seq->ith($e); if ($c % 2 == 0) { printf "%2d %6b %s [%d] %5b\n", $i, $value, $str, $c, $ez; } else { printf "%2d %6b %s [%d]\n", $i, $value, $str, $c; } if ($c % 2 == 0) { $e++; } } exit 0; } { require Math::BaseCnv; require Math::PlanePath::PowerArray; my $path; my $radix = 3; my $width = 9; $path = Math::PlanePath::PowerArray->new (radix => $radix); foreach my $y (reverse 0 .. 6) { foreach my $x (0 .. 5) { my $n = $path->xy_to_n($x,$y); my $nb = sprintf '%*s', $width, Math::BaseCnv::cnv($n,10,$radix); print $nb; } print "\n"; } exit 0; } { # max Dir4 require Math::BaseCnv; print 4-atan2(2,1)/atan2(1,1)/2,"\n"; require Math::NumSeq::PlanePathDelta; my $realpart = 3; my $radix = $realpart*$realpart + 1; my $planepath = "WythoffArray"; $planepath = "GcdRationals,pairs_order=rows_reverse"; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => $planepath, delta_type => 'Dir4'); my $dx_seq = Math::NumSeq::PlanePathDelta->new (planepath => $planepath, delta_type => 'dX'); my $dy_seq = Math::NumSeq::PlanePathDelta->new (planepath => $planepath, delta_type => 'dY'); my $max = -99; for (1 .. 1000000) { my ($i, $value) = $seq->next; $value = -$value; if ($value > $max) { my $dx = $dx_seq->ith($i); my $dy = $dy_seq->ith($i); my $ri = Math::BaseCnv::cnv($i,10,$radix); my $rdx = Math::BaseCnv::cnv($dx,10,$radix); my $rdy = Math::BaseCnv::cnv($dy,10,$radix); my $f = $dy && $dx/$dy; printf "%d %s %.5f %s %s %.3f\n", $i, $ri, $value, $rdx,$rdy, $f; $max = $value; } } exit 0; } Math-PlanePath-129/devel/filled-rings.pl0000644000175000017500000000263611720341360015725 0ustar gggg#!/usr/bin/perl -w # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use Math::PlanePath::FilledRings; # uncomment this to run the ### lines use Smart::Comments; { # average diff step my $path = Math::PlanePath::FilledRings->new; my $prev_n = $path->xy_to_n(0,0); my $prev_loop = $path->xy_to_n(0,0); my $diff_total = 0; my $diff_count = 0; foreach my $x (1 .. 500) { my $n = $path->xy_to_n($x,0); my $loop = $n - $prev_n; my $diff = $loop - $prev_loop; #printf "%2d %3d %3d %3d\n", $x, $n, $loop, $diff; $prev_n = $n; $prev_loop = $loop; $diff_total += $diff; $diff_count++; } my $avg = $diff_total/$diff_count; my $sqavg = $avg*$avg; print "diff average $avg squared $sqavg\n"; exit 0; } Math-PlanePath-129/devel/complex-plus.pl0000644000175000017500000000647513544510002016000 0ustar gggg#!/usr/bin/perl -w # Copyright 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use POSIX; use List::Util 'min', 'max'; use Math::BaseCnv; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use Math::PlanePath::ComplexPlus; use lib 'xt'; use MyOEIS; # uncomment this to run the ### lines # use Smart::Comments; { # picture of ComplexPlus Gray one bit different, cf TA require Image::Base::GD; my $width = 900; my $height = 600; my $scale = 20; my $ox = int($width * .7); my $oy = int($height * .7); my $transform = sub { my ($x,$y) = @_; $x *= $scale; $y *= $scale; $x += $ox; $y += $oy; return ($x,$height-1-$y); }; my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); require Math::PlanePath::ComplexPlus; my $path = Math::PlanePath::ComplexPlus->new; my $image = Image::Base::GD->new (-height => $height, -width => $width); $image->rectangle(0,0, $width-1,$height-1, 'black'); $image->ellipse($transform->(-.2,-.2), $transform->(.2,.2), 'red', 1); foreach my $n (0 .. 2**8-1) { my ($x,$y) = $path->n_to_xy($n); my $n_gray = Gray($n); foreach my $dir4 (0 .. 3) { my $dx = $dir4_to_dx[$dir4]; my $dy = $dir4_to_dy[$dir4]; my $x2 = $x + $dx; my $y2 = $y + $dy; my $n_dir = $path->xy_to_n($x2,$y2) // next; my $n_dir_gray = Gray($n_dir); ### neighbour: sprintf "%d to %d Gray %b to %b", $n, $n_dir, $n_gray, $n_dir_gray if (CountOneBits($n_gray ^ $n_dir_gray) == 1) { $image->line($transform->($x,$y), $transform->($x2,$y2), 'white'); } } } my $filename = '/tmp/gray.png'; $image->save($filename); require IPC::Run; IPC::Run::start(['xzgv',$filename],'&'); exit 0; } { # Gray codes my $path = Math::PlanePath::ComplexPlus->new; # { # my $n = $path->xy_to_n(0,-1); # print "$n\n"; # $n = $path->xy_to_n(0,1); # print "$n\n"; # } foreach my $y (reverse -5 .. 5) { foreach my $x (-5 .. 5) { my $n = $path->xy_to_n($x,$y); if (defined $n) { $n = sprintf '%b', Gray($n); } else { $n = ''; } printf " %8s", $n; } print "\n"; } exit 0; } sub Gray { my ($n) = @_; require Math::PlanePath::GrayCode; my $digits = [ digit_split_lowtohigh($n,2) ]; Math::PlanePath::GrayCode::_digits_to_gray_reflected($digits,2); return digit_join_lowtohigh($digits,2); } CHECK { Gray(0) == 0 or die; } sub CountOneBits { my ($n) = @_; my $count = 0; for ( ; $n; $n>>=1) { $count += ($n & 1); } return $count; } Math-PlanePath-129/devel/vertical.pl0000644000175000017500000000514111520123441015145 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use POSIX 'fmod'; use Math::BigRat; use Math::Prime::XS; #use Smart::Comments; use constant PHI => (1 + sqrt(5)) / 2; # (3n-1)*n/2 pentagonal # (3n+1)*n/2 second pentagonal # http://www.research.att.com/~njas/sequences/A005449 # sum of n consecutive numbers >= n (n+1)+(n+2)+...+(n+n) # triangular+square (n+1)*n/2 + n*n # (3n+1)*n/2-2 = offset (3n+7)*n/2 # http://www.research.att.com/~njas/sequences/A140090 # sum n+1 to n+n-3 or some such # (3n+1)*n/2 # (3n+1)*n/2 - 1 # (3n+1)*n/2 - 2 sub three { my ($i) = @_; return (3*$i+1)*$i/2 - 2; } sub is_perfect_square { my ($n) = @_; $n = sqrt($n); return ($n == int($n)); } { my $prev_k = 0; foreach my $k (0 .. 1000) { my $sq = 24*$k+1; if (is_perfect_square($sq)) { printf "%4d %+4d %4d %4d\n", $k, $k-$prev_k, $k%24, $sq; $prev_k = $k; } } exit 0; } { # i==0mod4 or 1mod4 always even # foreach my $k (0 .. 100) { my $i = 4*$k + 2; my $n = three($i); my $factors = factorize($n); printf "%4d %4d %s\n", $i,$n,$factors; # unless ($factors =~ /\Q*/) { # die; # } } exit 0; } { local $, = ','; print map {three($_)} 0..20; exit 0; } { my $a = Math::BigRat->new('3/2'); my $b = Math::BigRat->new('1/2'); my $c = Math::BigRat->new('-2'); my $x = -$b; my $sq = ($b*$b-4*$a*$c); my $y = $sq; $y->bsqrt; print "$x $sq $y\n"; my $r1 = ($x + $y)/(2*$a); my $r2 = ($x - $y)/(2*$a); print "$r1 $r2\n"; exit 0; } { foreach my $i (5 .. 500) { my $n = three($i); if (Math::Prime::XS::is_prime($n)) { say "$i $n"; last; } } exit 0; } sub factorize { my ($n) = @_; my @factors; foreach my $f (2 .. int(sqrt($n)+1)) { while (($n % $f) == 0) { push @factors, $f; ### $n $n /= $f; } } if ($n != 1) { push @factors, $n; } return join ('*',@factors); } exit 0; Math-PlanePath-129/devel/cellular-rule-xpm.pl0000644000175000017500000000341211646222723016721 0ustar gggg#!/usr/bin/perl -w # Copyright 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use strict; use Image::Base::PNGwriter; use List::Util 'min', 'max'; # uncomment this to run the ### lines #use Devel::Comments; my $white = '#FFFFFF'; $white = 'white'; my $class = 'Image::Base::PNGwriter'; $class = 'Image::Xpm'; eval "require $class; 1" or die; my $rule = 30; my @table = map {($rule & (1<<$_)) ? 1 : 0} 0 .. 7; print join(',',@table),"\n"; my $height = 500; my $width = 2*$height; my $image = $class->new (-width => $width, -height => $height); $image->rectangle(0,0,$width-1,$height-1, 'black', 1); # $image->xy($size-2,0,$white); # right $image->xy(int(($width-1)/2),0,$white); # centre foreach my $y (1..$height-1) { foreach my $x (0 .. $width-1) { my $p = 0; foreach my $o (-1,0,1) { $p *= 2; ### x: $x+$o ### y: $y-1 ### cell: $image->xy($x+$o,$y-1) ### cell: $image->xy($x+$o,$y-1) eq $white $p += ($image->xy(min(max($x+$o,0),$width-1),$y-1) eq $white); } ### $p if ($table[$p]) { $image->xy($x,$y,'white'); } } } $image->save('/tmp/x'); system ('xzgv /tmp/x'); exit 0; # vec() Math-PlanePath-129/devel/gosper-replicate.pl0000644000175000017500000000762513137306320016617 0ustar gggg#!/usr/bin/perl -w # Copyright 2016, 2017 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.006; use strict; use warnings; use Math::BaseCnv 'cnv'; use Math::Libm 'M_PI', 'hypot'; use Math::PlanePath::GosperReplicate; $|=1; # uncomment this to run the ### lines # use Smart::Comments; { # islands convex hull # not in OEIS: 6,84,726,5448,39162,276948 # not in OEIS: 1,14,121,908,6527,46158 # [1,14,121,908,6527,46158] - [1, 8, 57, 404, 2839, 19884] # not in OEIS: 0, 6, 64, 504, 3688, 26274 # v=[1,14,121,908,6527,46158] # for(i=2,#v,print(v[i]-7*v[i-1])) # 2*2839 + 2*404 - 6527 require Math::Geometry::Planar; my $path = Math::PlanePath::GosperReplicate->new; my @values; foreach my $k (1 .. 6) { my ($n_lo,$n_hi) = $path->level_to_n_range($k); my $points = [ map{[$path->n_to_xy($_)]} $n_lo .. $n_hi ]; my $planar = Math::Geometry::Planar->new; $planar->points($points); if (@$points > 4) { $planar = $planar->convexhull2; $points = $planar->points; } my $area = $planar->area / 6; my $whole_area = 7**$k; my $f = $area / $whole_area; my $num_points = scalar(@$points); print "k=$k hull points $num_points area $area cf $whole_area ratio $f\n"; push @values,$area; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, verbose=>1); exit 0; } # GP-DEFINE nearly_equal_epsilon = 1e-15; # GP-DEFINE nearly_equal(x,y, epsilon=nearly_equal_epsilon) = \ # GP-DEFINE abs(x-y) < epsilon; # GP-DEFINE b_angle = arg(b) # not in OEIS: 0.333473172251832115336090 # GP-DEFINE b_angle_degrees = b_angle * 180/Pi # not in OEIS: 19.1066053508690943945174 # GP-Test nearly_equal( b_angle, atan(sqrt3/5) ) # GP-Test b_angle_degrees > 19.10 # GP-Test b_angle_degrees < 19.10+1/10^2 { require Math::BigInt; Math::BigInt->import(try => 'GMP'); my $path = Math::PlanePath::GosperReplicate->new; my $n_max = Math::BigInt->new(0); my $pow = Math::BigInt->new(1); foreach my $k (0 .. 50) { my $m_max = 0; my $new_n_max = 0; foreach my $d (1 .. 6) { my $try_n = $n_max + $pow*$d; my ($x,$y) = $path->n_to_xy($try_n); my $m; $m = $x; $m = $x+$y; # 30 deg ### $try_n ### $m if ($m > $m_max) { $m_max = $m; $new_n_max = $try_n; } } $n_max = $new_n_max; my $n7 = cnv($n_max,10,7); my $m7 = cnv($m_max,10,7); print "k=$k $n_max\n $n7\n X=$m_max $m7\n"; $pow *= 7; } exit 0; } { # X maximum in level by points # k=0 0 0 0 0 # k=1 1 1 2 2 # k=2 8 11 7 10 # k=3 302 611 20 26 # k=4 2360 6611 57 111 # k=5 16766 66611 151 304 # k=6 100801 566611 387 1062 # k=7 689046 5566611 1070 3056 # k=8 4806761 55566611 2833 11155 my $path = Math::PlanePath::GosperReplicate->new; foreach my $k (0 .. 10) { my ($n_lo,$n_hi) = $path->level_to_n_range($k); my $m_max = 0; my $n_max = 0; foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); my $m; $m = -$x+$y; $m = $x; $m = $x+3*$y; # 60 deg $m = $x+$y; # 30 deg if ($m > $m_max) { $m_max = $m; $n_max = $n; } } my $n7 = cnv($n_max,10,7); my $m7 = cnv($m_max,10,7); print "k=$k $n_max $n7 $m_max $m7\n"; } exit 0; } Math-PlanePath-129/devel/lib/0002755000175000017500000000000014001441522013546 5ustar ggggMath-PlanePath-129/devel/lib/MyGraphs.pm0000644000175000017500000042775313774527450015703 0ustar gggg# Copyright 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Hyde # # This file is shared by a couple of distributions. # # This file 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, or (at your option) any later # version. # # This file 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 file. If not, see . # (Some tests in gmaker xt/MyGraphs-various.t) package MyGraphs; use 5.010; use strict; use warnings; use Carp 'croak'; use List::Util 'min','max','sum','minstr'; use Scalar::Util 'blessed'; use File::Spec; use File::HomeDir; use Math::Trig (); use POSIX 'ceil'; my @ipc; use base 'Exporter'; use vars '@EXPORT_OK'; @EXPORT_OK = ('Graph_Easy_view', 'Graph_Easy_edges_string', 'Graph_Easy_edge_list_string', 'edge_aref_to_Graph_Easy', 'Graph_Easy_line_graph', 'Graph_Easy_print_adjacency_matrix', 'edge_aref_to_Graph', 'Graph_view', 'Graph_tree_print','Graph_xy_print', 'Graph_print_tikz', 'Graph_branch_reduce', 'Graph_is_regular', 'Graph_is_isomorphic', 'Graph_is_subgraph', 'Graph_is_induced_subgraph', 'Graph_from_edge_aref', 'Graph_line_graph', 'Graph_is_line_graph_by_Beineke', 'Graph_Wiener_index','Graph_Wiener_part_at_vertex', 'Graph_terminal_Wiener_index','Graph_terminal_Wiener_part_at_vertex', 'Graph_to_sparse6_str', 'Graph_to_graph6_str', 'Graph_from_graph6_str', 'Graph_triangle_is_even', 'Graph_triangle_search','Graph_find_triangle', 'Graph_has_triangle','Graph_triangle_count', 'Graph_claw_search', 'Graph_has_claw','Graph_claw_count','Graph_claw_count', 'Graph_clique_number', 'Graph_width_list', 'Graph_is_cycles', 'Graph_find_all_cycles', 'Graph_find_all_4cycles', 'Graph_is_hanging_cycle', 'Graph_delete_hanging_cycles', 'Graph_girth', # smallest cycle 'Graph_is_Hamiltonian', 'Graph_rename_vertex','Graph_pad_degree', 'Graph_eccentricity_path', 'Graph_tree_centre_vertices', 'Graph_tree_domnum', 'Graph_tree_domsets_count','Graph_tree_minimal_domsets_count', 'Graph_is_domset','Graph_is_minimal_domset', 'Graph_domset_is_minimal', 'Graph_minimal_domsets_count_by_pred', 'Graph_is_total_domset', 'edge_aref_num_vertices', 'edge_aref_is_subgraph', 'edge_aref_is_induced_subgraph', 'edge_aref_degrees_allow_subgraph', 'edge_aref_string', 'edge_aref_to_parent_aref', 'edge_aref_degrees', 'edge_aref_degrees_distinct', 'edge_aref_is_regular', 'parent_aref_to_edge_aref', 'parent_aref_to_Graph_Easy', 'graph6_str_to_canonical', 'graph6_view', 'make_tree_iterator_edge_aref', 'make_graph_iterator_edge_aref', 'hog_searches_html','hog_grep', 'postscript_view_file', 'Graph_to_GraphViz2', 'Graph_set_xy_points', 'Graph_subtree_depth', 'Graph_subtree_children', 'Graph_star_replacement','Graph_cycle_replacement', ); # uncomment this to run the ### lines # use Smart::Comments; #------------------------------------------------------------------------------ # Graph::Easy extras # $filename is a postscript file # synchronous => 1, wait for viewer to exit before returning. sub postscript_view_file { my ($filename, %options) = @_; require IPC::Run; my @command = ('gv', '--scale=.7', $filename); if ($options{'synchronous'}) { IPC::Run::run(\@command); } else { push @ipc, IPC::Run::start(\@command,'&'); } } END { foreach my $h (@ipc) { $h->finish; } } # $graph is a Graph::Easy object, show it graphically # synchronous => 1, wait for viewer to exit before returning. sub Graph_Easy_view { my ($graph, %options) = @_; require File::Temp; my $dot = File::Temp->new (UNLINK => 0, SUFFIX => '.dot'); my $dot_filename = $dot->filename; # per Graph::Easy::As_graphviz print $dot $graph->as_graphviz; graphviz_view_file($dot_filename, %options); } # $str is DOT format graph sub graphviz_view { my ($str) = @_; graphviz_view_file(\$str); } # $filename is a filename string or a scalar ref to string contents sub graphviz_view_file { my ($filename, %options) = @_; require File::Temp; my $ps = File::Temp->new (UNLINK => 0, SUFFIX => '.ps'); my $ps_filename = $ps->filename; ### $ps_filename require IPC::Run; IPC::Run::run(['dot','-Tps', ], '<',$filename, '>',$ps_filename); # ['neato','-Tps','-s2'] postscript_view_file ($ps->filename, %options); } sub Graph_Easy_branch_reduce { my ($graph) = @_; foreach my $node ($graph->nodes) { my @predecessors = $node->predecessors(); my @successors = $node->successors(); if (@predecessors == 1 && @successors == 1) { $graph->del_node($node); $graph->add_edge($predecessors[0], $successors[0]); } } } sub Graph_Easy_leaf_reduce { my ($graph) = @_; # print "$graph"; foreach my $node ($graph->nodes) { my @successors = $node->successors; @successors == 2 || next; if (Graph_Easy_Node_is_leaf($successors[0]) && Graph_Easy_Node_is_leaf($successors[1])) { $graph->del_node($successors[1]); } } } sub Graph_Easy_Node_is_leaf { my ($node) = @_; my @successors = $node->successors; return (@successors == 0); } sub Graph_Easy_edges_string { my ($easy) = @_; Graph_Easy_edge_list_string($easy->edges); } sub Graph_Easy_edge_list_string { my @edges = map { [ $_->from->name, $_->to->name ] } @_; @edges = sort { $a->[0] cmp $b->[0] || $a->[1] cmp $b->[1] } @edges; return join(' ',map {join('-',@$_)} @edges); } sub Graph_Easy_print_adjacency_matrix { my ($easy) = @_; my $has_edge_either = ($easy->is_directed ? \&Graph::Easy::As_graph6::_has_edge_either_directed : 'has_edge'); my @vertices = $easy->sorted_nodes('name'); foreach my $from (0 .. $#vertices) { foreach my $to (0 .. $#vertices) { print $easy->$has_edge_either($vertices[$from], $vertices[$to]) ? ' 1' : ' 0'; } print "\n"; } } #------------------------------------------------------------------------------ # Graph.pm extras sub Graph_sorted_vertices { my ($graph) = @_; my @vertices = $graph->vertices; my $func = cmp_func(@vertices); return sort $func @vertices; } sub Graph_print_adjacency_matrix { my ($graph, $fh) = @_; $fh //= \*STDOUT; my @vertices = Graph_sorted_vertices($graph); print $fh "[" or die; foreach my $from (0 .. $#vertices) { foreach my $to (0 .. $#vertices) { print$fh $graph->has_edge($vertices[$from], $vertices[$to]) ? '1' : '0', $to==$#vertices ? ($from==$#vertices ? ']' : ';') : ',' or die; } # print "\n"; } } sub Graph_loopcount { my ($graph) = @_; my $loopcount = 0; foreach my $edge ($graph->edges) { $loopcount += ($edge->[0] eq $edge->[1]); } return $loopcount; } # modify $graph to branch reduce, meaning all degree-2 vertices are # contracted out by deleting and joining their neighbours with an edge sub Graph_branch_reduce { my ($graph) = @_; ### Graph_branch_reduce() ... my $more; do { $more = 0; foreach my $v ($graph->vertices) { my @neighbours = $graph->neighbours($v); if (@neighbours == 2) { ### delete: $v $graph->delete_vertex($v); $graph->add_edge($neighbours[0], $neighbours[1]); $more = 1; } } } while ($more); } # return a list of the immediate children of vertex $v # children are vertices compared numerically $child >= $v sub Graph_vertex_children { my ($graph, $v, %options) = @_; my $cmp = $options{'cmp'} || cmp_func($graph->vertices); my @children = grep {$cmp->($_,$v)>0} $graph->neighbours($v); return @children; } sub Graph_vertex_num_children { my ($graph, $v, %options) = @_; my @children = Graph_vertex_children($graph,$v, %options); return scalar(@children); } # $graph is a Graph.pm, show it graphically # synchronous => 1, wait for viewer to exit before returning. # xy => 1, treat each vertex name as x,y coordinates sub Graph_view { my ($graph, %options) = @_; ### Graph_view(): %options my @vertices = $graph->vertices; my $is_xy = ($options{'is_xy'} || $graph->get_graph_attribute('vertex_name_type_xy') || $graph->get_graph_attribute('vertex_name_type_xy_triangular') || $graph->get_graph_attribute('xy') || (@vertices && defined $graph->get_vertex_attribute($vertices[0],'xy')) || do { my $type = $graph->get_graph_attribute('vertex_name_type'); defined $type && $type =~ /^xy/ } || do { my ($v) = $graph->vertices; defined $v && defined($graph->get_vertex_attribute($v,'x')) }); ### $is_xy if ($is_xy) { my $graphviz2 = Graph_to_GraphViz2($graph, %options); GraphViz2_view($graphviz2, driver=>'neato', %options); return; } { my $graphviz2 = Graph_to_GraphViz2($graph, %options); GraphViz2_view($graphviz2, %options); return; } ### Convert ... require Graph::Convert; my $easy = Graph::Convert->as_graph_easy($graph); ### flow: $graph->get_graph_attribute('flow') ### flow: $graph->get_graph_attribute('flow') // 'south' $easy->set_attribute('flow', $graph->get_graph_attribute('flow') // 'south'); if (defined(my $name = $graph->get_graph_attribute('name'))) { $easy->set_attribute('label',$name); } Graph_Easy_view($easy, %options); # print "Graph: ", $graph->is_directed ? "directed\n" : "undirected\n"; # print "Easy: ", $easy->is_directed ? "directed\n" : "undirected\n"; } sub Graph_vertex_parent { my ($graph, $v, %options) = @_; my $cmp = $options{'cmp'} || cmp_func($graph->vertices); my @parents = grep {$cmp->($_,$v)<=0} $graph->neighbours($v); return $parents[0]; } sub Graph_vertex_depth { my ($graph, $v) = @_; my $depth = 0; while ($v = Graph_vertex_parent($graph,$v)) { $depth++; } return $depth; } sub Graph_tree_root { my ($graph) = @_; if (defined (my $root = $graph->get_graph_attribute('root'))) { return $root; } if (defined(my $root = $graph->get_graph_attribute('root_vertex'))) { return $root; } foreach my $v ($graph->vertices) { if ($v =~ /^0+$/) { return $v; # 0 or 00 or 000 etc } } if ($graph->has_vertex(1)) { return 1; } croak "No tree root found"; } # Note: This depends on the Graph_vertex_children() vertex numbering. sub Graph_tree_height { my ($graph, $root) = @_; $root //= Graph_tree_root($graph); ### $root my $height = 0; my @pending = ($root); my $depth = 0; for (;;) { @pending = map {Graph_vertex_children($graph,$_)} @pending; last unless @pending; $depth++; } return $depth; } # Return a list of arrayrefs [$v,$v,...] which are the vertices at # successive depths of tree $graph. The first arrayref contains the tree root. # Within each row vertices are sorted first by parent, then by given cmp. sub Graph_vertices_by_depth { my ($graph, %options) = @_; my $root = $options{'root'} // Graph_tree_root($graph); my @ret = ([$root]); my $cmp = $options{'cmp'} || cmp_func($graph->vertices); my %seen = ($root => 1); for (;;) { my @row = map { sort $cmp grep {!$seen{$_}++} $graph->neighbours($_) } @{$ret[-1]}; @row || last; push @ret, \@row; } return @ret; } # return a list of all the descendents of vertex $v # children are vertices compared numerically $child >= $v sub Graph_tree_descendents { my ($graph, $v) = @_; my @ret; my @pending = ($v); while (@pending) { @pending = map {Graph_vertex_children($graph,$_)} @pending; push @ret, @pending; } return @ret; } sub cmp_numeric ($$) { my ($a, $b) = @_; return $a <=> $b; } sub cmp_alphabetic ($$) { my ($a, $b) = @_; return $a cmp $b; } sub cmp_func { return (all_looks_like_number(@_) ? \&cmp_numeric : \&cmp_alphabetic); } sub Graph_tree_print { my ($graph, %options) = @_; ### Graph_tree_print() ... my $flow = ($options{'flow'} // 'down'); my $hat = '^'; my $slash = '/'; my $backslash = '\\'; if ($flow eq 'up') { $hat = 'v'; $slash = '\\'; $backslash = '/'; } { # by successive adjustment my $gap = 2; my $sibling_gap = 1; my $cmp = $options{'cmp'} || cmp_func($graph->vertices); ### numeric: $cmp==\&cmp_numeric my @vertices = $graph->vertices; my @vertices_by_depth = Graph_vertices_by_depth($graph, cmp => $cmp); ### @vertices_by_depth my %column; my %children; foreach my $v (@vertices) { $children{$v} = [ sort $cmp Graph_vertex_children($graph,$v,cmp=>$cmp) ]; $column{$v} = 0; ### children: "$v children ".join(',',@{$children{$v}}) } my $are_siblings = sub { my ($v1,$v2) = @_; my $p1 = Graph_vertex_parent($graph,$v1, cmp=>$cmp) // return 0; my $p2 = Graph_vertex_parent($graph,$v2, cmp=>$cmp) // return 0; return $p1 eq $p2; }; OUTER: for (my $limit = 0; $limit < 50; $limit++) { # avoid overlaps within row foreach my $depth (0 .. $#vertices_by_depth) { my $aref = $vertices_by_depth[$depth]; foreach my $i (1 .. $#$aref) { my $v1 = $aref->[$i-1]; my $v2 = $aref->[$i]; my $c = $column{$v1} + length($v1) + ($are_siblings->($v1,$v2) ? $sibling_gap : $gap); if ($column{$v2} < $c) { ### overlap in row: "depth=$depth $v2 to column $c" $column{$v2} = $c; next OUTER; } } } # parent half-way along children, # or children moved up if parent further along foreach my $p (@vertices) { my $children_aref = $children{$p}; next unless @$children_aref; my $min = $column{$children_aref->[0]}; my $max = $column{$children_aref->[-1]} + length($children_aref->[-1]); my $c = int(($max + $min - length($p))/2); if ($column{$p} < $c) { ### parent: "parent p=$p move up to middle $c ($min to $max)" $column{$p} = $c; next OUTER; } if ($column{$p} > $c) { my $v = $children_aref->[0]; $column{$v}++; ### parent: "parent $p at $column{$v} > mid $c, advance first child c=$v (".join(',',@$children_aref).") to column $column{$v}" next OUTER; } } # leading leaf child moves up to its sibling foreach my $parent (@vertices) { my $children_aref = $children{$parent}; my $v1 = $children_aref->[0] // next; my $v2 = $children_aref->[1] // next; next if @{$children{$v1}}; # want $v1 leaf my $c = $column{$v2} - length($v1) - $sibling_gap; if ($column{$v1} < $c) { $column{$v1} = $c; next OUTER; } } last; } my $total_column = max(map {$column{$_}+length($_)} @vertices) + 3; my @lines; foreach my $depth (0 .. $#vertices_by_depth) { my $aref = $vertices_by_depth[$depth]; my $c = 0; my $line = ''; foreach my $v (@$aref) { $column{$v} ||= 0; while ($c < ($column{$v}||0)) { $line .= " "; $c++; } $line .= $v . " "; $c += length($v)+1; } while ($c < $total_column) { $line .= " "; $c++; } my $count = @$aref; $line .= "count $count\n"; push @lines, $line; $line = ''; $c = 0; if ($depth < $#vertices_by_depth) { my @lines; foreach my $v (@$aref) { my $children_aref = $children{$v}; next unless @$children_aref; my $min = $column{$children_aref->[0]}; my $max = $column{$children_aref->[-1]} + length($children_aref->[-1]); if (@$children_aref > 1) { if (length($children_aref->[0]) > 1) { $min++; } if (length($children_aref->[-1]) > 1) { $max--; } } my $mid = int($column{$v} + length($v)/2); while ($c < $min) { $line .= ' '; $c++; } # while ($c < $max) { print($c == $mid ? '|' : '_'); $c++; } while ($c < $max) { $line .= ($c == $mid && @$children_aref != 2 ? '|' : @$children_aref == 1 ? ' ' : $c == $min ? $slash : $c == $max-1 ? $backslash : $c == $mid ? ($max-$min <=3 && $flow eq 'up' ? ' ' : $hat) : '-'); $c++; } } } $line .= "\n"; push @lines, $line; } if ($flow eq 'up') { @lines = reverse @lines; } print @lines; return; } { my @vertices_by_depth = Graph_vertices_by_depth($graph, cmp => $options{'cmp'}); ### @vertices_by_depth my @column; foreach my $aref (reverse @vertices_by_depth) { my $c = 0; foreach my $v (@$aref) { my @children = Graph_vertex_children($graph,$v); if (@children) { ### vertex: "$v children @children" $c = max($c, ceil( sum(map {$column[$_] + (length($_)+1)/2} @children) / scalar(@children) - (length($v)+1)/2 )); } $column[$v] = $c; $c += length($v) + 1; $c = max($c, map {$column[$_] + length($_)+1} Graph_tree_descendents($graph, $v)); } } my $total_column = max(map {($column[$_]||0)+length($_)} 0 .. $#column) + 3; foreach my $aref (@vertices_by_depth) { my $c = 0; ### columns: map {$column[$_]} @$aref foreach my $v (@$aref) { while ($c < $column[$v]) { print " "; $c++; } print $v," "; $c += length($v)+1; } while ($c < $total_column) { print " "; $c++; } my $count = @$aref; print "count $count\n"; } return; } } # use Smart::Comments; sub Graph_tree_layout { my ($graph, %options) = @_; ### Graph_tree_layout ... my $v = $options{'v'} // Graph_tree_root($graph); my $x = $options{'x'} || 0; my $y = $options{'y'} || 0; my $order = $options{'order'} || ''; my $align = $options{'align'} || ''; my $filled = $options{'filled'} // []; my @children = Graph_vertex_children($graph,$v); my @heights = map {Graph_tree_height($graph,$_)} @children; my @order; if ($order eq 'name') { @order = sort {$graph->get_vertex_attribute($children[$a],'name') cmp $graph->get_vertex_attribute($children[$b],'name')} 0 .. $#children; } else { @order = sort {$heights[$b] <=> $heights[$a]} 0 .. $#children; } my $h = (@children ? $heights[$order[0]]+1 : 0); ### $h Y: for (;;) { foreach my $i (0 .. $h) { if ($filled->[$x+$i]->[$y]) { $filled->[$x]->[$y] = 1; $y++; next Y; } } last; } ### place: "$v at $x,$y" Graph_set_xy_points($graph, $v => [$x,-$y]); $filled->[$x]->[$y] = 1; foreach my $i (@order) { Graph_tree_layout($graph, v=>$children[$i], x=>$x+1, y=>$y++, filled => $filled, order => $order, align => $align); } } # no Smart::Comments; #------------------------------------------------------------------------------ # vertices are coordinate strings "$x,$y" and edges along a square grid # print an ascii form of the graph # sub Graph_xy_print { my ($graph) = @_; my @vertices = $graph->vertices; my @points = map {[split /,/]} @vertices; my @x = map {$_->[0]} @points; my @y = map {$_->[1]} @points; my $x_min = (@x ? min(@x) - 1 : 0); my $x_max = (@x ? max(@x) + 1 : 0); my $y_min = (@y ? min(@y) - 1 : 0); my $y_max = (@y ? max(@y) + 1 : 0); foreach my $y (reverse $y_min .. $y_max) { printf "%3s ", ''; foreach my $x ($x_min .. $x_max) { my $from = "$x,$y"; # vertical edge to above print $graph->has_edge($from, $x.",".($y+1)) ? "| " : " "; } print "\n"; printf "%3d ", $y; foreach my $x ($x_min .. $x_max) { my $from = "$x,$y"; # horizontal edge to next print $graph->has_vertex($from) ? "*" : " "; print $graph->has_edge($from, ($x+1).",".$y) ? "---" : " "; } print "\n"; } print " "; foreach my $x ($x_min .. $x_max) { printf "%4d", $x; } print "\n"; } sub Graph_xy_print_triangular { my ($graph) = @_; my @vertices = $graph->vertices; my @points = map {[split /,/]} @vertices; my @x = map {$_->[0]} @points; my @y = map {$_->[1]} @points; my $x_min = (@x ? min(@x) - 1 : 0); my $x_max = (@x ? max(@x) + 1 : 0); my $y_min = (@y ? min(@y) - 1 : 0); my $y_max = (@y ? max(@y) + 1 : 0); foreach my $y (reverse $y_min .. $y_max) { printf "%3s ", ''; foreach my $x ($x_min .. $x_max) { my $from = "$x,$y"; # vertical edge to above print $graph->has_edge($from, ($x ).",".($y+1)) ? "|" : " "; print $graph->has_edge(($x).",".($y+1), ($x+1).",".($y)) ? "\\" : $graph->has_edge($from, ($x+1).",".($y+1)) ? "/" : " "; } print "\n"; printf "%3d ", $y; foreach my $x ($x_min .. $x_max) { my $from = "$x,$y"; # horizontal edge to next print $graph->has_vertex($from) ? "*" : $graph->has_edge(($x-1).",".$y, ($x+1).",".$y) ? "-" : " "; print $graph->has_edge(($x-1).",".$y, ($x+1).",".$y) || $graph->has_edge($from, ($x+1).",".$y) || $graph->has_edge($from, ($x+2).",".$y) ? "-" : " "; } print "\n"; } print " ", ($x_min&1 ? ' ' : ''); for (my $x = $x_min+($x_min&1); $x <= $x_max; $x+=2) { printf "%4d", $x; } print "\n"; } #------------------------------------------------------------------------------ our $HOG_directory = File::Spec->catdir(File::HomeDir->my_home, 'HOG'); # $coderef = make_tree_iterator_edge_aref() # Return a function which iterates through trees in the form of edge arrayrefs. # Each call to the function is # $edge_aref = $coderef->(); # returning an arrayref [ [1,2], [2,3], ... ] of a tree, or undef at end of # iteration. # # Optional key/value parameters are # num_vertices_min => $integer \ min and max vertices in the trees # num_vertices_max => $integer / # degree_list => arrayref [ 1, 2, 4 ] # degree_max => $integer # degree_predicate => $coderef # sub make_tree_iterator_edge_aref { my %option = @_; require Graph::Graph6; my $degree_predicate_aref; if (defined (my $degree_list = $option{'degree_list'})) { my @degree_predicate_array; foreach my $degree (@$degree_list) { $degree_predicate_array[$degree] = 1; } $degree_predicate_aref = \@degree_predicate_array; } elsif (defined (my $degree_max = $option{'degree_max'})) { my @degree_predicate_array; foreach my $degree (1 .. $degree_max) { $degree_predicate_array[$degree] = 1; } $degree_predicate_aref = \@degree_predicate_array; } my $num_vertices= ($option{'num_vertices'} // $option{'num_vertices_min'} // 1); $num_vertices = max(1, $num_vertices); # no trees of 0 vertices my $num_vertices_max = ($option{'num_vertices_max'} // $num_vertices); $num_vertices--; my $fh; return sub { for (;;) { if (! $fh) { if (defined $num_vertices_max && $num_vertices >= $num_vertices_max) { return; } $num_vertices++; ### open: $num_vertices my $filename = File::Spec->catfile($HOG_directory, sprintf('trees%02d.g6', $num_vertices)); open $fh, '<', $filename or die "Cannot open $filename: $!"; } my @edges; unless (Graph::Graph6::read_graph(fh => $fh, num_vertices_ref => \my $file_num_vertices, edge_aref => \@edges)) { ### EOF ... close $fh or die; undef $fh; next; } my $edge_aref = \@edges; if ($degree_predicate_aref && ! edge_aref_degree_check($edge_aref, $degree_predicate_aref)) { ### skip for degree_max ... next; } return $edge_aref; } }; } # Return an iterator $itfunc to be called as # $edge_aref = $itfunc->() # which iterates through all connected graphs. # Parameters: # num_vertices => integer # num_vertices_min => integer # num_vertices_max => integer # num_edges_min => integer # num_edges_max => integer # connected => bool, default true # sub make_graph_iterator_edge_aref { my %option = @_; require Graph::Graph6; my $num_vertices = ($option{'num_vertices'} // $option{'num_vertices_min'} // 1); my $num_vertices_max = ($option{'num_vertices_max'} // $option{'num_vertices'}); my $num_edges_min = $option{'num_edges_min'}; my $num_edges_max = $option{'num_edges_max'}; my @geng_edges_option; if ($option{'verbose'}) { push @geng_edges_option, '-v'; } else { push @geng_edges_option, '-q'; } if ($option{'connected'} // 1) { push @geng_edges_option, '-c'; } if (defined $num_edges_max || defined $num_edges_min) { if (! defined $num_edges_min) { $num_edges_min = 0; } if (! defined $num_edges_max) { $num_edges_max = '#'; } push @geng_edges_option, "$num_edges_min:$num_edges_max"; } ### @geng_edges_option $num_vertices--; require IPC::Run; my $fh; return sub { for (;;) { if (! $fh) { if (defined $num_vertices_max && $num_vertices >= $num_vertices_max) { return; } $num_vertices++; ### open: $num_vertices IPC::Run::start(['nauty-geng', # '-l', # canonical $num_vertices, @geng_edges_option], '<', File::Spec->devnull, '|', ['sort'], '>pipe', \*OUT); $fh = \*OUT; } my @edges; unless (Graph::Graph6::read_graph(fh => $fh, num_vertices_ref => \my $file_num_vertices, edge_aref => \@edges)) { ### EOF ... close $fh or die; undef $fh; next; } my $edge_aref = \@edges; return $edge_aref; } }; } # Return true if the degrees of the nodes in $edge_aref all have # arrayref $degree_predicate_aref->[$degree] true. # sub edge_aref_degree_check { my ($edge_aref, $degree_predicate_aref) = @_; my @vertex_degree; foreach my $edge (@$edge_aref) { my ($from, $to) = @$edge; $vertex_degree[$from]++; $vertex_degree[$to]++; } foreach my $degree (@vertex_degree) { if (! $degree_predicate_aref->[$degree]) { return 0; } } return 1; } # $edge_aref is an arrayref [ [from,to], [from,to], ... ] # where each vertex is integer 0 upwards # Return a list (degree, degree, ...) of degree of each vertex sub edge_aref_degrees { my ($edge_aref) = @_; ### edge_aref_degrees: $edge_aref my @vertex_degree; foreach my $edge (@$edge_aref) { my ($from, $to) = @$edge; $vertex_degree[$from]++; $vertex_degree[$to]++; } return map {$_//0} @vertex_degree; } # $edge_aref is an arrayref [ [from,to], [from,to], ... ] # where each vertex is integer 0 upwards # Return a list (degree, degree, ...) of distinct vertex degrees which occur # in the graph. sub edge_aref_degrees_distinct { my ($edge_aref) = @_; my @vertex_degree = edge_aref_degrees($edge_aref); my %seen; @vertex_degree = grep {! $seen{$_}++} @vertex_degree; return sort {$a<=>$b} @vertex_degree; } # Trees by search. # { # my @parent = (undef, -1); # my $v = 1; # for (;;) { # my $p = ++$parent[$v]; # ### at: "$v consider new parent $p" # if ($p >= $v) { # ### backtrack ... # $v--; # if ($v < 1) { last; } # $p = $parent[$v]; # unparent this preceding v # $num_children[$p]--; # next; # } # # if ($num_children[$p] >= ($p==0 ? 4 : 3)) { # next; # } # # $num_vertices = $v; # $process_tree->(); # # if ($v < $num_vertices_limit) { # # descend # $num_children[$p]++; # # $num_children[$p] == grep {$_==$p} @parent[1..$v] or die; # $num_children[$v] = 0; # $v++; # $parent[$v] = -1; # $num_vertices = $v; # } # } # } # Tree iterator by parent. # { # @parent = (undef); # @num_children = (0); # my $v = 0; # for (;;) { # $num_children[$v]++; # my $new_v = $v + $num_children[$v]; # ### at: "$v consider new children $num_children[$v]" # # if ($num_children[$v] > ($v==0 ? 4 : 3) # || $new_v > $num_vertices_limit) { # ### backtrack ... # $v = $parent[$v] // last; # next; # } # # # add children # foreach my $i (1 .. $num_children[$v]) { # my $c = $v + $i; # $parent[$c] = $v; # $num_children[$c] = 0; # } # $v = $new_v-1; # $num_vertices = $v; # $process_tree->(); # } # } #------------------------------------------------------------------------------ use constant::defer hog_directory => sub { require File::Spec; require File::HomeDir; File::Spec->catdir(File::HomeDir->my_home, 'HOG'); }; use constant::defer hog_all_filename => sub { require File::Spec; File::Spec->catdir(hog_directory, 'all.g6'); }; use constant::defer hog_mmap_ref => sub { require File::Map; my $mmap; File::Map::map_file ($mmap, hog_all_filename()); return \$mmap; }; # $str is a string of graph6 in canonical labelling. # Return true if it is in the House of Graphs, based on grepping the all.g6 # file (hog_all_filename()). sub hog_grep { my ($str) = @_; require File::Slurp; ### hog_grep(): $str $str =~ s/\n$//; my $mmap_ref = hog_mmap_ref(); if ($$mmap_ref =~ /^\Q$str\E$/m) { $str =~ s/\n+$//g; foreach my $filename (glob(File::Spec->catfile(hog_directory(), 'graph_*.g6'))) { if (defined (my $file_str = File::Slurp::read_file($filename, err_mode=>'quiet'))) { $file_str =~ s/\n+$//g; if ($file_str eq $str) { if ($filename =~ m{graph_([^/]*)\.g6$}) { return $1; } else { return $filename; } } } } return -1; } } # $num is a House of Graphs graph ID number. # Return the local filename for its graph6. # There's no check whether the file actually exists. sub hog_num_to_filename { my ($num) = @_; require File::Spec; File::Spec->catfile(hog_directory(), "graph_$num.g6"); } sub hog_compare { my ($id, $g6_str) = @_; require File::Slurp; my $filename = hog_num_to_filename($id); my $file_str = File::Slurp::read_file($filename); my $canon_g6_str = graph6_str_to_canonical($g6_str); my $canon_file_str = graph6_str_to_canonical($file_str); if ($g6_str ne $file_str) { print "id=$id wrong\n"; print "string $g6_str"; print "file $file_str"; print "canon string $canon_g6_str"; print "canon file $canon_file_str"; croak "wrong"; } } sub hog_id_to_url { my ($id) = @_; # ENHANCE-ME: maybe escape against some bad id string return "https://hog.grinvin.org/ViewGraphInfo.action?id=$id"; } # hog_searches_html($graph,$graph,...) # Create a /tmp/USERNAME/hog-searches.html of forms to search hog for each # $graph. Each $graph can be either Graph.pm or Graph::Easy. # # The hog-searches.html is a bit rough, and requires you select the 0.g6, # 1.g6, etc file to search for. The HOG server expects a file upload, and # don't think can induce a browser to do a file-like POST other than by # selecting a file. Some Perl code POST could do it easily, but the idea is # to present a range of searches and you might only do a few. # sub hog_searches_html { my @graphs = @_; ### hog_searches_html() ... require HTML::Entities; require File::Spec; require File::Temp; require POSIX; my $dir = File::Spec->catdir('/tmp', POSIX::cuserid()); mkdir $dir; my $html_filename = File::Spec->catfile($dir, 'hog-searches.html'); my $hog_url = 'https://hog.grinvin.org'; # $hog_url = 'http://localhost:10000'; # for testing my @names; open my $h, '>', $html_filename or die; print $h <<'HERE'; HERE my %seen_canonical; foreach my $i (0 .. $#graphs) { my $graph = $graphs[$i]; ### graph: "$graph" if (! ref $graph) { ### convert graph6 string ... $graph = Graph_from_graph6_str($graph); } elsif (! blessed($graph)) { ### convert edge_aref ... if (ref $graph) { $graph = edge_aref_to_Graph_Easy($graph); } else { $graph = Graph_from_graph6_str($graph); } } my $png_fh = File::Temp->new; my $png_filename = $png_fh->filename; my $graph6_str; if ($graph->isa('Graph::Easy')) { require Graph::Easy::As_graph6; $graph6_str = $graph->as_graph6; } else { $graph6_str = Graph_to_graph6_str($graph); } my $graph6_size = length $graph6_str; my $num_vertices = $graph->vertices; my $num_edges = $graph->edges; my $name; my $flow = 'south'; my $vertex_name_type; if ($graph->isa('Graph::Easy')) { $name = $graph->get_attribute('label'); # FIXME: custom attributes? # $vertex_name_type = $graph->get_attribute('graph','vertex_name_type'); } else { $name = $graph->get_graph_attribute('name'); $vertex_name_type = $graph->get_graph_attribute('vertex_name_type'); $flow = $graph->get_graph_attribute('flow') // $flow; } $vertex_name_type //= ''; $name //= ''; $names[$i] = $name; my $graph6_canonical = graph6_str_to_canonical($graph6_str); my $canonical = $graph6_canonical; if (length($canonical) > 30) { $canonical = ''; } else { $canonical = "
canonical " . HTML::Entities::encode_entities($canonical); } if (defined(my $prev = $seen_canonical{$graph6_canonical})) { print "g$i $name\n REPEAT g$prev $names[$prev]\n"; print $h "
repeat of $seen_canonical{$graph6_canonical} ", HTML::Entities::encode_entities($names[$prev]), "\n"; } else { $seen_canonical{$graph6_canonical} = $i; } my $got = ''; if (my $num = hog_grep($graph6_canonical)) { my $str = $graph6_canonical; $str =~ s/\n+$//; print "g$i HOG got $str n=$num_vertices", ($num eq '-1' ? '' : " id=$num"), " $name\n"; if ($num eq '-1') { my $filename = HTML::Entities::encode_entities(hog_all_filename()); $got = "
got in $filename\n"; } else { my $url = hog_id_to_url($num); $num = HTML::Entities::encode_entities($num); $got = "
got HOG id $num\n"; } } print $h <<"HERE";

$graph6_size bytes, $num_vertices vertices, $num_edges edges $name$canonical$got HERE if ($num_vertices == 0) { print $h "empty\n"; } print $h <<"HERE";

HERE if ($num_vertices <= 60) { my $is_xy = $graph->isa('Graph') && ($graph->get_graph_attribute('vertex_name_type_xy') || $graph->get_graph_attribute('xy') || do { my $type = $graph->get_graph_attribute('vertex_name_type'); defined $type && $type =~ /^xy/ } || do { my ($v) = sort $graph->vertices; defined $v && defined($graph->get_vertex_attribute($v,'x')) }); if ($is_xy || 1) { ### write with graphviz2 neato ... my $graphviz2 = Graph_to_GraphViz2($graphs[$i]); $graphviz2->run(format => 'png', output_file=>$png_filename, driver => 'neato'); ### dot_input: $graphviz2->dot_input } elsif (1) { ### write with graphviz2 dot ... my $graphviz2 = Graph_to_GraphViz2($graphs[$i]); $graphviz2->run(format => 'png', output_file=>$png_filename); } else { my $easy = $graph; if ($graph->isa('Graph')) { ### Graph num nodes ... my $graph = $graph->copy; foreach my $v ($graph->vertices) { $graph->delete_vertex_attribute($v,'xy'); } require Graph::Convert; $easy = Graph::Convert->as_graph_easy($graph); } # Graph_Easy_blank_labels($easy); foreach my $v (1,0) { if (defined($easy->node($v))) { $easy->set_attribute('root',$v); # for as_graphviz() $easy->set_attribute('flow',$flow); # for as_graphviz() } } ### Graph-Easy num nodes: scalar($easy->nodes) $easy->set_attribute('x-dot-start','1'); my $graphviz = $easy->as_graphviz; # $graphviz =~ s/node \[/node [\n height=.08,\n width=.08,\n fixedsize=1,/; # print $graphviz; require IPC::Run; IPC::Run::run(['dot','-Tpng', ], '<',\$graphviz, '>',$png_filename); # IPC::Run::run(['neato','-Tpng'], '<',\$graphviz, '>',$png_filename); # IPC::Run::run(['fdp','-Tpng'], '<',\$graphviz, '>',$png_filename); # print $easy->as_ascii; } require File::Slurp; my $png = File::Slurp::read_file($png_filename); require URI::data; my $png_uri = URI->new("data:"); $png_uri->data($png); $png_uri->media_type('image/png'); # my = URI::data->new($png,'image/png'); print $h qq{\n}; } } print $h <<'HERE'; HERE close $h or die; print scalar(@graphs)," graphs\n"; print "iceweasel file://$html_filename >/dev/null 2>&1 &\n"; } # blank out all labels of a Graph::Easy sub Graph_Easy_blank_labels { my ($easy) = @_; foreach my $node ($easy->nodes) { $node->set_attribute(label => ' '); } } sub edge_aref_to_Graph_Easy { my ($edge_aref) = @_; ### $edge_aref require Graph::Easy; my $easy = Graph::Easy->new (undirected => 1); foreach my $edge (@$edge_aref) { scalar(@$edge) == 2 or die "bad edge_aref"; my ($from, $to) = @$edge; ($from =~ /^[0-9]+$/ && $to =~ /^[0-9]+$/) or die "bad edge_aref"; $easy->add_edge($from,$to); } return $easy; } sub edge_aref_to_Graph { my ($edge_aref) = @_; require Graph; my $graph = Graph->new (undirected => 1); $graph->add_edges(@$edge_aref); return $graph; } sub edge_aref_string { my ($edge_aref) = @_; return join(',', map{join('-',@$_)} @$edge_aref) . ' ['.scalar(@$edge_aref).' edges]'; } # Create a file /tmp/USERNAME/hog-upload.html which is an upload of $graph. # This uses the HOG add-a-graph by drawing. Log-in first, then click Upload # in hog-upload.html. # # The upload is an adjacency matrix and vertex locations. These are the # text fields in the HTML, but are likely to be too big to see anything # useful. # Vertex locations are from Graph_vertex_xy($graph, ...). # The server draws straight-line edges between locations. # hog-upload.html includes a simple png image so you can preview how it # ought to come out. The Upload click goes to the usual HOG page to enter a # name and comment. You don't see the image in HOG until after that, but if # it goes badly wrong you can always delete the graph. # sub hog_upload_html { my ($graph, %options) = @_; require POSIX; require File::Spec; require File::Temp; my $dir = File::Spec->catdir('/tmp', POSIX::cuserid()); mkdir $dir; my $html_filename = File::Spec->catfile($dir, 'hog-upload.html'); # my $png_filename = File::Spec->catfile($dir, 'hog-upload.png'); my $png_fh = File::Temp->new; my $png_filename = $png_fh->filename; my $hog_url = 'https://hog.grinvin.org'; # $hog_url = 'http://localhost'; # for testing my @vertices = MyGraphs::Graph_sorted_vertices($graph); my $name = $graph->get_graph_attribute('name') // ''; my $num_vertices = scalar(@vertices); my $num_edges = $graph->edges; print "graph $name\n"; print "$num_vertices vertices, $num_edges edges\n"; my $yscale = $options{'yscale'} || 1; if ($graph->get_graph_attribute('is_xy_triangular')) { $yscale *= sqrt(3); } my @points = map { my ($x,$y) = MyGraphs::Graph_vertex_xy($graph,$_) or croak("no X,Y coordinates for vertex ",$_); [$x,$yscale*$y] } @vertices; ### @points if (my $a = $options{'rotate_degrees'}) { $a = Math::Trig::deg2rad($a); my $s = sin($a); my $c = cos($a); @points = map { [ $_->[0] * $c - $_->[1] * $s, $_->[0] * $s + $_->[1] * $c ] } @points; } my @x = map {$_->[0]} @points; my @y = map {$_->[1]} @points; my $size = max( max(@x)-min(@x), max(@y)-min(@y) ); require Geometry::AffineTransform; my $affine = Geometry::AffineTransform->new; $affine->translate( -(max(@x)+min(@x))/2, -(max(@y)+min(@y))/2 ); $affine->scale(1/$size, -1/$size); # Y down the page $affine->scale(380, 380); $affine->translate(200, 200); @points = map {[$affine->transform(@$_)]} @points; @points = map {[map {POSIX::round($_)} @$_]} @points; @x = map {$_->[0]} @points; @y = map {$_->[1]} @points; print "transformed x ",min(@x)," to ",max(@x), " y ",min(@y)," to ",max(@y),"\n"; require Image::Base::GD; my $image = Image::Base::GD->new (-width => 400, -height => 400); $image->rectangle(0,0, 400,400, 'white', 1); $image->rectangle(0,0, 399,399, 'blue'); foreach my $from (0 .. $#vertices) { foreach my $to (0 .. $#vertices) { if ($graph->has_edge($vertices[$from], $vertices[$to])) { $image->line(@{$points[$from]}, @{$points[$to]}, 'red'); } } } foreach my $from (0 .. $#vertices) { my ($x,$y) = @{$points[$from]}; $image->ellipse($x-1,$y-1, $x+1,$y+1, 'black'); } $image->save($png_filename); require File::Slurp; my $png = File::Slurp::read_file($png_filename); require URI::data; my $png_uri = URI->new("data:"); $png_uri->data($png); $png_uri->media_type('image/png'); # my = URI::data->new($png,'image/png'); # stringize the points @points = map {join('-',@$_).';'} @points; ### @points unless (list_is_all_distinct_eq(@points)) { die "oops, some point coordinates have rounded together"; } my $coordinateString = join('',@points); ### $coordinateString # 0100000000000000%0D%0A # 1010000000000000%0D%0A # 0101000000000000%0D%0A # 0010100000000000%0D%0A # 0001010000000000%0D%0A # 0000101010001000%0D%0A # 0000010100000000%0D%0A # 0000001010000000%0D%0A # 0000010100000000%0D%0A # 0000000000101000%0D%0A # 0000000001010000%0D%0A # 0000000000100001%0D%0A # 0000010001000000%0D%0A # 0000000000000010%0D%0A # 0000000000000101%0D%0A # 0000000000010010 my @adjacencies = map { my $from = $_; join('', map {$graph->has_edge($from,$_) ? 1 : 0} @vertices) } @vertices; ### @adjacencies my $adjacencyString = join("\r\n",@adjacencies); require HTML::Entities; my @names; $name = HTML::Entities::encode_entities($name); my $upsize = length($adjacencyString) + length($coordinateString) + 20; print "upload size $upsize bytes\n"; open my $h, '>', $html_filename or die; print $h <<"HERE";

Upload

$name
$num_vertices vertices, $num_edges edges, size $upsize bytes



HERE close $h or die; print "iceweasel file://$html_filename >/dev/null 2>&1 &\n"; } # Return true of all arguments are different, as compared by "eq". sub list_is_all_distinct_eq { my %seen; foreach (@_) { if ($seen{$_}++) { return 0; } } return 1; } #------------------------------------------------------------------------------ # nauty bits sub graph6_view { my ($g6_str, %options) = @_; my $graph = Graph_from_graph6_str($g6_str); my $name = $options{'name'}; if (! defined $name || $name eq '') { my $num_vertices = $graph->vertices; my $num_edges = $graph->edges; $graph->set_graph_attribute (name => "$num_vertices vertices, $num_edges edges"); } Graph_view($graph); } sub graph6_str_to_canonical { my ($g6_str, %options) = @_; ### graph6_str_to_canonical(): $g6_str # num_vertices == 0 is already canonical and nauty-labelg doesn't like to # crunch that if ($g6_str =~ /^\?/) { return $g6_str; } unless ($g6_str =~ /\n$/) { $g6_str .= "\n"; } if ($g6_str =~ /\n.*\n/s) { croak "multiple newlines in g6 string"; } my $canonical; my $err; require IPC::Run; if (! IPC::Run::run (['nauty-labelg', (($options{'format'}||'') eq 'sparse6' ? '-s' : '-g'), # graph6 output # '-i2', ], '<',\$g6_str, '>',\$canonical, '2>',\$err)) { die "nauty-labelg error: ",$canonical,$err; } return $canonical; } sub Graph_to_sparse6_str { my ($graph) = @_; require Graph::Writer::Sparse6; my $writer = Graph::Writer::Sparse6->new; open my $fh, '>', \my $str or die; $writer->write_graph($graph, $fh); return $str; } sub Graph_to_graph6_str { my ($graph, %options) = @_; require Graph::Writer::Graph6; my $writer = Graph::Writer::Graph6->new (format => ($options{'format'}||'graph6')); open my $fh, '>', \my $str or die; $writer->write_graph($graph, $fh); return $str; } # $str is a graph6 or sparse6 string sub Graph_from_graph6_str { my ($str) = @_; require Graph::Reader::Graph6; my $reader = Graph::Reader::Graph6->new; open my $fh, '<', \$str or die; return $reader->read_graph($fh); } # $filename is a file containing graph6 or sparse6 sub Graph_from_graph6_filename { my ($filename) = @_; require Graph::Reader::Graph6; my $reader = Graph::Reader::Graph6->new; open my $fh, '<', $filename or die 'Cannot open ',$filename,': ',$!; return $reader->read_graph($fh); } # return true if Graph.pm graphs $g1 and $g2 are isomorphic sub Graph_is_isomorphic { my ($g1, $g2) = @_; my $g1_str = graph6_str_to_canonical(Graph_to_graph6_str($g1)); my $g2_str = graph6_str_to_canonical(Graph_to_graph6_str($g2)); return $g1_str eq $g2_str; } sub Graph_from_edge_aref { my ($edge_aref, %options) = @_; my $num_vertices = delete $options{'num_vertices'}; my $graph = Graph->new (undirected => 1); $graph->add_vertices (0 .. ($num_vertices||0)-1); foreach my $edge (@$edge_aref) { scalar(@$edge) == 2 or die "bad edge_aref"; my ($from, $to) = @$edge; ($from =~ /^[0-9]+$/ && $to =~ /^[0-9]+$/) or die "bad edge_aref"; $graph->add_edge($from,$to); } return $graph; } sub Graph_from_vpar { my ($vpar, @options) = @_; require Graph; my $graph = Graph->new (@options); $graph->add_vertices(1 .. $#$vpar); foreach my $v (1 .. $#$vpar) { if ($vpar->[$v]) { $graph->add_edge ($v, $vpar->[$v]); } else { $graph->set_graph_attribute('root',$v); } } if ($graph->is_directed) { $graph->set_graph_attribute('flow','north'); } return $graph; } sub Graph_to_vpar { my ($graph, $root) = @_; $root //= Graph_tree_root($graph); ### Graph_to_vpar() ... ### $root my @vertices = sort $graph->vertices; my @vpar = (undef, (0) x scalar(@vertices)); unshift @vertices, undef; ### @vertices my %vertex_to_v; foreach my $v (1 .. $#vertices) { $vertex_to_v{$vertices[$v]} = $v; } my %seen; $vpar[$vertex_to_v{$root}] = 0; $seen{$root} = 1; my @pending = ($root); while (@pending) { my $vertex = pop @pending; my $v = $vertex_to_v{$vertex}; ### vertex: "$vertex v=$v" my @neighbours = $graph->neighbours($vertex); ### @neighbours my $p = 0; $seen{$vertex} = 1; $vpar[$v] = 0; foreach my $neighbour (@neighbours) { if ($seen{$neighbour}) { $p = $vertex_to_v{$neighbour}; $vpar[$v] = $p; ### set: "$v parent $p" } else { push @pending, $neighbour; } } } ### @vpar return \@vpar; } sub Graph_vpar_str { my ($graph) = @_; my $vpar = Graph_to_vpar($graph); my $str = "["; foreach my $v (1 .. $#$vpar) { $str .= ($vpar->[$v] // 'undef'); if ($v != $#$vpar) { $str .= ","; } } $str .= "]"; } sub Graph_print_vpar { my ($graph) = @_; my $vpar = Graph_to_vpar($graph); print "["; foreach my $v (1 .. $#$vpar) { print $vpar->[$v] // 'undef'; if ($v != $#$vpar) { print ","; } } print "]\n"; } # synchronous => 1, wait for viewer to exit before returning. sub vpar_view { my ($vpar, %options) = @_; ### Graph_view(): %options my $graphviz2 = vpar_to_GraphViz2($vpar, %options); GraphViz2_view ($graphviz2, %options); } sub vpar_name { my ($vpar) = @_; my $str = 'N='.$#$vpar.' vpar'; my $sep = ' '; foreach my $i (1..$#$vpar) { $str .= $sep; if (length($str) >= 45) { $str .= '...'; return $str; } $str .= $vpar->[$i]; $sep = ','; } return $str; } sub vpar_to_GraphViz2 { my ($vpar, %options) = @_; ### vpar_to_GraphViz2(): %options require GraphViz2; my $name = $options{'name'} // vpar_name($vpar); my $flow = ($options{'flow'} // 'up'); my $graphviz2 = GraphViz2->new (global => { directed => 1 }, graph => { label => $name, rankdir => ($flow eq 'down' ? 'TB' : $flow eq 'up' ? 'BT' : $flow), ordering => 'out', }, node => { margin => 0, # cf default 0.11,0.055 }, ); foreach my $v (1 .. $#$vpar) { $graphviz2->add_node(name => $v, margin => '0.04,0.03', # cf default 0.11,0.055 height => '0.1', # inches, minimum width => '0.1', # inches, minimum ); } foreach my $from (1 .. $#$vpar) { if (my $to = $vpar->[$from]) { $graphviz2->add_edge(from => $from, to => $to); } } # roots in cluster at same rank so aligned horizontally $graphviz2->push_subgraph (subgraph => {rank => 'same'}); foreach my $v (1 .. $#$vpar) { unless ($vpar->[$v]) { $graphviz2->add_node(name => $v); } } $graphviz2->pop_subgraph; return $graphviz2; } #------------------------------------------------------------------------------ # triangles # ($a,$b,$c) are vertices of a triangle in $graph. # a # / \ # b---c # Return true if this is an even triangle. For an even triangle every other # vertex in the graph has an edge going to an even number of the vertices # a,b,c. This means either no edges to them, or edges to exactly 2 of them. # sub Graph_triangle_is_even { my ($graph, $a,$b,$c) = @_; ### Graph_triangle_is_even(): "$a $b $c" foreach my $v ($graph->vertices) { next if $v eq $a || $v eq $b || $v eq $c; my $count = (($graph->has_edge($v,$a) ? 1 : 0) + ($graph->has_edge($v,$b) ? 1 : 0) + ($graph->has_edge($v,$c) ? 1 : 0)); ### count: "$v is $count" unless ($count == 0 || $count == 2) { ### triangle odd ... return 0; } } ### triangle even ... return 1; } # $graph is a Graph.pm. # Call $stop = $callback->($a,$b,$c) for each triangle in $graph. # If the return $stop is true then stop the search. # The return is the $stop value, or undef at end of search. # # c # / \ # a---b # # Triangles are found one way only, so if a,b,c then no calls also for # permutations like b,a,c. It's unspecified exactly which vertices are the # $a,$b,$c in the callback (though the current code has then in ascending # alphabetical order). # sub Graph_triangle_search { my ($graph, $callback) = @_; foreach my $a ($graph->vertices) { my @a_neighbours = sort $graph->neighbours($a); foreach my $bi (0 .. $#a_neighbours-2) { my $b = $a_neighbours[$bi]; next if $b lt $a; foreach my $ci ($bi+1 .. $#a_neighbours-1) { my $c = $a_neighbours[$ci]; if ($graph->has_edge($b,$c)) { if (my $stop = $callback->($a,$b,$c)) { return $stop; } } } } } return undef; } # $graph is a Graph.pm. # ($a,$b,$c) = Graph_find_triangle($graph); # Return a list of vertices which are a triangle within $graph. # If no triangles then return an empty list; # c # / \ # a---b sub Graph_find_triangle { my ($graph) = @_; my @ret; Graph_triangle_search($graph, sub {@ret = @_}); return @ret; } # $graph is a Graph.pm. # Return true if $graph contains a triangle. sub Graph_has_triangle { my ($graph) = @_; return Graph_triangle_search($graph, sub {1}); } # $graph is a Graph.pm. # Return the number of triangles in $graph. sub Graph_triangle_count { my ($graph) = @_; my $count = 0; Graph_triangle_search($graph, sub { $count++; return 0}); return $count; } #------------------------------------------------------------------------------ # Claws # $graph is a Graph.pm. # Call $stop = $callback->($a,$b,$c,$d) for each claw in $graph. # $a is the centre. # If the return $stop is true then stop the search. # The return is the $stop value, or undef at end of search. # b # / # a--c # \ # d sub Graph_claw_search { my ($graph, $callback) = @_; foreach my $a ($graph->vertices) { my @a_neighbours = $graph->neighbours($a); foreach my $bi (0 .. $#a_neighbours-2) { my $b = $a_neighbours[$bi]; foreach my $ci ($bi+1 .. $#a_neighbours-1) { my $c = $a_neighbours[$ci]; next if $graph->has_edge($b,$c); foreach my $di ($ci+1 .. $#a_neighbours) { my $d = $a_neighbours[$di]; next if $graph->has_edge($b,$d) || $graph->has_edge($c,$d); if (my $stop = $callback->($a,$b,$c,$d)) { return $stop; } } } } } return undef; } # $graph is a Graph.pm. # ($a,$b,$c,$d) = Graph_find_claw($graph); # Return a list of vertices which are a claw (a 4-star) within $graph, as an # induced subgraph. $a is the centre. # If no claws then return an empty list; # b # / # a--c # \ # d sub Graph_find_claw { my ($graph) = @_; my @ret; Graph_claw_search($graph, sub {@ret = @_}); return @ret; } # $graph is a Graph.pm. # Return true if $graph contains a claw (star-4) as an induced subgraph. sub Graph_has_claw { my ($graph) = @_; return Graph_claw_search($graph, sub {1}); } # $graph is a Graph.pm. # Return the number of induced claws in $graph. sub Graph_claw_count { my ($graph) = @_; my $count = 0; Graph_claw_search($graph, sub { $count++; return 0}); return $count; } #------------------------------------------------------------------------------ # Return a list of how many vertices at depths 0 etc down from $root. # The first width is depth=0 which is $root itself so width=1. sub Graph_width_list { my ($graph, $root) = @_; my @widths; my %seen; my @pending = ($root); while (@pending) { push @widths, scalar(@pending); my @new_pending; foreach my $v (@pending) { $seen{$v} = 1; my @children = $graph->neighbours($v); @children = grep {! $seen{$_}} @children; push @new_pending, @children; } @pending = @new_pending; } return @widths; } # $graph is a Graph.pm. # Return true if all vertices of $graph have same degree. # sub Graph_is_regular { my ($graph) = @_; my $degree; foreach my $v ($graph->vertices) { my $d = $graph->degree($v); $degree //= $d; if ($d != $degree) { return 0; } } return 1; } # $graph and $subgraph are Graph.pm objects. # Return true if $subgraph is a subgraph of $graph. # This is a check of graph structure. The vertex names in the two can be # different. # sub Graph_is_subgraph { my ($graph, $subgraph) = @_; my $num_vertices = $graph->vertices; my $subgraph_num_vertices = $subgraph->vertices; edge_aref_is_subgraph(edge_aref_from_Graph($graph), edge_aref_from_Graph($subgraph), num_vertices => $num_vertices, subgraph_num_vertices => $subgraph_num_vertices); } sub edge_aref_from_Graph { my ($graph) = @_; ### edge_aref_from_Graph(): "$graph" my @vertices = sort $graph->vertices; my %vertices = map { $vertices[$_] => $_ } 0 .. $#vertices; my @edges = $graph->edges; return [ map { my ($from,$to) = @$_; [ $vertices{$from},$vertices{$to} ] } @edges ]; } sub Graph_is_induced_subgraph { my ($graph, $subgraph, %options) = @_; my @graph_vertices = sort $graph->vertices; my @subgraph_vertices = sort $subgraph->vertices; ### @graph_vertices ### @subgraph_vertices my @ret; my @used = (0) x (scalar(@graph_vertices) + 1); my @map = (-1) x (scalar(@subgraph_vertices) + 1); my $pos = 0; OUTER: for (;;) { $used[$map[$pos]] = 0; ### undo use: "used=".join(',',@used) for (;;) { my $m = ++$map[$pos]; ### $m if ($m > $#graph_vertices) { $pos--; ### backtrack to pos: $pos if ($pos < 0) { last OUTER; } next OUTER; } if (! $used[$m]) { $used[$m] = 1; last; } ### used ... } ### incremented: "pos=$pos map=".join(',',@map)." used=".join(',',@used) if ($graph->vertex_degree($graph_vertices[$map[$pos]]) < $subgraph->vertex_degree($subgraph_vertices[$pos])) { ### graph degree smaller than subgraph ... next; } foreach my $p (0 .. $pos-1) { ### consider: "pos=$pos p=$p graph $graph_vertices[$map[$p]] to $graph_vertices[$map[$pos]] subgraph $subgraph_vertices[$p] to $subgraph_vertices[$pos]" my $gedge = !! $graph->has_edge ($graph_vertices[$map[$p]], $graph_vertices[$map[$pos]]); my $sedge = !! $subgraph->has_edge($subgraph_vertices[$p], $subgraph_vertices[$pos]); if ($gedge != $sedge) { next OUTER; } } # good for this next vertex at $pos, descend if (++$pos > $#subgraph_vertices) { # print "found:\n"; # foreach my $p (0 .. $#subgraph_vertices) { # print " $subgraph_vertices[$p] <-> $graph_vertices[$map[$p]]\n"; # } if ($options{'all_maps'}) { push @ret, { map {$subgraph_vertices[$_] => $graph_vertices[$map[$_]]} 0 .. $#subgraph_vertices}; ### new map: $ret[-1] $pos--; } else { return join(', ', map {"$subgraph_vertices[$_]=$graph_vertices[$map[$_]]"} 0 .. $#subgraph_vertices); } } $map[$pos] = -1; } if ($options{'all_maps'}) { return @ret; } else { return 0; } } sub edge_aref_is_induced_subgraph { my ($edge_aref, $subgraph_edge_aref) = @_; if (@$edge_aref < @$subgraph_edge_aref) { return 0; } my @degree; my @neighbour; foreach my $edge (@$edge_aref) { $neighbour[$edge->[0]][$edge->[1]] = 1; $neighbour[$edge->[1]][$edge->[0]] = 1; $degree[$edge->[0]]++; $degree[$edge->[1]]++; } ### @degree my @subgraph_degree; my @subgraph_neighbour; foreach my $edge (@$subgraph_edge_aref) { $subgraph_neighbour[$edge->[0]][$edge->[1]] = 1; $subgraph_neighbour[$edge->[1]][$edge->[0]] = 1; $subgraph_degree[$edge->[0]]++; $subgraph_degree[$edge->[1]]++; } ### @subgraph_degree { my @degree_sorted = sort {$b<=>$a} @degree; # descending my @subgraph_degree_sorted = sort {$b<=>$a} @subgraph_degree; foreach my $i (0 .. $#subgraph_degree_sorted) { if ($subgraph_degree_sorted[$i] > $degree_sorted[$i]) { return 0; } } } my $num_vertices = scalar(@neighbour); my $subgraph_num_vertices = scalar(@subgraph_neighbour); my @used = (0) x ($num_vertices + 1); my @map = (-1) x ($subgraph_num_vertices + 1); my $pos = 0; OUTER: for (;;) { $used[$map[$pos]] = 0; ### undo use: "used=".join(',',@used) for (;;) { my $m = ++$map[$pos]; ### $m if ($m >= $num_vertices) { $pos--; ### backtrack to pos: $pos if ($pos < 0) { return 0; } next OUTER; } if (! $used[$m]) { $used[$m] = 1; last; } ### used ... } ### incremented: "pos=$pos map=".join(',',@map)." used=".join(',',@used) if ($degree[$map[$pos]] < $subgraph_degree[$pos]) { ### graph degree smaller than subgraph ... next; } foreach my $p (0 .. $pos-1) { ### consider: "pos=$pos p=$p graph $map[$p] to $map[$pos] subgraph $p to $pos" my $has_edge = ! $neighbour[$map[$p]][$map[$pos]]; my $subgraph_has_edge = ! $subgraph_neighbour[$p][$pos]; if ($has_edge != $subgraph_has_edge) { next OUTER; } } # good for this next vertex at $pos, descend if (++$pos >= $subgraph_num_vertices) { # print "found:\n"; # foreach my $p (0 .. $subgraph_num_vertices-1) { # print " $p <-> $map[$p]\n"; # } return join(', ', map {"$_=$map[$_]"} 0 .. $subgraph_num_vertices-1); # return 1; } $map[$pos] = -1; } } sub edge_aref_is_subgraph { my ($edge_aref, $subgraph_edge_aref, %option) = @_; ### edge_aref_is_subgraph() ... ### $edge_aref ### $subgraph_edge_aref my @degree; my @neighbour; foreach my $edge (@$edge_aref) { $neighbour[$edge->[0]][$edge->[1]] = 1; $neighbour[$edge->[1]][$edge->[0]] = 1; $degree[$edge->[0]]++; $degree[$edge->[1]]++; } ### @degree my @subgraph_degree; my @subgraph_neighbour; foreach my $edge (@$subgraph_edge_aref) { $subgraph_neighbour[$edge->[0]][$edge->[1]] = 1; $subgraph_neighbour[$edge->[1]][$edge->[0]] = 1; $subgraph_degree[$edge->[0]]++; $subgraph_degree[$edge->[1]]++; } ### @subgraph_degree if (defined (my $num_vertices = $option{'num_vertices'})) { $num_vertices >= @degree or croak "num_vertices option too small"; $#degree = $num_vertices - 1; $#neighbour = $num_vertices - 1; } if (defined (my $subgraph_num_vertices = $option{'subgraph_num_vertices'})) { $subgraph_num_vertices >= @subgraph_degree or croak "num_vertices option too small"; $#subgraph_degree = $subgraph_num_vertices - 1; $#subgraph_neighbour = $subgraph_num_vertices - 1; } if (@degree < @subgraph_degree) { ### graph fewer vertices than subgraph ... return 0; } my $num_vertices = scalar(@neighbour); my $subgraph_num_vertices = scalar(@subgraph_neighbour); foreach my $i (0 .. $num_vertices-1) { $degree[$i] ||= 0; } foreach my $i (0 .. $subgraph_num_vertices-1) { $subgraph_degree[$i] ||= 0; } my @used = (0) x ($num_vertices + 1); my @map = (-1) x ($subgraph_num_vertices + 1); my $pos = 0; OUTER: for (;;) { $used[$map[$pos]] = 0; ### undo use: "used=".join(',',@used) for (;;) { my $m = ++$map[$pos]; ### $m if ($m >= $num_vertices) { $pos--; ### backtrack to pos: $pos if ($pos < 0) { return 0; } next OUTER; } if (! $used[$m]) { $used[$m] = 1; last; } ### used ... } ### incremented: "pos=$pos map=".join(',',@map)." used=".join(',',@used) if (($degree[$map[$pos]]||0) < ($subgraph_degree[$pos]||0)) { ### graph degree smaller than subgraph ... ### degree: $degree[$map[$pos]] ### subgraph degree: $subgraph_degree[$pos] next; } foreach my $p (0 .. $pos-1) { ### consider: "pos=$pos p=$p graph $map[$p] to $map[$pos] subgraph $p to $pos" my $has_edge = $neighbour[$map[$p]][$map[$pos]]; my $subgraph_has_edge = $subgraph_neighbour[$p][$pos]; if ($subgraph_has_edge && ! $has_edge) { next OUTER; } } # good for this next vertex at $pos, descend if (++$pos >= $subgraph_num_vertices) { # print "found:\n"; # foreach my $p (0 .. $subgraph_num_vertices-1) { # print " $p <-> $map[$p]\n"; # } return join(', ', map {"$_=$map[$_]"} 0 .. $subgraph_num_vertices-1); # return 1; } $map[$pos] = -1; } } sub edge_aref_degrees_allow_subgraph { my ($edge_aref, $subgraph_edge_aref) = @_; if (@$edge_aref < @$subgraph_edge_aref) { return 0; } my @degree; foreach my $edge (@$edge_aref) { $degree[$edge->[0]]++; $degree[$edge->[1]]++; } ### @degree my @subgraph_degree; foreach my $edge (@$subgraph_edge_aref) { $subgraph_degree[$edge->[0]]++; $subgraph_degree[$edge->[1]]++; } ### @subgraph_degree @degree = sort {$b<=>$a} @degree; # descending @subgraph_degree = sort {$b<=>$a} @subgraph_degree; foreach my $i (0 .. $#subgraph_degree) { if ($subgraph_degree[$i] > $degree[$i]) { return 0; } } return 1; } sub edge_aref_eccentricity { my ($edge_aref, $v) = @_; ### $v my $eccentricity = 0; my @edges = @$edge_aref; my @pending = ($v); while (@pending) { ### @edges ### @pending $eccentricity++; my @new_pending; foreach my $v (@pending) { @edges = grep { my ($from,$to) = @$_; my $keep = 1; if ($from == $v) { push @new_pending, $to; $keep = 0; } elsif ($to == $v) { push @new_pending, $from; $keep = 0; } $keep } @edges; } @pending = @new_pending; } return $eccentricity; } # return true if all vertices of $graph have same degree sub edge_aref_is_regular { my ($edge_aref) = @_; my @degrees = edge_aref_degrees($edge_aref); ### @degrees foreach my $i (1 .. $#degrees) { if ($degrees[$i] != $degrees[0]) { return 0; } } return 1; } #------------------------------------------------------------------------------ sub Graph_Wiener_part_at_vertex { my ($graph,$vertex) = @_; my $total = 0; $graph->for_shortest_paths(sub { my ($t, $u,$v, $n) = @_; if ($u eq $vertex) { $total += $t->path_length($u,$v); } }); return $total; } sub Graph_Wiener_index { my ($graph) = @_; my $total = 0; $graph->for_shortest_paths(sub { my ($t, $u,$v, $n) = @_; $total += $t->path_length($u,$v); }); return $total/2; } sub Graph_terminal_Wiener_index { my ($graph) = @_; my $total = 0; my $for = $graph->for_shortest_paths (sub { my ($t, $u,$v, $n) = @_; ### u: $graph->vertex_degree($u) ### v: $graph->vertex_degree($v) if ($graph->vertex_degree($u) == 1 && $graph->vertex_degree($v) == 1) { $total += $t->path_length($u,$v); } }); return $total/2; } sub Graph_terminal_Wiener_part_at_vertex { my ($graph, $vertex) = @_; ### Graph_terminal_Wiener_part_at_vertex(): $vertex my $total = 0; my $for = $graph->for_shortest_paths (sub { my ($t, $u,$v, $n) = @_; # ### u: $graph->vertex_degree($u) # ### v: $graph->vertex_degree($v) ### path: "$u to $v" # can have $vertex not a leaf node if ($u eq $vertex && $graph->vertex_degree($v) == 1) { ### length: $t->path_length($u,$v) $total += $t->path_length($u,$v); } }); return $total; } #------------------------------------------------------------------------------ # $graph is a Graph.pm. # Return a new Graph.pm which is its line graph. # Each vertex of the line graph is an edge of $graph and edges in the line # graph are between those $graph edges with a vertex in common. # sub Graph_line_graph { my ($graph) = @_; my $line = Graph->new (undirected => $graph->is_undirected); $line->set_graph_attribute (name => join(', ', ($graph->get_graph_attribute('name') // ()), 'line graph')); foreach my $from_edge ($graph->edges) { my $from_edge_name = join(':', @$from_edge); my ($from_vertex, $to_vertex) = @$from_edge; foreach my $to_edge ($graph->edges_at($from_vertex), $graph->edges_at($to_vertex)) { my $to_edge_name = join(':', @$to_edge); if ($from_edge_name ne $to_edge_name && ! $line->has_edge ($from_edge_name, $to_edge_name) && ! $line->has_edge ($to_edge_name, $from_edge_name)) { $line->add_edge($from_edge_name, $to_edge_name); } } } return $line; } sub Graph_Easy_line_graph { my ($easy) = @_; my $line = Graph::Easy->new (undirected => $easy->is_undirected); foreach my $from_edge ($easy->edges) { my $from_name = $from_edge->name; foreach my $to_edge ($from_edge->from->edges, $from_edge->to->edges) { my $to_name = $to_edge->name; if ($from_name ne $to_name && ! $line->has_edge ($from_name, $to_name) && ! $line->has_edge ($to_name, $from_name)) { $line->add_edge($from_name, $to_name); } } } return $line; } # Graph_Beineke_graphs() returns a list of Graph.pm graphs of Beineke G1 to G9. use constant::defer Graph_Beineke_graphs => sub { require Graph::Maker::Beineke; map { Graph::Maker->new('Beineke', G=>$_, undirected=>1) } 1 .. 9; }; # $graph is a Graph.pm. # Return true if $graph is a line graph, by checking none of Beineke G1 to # G9 are induced subgraphs. # sub Graph_is_line_graph_by_Beineke { my ($graph) = @_; ### Graph_is_line_graph_by_Beineke() ... foreach my $G (Graph_Beineke_graphs()) { if (Graph_is_induced_subgraph($graph, $G)) { ### is induced subgraph, so not line graph ... return 0; } ### not subgraph ... } ### final is line graph ... return 1; } #------------------------------------------------------------------------------ # Graph Doubles # $graph is a Graph.pm. # Return a new graph which is the bipartite double of $graph. # The new graph is two copies of the original vertices "$v.A" and $v.B". # An edge $u to $v in $graph becomes edges $u.A to $v.B # and $u.B to $v.A # sub Graph_bipartite_double { my ($graph) = @_; my $double = $graph->new; # same directed, countedged, etc foreach my $v ($graph->vertices) { $double->add_vertex("$v.A"); $double->add_vertex("$v.B"); } foreach my $edge ($graph->edges) { my ($from,$to) = @$edge; ### edge: "$from to $to" $double->add_edge("$from.A","$to.B"); $double->add_edge("$from.B","$to.A"); } return $double; } #------------------------------------------------------------------------------ # GraphViz2 conversions # file:///usr/share/doc/graphviz/html/info/attrs.html # $graph is a Graph.pm object. # Return a GraphViz2 object. # sub Graph_to_GraphViz2 { my ($graph, %options) = @_; ### Graph_to_GraphViz2: %options require GraphViz2; $options{'vertex_name_type'} //= $graph->get_graph_attribute('vertex_name_type') // ''; my $is_xy = ($options{'is_xy'} || $options{'vertex_name_type'} =~ /^xy/ || $graph->get_graph_attribute('vertex_name_type_xy') || $graph->get_graph_attribute('vertex_name_type_xy_triangular')); my $is_xy_triangular = ($graph->get_graph_attribute('is_xy_triangular') || $options{'vertex_name_type'} =~ /^xy-triangular/ || $graph->get_graph_attribute('vertex_name_type_xy_triangular')); ### $is_xy ### $is_xy_triangular my $name = $graph->get_graph_attribute('name'); my $flow = ($options{'flow'} // $graph->get_graph_attribute('flow') // 'down'); if ($flow eq 'north') { $flow = 'BT'; } if ($flow eq 'east') { $flow = 'LR'; } my $graphviz2 = GraphViz2->new (global => { directed => $graph->is_directed }, graph => { (defined $name ? (label => $name) : ()), (defined $flow ? (rankdir => $flow) : ()), # ENHANCE-ME: take this in %options somehow # Scale like "3" means input coordinates are tripled, so # actual drawing is 1/3 of an inch steps. inputscale => 3, }, node => { margin => 0, # cf default 0.11,0.055 }, ); foreach my $v ($graph->vertices) { my @attrs; if (my ($x,$y) = Graph_vertex_xy($graph,$v)) { if ($is_xy_triangular) { $y = sprintf '%.5f', $y*sqrt(3); } if (defined $options{'scale'}) { $x *= $options{'scale'}; $y *= $options{'scale'}; } push @attrs, pin=>1, pos=>"$x,$y"; ### @attrs } if (defined(my $name = $graph->get_vertex_attribute($v,'name'))) { push @attrs, label => $name; } $graphviz2->add_node(name => $v, margin => '0.03,0.02', # cf default 0.11,0.055 height => '0.1', # inches, minimum width => '0.1', # inches, minimum @attrs); } foreach my $edge ($graph->edges) { my ($from, $to) = @$edge; $graphviz2->add_edge(from => $from, to => $to); } return $graphviz2; } sub Graph_vertex_xy { my ($graph, $v) = @_; if (defined (my $xy = $graph->get_vertex_attribute($v,'xy'))) { return split /,/, $xy; } if ($graph->get_graph_attribute('vertex_name_type_xy_triangular')) { my ($x,$y) = split /,/, $v; return ($x, $y*sqrt(3)); } if ($graph->get_graph_attribute('vertex_name_type_xy')) { return split /,/, $v; } if (defined(my $x = $graph->get_vertex_attribute($v,'x')) && defined(my $y = $graph->get_vertex_attribute($v,'y'))) { return ($x,$y); } return (); } sub Graph_set_xy_points { my $graph = shift; while (@_) { my $v = shift; my $point = shift; ### $v ### $point $graph->set_vertex_attribute($v, x => $point->[0]); $graph->set_vertex_attribute($v, y => $point->[1]); } } # $graphviz2 is a GraphViz2 object. # sub GraphViz2_view { my ($graphviz2, %options) = @_; require File::Temp; my $ps = File::Temp->new (UNLINK => 0, SUFFIX => '.ps'); my $ps_filename = $ps->filename; $graphviz2->run(format => 'ps', output_file => $ps_filename, ($options{'driver'} ? (driver => $options{'driver'}) : ()), ); postscript_view_file($ps_filename, %options); # $graphviz2->run(format => 'xlib', # driver => 'neato', # ); } sub parent_aref_view { my ($aref) = @_; Graph_Easy_view(parent_aref_to_Graph_Easy($aref)); } #------------------------------------------------------------------------------ # $name is a vertex name. # Return a form suitable for use as a PGF/Tikz node name. # ENHANCE-ME: not quite right, would want to fixup most parens and more too. # sub vertex_name_to_tikz { my ($name) = @_; $name =~ s/[,:]/-/g; return $name; } # $graph is an undirected Graph.pm. # Print some PGF/Tikz TeX nodes and edges. # The output is a bit rough, and usually must be massaged by hand. # sub Graph_print_tikz { my ($graph) = @_; my $is_xy = $graph->get_graph_attribute('vertex_name_type_xy'); my @vertices = sort $graph->vertices; my $flow = 'east'; my $rows = int(sqrt(scalar(@vertices))); my $r = 0; my $c = 0; my %seen_vn; foreach my $v (@vertices) { my $x = ($flow eq 'west' ? -$c : $c); my $vn = vertex_name_to_tikz($v); if (exists $seen_vn{$vn}) { croak "Oops, duplicate tikz vertex name for \"$v\" and \"$seen_vn{$vn}\""; } $seen_vn{$vn} = $v; my $at = ($is_xy ? $v : "$x,$r"); print " \\node ($vn) at ($at) [my box] {$v};\n"; $r++; if ($r >= $rows) { $c++; $r = 0; } } print "\n"; my $arrow = $graph->is_directed ? "->" : ""; foreach my $edge ($graph->unique_edges) { my ($from,$to) = @$edge; my $count = $graph->get_edge_count($from,$to); my $node = ($count == 1 ? '' : "node[pos=.5,auto=left] {$count} "); $from = vertex_name_to_tikz($from); $to = vertex_name_to_tikz($to); if ($from eq $to) { print " \\draw [$arrow,loop below] ($from) to $node();\n"; } else { print " \\draw [$arrow] ($from) to $node($to);\n"; } } print "\n"; } sub all_looks_like_consecutive_number { all_looks_like_number(@_) or return 0; my @a = sort {$a<=>$b} @_; foreach my $i (1 .. $#a) { $a[$i] == $a[$i-1] + 1 or return 0; } return 1; } sub all_looks_like_number { foreach (@_) { (Scalar::Util::looks_like_number($_) && $_ <= (1<<24)) or return 0; } return 1; } sub sort_num_or_alnum { foreach (@_) { unless (Scalar::Util::looks_like_number($_)) { return sort @_; } } return sort {$a<=>$b} @_; } sub Graph_print_dreadnaut { my ($graph) = @_; print Graph_dreadnaut_str($graph); } sub Graph_dreadnaut_str { my ($graph, %options) = @_; my @vertices = $graph->vertices; my $base; if (@vertices && all_looks_like_number(@vertices)) { ### numeric vertices ... @vertices = sort {$a<=>$b} @vertices; $base = $vertices[0]; } else { ### non-numeric vertices, sort ... @vertices = sort @vertices; $base = $options{'base'} || 0; } my %vertex_to_n; @vertex_to_n{@vertices} = $base .. $base+$#vertices; # hash slice ### %vertex_to_n my $str = ''; $str .= ($graph->is_directed ? 'd' : '-d') . ' n='.scalar(@vertices) . " \$=$base g"; my $comma = ''; my $prev_i = 0; my @edges = sort {$vertex_to_n{$a->[0]} <=> $vertex_to_n{$b->[0]} || $vertex_to_n{$a->[1]} <=> $vertex_to_n{$b->[1]}} $graph->edges; ### num edges: scalar(@edges) my $prev_from = $base; my $join = ''; foreach my $edge (@edges) { ### $edge $str .= $comma; my $from = $vertex_to_n{$edge->[0]}; my $to = $vertex_to_n{$edge->[1]}; ### indices: "$from to $to" if ($from != $prev_from) { $str .= ($from == $prev_from + 1 ? ';' : "$join$from:"); $join = ''; $prev_from = $from; } $str .= "$join$to"; $join = ' '; } ### $str return $str . "."; } sub Graph_run_dreadnaut { my ($graph, %options) = @_; require IPC::Run; my $str = Graph_dreadnaut_str($graph,%options) . " a x\n"; if ($options{'verbose'}) { print $str; } if (! IPC::Run::run(['dreadnaut'], '<',\$str)) { die "dreadnaut: $!"; } } #------------------------------------------------------------------------------ # $graph is an undirected Graph.pm. # Return the clique number of $graph. # The clique number is the number of vertices in the maximum clique # (complete graph) contained in $graph. # Currently this is a brute force search, so quite slow and suitable only for # small number of vertices. # sub Graph_clique_number { my ($graph) = @_; my @vertices = sort $graph->vertices; my @clique = (-1); my $maximum_clique_size = 0; my $pos = 0; OUTER: for (;;) { ### at: join(',',@clique[0..$pos]) if (++$clique[$pos] > $#vertices) { # backtrack if (--$pos < 0) { last; } next; } my $v = $vertices[$clique[$pos]]; foreach my $i (0 .. $pos-1) { if (! $graph->has_edge($v, $vertices[$clique[$i]])) { next OUTER; } } $pos++; if ($pos > $maximum_clique_size) { # print " new high $maximum_clique_size\n"; $maximum_clique_size = $pos; } if ($pos > $#vertices) { # $graph is a complete-N last; } $clique[$pos] = $clique[$pos-1]; } return $maximum_clique_size; } # $graph is a Graph.pm and @vertices are vertex names in it. # Return true if those vertices are a clique, meaning edge between all pairs. sub Graph_is_clique { my ($graph, @vertices) = @_; foreach my $i (0 .. $#vertices) { $graph->has_vertex($vertices[$i]) or die; foreach my $j (0 .. $#vertices) { next if $i == $j; ### has: "$vertices[$i] $vertices[$j] is ".($graph->has_edge($vertices[$i], $vertices[$j])||0) $graph->has_edge($vertices[$i], $vertices[$j]) or return 0; } } return 1; } #------------------------------------------------------------------------------ # $graph is a tree # $v is a child node of $parent # Return the depth of the subtree $v and deeper underneath $parent. # If $v is a leaf then it is the entire subtree and the return is depth 1. # sub Graph_subtree_depth { my ($graph, $parent, $v) = @_; ### $parent ### $v $graph->has_edge($parent,$v) or die "oops, $parent and $v not adjacent"; my $depth = 0; my %seen = ($parent => 1, $v => 1); my @pending = ($v); do { @pending = map {$graph->neighbours($_)} @pending; @pending = grep {! $seen{$_}++} @pending; $depth++; } while (@pending); return $depth; } # $graph is a tree # $v is a child node of $parent # return the children of $v, being all neighbours except $parent sub Graph_subtree_children { my ($graph, $parent, $v) = @_; return grep {$_ ne $parent} $graph->neighbours($v); } #------------------------------------------------------------------------------ # $edge_aref is an arrayref [ [from,to], [from,to], ... ] # where each vertex is integer 0 upwards # Return the number of vertices, which means the maximum + 1 of the vertex # numbers in the elements. # sub edge_aref_num_vertices { my ($edge_aref) = @_; if (! @$edge_aref) { return 0; } return max(map {@$_} @$edge_aref) + 1; } # $edge_aref is an arrayref [ [from,to], [from,to], ... ] # where each vertex is integer 0 upwards forming a tree with root 0 # Return an arrayref of the parent of each vertex, so $a->[i] = parent of i # sub edge_aref_to_parent_aref { my ($edge_aref) = @_; ### edge_aref_to_parent_aref() ... my @neighbours; foreach my $edge (@$edge_aref) { my ($from, $to) = @$edge; push @{$neighbours[$from]}, $to; push @{$neighbours[$to]}, $from; } my @parent; my @n_to_v = (0); my @v_to_n = (0); my $upto_v = 1; for (my $v = 0; $v < $upto_v; $v++) { ### neighbours: "$v=n$v_to_n[$v] to n=".join(',',@{$neighbours[$v_to_n[$v]]}) foreach my $n (@{$neighbours[$v_to_n[$v]]}) { if (! defined $n_to_v[$n]) { $n_to_v[$n] = $upto_v; $v_to_n[$upto_v] = $n; $parent[$upto_v] = $v; $upto_v++; } } } foreach my $edge (@$edge_aref) { foreach my $n (@$edge) { $n = $n_to_v[$n]; # mutate array } } ### @parent ### num_vertices: scalar(@parent) return \@parent; } # $parent_aref is an arrayref where $a->[i] = parent of i # vertices are integers 0 upwards # Return an edge aref [ [from,to], [from,to], ... ] # sub parent_aref_to_edge_aref { my ($parent_aref) = @_; return [ map {[$parent_aref->[$_] => $_]} 1 .. $#$parent_aref ]; } # $parent_aref is an arrayref where $a->[i] = parent of i # vertices are integers 0 upwards # Return a Graph::Easy # sub parent_aref_to_Graph_Easy { my ($parent_aref) = @_; require Graph::Easy; my $graph = Graph::Easy->new(undirected => 1); if (@$parent_aref) { $graph->add_vertex(0); foreach my $v (1 .. $#$parent_aref) { $graph->add_edge($v,$parent_aref->[$v]); } } return $graph; } #------------------------------------------------------------------------------ # $graph is a Graph.pm. # Modify $graph by changing the name of vertex $old_name to $new_name. # If $old_name and $new_name are the same then do nothing. # Otherwise $new_name should not exist already. # sub Graph_rename_vertex { my ($graph, $old_name, $new_name) = @_; ### $old_name ### $new_name return if $old_name eq $new_name; if ($graph->has_vertex($new_name)) { croak "Graph vertex \"$new_name\" exists already"; } $graph->add_vertex($new_name); $graph->set_vertex_attributes($new_name, $graph->get_vertex_attributes($old_name)); foreach my $edge ($graph->edges_at($old_name)) { my ($from,$to) = @$edge; if ($from eq $old_name) { $from = $new_name; } if ($to eq $old_name) { $to = $new_name; } ### $from ### $to $graph->add_edge($from,$to); } $graph->delete_vertex($old_name); } # $graph is a Graph.pm. # Return a new vertex name for $graph, one which does not otherwise occur in # $graph. # sub Graph_new_vertex_name { my ($graph, $prefix) = @_; if (! defined $prefix) { $prefix = ''; } my $upto = $graph->get_graph_attribute('Graph_new_vertex_name_upto') // 0; $upto++; $graph->set_graph_attribute('Graph_new_vertex_name_upto',$upto); return "$prefix$upto"; } # $graph is a Graph.pm. # Add vertices to pad out existing vertices to all degree $N. sub Graph_pad_degree { my ($graph, $N) = @_; my $upto = 1; my @original_vertices = $graph->vertices; foreach my $v (@original_vertices) { while ($graph->vertex_degree($v) < $N) { $graph->add_edge($v, Graph_new_vertex_name($graph)); $graph->set_graph_attribute('vertex_name_type',undef); } } return $graph; } # $graph is a Graph.pm. sub Graph_degree_sequence { my ($graph) = @_; return sort {$a<=>$b} map {$graph->vertex_degree($_)} $graph->vertices; } #------------------------------------------------------------------------------ # $graph is a Graph.pm. # Replace each vertex by a star of N vertices. # Existing edges become edges between an arm of the new stars. # All vertices must be degree <= N-1 (the arms of the stars) # # Key/value options are # # edges_between => $integer, default 1 # Number of edges in connections between new stars. # Default 1 is replacing each edge by an edge between the stars. # > 1 means extra vertices for those connections. # 0 means the stars have a vertex in common for existing edges. # sub Graph_star_replacement { my ($graph, $N, %options) = @_; my $new_graph = $graph->new (undirected => $graph->is_undirected); my $edges_between = $options{'edges_between'} // 1; ### $edges_between my $upto = 1; my %v_to_arms; foreach my $v ($graph->vertices) { my $centre = $upto++; foreach my $i (2 .. $N) { my $arm = $upto++; $new_graph->add_edge($centre,$arm); push @{$v_to_arms{$v}}, $arm; } } foreach my $edge ($graph->edges) { my ($u,$v) = @$edge; $u = (pop @{$v_to_arms{$u}}) // croak "oops, degree > $N"; $v = (pop @{$v_to_arms{$v}}) // croak "oops, degree > $N"; if ($edges_between == 0) { Graph_merge_vertices($new_graph, $u, $v); } else { my @between = map {my $b = $upto++; $b} 2 .. $edges_between; $new_graph->add_path($u, @between, $v); } } if (defined (my $name = $graph->get_graph_attribute('name'))) { my $append = ", $N-star rep"; if ($name =~ /\Q$append\E$/) { $name .= ' 2'; } elsif ($name =~ s{(\Q$append\E )(\d+)$}{$1.($2+1)}e) { } else { $name .= $append; } $graph->set_graph_attribute (name => $name); ### $name } return $new_graph; } sub _closest_xy_pair { my ($aref, $bref) = @_; if (@$aref == 0 || @$bref == 0) { return; } my $min_a = 0; my $min_b = 0; my $min_norm; foreach my $a (0 .. $#$aref) { my ($ax,$ay) = split /,/, $aref->[$a]; foreach my $b (0 .. $#$bref) { my ($bx,$by) = split /,/, $bref->[$b]; my $norm = ($ax-$bx)**2 + ($ay-$by)**2; if (! defined $min_norm || $norm < $min_norm) { $min_a = $a; $min_b = $b; $min_norm = $norm; } } } return (splice(@$aref, $min_a, 1), splice(@$bref, $min_b, 1)); } # Graph_merge_vertices($graph, $v, $v2, $v3, ...) # $graph is a Graph.pm # Modify $graph to merge all the given vertices into one. # Edges going to any of them are moved to go to $v, and the rest deleted. # Only for undirected graphs currently. # sub Graph_merge_vertices { my $graph = shift; $graph->expect_undirected; my $v = shift; foreach my $other (@_) { ### Graph_merge_vertices(): "$v, $other" foreach my $neighbour ($graph->neighbours($other)) { ### $neighbour unless ($neighbour eq $v) { $graph->add_edge ($v, $neighbour); } } $graph->delete_vertex($other); } } # $graph is a Graph.pm. # Replace each vertex by an N-cycle. # Existing edges become edges between vertices of the cycles, consecutively # around the cycle. # sub Graph_cycle_replacement { my ($graph, $N, %options) = @_; my $edges_between = $options{'edges_between'} // 1; my $vertex_name_type = $graph->get_graph_attribute('vertex_name_type') // ''; my $xy = ($vertex_name_type =~ /^xy/) && $N==4; ### $vertex_name_type ### $xy my $new_graph = $graph->new (undirected => $graph->is_undirected); my $upto = 1; my %v_to_arms; foreach my $v ($graph->vertices) { my @c; if ($xy) { my ($x,$y) = split /,/,$v; $x *= $edges_between+4; $y *= $edges_between+4; @c = ( ($x+1).','.($y+1), ($x-1).','.($y+1), ($x-1).','.($y-1), ($x+1).','.($y-1) ); } else { @c = map {my $c = $upto++; $c} 1 .. $N; } foreach my $c (@c) { die if $new_graph->has_vertex($c); } $new_graph->add_cycle(@c); $v_to_arms{$v} = \@c; } foreach my $edge ($graph->edges) { my ($u,$v) = @$edge; my @between; if ($xy) { ($u,$v) = _closest_xy_pair($v_to_arms{$u}, $v_to_arms{$v}) or croak "oops, degree > $N"; # $u = (pop @{$v_to_arms{$u}}) // croak "oops, degree > $N"; # $v = (pop @{$v_to_arms{$v}}) // croak "oops, degree > $N"; my ($ux,$uy) = split /,/,$u; my ($vx,$vy) = split /,/,$v; @between = map { my $x = $ux + ($vx-$ux)/($edges_between+2); my $y = $uy + ($vy-$uy)/($edges_between+2); my $b = "$x,$y"; die if $new_graph->has_vertex($b); $b; } 1 .. $edges_between; } else { $u = (shift @{$v_to_arms{$u}}) // croak "oops, degree > $N"; $v = (shift @{$v_to_arms{$v}}) // croak "oops, degree > $N"; @between = map {my $b = $upto++; $b} 1 .. $edges_between; } if ($edges_between == 0) { Graph_merge_vertices($new_graph, $u, $v); } else { $new_graph->add_path($u, @between, $v); } } if (defined (my $name = $graph->get_graph_attribute('name'))) { my $append = ", $N-star rep"; if ($name =~ /\Q$append\E$/) { $name .= ' 2'; } elsif ($name =~ s{(\Q$append\E )(\d+)$}{$1.($2+1)}e) { } else { $name .= $append; } $new_graph->set_graph_attribute (name => $name); } $new_graph->set_graph_attribute('vertex_name_type', $vertex_name_type); return $new_graph; } #------------------------------------------------------------------------------ # $graph is a Graph.pm. # Return a list of vertices which are a path achieving the eccentricity of $u. # # FIXME: is $graph->longest_path args ($u,$v) a documented feature? sub Graph_eccentricity_path { my ($graph, $u) = @_; $graph->expect_undirected; my $max = 0; my $max_v; for my $v ($graph->vertices) { next if $u eq $v; my $len = $graph->path_length($u, $v); if (defined $len && (! defined $max || $len > $max)) { $max = $len; $max_v = $v; } } return $graph->longest_path($u,$max_v); } #------------------------------------------------------------------------------ # $graph is a Graph.pm undirected tree. # Return ($eccentricity, $vertex,$vertex) which is the centre 1 or 2 vertex # names and their eccentricity. # Only tested on bicentral trees. # FIXME: the return is not eccentricity but num vertices to reach maximum? sub Graph_tree_centre_vertices { my ($graph) = @_; { my $eccentricity = 0; my %seen; my %unseen = map {$_=>1} $graph->vertices; my @prev_unseen; for (;;) { ### seen: join(' ',keys %seen) ### unseen: join(' ',keys %unseen) ### $eccentricity %unseen or last; $eccentricity++; @prev_unseen = keys %unseen; my @leaves; foreach my $v (@prev_unseen) { my @neighbours = grep {! exists $seen{$_}} $graph->neighbours($v); if (@neighbours <= 1) { push @leaves, $v; } } ### @leaves delete @unseen{@leaves}; # leaf nodes go from unseen to seen @seen{@leaves} = (); } return ($eccentricity, @prev_unseen); } { $graph = $graph->copy; my @prev_vertices; for (;;) { my @vertices = $graph->vertices or last; @prev_vertices = @vertices; my @leaves = grep {$graph->degree($_) <= 1} @vertices; $graph->delete_vertices(@leaves); } return @prev_vertices; } } # $graph is an undirected connected Graph.pm. # Return a list of its leaf vertices. # sub Graph_leaf_vertices { my ($graph) = @_; return grep {$graph->vertex_degree($_)<=1} $graph->vertices; } # $graph is a Graph.pm undirected tree. # Return a list of vertices which attain the diameter of tree $graph. # sub Graph_tree_diameter_path { my ($graph) = @_; if ($graph->vertices == 0) { return; } my ($eccentricity, @centres) = Graph_tree_centre_vertices($graph); ### @centres my @paths = ([ $centres[0] ]); my @prev_paths = @paths; for (;;) { ### paths: map {join(',',@$_)} @paths my @new_paths; foreach my $path (@paths) { my $v = $path->[-1]; foreach my $neighbour ($graph->neighbours($v)) { next if @$path>=2 && $neighbour eq $path->[-2]; push @new_paths, [@$path,$neighbour]; } } if (@new_paths) { @prev_paths = @paths; @paths = @new_paths; } else { last; } } my $path = shift @paths; ### final path: join(',',@$path) push @paths, @prev_paths; if (@paths) { foreach my $other_path (@paths) { ### final path: join(',',@$path) ### consider other: join(',',@$other_path) if (@$other_path < 2 || $other_path->[1] ne $path->[1]) { my @join = reverse @$path; pop @join; push @join, @$other_path; ### join to: join(',',@join) $path = \@join; last; } } } ### $eccentricity ### path length: scalar(@$path) scalar(@$path) == 2*$eccentricity - (@centres==1) or die "oops"; return @$path; } # $graph is an undirected connected Graph.pm. # Return the number of paths attaining the diameter of $graph. # A path u--v is counted just once, not also v--u. # sub Graph_diameter_count { my ($graph) = @_; if ($graph->vertices <= 1) { return 1; } my $diameter = 0; my $count = 0; $graph->for_shortest_paths(sub { my ($t, $u,$v, $n) = @_; my $len = $t->path_length($u,$v); if ($len > $diameter) { ### new high path length: $len $count = 0; $diameter = $len; } if ($len == $diameter) { $count++; ### equal high path length to count: $count } }); ### $diameter return ($graph->is_undirected ? $count/2 : $count); } # $graph is a Graph.pm. # Insert $n new vertices into each of its edges. # If $n omitted or undef then default 1 vertex in each edge. sub Graph_subdivide { my ($graph, $n) = @_; if (! defined $n) { $n = 1; } foreach my $edge ($graph->edges) { $graph->delete_edge (@$edge); my $prefix = "$edge->[0]-$edge->[1]-"; $graph->add_path ($edge->[0], (map {Graph_new_vertex_name($graph,$prefix)} 1 .. $n), $edge->[1]); } if ($n && $graph->edges && defined (my $name = $graph->get_graph_attribute('name'))) { $graph->set_graph_attribute (name => "$name subdivision".($n > 1 ? " $n" : "")); } return $graph; } #------------------------------------------------------------------------------ # Independence Number # $graph is a Graph.pm undirected tree or forest. # Return its independence number. # sub Graph_tree_indnum { my ($graph) = @_; ### Graph_tree_indnum: "num_vertices ".scalar($graph->vertices) $graph->expect_acyclic; $graph = $graph->copy; my $indnum = 0; my %exclude; OUTER: while ($graph->vertices) { foreach my $v ($graph->vertices) { my $degree = $graph->vertex_degree($v); next unless $degree <= 1; my ($u) = $graph->neighbours($v); ### consider: "$v degree $degree neighbours ".($u//'undef') if (delete $exclude{$v}) { ### exclude ... } else { ### leaf include ... $indnum++; if (defined $u) { $exclude{$u} = 1; } } $graph->delete_vertex($v); next OUTER; } die "oops, not a tree"; } return $indnum; } sub Graph_make_most_indomsets { my ($n) = @_; my $graph = Graph->new (undirected=>1); my $v = 0; while ($n > 0) { if ($v) { $graph->add_edge(0,$v) } # to x my $u = $v; my $size = 3 + (($n%3)!=0); foreach my $i (0 .. $size-1) { # triangle or complete-4 foreach my $j (0 .. $i-1) { $graph->add_edge($u+$i, $u+$j); } } $n -= $size; $v += $size; } return $graph; } sub Graph_is_indset { my ($graph,$aref) = @_; foreach my $from (@$aref) { foreach my $to (@$aref) { if ($graph->has_edge($from,$to)) { return 0; } } } return 1; } sub Graph_indnum_and_count { my ($graph) = @_; require Algorithm::ChooseSubsets; my @vertices = sort $graph->vertices; my $it = Algorithm::ChooseSubsets->new(\@vertices); my $indnum = 0; my $count = 0; while (my $aref = $it->next) { if (Graph_is_indset($graph,$aref)) { if (@$aref == $indnum) { $count++; } elsif (@$aref > $indnum) { $indnum = @$aref; $count = 1; } } } return ($indnum, $count); } #------------------------------------------------------------------------------ # Domination Number # Cockayne, Goodman, Hedetniemi, "A Linear Algorithm for the Domination # Number of a Tree", Information Processing Letters, volume 4, number 2, # November 1975, pages 41-44. # $graph is a Graph.pm undirected tree or forest. # Return its domination number. # sub Graph_tree_domnum { my ($graph) = @_; ### Graph_tree_domnum: "num_vertices ".scalar($graph->vertices) $graph->expect_acyclic; $graph = $graph->copy; my $domnum = 0; my %mtype = map {$_=>'bound'} $graph->vertices; OUTER: while ($graph->vertices) { foreach my $v ($graph->vertices) { my $degree = $graph->vertex_degree($v); next unless $degree <= 1; ### consider: $v ### $degree my ($u) = $graph->neighbours($v); if ($mtype{$v} eq 'free') { ### free, delete ... } elsif ($mtype{$v} eq 'bound') { ### bound ... if (defined $u) { ### set neighbour $u required ... $mtype{$u} = 'required'; } else { ### no neighbour, domnum++ ... $domnum++; } } elsif ($mtype{$v} eq 'required') { ### required, domnum++ ... $domnum++; if (defined $u && $mtype{$u} eq 'bound') { ### set neighbour $u free ... $mtype{$u} = 'free'; } } else { die; } delete $mtype{$v}; $graph->delete_vertex($v); next OUTER; } die "oops, not a tree"; } return $domnum; } #------------------------------------------------------------------------------ # Dominating Sets Count # with(n) = prod(child any) # sets including parent # undom(n) = prod(child dom) # sets without parent and parent undominated # dom(n) = prod(child with + dom) - prod(child dom) # # sets without parent and parent dominated # T(n) = with(n) + dom(n); # # with + dom = any - undom # # * # / | \ # * * * # /|\ /|\ /|\ # * * * * * * * * * # # path 1--2 2 with + 1 without = 3 any 0 without undom # path 1--2--3 3 with + 2 without = 5 any 1 without undom # 2 without dom # cannot e,1,3 # $graph is a Graph.pm tree. # Return the number of dominating sets in $graph. # sub Graph_tree_domsets_count { my ($graph) = @_; require Math::BigInt; $graph = $graph->copy; $graph->vertices || return 1; # empty graph my %data; my $one = Math::BigInt->new(1); OUTER: for (;;) { foreach my $v (sort $graph->vertices) { my $degree = $graph->vertex_degree($v); next unless $degree <= 1; # with(n) = prod(c any) # without(n) = prod(c with + dom = domsets) # without_undom(n) = prod(c dom); # my $c_with = $data{$v}->{'with'} // $one; my $c_without_undom = $data{$v}->{'without_undom'} // $one; my $c_without = $data{$v}->{'without'} // $one; my $c_without_dom = $c_without - $c_without_undom; my $c_domsets = $c_with + $c_without_dom; my $c_any = $c_with + $c_without; ### consider: "$v deg=$degree with $c_with, without $c_without, without_undom $c_without_undom" ### consider: " so without_dom=$c_without_dom domsets=$c_domsets any=$c_any" if ($degree == 0) { return $c_domsets; } my ($u) = $graph->neighbours($v); $data{$u}->{'with'} //= $one; $data{$u}->{'without'} //= $one; $data{$u}->{'without_undom'} //= $one; $data{$u}->{'with'} *= $c_any; $data{$u}->{'without'} *= $c_domsets; $data{$u}->{'without_undom'} *= $c_without_dom; delete $data{$v}; $graph->delete_vertex($v); next OUTER; } die "oops, not a tree $graph"; } # OUTER: for (;;) { # foreach my $v (sort $graph->vertices) { # my $degree = $graph->vertex_degree($v); # next unless $degree <= 1; # # $data{$v}->{'prod_c_any'} //= $one; # $data{$v}->{'prod_c_dom'} //= $one; # $data{$v}->{'prod_c_with_or_dom'} //= $one; # # # with(n) = prod(c any) # # undom(n) = prod(c dom); # # dom(n) = prod(c with + dom) - prod(c dom) # # # my $with = $data{$v}->{'prod_c_any'}; # my $undom = $data{$v}->{'prod_c_dom'}; # my $dom = $data{$v}->{'prod_c_with_or_dom'} - $undom; # my $ret = $with + $dom; # my $any = $ret + $undom; # # ### consider: "$v deg=$degree prods $data{$v}->{'prod_c_any'}, $data{$v}->{'prod_c_dom'}, $data{$v}->{'prod_c_with_or_dom'}" # ### consider: " with $with dom $dom undom=$undom, ret $ret any $any" # # if ($degree == 0) { # return $ret; # } # # my ($u) = $graph->neighbours($v); # $data{$u}->{'prod_c_any'} //= $one; # $data{$u}->{'prod_c_dom'} //= $one; # $data{$u}->{'prod_c_with_or_dom'} //= $one; # $data{$u}->{'prod_c_any'} *= $any; # $data{$u}->{'prod_c_dom'} *= $dom; # $data{$u}->{'prod_c_with_or_dom'} *= $ret; # # delete $data{$v}; # $graph->delete_vertex($v); # next OUTER; # } # die "oops, not a tree $graph"; # } } # 1 2 3 4 5 # path 1,1,2,2,4,4,7,9,13,18,25,36,49 # 1 with=1=1+0 without=0=0+1 domsets=1+0 = 1 # 2 with=1=1+0 without=1=1+0 domsets=1+1 = 2 # 3 with=2=1+1 without=2=1+1 domsets=1+1 = 2 # 4 with=2=1+1 without=3=2+1 domsets=2+2 = 4 # 5 with=4=1+3 without=4=2+2 domsets=4+2 = 6 # # 1,3,4 without_dom # 2,4 without_dom # 1,3,5 with_unreq # 2,5 with_req # 1,3,4,5 not minimal # 2,4,5 with_unreq but itself not minimal # # $graph is a Graph.pm tree. # Return the number of minimal dominating sets in $graph. # sub Graph_tree_minimal_domsets_count { my ($graph) = @_; return tree_minimal_domsets_count_data_ret (Graph_tree_minimal_domsets_count_data($graph)); } # $graph is a Graph.pm tree. # Return a hashref of data counting minimal dominating sets in $graph. # sub Graph_tree_minimal_domsets_count_data { my ($graph) = @_; require Math::BigInt; $graph->vertices || return tree_minimal_domsets_count_data_initial(); # empty graph $graph = $graph->copy; my %data; foreach my $v ($graph->vertices) { $data{$v} = tree_minimal_domsets_count_data_initial(); } OUTER: for (;;) { foreach my $c (sort {$a cmp $b} $graph->vertices) { my $degree = $graph->vertex_degree($c); next unless $degree <= 1; my ($v) = $graph->neighbours($c) or return $data{$c}; # root $data{$v} //= tree_minimal_domsets_count_data_initial(); tree_minimal_domsets_count_data_product_into ($data{$v}, delete($data{$c}) // tree_minimal_domsets_count_data_initial()); $graph->delete_vertex($c); next OUTER; } die "oops, not a tree $graph"; } } sub tree_minimal_domsets_count_data_initial { my $zero = Math::BigInt->new(0); my $one = Math::BigInt->new(1); $zero = 0; $one = 1; return { with => $one, with_notreq => $one, with_min_notreq => $one, without_dom_sole => $zero, without_notsole => $one, without_undom => $one, }; } sub tree_minimal_domsets_count_data_ret { my ($data) = @_; return ($data ->{'with'} - $data->{'with_notreq'} + $data->{'with_min_notreq'} + $data->{'without_dom_sole'} + $data->{'without_notsole'} - $data->{'without_undom'}); } # The args are 0 or more tree_minimal_domsets hashrefs. # Return their product. This is a tree_minimal_domsets hashref for a # vertex which has the given args as child vertices. # sub tree_minimal_domsets_count_data_product { return tree_minimal_domsets_count_data_product_into (tree_minimal_domsets_count_data_initial(), @_); } # $p is a tree_minimal_domsets hashref and zero or more further args likewise # which are children of $p. # Return their product for $p with those children. # sub tree_minimal_domsets_count_data_product_into { ### tree_minimal_domsets_count_data_product_into() ... my $p = shift; ### $p foreach my $v (@_) { # ### $v my $v_with_notmin_notreq = $v->{'with_notreq'} - $v->{'with_min_notreq'}; my $v_without_dom_notsole = $v->{'without_notsole'} - $v->{'without_undom'}; my $v_without_dom = $v->{'without_dom_sole'} + $v_without_dom_notsole; my $v_mindom = ($v->{'with'} - $v_with_notmin_notreq # with_min + $v_without_dom); my $v_with_req = $v->{'with'} - $v->{'with_notreq'}; $p->{'with'} *= $v_with_req + $v->{'without_notsole'}; $p->{'with_notreq'} *= $v_with_req + $v_without_dom_notsole; $p->{'with_min_notreq'} *= $v_without_dom_notsole; $p->{'without_dom_sole'} = ($p->{'without_dom_sole'} * $v_without_dom + $p->{'without_undom'} * $v_with_notmin_notreq); $p->{'without_notsole'} *= $v_mindom; $p->{'without_undom'} *= $v_without_dom; } return $p; } # $graph is a Graph.pm. # $aref is an arrayref of vertex names. # Return true if these vertices are a dominating set in $graph. # sub Graph_is_domset { my ($graph, $aref) = @_; my %vertices; @vertices{$graph->vertices} = (); delete @vertices{@$aref, map {$graph->neighbours($_)} @$aref}; return keys(%vertices) == 0; } # $graph is a Graph.pm. # $aref is an arrayref of vertex names. # Return true if these vertices are minimal for the amount of $graph they # dominate, meaning any vertex removed would reduce the amount of $graph # dominated. # sub Graph_domset_is_minimal { my ($graph, $aref) = @_; my %count; foreach my $v (@$aref) { foreach my $d ($v, $graph->neighbours($v)) { $count{$d}++; } } V: foreach my $v (@$aref) { foreach my $d ($v, $graph->neighbours($v)) { if ($count{$d} < 2) { next V; } } return 0; # $v and neighbours all count >=2 } return 1; } # $graph is a Graph.pm. # $aref is an arrayref of vertex names. # Return true if these vertices are a minimal dominating set in $graph. # sub Graph_is_minimal_domset { my ($graph, $aref) = @_; return Graph_is_domset($graph,$aref) && Graph_domset_is_minimal($graph,$aref); } # Return the number of minimal dominating sets in $graph by iterating # through all vertex sets and testing by the Graph_is_minimal_domset() # predicate. This is quite slow so suitable only for small number of # vertices. # sub Graph_minimal_domsets_count_by_pred { my ($graph) = @_; return Graph_sets_count_by_pred($graph, \&Graph_is_minimal_domset); } sub Graph_sets_count_by_pred { my ($graph, $func) = @_; require Algorithm::ChooseSubsets; my $count = 0; my @vertices = sort $graph->vertices; my $it = Algorithm::ChooseSubsets->new(\@vertices); while (my $aref = $it->next) { if ($func->($graph,$aref)) { $count++; } } return $count; } sub Graph_sets_minimum_and_count_by_pred { my ($graph, $func) = @_; require Algorithm::ChooseSubsets; my @count; my $minsize = $graph->vertices; my @vertices = sort $graph->vertices; my $it = Algorithm::ChooseSubsets->new(\@vertices); while (my $aref = $it->next) { my $size = @$aref; next if $size > $minsize; if ($func->($graph,$aref)) { $count[$size]++; $minsize = min($minsize,$size); } } return ($minsize, $count[$minsize]); } #------------------------------------------------------------------------------ # Total Dominating Sets # $graph is a Graph.pm. # $aref is an arrayref of vertex names. # Return true if these vertices are a total dominating set in $graph. # Every vertex must have one of $aref as a neighbour. # Unlike a plain dominating set, $aref vertices to not dominate themselves, # they must have a neighbour in the set. # sub Graph_is_total_domset { my ($graph, $aref) = @_; my %vertices; @vertices{$graph->vertices} = (); delete @vertices{map {$graph->neighbours($_)} @$aref}; return keys(%vertices) == 0; } #------------------------------------------------------------------------------ # $graph is a Graph.pm and $sptg is its $graph->SPT_Dijkstra() tree. # Set $sptg vertex attribute "count" on each vertex $v which gives the count # of number of paths from SPT_Dijkstra_root to that $v. # sub Graph_SPT_counts { my ($graph,$sptg, %options) = @_; my $start = $sptg->get_graph_attribute('SPT_Dijkstra_root'); my $one = $options{'one'} || 1; $sptg ->set_vertex_attribute ($start,'count',$one); foreach my $from (sort {($sptg->get_vertex_attribute($a,'weight') || 0) <=> ($sptg->get_vertex_attribute($b,'weight') || 0)} $sptg->vertices) { my $target_distance = ($sptg->get_vertex_attribute($from,'weight') || 0) + 1; my $from_count = $sptg ->get_vertex_attribute($from,'count'); ### from: $from . ' weight ' .($sptg->get_vertex_attribute($from,'weight') || 0) ### $from_count ### $target_distance foreach my $to ($graph->neighbours($from)) { if (($sptg->get_vertex_attribute($to,'weight') || 0) == $target_distance) { ### to: $to . ' weight ' .($sptg->get_vertex_attribute($to,'weight') || 0) $sptg ->set_vertex_attribute ($to,'count', $from_count + ($sptg ->get_vertex_attribute($to,'count') || 0)); } else { ### skip: $to . ' weight ' .($sptg->get_vertex_attribute($to,'weight') || 0) } } } } #------------------------------------------------------------------------------ # Cycles sub Graph_is_cycle { my ($graph, $aref) = @_; foreach my $i (0 .. $#$aref) { $graph->has_edge($aref->[$i], $aref->[$i-1]) or return 0; } return 1; } # $graph is a Graph.pm. Find all cycles in it. # The return is a list of arrayrefs, with each arrayref containing vertices # which are a cycle. # Each cycle appears just once, so just one direction around, not both ways. # # The order of vertices within each cycle and the order of cycles in the # return are both unspecified. Within each cycle has a canonical order, but # don't rely on that. The order of cycles is hash-random. # sub Graph_find_all_cycles { my ($graph) = @_; my @paths = map {[$_]} $graph->vertices; my @cycles; while (@paths) { ### num paths: scalar @paths my @new_paths; foreach my $path (@paths) { NEIGHBOUR: foreach my $next ($graph->neighbours($path->[-1])) { next if $next lt $path->[0]; # must have start smallest if ($next eq $path->[0]) { # back to start, len=1 or >=3 Graph_is_cycle($graph, $path) or die; if (@$path!=2 && $path->[1] lt $path->[-1]) { # direction smaller second only push @cycles, $path; } } else { foreach my $i (1 .. $#$path) { next NEIGHBOUR if $next eq $path->[$i]; # back to non-start } push @new_paths, [ @$path, $next ]; } } } @paths = @new_paths; } return @cycles; } sub Graph_num_cycles { my ($graph) = @_; my @cycles = Graph_find_all_cycles($graph); return scalar @cycles; } # Return true if $graph has a bi-cyclic component, meaning a connected # component with 2 or more cycles in it. sub Graph_has_bicyclic_component { my ($graph) = @_; my @components = $graph->connected_components; foreach my $component (@components) { my $subgraph = $graph->subgraph($component); if (MyGraphs::Graph_num_cycles($subgraph) >= 2) { return 1; } } return 0; } # length of the smallest cycle in $graph sub Graph_girth { my ($graph) = @_; ### Graph_girth() ... my $num_vertices = scalar $graph->vertices; my $girth; my $min = $graph->is_directed ? 1 : 3; OUTER: foreach my $from ($graph->vertices) { ### $from my %seen = ($from => 1); my @pending = ($from); foreach my $len (1 .. ($girth||$num_vertices)) { ### at: "len=$len pending=".join(' ',@pending) my @new_pending; foreach my $to (map {$graph->successors($_)} @pending) { if ($len>=$min && $to eq $from) { ### cycle: "to=$to len=$len" if (!defined $girth || $len < $girth) { ### is new low ... $girth = $len; } next OUTER; } unless ($seen{$to}++) { push @new_pending, $to; } } @pending = @new_pending; } } return $girth; } # $graph is an undirected Graph.pm. # If $v is in a hanging cycle, other than the attachment point, then return # an arrayref of the vertices of that cycle other than the attachment point # (in an unspecified order). # For example, # # 4---5 # \ / # 1---2---3---6 # # has hanging cycle 3,4,5. $v=4 or $v=5 gives return is [4,5]. # If $v is not in a hanging cycle then return undef. # sub Graph_is_hanging_cycle { my ($graph, $v) = @_; if ($graph->degree($v) != 2) { return undef; } my %cycle = ($v => 1); my @pending = $graph->neighbours($v); my @end; while (@pending) { $v = pop @pending; next if $cycle{$v}; if ($graph->degree($v) != 2) { push @end, $v; next; } $cycle{$v} = 1; push @pending, $graph->neighbours($v); } if (@end == 0 || (@end==2 && $end[0] eq $end[1])) { return [ keys %cycle ]; } else { return undef; } } # $graph is an undirected Graph.pm. # Modify $graph to remove any hanging cycles. # For example, # # 4---5 # \ / # 1---2---3---6 # # has hanging cycle 3,4,5. Vertices 4,5 are removed. # sub Graph_delete_hanging_cycles { my ($graph) = @_; my $count = 0; MORE: for (;;) { foreach my $v ($graph->vertices) { if (my $aref = Graph_is_hanging_cycle($graph,$v)) { $graph->delete_vertices(@$aref); $count++; next MORE; } } last; } if ($count && defined(my $name = $graph->get_graph_attribute('name'))) { $graph->set_graph_attribute (name => "$name, stripped hanging"); } return $count; } # d-----c # | | # a-----b sub Graph_find_all_4cycles { my ($graph, %options) = @_; ### Graph_find_all_4cycles() ... my $callback = $options{'callback'} || sub{}; my %seen; foreach my $a (sort $graph->vertices) { my @a_neighbours = $graph->neighbours($a); ### a: "$a to ".join(',',@a_neighbours) foreach my $b (@a_neighbours) { next if $b eq $a; # ignore self-loops my @b_neighbours = $graph->neighbours($b); if (! $graph->has_edge($a,$b)) { print " a=$a\n"; foreach my $neighbour (@a_neighbours) { print " $neighbour\n"; } die "oops, no edge $a to $b"; } foreach my $c (@b_neighbours) { next if $c eq $a; next if $c eq $b; my @c_neighbours = $graph->neighbours($c); if (! $graph->has_edge($b,$c)) { die "oops"; } foreach my $d (@c_neighbours) { if (! $graph->has_edge($c,$d)) { die "oops"; } next if $d eq $a; next if $d eq $b; next if $d eq $c; my @d_neighbours = $graph->neighbours($d); ### $d ### cycle: "$a $b $c $d goes ".join(',',@d_neighbours) next unless $graph->has_edge($d,$a) || $graph->has_edge($a,$d); next if $seen{$a,$b,$c,$d}++; next if $seen{$b,$c,$d,$a}++; next if $seen{$c,$d,$a,$b}++; next if $seen{$d,$a,$b,$c}++; next if $seen{$d,$c,$b,$a}++; next if $seen{$c,$b,$a,$d}++; next if $seen{$b,$a,$d,$c}++; next if $seen{$a,$d,$c,$b}++; # print "raw ",join(' -- ',($a,$b,$c,$d)),"\n"; # print " has_edge ",$graph->has_edge($a,$b),"\n"; # print " has_edge ",$graph->has_edge($b,$c),"\n"; # print " has_edge ",$graph->has_edge($c,$d),"\n"; # print " has_edge ad ",$graph->has_edge($d,$a),"\n"; # must not mutate the loop variables $a,$b,$c,$d, so @cycle my @cycle = ($a,$b,$c,$d); my $min = minstr(@cycle); while ($cycle[0] ne $min) { # rotate to $cycle[0] the minimum push @cycle, (shift @cycle); } $callback->(@cycle); } } } } return; } #------------------------------------------------------------------------------ # Euler Cycle # Return a list of vertices v1,v2,...,vn,v1 which is an Euler cycle, so # traverse each edge exactly once. # sub Graph_Euler_cycle { my ($graph, %options) = @_; my $type = $options{'type'} || 'cycle'; ### $type my @vertices = $graph->vertices; my $func = cmp_func(@vertices); @vertices = sort $func @vertices; my @edges = $graph->edges; my $num_edges = scalar(@edges); my @edge_keys = map {join(' to ',@$_)} @edges; my %edge_keys = map { my $key = join(' to ',@$_); ($key => $key, join(' to ',reverse @$_) => $key) } @edges; my %neighbours; foreach my $v (@vertices) { $neighbours{$v} = [ sort $func $graph->neighbours($v) ]; } my @path = $vertices[0]; my $try; $try = sub { my ($visited) = @_; if (scalar(keys %$visited) >= $num_edges) { return 1; } my $v = $path[-1]; foreach my $to (@{$neighbours{$v}}) { my $edge = $edge_keys{"$v to $to"}; next if $visited->{$edge}; push @path, $to; if ($try->({ %$visited, $edge => 1 })) { return 1; } pop @path; } return 0; }; if ($try->({})) { return @path; } else { return; } # my @path; # my %visited; # my $v = $vertices[0]; # my @nn = (-1); # my $upto = 0; # for (;;) { # my $v = $path[$upto]; # my $n = ++$nn[$upto]; # my $to = $neighbours{$v}->[$n]; # ### at: join('--',@path) . " upto=$upto v=$v n=$n" # ### $to # ### assert: 0 <= $n && $n <= $#{$neighbours{$v}}+1 # if (! defined $to) { # ### no more neighbours, backtrack ... # $visited{$v} = 0; # $upto--; # last if $upto < 0; # next; # } # if ($visited{$to}) { # ### to is visited ... # if ($upto == $num_vertices-1 # && ($type eq 'path' # || $to eq $path[0])) { # ### found path or cycle ... # if ($options{'verbose'}) { print "found ",join(',',@path),"\n"; } # if ($options{'found_coderef'}) { $options{'found_coderef'}->(@path); } # if (! $options{'all'}) { return 1; } # } # next; # } # # # extend path to $to # $upto++; # $path[$upto] = $to; # $visited{$to} = 1; # $nn[$upto] = -1; # } } #------------------------------------------------------------------------------ # Hamiltonian Cycle # $graph is a Graph.pm. # Return true if it has a Hamiltonian cycle (a cycle visiting all vertices # once each). Key/value options are # # type => "cycle" or "path" (default "cycle") # # type "path" means search for a Hamiltonian path (a path visiting all # vertices once each). # # Currently this is a depth first search so quite slow and suitable only for # a small number of vertices. # sub Graph_is_Hamiltonian { my ($graph, %options) = @_; my $type = $options{'type'} || 'cycle'; ### $type my @vertices = $graph->vertices; my $num_vertices = scalar(@vertices); my %neighbours; foreach my $v (@vertices) { $neighbours{$v} = [ $graph->neighbours($v) ]; } foreach my $start (defined $options{'start'} ? $options{'start'} : $type eq 'path' ? @vertices : $vertices[0]) { if ($options{'verbose'}) { print "try start $start\n"; } my @path = ($start); my %visited = ($path[0] => 1); my @nn = (-1); my $upto = 0; for (;;) { my $v = $path[$upto]; my $n = ++$nn[$upto]; my $to = $neighbours{$v}->[$n]; ### at: join('--',@path) . " upto=$upto v=$v n=$n" ### $to ### assert: 0 <= $n && $n <= $#{$neighbours{$v}}+1 if (! defined $to) { ### no more neighbours, backtrack ... $visited{$v} = 0; $upto--; last if $upto < 0; next; } if ($visited{$to}) { ### to is visited ... if ($upto == $num_vertices-1 && ($type eq 'path' || $to eq $path[0])) { ### found path or cycle ... if ($options{'verbose'}) { print "found ",join(',',@path),"\n"; } if ($options{'found_coderef'}) { $options{'found_coderef'}->(@path); } if (! $options{'all'}) { return 1; } } next; } # extend path to $to $upto++; $path[$upto] = $to; $visited{$to} = 1; $nn[$upto] = -1; } } return 0; } #------------------------------------------------------------------------------ # Directed Graphs # $graph is a directed Graph.pm. # Return the number of maximal paths. # A maximal path is from a predecessorless to a successorless. # There might be multiple paths between a given predecessorless and # successorless. All such paths are counted. # sub Graph_num_maximal_paths { my ($graph) = @_; ### Graph_num_maximal_paths() ... $graph->expect_directed; my %indegree_remaining; my %ways; my %pending; foreach my $v ($graph->vertices) { $pending{$v} = 1; if ($indegree_remaining{$v} = $graph->in_degree($v)) { $ways{$v} = 0; } else { $ways{$v} = 1; } } my $ret = 0; while (%pending) { ### at pending: scalar(keys %pending) my $progress; foreach my $v (keys %pending) { if ($indegree_remaining{$v}) { ### not ready: "$v indegree_remaining $indegree_remaining{$v}" ### assert: $indegree_remaining{$v} >= 0 next; } delete $pending{$v}; my @successors = $graph->successors($v); if (@successors) { foreach my $to (@successors) { ### edge: "$v to $to countedge ".$graph->get_edge_count($v,$to) $pending{$to} or die "oops, to=$to not pending"; $ways{$to} += $ways{$v} * $graph->get_edge_count($v,$to); $indegree_remaining{$to}--; $progress = 1; } } else { # successorless $ret += $ways{$v}; } } if (%pending && !$progress) { die "Graph_num_maximal_paths() oops, no progress, circular graph"; } } return $ret; } #------------------------------------------------------------------------------ # Lattices # $graph is a directed Graph.pm. # Return the number of pairs of comparable elements $u,$v, meaning pairs # where there is a path from $u to $v. The count includes $u,$u empty path. # For a lattice graph, this is the number of "intervals" in the lattice. # sub Graph_num_intervals { my ($graph) = @_; my $ret = 0; foreach my $v ($graph->vertices) { $ret += 1 + $graph->all_successors($v); } return $ret; } sub Graph_successors_matrix { my ($graph, $vertices_aref, $vertex_to_index_href) = @_; ### $vertices_aref ### $vertex_to_index_href my @ret; foreach my $i_from (0 .. $#$vertices_aref) { foreach my $to ($graph->successors($vertices_aref->[$i_from])) { my $i_to = $vertex_to_index_href->{$to} // die "oops, not found: $to"; $ret[$i_from]->[$i_to] = 1; } } return \@ret; } sub Graph_reachable_matrix { my ($graph, $vertices_aref, $vertex_to_index_href) = @_; my $ret = Graph_successors_matrix($graph,$vertices_aref,$vertex_to_index_href); foreach my $i (0 .. $#$vertices_aref) { $ret->[$i]->[$i] = 1; } my $more = 1; while ($more) { $more = 0; foreach my $i (0 .. $#$vertices_aref) { foreach my $j (0 .. $#$vertices_aref) { foreach my $k (0 .. $#$vertices_aref) { if ($ret->[$i]->[$j] && $ret->[$j]->[$k] && ! $ret->[$i]->[$k]) { $ret->[$i]->[$k] = 1; $more = 1; } } } } } return $ret; } # $graph is a directed Graph.pm which is a lattice. # Return its "intervals lattice". # # An interval is a pair [$x,$y] with $y reachable from $x. # Each vertex of the intervals lattice is such an interval, in the form of a # string "$x-$y". Edges are from "$x-$y" to "$u-$v" where $x < $u and $y < $v, # where < means $u reachable from $x, and $v reachable from $y. # sub Graph_make_intervals_lattice { my ($graph, $covers) = @_; $graph->expect_directed; my $intervals = Graph->new; my @vertices = $graph->vertices; my %vertex_to_index; @vertex_to_index{@vertices} = (0 .. $#vertices); my $graph_reachable = Graph_reachable_matrix($graph, \@vertices, \%vertex_to_index); ### $graph_reachable sum(map{sum(map {$_||0} @$_)} @$graph_reachable) == Graph_num_intervals($graph) or die; my %intervals; foreach my $a (0 .. $#vertices) { foreach my $b (0 .. $#vertices) { next unless $graph_reachable->[$a]->[$b]; my $from = "$vertices[$a]-$vertices[$b]"; $intervals->add_vertex($from); $intervals{$from} = [$a,$b]; } } foreach my $from (keys %intervals) { my $from_aref = $intervals{$from}; foreach my $to (keys %intervals) { next if $to eq $from; my $to_aref = $intervals{$to}; next unless $graph_reachable->[$from_aref->[0]]->[$to_aref->[0]]; next unless $graph_reachable->[$from_aref->[1]]->[$to_aref->[1]]; ### $from ### $to # print "$a $b $c $d\n"; # next if $covers && defined $intervals->path_length($from,$to); $intervals->add_edge($from, $to); } } return $covers ? Graph_covers($intervals) : $intervals; # $graph->expect_directed; # my $intervals = Graph->new; # foreach my $a ($graph->vertices) { # foreach my $b ($graph->vertices) { # next unless defined $graph->path_length($a,$b); # my $from = "$a -- $b"; # # foreach my $c ($graph->vertices) { # next unless defined $graph->path_length($a,$c); # foreach my $d ($graph->vertices) { # next unless defined $graph->path_length($c,$d); # next unless defined $graph->path_length($b,$d); # my $to = "$c -- $d"; # next if $to eq $from; # # print "$a $b $c $d\n"; # next if $covers && defined $intervals->path_length($from,$to); # $intervals->add_edge($from, $to); # } # } # } # } # return $covers ? Graph_covers($intervals) : $intervals; } # $graph is a directed Graph.pm which is expected to be acyclic. # Delete edges to leave just its cover relations. # # At some from->to, if there is also from->mid->to then edge from->to is not # a cover and is deleted. # sub Graph_covers { my ($graph) = @_; $graph->expect_acyclic; my @vertices = $graph->vertices; my %vertex_to_index; @vertex_to_index{@vertices} = (0 .. $#vertices); my $reachable = Graph_reachable_matrix($graph, \@vertices, \%vertex_to_index); foreach my $from (0 .. $#vertices) { foreach my $mid (0 .. $#vertices) { next if $from == $mid; next unless $reachable->[$from]->[$mid]; foreach my $to (0 .. $#vertices) { next if $mid == $to; next unless $reachable->[$mid]->[$to]; $graph->delete_edge($vertices[$from],$vertices[$to]); } } } return $graph; } # $graph is a directed Graph.pm which is expected to be a lattice. # Return its unique lowest element. sub Graph_lattice_lowest { my ($graph) = @_; my @predecessorless = $graph->predecessorless_vertices; @predecessorless==1 or die "Graph_lattice_lowest() oops, expected one predecessorless"; return $predecessorless[0]; } # $graph is a directed Graph.pm which is expected to be a lattice. # Return its unique highest element. sub Graph_lattice_highest { my ($graph) = @_; my @successorless = $graph->successorless_vertices; @successorless==1 or die "Graph_lattice_highest() oops, expected one successorless"; return $successorless[0]; } # $graph is a directed Graph.pm which is expected to be a lattice. # Return $href where # $href->{'max'}->{$x}->{$y} is the lattice max($x,y) # $href->{'min'}->{$x}->{$y} is the lattice min($x,y) # sub Graph_lattice_minmax_hash { my ($graph) = @_; my $verbose = 1; my %hash; my @vertices = $graph->vertices; foreach my $elem (['all_successors','max'], ['all_predecessors','min']) { my ($all_method, $key) = @$elem; # $all_successors{$x}->{$y} = boolean, true x has y after it, false if not. # x is a successor of itself ($graph->all_successors doesn't include x # itself). my %all_successors; foreach my $x (@vertices) { $all_successors{$x}->{$x} = 1; foreach my $s ($graph->$all_method($x)) { $all_successors{$x}->{$s} = 1; } } # For each pair x,y look at the common successors and choose the smallest. # Smallest in the sense the smaller has bigger among its successors. foreach my $x (@vertices) { my $xs_href = $all_successors{$x}; foreach my $y (@vertices) { my $ys_href = $all_successors{$y}; my $m; foreach my $xs (keys %$xs_href) { if ($ys_href->{$xs}) { # common successor if (!defined $m || $all_successors{$xs}->{$m}) { $m = $xs; # which is before best $m so far } } } $hash{$key}->{$x}->{$y} = $m; } } } return \%hash; # foreach my $v (@vertices) { # $hash{'max'}->{$v}->{$v} # = $hash{'min'}->{$v}->{$v} = $v; # } # foreach my $x (@vertices) { # foreach my $y ($graph->all_successors($x)) { # $hash{'max'}->{$x}->{$y} # = $hash{'max'}->{$y}->{$x} = $y; # if ($verbose) { print "successor $x max $y = $y\n"; } # } # foreach my $y ($graph->all_predecessors($x)) { # $hash{'min'}->{$x}->{$y} # = $hash{'min'}->{$y}->{$x} = $y; # if ($verbose) { print "predecessor $x min $y = $y\n"; } # } # } # my $more = 1; # while ($more) { # $more = 0; # foreach my $M ('min','max') { # foreach my $x (@vertices) { # foreach my $y (@vertices) { # if (defined(my $m $hash{$M}->{$x}->{$y})) { # foreach my $z (@vertices) { # # if (defined(my $m = $hash{'max'}->{$y}->{$z})) { # $more = 1; # $hash{'max'}->{$x}->{$y} # = $hash{'max'}->{$y}->{$x} # = $m; # if ($verbose) { print "chain $x max $y = $m from $z\n"; } # } # } # } # if (! defined $hash{'min'}->{$x}->{$y}) { # foreach my $z ($graph->predecessors($y)) { # if (defined(my $m = $hash{'min'}->{$x}->{$z})) { # $more = 1; # $hash{'min'}->{$x}->{$y} # = $hash{'min'}->{$y}->{$x} # = $m; # if ($verbose) { print "chain $x min $y = $m from $z\n"; } # } # } # } # } # } # } # my $more = 1; # while ($more) { # $more = 0; # foreach my $x (@vertices) { # foreach my $y (@vertices) { # if (! defined $hash{'max'}->{$x}->{$y}) { # foreach my $z ($graph->successors($y)) { # if (defined(my $m = $hash{'max'}->{$x}->{$z})) { # $more = 1; # $hash{'max'}->{$x}->{$y} # = $hash{'max'}->{$y}->{$x} # = $m; # if ($verbose) { print "chain $x max $y = $m from $z\n"; } # } # } # } # if (! defined $hash{'min'}->{$x}->{$y}) { # foreach my $z ($graph->predecessors($y)) { # if (defined(my $m = $hash{'min'}->{$x}->{$z})) { # $more = 1; # $hash{'min'}->{$x}->{$y} # = $hash{'min'}->{$y}->{$x} # = $m; # if ($verbose) { print "chain $x min $y = $m from $z\n"; } # } # } # } # } # } # } # # return \%hash; } # $graph is a directed Graph.pm which is expected to be a lattice. # $href is a hashref as returned by Graph_lattice_minmax_hash(). # Check that the relations in $href follow the lattice rules. # die() if bad. # sub Graph_lattice_minmax_validate { my ($graph, $href) = @_; my $str = Graph_lattice_minmax_reason($graph,$href); if ($str) { die 'Graph_lattice_minmax_validate() ', $str; } } # $graph is a directed Graph.pm which is expected to be a lattice. # $href is a hashref as returned by Graph_lattice_minmax_hash(). # Check that the relations in $href follow the lattice rules. # If good then return empty string ''. # If bad then return a string describing the problem. # sub Graph_lattice_minmax_reason { my ($graph, $href) = @_; # defined foreach my $x ($graph->vertices) { foreach my $y ($graph->vertices) { foreach my $M ('min','max') { defined $href->{$M}->{$x}->{$y} or return "missing $x $M $y"; } } } # commutative foreach my $x ($graph->vertices) { foreach my $y ($graph->vertices) { foreach my $M ('min','max') { $href->{$M}->{$x}->{$y} eq $href->{$M}->{$y}->{$x} or return "not commutative $x $M $y"; } } } # idempotent foreach my $x ($graph->vertices) { foreach my $y ($graph->vertices) { foreach my $M ('min','max') { my $m = $href->{$M}->{$x}->{$y}; $href->{$M}->{$x}->{$m} eq $m or return "not idempotent $x $M $y"; } } } # absorptive a ^ (a v b) = a v (a ^ b) = a # L H foreach my $x ($graph->vertices) { foreach my $y ($graph->vertices) { my $min = $href->{'min'}->{$x}->{$y}; my $max = $href->{'max'}->{$x}->{$y}; my $a = $href->{'max'}->{$x}->{$min}; my $b = $href->{'min'}->{$x}->{$max}; ($a eq $x && $b eq $x) or return "not absorptive $x and $y min $min max $max got $a and $b"; } } # associative (xy)z = x(yz) foreach my $x ($graph->vertices) { foreach my $y ($graph->vertices) { foreach my $z ($graph->vertices) { foreach my $M ('min','max') { my $a = $href->{$M}->{$href->{$M}->{$x}->{$y}}->{$z}; my $b = $href->{$M}->{$x}->{$href->{$M}->{$y}->{$z}}; $a eq $b or return "not associative $x $M $y $M $z got $a and $b"; } } } } return ''; } # $graph is a directed Graph.pm which is a lattice. # $href is a hashref as returned by Graph_lattice_minmax_hash(). # Return true if $graph is semi-distributive. # sub lattice_minmax_is_semidistributive { my ($graph, $href) = @_; foreach my $x ($graph->vertices) { foreach my $y ($graph->vertices) { my $m = $href->{'min'}->{$x}->{$y}; my $M = $href->{'max'}->{$x}->{$y}; foreach my $z ($graph->vertices) { if ($m eq $href->{'min'}->{$x}->{$z}) { $href->{'min'}->{$x}->{$href->{'max'}->{$y}->{$z}} eq $m or return 0; } if ($M eq $href->{'max'}->{$x}->{$z}) { $href->{'max'}->{$x}->{$href->{'min'}->{$y}->{$z}} eq $M or return 0; } } } } } # $graph is a directed Graph.pm which is a lattice. # $href is a hashref as returned by Graph_lattice_minmax_hash(). # Return the number of complementary pairs in $graph. # A complementary pair is vertices u,v where # min(u,v) = global min and max(u,v) = global max # so they neither meet nor join other than the global min,max. # # u = global min and v = global max is always a complementary pair. # If the lattice is just 1 vertex then this includes u=v as a pair. # sub lattice_minmax_num_complementary_pairs { my ($graph, $href) = @_; my $lowest = MyGraphs::Graph_lattice_lowest($graph); my $highest = MyGraphs::Graph_lattice_highest($graph); my @vertices = $graph->vertices; my $count_complementary = 0; foreach my $i (0 .. $#vertices) { my $u = $vertices[$i]; foreach my $j ($i .. $#vertices) { my $v = $vertices[$j]; my $min = $href->{'min'}->{$u}->{$v}; my $max = $href->{'max'}->{$u}->{$v}; $count_complementary += ($min eq $lowest && $max eq $highest); } } return $count_complementary; } # Think not efficient to check pair-by-pair. # # # Return true if $u and $v are complementary, meaning their min is the # # bottom element and max is the top element. # sub lattice_is_complementary { # my ($graph, $u,$v) = @_; # return lattice_min($graph, $u,$v) eq Graph_lattice_lowest($graph) # && lattice_max($graph, $u,$v) eq Graph_lattice_highest($graph); # } # Is it efficient to search lattice min(x,y) or max(x,y), or better always # build whole table? # # sub lattice_min { # my ($graph, $u, $v) = @_; # return lattice_min_or_max($graph,$u,$v, 'predecessors', 'all_predecessors'); # } # sub lattice_max { # my ($graph, $u, $v) = @_; # return lattice_min_or_max($graph,$u,$v, 'successors', 'all_successors'); # } # sub lattice_min_or_max { # my ($graph, $u, $v, $immediate, $all) = @_; # # die "WRONG"; # # my @verts = ($u,$v); # my @verts_descendants; # foreach my $i (0,1) { # $verts_descendants[$i]->[0]->{$verts[$i]} = 1; # } # for (my $distance = 0; ; $distance++) { # foreach my $i (0,1) { # foreach my $from (keys %{$verts_descendants[$i]->[$distance]}) { # foreach my $to_distance (0 .. $distance) { # if ($verts_descendants[!$i]->[$to_distance]->{$from}) { # return $from; # } # } # } # } # foreach my $i (0,1) { # $verts_descendants[$i]->[$distance+1] # = graph_following_set_hashref($graph,$immediate, # $verts_descendants[$i]->[$distance]); # } # if (! $verts_descendants[0]->[$distance+1] # && ! $verts_descendants[1]->[$distance+1]) { # die "lattice_min_or_max() not found"; # } # } # # # my %v_successors; @v_successors{$v, $graph->$all($v)} = (); # hash slice # # my %t = ($u => 1); # # while (%t) { # # foreach my $t (keys %t) { # # if (exists $v_successors{$t}) { # # return $t; # # } # # } # # my %new_t; # # foreach my $t (keys %t) { # # @new_t{$graph->$immediate($t)} = (); # hash slice # # } # # %t = %new_t; # # } # # die "lattice_min_or_max() not found"; # } # sub graph_following_set_hashref { # my ($graph, $method, $href) = @_; # my %ret; # foreach my $v (keys %$href) { # @ret{$graph->$method($v)} = (); # hash slice # } # return \%ret; # } #------------------------------------------------------------------------------ 1; __END__ Math-PlanePath-129/devel/lib/Math/0002755000175000017500000000000014001441522014437 5ustar ggggMath-PlanePath-129/devel/lib/Math/SquareRadical.pm0000644000175000017500000001147613734026652017543 0ustar gggg# Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::SquareRadical; use 5.004; use strict; use Carp 'croak'; use Scalar::Util 'blessed'; use vars '$VERSION', '@ISA'; $VERSION = 129; # uncomment this to run the ### lines use Smart::Comments; use overload '""' => \&stringize; '0+' => \&numize; 'bool' => \&bool; # '<=>' => \&spaceship; 'neg' => \&neg; '+' => \&add, '-' => \&sub, '*' => \&mul, fallback => 1; sub new { my ($class, $int, $factor, $root) = @_; $factor ||= 0; $root ||= 0; unless ($root >= 0) { croak "Negative root for SquareRadical"; } return bless [ $int, $factor, $root ], $class; } sub bool { my ($self) = @_; ### bool(): @$self return $self->[0] || $self->[1]; } sub numize { my ($self) = @_; ### numize(): @$self return ($self->[0] + $self->[1]*sqrt($self->[2])) + 0; } sub stringize { my ($self) = @_; ### stringize(): @$self my $factor = $self->[1]; if ($factor == 0) { return "$self->[0]"; } else { return "$self->[0]".($factor >= 0 ? '+' : '').$factor."*sqrt($self->[2])"; } } # a+b*sqrt(c) <=> d # b*sqrt(c) <=> d-a # b^2*c <=> (d-a)^2 # if both same sign # # a+b*sqrt(c) <=> d+e*sqrt(f) # (a-d)+b*sqrt(c) <=> e*sqrt(f) # (a-d)^2 + 2*(a-d)*b*sqrt(c) + b^2*c <=> e^2*f # 2*(a-d)*b*sqrt(c) <=> e^2*f - b^2*c - (a-d)^2 # 4*(a-d)^2*b^2*c <=> (e^2*f - b^2*c - (a-d)^2)^2 # sub spaceship { my ($self, $other) = @_; ### spaceship() ... if (blessed($other) && $other->isa('Math::SquareRadical')) { if ($self->[1] != $other->[1]) { croak "Different roots"; } return bless [ $self->[0] + $other->[0], $self->[1] + $other->[1] ]; } else { my $factor = $self->[1]; my $rhs = ($other - $self->[0]); return (($rhs < 0) <=> ($factor < 0) || (($factor*$factor*$self->[2] <=> $rhs*$rhs) * ($rhs < 0 ? -1 : 1))); } } sub neg { my ($self) = @_; ### neg(): @$self return $self->new(- $self->[0], - $self->[1], $self->[2]); } # c = g^2*f # a+b*sqrt(c) + d+e*sqrt(f) # = a+d + b*g*sqrt(f) + e*sqrt(f) # = (a+d) + (b*g + e)*sqrt(f) # sub add { my ($self, $other) = @_; ### add(): @$self if (blessed($other) && $other->isa('Math::SquareRadical')) { my $root1 = $self->[2]; my $root2 = $other->[2]; if ($root1 % $root2 == 0) { $self->new($self->[0] + $other->[0], ($root1/$root2)*$self->[1] + $other->[1], $root2); } elsif ($root1 % $root2 == 0) { $self->new($self->[0] + $other->[0], ($root1/$root2)*$self->[1] + $other->[1], $root2); } else { croak "Different roots"; } } else { return $self->new($self->[0] + $other, $self->[1], $self->[2]); } } # sub sub { # my ($self, $other, $swap) = @_; # my $ret; # if (blessed($other) && $other->isa('Math::SquareRadical')) { # if ($self->[1] != $other->[1]) { # croak "Different roots"; # } # $ret = bless [ $self->[0] - $other->[0], # $self->[1] - $other->[1] ]; # } else { # $ret = bless [ $self->[0] - $other, $self->[1] ]; # } # if ($swap) { # $ret->[0] = - $ret->[0]; # $ret->[1] = - $ret->[1]; # } # return $ret; # } # (a + b*sqrt(c))*(d + e*sqrt(f)) # = a*d + b*d*sqrt(c) + a*e*sqrt(f) + b*e*sqrt(c*f) # if c=g^2*f # = a*d + b*d*g*sqrt(f) + a*e*sqrt(f) + b*e*g*f sub mul { my ($self, $other) = @_; ### mul(): @$self if (blessed($other) && $other->isa('Math::SquareRadical')) { my $root1 = $self->[2]; my $root2 = $other->[2]; if ($root1 % $root2 == 0) { my $g2 = $root1/$root2; my $g = sqrt($g2); if ($g*$g == $g2) { $self->new($self->[0] + $other->[0], $g*$self->[1] + $other->[1], $root2); } } elsif ($root2 % $root1 == 0) { my $g2 = $root2/$root1; my $g = sqrt($g2); if ($g*$g == $g2) { $self->new($self->[0] + $other->[0], $self->[1] + $g*$other->[1], $root1); } } else { croak "Different roots"; } } else { return $self->new($self->[0] * $other, $self->[1] * $other, $self->[2]); } } Math-PlanePath-129/devel/lib/Math/PlanePath/0002755000175000017500000000000014001441522016313 5ustar ggggMath-PlanePath-129/devel/lib/Math/PlanePath/R7DragonCurve.pm0000644000175000017500000001640313734026651021321 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=R7DragonCurve --all --scale=10 # cf A176405 R7 turns # A176416 R7B turns package Math::PlanePath::R7DragonCurve; use 5.004; use strict; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use Math::PlanePath; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest', 'xy_is_even'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh'; use vars '$VERSION', '@ISA'; $VERSION = 129; @ISA = ('Math::PlanePath'); # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant parameter_info_array => [ { name => 'type', share_key => 'type_r7dragon', display => 'Type', type => 'enum', default => 'A', choices => ['A','B'], }, { name => 'arms', share_key => 'arms_6', display => 'Arms', type => 'integer', minimum => 1, maximum => 6, default => 1, width => 1, description => 'Arms', } ]; use constant dx_minimum => -2; use constant dx_maximum => 2; use constant dy_minimum => -1; use constant dy_maximum => 1; #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(6, $self->{'arms'} || 1)); $self->{'type'} ||= 'A'; return $self; } my @dir6_to_si = (1,0,0, -1,0,0); my @dir6_to_sj = (0,1,0, 0,-1,0); my @dir6_to_sk = (0,0,1, 0,0,-1); # F0F1F1F0F0F1F, 0->0, 1->1 # # 14 12 # \ / \ # \/ \ # 13,10--11,8 # \ / \ # 9/ \ # 2----3,6----7 i=+2,j=+1 # \ / \ # \ / \ # 0----1,4----5 # # 0 1 2 3 4 5 # B 5----6,3----7 i=+2,j=+1 # \ / \ # \ / \ # 0----1,4----2 # # 0 1 2 3 4 5 my @digit_to_i = (0,1,0,1,1,2,1); my @digit_to_j = (0,0,1,1,0,0,1); my @digit_to_rot = (0,1,0,-1,0,1,0); # 0 1 2 3 4 5 6 my @digit_b_to_a = (0,4,5,3,1,2,6); sub n_to_xy { my ($self, $n) = @_; ### R7DragonCurve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } my $zero = ($n * 0); # inherit bignum 0 my $i = 0; my $j = 0; my $k = 0; my $si = $zero; my $sj = $zero; my $sk = $zero; # initial rotation from arm number { my $int = int($n); my $frac = $n - $int; # inherit possible BigFloat $n = $int; # BigFloat int() gives BigInt, use that my $rot = _divrem_mutate ($n, $self->{'arms'}); my $s = $zero + 1; # inherit bignum 1 if ($rot >= 3) { $s = -$s; # rotate 180 $frac = -$frac; $rot -= 3; } if ($rot == 0) { $i = $frac; $si = $s; } # rotate 0 elsif ($rot == 1) { $j = $frac; $sj = $s; } # rotate +60 else { $k = $frac; $sk = $s; } # rotate +120 } foreach my $digit (digit_split_lowtohigh($n,7)) { ### at: "$i,$j,$k side $si,$sj,$sk" ### $digit if ($self->{'type'} eq 'B') { $digit = $digit_b_to_a[$digit]; } if ($digit == 1) { ($i,$j,$k) = (-$j,-$k,$i); # rotate +120 $i += $si; $j += $sj; $k += $sk; } elsif ($digit == 2) { $i -= $sk; $j += $si; $k += $sj; } elsif ($digit == 3) { ($i,$j,$k) = ($k,-$i,-$j); $i += $si; $j += $sj; $k += $sk; $i -= $sk; $j += $si; $k += $sj; } elsif ($digit == 4) { $i += $si; $j += $sj; $k += $sk; } elsif ($digit == 5) { ($i,$j,$k) = (-$j,-$k,$i); # rotate +120 $i += 2*$si; $j += 2*$sj; $k += 2*$sk; } elsif ($digit == 6) { $i += $si; $j += $sj; $k += $sk; $i -= $sk; $j += $si; $k += $sj; } # $i += $digit_to_i[$digit]; # $j += $digit_to_j[$digit]; # multiple 2i+j ($si,$sj,$sk) = (2*$si - $sk, 2*$sj + $si, 2*$sk + $sj); } ### final: "$i,$j,$k side $si,$sj,$sk" ### is: (2*$i + $j - $k).",".($j+$k) return (2*$i + $j - $k, $j+$k); } # all even points when arms==6 sub xy_is_visited { my ($self, $x, $y) = @_; # FIXME return 0; if ($self->{'arms'} == 6) { return xy_is_even($self,$x,$y); } else { return defined($self->xy_to_n($x,$y)); } } # maximum extent -- no, not quite right # # .----* # \ # *----. # # Two triangle heights, so # rnext = 2 * r * sqrt(3)/2 # = r * sqrt(3) # rsquared_next = 3 * rsquared # Initial X=2,Y=0 is rsquared=4 # then X=3,Y=1 is 3*3+3*1*1 = 9+3 = 12 = 4*3 # then X=3,Y=3 is 3*3+3*3*3 = 9+3 = 36 = 4*3^2 # my @try_dx = (2, 1, -1, -2, -1, 1); my @try_dy = (0, 1, 1, 0, -1, -1); sub xy_to_n { return scalar((shift->xy_to_n_list(@_))[0]); } sub xy_to_n_list { my ($self, $x, $y) = @_; ### R7DragonCurve xy_to_n_list(): "$x, $y" # FIXME return; $x = round_nearest($x); $y = round_nearest($y); if (is_infinite($x)) { return $x; # infinity } if (is_infinite($y)) { return $y; # infinity } my @n_list; my $xm = 2*$x; # doubled out my $ym = 2*$y; foreach my $i (0 .. $#try_dx) { my $t = $self->Math::PlanePath::R7DragonMidpoint::xy_to_n ($xm+$try_dx[$i], $ym+$try_dy[$i]); ### try: ($xm+$try_dx[$i]).",".($ym+$try_dy[$i]) ### $t next unless defined $t; my ($tx,$ty) = n_to_xy($self,$t) # not a method for R7DragonRounded or next; if ($tx == $x && $ty == $y) { ### found: $t if (@n_list && $t < $n_list[0]) { unshift @n_list, $t; } elsif (@n_list && $t < $n_list[-1]) { splice @n_list, -1,0, $t; } else { push @n_list, $t; } if (@n_list == 3) { return @n_list; } } } return @n_list; } # minimum -- no, not quite right # # *----------* # \ # \ * # * \ # \ # *----------* # # width = side/2 # minimum = side*sqrt(3)/2 - width # = side*(sqrt(3)/2 - 1) # # minimum 4/9 * 2.9^level roughly # h = 4/9 * 2.9^level # 2.9^level = h*9/4 # level = log(h*9/4)/log(2.9) # 3^level = 3^(log(h*9/4)/log(2.9)) # = h*9/4, but big bigger for log # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### R7DragonCurve rect_to_n_range(): "$x1,$y1 $x2,$y2" my $xmax = int(max(abs($x1),abs($x2))); my $ymax = int(max(abs($y1),abs($y2))); return (0, ($xmax*$xmax + 3*$ymax*$ymax + 1) * 1/5 * $self->{'arms'}); } 1; __END__ Math-PlanePath-129/devel/lib/Math/PlanePath/FourReplicate.pm0000644000175000017500000000651313734026652021437 0ustar gggg# Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Working. But what is this properly called? # strips N mod 3 (X-Y)/2 == N mod 3 package Math::PlanePath::FourReplicate; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use Math::PlanePath; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest', 'xy_is_even'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use vars '$VERSION', '@ISA'; $VERSION = 129; @ISA = ('Math::PlanePath'); # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; #------------------------------------------------------------------------------ my @digit_to_x = (0,2,-1,-1); my @digit_to_y = (0,0, 1,-1); sub n_to_xy { my ($self, $n) = @_; ### FourReplicate n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } my $zero = ($n * 0); # inherit bignum 0 my $x = my $y = $zero; my $len = $zero + 1; foreach my $digit (digit_split_lowtohigh($n,4)) { $x += $len * $digit_to_x[$digit]; $y += $len * $digit_to_y[$digit]; $len = -2*$len; } return ($x, $y); } # all even points when arms==6 *xy_is_visited = \&xy_is_even; # -1,1 # * . * . # \ # . *-.-* 2,0 # / # * . * . # -1,-1 # # $x % 4 # $y % 4 # # 3 | 2 3 # 2 | 1 0 # 1 | 3 2 # 0 | 0 1 # +--------------- # 0 1 2 3 my @yx_to_digit = ([ 0, undef, 1, undef ], [ undef, 3, undef, 2 ], [ 1, undef, 0, undef ], [ undef, 2, undef, 3 ]); sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest($x); $y = round_nearest($y); if (is_infinite($x)) { return $x; # infinity } if (is_infinite($y)) { return $y; # infinity } my $zero = $x*0*$y; my @ndigits; while ($x || $y) { ### at: "x=$x y=$y" my $ndigit = $yx_to_digit[$y%4]->[$x%4]; if (! defined $ndigit) { return undef; } push @ndigits, $ndigit; $x -= $digit_to_x[$ndigit]; $y -= $digit_to_y[$ndigit]; ### $ndigit ### dxdy: "dx=$digit_to_x[$ndigit] dy=$digit_to_y[$ndigit]" $x /= -2; $y /= -2; } return digit_join_lowtohigh(\@ndigits,4,$zero);; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### FourReplicate rect_to_n_range(): "$x1,$y1 $x2,$y2" my $xmax = int(max(abs($x1),abs($x2))); my $ymax = int(max(abs($y1),abs($y2))); return (0, ($xmax*$xmax + 3*$ymax*$ymax + 1) * 32); return (0, 4**6); # ($xmax*$xmax + 3*$ymax*$ymax + 1)); } 1; __END__ Math-PlanePath-129/devel/lib/Math/PlanePath/ZeckendorfTerms-oeis.t0000644000175000017500000000314212132222017022537 0ustar gggg#!/usr/bin/perl -w # Copyright 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'max'; use Test; plan tests => 46; use lib 't','xt'; use MyTestHelpers; MyTestHelpers::nowarnings(); use MyOEIS; use Math::PlanePath::ZeckendorfTerms; #------------------------------------------------------------------------------ # A134561 by anti-diagonals MyOEIS::compare_values (anum => 'A134561', func => sub { my ($count) = @_; require Math::PlanePath::Diagonals; my $path = Math::PlanePath::ZeckendorfTerms->new; my $diag = Math::PlanePath::Diagonals->new (direction => 'up', x_start=>1,y_start=>1); my @got; for (my $d = $diag->n_start; @got < $count; $d++) { my ($x,$y) = $diag->n_to_xy($d); # by anti-diagonals push @got, $path->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/devel/lib/Math/PlanePath/PeanoVertices.pm0000644000175000017500000000737113734026651021444 0ustar gggg# works, worth having separately ? # alternating diagonals when even radix ? # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=PeanoVertices --all --output=numbers # math-image --path=PeanoVertices,radix=5 --lines # package Math::PlanePath::PeanoVertices; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh'; use Math::PlanePath::PeanoCurve; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 1; use constant parameter_info_array => [ { name => 'radix', share_key => 'radix_3', display => 'Radix', type => 'integer', minimum => 2, default => 3, width => 3, } ]; sub new { my $self = shift->SUPER::new(@_); $self->{'radix'} ||= 3; $self->{'peano'} = Math::PlanePath::PeanoCurve->new (radix => $self->{'radix'}); return $self; } sub n_to_xy { my ($self, $n) = @_; ### PeanoVertices n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { # ENHANCE-ME: for odd radix the ends join and the direction can be had # without a full N+1 calculation my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my ($x,$y) = $self->{'peano'}->n_to_xy($n) or return; if ($x % 2) { if ($y % 2) { $x += 1; $y += 1; } else { $x -= 0; $y += 1; } } else { if ($y % 2) { $x += 1; $y -= 0; } else { $x -= 0; $y -= 0; } } ($x,$y) = (($y+$x)/2, ($y-$x)/2); return ($x, $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### PeanoVertices xy_to_n(): "$x, $y" return undef; $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; return (0, 1000); $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### rect_to_n_range(): "$x1,$y1 to $x2,$y2" if ($x2 < 0 || $y2 < 0) { return (1, 0); } my $radix = $self->{'radix'}; my ($power, $level) = round_down_pow (max($x2,$y2)*$radix/2, $radix); if (is_infinite($level)) { return (0, $level); } return (0, 2*$power*$power - 1); } 1; __END__ Math-PlanePath-129/devel/lib/Math/PlanePath/four-replicate.pl0000644000175000017500000000360412165124417021604 0ustar gggg#!/usr/bin/perl -w # Copyright 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::PlanePath::FourReplicate; # uncomment this to run the ### lines #use Smart::Comments; { require Math::BaseCnv; my $path = Math::PlanePath::FourReplicate->new; foreach my $n (0 .. 2**30) { my ($x,$y) = $path->n_to_xy($n); my ($n_lo,$n_hi) = $path->rect_to_n_range(0,0,$x,$y); if ($n_hi < $n) { my $n4 = Math::BaseCnv::cnv($n,10,4); my $n_hi4 = Math::BaseCnv::cnv($n_hi,10,4); print "n=$n4 outside n_hi=$n_hi4\n"; } } exit 0; } { require Math::PlanePath::FourReplicate; my $path = Math::PlanePath::FourReplicate->new; my @table; my $xmod = 4; my $ymod = 4; foreach my $n (0 .. 2**8) { my ($x,$y) = $path->n_to_xy($n); my $mx = $x % $xmod; my $my = $y % $ymod; my $href = ($table[$mx][$my] ||= {}); $href->{$n%4} = 1; } my $width = 3; foreach my $my (reverse 0 .. $ymod-1) { printf "%2d", $my; foreach my $mx (0 .. $xmod-1) { my $href = ($table[$mx][$my] ||= {}); my $str = join(',', keys %$href); printf " %*s", $width, $str; } print "\n"; } print "\n "; foreach my $mx (0 .. $xmod-1) { printf " %*s", $width, $mx; } print "\n"; exit 0; } Math-PlanePath-129/devel/lib/Math/PlanePath/NxNinv.pm0000644000175000017500000000643613774425766020134 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # A072732 # A072733 inverse # A072736 X coord # A072737 Y coord # # A072734 # A072740 X coord # A072741 Y coord # # plothraw(OEIS_samples("A072736"), OEIS_samples("A072737"), 1) # A072737 Y coord package Math::PlanePath::NxNinv; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; sub n_to_xy { my ($self, $n) = @_; ### NxN n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { # fractions on straight line ? my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } # d = [ 0, 1, 2, 3, 4 ] # n = [ 0, 1, 3, 6, 10 ] # N = (d+1)*d/2 # d = (-1 + sqrt(8*$n+1))/2 # my $d = int( (_sqrtint(8*$n+1) - 1) / 2 ); $n -= $d*($d+1)/2; ### $d ### $n my $x = $d-$n; # downwards my $y = $n; # upwards my $diff = $x-$y; ### diagonals xy: "$x, $y diff=$diff" if ($x <= $y) { my $h = int($x/2); return ($h, $h + ($x%2) + 2*($y - 2*$h - ($x%2))); } else { my $h = int($y/2); return (1 + $h + ($y%2) + 2*($x-1 - 2*$h - ($y%2)), $h); } } sub xy_to_n { my ($self, $x, $y) = @_; ### NxN xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } my $diff = $x-$y; if ($diff <= 0) { ($x,$y) = (2*$x + ($diff % 2), 2*$x + int((1-$diff)/2)); } else { ### pos diff, use y ... ($x,$y) = (2*($y+1) - 1 + int($diff/2), 2*$y + (($diff+1) % 2)); } return (($x+$y)**2 + $x+3*$y)/2; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### NxN rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 0) { ### all outside first quadrant ... return (1, 0); } return (0, $self->xy_to_n($x2,0)); return (0, $self->xy_to_n($x2,$y2)); } 1; __END__ Math-PlanePath-129/devel/lib/Math/PlanePath/WythoffDifference.pm0000644000175000017500000001122113734026651022263 0ustar gggg# Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::WythoffDifference; use 5.004; use strict; use List::Util 'max'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # uncomment this to run the ### lines # use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant xy_is_visited => 1; use Math::PlanePath::WythoffArray; my $wythoff = Math::PlanePath::WythoffArray->new; sub n_to_xy { my ($self, $n) = @_; ### WythoffDifference n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n) || $n == 0) { return ($n,$n); } { # fractions on straight line ? my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } # f1+f0 > i # f0 > i-f1 # check i-f1 as the stopping point, so that if i=UV_MAX then won't # overflow a UV trying to get to f1>=i # my @fibs; { my $f0 = ($n * 0); # inherit bignum 0 my $f1 = $f0 + 1; # inherit bignum 1 while ($f0 <= $n-$f1) { ($f1,$f0) = ($f1+$f0,$f1); push @fibs, $f1; # starting $fibs[0]=1 } } ### @fibs my $orig_n = $n; # indices into fib[] which are the Fibonaccis adding up to $n my @indices; for (my $i = $#fibs; $i >= 0; $i--) { ### at: "n=$n f=".$fibs[$i] if ($n >= $fibs[$i]) { push @indices, $i; $n -= $fibs[$i]; ### sub: "$fibs[$i] to n=$n" --$i; } } ### @indices my $y = 0; my $shift; my $x; my $low = $indices[-1]; if ($low % 2) { # odd trailing zeros $x = ($low+1)/2; $shift = $low + 2; pop @indices; } else { # even trailing zeros $x = 0; $shift = 1; if ($low == 0) { pop @indices; } else { $y = -1; } } foreach my $i (@indices) { ### y add: "ishift=".($i-$shift)." fib=".$fibs[$i-$shift] $y += $fibs[$i-$shift]; } ### $y return ($x, $y); } # 6 | 11 28 73 191 500 # 5 | 9 23 60 157 411 # 4 | 8 20 52 136 356 # 3 | 6 15 39 102 267 # 2 | 4 10 26 68 178 # 1 | 3 7 18 47 123 # 0 | 1 2 5 13 34 # +------------------- # 0 1 2 3 4 # 9 | 100100 10001010 1000101000 100010100000 10001010000000 # 8 | 100001 10000010 1000001000 100000100000 10000010000000 # 7 | 10101 1010010 101001000 10100100000 1010010000000 # 6 | 10100 1001010 100101000 10010100000 1001010000000 # 5 | 10001 1000010 100001000 10000100000 1000010000000 # 4 | 10000 101010 10101000 1010100000 101010000000 # 3 | 1001 100010 10001000 1000100000 100010000000 # 2 | 101 10010 1001000 100100000 10010000000 # 1 | 100 1010 101000 10100000 1010000000 # 0 | 1 10 1000 100000 10000000 # +-------------------------------------------------------- # 0 1 2 3 4 sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); if ($x == 0) { my $n1 = $wythoff->xy_to_n(1,$y); if ($n1) { $n1 -= $wythoff->xy_to_n(0,$y); } return $n1; } return $wythoff->xy_to_n(2*$x-1,$y); } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### WythoffDifference rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 0) { ### all outside first quadrant ... return (1, 0); } # bottom left into first quadrant if ($x1 < 0) { $x1 *= 0; } if ($y1 < 0) { $y1 *= 0; } return ($self->xy_to_n($x1,$y1), # bottom left $self->xy_to_n($x2,$y2)); # top right } 1; __END__ Math-PlanePath-129/devel/lib/Math/PlanePath/squares-dispersion.pl0000644000175000017500000000565611770201234022526 0ustar gggg#!/usr/bin/perl -w # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::NumSeq::Squares; # uncomment this to run the ### lines #use Smart::Comments; # diagonals slope=2 # Classic Sequences # http://oeis.org/classic.html # # A082156 # 1 4 9 16 25 d^2 # +3 +5 +7 +9 # # 2 6 12 20 30 (d^2 + 3 d + 2) # +4 +6 +8 +10 # # 3 8 15 24 35 (d^2 + 4 d + 3) # +5 +7 +9 +11 # # 5 11 19 29 41 (d^2 + 5 d + 5) # +6 +8 +10 +12 # # 7 14 23 34 47 (d^2 + 6 d + 7) # +7 +9 +11 +13 { # rows my @non_squares = (0); foreach my $n (0 .. 100) { push @non_squares, $n if ! is_square($n); } print join(',',@non_squares),"\n"; # 1 4 9 16 25 36 49 64 81 100 121 144 # 2 6 12 20 30 42 56 72 90 110 132 # 3 8 15 24 35 48 63 80 99 120 # 5 11 19 29 41 55 71 89 109 # 7 14 23 34 47 62 79 # 10 18 28 40 54 70 # 13 22 33 46 61 # 17 27 39 53 # 21 32 45 # 26 38 # 31 # # 0 1 2 3 4 5 6 7 8 9 10 my @o = (0, 0, 0, 1, 2, 4, 6, 9, 12, 16, 20); # +0 +0 +1 +1 +2 +2 +3 +3 +4 +4 # (2x+y+2)(2x+y-2) = 4xx+4xy+yy # N = (x+1)**2 + (x+1)*y + (y*y - 2*y + odd)/4 # = x^2 + 2x + 1+ xy + y + y^2/4 - y/2 + odd/4 # = x^2 + 2x + 1+ xy + y^2/4 + y/2 + odd/4 # = (4x^2 + 8x + 4+ 4xy + y^2 + 2y + odd)/4 # = (4x^2 + 4xy + 8x + y^2 + 2y + 4 + odd)/4 # = ((2x+y+2)^2 + 2y+odd) / 4 my @seen; foreach my $y (0 .. 10) { foreach my $x (0 .. 14) { my $odd = ($y & 1); my $o = ($odd ? ($y*$y - 2*$y + 1)/4 : ($y*$y - 2*$y)/4); # even if ($o != $o[$y]) { die } #my $o = ($o[$y]||0); my $n = ($x+1)**2 + ($x+1)*$y + $o; # my $n = ((2*$x+$y+2)**2 + 2*$y + $odd) / 4; my $dup = ($seen[$n]++ ? '*' : ' '); printf ' %3d%s', $n, $dup; } print "\n"; } exit 0; } { # non-squares my $next_root = 1; my $next_square = 1; my $prev = 0; foreach my $n (1 .. 50) { my $non = non_square($n); if ($non != $prev+1) { print "--\n"; } my $sq = is_square($non) ? ' ***' : ''; print "$non$sq\n"; $prev = $non; } sub non_square { my ($n) = @_; return $n + int(sqrt($n))-1; } sub is_square { my ($n) = @_; return Math::NumSeq::Squares->pred($n); } exit 0; } Math-PlanePath-129/devel/lib/Math/PlanePath/FibonacciWordKnott.pm0000644000175000017500000002306113734026652022421 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # http://alexis.monnerot-dumaine.neuf.fr/articles/fibonacci%20fractal.pdf # [gone] # # math-image --path=FibonacciWordKnott --output=numbers_dash package Math::PlanePath::FibonacciWordKnott; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use Math::PlanePath::FibonacciWordFractal; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; my @dir4_to_dx = (0,-1,0,1); my @dir4_to_dy = (1,0,-1,0); sub n_to_xy { my ($self, $n) = @_; ### FibonacciWordKnott n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } # my $frac; # { # my $int = int($n); # $frac = $n - $int; # inherit possible BigFloat # $n = $int; # BigFloat int() gives BigInt, use that # } { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $zero = ($n * 0); # inherit bignum 0 my $one = $zero + 1; # inherit bignum 0 my @f = ($one, 2+$zero); my @xend = ($zero, $zero, $one); # F3 N=2 X=1,Y=1 my @yend = ($zero, $one, $one); my $level = 2; while ($f[-1] < $n) { push @f, $f[-1] + $f[-2]; my ($x,$y); my $m = ($level % 6); if ($m == 1) { $x = $yend[-2]; # -90 $y = - $xend[-2]; } elsif ($m == 2) { $x = $xend[-2]; # T -90 $y = - $yend[-2]; } elsif ($m == 3) { $x = $yend[-2]; # T $y = $xend[-2]; } elsif ($m == 4) { $x = - $yend[-2]; # +90 $y = $xend[-2]; } elsif ($m == 5) { $x = - $xend[-2]; # T +90 $y = $yend[-2]; } elsif ($m == 0) { $x = $yend[-2]; # T $y = $xend[-2]; } push @xend, $xend[-1] + $x; push @yend, $yend[-1] + $y; ### push xy: "levelmod=".($level%6)." add $x,$y for $xend[-1],$yend[-1] for f=$f[-1]" $level++; } my $x = $zero; my $y = $zero; my $rot = 0; my $transpose = 0; while (@xend > 1) { ### at: "$x,$y rot=$rot transpose=$transpose level=$level n=$n consider f=$f[-1]" my $xo = pop @xend; my $yo = pop @yend; if ($n >= $f[-1]) { $n -= $f[-1]; ### offset: "$xo, $yo for ".($level % 6) if ($transpose) { ($xo,$yo) = ($yo,$xo); } if ($rot & 2) { $xo = -$xo; $yo = -$yo; } if ($rot & 1) { ($xo,$yo) = (-$yo,$xo); } ### apply rot to offset: "$xo, $yo" $x += $xo; $y += $yo; my $m = $level % 6; if ($m == 1) { # F8 N=21 etc # -90 if ($transpose) { $rot++; } else { $rot--; # -90 } } elsif ($m == 2) { # F3 N=2 etc # T -90 if ($transpose) { $rot++; } else { $rot--; # -90 } $transpose ^= 3; } elsif ($m == 3) { # F4 N=3 etc $transpose ^= 3; # T } elsif ($m == 4) { # F5 N=5 etc # +90 if ($transpose) { $rot--; } else { $rot++; # +90 } } elsif ($m == 5) { # F6 N=8 etc # T +90 if ($transpose) { $rot--; } else { $rot++; # +90 } $transpose ^= 3; } else { # ($m == 0) # F7 N=13 etc $transpose ^= 3; # T } } pop @f; $level--; } # mod 6 twist ? # ### final rot: "$rot transpose=$transpose gives ".(($rot^$transpose)&3) # $rot = ($rot ^ $transpose) & 3; # $x = $frac * $dir4_to_dx[$rot] + $x; # $y = $frac * $dir4_to_dy[$rot] + $y; ### final with frac: "$x,$y" return ($x,$y); } my $moffset = 1; #use Smart::Comments; sub xy_to_n { my ($self, $x, $y) = @_; ### FibonacciWordKnott xy_to_n(): "$x, $y" $x = round_nearest($x); if (is_infinite($x)) { return $x; } $y = round_nearest($y); if (is_infinite($y)) { return $y; } foreach my $xoffset (1,0,-1) { foreach my $yoffset (1,0,-1) { ### try: "x=".(2*$y+$yoffset)." y=".(2*$x+$xoffset) if (defined (my $n = $self->Math::PlanePath::FibonacciWordFractal::xy_to_n(2*$x+$xoffset, 2*$y+$yoffset))) { ### $n if (my ($nx,$ny) = $self->n_to_xy($n)) { ### rev: "nx=$nx,ny=$ny" if ($nx == $x && $ny == $y) { return $n; } } } } } return undef; no Smart::Comments; my $zero = ($x * 0 * $y); # inherit bignum 0 my $one = $zero + 1; # inherit bignum 0 my @f = ($one, $zero+2); my @xend = ($zero, $one); # F3 N=2 X=1,Y=1 my @yend = ($one, $one); my $level = 3; for (;;) { my ($xo,$yo); my $m = ($level-$moffset) % 6; ### $m if ($m == 2) { $xo = $yend[-2]; # T $yo = $xend[-2]; } elsif ($m == 3) { $xo = $yend[-2]; # -90 $yo = - $xend[-2]; } elsif ($m == 4) { $xo = $xend[-2]; # T -90 $yo = - $yend[-2]; } elsif ($m == 5) { ### T $xo = $yend[-2]; # T $yo = $xend[-2]; } elsif ($m == 0) { $xo = - $yend[-2]; # +90 $yo = $xend[-2]; } elsif ($m == 1) { $xo = - $xend[-2]; # T +90 $yo = $yend[-2]; } $xo += $xend[-1]; $yo += $yend[-1]; last if ($xo > $x && $yo > $y); push @f, $f[-1] + $f[-2]; push @xend, $xo; push @yend, $yo; $level++; ### new: "level=$level $xend[-1],$yend[-1] for N=$f[-1]" } ### @xend ### @yend my $n = 0; while ($level >= 2) { ### at: "$x,$y n=$n level=$level consider $xend[-1],$yend[-1] for $f[-1]" if (($level+3-$moffset) % 6 < 3) { ### 3,4,5 X ... if ($x >= $xend[-1]) { $n += $f[-1]; $x -= $xend[-1]; $y -= $yend[-1]; ### shift to: "$x,$y levelmod ".($level % 6) if (($level % 6) == 3) { # F3 N=2 etc ($x,$y) = (-$y,$x); # +90 } elsif (($level % 6) == 4) { # F4 N=3 etc $y = -$y; # +90 T } elsif (($level % 6) == 5) { # F5 N=5 etc ($x,$y) = ($y,$x); # T } ### rot to: "$x,$y" if ($x < 0 || $y < 0) { return undef; } } } else { ### 0,1,2 Y ... if ($y >= $yend[-1]) { $n += $f[-1]; $x -= $xend[-1]; $y -= $yend[-1]; ### shift to: "$x,$y levelmod ".($level % 6) if (($level % 6) == 0) { # F6 N=8 etc ($x,$y) = ($y,-$x); # -90 } elsif (($level % 6) == 1) { # F7 N=13 etc $x = -$x; # -90 T } elsif (($level % 6) == 2) { # F8 N=21 etc, incl F2 N=1 ($x,$y) = ($y,$x); # T } ### rot to: "$x,$y" if ($x < 0 || $y < 0) { return undef; } } } pop @f; pop @xend; pop @yend; $level--; } if ($x != 0 || $y != 0) { return undef; } return $n; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### FibonacciWordKnott rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### rect_to_n_range(): "$x1,$y1 to $x2,$y2" if ($x2 < 0 || $y2 < 0) { return (1, 0); } foreach ($x1,$x2,$y1,$y2) { if (is_infinite($_)) { return (0, $_); } } my $zero = ($x1 * 0 * $y1 * $x2 * $y2); # inherit bignum 0 my $one = $zero + 1; # inherit bignum 0 my $f0 = 1; my $f1 = 2; my $xend0 = $zero; my $xend1 = $one; my $yend0 = $one; my $yend1 = $one; my $level = 3; for (;;) { my ($xo,$yo); if (($level % 6) == 3) { # at F3 N=2 etc $xo = $yend0; # -90 $yo = - $xend0; } elsif (($level % 6) == 4) { # at F4 N=3 etc $xo = $xend0; # T -90 $yo = - $yend0; } elsif (($level % 6) == 5) { # at F5 N=5 etc $xo = $yend0; # T $yo = $xend0; } elsif (($level % 6) == 0) { # at F6 N=8 etc $xo = - $yend0; # +90 $yo = $xend0; } elsif (($level % 6) == 1) { # at F7 N=13 etc $xo = - $xend0; # T +90 $yo = $yend0; } else { # if (($level % 6) == 2) { # at F8 N=21 etc $xo = $yend0; # T $yo = $xend0; } ($f1,$f0) = ($f1+$f0,$f1); ($xend1,$xend0) = ($xend1+$xo,$xend1); ($yend1,$yend0) = ($yend1+$yo,$yend1); $level++; ### consider: "f1=$f1 xy end $xend1,$yend1" if ($xend1 > $x2 && $yend1 > $y2) { return (0, $f1 - 1); } } } 1; __END__ Math-PlanePath-129/devel/lib/Math/PlanePath/SumFractions.pm0000644000175000017500000000731613734026651021311 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::SumFractions; use 5.004; use strict; use List::Util 'max'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::NumSeq::BalancedBinary; # uncomment this to run the ### lines # use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant xy_is_visited => 1; sub new { my $self = shift->SUPER::new (@_); $self->{'seq'} = Math::NumSeq::BalancedBinary->new; return $self; } sub n_to_xy { my ($self, $n) = @_; ### SumFractions n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n) || $n == 0) { return ($n,$n); } { # fractions on straight line ? my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } my $d = int((_sqrtint(8*$n-7) - 1) / 2); $n -= $d*($d+1)/2 + 1; ### $d ### $n return _dn_to_xy($d,$n); } sub _dn_to_xy { my ($d,$n) = @_; if ($n == 0) { return (1,1); } if ($n == $d) { return (1,$d+1) }; return _rat_sum(_dn_to_xy($d-1,$n), _dn_to_xy($d-1,$n-1)); } sub _rat_sum { my ($x1,$y1, $x2,$y2) = @_; my $num = $x1*$y2 + $x2*$y1; my $den = $y1*$y2; my $gcd = Math::PlanePath::GcdRationals::_gcd($num,$den); return ($num/$gcd, $den/$gcd); } use Math::PlanePath::GcdRationals; *_gcd = \&Math::PlanePath::GcdRationals::_gcd; sub xy_to_n { my ($self, $x, $y) = @_; ### SumFractions xy_to_n(): "$x, $y" return undef; $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } my $zero = $x * 0 * $y; if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my $value = $self->{'seq'}->ith($y) || 0; ### value at y: $value my $pow = (4+$zero)**$x; $value *= $pow; $value += 2*($pow-1)/3; ### mul: sprintf '%#b', $pow ### add: sprintf '%#b', 2*($pow-1)/3 ### value: sprintf '%#b', $value ### $value ### value: ref $value && $value->as_bin return $self->{'seq'}->value_to_i($value); } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### SumFractions rect_to_n_range(): "$x1,$y1 $x2,$y2" return (1,10000); $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 0) { ### all outside first quadrant ... return (1, 0); } # bottom left into first quadrant if ($x1 < 0) { $x1 *= 0; } if ($y1 < 0) { $y1 *= 0; } return (0, 4**($x2+$y2)); return ($self->xy_to_n($x1,$y1), # bottom left $self->xy_to_n($x2,$y2)); # top right } 1; __END__ Math-PlanePath-129/devel/lib/Math/PlanePath/MooreSpiral.pm0000644000175000017500000004265713734026652021140 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=MooreSpiral --all --output=numbers_dash # math-image --path=MooreSpiral,arms=2 --all --output=numbers_dash # www.nahee.com/spanky/www/fractint/lsys/variations.html # William McWorter mcworter@midohio.net # http://www.nahee.com/spanky/www/fractint/lsys/moore.gif package Math::PlanePath::MooreSpiral; use 5.004; use strict; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant parameter_info_array => [ { name => 'arms', share_key => 'arms_2', display => 'Arms', type => 'integer', minimum => 1, maximum => 2, default => 1, width => 1, description => 'Arms', } ]; sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(2, $self->{'arms'} || 1)); return $self; } my @next_state = (20,30, 0, 60, 0,10, 70,60,50, undef, # 0 30, 0, 10,70,10, 20,40,70, 60,undef, # 10 0, 10,20,40, 20,30,50, 40,70,undef, # 20 10,20,30, 50,30, 0, 60,50,40, undef, # 30 10,20, 30,50,40, 20,40,70, 60,undef, # 40 20, 30, 0,60, 50,30,50, 40,70,undef, # 50 30, 0,10, 70,60, 0, 60,50,40, undef, # 60 0,10, 20,40,70, 10,70,60, 50,undef); # 70 my @digit_to_x = ( 0, 1, 1, 0,-1,-2, -2,-2,-3, -3, # 0 0, 0, -1,-1,-1, -1, 0, 1, 1, 0, # 10 0, -1,-1, 0, 1, 2, 2, 2, 3, 3, # 20 0, 0, 1, 1, 1, 1, 0,-1,-1, 0, # 30 0, 0, 1, 1, 1, 2, 3, 4, 4, 3, # 40 0, 1, 1, 0, -1,-1,-1, -1, 0, 0, # 50 0, 0,-1, -1,-1,-2, -3,-4,-4, -3, # 60 0,-1, -1, 0, 1, 1, 1, 1, 0, 0); # 70 my @digit_to_y = ( 0, 0, 1, 1, 1, 1, 0,-1,-1, 0, # 0 0, 1, 1, 0,-1, -2,-2,-2, -3,-3, # 10 0, 0,-1,-1, -1,-1, 0, 1, 1, 0, # 20 0,-1,-1, 0, 1, 2, 2, 2, 3, 3, # 30 0,-1, -1, 0, 1, 1, 1, 1, 0, 0, # 40 0, 0, 1, 1, 1, 2, 3, 4, 4, 3, # 50 0, 1, 1, 0,-1,-1, -1,-1, 0, 0, # 60 0, 0, -1,-1,-1, -2,-3,-4, -4,-3); # 70 # state length 80 in each of 4 tables # rot2 state 20 sub n_to_xy { my ($self, $n) = @_; ### MooreSpiral n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; # frac # initial state from arm number $int mod $arms my $state = 20; my $arms = $self->{'arms'}; if ($arms > 1) { my $arm = _divrem_mutate($int,2); if ($arm) { $state = 0; $int += 1; } } my @digits = digit_split_lowtohigh($int,9); my $zero = $int*0; # inherit bignum 0 my $len = ($zero+3) ** scalar(@digits); unless ($#digits & 1) { $state ^= 20; # rot 18re0 } ### digits: join(', ',@digits)." count ".scalar(@digits) ### $len ### initial state: $state my $x = 0; my $y = 0; my $dir = 0; while (@digits) { $len /= 3; ### at: "$x,$y" ### $len ### digit: $digits[-1] ### state: $state # . " ".state_string($state) $state += (my $digit = pop @digits); if ($digit != 8) { } $dir = $state; # lowest non-zero digit ### digit_to_x: $digit_to_x[$state] ### digit_to_y: $digit_to_y[$state] ### next_state: $next_state[$state] $x += $len * $digit_to_x[$state]; $y += $len * $digit_to_y[$state]; $state = $next_state[$state]; } ### final: "$x,$y" # with $n fractional part return ($n * ($digit_to_x[$dir+1] - $digit_to_x[$dir]) + $x, $n * ($digit_to_y[$dir+1] - $digit_to_y[$dir]) + $y); } # 61-62 67-68-69-70 4 # | | | | # 60 63 66 73-72-71 3 # | | | | # 59 64-65 74-75-76 2 # | | # 11-10 5--4--3--2 58-57-56 83-82 77 1 # | | | | | | | | # 12 9 6 0--1 53-54-55 84 81 78 <- Y=0 # | | | | | | | # 13 8--7 52-51-50 85 80-79 -1 # | | | # 14-15-16 25-26 31-32-33-34 43-44 49 86-87-88 97-98 -2 # | | | | | | | | | | | # 19-18-17 24 27 30 37-36-35 42 45 48 91-90-89 96 99 -3 # | | | | | | | | | | | # 20-21-22-23 28-29 38-39-40-41 46-47 92-93-94-95 ... -4 # 40 -3*9 = 40-27=13 # 13 -8 = 5 # # bottom right corner "40" N=(9^level-1)/2 # bottom left corner "20" # N=(9^level-1)/2 - 3*3^level # len=3 Nr=(9*len*len-1)/2=40 # Nl=Nr - 2*len*len - (len-1) # = (9*len*len-1)/2 - 2*len*len - (len-1) # = (9*len*len-1 - 4*len*len - 2*(len-1))/2 # = (9*len*len - 1 - 4*len*len - 2*len + 2)/2 # = (5*len*len - 2*len + 1)/2 # = ((5*len - 2)*len + 1)/2 # # round 2,5,etc 1+(3^level-1)/2 = x # 2*(x-1) = 3^level-1 # 3^level = 2x-2+1 = 2x-1 # offset 1,4,etc 1+...+3^(level-1) = (3^level-1)/2 # my @yx_to_rot = (0,3,0, # y=0 1,2,1, # y=1 0,3,0); # y=2 my @yx_to_digit = (-2,-3,-4, # y=0 -1,0,1, # y=1 4,3,2); # y=2 sub xy_to_n { my ($self, $x, $y) = @_; ### MooreSpiral xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); my ($len, $level) = round_down_pow (max(abs($x),abs($y))*2 - 1, 3); ### $len ### $level # offset to make bottom left corner X=0,Y=0 { my $offset = (3*$len-1)/2; $x += $offset; $y += $offset; ### $offset ### offset to: "$x,$y" ### assert: $x >= 0 ### assert: $y >= 0 ### assert: $x < 3*$len ### assert: $y < 3*$len } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my $arms = $self->{'arms'}; my $npow = $len*$len; my $n = ($x * 0 * $y); # + (9*$npow - 1)/2; my $rot = ($level & 1 ? 2 : 0); my @x = digit_split_lowtohigh ($x, 3); my @y = digit_split_lowtohigh ($y, 3); ### @x ### @y for ( ; $level >= 0; $level--) { ### $n ### $rot $x = $x[$level] || 0; $y = $y[$level] || 0; ### raw xy digits: "$x,$y" if ($rot&1) { ($x,$y) = (2-$y,$x) # rotate +90 } if ($rot&2) { $x = 2-$x; # rotate 180 $y = 2-$y; } ### rotated xy digits: "$x,$y" my $k = $y*3+$x; $rot += $yx_to_rot[$k]; my $digit = $yx_to_digit[$k]; $n += $npow*$digit; ### $digit ### add to n: $npow*$digit if ($n < 0 && $self->{'arms'} < 2) { ### negative when only 1 arm ... return undef; } $npow /= 9; } ### final n: $n if ($arms < 2) { return $n; } if ($n < 0) { return -1-2*$n; } else { return 2*$n; } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### MooreSpiral rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); my ($len, $level) = round_down_pow (max(abs($x1),abs($y1), abs($x2),abs($y2))*2-1, 3); ### $len ### $level return (0, ($x1 * 0 * $y1 * $x2 * $y2) + (9*$len*$len - 1) * $self->{'arms'} / 2); } 1; __END__ =for stopwords eg Ryde ie MooreSpiral Math-PlanePath Moore =head1 NAME Math::PlanePath::MooreSpiral -- 9-segment self-similar spiral =head1 SYNOPSIS use Math::PlanePath::MooreSpiral; my $path = Math::PlanePath::MooreSpiral->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is an integer version of a 9-segment self-similar curve by ... 61-62 67-68-69-70 4 | | | | 60 63 66 73-72-71 3 | | | | 59 64-65 74-75-76 2 | | 11-10 5--4--3--2 58-57-56 83-82 77 1 | | | | | | | | 12 9 6 0--1 53-54-55 84 81 78 <- Y=0 | | | | | | | 13 8--7 52-51-50 85 80-79 -1 | | | 14-15-16 25-26 31-32-33-34 43-44 49 86-87-88 97-98 -2 | | | | | | | | | | | 19-18-17 24 27 30 37-36-35 42 45 48 91-90-89 96 99 -3 | | | | | | | | | | | 20-21-22-23 28-29 38-39-40-41 46-47 92-93-94-95 ... -4 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 10 11 12 The base pattern is the N=0 to N=9 shape. Then there's 9 copies of that shape in the same relative directions as those segments and with reversals in the 3,6,7,8 parts. The first reversed section is N=3*9=27 to N=4*9=36. rev 5------4------3------2 | | | | 9 6 0------1 | |rev rev| | 8------7 rev Notice the points N=9,18,27,...,81 are the base shape rotated 180 degrees. Likewise for N=81,162,etc and any multiples of N=9^level, with each successive level being rotated 180 degrees relative to the preceding. The effect is to spiral around with an ever fatter 3^level width, ****************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** *************************** ********* *************************** ********* *************************** ********* *************************** ****** ********* *************************** *** ** ********* *************************** *** ********* *************************** ****************** *************************** ****************** *************************** ****************** *************************** *************************** *************************** *************************** *************************** *************************** *************************** *************************** *************************** =head2 Arms The optional C 2> parameter can give a second copy of the spiral rotated 180 degrees. With two arms all points of the plane are covered. 93--91 81--79--77--75 57--55 45--43--41--39 122-124 .. | | | | | | | | | | | 95 89 83 69--71--73 59 53 47 33--35--37 120 126 132 | | | | | | | | | | | 97 87--85 67--65--63--61 51--49 31--29--27 118 128-130 | | | 99-101-103 22--20 10-- 8-- 6-- 4 13--15 25 116-114-112 | | | | | | | | | 109-107-105 24 18 12 1 0-- 2 11 17 23 106-108-110 | | | | | | | | | 111-113-115 26 16--14 3-- 5-- 7-- 9 19--21 104-102-100 | | | 129-127 117 28--30--32 50--52 62--64--66--68 86--88 98 | | | | | | | | | | | 131 125 119 38--36--34 48 54 60 74--72--70 84 90 96 | | | | | | | | | | | .. 123-121 40--42--44--46 56--58 76--78--80--82 92--94 The first arm is the even numbers N=0,2,4,etc and the second arm is the odd numbers N=1,3,5,etc. =head2 Wunderlich Serpentine Curve The way the ends join makes little "S" shapes similar to the PeanoCurve. The first is at N=5 to N=13, 11-10 5 | | | 12 9 6 | | | 13 8--7 The wider parts then have these sections alternately horizontal or vertical in the style of Walter Wunderlich's "serpentine" type 010 101 010 curve. For example the 9x9 block N=41 to N=101, 61--62 67--68--69--70 115-116 121 | | | | | | | 60 63 66 73--72--71 114 117 120 | | | | | | | 59 64--65 74--75--76 113 118-119 | | | 58--57--56 83--82 77 112-111-110 | | | | | 53--54--55 84 81 78 107-108-109 | | | | | 52--51--50 85 80--79 106-105-104 | | | 43--44 49 86--87--88 97--98 103 | | | | | | | 42 45 48 91--90--89 96 99 102 | | | | | | | 41 46--47 92--93--94--95 100-101 The whole curve is in fact like the Wunderlich serpentine started from the middle. This can be seen in the two arms picture above (in mirror image of the usual PlanePath start direction for Wunderlich's curve). =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::MooreSpiral-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head1 FORMULAS =head2 X,Y to N The correspondence to Wunderlich's 3x3 serpentine curve can be used to turn X,Y coordinates in base 3 into an N. Reckoning the innermost 3x3 as level=1 then the smallest abs(X) or abs(Y) in a level is Xlevelmin = (3^level + 1) / 2 eg. level=2 Xlevelmin=5 which can be reversed as level = log3floor( max(abs(X),abs(Y)) * 2 - 1 ) eg. X=7 level=log3floor(2*7-1)=2 An offset can be applied to put X,Y in the range 0 to 3^level-1, offset = (3^level-1)/2 eg. level=2 offset=4 Then a table can give the N base-9 digit corresponding to X,Y digits Y=2 4 3 2 N digit Y=1 -1 0 1 Y=0 -2 -3 -4 X=0 X=1 X=2 A current rotation maintains the "S" part directions and is updated by a table Y=2 0 +3 0 rotation when descending Y=1 +1 +2 +1 into sub-part Y=0 0 +3 0 X=0 X=1 X=2 The negative digits of N represent backing up a little in some higher part. If N goes negative at any state then X,Y was off the main curve and instead on the second arm. If the second arm is not of interest the calculation can stop at that stage. It no doubt would also work to take take X,Y as balanced ternary digits 1,0,-1, but it's not clear that would be any faster or easier to calculate. =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/devel/lib/Math/PlanePath/WythoffLines.pm0000644000175000017500000002607613734026651021321 0ustar gggg# Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # x=45,y=10 x=59,y=19 dx=14,dy=9 14/9=1.55 # # x=42,y=8 x=113,y=52 dx=71,dy=44 71/44=1.613 # # below # 32,12 to 36,4 sqrt((32-36)^2+(12-4)^2) = 9 # 84,34 to 99,14 sqrt((84-99)^2+(34-14)^2) = 25 # 180,64 to 216,11 sqrt((180-216)^2+(64-11)^2) = 64 # # above # 14,20 to 5,32 sqrt((14-5)^2+(20-32)^2) = 15 = 9*1.618 3 # 34,50 to 14,85 sqrt((34-14)^2+(50-85)^2) = 40 = 25*1.618 5 # 132,158 to 77,247 sqrt((132-77)^2+(158-247)^2) = 104 = 64*1.618 8 # 8,525 to 133,280 sqrt((8-133)^2+(525-280)^2) = 275 = 169*1.618 13 package Math::PlanePath::WythoffLines; use 5.004; use strict; use List::Util 'max'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'bit_split_lowtohigh'; *_sqrtint = \&Math::PlanePath::_sqrtint; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ { name => 'shift', display => 'Shift', type => 'integer', default => 0, width => 3, }, ]; # shift x_minimum() y_minimum() # -4 13 8 # -3 8 5 # -2 5 3 # -1 3 2 # 1 2 1 # 0 2 1 ... # 1 1 1 fib(1) # 2 1 /---> 0 -----^ fib(0) # 3 0 <--/ 1 a # 4 1 -1 b # 5 -1 2 c # 6 2 -4 d -4=2*-1-2 # 7 -4 4 e 4=2*2-0 # 8 4 -12 -12=2*-4-4 # 9 -12 9 9=2*4-(-1) # 10 9 -33 # 11 -33 22 22=3*9-4-1 a(n)=3a(n-2)-a(n-4)-1 # 12 22 -88 -88=2*-33-22 2*a(n-2)-a(n-1) # 13 -88 56 56=2*22+12 2*a(n-2)-a(n-5) # 14 56 -232 -232=2*-88-56 2*a(n-2)-a(n-1) # 15 -232 145 145=2*56+33 2*a(n-2)-a(n-5) # 16 -609 -609=2*-232-145 # 17 -609 378 378=2*145-(-88) # # shift -4,-12,-33,-88,-232 = 1-Fib(2*s+1) # shift 9,22,56,145,378,988 # a(n)=3*a(n-1)-a(n-2)-1 # with $shift reckoned for y_minimum() sub _calc_minimum { my ($shift) = @_; if ($shift <= 2) { return _fibonacci(2-$shift); } if ($shift & 1) { # shift odd >= 3, so (shift-1)/2 >= 1 my $a = 1; my $b = 2; foreach (2 .. ($shift-1)/2) { ($a,$b) = ($b, 3*$b-$a-1); } return $a; } else { # shift even >= 4 return 1 - _fibonacci($shift-1); } # $a = 1; # $b = -1; # my $c = 2; # my $d = -4; # my $e = 4; # for (my $i = 2; $i < $shift; $i++) { # ($a,$b,$c,$d,$e) = ($b,$c,$d,$e, 2*$d-$e); # $i++; # last unless $i < $shift; # ($a,$b,$c,$d,$e) = ($b,$c,$d,$e, 2*$d-$a); # } # return $a; } sub _fibonacci { my ($n) = @_; $a = 0; $b = 1; foreach (1 .. $n) { ($a,$b) = ($b,$a+$b); } return $a; } sub x_minimum { my ($self) = @_; return _calc_minimum($self->{'shift'}-1); } sub y_minimum { my ($self) = @_; return _calc_minimum($self->{'shift'}); } #------------------------------------------------------------------------------ use Math::PlanePath::WythoffArray; my $wythoff = Math::PlanePath::WythoffArray->new; sub new { my $self = shift->SUPER::new(@_); $self->{'shift'} ||= 0; return $self; } sub n_to_xy { my ($self, $n) = @_; ### WythoffLines n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n) || $n == 0) { return ($n,$n); } { # fractions on straight line my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } # $n -= 1; # my $y = $wythoff->xy_to_n(0,$n); # my $x = $wythoff->xy_to_n(1,$n); # 1 2.000, 1.000 1 1_100000 5.000,3.000(5.831) # 2 7.000, 4.000 2 1_100000 3.000,2.000(3.606) # 3 10.000, 6.000 3 1_100000 5.000,3.000(5.831) # 4 15.000, 9.000 4 1_100000 5.000,3.000(5.831) # 5 20.000, 12.000 5 1_100000 3.000,2.000(3.606) # 6 23.000, 14.000 6 1_100000 5.000,3.000(5.831) # 7 28.000, 17.000 7 1_100000 3.000,2.000(3.606) my $zero = $n*0; # spectrum(Y+1) so Y,Ybefore are notional two values at X=-2 and X=-1 my $y = $n-1; my $x = int((_sqrtint(5*$n*$n) + $n) / 2); # ($y,$x) = (1*$x + 1*$y, # 2*$x + 1*$y); # shift s to -1 # 1 to s # but forward by 2 extra # s to -1+2=1 # 1+2=3 to s foreach ($self->{'shift'} .. 1) { ($y,$x) = ($x,$x+$y); } foreach (3 .. $self->{'shift'}) { # prev+y=x # prev = x-y ($y,$x) = ($x-$y,$y); } return ($x,$y); } sub xy_to_n { my ($self, $x, $y) = @_; ### WythoffLines xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); # if (is_infinite($y)) { return $y; } # unshift # foreach ($self->{'shift'} .. -1) { ($y,$x) = ($x-$y,$y); } foreach (1 .. $self->{'shift'}) { ($y,$x) = ($x,$x+$y); } ### unshifted to: "$x,$y" if (my ($cy,$ny) = $wythoff->n_to_xy($y)) { ### y: "cy=$cy ny=$ny" if ($cy == 0) { if (my ($cx,$nx) = $wythoff->n_to_xy($x)) { if ($cx == 1 && $nx == $ny) { return $nx+1; } } } } return undef; # my $y = $wythoff->xy_to_n(0,$n); # my $x = $wythoff->xy_to_n(1,$n); } sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### WythoffLines rect_to_n_range(): "$x1,$y1 $x2,$y2" my $zero = $x1 * 0 * $y1 * $x2 * $y2; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); # FIXME: probably not quite right my $phi = (1 + sqrt(5+$zero)) / 2; return (1, max (1, int($phi**($self->{'shift'}-2) * max ($x1,$x2, max($y1,$y2)*$phi)))); } 1; __END__ =for stopwords eg Ryde Math-PlanePath Moore Wythoff Zeckendorf concecutive fibbinary OEIS =head1 NAME Math::PlanePath::WythoffLines -- table of Fibonacci recurrences =head1 SYNOPSIS use Math::PlanePath::WythoffLines; my $path = Math::PlanePath::WythoffLines->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis path is the Wythoff preliminary triangle by Clark Kimberling, =cut # math-image --path=WythoffLines --output=numbers --all --size=60x14 =pod 13 | 105 118 131 144 60 65 70 75 80 85 90 95 100 12 | 97 110 47 52 57 62 67 72 77 82 87 92 11 | 34 39 44 49 54 59 64 69 74 79 84 10 | 31 36 41 46 51 56 61 66 71 76 9 | 28 33 38 43 48 53 58 63 26 8 | 25 30 35 40 45 50 55 23 7 | 22 27 32 37 42 18 20 6 | 19 24 29 13 15 17 5 | 16 21 10 12 14 4 | 5 7 9 11 3 | 4 6 8 2 | 3 2 1 | 1 Y=0 | +----------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 A coordinate pair Y and X are the start of a Fibonacci style recurrence, F[1]=Y, F[2]=X F[i+i] = F[i] + F[i-1] Any such sequence eventually becomes a row of the Wythoff array (L) after some number of initial iterations. The N value at X,Y is the row number of the Wythoff array containing sequence beginning Y and X. Rows are numbered starting from 1. Eg. Y=4,X=1 sequence: 4, 1, 5, 6, 11, 17, 28, 45, ... row 7 of WythoffArray: 17, 28, 45, ... so N=7 at Y=4,X=1 Conversely a given N is positioned in the triangle according to where row number N of the Wythoff array "precurses" by running the recurrence in reverse, F[i-1] = F[i+i] - F[i] It can be shown that such a precurse always reaches a pair Y and X with YE=1 and 0E=XEY, hence making the triangular X,Y arrangement above. N=7 WythoffArray row 7 is 17,28,45,73,... go backwards from 17,28 by subtraction 11 = 28 - 17 6 = 17 - 11 5 = 11 - 6 1 = 6 - 5 4 = 5 - 1 stop on reaching 4,1 which is Y=4,X=1 satisfying Y>=1 and 0<=X=XEY =cut # (r-1 + floor(r*phi)) / (r-1 + 2*floor(r*phi)) # ~= (r-1+r*phi)/(r-1+2*r*phi) # = (r*(phi+1) - 1) / (r*(2phi+1) - 1) # -> r*(phi+1) / r*(2*phi+1) # = (phi+1) / (2*phi+1) # = 1/phi = 0.618 =pod =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::WythoffLines-Enew ()> Create and return a new path object. =back =head1 OEIS The Wythoff array is in Sloane's Online Encyclopedia of Integer Sequences in various forms, =over L (etc) =back A165360 X A165359 Y A166309 N by rows =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/devel/lib/Math/PlanePath/BinaryTerms-oeis.t0000644000175000017500000000663412132055333021707 0ustar gggg#!/usr/bin/perl -w # Copyright 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'max'; use Test; plan tests => 46; use lib 't','xt'; use MyTestHelpers; MyTestHelpers::nowarnings(); use MyOEIS; use Math::PlanePath::BinaryTerms; { require Math::BaseCnv; my $radix = 3; my $path = Math::PlanePath::BinaryTerms->new (radix => $radix); foreach my $y ($path->y_minimum .. 8) { printf '%2d', $y; foreach my $x ($path->x_minimum .. 7) { my $n = $path->xy_to_n($x,$y); my $nr = Math::BaseCnv::cnv($n,10,$radix); printf " %10s", $nr; } print "\n"; } } #------------------------------------------------------------------------------ # A068076 X = num integers 'A068076', func => sub { my ($count) = @_; my $path = Math::PlanePath::BinaryTerms->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x-1; } return \@got; }); #------------------------------------------------------------------------------ # A067576 binary by anti-diagonals upwards MyOEIS::compare_values (anum => 'A067576', func => sub { my ($count) = @_; require Math::PlanePath::Diagonals; my $path = Math::PlanePath::BinaryTerms->new (radix => 2); my $diag = Math::PlanePath::Diagonals->new (direction => 'up', x_start=>1,y_start=>1); my @got; for (my $d = $diag->n_start; @got < $count; $d++) { my ($x,$y) = $diag->n_to_xy($d); # by anti-diagonals push @got, $path->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A066884 binary diagonals downwards MyOEIS::compare_values (anum => 'A066884', func => sub { my ($count) = @_; require Math::PlanePath::Diagonals; my $path = Math::PlanePath::BinaryTerms->new; my $diag = Math::PlanePath::Diagonals->new (x_start=>1,y_start=>1); my @got; for (my $d = $diag->n_start; @got < $count; $d++) { my ($x,$y) = $diag->n_to_xy($d); # by anti-diagonals push @got, $path->xy_to_n($x,$y); } return \@got; }); # A067587 inverse MyOEIS::compare_values (anum => 'A067587', func => sub { my ($count) = @_; require Math::PlanePath::Diagonals; my $path = Math::PlanePath::BinaryTerms->new; my $diag = Math::PlanePath::Diagonals->new (x_start=>1,y_start=>1); my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $diag->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/devel/lib/Math/PlanePath/Godfrey.pm0000644000175000017500000000772013734026652020273 0ustar gggg# Copyright 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Edwin L. Godfrey, "Enumeration of the Rational Points Between 0 and 1", # National Mathematics Magazine, volume 12, number 4, January 1938, pages # 163-166. http://www.jstor.org/stable/3028080 # cf # A126572 Array read by antidiagonals: a(n,m) = the m-th integer from among those positive integers coprime to n. # 1/1 1/2 1/3 1/4 1/5 1/6 1/7 ... # 2/1 2/3 2/5 2/7 2/9 2/11 2/13 ... # 3/1 3/2 3/4 3/5 3/7 3/8 3/10 ... # 4/1 4/3 4/5 4/7 4/9 4/11 4/13 ... # 5/1 5/2 5/3 5/4 5/6 5/7 5/8 ... # 6/1 6/5 6/7 6/11 6/13 6/17 6/19 ... # 7/1 7/2 7/3 7/4 7/5 7/6 7/8 ... # 1/2 1/3 1/4 1/5 1/6 1/7 # 2/3 2/5 2/7 2/9 2/11 2/13 # 3/4 3/5 3/7 3/8 3/10 3/11 # 4/5 4/7 4/9 4/11 4/13 4/15 package Math::PlanePath::Godfrey; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem = \&Math::PlanePath::_divrem; *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::CoprimeColumns; # uncomment this to run the ### lines # use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant x_minimum => 1; use constant y_minimum => 2; use constant diffxy_maximum => -1; # upper octant X<=Y-1 so X-Y<=-1 use constant gcdxy_maximum => 1; # no common factor #------------------------------------------------------------------------------ sub n_to_xy { my ($self, $n) = @_; ### Godfrey n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n)) { return ($n,$n); } my $d = int((_sqrtint(8*$n-7) + 1) / 2); ### $d ### base: ($d-1)*$d/2 $n -= ($d-1)*$d/2; my $y = $n; my $q = $d - $y; # ### assert: $n >= 0 # ### assert: $y >= 1 my $tot = Math::PlanePath::CoprimeColumns::_totient($y); my ($f, $count) = _divrem ($q, $tot); ### $y ### $q ### $tot my $x = 1; if ($count) { for (;;) { $x++; if (Math::PlanePath::CoprimeColumns::_coprime($x,$y)) { --$count or last; } } } # final den: $x + ($f+1)*$y) return ($y, $x + ($f+1)*$y); } sub xy_to_n { my ($self, $x, $y) = @_; ### Godfrey xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 1 || $y < 1) { return undef; } if (is_infinite($x)) { return $x; } my ($f, $r) = _divrem ($y, $x); ### $f ### $r my $w = ($f-1) * Math::PlanePath::CoprimeColumns::_totient($x); ### w from totient: $w foreach my $i (1 .. $r) { if (Math::PlanePath::CoprimeColumns::_coprime($i,$x)) { ### coprime: "$i, x=$x, increment" $w++; } } my $d = $x + $w - 1; ### $x ### $w ### $d ### return: $d*($d-1)/2 + $x return $d*($d-1)/2 + $x; } sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### Godfrey rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 1 || $y2 < 1) { return (1,0); } return (1, $self->xy_to_n($x2,$y2)); } 1; __END__ =cut # math-image --path=Godfrey --output=numbers --all --size=60x14 =pod Math-PlanePath-129/devel/lib/Math/PlanePath/BinaryTerms.pm0000644000175000017500000002302313734026652021125 0ustar gggg# Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # cf A134562 base-3 Y=sum digits # http://cut-the-knot.org/wiki-math/index.php?n=Probability.ComboPlayground # combinations # row # Y=1 2^k # Y=2 2-bit numbers # column # X=1 first with Y many bits is Zeck 11111 # A027941 Fib(2n+1)-1 # X=2 second with Y many bits is Zeck 101111 high 1, low 1111 # A005592 F(2n+1)+F(2n-1)-1 # X=3 third with Y many bits is Zeck 110111 # A005592 F(2n+1)+F(2n-1)-1 # X=4 fourth with Y many bits is Zeck 111011 # 111101 # 111110 # 1001111 # 1010111 # 1011011 # 1011101 # 1011110 # 1100111 # 1101011 # 1101101 # 1101110 # 1110011 # 1110101 # 1110110 # 1111001 # 1111010 # 1111100 # 15 binomial(6,4)=15 # # binomial(a,b) = a! / (b! * (a-b!)) # # binomial(X-1,X-1) 4,4 # binomial(X, X-1) 5,4 # binomial(X+1,X-1) 5,4 # bin(a+1,b) = (a+1)!/(b! * (a+1-b)!) # bin(a+1,b) = a!/(b! * (a-b)!) * (a+1)/(a+1-b) # bin(a+1,b) = bin(a,b) * (a+1)/(a+1-b) # # bin(a,b+1) = (a)!/((b+1)! * (a-b-1)!) # bin(a,b+1) = (a)!/(b! * (a-b)!) * (b+1)*(a-b) # bin(a,b+1) = bin(a,b) * (b+1)*(a-b) # # bin(a-1,b) = (a-1)! / (b! * (a-1-b)!) # bin(a-1,b) = a! / (b! * (a-b)!) ( (a-b)/a # bin(a-1,b) = bin(a,b) * (a-b)/a # bin(a,b-1) = a!/((b-1)! * (a-b+1)!) # bin(a,b-1) = a!/(b! * (a-b)!) * b/(a-b+1) # bin(a,b-1) = bin(a,b) * b/(a-b+1) # # # 1 2 3 4 5 6 # Y=2 11 101 110 1001 1010 1100 # 3 5 6 9 10 12 # 1 \------2 \-------------3 # 1 2 3 4 5 6 # Y=3 111 1011 1101 1110 # 3 11 13 14 # 1 \-------------3 \------------- # 1 2 3 4 5 6 # Y=4 111 1011 1101 1110 # 3 11 13 14 # 1 \-------------3 \------------- package Math::PlanePath::BinaryTerms; use 5.004; use strict; use List::Util 'sum'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem = \&Math::PlanePath::_divrem; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ Math::PlanePath::Base::Digits::parameter_info_radix2(), ]; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant y_minimum => 1; use constant x_minimum => 1; #------------------------------------------------------------------------------ my $global_radix = 0; my $next_n = 1; my @n_to_x; my @n_to_y; my @yx_to_n; sub new { my $self = shift->SUPER::new(@_); $self->{'radix'} ||= 2; if ($global_radix != $self->{'radix'}) { $global_radix = $self->{'radix'}; $next_n = 1; @n_to_x = (); @n_to_y = (); @yx_to_n = (); } return $self; } sub _extend { my ($self) = @_; ### _extend() ... ### $next_n my $n = $next_n++; my @ndigits = digit_split_lowtohigh($n,$self->{'radix'}); ### ndigits low to high: join(',',@ndigits) my $y = 0; foreach (@ndigits) { if ($_) { $y++; } } my $row = ($yx_to_n[$y] ||= []); my $x = scalar(@$row) || 1; $row->[$x] = $n; $n_to_x[$n] = $x; $n_to_y[$n] = $y; ### push: "x=$x y=$y n=$n" ### @yx_to_n } sub n_to_xy { my ($self, $n) = @_; ### BinaryTerms n_to_xy(): "$n radix=$self->{'radix'}" if ($n < 1) { return; } if (is_infinite($n) || $n == 0) { return ($n,$n); } { # fractions on straight line ? my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } my $radix = $self->{'radix'}; if ($radix > 2) { while ($next_n <= $n) { _extend($self); } return ($n_to_x[$n], $n_to_y[$n]); } { my @ndigits = digit_split_lowtohigh($n,$radix); pop @ndigits; # drop high 1-bit my $ones = sum(0,@ndigits); my $y = $ones + 1; ### $y ### ndigits low to high: join(',',@ndigits) ### $ones my $binomial = my $x = $n * 0 + 1; # inherit bignum 1 for (my $len = $ones; $len <= $#ndigits; ) { ### block add to x: $binomial $x += $binomial * ($radix-1)**$ones; # bin(a+1,b) = bin(a,b) * (a+1)/(a+1-b) $len++; $binomial *= $len; ### assert: $binomial % ($len-$ones) == 0 $binomial /= ($len-$ones); ### assert: $binomial == _binomial($len,$ones) } # here $binomial = binomial(len,ones) my $len = scalar(@ndigits); foreach my $digit (reverse @ndigits) { # high to low ### digit: "$digit len=$len ones=$ones binomial=$binomial x=$x" if ($len == $ones || $ones == 0) { last; } # bin(a-1,b) = bin(a,b) * (a-b)/a $binomial *= ($len-$ones); ### assert: $binomial % $len == 0 $binomial /= $len; $len--; ### decr len to: "len=$len ones=$ones binomial=$binomial" ### assert: $binomial == _binomial($len,$ones) if ($digit) { ### add to x: $binomial $x += $binomial * $digit * ($radix-1)**$ones; # bin(a,b-1) = bin(a,b) * b/(a-b+1) ### assert: ($binomial * $ones) % ($len-$ones+1) == 0 $binomial *= $ones; $ones--; $binomial /= ($len-$ones); ### assert: $binomial == _binomial($len,$ones) } } ### result: "x=$x ones=$ones" return ($x, $y); } } sub xy_to_n { my ($self, $x, $y) = @_; ### BinaryTerms xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); my $radix = $self->{'radix'}; if ($radix > 2) { if ($x < 1 || $y < 1) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } for (;;) { if (defined (my $n = $yx_to_n[$y][$x])) { return $n; } _extend($self); } } { $x -= 1; if ($x < 0 || $y < 1) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my $len = my $ones = $y-1; my $binomial = 1; while ($x >= $binomial * ($radix-1)**$ones) { ### subtract high from: "len=$len ones=$ones binomial=$binomial x=$x" $x -= $binomial; # bin(a+1,b) = bin(a,b) * (a+1)/(a+1-b) $len++; $binomial *= $len; ### assert: $binomial % ($len-$ones) == 0 $binomial /= ($len-$ones); ### assert: $binomial == _binomial($len,$ones) } ### found high: "len=$len ones=$ones binomial=$binomial x=$x" my @ndigits = (1); # high to low while ($len > 0) { ### at: "len=$len ones=$ones binomial=$binomial x=$x" ### assert: $len >= $ones if ($len == $ones) { push @ndigits, (1) x $ones; last; } if ($ones == 0) { push @ndigits, (0) x $len; last; } # bin(a-1,b) = bin(a,b) * (a-b)/a $binomial *= ($len-$ones); ### assert: $binomial % $len == 0 $binomial /= $len; $len--; ### decr len to: "len=$len ones=$ones binomial=$binomial" ### assert: $binomial == _binomial($len,$ones) my $bcmp = $binomial * ($radix-1)**$ones; ### compare: "x=$x bcmp=$bcmp" if ($x >= $bcmp) { ### yes, above, push digit ... # (my $digit, $x) = _divrem($x,$bcmp); # push @ndigits, $digit; # ### assert: $digit >= 1 # ### assert: $digit < $radix $x -= $binomial * ($radix-1)**$ones; push @ndigits, 1; # bin(a,b-1) = bin(a,b) * b/(a-b+1) $binomial *= $ones; $ones--; ### assert: ($binomial * $ones) % ($len-$ones) == 0 $binomial /= $len-$ones; ### assert: $binomial == _binomial($len,$ones) } else { ### no, push 0 digit ... push @ndigits, 0; } } ### ndigits: join(',',@ndigits) @ndigits = reverse @ndigits; return digit_join_lowtohigh(\@ndigits,$radix, $x*0*$y); } } sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### BinaryTerms rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 1 || $y2 < 1) { return (1,0); } return (1, max($self->xy_to_n($x2,$y2), $self->xy_to_n($x2,1))); return (1, 10000); } sub _binomial { my ($a,$b) = @_; $a >= $b or die "_binomial($a,$b)"; my $ret = 1; foreach (2 .. $a) { $ret *= $_ } foreach (2 .. $b) { $ret /= $_ } foreach (2 .. $a-$b) { $ret /= $_ } ### _binomial: "a=$a b=$b binomial=$ret" return $ret; } 1; __END__ =cut # math-image --path=BinaryTerms --output=numbers --all --size=60x14 =pod Math-PlanePath-129/devel/lib/Math/PlanePath/WythoffTriangle.pm0000644000175000017500000000570213734026651022005 0ustar gggg# Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::WythoffTriangle; use 5.004; use strict; use List::Util 'max'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # uncomment this to run the ### lines # use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant y_minimum => 1; use constant xy_is_visited => 1; use Math::PlanePath::WythoffPreliminaryTriangle; my $preliminary = Math::PlanePath::WythoffPreliminaryTriangle->new; sub n_to_xy { my ($self, $n) = @_; ### WythoffTriangle n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n) || $n == 0) { return ($n,$n); } { # fractions on straight line ? my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } my ($x,$y) = $preliminary->n_to_xy($n) or return; $x = 0; foreach my $x2 (0 .. $y-1) { my $n2 = $preliminary->xy_to_n($x2,$y) or return; ### cf: "x2=$x2 n2=$n2" if ($n2 < $n) { ### is below ... $x++; } } return ($x, $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### WythoffTriangle xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($y < 1) { return undef; } if (is_infinite($y)) { return $y; } unless ($x >= 0 && $x < $y) { return undef; } my @n = sort {$a<=>$b} map { $preliminary->xy_to_n($_,$y) } 0 .. $y-1; return $n[$x]; } sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### WythoffTriangle rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 1) { ### all outside first quadrant ... return (1, 0); } # bottom left into first quadrant if ($x1 < 0) { $x1 *= 0; } if ($y1 < 0) { $y1 *= 0; } return (1, $self->xy_to_n(0,2*$y2)); } 1; __END__ Math-PlanePath-129/devel/lib/Math/PlanePath/QuintetSide.pm0000644000175000017500000001711613734026651021131 0ustar gggg# mostly works, but any good ? # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=QuintetSide --lines --scale=10 # math-image --path=QuintetSide --output=numbers package Math::PlanePath::QuintetSide; use 5.004; use strict; use POSIX 'ceil'; use Math::Libm 'hypot'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA', '@_xend','@_yend'; $VERSION = 129; use Math::PlanePath 37; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh'; use Math::PlanePath::SacksSpiral; # uncomment this to run the ### lines #use Devel::Comments; use constant n_start => 0; sub n_to_xy { my ($self, $n) = @_; ### QuintetSide n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $x; my $y = 0; { my $int = int($n); $x = $n - $int; $n = $int; } my $xend = 1; my $yend = 0; foreach my $digit (digit_split_lowtohigh($n,3)) { my $xend_offset = $xend - $yend; # end + end rotated +90 my $yend_offset = $yend + $xend; # being the digit 2 position ### at: "$x,$y" ### $digit ### $xend ### $yend ### $xend_offset ### $yend_offset if ($digit == 1) { ($x,$y) = (-$y + $xend, # rotate +90 $x + $yend); } elsif ($digit == 2) { $x += $xend_offset; # digit 2 offset position $y += $yend_offset; } $xend += $xend_offset; # 2*end + end rotated +90 $yend += $yend_offset; } ### final: "$x,$y" return ($x, $y); } @_xend = (1); @_yend = (0); sub _ends_for_level { my ($level) = @_; ### $#_xend if ($#_xend < $level) { my $x = $_xend[-1]; my $y = $_yend[-1]; do { ($x,$y) = (2*$x - $y, # 2*$x + rotate +90 2*$y + $x); # 2*$y + rotate +90 ### _ends_for_level() push: scalar(@_xend)." $x,$y" # ### assert: "$x,$y" eq join(','__PACKAGE__->n_to_xy(scalar(@xend) ** 3)) push @_xend, $x; push @_yend, $y; } while ($#_xend < $level); } } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest($x); $y = round_nearest($y); ### QuintetSide xy_to_n(): "$x, $y" my $r = hypot($x,$y); my $level = ceil(log($r+1)/log(sqrt(5))); if (is_infinite($level)) { return $level; } return _xy_to_n_in_level($x,$y,$level); } sub _xy_to_n_in_level { my ($x, $y, $level) = @_; _ends_for_level($level); my @pending_n = (0); my @pending_x = ($x); my @pending_y = ($y); my @pending_level = ($level); while (@pending_n) { my $n = pop @pending_n; $x = pop @pending_x; $y = pop @pending_y; $level = pop @pending_level; ### consider: "$x,$y n=$n level=$level" if ($level == 0) { if ($x == 0 && $y == 0) { return $n; } next; } my $xend = $_xend[$level-1]; my $yend = $_yend[$level-1]; if (hypot($x,$y) * (.9/sqrt(5)) > hypot($xend,$yend)) { ### radius out of range: hypot($x,$y)." cf end ".hypot($xend,$yend) next; } $level--; $n *= 3; ### descend: "end=$xend,$yend" # digit 0 push @pending_n, $n; push @pending_x, $x; push @pending_y, $y; push @pending_level, $level; ### push: "$x,$y digit=0" # digit 1 $x -= $xend; $y -= $yend; ($x,$y) = ($y, -$x); # rotate -90 push @pending_n, $n + 1; push @pending_x, $x; push @pending_y, $y; push @pending_level, $level; ### push: "$x,$y digit=1" # digit 2 $x -= $xend; $y -= $yend; ($x,$y) = (-$y, $x); # rotate +90 push @pending_n, $n + 2; push @pending_x, $x; push @pending_y, $y; push @pending_level, $level; ### push: "$x,$y digit=2" } return undef; } # radius = sqrt(5) ^ level # log(radius) = level * log(sqrt(5)) # level = log(radius) * 1/log(sqrt(5)) # sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $y1 *= sqrt(3); $y2 *= sqrt(3); my ($r_lo, $r_hi) = Math::PlanePath::SacksSpiral::_rect_to_radius_range ($x1,$y1, $x2,$y2); my $level = ceil (log($r_hi+.1) * (1/log(sqrt(5)))); if ($level < 1) { $level = 1; } return (0, 3**$level - 1); } 1; __END__ =for stopwords eg Ryde =head1 NAME Math::PlanePath::QuintetSide -- one side of the quintet tiling =head1 SYNOPSIS use Math::PlanePath::QuintetSide; my $path = Math::PlanePath::QuintetSide->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path is ... ... | 26----27 | 24----25 | 23----22 | 20----21 | 18----19 | 17----16 | 15----14 | 13----12 6 | 11----10 5 | 8---- 9 4 | 6---- 7 3 | 5---- 4 2 | 2---- 3 1 | 0---- 1 <- Y=0 ^ X=0 1 2 3 It slowly spirals around counter clockwise, with a lot of wiggling in between. The N=3^level point is at N = 3^level angle = level * atan(1/2) = level * 26.56 degrees radius = sqrt(5) ^ level A full revolution for example takes roughly level=14 which is about N=4,780,000. Both ends of such levels are in fact sub-spirals, like an "S" shape. =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::QuintetSide-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional C<$n> gives a point on the straight line between surrounding integer N. =back =head1 SEE ALSO L, L L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/devel/lib/Math/PlanePath/ParabolicRows.pm0000644000175000017500000000733413734026651021443 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # A056520 1,2,6,15 (n+2)*(2*n^2-n+3)/6 starting n=0 # package Math::PlanePath::ParabolicRows; use 5.004; use strict; #use List::Util 'min', 'max'; *min = \&Math::PlanePath::_min; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; # first N in row, counting from N=1 at X=0,Y=0 # [ 0,1,2,3 ], # [ 1,2,6,15 ] # N = (1/3 y^3 + 1/2 y^2 + 1/6 y + 1) # = (2 y^3 + 3 y^2 + y + 1) / 6 # = ((2*y + 3)*y + 1)*y/6 + 1 + $x; sub n_to_xy { my ($self, $n) = @_; ### ParabolicRows n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; if (2*$n >= 1) { # if frac>=0.5 $int += 1; $n -= 1; } ### $int ### $n my $yhi = _sqrtint($int) + 2; my $y = 0; for (;;) { my $ymid = int(($yhi+$y)/2); ### at: "y=$y ymid=$ymid yhi=$yhi" if ($ymid == $y) { ### assert: $y+1 == $yhi ### found, row starting: ((2*$y + 3)*$y + 1)*$y/6 + 1 ### $y ### x: $n + ($int - ((2*$y + 3)*$y + 1)*$y/6) return ($n + ($int - ((2*$y + 3)*$y + 1)*$y/6 - 1), $y); } ### compare: ((2*$ymid + 3)*$ymid + 1)*$ymid/6 + 1 if ($int >= ((2*$ymid + 3)*$ymid + 1)*$ymid/6 + 1) { $y = $ymid; } else { $yhi = $ymid; } } # my $y = 0; # for (;;) { # my $max = ($y+1)**2; # if ($int <= $max) { # return ($n+$int-1,$y); # } # $y++; # $int -= $max; # } } sub xy_to_n { my ($self, $x, $y) = @_; ### ParabolicRows xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($y < 0) { return undef; } my $ysquared = ($y+1)*($y+1); if ($x >= $ysquared) { return undef; } return ((2*$y + 3)*$y + 1)*$y/6 + 1 + $x; } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### ParabolicRows rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 0) { ### all outside first quadrant ... return (1, 0); } if ($y1 < 0) { $y1 *= 0; } if ($x1 < 0) { $x1 *= 0; } elsif ($x1 >= ($y1+1)*($y1+1)) { $y1 = _sqrt_ceil($x1+1); ### increase y1 to put x1 in range: $y1 } ### assert: defined $self->xy_to_n ($x1, $y1) ### assert: defined $self->xy_to_n (min($x2,($y2+2)*$y2), $y2) # monotonic increasing in $x and $y directions, so this is exact return ($self->xy_to_n ($x1, $y1), $self->xy_to_n (min($x2,($y2+2)*$y2), $y2)); } sub _sqrt_ceil { my ($n) = @_; my $sqrt = _sqrtint($n); if ($sqrt*$sqrt < $n) { $sqrt += 1; } return $sqrt; } 1; __END__ Math-PlanePath-129/devel/lib/Math/PlanePath/PyramidReplicate.pm0000644000175000017500000001653113734026651022131 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=PyramidReplicate --lines --scale=10 # math-image --path=PyramidReplicate --all --output=numbers_dash --size=80x50 package Math::PlanePath::PyramidReplicate; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow'; # uncomment this to run the ### lines #use Devel::Comments; use constant n_start => 0; # 4 3 2 # 5 0 1 # 6 7 8 # my @digit_to_x = (0,1,0,-1, -2,-3,-2,-1, 0,-1, 0, 1, 2,1,2,3); my @digit_to_y = (0,0,1, 0, 1, 1, 0, 1, -1,-1,-2,-1, 1,1,0,1); sub n_to_xy { my ($self, $n) = @_; ### PyramidReplicate n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $x = my $y = ($n * 0); # inherit bignum 0 my $len = ($x + 1); # inherit bignum 1 my $bx = 1; my $by = 1; while ($n) { my $digit = $n % 16; $n = int($n/16); ### at: "$x,$y" ### $digit $x += $digit_to_x[$digit] * $bx; $y += $digit_to_y[$digit] * $by; $bx *= 6; $by *= 4; } ### final: "$x,$y" return ($x,$y); } # mod digit # 5 3 4 4 3 2 (x mod 3) + 3*(y mod 3) # 2 0 1 5 0 1 # 8 6 7 6 7 8 # my @mod_to_digit = (0,1,5, 3,2,4, 7,8,6); sub xy_to_n { my ($self, $x, $y) = @_; ### PyramidReplicate xy_to_n(): "$x, $y" return undef; $x = round_nearest ($x); $y = round_nearest ($y); my ($len,$level_limit); { my $xa = abs($x); my $ya = abs($y); ($len,$level_limit) = round_down_pow (2*($xa > $ya ? $xa : $ya) || 1, 3); ### $level_limit ### $len } $level_limit += 2; if (is_infinite($level_limit)) { return $level_limit; } my $n = ($x * 0 * $y); # inherit bignum 0 my $power = ($n + 1); # inherit bignum 1 while ($x || $y) { if ($level_limit-- < 0) { ### oops, level limit reached ... return undef; } my $m = ($x % 3) + 3*($y % 3); my $digit = $mod_to_digit[$m]; ### at: "$x,$y m=$m digit=$digit" $x -= $digit_to_x[$digit]; $y -= $digit_to_y[$digit]; ### subtract: "$digit_to_x[$digit],$digit_to_y[$digit] to $x,$y" ### assert: $x % 3 == 0 ### assert: $y % 3 == 0 $x /= 3; $y /= 3; $n += $digit * $power; $power *= 9; } return $n; } # level N Xmax # 1 9^1-1 1 # 2 9^2-1 1+3 # 3 9^3-1 1+3+9 # X <= 3^0+3^1+...+3^(level-1) # X <= 1 + 3^0+3^1+...+3^(level-1) # X <= (3^level - 1)/2 # 2*X+1 <= 3^level # level >= log3(2*X+1) # # X < 1 + 3^0+3^1+...+3^(level-1) # X < 1 + (3^level - 1)/2 # (3^level - 1)/2 > X-1 # 3^level - 1 > 2*X-2 # 3^level > 2*X-1 # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### PyramidReplicate rect_to_n_range(): "$x1,$y1 $x2,$y2" my $max = abs(round_nearest($x1)); foreach ($y1, $x2, $y2) { my $m = abs(round_nearest($_)); if ($m > $max) { $max = $m } } my ($len,$level) = round_down_pow (2*($max||1)-1, 3); return (0, 9*$len*$len - 1); # 9^level-1 } 1; __END__ =for stopwords eg Ryde Math-PlanePath aabbccdd =head1 NAME Math::PlanePath::PyramidReplicate -- replicating squares =head1 SYNOPSIS use Math::PlanePath::PyramidReplicate; my $path = Math::PlanePath::PyramidReplicate->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is a self-similar replicating pyramid shape made from 4 points each, 4 3 2 1 <- Y=0 -1 -2 -3 -4 ^ -4 -3 -2 -1 X=0 1 2 3 4 The base shape is the initial N=0 to N=8 section, +---+ | 2 | +---+---+---+ | 3 | 0 | 1 | +---+---+---+ It then repeats inverted to make a similar shape but upside-down, +---+---+---+---+---+---+---+ | 5 4 7 | 2 |13 12 15 | +---+ +---+ +---+ +---+ | 6 | 3 0 1 |14 | +---+---+---+---+---+ | 9 8 11 | +---+ +---+ |10 | +---+ =head2 Level Ranges A given replication extends to ... Nlevel = 4^level - 1 - ... <= X <= ... - ... <= Y <= ... =head2 Complex Base This pattern corresponds to expressing a complex integer X+i*Y in base b=... X+Yi = a[n]*b^n + ... + a[2]*b^2 + a[1]*b + a[0] using complex digits a[i] encoded in N in integer base 4 ... a[i] digit N digit ---------- ------- 0 0 1 1 i 2 -1 3 =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::PyramidReplicate-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head1 SEE ALSO L, L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/devel/lib/Math/PlanePath/wythoff-lines.pl0000644000175000017500000000254012375744415021470 0ustar gggg#!/usr/bin/perl -w # Copyright 2013, 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::PlanePath::WythoffLines; { foreach my $shift (-3 .. 17) { my $path = Math::PlanePath::WythoffLines->new (shift => $shift); my $x_minimum = $path->x_minimum; my $y_minimum = $path->y_minimum; my $m = Math::PlanePath::WythoffLines::_calc_minimum($shift); printf "%2d %4d %4d %4d\n", $shift, $m, $x_minimum, $y_minimum; } exit 0; } { my @values; for (my $shift = 8; $shift < 28; $shift += 2) { push @values, Math::PlanePath::WythoffLines::_calc_minimum($shift); } print join(',',@values),"\n"; require Math::OEIS::Grep; Math::OEIS::Grep->search(array=>\@values); exit 0; } Math-PlanePath-129/devel/lib/Math/PlanePath/NxNvar.pm0000644000175000017500000000613313734026651020104 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::NxNvar; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; *_sqrtint = \&Math::PlanePath::_sqrtint; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; sub n_to_xy { my ($self, $n) = @_; ### NxN n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { # fractions on straight line ? my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } # d = [ 0, 1, 2, 3, 4 ] # n = [ 0, 1, 3, 6, 10 ] # N = (d+1)*d/2 # d = (-1 + sqrt(8*$n+1))/2 my $d = int((_sqrtint(8*$n+1) - 1) / 2); $n -= $d*($d+1)/2; ### $d ### $n my $x = $d-$n; # downwards my $y = $n; # upwards my $diff = $x-$y; ### diagonals xy: "$x, $y diff=$diff" if ($diff < 0) { return (2*$x + (($diff+1) % 2), 2*$x + int((-$diff + ($diff%2))/2)); } elsif ($diff < 3) { return (2*$y + $diff, 2*$y); } else { return (2*$y + int(($diff+1)/2) + (($diff+1) % 2), 2*$y + ($diff % 2)); } } sub xy_to_n { my ($self, $x, $y) = @_; ### NxN xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } return undef; if ($x <= $y) { my $h = int($x/2); ($x,$y) = ($h, $h + ($x%2) + 2*($y - 2*$h - ($x%2))); } else { my $h = int($y/2); ($x,$y) = (1 + $h + ($y%2) + 2*($x-1 - 2*$h - ($y%2)), $h); } return (($x+$y)**2 + $x+3*$y)/2; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### NxN rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 0) { ### all outside first quadrant ... return (1, 0); } return (0, $x2 * $y2); } 1; __END__ Math-PlanePath-129/devel/lib/Math/PlanePath/godfrey.pl0000644000175000017500000000277212375744415020340 0ustar gggg#!/usr/bin/perl -w # Copyright 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use POSIX (); use List::Util 'sum'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use Math::PlanePath::Godfrey; # uncomment this to run the ### lines use Smart::Comments; { my $path = Math::PlanePath::Godfrey->new; foreach my $n (1 .. 1+2+3+4+5+6+7) { my ($x,$y) = $path->n_to_xy($n); print "$y,"; } print "\n"; exit 0; } { require Math::NumSeq::OEIS::File; my $seq = Math::NumSeq::OEIS::File->new(anum=>'A126572'); # OFFSET=1 my $perm = Math::NumSeq::OEIS::File->new(anum=>'A038722'); # OFFSET=1 my @values; foreach my $n (1 .. 1+2+3+4+5+6+7) { my $pn = $perm->ith($n); push @values, $seq->ith($n); } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; } Math-PlanePath-129/devel/lib/Math/PlanePath/WythoffDifference-oeis.t0000644000175000017500000000734113775042756023070 0ustar gggg#!/usr/bin/perl -w # Copyright 2013, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'max'; use Math::BigInt try => 'GMP'; # for bignums in reverse-add steps use Test; plan tests => 46; use lib 't','xt'; use MyTestHelpers; MyTestHelpers::nowarnings(); use MyOEIS; use Math::PlanePath::WythoffDifference; use Math::PlanePath::Diagonals; #------------------------------------------------------------------------------ # A080164 -- Wythoff difference array by anti-diagonals MyOEIS::compare_values (anum => 'A080164', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffDifference->new; my $diag = Math::PlanePath::Diagonals->new (direction => 'up'); my @got; for (my $d = $diag->n_start; @got < $count; $d++) { my ($x,$y) = $diag->n_to_xy($d); # by anti-diagonals push @got, $path->xy_to_n($x,$y); } return \@got; }); # A134571 downwards MyOEIS::compare_values (anum => 'A134571', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffDifference->new; my $diag = Math::PlanePath::Diagonals->new (direction => 'down'); my @got; for (my $d = $diag->n_start; @got < $count; $d++) { my ($x,$y) = $diag->n_to_xy($d); # by anti-diagonals push @got, $path->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A191361 -- Wythoff difference array X-Y, diagonal containing n MyOEIS::compare_values (anum => 'A191361', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffDifference->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $x-$y; } return \@got; }); #------------------------------------------------------------------------------ # A000201 -- Wythoff difference Y axis # lower Wythoff sequence, spectrum of phi MyOEIS::compare_values (anum => 'A000201', max_count => 200, func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffDifference->new; my @got; for (my $y = Math::BigInt->new(0); @got < $count; $y++) { push @got, $path->xy_to_n (0, $y); } return \@got; }); #------------------------------------------------------------------------------ # A001519 -- Wythoff difference X axis, a(n) = 3*a(n-1) - a(n-2) # A122367 MyOEIS::compare_values (anum => 'A122367', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffDifference->new; my @got; for (my $x = Math::BigInt->new(0); @got < $count; $x++) { push @got, $path->xy_to_n ($x, 0); } return \@got; }); MyOEIS::compare_values (anum => 'A001519', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffDifference->new; my @got = (1); # extra initial 1 for (my $x = Math::BigInt->new(0); @got < $count; $x++) { push @got, $path->xy_to_n ($x, 0); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/devel/lib/Math/PlanePath/BalancedArray.pm0000644000175000017500000000646313734026652021367 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::BalancedArray; use 5.004; use strict; use List::Util 'max'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::NumSeq::BalancedBinary; # uncomment this to run the ### lines #use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant xy_is_visited => 1; sub new { my $self = shift->SUPER::new (@_); $self->{'seq'} = Math::NumSeq::BalancedBinary->new; return $self; } sub n_to_xy { my ($self, $n) = @_; ### BalancedArray n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n) || $n == 0) { return ($n,$n); } { # fractions on straight line ? my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } my $value = $self->{'seq'}->ith($n)||0; ### value: sprintf '%#b', $value my $x = 0; while (($value % 4) == 2) { $x++; $value -= 2; $value /= 4; } return ($x, $value ? $self->{'seq'}->value_to_i($value) : 0); } sub xy_to_n { my ($self, $x, $y) = @_; ### BalancedArray xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } my $zero = $x * 0 * $y; if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my $value = $self->{'seq'}->ith($y) || 0; ### value at y: $value my $pow = (4+$zero)**$x; $value *= $pow; $value += 2*($pow-1)/3; ### mul: sprintf '%#b', $pow ### add: sprintf '%#b', 2*($pow-1)/3 ### value: sprintf '%#b', $value ### $value ### value: ref $value && $value->as_bin return $self->{'seq'}->value_to_i($value); } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### BalancedArray rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 0) { ### all outside first quadrant ... return (1, 0); } # bottom left into first quadrant if ($x1 < 0) { $x1 *= 0; } if ($y1 < 0) { $y1 *= 0; } return (0, 4**($x2+$y2)); return ($self->xy_to_n($x1,$y1), # bottom left $self->xy_to_n($x2,$y2)); # top right } 1; __END__ Math-PlanePath-129/devel/lib/Math/PlanePath/WythoffTriangle-oeis.t0000644000175000017500000000375012112751302022555 0ustar gggg#!/usr/bin/perl -w # Copyright 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'max'; use Test; plan tests => 46; use lib 't','xt'; use MyTestHelpers; MyTestHelpers::nowarnings(); use MyOEIS; use Math::PlanePath::WythoffTriangle; #------------------------------------------------------------------------------ # A166310 Wythoff Triangle, N by rows MyOEIS::compare_values (anum => 'A166310', func => sub { my ($count) = @_; require Math::PlanePath::PyramidRows; my $path = Math::PlanePath::WythoffTriangle->new; my $rows = Math::PlanePath::PyramidRows->new (step=>1); my @got; for (my $r = $rows->n_start; @got < $count; $r++) { my ($x,$y) = $rows->n_to_xy($r); # by rows $y += 1; push @got, $path->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ # A165359 column 1 of left justified Wythoff, gives triangle Y MyOEIS::compare_values (anum => 'A165359', func => sub { my ($count) = @_; my $path = Math::PlanePath::WythoffTriangle->new; my @got; for (my $n = $path->n_start; @got < $count; $n++) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/devel/lib/Math/PlanePath/PowerRows.pm0000644000175000017500000000763313734026651020645 0ustar gggg# Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::PowerRows; use 5.004; use strict; #use List::Util 'min', 'max'; *min = \&Math::PlanePath::_min; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow'; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; use constant parameter_info_array => [ Math::PlanePath::Base::Digits::parameter_info_radix2(), { name => 'align', type => 'enum', share_key => 'align_rl', display => 'Align', default => 'right', choices => ['right', 'left'], choices_display => ['Right', 'Left'], }, ]; sub x_minimum { my ($self) = @_; return ($self->{'align'} eq 'right' ? 0 : undef); } sub x_maximum { my ($self) = @_; return ($self->{'align'} eq 'left' ? 0 : undef); } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'align'} ||= 'right'; $self->{'radix'} ||= 2; return $self; } # Nrow = 1/2 + (r + r + r^2 + ... + r^(depth-1)) # = 1/2 + (r^depth - 1) / (r-1) # (N-1/2)*(r-1) = r^depth - 1 # r^depth = (N-1/2)*(r-1) + 1 # = (2N-1)*(r-1)/2 + 1 # 2Nrow = 1 + 2*(r^depth - 1) / (r-1); # = 1 + 2*(pow - 1) / (r-1); # sub n_to_xy { my ($self, $n) = @_; ### PowerRows n_to_xy(): $n $n *= 2; if ($n < 1) { return; } if (is_infinite($n)) { return ($n,$n); } my $radix = $self->{'radix'}; my ($pow, $y) = round_down_pow (($n-1)*($radix-1)/2 + 1, $radix); if ($self->{'align'} eq 'left') { $n -= 2*$pow; } else { $n -= 2; } return ($n/2 - ($pow-1)/($radix-1), $y); } # uncomment this to run the ### lines # use Smart::Comments; sub xy_to_n { my ($self, $x, $y) = @_; ### PowerRows xy_to_n(): "$x, $y" $y = round_nearest ($y); if ($y < 0) { ### all Y negative ... return undef; } my $radix = $self->{'radix'}; my $zero = $x * 0 * $y; $y = ($radix + $zero) ** $y; ### Y power: $y $x = round_nearest ($x); if ($self->{'align'} eq 'left') { if ($x > 0 || $x <= -$y) { ### X outside 0 to -R^Y ... return undef; } $x += $y; $x -= 1; } else { if ($x < 0 || $x >= $y) { ### X outside 0 to R^Y ... return undef; } } # Nrow = 1 + (r^depth - 1) / (r-1) return $x + ($y-1)/($radix-1) + 1; } # Nrow = 1 + (r^Y - 1) / (r-1) # Nlast = Nrow(Y+1)-1 # = 1 + (r^(Y+1) - 1) / (r-1) - 1 # = (r^(Y+1) - 1) / (r-1) sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### PowerRows rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($y2 < 0 || ($self->{'align'} eq 'right' ? $x2 < 0 : $x1 > 0)) { ### all outside ... return (1, 0); } my $radix = $self->{'radix'}; my $zero = $x1 * 0 * $x2 * $y1 * $y2; return (1, (($radix + $zero) ** ($y2+1) - 1) / ($radix-1)) } 1; __END__ Math-PlanePath-129/devel/lib/Math/PlanePath/PeanoHalf.pm0000644000175000017500000002402513734026651020525 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=PeanoHalf,arms=2 --all --output=numbers_dash # http://www.nahee.com/spanky/www/fractint/lsys/variations.html # http://www.nahee.com/spanky/www/fractint/lsys/moore.gif # William McWorter mcworter@midohio.net package Math::PlanePath::PeanoHalf; use 5.004; use strict; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::PeanoCurve; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant parameter_info_array => [ { name => 'radix', share_key => 'radix_3', display => 'Radix', type => 'integer', minimum => 2, default => 3, width => 3, }, { name => 'arms', share_key => 'arms_2', display => 'Arms', type => 'integer', minimum => 1, maximum => 2, default => 1, width => 1, description => 'Arms', } ]; sub new { my $self = shift->SUPER::new(@_); if (! $self->{'radix'} || $self->{'radix'} < 2) { $self->{'radix'} = 3; } $self->{'arms'} = max(1, min(2, $self->{'arms'} || 1)); return $self; } sub n_to_xy { my ($self, $n) = @_; ### PeanoHalf n_to_xy(): $n if ($n < 0) { return; } my $arms = $self->{'arms'}; my $x_reverse; if ($arms > 1) { my $int = int($n); my $x_reverse = _divrem_mutate($int,2); $int = -$int; } else { $x_reverse = 0; } my $radix = $self->{'radix'}; my ($len, $level) = round_down_pow (2*$n*$radix, $radix); ### $len ### peano at: $n + ($len*$len-1)/2 my ($x,$y) = $self->Math::PlanePath::PeanoCurve::n_to_xy($n + ($len*$len-1)/2); my $half = ($len-1)/2; my $y_reverse; if ($radix % 2) { $x_reverse ^= ($level & 1); $y_reverse = $x_reverse ^ 1; } else { $y_reverse = $x_reverse; } if ($x_reverse) { $x = $half - $x; } else { $x -= $half; } if ($y_reverse) { $y = $half - $y; } else { $y -= $half; } return ($x, $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### PeanoHalf xy_to_n(): "$x, $y" return undef; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### PeanoHalf rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); my $radix = $self->{'radix'}; my $zero = ($x1 * 0 * $y1 * $x2 * $y2); # inherit bignum my ($len, $level) = round_down_pow ($zero + max(abs($x1),abs($y1), abs($x2),abs($y2))*2-1, $radix); ### $len ### $level $len *= $radix; return (0, ($len*$len - 1) * $self->{'arms'} / 2); } 1; __END__ =for stopwords eg Ryde ie PeanoHalf Math-PlanePath Moore =head1 NAME Math::PlanePath::PeanoHalf -- 9-segment self-similar spiral =head1 SYNOPSIS use Math::PlanePath::PeanoHalf; my $path = Math::PlanePath::PeanoHalf->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is an integer version of a 9-segment self-similar curve by ... =cut # math-image --path=PeanoHalf --expression='i<=44?i:0' --output=numbers_dash =pod 7-- 6-- 5-- 4-- 3-- 2 1 | | 8-- 9--10 0-- 1 <- Y=0 | 13--12--11 -1 | 14--15--16 29--30--31--32--33--34 -2 | | | 19--18--17 28--27--26 37--36--35 ...--44 -3 | | | | 20--21--22--23--24--25 38--39--40--41--42--43 -4 ^ -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 ****************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** ****************************************************** *************************** ********* *************************** ********* *************************** ********* *************************** ****** ********* *************************** *** ** ********* *************************** *** ********* *************************** ****************** *************************** ****************** *************************** ****************** *************************** *************************** *************************** *************************** *************************** *************************** *************************** *************************** *************************** =head2 Arms The optional C 2> parameter can give a second copy of the spiral rotated 180 degrees. With two arms all points of the plane are covered. 93--91 81--79--77--75 57--55 45--43--41--39 122-124 .. | | | | | | | | | | | 95 89 83 69--71--73 59 53 47 33--35--37 120 126 132 | | | | | | | | | | | 97 87--85 67--65--63--61 51--49 31--29--27 118 128-130 | | | 99-101-103 22--20 10-- 8-- 6-- 4 13--15 25 116-114-112 | | | | | | | | | 109-107-105 24 18 12 1 0-- 2 11 17 23 106-108-110 | | | | | | | | | 111-113-115 26 16--14 3-- 5-- 7-- 9 19--21 104-102-100 | | | 129-127 117 28--30--32 50--52 62--64--66--68 86--88 98 | | | | | | | | | | | 131 125 119 38--36--34 48 54 60 74--72--70 84 90 96 | | | | | | | | | | | .. 123-121 40--42--44--46 56--58 76--78--80--82 92--94 The first arm is the even numbers N=0,2,4,etc and the second arm is the odd numbers N=1,3,5,etc. =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::PeanoHalf-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head1 FORMULAS =head2 X,Y to N The correspondence to Wunderlich's 3x3 serpentine curve can be used to turn X,Y coordinates in base 3 into an N. Reckoning the innermost 3x3 as level=1 then the smallest abs(X) or abs(Y) in a level is Xlevelmin = (3^level + 1) / 2 eg. level=2 Xlevelmin=5 which can be reversed as level = log3floor( max(abs(X),abs(Y)) * 2 - 1 ) eg. X=7 level=log3floor(2*7-1)=2 An offset can be applied to put X,Y in the range 0 to 3^level-1, offset = (3^level-1)/2 eg. level=2 offset=4 Then a table can give the N base-9 digit corresponding to X,Y digits Y=2 4 3 2 N digit Y=1 -1 0 1 Y=0 -2 -3 -4 X=0 X=1 X=2 A current rotation maintains the "S" part directions and is updated by a table Y=2 0 +3 0 rotation when descending Y=1 +1 +2 +1 into sub-part Y=0 0 +3 0 X=0 X=1 X=2 The negative digits of N represent backing up a little in some higher part. If N goes negative at any state then X,Y was off the main curve and instead on the second arm. If the second arm is not of interest the calculation can stop at that stage. It no doubt would also work to take take X,Y as balanced ternary digits 1,0,-1, but it's not clear that would be any faster or easier to calculate. =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/devel/lib/Math/PlanePath/Z2DragonCurve.pm0000644000175000017500000001142613734026651021324 0ustar gggg# Copyright 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # much overlap package Math::PlanePath::Z2DragonCurve; use 5.004; use strict; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest', 'xy_is_even'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh'; use vars '$VERSION', '@ISA'; $VERSION = 129; @ISA = ('Math::PlanePath'); # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; #------------------------------------------------------------------------------ # # . # h # . # ......... # . # ....g... # . . # . . . # . . # .. f..10---d--11 # . | # 7...|.... # | | . # 8---c---9 e # | . # 6-------5 3 # | # 2---b---3 2 # | | # | 4 1 # | # 0---a---1 0 # # 0 1 2 3 4 # 10---*--11 # | # 7 | # | | # 8---*---9 # | # 6-------5 # \ / | \ # 2--/*---3 # /|\ |/ \ # | 4 # \ / \|/ / # 0---*---1 # \ / / \ sub n_to_xy { my ($self, $n) = @_; ### Z2DragonCurve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } my $zero = ($n * 0); # inherit bignum 0 { # high to low my $x = 0; my $y = 0; my $dx = 1; my $dy = 0; # return if $n >=9; my $lowdigit = _divrem_mutate($n, 4); my @digits = digit_split_lowtohigh($n,3); foreach my $digit (reverse(@digits), $lowdigit) { ### at: "$x,$y digit=$digit" ($x,$y) = ($x-$y,$x+$y); # rotate +45 $x += 1; ### rotate to: "$x,$y" if ($digit == 0) { $x -= $dx; $y -= $dy; } elsif ($digit == 1) { $x += $dx; $y += $dy; ($dx,$dy) = (-$dy,$dx); # rotate +90 } elsif ($digit == 2) { $x += $dx - 2*$dy; # across then at +90 $y += $dy + 2*$dx; } elsif ($digit == 3) { $x += 3*$dx - 2*$dy; # across then at +90, for $lowdigit $y += 3*$dy + 2*$dx; } } ### return: "$x,$y" return ($x,$y); } { # low to high my $x = 0; my $y = 0; my $dx = 1 + $zero; my $dy = $zero; return if $n >=16; my $lowdigit = _divrem_mutate($n, 3); if ($lowdigit == 0) { } elsif ($lowdigit == 1) { $x = 2; } elsif ($lowdigit == 2) { $x = 2; $y = 2; } elsif ($lowdigit == 3) { $x = 4; $y = 2; } foreach my $digit (digit_split_lowtohigh($n,3)) { # $dx *= 2; # $dy *= 2; ($dx,$dy) = ($dx+$dy,$dy-$dx); # rotate 45 # ($dx,$dy) = (-$dy,$dx); # rotate +90 if ($digit == 0) { } elsif ($digit == 1) { ($x,$y) = (-$y,$x); # rotate +90 $x += 3/2*$dx; $y += 3/2*$dy; ($dx,$dy) = (-$dy,$dx); # rotate +90 $x += 1/2*$dx; $y += 1/2*$dy; } elsif ($digit == 2) { $x -= 4/2*$dy; $y += 4/2*$dx; } } return ($x,$y); } } sub xy_to_n { my ($self, $x, $y) = @_; return undef; } # minimum -- no, not quite right # # *----------* # \ # \ * # * \ # \ # *----------* # # width = side/2 # minimum = side*sqrt(3)/2 - width # = side*(sqrt(3)/2 - 1) # # minimum 4/9 * 2.9^level roughly # h = 4/9 * 2.9^level # 2.9^level = h*9/4 # level = log(h*9/4)/log(2.9) # 3^level = 3^(log(h*9/4)/log(2.9)) # = h*9/4, but big bigger for log # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### Z2DragonCurve rect_to_n_range(): "$x1,$y1 $x2,$y2" my $xmax = int(max(abs($x1),abs($x2))); my $ymax = int(max(abs($y1),abs($y2))); return (0, ($xmax*$xmax + $ymax*$ymax + 1)); } 1; __END__ Math-PlanePath-129/devel/lib/Math/PlanePath/NxN.pm0000644000175000017500000000614413734026651017375 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::NxN; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; *_sqrtint = \&Math::PlanePath::_sqrtint; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; sub n_to_xy { my ($self, $n) = @_; ### NxN n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { # fractions on straight line ? my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } # d = [ 0, 1, 2, 3, 4 ] # n = [ 0, 1, 3, 6, 10 ] # N = (d+1)*d/2 # d = (-1 + _sqrtint(8*$n+1))/2 my $d = int((_sqrtint(8*$n+1) - 1) / 2); $n -= $d*($d+1)/2; ### $d ### $n my $x = $d-$n; # downwards my $y = $n; # upwards my $diff = $x-$y; ### diagonals xy: "$x, $y diff=$diff" if ($diff <= 0) { ### non-pos diff, use x ... return (2*$x + ($diff % 2), 2*$x + int((1-$diff)/2)); } else { ### pos diff, use y ... return (2*($y+1) - 1 + int($diff/2), 2*$y + (($diff+1) % 2)); } } sub xy_to_n { my ($self, $x, $y) = @_; ### NxN xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } if ($x <= $y) { my $h = int($x/2); ($x,$y) = ($h, $h + ($x%2) + 2*($y - 2*$h - ($x%2))); } else { my $h = int($y/2); ($x,$y) = (1 + $h + ($y%2) + 2*($x-1 - 2*$h - ($y%2)), $h); } return (($x+$y)**2 + $x+3*$y)/2; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### NxN rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 0) { ### all outside first quadrant ... return (1, 0); } if ($x1 < 0) { $x1 *= 0; } if ($y1 < 0) { $y1 *= 0; } return (0, $x2 * $y2); } 1; __END__ Math-PlanePath-129/devel/lib/Math/PlanePath/z2-dragon.pl0000644000175000017500000000566712300052537020473 0ustar gggg# Copyright 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use strict; use Math::PlanePath::Z2DragonCurve; { require Image::Base::GD; my $width = 1010; my $height = 710; my $image = Image::Base::GD->new (-width => $width, -height => $height); $image->rectangle (0,0, $width-1,$height-1, 'black'); # -7/3 to +7/3 my @lines = ([int($width * .29), int($height*.5), int($width * .71), int($height*.5)]); foreach my $level (1 .. 10) { my @new_lines; foreach my $line (@lines) { my ($x1,$y1, $x2,$y2) = @$line; my $dx = ($x2 - $x1) / 4; my $dy = ($y2 - $y1) / 4; push @new_lines, [ $x1 - $dx + $dy, $y1 - $dy - $dx, $x1 + $dx - $dy, $y1 + $dy + $dx ]; push @new_lines, [ $x1 + $dx - $dy, $y1 + $dy + $dx, $x2 - $dx + $dy, $y2 - $dy - $dx ]; push @new_lines, [ $x2 - $dx + $dy, $y2 - $dy - $dx, $x2 + $dx - $dy, $y2 + $dy + $dx ]; } # push @lines, @new_lines; @lines = @new_lines; } foreach my $line (@lines) { $image->line (@$line, 'white'); } # $image->ellipse ($x_offset-2,$y_offset-2, # $x_offset+2,$y_offset+2, 'red'); $image->save('/tmp/x.png'); system('xzgv /tmp/x.png'); exit 0; } { require Image::Base::GD; my $width = 1210; my $height = 810; my $x_offset = int($width * .3); my $y_offset = int($height * .2); my $image = Image::Base::GD->new (-width => $width, -height => $height); $image->rectangle (0,0, $width-1,$height-1, 'black'); my $foreground = 'white'; my $path = Math::PlanePath::Z2DragonCurve->new; my $scale = 10; foreach my $n (0 .. 100000) { next if $n % 4 == 3; my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); $y1 = -$y1; $y2 = -$y2; $x1 *= $scale; $y1 *= $scale; $x2 *= $scale; $y2 *= $scale; $x1 += $x_offset; $x2 += $x_offset; $y1 += $y_offset; $y2 += $y_offset; $image->line ($x1,$y1, $x2,$y2, 'white'); } $image->ellipse ($x_offset-2,$y_offset-2, $x_offset+2,$y_offset+2, 'red'); $image->save('/tmp/x.png'); system('xzgv /tmp/x.png'); exit 0; } Math-PlanePath-129/devel/lib/Math/PlanePath/ZeckendorfTerms.pm0000644000175000017500000000764013734026650022000 0ustar gggg# Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # A134561 triangle T(n,k) = k-th number whose Zeckendorf has exactly n terms # 4180 5777 6387 6620 6709 6743 6756 6761 6763 6764 8361 # 1596 2206 2439 2528 2562 2575 2580 2582 2583 3193 3426 # 609 842 931 965 978 983 985 986 1219 1308 1342 # 232 321 355 368 373 375 376 465 499 512 517 # 88 122 135 140 142 143 177 190 195 197 198 # 33 46 51 53 54 67 72 74 75 80 82 # 12 17 19 20 25 27 28 30 31 32 38 # 4 6 7 9 10 11 14 15 16 18 22 # 1 2 3 5 8 13 21 34 55 89 144 # Y=1 Fibonacci # Y=2 A095096 # X=1 first with Y many bits is Zeck 101010101 # A027941 Fib(2n+1)-1 # X=2 second with Y many bits is Zeck 1001010101 high 1, low 10101 # A005592 F(2n+1)+F(2n-1)-1 # X=3 third with Y many bits is Zeck 1010010101 # A005592 F(2n+1)+F(2n-1)-1 # X=4 fourth with Y many bits is Zeck 1010100101 package Math::PlanePath::ZeckendorfTerms; use 5.004; use strict; use List::Util 'max'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # uncomment this to run the ### lines # use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant y_minimum => 1; use constant x_minimum => 1; use Math::NumSeq::FibbinaryBitCount; my $fbc = Math::NumSeq::FibbinaryBitCount->new; my $next_n = 1; my @n_to_x; my @n_to_y; my @yx_to_n; sub _extend { my ($self) = @_; my $n = $next_n++; my $y = $fbc->ith($n); my $row = ($yx_to_n[$y] ||= []); my $x = scalar(@$row) || 1; $row->[$x] = $n; $n_to_x[$n] = $x; $n_to_y[$n] = $y; } sub n_to_xy { my ($self, $n) = @_; ### ZeckendorfTerms n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n) || $n == 0) { return ($n,$n); } { # fractions on straight line ? my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } my $y = $fbc->ith($n); while ($next_n <= $n) { _extend($self); } ### $self return ($n_to_x[$n], $n_to_y[$n]); } sub xy_to_n { my ($self, $x, $y) = @_; ### ZeckendorfTerms xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 1 || $y < 1) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } for (;;) { if (defined (my $n = $yx_to_n[$y][$x])) { return $n; } _extend($self); } } sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### ZeckendorfTerms rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; return (1, 1000); # increasing horiziontal and vertical return (1, $self->xy_to_n($x2,$y2)); } 1; __END__ =cut # math-image --path=ZeckendorfTerms --output=numbers --all --size=60x14 =pod Math-PlanePath-129/devel/lib/Math/PlanePath/PeanoRounded.pm0000644000175000017500000003410113734026651021247 0ustar gggg# works, worth having separately ? # alternating diagonals when even radix ? # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=PeanoRounded --all --output=numbers # math-image --path=PeanoRounded,radix=5 --lines # package Math::PlanePath::PeanoRounded; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant parameter_info_array => [ { name => 'radix', share_key => 'radix_3', display => 'Radix', type => 'integer', minimum => 2, default => 3, width => 3, } ]; sub new { my $self = shift->SUPER::new(@_); if (! $self->{'radix'} || $self->{'radix'} < 2) { $self->{'radix'} = 3; } return $self; } sub n_to_xy { my ($self, $n) = @_; ### PeanoRounded n_to_xy(): $n if ($n < 0) { # negative return; } if (is_infinite($n)) { return ($n,$n); } { # ENHANCE-ME: for odd radix the ends join and the direction can be had # without a full N+1 calculation my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } # low to high my $x = _divrem_mutate($n,2); my $y = $x; my $power = ($n * 0) + 2; # inherit BigInt 2 my $radix = $self->{'radix'}; my @digits = digit_split_lowtohigh($n,$radix); while (@digits) { ### $n ### $power { my $digit = shift @digits; # low to high if ($digit & 1) { $y = $power-1 - $y; # 99..99 - Y } $x += $power * $digit; } last unless @digits; { my $digit = shift @digits; # low to high $y += $power * $digit; $power *= $radix; if ($digit & 1) { $x = $power-1 - $x; } } } return ($x, $y); # # high to low # my $radix = $self->{'radix'}; # my $radix_minus_1 = $radix - 1; # my (@n); # while ($n) { # push @n, $n % $radix; $n = int($n/$radix); # push @n, $n % $radix; $n = int($n/$radix); # } # my $x = 0; # my $y = 0; # my $xk = 0; # my $yk = 0; # while (@n) { # { # my $digit = pop @n; # $xk ^= $digit; # $y *= $radix; # $y += ($yk & 1 ? $radix_minus_1-$digit : $digit); # } # { # my $digit = pop @n; # $yk ^= $digit; # $x *= $radix; # $x += ($xk & 1 ? $radix_minus_1-$digit : $digit); # } # } # ### is: "$x,$y" # return ($x, $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### PeanoRounded xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my $xlow = _divrem_mutate ($x, 2); my $ylow = _divrem_mutate ($y, 2); my $radix = $self->{'radix'}; my $radix_minus_1 = $radix - 1; my @x = digit_split_lowtohigh($x,$radix); my @y = digit_split_lowtohigh($y,$radix); push @x, (0) x max(0, scalar(@y) - scalar(@x)); push @y, (0) x max(0, scalar(@x) - scalar(@y)); my $xk = 0; my $yk = 0; my $n = 0; while (@x) { { my $digit = pop @y || 0; if ($yk & 1) { $digit = $radix_minus_1 - $digit; } $n = ($n * $radix) + $digit; $xk ^= $digit; } { my $digit = pop @x || 0; if ($xk & 1) { $digit = $radix_minus_1 - $digit; } $n = ($n * $radix) + $digit; $yk ^= $digit; } } if ($yk & 1) { $ylow = 1-$ylow; } if ($xk & 1) { $xlow = 1-$xlow; } $n *= 2; if ($xlow == 0 && $ylow == 0) { return $n; } elsif ($xlow == 1 && $ylow == 1) { return $n + 1; } return undef; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### rect_to_n_range(): "$x1,$y1 to $x2,$y2" if ($x2 < 0 || $y2 < 0) { return (1, 0); } my $radix = $self->{'radix'}; my ($power, $level) = round_down_pow (max($x2,$y2)*$radix/2, $radix); if (is_infinite($level)) { return (0, $level); } return (0, 2*$power*$power - 1); # Would need to backtrack if the rectangle misses the 2/4 cells filled ... # my $n_power = 2 * $power * $power * $radix; # my $max_x = 0; # my $max_y = 0; # my $max_n = 0; # my $max_xk = 0; # my $max_yk = 0; # # my $min_x = 0; # my $min_y = 0; # my $min_n = 0; # my $min_xk = 0; # my $min_yk = 0; # # # l<=cc2 or h-1c2 or h<=c1 # # so does overlap if # # l<=c2 and h>c1 # # # my $radix_minus_1 = $radix - 1; # my $overlap = sub { # my ($c,$ck,$digit, $c1,$c2) = @_; # if ($ck & 1) { # $digit = $radix_minus_1 - $digit; # } # ### overlap consider: "inv".($ck&1)."digit=$digit ".($c+$digit*$power)."<=c<".($c+($digit+1)*$power)." cf $c1 to $c2 incl" # return ($c + $digit*$power <= $c2 # && $c + ($digit+1)*$power > $c1); # }; # # while ($level-- >= 0) { # ### $power # ### $n_power # ### $max_n # ### $min_n # { # my $digit; # for ($digit = $radix_minus_1; $digit > 0; $digit--) { # last if &$overlap ($max_y,$max_yk,$digit, $y1,$y2); # } # $max_n += $n_power * $digit; # $max_xk ^= $digit; # if ($max_yk&1) { $digit = $radix_minus_1 - $digit; } # $max_y += $power * $digit; # ### max y digit (complemented): $digit # ### $max_y # ### $max_n # } # { # my $digit; # for ($digit = 0; $digit < $radix_minus_1; $digit++) { # last if &$overlap ($min_y,$min_yk,$digit, $y1,$y2); # } # $min_n += $n_power * $digit; # $min_xk ^= $digit; # if ($min_yk&1) { $digit = $radix_minus_1 - $digit; } # $min_y += $power * $digit; # ### min y digit (complemented): $digit # ### $min_y # ### $min_n # } # # $n_power = int($n_power/$radix); # { # my $digit; # for ($digit = $radix_minus_1; $digit > 0; $digit--) { # last if &$overlap ($max_x,$max_xk,$digit, $x1,$x2); # } # $max_n += $n_power * $digit; # $max_yk ^= $digit; # if ($max_xk&1) { $digit = $radix_minus_1 - $digit; } # $max_x += $power * $digit; # ### max x digit (complemented): $digit # ### $max_x # ### $max_n # } # { # my $digit; # for ($digit = 0; $digit < $radix_minus_1; $digit++) { # last if &$overlap ($min_x,$min_xk,$digit, $x1,$x2); # } # $min_n += $n_power * $digit; # $min_yk ^= $digit; # if ($min_xk&1) { $digit = $radix_minus_1 - $digit; } # $min_x += $power * $digit; # ### min x digit (complemented): $digit # ### $min_x # ### $min_n # } # # $power = int($power/$radix); # $n_power = int($n_power/$radix); # } # # ### is: "$min_n at $min_x,$min_y to $max_n at $max_x,$max_y" # return ($min_n, $max_n); } 1; __END__ =for stopwords Giuseppe Peano Peano's eg Sur une courbe qui remplit toute aire Mathematische Annalen Ryde OEIS ZOrderCurve ie PeanoCurve Math-PlanePath versa Online Radix radix HilbertCurve =head1 NAME Math::PlanePath::PeanoRounded -- 3x3 self-similar quadrant traversal, with rounded corners =head1 SYNOPSIS use Math::PlanePath::PeanoRounded; my $path = Math::PlanePath::PeanoRounded->new; my ($x, $y) = $path->n_to_xy (123); # or another radix digits ... my $path5 = Math::PlanePath::PeanoRounded->new (radix => 5); =head1 DESCRIPTION This is a version of the PeanoCurve with rounded-off corners, 11 | 76-75 72-71 68-67 | / \ / \ / \ 10 | 77 74-73 70-69 66 | | | 9 | 78 81-82 61-62 65 | \ / \ / \ / 8 | 79-80 83 60 63-64 | | | 7 | 88-87 84 59 56-55 | / \ / \ / \ 6 | ...-89 86-85 58-57 54 | | 5 | 13-14 17-18 21-22 49-50 53 | / \ / \ / \ / \ / 4 | 12 15-16 19-20 23 48 51-52 | | | | 3 | 11 8--7 28-27 24 47 44-43 | \ / \ / \ / \ / \ 2 | 10--9 6 29 26-25 46-45 42 | | | | 1 | 1--2 5 30 33-34 37-38 41 | / \ / \ / \ / \ / Y=0 | 0 3--4 31-32 35-36 39-40 +------------------------------------------------------ X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 =head2 Radix The C parameter can do the calculation in a base other than 3, using the same kind of direction reversals. For example radix 5 gives 5x5 groups, =cut # math-image --path=PeanoRounded,radix=5 --all --output=numbers_dash =pod radix => 5 9 | 41-42 45-46 49-... | / \ / \ / 8 | 40 43-44 47-48 | | radix=5 7 | 39 36-35 32-31 | \ / \ / \ 6 | 38-37 34-33 30 | | 5 | 21-22 25-26 29 | / \ / \ / 4 | 20 23-24 27-28 | | 3 | 19 16-15 12-11 | \ / \ / \ 2 | 18-17 14-13 10 | | 1 | 1--2 5--6 9 | / \ / \ / Y=0 | 0 3--4 7--8 | +--------------------------------- X=0 1 2 3 4 5 6 7 8 9 If the radix is even then the ends of each group don't join up. For example in radix 4 N=31 isn't next to N=32. =cut # math-image --path=PeanoRounded,radix=4 --all --output=numbers_dash =pod 7 | 30-29 26-25 32 | / \ / \ \ 6 | 31 28-27 24 33--... | | 5 | 17-18 21-22 | | / \ / \ | 4 | 16 19-20 23 | | 3 | | 14-13 10--9 | | / \ / \ 2 | 15 12-11 8 | | 1 | 1--2 5--6 | | / \ / \ | Y=0 | 0 3--4 7 +----------------------------------------- X=0 1 2 4 5 6 7 8 9 10 =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::PeanoRounded-Enew ()> =item C<$path = Math::PlanePath::PeanoRounded-Enew (radix =E $r)> Create and return a new path object. The optional C parameter gives the base for digit splitting. The default is ternary, C 3>. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =back =head1 SEE ALSO L, L, L Giuseppe Peano, "Sur Une Courbe, Qui Remplit Toute Une Aire Plane", Mathematische Annalen, volume 36, number 1, 1890, p157-160 =over DOI 10.1007/BF01199438 http://www.springerlink.com/content/w232301n53960133/ =back =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/devel/lib/Math/PlanePath/ParabolicRuns.pm0000644000175000017500000000502413734026651021432 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::ParabolicRuns; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow'; # uncomment this to run the ### lines #use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; sub n_to_xy { my ($self, $n) = @_; ### ParabolicRuns n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n)) { return ($n,$n); } $n -= 1; my @x; for (my $k = 0; ; $k++) { $x[$k] = 0; for (my $y = $k; $y >= 0; $y--) { my $len = $k-$y+1; if ($n < $len) { return ($x[$y] + $n, $y); } $x[$y] += $len; $n -= $len; } } } sub xy_to_n { my ($self, $x, $y) = @_; ### ParabolicRuns xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my $n = 1; my @sx; for (my $k = 0; ; $k++) { $sx[$k] = 0; for (my $sy = $k; $sy >= 0; $sy--) { my $len = $k-$sy+1; if ($y == $sy) { if ($x < $len) { return ($n + $x); } $x -= $len; } $n += $len; } } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### ParabolicRuns rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; return (1, 2*($x2+1)*($y2+1)**2); } 1; __END__ Math-PlanePath-129/devel/lib/Math/PlanePath/SquaRecurve.pm0000644000175000017500000004331713734026651021142 0ustar gggg# Copyright 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # https://books.google.com.au/books?id=-4W_5ZISxpsC&pg=PA49 # # cf counting all 5x5 traversals # 1,1,7,138,5960 # not in OEIS: 138,5960 package Math::PlanePath::SquaRecurve; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; *_sqrtint = \&Math::PlanePath::_sqrtint; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh','digit_join_lowtohigh'; use Math::PlanePath::PeanoCurve; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant parameter_info_array => [ { name => 'k', display => 'K', type => 'integer', minimum => 3, default => 5, width => 3, page_increment => 10, step_increment => 2, } ]; # ../../../squarecurve.pl # ../../../run.pl my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); sub new { my $self = shift->SUPER::new(@_); $self->{'k'} ||= 5; my $k = $self->{'k'} | 1; my $turns = $k >> 1; my $square = $k*$k; my @digit_to_x; my @digit_to_y; $self->{'digit_to_x'} = \@digit_to_x; $self->{'digit_to_y'} = \@digit_to_y; my @digit_to_dir; { my $x = 0; my $y = 0; my $dx = 0; my $dy = 1; my $dir = 1; my $n = 0; my $run = sub { my ($r) = @_; foreach my $i (1 .. $r) { $digit_to_x[$n] = $x; $digit_to_y[$n] = $y; $digit_to_dir[$n] = $dir & 3; $n++; $x += $dx; $y += $dy; } }; my $spiral = sub { while (@_) { my $r = shift; $run->($r || 1); ($dx,$dy) = ($dy,-$dx); # rotate -90 $dir--; last if $r == 0; } $dx = -$dx; $dy = -$dy; $dir += 2; while (@_) { my $r = shift; $run->($r); ($dx,$dy) = (-$dy,$dx); # rotate +90 $dir++; } }; # 7,9, 3,4 my $first = (($turns-1) & 2); $spiral->(reverse(0 .. $turns), 1 .. $turns-1, ($first ? ($turns-1) : ($turns, $turns-1))); ($dx,$dy) = (-$dx,-$dy); # rotate 180 $dir += 2; if ($first) { $spiral->(0,1); } $spiral->(($first ? ($turns) : ()), reverse(0 .. $turns), 1 .. $turns-1, $turns-2); ($dx,$dy) = (-$dx,-$dy); # rotate 180 $dir += 2; $spiral->(reverse(0 .. $turns), 1 .. $turns-2, ($first ? ($turns-1) : ($turns-2))); if ($first) { } else { ($dx,$dy) = (-$dx,-$dy); # rotate 180 $dir += 2; $spiral->(0,1); } $spiral->(($first ? $turns-2 : $turns-1), reverse(0 .. $turns-1), 1 .. $turns); } my @next_state; my @digit_to_sx; my @digit_to_sy; $self->{'next_state'} = \@next_state; $self->{'digit_to_sx'} = \@digit_to_sx; $self->{'digit_to_sy'} = \@digit_to_sy; my %xy_to_n; my $more = 1; while ($more) { $more = 0; my %xy_to_n_list; $more = 0; foreach my $n (0 .. $k*$k-1) { next if defined $digit_to_sx[$n]; my $dir = $digit_to_dir[$n]; my $x = $digit_to_x[$n]; my $y = $digit_to_y[$n]; my $dx = $dir4_to_dx[$dir]; my $dy = $dir4_to_dy[$dir]; my ($lx,$ly) = (-$dy,$dx); # rotate +90 my $count = 0; my ($sx,$sy,$snext); foreach my $right (0, 4) { my $next_state = $dir ^ $right; my $cx = (2*$x + $dx + $lx - 1)/2; my $cy = (2*$y + $dy + $ly - 1)/2; ### consider: "$n right=$right is $cx,$cy" if ($cx >= 0 && $cy >= 0 && $cx < $k && $cy < $k) { push @{$xy_to_n_list{"$cx,$cy"}}, $n, $next_state; $count++; ($sx,$sy) = ($cx,$cy); $snext = $next_state; } ($lx,$ly) = (-$lx,-$ly); } if ($count==1) { die if defined $digit_to_sx[$n]; ### store one side: "$n at $sx,$sy next state $snext" $digit_to_sx[$n] = $sx; $digit_to_sy[$n] = $sy; $next_state[$n] = $snext; $more = 1; my $sxy = "$sx,$sy"; if (defined $xy_to_n{$sxy} && $xy_to_n{$sxy} != $n) { die "already $xy_to_n{$sxy}"; } $xy_to_n{$sxy} = $n; } } while (my ($cxy,$n_list) = each %xy_to_n_list) { ### cxy: "$cxy ".join(',',@$n_list) if (@$n_list == 2) { my $n = $n_list->[0]; my ($sx,$sy) = split /,/, $cxy; my $sxy = "$sx,$sy"; if (defined $xy_to_n{$sxy} && $xy_to_n{$sxy} != $n) { ### already $xy_to_n{$sxy} next; } $xy_to_n{$sxy} = $n; $digit_to_sx[$n] = $sx; $digit_to_sy[$n] = $sy; $next_state[$n] = $n_list->[1]; $more = 1; ### store one choice: "$n at $sx,$sy next state $next_state[$n]" } } } ### sx : join(',',@digit_to_sx) ### sy : join(',',@digit_to_sy) ### next state: join(',',@next_state) return $self; } sub n_to_xy { my ($self, $n) = @_; ### SquaRecurve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; my $k = $self->{'k'} | 1; my $square = $k*$k; if ($n >= $square**3) { return; } my @digits = digit_split_lowtohigh($int,$square); while (@digits < 1) { push @digits, 0; } my $digit_to_sx = $self->{'digit_to_sx'}; my $digit_to_sy = $self->{'digit_to_sy'}; my $next_state = $self->{'next_state'}; my @x; my @y; my $dir = 1; my $right = 4; my $fracdir = 1; foreach my $i (reverse 0 .. $#digits) { # high to low my $digit = $digits[$i]; ### at: "dir=$dir right=$right digit=$digit" if ($digit != $square-1) { # lowest non-24 digit $fracdir = $dir; } if ($right) { $digit = $square-1-$digit; ### reverse: "digit=$digit" } my $x = $digit_to_sx->[$digit]; my $y = $digit_to_sy->[$digit]; ### sxy: "$x,$y" # if ($right) { # $x = $k-1-$x; # $y = $k-1-$y; # } if (($dir ^ ($right>>1)) & 2) { $x = $k-1-$x; $y = $k-1-$y; } if ($dir & 1) { ($x,$y) = ($k-1-$y, $x); } ### rotate to: "$x,$y" $x[$i] = $x; $y[$i] = $y; my $next = $next_state->[$digit]; # if ($right) { # } else { # $dir += $next & 3; # } $dir += $next & 3; $right ^= $next & 4; } ### final: "dir=$dir right=$right" ### @x ### @y ### frac: $n my $zero = $int * 0; return ($n * 0 # ($digit_to_sx->[$dirstate+1] - $digit_to_sx->[$dirstate]) + digit_join_lowtohigh(\@x, $k, $zero), $n * 0 # ($digit_to_sy->[$dirstate+1] - $digit_to_sy->[$dirstate]) + digit_join_lowtohigh(\@y, $k, $zero)); { my $digit_to_x = $self->{'digit_to_x'}; if ($n > $#$digit_to_x) { return; } return ($self->{'digit_to_sx'}->[$n], $self->{'digit_to_sy'}->[$n]); } my $turns = $k >> 1; my $t1 = $turns + 1; my $rot = -$turns; my $x = 0; my $y = 0; my $qx = 0; my $qy = 0; my $midpoint = $turns*$t1/2 + 1; if (($n -= $midpoint) >= 0) { ### after middle ... return; } else { # $qx += $dir4_to_dx[(0*$turns+1)&3]; # $qy += $dir4_to_dy[(0*$turns+1)&3]; # $qx -= $dir4_to_dy[($turns+2)&3]; # $qy += $dir4_to_dx[($turns+2)&3]; # $qy += 1; # $x -= 1; if ($n += 1) { ### before middle ... $n = -$n; $rot += 2; # $y -= 1; # $x -= 1; } else { ### centre segment ... $rot += 1; # $qy -= $dir4_to_dx[(-$turns)&3]; } } ### key n: $n my $q = ($turns*$turns-1)/4; ### $q # d: [ 0, 1, 2 ] # n: [ 0, 3, 10 ] # d = -1/4 + sqrt(1/2 * $n + 1/16) # = (-1 + sqrt(8*$n + 1)) / 4 # N = (2*$d + 1)*$d # rel = (2*$d + 1)*$d + 2*$d+1 # = (2*$d + 3)*$d + 1 # my $d = int( (_sqrtint(8*$n+1) - 1)/4 ); $n -= (2*$d+3)*$d + 1; ### $d ### key signed rem: $n if ($n < 0) { ### key horizontal ... $x += $n+$d + 1; $y += -$d; if ($d % 2) { ### key top ... $rot += 2; $y -= 1; } else { ### key bottom ... } } else { ### key vertical ... $x += -$d - 1; $y += $d - $n; $rot += 2; if ($d % 2) { ### key right ... $rot += 2; $y += 1; } else { ### key left ... } } ### kxy raw: "$x, $y" if ($rot & 2) { $x = -$x; $y = -$y; } if ($rot & 1) { ($x,$y) = ($y,-$x); } ### kxy rotated: "$x,$y" # if ($k%8==1 && !$before) { # $y += 1; # } # if ($k%8==3 && !$before) { # $x += 1; # } # if ($k%8==5 && $before) { # $y += 1; # } # if ($k%8==7 && $before) { # $x += 1; # } $x += $qx; $y += $qy; return ($x,$y); # my $q = ($k*$k-1)/4; ### $k ### $q ### $turns # if ($n > $q/2) { return (0,0); } my $before; # $qx += ($k >> 2); # $qy += ($k >> 2); if ($n > $q/2) { return; } if ($n >= $q+$turns) { $n -= $q+$turns; $qx += 1; $qy += ($k >> 1) + 1; } if ($n >= $q+$turns-2) { $n -= $q+$turns-2; $qx += ($k >> 1) + 10; $qy += 1; $rot++; } # $x -= $dir4_to_dx[$rot&3]; # $y += $dir4_to_dy[$rot&3]; } sub xy_to_n { my ($self, $x, $y) = @_; ### SquaRecurve xy_to_n(): "$x, $y" return undef; $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; return (0, 25**3); $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### rect_to_n_range(): "$x1,$y1 to $x2,$y2" if ($x2 < 0 || $y2 < 0) { return (1, 0); } my $radix = $self->{'k'}; my ($power, $level) = round_down_pow (max($x2,$y2), $radix); if (is_infinite($level)) { return (0, $level); } return (0, $radix*$radix*$power*$power - 1); } #------------------------------------------------------------------------------ 1; __END__ =for stopwords Ryde OEIS DekkingCurve =head1 NAME Math::PlanePath::SquaRecurve -- spiralling self-similar blocks =head1 SYNOPSIS use Math::PlanePath::SquaRecurve; my $path = Math::PlanePath::SquaRecurve->new (k => 5); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path is the SquaRecurve of =over Douglas M. McKenna, 1978, as described in "SquaRecurves, E-Tours, Eddies, and Frenzies: Basic Families of Peano Curves on the Square Grid", in "The Lighter Side of Mathematics: Proceedings of the Eugene Strens Memorial Conference on Recreational Mathematics and its History", Mathematical Association of America, 1994, pages 49-73, ISBN 0-88385-516-X. =back Peano curve with segments going across unit squares. Points N are opposite corners of these squares, so all are even points (X+Y even). =cut # generated by: # math-image --path=SquaRecurve --all --output=numbers --size=20x15 =pod 9 | 61 63 65 79 81 8 | 60 58,62 64,68 66,78 76,80 7 | 55,59 57,69 67,71 73,77 75,87 6 | 54 52,56 38,70 36,72 34,74 5 | 49,53 39,51 37,41 31,35 33,129 4 | 48 46,50 40,44 30,42 28,32 3 | 7,47 9,45 11,43 25,29 27,135 2 | 6 4,8 10,14 12,24 22,26 1 | 1,5 3,15 13,17 19,23 21,141 Y=0 | 0 2 16 18 20 +---------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 Segments between the initial points can be illustrated, | +---- 7,47 ---+---- 9,45 -- | ^ | \ | ^ | \ | / | \ | / | v | / | v | / | ... 6 -----+---- 4,8 ----+-- | ^ | / | ^ | | \ | / | \ | | \ | v | \ | +-----1,5 ----+---- 3,15 | ^ | \ | ^ | | / | \ | / | | / | v | / | N=0------+------2------+-- Segment N=0 to N=1 goes from the origin X=0,Y=0 up to X=1,Y=1, then N=2 is down again to X=2,Y=0, and so on. This can be compared to the PeanoCurve which goes between the middle of each square, so the midpoints of these segments. Peano's conception of a space-filling curve is ternary digits of a fractional f which fills a unit square going from f=0 at X=0,Y=0 up to f=1 at X=1,Y=1. The integer form here does this with digits above the ternary point. =head2 Even Radix , -----+--- 14, ---+----- 12, - | ^ | / | ^ | / | | \ | / | \ | / | | \ | v | \ | v | +---- 9,15 ---+--- 11,13 ---+-- | ^ | / | ^ | / | | \ | / | \ | / | | \ | v | \ | v | +-----1,7 ----+---- 3,5 ----+-- | ^ | \ | ^ | \ | radix => 4 | / | \ | / | \ | | / | v | / | v | 8 -----+---- 6,10 ---+---- 4, - | ^ | \ | ^ | \ | | / | \ | / | \ | | / | v | / | v | N=0------+------2------+------+--- =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::SquaRecurve-Enew ()> =item C<$path = Math::PlanePath::SquaRecurve-Enew (radix =E $r)> Create and return a new path object. The optional C parameter gives the base for digit splitting. The default is ternary, C 3>. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =back =head1 FORMULAS =head2 N to Turn The curve turns left or right 90 degrees at each point N E= 1. The turn is 90 degrees turn(N) = 90 degrees * (-1)^(N + number of low ternary 0s of N) = -1,1,1,1,-1,-1,-1,1,-1,1,-1,-1,-1,1,1,1,-1,1 =cut # GP-DEFINE turn(n) = (-1)^(n + valuation(n,3)); # GP-Test vector(18,n, turn(n)) == \ # GP-Test [-1,1, 1, 1,-1, -1, -1,1,-1,1,-1, -1, -1,1,1,1,-1,1] # not in OEIS: -1,1,1,1,-1,-1,-1,1,-1,1,-1,-1,-1,1,1,1,-1,1 # not in OEIS: 1,-1,-1,-1,1,1,1,-1,1,-1,1,1,1,-1,-1,-1,1,-1 \\ negated # not in OEIS: 0,1,1,1,0,0,0,1,0,1,0,0,0,1,1,1,0,1,0,1,1,1,0,0,0,1,1,1,0,0 \\ ones # not in OEIS: 1,0,0,0,1,1,1,0,1,0,1,1,1,0,0,0,1,0 \\ zeros # GP-Test vector(900,n, turn(3*n)) == \ # GP-Test vector(900,n, -turn(n)) # GP-Test vector(900,n, turn(3*n+1)) == \ # GP-Test vector(900,n, -(-1)^n) # GP-Test vector(900,n, turn(3*n+2)) == \ # GP-Test vector(900,n, (-1)^n) # vector(25,n, (-1)^valuation(n,3)) # not in OEIS: 1,1,-1,1,1,-1,1,1,1,1,1,-1,1,1,-1,1,1,1,1,1,-1,1,1,-1,1,1,-1,1 # vector(100,n, valuation(n,3)%2) # A182581 num ternary low 0s mod 2 =pod The power of -1 means left or right flip for each low ternary 0 of N, and flip again if N is odd. Odd N is an odd number of ternary 1 digits. This formula follows from the turns in a new low base-9 digit. The start and end of the base figure are in the same directions so the turns at 9*N are unchanged. Then 9*N+r goes as r in the base figure, but flipped LE-ER when N odd since blocks are mirrored alternately. turn(9N) = turn(N) turn(9N+r) = turn(r)*(-1)^N for 1 <= r <= 8 =cut # GP-Test vector(900,n, turn(9*n)) == \ # GP-Test vector(900,n, turn(n)) # GP-Test matrix(90,8,n,r, turn(9*n+r)) == \ # GP-Test matrix(90,8,n,r, turn(r)*(-1)^n) =pod Just in terms of base 3, a single new low ternary digit is a transpose of what's above, and the base figure turns r=1,2 and LE-ER when N above is odd again. The same for any odd radix. =head1 SEE ALSO L, L =over DOI 10.1007/BF01199438 http://www.springerlink.com/content/w232301n53960133/ =back =head1 HOME PAGE L =head1 LICENSE Copyright 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/devel/lib/Math/square-radical.pl0000644000175000017500000000166212171603336017705 0ustar gggg# Copyright 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::SquareRadical; # uncomment this to run the ### lines use Smart::Comments; { my $s = Math::SquareRadical->new(1); print "$s\n"; } { my $s = Math::SquareRadical->new(1,2,3); ### $s print "$s\n"; } exit 0; Math-PlanePath-129/devel/lib/MyFLAT.pm0000644000175000017500000024731714001205057015155 0ustar gggg# Copyright 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # # This file 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, or (at your option) any later # version. # # This file 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 file. See the file COPYING. If not, see # . # Some miscellaneous functions related FLAT.pm automatons. package MyFLAT; use 5.010; use strict; use warnings; use Carp 'croak'; use List::Util 'max','sum'; use Scalar::Util 'looks_like_number'; use Regexp::Common 'balanced'; # uncomment this to run the ### lines # use Smart::Comments; use base 'Exporter'; our @EXPORT_OK = ( # generic, methods # 'get_non_accepting','num_accepting','num_non_accepting', # 'eventually_accepting', 'get_eventually_accepting', # 'get_eventually_accepting_info', # 'prefix', # 'prefix_accepting','get_prefix_accepting','get_prefix_accepting_info', # 'separate_sinks','add_sink', # 'rename_accepting_last', # 'is_accepting_sink', # 'blocks','binary_to_base4', # 'transmute', # generic functions 'fraction_digits', # fairly specific 'zero_digits_flat','one_bits_flat', 'bits_N_even_flat','bits_N_odd_flat','bits_of_length_flat', 'aref_to_FLAT_DFA', # temporary # 'as_nfa','concat','minimize','reverse, # methods # misc 'FLAT_count_contains', 'FLAT_rename', 'FLAT_to_perl_re', # personal preferences 'view', 'FLAT_check_is_equal','FLAT_check_is_subset', 'FLAT_show_breadth', 'FLAT_print_perl_table', 'FLAT_print_perl_accepting', 'FLAT_print_gp_inline_table', 'FLAT_print_gp_inline_accepting', 'FLAT_print_tikz', ); our %EXPORT_TAGS = (all => \@EXPORT_OK); #------------------------------------------------------------------------------ =pod =over =item C<@states = $fa-EMyFLAT::get_non_accepting> Return a list of all the non-accepting states in C<$fa>. =back =cut sub get_non_accepting { my ($fa) = @_; return grep {! $fa->is_accepting($_)} $fa->get_states; } #------------------------------------------------------------------------------ =pod =over =item C<$count = num_accepting($fa)> =item C<$count = num_non_accepting($fa)> Return the number of accepting or non-accepting states in C<$fa>. =back =cut sub num_accepting { my ($fa) = @_; my @states = $fa->get_accepting; return scalar(@states); } sub num_non_accepting { my ($fa) = @_; my @states = $fa->MyFLAT::get_non_accepting; return scalar(@states); } sub num_symbols { my ($fa) = @_; my @alphabet = $fa->alphabet; return scalar(@alphabet); } #------------------------------------------------------------------------------ sub descendants { my ($self, $state, $symb) = @_; my %try; @try{ref $state eq 'ARRAY' ? @$state : $state} = (); # hash slice my %seen; my %ret; while (%try) { foreach my $from (keys %try) { delete $try{$from}; $seen{$from} = 1; foreach my $to ($self->successors($from,$symb)) { $ret{$to} = 1; unless ($seen{$to}++) { $try{$to} = undef; } } } } return keys %ret; } #------------------------------------------------------------------------------ # Prefixes =pod =over =item C<$new_lang = $lang-Eprefix> =item C<$new_lang = $lang-Eprefix ($proper)> Return a new regular language object for prefixes of C<$lang>. This means all strings S for which there exists some T where S.T is in C<$lang>. For example if "abc" is in C<$lang> then C<$new_lang> has all prefixes "", "a", "ab", "abc". The default is to allow T empty, so all strings of C<$lang> are included in C<$new_lang>. Optional parameter C<$proper> true means T must be non-empty so only proper prefixes S are accepted. In both cases prefix S can be the empty string, if suitable T exists. For C<$proper> false this means if C<$lang> accepts anything at all (Cis_empty>). For C<$proper> true it means if C<$lang> accepts some non-empty string. =back =cut sub prefix { my ($self, $proper) = @_; $self = $self->clone; my @ancestors = $self->MyFLAT::ancestors([$self->get_accepting]); if ($proper) { $self->unset_accepting($self->get_accepting); } $self->set_accepting(@ancestors); return $self; } sub ancestors { my ($self, $state, $symb) = @_; # "targets" are the states sought as successors. Initially given $state, # then the immediate successors of those, successors of successors, # etc. # "ret" is those successors. It does not include the initial $state, # unless a cycle comes back around to some of $state. # "try" are states to look at for a successor target. Initially # everything, then when a state is put in "ret" don't try it again. my %targets; @targets{ref $state eq 'ARRAY' ? @$state : $state} = (); # hash slice my %try; @try{$self->get_states} = (); # hash slice my %ret; my $more; do { $more = 0; foreach my $from (keys %try) { foreach my $to ($self->successors($from,$symb)) { if (exists $targets{$to}) { delete $try{$from}; $ret{$from} = 1; $targets{$from} = 1; $more = 1; } } } } while ($more); return keys %ret; } # UNTESTED # like successors, but the one-step preceding sub predecessors { my ($self, $state, $symb) = @_; my %targets; @targets{ref $state eq 'ARRAY' ? @$state : $state} = (); # hash slice ### %targets my @ret; foreach my $from ($self->get_states) { foreach my $to ($self->successors($from,$symb)) { if (exists $targets{$to}) { push @ret, $from; } } } return @ret; } =pod =over =item C<@states = $fa-Eget_prefix_states ()> =item C<@states = $fa-Eget_prefix_states ($proper)> Return a list of those states which C would make accepting (and all other states non-accepting). This is all ancestor states of the accepting states in C<$fa>, so the predecessors of accepting, the predecessors of them, etc. The default C<$proper> false includes the original accepting states (so all original strings of C<$fa>). For C<$proper> the original accepting states are not included, unless they occur as ancestors. (If they do, and are reachable from starting states, then it means there are already some prefixes accepted by C<$fa>.) No attention is paid to start states and what might be reached from them. This allows prefixing to be found or manipulated before setting starts. C<$fa> can be modified to accept also its prefixes like a non-copying form of C<$fa-Eprefix()> by $fa->set_accepting($fa->get_prefix_states); =item C<@states = get_prefix_accepting($fa)> =item C<$fa = prefix_accepting($fa)> C returns states which are not accepting but which by some sequence of symbols are able to reach accepting. Some states of C<$fa> may be non-accepting, but able to reach an accepting state by some sequence of symbols. C returns a list of those states. C returns a new FLAT which has these "prefix accepting" states set as accepting. The effect is to accept all strings C<$fa> does, and in addition accept all prefixes of strings accepted by C<$fa>, including the empty string. Prefix accepting states are the predecessors of accepting states, and predecessors of those prefix states, etc. This usually extends back to starting states, and includes those states. But no attention is paid to starting-ness, the process just continues back by predecessors, irrespective of what might be actually reachable from a starting state. =back =cut # Return depth=>$depth,states=>$aref. sub get_prefix_accepting_info { my ($fa) = @_; my $alphabet_aref = [ $fa->alphabet ]; my %non_accepting; @non_accepting{$fa->MyFLAT::get_non_accepting} = (); # hash slice my $depth = -1; my %prefixes; my $more; do { $depth++; STATE: while (my ($state) = each %non_accepting) { # if any successor is an accepting or accepting prefix then this state # is an accepting prefix too if (grep {$prefixes{$_} || $fa->is_accepting($_)} $fa->successors([$fa->epsilon_closure($state)], $alphabet_aref)) { $prefixes{$state} = 1; delete $non_accepting{$state}; $more = 1; } } } while ($more--); return (depth => $depth, states => [ keys %prefixes ]); } sub get_prefix_accepting { my ($fa) = @_; my %info = get_prefix_accepting_info($fa); return @{$info{'states'}}; } # Return a new FLAT (a clone) which accepts any initial prefix of the # strings accepted by $fa. # Each state is set to accepting if it has any accepting successor (some # symbol leads to accepting), and repeating until no more such can be found. # sub prefix_accepting { my ($fa, %options) = @_; my %info = get_prefix_accepting_info($fa); my $states = $info{'states'}; my $depth = $info{'depth'}; if ($options{'verbose'}) { print $fa->{name}//'', " accepting prefixes, count ",scalar(@$states)," more, depth=$depth\n"; } $fa = $fa->clone; $fa->set_accepting(@$states); if (defined (my $name = $fa->{'name'})) { if ($depth) { $name .= ' prefixes'; } $fa->{'name'} = $name; } return $fa; } { package FLAT::Regex; sub MyFLAT_prefix { my $self = shift; $self->_from_op($self->op->MyFLAT_prefix(@_)); } sub MyFLAT_suffix { my $self = shift; $self->_from_op($self->op->MyFLAT_suffix(@_)); } } { package FLAT::Regex::Op::atomic; sub MyFLAT_prefix { my ($self, $proper) = @_; my $member = $self->members; return (! defined $member ? $self # null regex, unchanged : $proper # symbol becomes empty string, # empty string becomes null regexp ? (ref $self)->new(length($member) ? '' : undef) : length($member) # symbol, accept it and also empty string ? FLAT::Regex::Op::alt->new((ref $self)->new(''), $self) # empty string, unchanged : $self); } *MyFLAT_suffix = \&MyFLAT_prefix; } { package FLAT::Regex::Op::star; sub MyFLAT_prefix { my ($self, $proper) = @_; my $member = $self->members; # M* -> M* properprefix(M) # Can be proper prefix always since a itself covered by M*. # But must check M has a non-empty string before doing that. # Otherwise get (M* #) which doesn't match anything at all, but for # $proper==0 want to match the empty string (unless M is_empty). return ($member->has_nonempty_string ? FLAT::Regex::Op::concat->new($self, $member->MyFLAT_prefix(1)) : $proper ? FLAT::Regex::Op::atomic->new(undef) : $member); # empty string or null regex remains so } sub MyFLAT_suffix { my ($self, $proper) = @_; my $member = $self->members; # like prefix but reverse return ($member->has_nonempty_string ? FLAT::Regex::Op::concat->new($member->MyFLAT_suffix(1), $self) : $proper ? FLAT::Regex::Op::atomic->new(undef) : $member); # empty string or null regex remains so } } { package FLAT::Regex::Op::concat; sub MyFLAT_prefix { my ($self,$proper) = @_; # B C -> properprefix(B) | B prefix(C) # # If prefix(C) is not null then it includes the empty string and can go # to properprefix(B) since whole B is covered by (B []). # # If C is the empty string and $proper==1 then prefix(C) is null so get # (B #) which matches nothing and thus doesn't give whole C. This is as # desired, since it is not a proper prefix in that case. # # For 3 or more concat members, nest like # A B C -> properprefix(A) | A ( properprefix(B) | B prefix(C) ) # # properprefix() is allowed for the earlier parts once a non-null prefix # is seen. # # An empty $member means the whole concat matches nothing. Watch for # that explicitly since B=# would give prefix(A) | (A #) which would # wrongly accept prefix(A). my $ret; foreach my $member (CORE::reverse $self->members) { if ($member->is_empty) { return $member; } my $prefix = $member->MyFLAT_prefix($proper); $ret = (defined $ret ? FLAT::Regex::Op::alt->new ($prefix, __PACKAGE__->new($member, $ret)) : $prefix); $proper ||= ! $prefix->is_empty; } return $ret; } sub MyFLAT_suffix { my ($self,$proper) = @_; # similar to prefix, working forwards through members for the nesting # A B -> suffix(A) B | propersuffix(B) # A B C -> ( suffix(A) B | propersuffix(B)) C ) | propersuffix(C) my $ret; foreach my $member ($self->members) { if ($member->is_empty) { return $member; } my $suffix = $member->MyFLAT_suffix($proper); $ret = (defined $ret ? FLAT::Regex::Op::alt->new (__PACKAGE__->new($ret, $member), $suffix) : $suffix); $proper ||= ! $suffix->is_empty; } return $ret; } } { package FLAT::Regex::Op; # return new op of $self members transformed by $member->$method on each sub MyFLAT__map_method { my $self = shift; my $method = shift; return (ref $self)->new(map {$_->$method(@_)} $self->members); } } { package FLAT::Regex::Op::alt; # prefix(X|Y) = prefix(X) | prefix(Y) # suffix(X|Y) = suffix(X) | suffix(Y) sub MyFLAT_prefix { my $self = shift; return $self->MyFLAT__map_method('MyFLAT_prefix',@_); } sub MyFLAT_suffix { my $self = shift; return $self->MyFLAT__map_method('MyFLAT_suffix',@_); } } { package FLAT::Regex::Op::shuffle; *MyFLAT_prefix = \&FLAT::Regex::Op::alt::MyFLAT_prefix; *MyFLAT_suffix = \&FLAT::Regex::Op::alt::MyFLAT_suffix; } #------------------------------------------------------------------------------ # Eventually Accepting =pod =over =item C<@states = get_eventually_accepting($fa)> =item C<$fa = eventually_accepting($fa)> Some states of C<$fa> may be "eventually accepting" in the sense that after more symbols they are certain to reach accepting, for all possible further symbol values. For example suppose alphabet a,b,c. If bba, bbb and bbc are all accepted by C<$fa> then string "bb" is reckoned as eventually accepted since one further symbol, any of a,b,c, goes to accepting. C returns a list of states which are eventually accepting. C returns a clone of C<$fa> which has those states set as accepting. Eventually accepting states are found first as any state with all symbols going to accepting, then any state with all symbols going to either accepting or eventually accepting, and so on until no more such further states. In an NFA, any epsilon transitions are crossed in the usual way, but there should be just one starting state (or just one which ever leads to accepting). If multiple starting states then the simple rule used will sometimes fail to find all eventually accepting states and hence strings. C will collapse multiple starts. =back =cut # Return depth=>$depth,states=>$aref. sub get_eventually_accepting_info { my ($fa) = @_; my $alphabet_aref = [ $fa->alphabet ]; my %non_accepting; @non_accepting{$fa->MyFLAT::get_non_accepting} = (); # hash slice my %eventually; my $depth = -1; my $more; do { $depth++; my @new_eventually; STATE: while (my ($state) = each %non_accepting) { ### $state foreach my $to_state ($fa->successors([$fa->epsilon_closure($state)], $alphabet_aref)) { ### $to_state unless ($eventually{$to_state} || $fa->is_accepting($to_state)) { next STATE; } } push @new_eventually, $state; } foreach my $state (@new_eventually) { $eventually{$state} = 1; delete $non_accepting{$state}; $more = 1; } } while ($more--); return (depth => $depth, states => [ keys %eventually ]); } sub get_eventually_accepting { my ($fa) = @_; my %info = get_eventually_accepting_info($fa); return @{$info{'states'}}; } # Return a new FLAT (a clone) which accepts strings eventually accepted by $fa. sub eventually_accepting { my ($fa, %options) = @_; my %info = get_eventually_accepting_info($fa); my $states = $info{'states'}; my $depth = $info{'depth'}; if ($options{'verbose'}) { print $fa->{name}//'', " eventually accepting, count ",scalar(@$states)," more, depth=$depth\n"; } $fa = $fa->clone; $fa->set_accepting(@$states); if (defined (my $name = $fa->{'name'})) { if ($depth) { $name .= ' eventually'; } $fa->{'name'} = $name; } return $fa; } #------------------------------------------------------------------------------ =pod =over =item C<$fa = fraction_digits($num,$den, %options)> Return a C which matches digits of fraction C<$num/$den>. The DFA remains accepting as long as it is given successive digits of the fraction, and goes non-accepting (and remains so) on a wrong digit. The default is decimal digits, or optional key/value radix => integer>=2 If C<$num/$den> is an exact fraction in C<$radix>, meaning C<$num/$den == n/$radix**k> for some integer n,k, then it has two different representations. Firstly terminating digits followed by trailing 0s, secondly C<$n-1> followed by trailing C<$radix-1> digits. For example 42/100 is 420000... and 419999... Both digit sequences converge to 42/100. For fractions not an exact power of C<$radix> there is just one digit sequence which converges to C<$num/$den>. C<$num == 0> gives a DFA matching 000..., or C<$num==$den> for fraction C<$num/$den == 1> gives a DFA matching 9999... (or whatever C<$radix-1>). In all cases the C<$fa-Ealphabet> is all the digits 0 to C<$radix-1>. Those which are "wrong" digits at a given point go to a non-accepting sink state. This is designed so that C<$fa-Ecomplement> gives all digit strings except fraction C<$num/$den>. MAYBE: Option to omit wrong digits in an NFA, so transitions only for the accepted digits. MAYBE: Currently the symbols for digits in a radix 11 or higher are decimal strings, but that might change. Could have an option for hex or a table or func. Decimal strings are easy to work with their values in Perl if a further func might act on the resulting FLAT. C can always change for final result if desired. =back =cut sub fraction_digits { my ($num, $den, %options) = @_; ### fraction_digits(): "$num / $den" require FLAT::DFA; my $f = FLAT::DFA->new; my $radix = $options{'radix'} || 10; ### $radix my $not_accept = $f->add_states(1); $f->add_transition ($not_accept,$not_accept, 0..$radix-1); unless ($num >=0 && $num <= $den) { croak "fraction_digits() must have 0<=num<=den"; } unless ($radix >= 2) { croak "fraction_digits() must have radix>=2"; } my %num_to_state; my $prev_state = $f->add_states(1); $f->set_starting ($prev_state); $f->set_accepting ($prev_state); $num_to_state{$num} = $prev_state; my $prev_digit; my $prev_prev_state; if ($num == $den) { # 1/1 match .9999... $f->add_transition ($prev_state,$prev_state, $radix-1); $f->add_transition ($prev_state,$not_accept, 0..$radix-2); return $f; } for (;;) { ### $num $num *= $radix; my $digit = int($num / $den); $num %= $den; if ($digit >= 10) { $digit = chr(ord('A')+$digit-10); } ### $digit my $cycle_state = $num_to_state{$num}; my $state = $cycle_state // $f->add_states(1); $f->set_accepting ($state); $f->add_transition ($prev_state,$state, $digit); $f->add_transition ($prev_state,$not_accept, grep {$_!=$digit} 0..$radix-1); if (defined $cycle_state) { if ($num == 0 && $prev_digit) { $state = $f->add_states(1); $f->set_accepting ($state); $f->set_transition ($prev_prev_state, $not_accept, grep {$_!=$prev_digit-1 && $_!=$prev_digit} 0..$radix-1); $f->add_transition ($prev_prev_state,$state, $prev_digit-1); $f->add_transition ($state,$state, $radix-1); $f->add_transition ($state,$not_accept, 0..$radix-2); } return $f; } $num_to_state{$num} = $state; $prev_digit = $digit; $prev_prev_state = $prev_state; $prev_state = $state; } } # $radix ||= 10; # unless ($num >=0 && $num < $den) { # croak "fraction_digits() must have 0<=num$index of digits in @digits # my $pos = 0; # for (;;) { # if (defined(my $rpos = $seen{$num})) { # # this numerator is a repeat of what was at $rpos, so cycle back to there # require FLAT::DFA; # my $f = FLAT::DFA->new; # my @states = $f->add_states($pos+1); # $f->set_starting($states[0]); # $f->set_accepting(@states[0..$pos-1]); # foreach my $i (0 .. $pos) { # foreach my $d (0 .. $radix-1) { # my $to = ($i==$pos || $d != $digits[$i] ? $pos # not accept # : $i == $pos-1 ? $rpos # cycle back # : $i+1); # next # $f->add_transition ($states[$i],$states[$to]); # } # } # $f->{'name'} = "$num/$den radix $radix"; # return $f; # } # # ### $num # ### assert: $num >= 0 # ### assert: $num < $den # $seen{$num} = $pos++; # $num *= $radix; # my $digit = int($num / $den); # $num %= $den; # if ($digit >= 10) { $digit = chr(ord('A')+$digit-10); } # push @digits, $digit; # } # # my $str; # $str .= $digit; # my $re = substr($str,0,$rpos) . "(".substr($str,$rpos) . ")*"; # ### $str # ### $rpos # ### $re # my $f = FLAT::Regex->new($re)->as_dfa; # $f->{'name'} = "$num/$den radix $radix"; # $f = prefix($f); # return $f; # $fa is a FLAT::DFA which matches fractions represented as strings of digits. # Return a new FLAT::DFA which matches any terminating fraction like 10111 # also as its non-terminating equivalent 101101111... # FIXME: currently only works for binary, and only when terminating # fractions end with a 1, not with low 0s. sub fraction_also_nines { my ($fa, %options) = @_; # FLAT::Regex->new ('(0|1)* 1 0*')->as_nfa; my $binary_odd_flat = FLAT::Regex->new ('(0|1)* 1')->as_dfa; return $fa->as_dfa ->intersect($binary_odd_flat) ->MyFLAT::skip_final ->MyFLAT::concat(FLAT::Regex->new ('01*')->as_dfa) ->union($fa) ->MyFLAT::set_name($fa->{'name'}); } # $fa is a FLAT::NFA or FLAT::DFA accepting strings of digits. # Those strings are interpreted as fractional numbers .ddddd... # Return a new FLAT (same DFA or NFA) which accepts these same strings and # also representations ending 999... # For example if 321 is accepted then 3209999... is also accepted. # # The radix is taken from $fa->alphabet, or option radix=>$r can be given if # $fa might not have all digits appearing. # # The digit strings read high to low by default. Option # direction=>"lowtohigh" can interpret them low to high instead. Low to # high will be more efficient since manipulations are at the low end # (propagate a carry up through low "9"s), but both work. # # sub fraction_nines { # my ($fa, %options) = @_; # ### digits_increment() ... # # # starting state is flip # # in flip 0-bit successor as a 1-bit, and thereafter unchanged # # 1-bit successor as a 0-bit, continue flip # # my $direction = $options{'direction'} || 'hightolow'; # my $radix = $options{'radix'} || max($fa->alphabet)+1; # my $nine = $radix-1; # # my $is_dfa = $fa->isa('FLAT::DFA'); # $fa = $fa->clone->MyFLAT::as_nfa; # if ($direction eq 'hightolow') { $fa = $fa->reverse; } # # my %flipped_states; # { # # states reachable by runs of 9s from starting states # my @pending = $fa->get_starting; # while (defined (my $state = shift @pending)) { # unless (exists $flipped_states{$state}) { # my ($new_state) = $fa->add_states(1); # ### add: "state=$state new=$new_state" # $flipped_states{$state} = $new_state; # # if ($fa->is_starting($state)) { # $fa->set_starting($new_state); # $fa->unset_starting($state); # } # push @pending, $fa->successors($state, $nine); # } # } # } # # while (my ($state, $flipped_state) = each %flipped_states) { # ### setup: "$state nines becomes $flipped_state" # # foreach my $digit (0 .. $nine-1) { # foreach my $successor ($fa->successors($state, $digit)) { # ### digit: "digit=$digit $flipped_state -> $successor on 1" # $fa->add_transition($flipped_state, $successor, $digit+1); # } # } # if ($fa->is_accepting($state)) { # # 99...99 accepting becomes 00..00 1 accepting, with a new state for # # the additional 1-bit to go to # my ($new_state) = $fa->add_states(1); # $fa->set_accepting($new_state); # $fa->add_transition($flipped_state, $new_state, 1); # ### carry above accepting: $new_state # } # # foreach my $successor ($fa->successors($state, $nine)) { # ### nine: "$flipped_state -> $flipped_states{$successor} on 0" # $fa->add_transition($flipped_state, $flipped_states{$successor}, 0); # } # } # # if (defined $fa->{'name'}) { # $fa->{'name'} =~ s{\+(\d+)$}{'+'.($1+1)}e # or $fa->{'name'} .= '+1'; # } # # if ($direction eq 'hightolow') { $fa = $fa->reverse; } # if ($is_dfa) { $fa = $fa->as_dfa; } # return $fa; # } #------------------------------------------------------------------------------ =pod =over =item C<$new_fa = $fa-EMyFLAT::separate_sinks> Return a copy of C<$fa> which has separate sink states. A sink state is where all out transitions loop back to itself. If two or more states go to the same sink then the return has new states so that each goes to its own such sink. The new sinks are the same accepting or not as each original sink. This does not change the strings accepted, but can help viewing a big diagram where many long range transitions go to a single accepting and/or non-accepting sink. Only single sink states are sought. Multiple states cycling among themselves all the same accepting or non-accepting are sinks, but they can be merged by an C. =item C<$bool = $fa-EMyFLAT::is_sink($state)> Return true if C<$state> has all transitions go to itself. =back =cut sub separate_sinks { my ($fa) = @_; $fa = $fa->clone; my %sink_used; my @alphabet = $fa->alphabet; foreach my $from_state ($fa->get_states) { foreach my $to_state ($fa->successors($from_state)) { next unless $fa->MyFLAT::is_sink($to_state); next if $from_state==$to_state; next unless $sink_used{$to_state}++; my $new_state = $fa->MyFLAT::copy_state($to_state); my @labels = FLAT_get_transition_labels($fa,$from_state,$to_state); ### common sink: "$from_state to $to_state, new $new_state, labels ".join(' ',@labels) # when $fa is an NFA add_transition() accumulates, so for it must # remove old transitions $fa->remove_transition($from_state,$to_state); $fa->add_transition($from_state,$new_state,@labels); } } return $fa; } # $fa is a FLAT::FA. # FIXME: what about cycles of mutual transitions among accepting states? sub is_sink { my ($fa, $state) = @_; my @next = $fa->successors($state); return @next==1 && $next[0]==$state; } sub get_sink_states { my ($fa) = @_; return grep {$fa->MyFLAT::is_sink($_)} $fa->get_states; } sub is_accepting_sink { my ($fa, $state) = @_; $fa->MyFLAT::is_sink($state) && $fa->is_accepting($state); } sub get_accepting_sinks { my ($fa) = @_; return grep {$fa->MyFLAT::is_accepting_sink($_)} $fa->get_states; } sub num_accepting_sinks { my ($fa) = @_; # this depends on use of grep in get_accepting_sinks() return scalar($fa->MyFLAT::get_accepting_sinks); } =pod =over =item C<$new_state = $fa-EMyFLAT::copy_state ($state)> Add a state to C<$fa> which is a copy of C<$state>. Transitions out and accepting-ness of C<$new_state> and the same as C<$state>. Return the new state number. =back =cut sub copy_state { my ($fa, $state) = @_; ### copy_state(): $state my ($new_state) = $fa->add_states(1); if ($fa->is_accepting ($state)) { $fa->set_accepting($new_state); } # ENHANCE-ME: transition can be copied more efficiently? foreach my $symbol ($fa->alphabet) { foreach my $next ($fa->successors($state, $symbol)) { my $new_next = ($next == $state ? $new_state : $next); $fa->add_transition($new_state,$new_next, $symbol); } } return $new_state; } #------------------------------------------------------------------------------ # $fa is a FLAT::FA. # Return a new FLAT with some of its states or symbols renamed. # # symbols_func => $coderef called $new_symbol = $coderef->($old_symbol) # symbols_map => $hashref of $old_symbol => $new_symbol # states_map => $hashref of $old_state => $new_state # states_list => arrayref of existing states in order for the new # # Any states or symbols in $fa unmentioned in these mappings are unchanged, # so some can be changed and the rest left alone. # # Symbols can be swapped or cycled by for example {'A'=>'B', 'B'=>'A'}. # States similarly. # sub FLAT_rename { my ($fa, %options) = @_; my @alphabet = $fa->alphabet; my $symbols_func = $options{'symbols_func'} // do { my $symbols_map = $options{'symbols_map'} // {}; sub { my ($symbol) = @_; return $symbols_map->{$symbol}; } }; my $states_map = $options{'states_map'} // {}; if (defined(my $states_list = $options{'states_list'})) { $states_map = { map {$_ => $states_list->[$_]} 0 .. $#$states_list }; } my $new = (ref $fa)->new; $new->add_states($fa->num_states); foreach my $old_state ($fa->get_states) { my $new_state = $states_map->{$old_state} // $old_state; if ($fa->is_accepting($old_state)) { $new->set_accepting($new_state); } if ($fa->is_starting ($old_state)) { $new->set_starting ($new_state); } foreach my $symbol (@alphabet) { my $new_symbol = $symbols_func->($symbol) // $symbol; foreach my $old_next ($fa->successors($old_state, $symbol)) { my $new_next = $states_map->{$old_next} // $old_next; $new->add_transition($new_state, $new_next, $new_symbol); } } } $new->{'name'} = $fa->{'name'}; return $new; } # Return a new FLAT::FA of the same type as $fa but where any accepting # states are numbered last. sub rename_accepting_last { my ($fa, %options) = @_; return FLAT_rename($fa, states_list => [ $fa->MyFLAT::get_non_accepting, $fa->get_accepting ]); } sub _sort_sensibly { if (grep {!looks_like_number($_)} @_) { return sort @_; } else { return sort {$a<=>$b} @_; } } sub alphabet_sorted { my ($fa) = @_; return _sort_sensibly($fa->alphabet); } sub states_breadth_first { my ($fa) = @_; my @ret; my $upto = 0; my @alphabet = $fa->MyFLAT::alphabet_sorted; my @pending = sort {$a<=>$b} $fa->get_starting; while (@pending) { my $state = shift @pending; next if defined $ret[$state]; $ret[$state] = $upto++; foreach my $symbol (@alphabet) { push @pending, sort {$a<=>$b} $fa->successors($state, $symbol); } } return @ret; } sub rename_breadth_first { my ($fa) = @_; return FLAT_rename($fa, states_list => [$fa->MyFLAT::states_breadth_first]); } #------------------------------------------------------------------------------ # zero_digits_flat() returns a FLAT::DFA matching a run of 0 digits, # possibly an empty run. This is regex "0*", but with alphabet 0 .. $radix-1. sub zero_digits_flat { my ($radix) = @_; my $f = FLAT::DFA->new; $f->add_states(2); $f->set_starting(0); $f->set_accepting(0); $f->add_transition(0,0, 0); # state 0 accept 0s $f->add_transition(0,1, 1 .. $radix-1); $f->add_transition(1,1, 0 .. $radix-1); # state 1 non-accepting sink return $f; } # one_bits_flat() returns a FLAT::DFA matching a run of 1 bits, possibly an # empty run. This is regex "1*", but with alphabet 0,1. use constant::defer one_bits_flat => sub { require FLAT::DFA; my $f = FLAT::DFA->new; $f->add_states(2); $f->set_starting(0); $f->set_accepting(0); $f->add_transition(0,0, 1); $f->add_transition(0,1, 0); $f->add_transition(1,1, 1); $f->add_transition(1,1, 0); return $f; }; # Return a FLAT::DFA which matches bit strings which are an even number N. # An empty string "" is reckoned as 0 and so is matched. use constant::defer bits_N_even_flat => sub { require FLAT::Regex; my $f = FLAT::Regex->new('(0|1)* 0 | []')->as_dfa; $f->{'name'} = 'even N'; return $f; }; # Return a FLAT::DFA which matches bit strings which are an odd number N. use constant::defer bits_N_odd_flat => sub { require FLAT::Regex; my $f = FLAT::Regex->new('(0|1)* 1')->as_dfa; $f->{'name'} = 'odd N'; return $f; }; # Return a FLAT::DFA which matches exactly $len many bits 0,1. sub bits_of_length_flat { my ($len) = @_; my $f = FLAT::DFA->new; if ($len < 0) { $len = -1; } $f->add_states($len+2); $f->set_starting(0); if ($len >= 0) { $f->set_accepting($len); } foreach my $state (0 .. $len) { $f->add_transition($state,$state+1, 0); $f->add_transition($state,$state+1, 1); } my $non = $len+1; $f->add_transition($non,$non, 0); $f->add_transition($non,$non, 1); return $f->MyFLAT::set_name("$len bits"); # return FLAT::Regex->new('(0|1)' x $len) # ->as_dfa # ->MyFLAT::minimize # ->MyFLAT::set_name("$len bits"); } sub bits_of_length_or_more_flat { my ($len) = @_; require FLAT::Regex; return FLAT::Regex->new(('(0|1)' x $len) . '(0|1)*') ->as_dfa ->MyFLAT::minimize ->MyFLAT::set_name(">=$len bits"); } #------------------------------------------------------------------------------ # Return all the labels which transition $from_state to $to_state. sub FLAT_get_transition_labels { my ($fa, $from_state, $to_state) = @_; ### FLAT_get_transition_labels(): "$from_state to $to_state" my @ret; foreach my $symbol ($fa->alphabet) { ### $symbol my $next; if ((($next) = $fa->successors($from_state, $symbol)) && $next==$to_state) { push @ret, $symbol; } ### $next } ### @ret return @ret; } #------------------------------------------------------------------------------ # printouts sub FLAT_varname { my ($fa) = @_; my $name = $fa->{'name'}; if (defined $name) { $name =~ tr/a-zA-Z0-9_/_/c; } return $name; } sub FLAT_print_perl_table { my ($fa, $name) = @_; $name //= FLAT_varname($fa); my @alphabet = sort {$a<=>$b} $fa->alphabet; print "# alphabet ",join(',',@alphabet),"\n"; require MyPrintwrap; print "\@$name = (\n"; MyPrintwrap::printwrap_indent(" "); my @states = $fa->get_states; foreach my $state (@states) { my @row = map { my $symbol = $_; my @next = $fa->successors($state,$symbol); if (@next != 1) { croak "Not single next for $state symbol $symbol"; } $next[0] } @alphabet; MyPrintwrap::printwrap(" [".join(',',@row)."]" . ($state == $#states ? "" : ',')); } print ");\n"; } sub FLAT_print_perl_accepting { my ($fa, $name) = @_; $name //= FLAT_varname($fa); my @accepting = $fa->get_accepting; my $start = "\@$name = ("; my $end = ");\n"; my $line = $start . join(',',@accepting) . $end; if (length $line < 79) { print "$line\n"; return; } require MyPrintwrap; MyPrintwrap::printwrap_indent(" "); print $start,"\n"; foreach my $i (0 .. $#accepting) { MyPrintwrap::printwrap("$accepting[$i]" . ($i == $#accepting ? "" : ',')); } MyPrintwrap::printwrap($end); } sub FLAT_print_gp_inline_table { my ($fa, $name) = @_; require MyPrintwrap; MyPrintwrap::printwrap_indent("% GP-DEFINE "); $MyPrintwrap::Printwrap = 0; MyPrintwrap::printwrap("$name = {["); $MyPrintwrap::Printwrap += 2; my @alphabet = sort {$a<=>$b} $fa->alphabet; my @states = $fa->get_states; foreach my $state (@states) { my @row = map { my @to = $fa->successors($state,$_); @to<=1 or die "oops, not a DFA"; @to ? $to[0]+1 : "'none" } @alphabet; MyPrintwrap::printwrap(join(',',@row) . ($state == $#states ? '' : ';')); } MyPrintwrap::printwrap("]};\n"); } sub FLAT_print_gp_inline_accepting { my ($fa, $name) = @_; require MyPrintwrap; MyPrintwrap::printwrap_indent("% GP-DEFINE "); $MyPrintwrap::Printwrap = 0; MyPrintwrap::printwrap("$name = {["); $MyPrintwrap::Printwrap += 2; my $join = ''; my @accepting = $fa->get_accepting; foreach my $i (0 .. $#accepting) { MyPrintwrap::printwrap(($accepting[$i]+1) . ($i == $#accepting ? '' : ',')); } MyPrintwrap::printwrap("]};\n"); } sub FLAT_print_tikz { my ($fa, %options) = @_; my $node_prefix = $options{'node_prefix'} // 's'; my $flow = $options{'flow'} // $fa->{'flow'} // 'east'; my $state_labels = $options{'state_labels'}; print "% accepting ", join(',',$fa->get_accepting), "\n"; my @column_to_states; my @state_to_column; my $put_state = sub { my ($state, $column) = @_; $state_to_column[$state] = $column; push @{$column_to_states[$column]}, $state; }; foreach my $state ($fa->get_starting) { $put_state->($state, 0); } for (my $c = 0; $c <= $#column_to_states; $c++) { foreach my $from_state (@{$column_to_states[$c]}) { next unless defined $state_to_column[$from_state]; my $to_column = $state_to_column[$from_state] + 1; foreach my $to_state (sort $fa->successors($from_state)) { next if defined $state_to_column[$to_state]; $put_state->($to_state, $to_column); } } } # unreached states at end foreach my $state ($fa->get_states) { next if defined $state_to_column[$state]; $put_state->($state, scalar(@column_to_states)); } foreach my $column (0 .. $#column_to_states) { my $states = $column_to_states[$column]; foreach my $i (0 .. $#$states) { my $state = $states->[$i]; my $x = $column; my $y = $i - int(scalar(@$states)/2); if ($flow eq 'west') { $x = -$x; } if ($flow eq 'north') { ($x,$y) = ($y,$x); } if ($flow eq 'south') { ($x,$y) = ($y,-$x); } my $state_name = "$node_prefix$state"; my $state_str = ($state_labels ? $state_labels->[$state] : $state); print " \\node ($state_name) at ($x,$y) [my box] {$state_str};\n"; } } print "\n"; my @alphabet = sort {$a<=>$b} $fa->alphabet; foreach my $from_state ($fa->get_states) { my $from_state_name = "$node_prefix$from_state"; print " % $from_state_name\n"; require Tie::IxHash; my %to_lists; tie %to_lists, 'Tie::IxHash'; foreach my $symbol (@alphabet) { if (my ($to_state) = $fa->successors($from_state, $symbol)) { push @{$to_lists{$to_state}}, $symbol; } } while (my ($to_state, $labels) = each %to_lists) { my $to_state_name = "$node_prefix$to_state"; $labels = join(',', @$labels); if ($from_state eq $to_state) { print " \\draw [->,loop below] ($from_state_name) to node[pos=.12,auto=left] {$labels} ();\n"; } else { my $bend = ''; my $pos = '.45'; if ($fa->get_transition($to_state,$from_state)) { $bend = ',bend left=10'; $pos = '.5'; } print " \\draw [->$bend] ($from_state_name) to node[pos=$pos,auto=left] {$labels} ($to_state_name);\n"; } } print "\n"; } } #------------------------------------------------------------------------------ # $aref is an arrayref of arrayrefs which is a state table. # [ [1,2], # [2,0], # [0,1] ] # States are numbered C<0> to C<$#$aref> inclusive. # The table has C<$new_state = $aref-E[$state]-E[$digit]>. # Return a FLAT::DFA of this state table. # # Optional further key/value arguments are # starting => $state # accepting => $state # accepting_list => arrayref [ $state, $state, ... ] # name => $string # # C is the starting state, or default 0. # # C or C are the state or states which are accepting. # If both C and C are given then both their states # specified are made accepting. # sub aref_to_FLAT_DFA { my ($aref, %options) = @_; require FLAT::DFA; my $f = FLAT::DFA->new; my @fstates = $f->add_states(scalar(@$aref)); my $starting = $options{'starting'} // 0; $f->set_starting($fstates[$starting]); ### starting: "$starting (= $fstates[$starting])" my @accepting = (@{$options{'accepting_list'} // []}, $options{'accepting'} // ()); if (! @accepting) { @accepting = $#$aref; } $f->set_accepting(map {$fstates[$_]} @accepting); my $width = @{$aref->[0]}; foreach my $state (0 .. $#$aref) { my $row = $aref->[$state]; if (@$row != $width) { croak "state row $state doesn't have $width entries"; } foreach my $digit (0 .. $#$row) { my $to_state = $row->[$digit] // next; # croak "state $state digit $digit destination undef"; ($to_state >= 0 && $to_state <= $#$aref) or croak "state $state digit $digit destination $to_state out of range"; ### transition: "$state(=$fstates[$state]) digit=$digit -> $to_state($fstates[$to_state])" $f->add_transition($fstates[$state], $fstates[$to_state], $digit); } } $f->{'name'} = $options{'name'}; return $f; } #------------------------------------------------------------------------------ # $fa is a FLAT::NFA or FLAT::DFA. # Return a list of how many strings of length $len are accepted, for $len # running 0 to $max_len inclusive. # The counts can become large, especially when $fa has a lot of symbols. # The numeric type of the return is inherited from $max_len, so for example # if it is a Math::BigInt then that is used for the returns. # In general, the counts are a linear recurrences with order at most the number # of states in $fa. Such recurrences include constants (like one string of # each length), and polynomials. # # MAYBE: length => $len count strings = $len accepted # MAYBE: max_length => $len count strings <= $len accepted # MAYBE: by_length_upto => $len counts of strings each length <= $len # # count_matrix($fa) = [],[] $array[$row]->[$col] with M*initcol = counts # count_recurrence($fa) # sub FLAT_count_contains { my ($fa, $max_len, %options) = @_; my @states = $fa->get_states; my @accepting = $fa->get_accepting; my @alphabet = $fa->alphabet; my $zero = $max_len*0; # inherit bignum from $max_len my $ret_type = $options{'ret_type'} || 'accepting'; my @counts = map {$zero} 0 .. $#states; ### starting: $fa->get_starting ### @accepting ### @counts foreach my $state ($fa->get_starting) { $counts[$state]++; } my @ret; if ($ret_type eq 'rows') { @ret = map {[]} 0 .. $#counts; } foreach my $k (0 .. $max_len) { ### at: "k=$k ".join(',',map{$_//'_'}@counts)." total ".sum(0,map{$_//0}@counts)." accepting ".sum(0,map{$counts[$_]//0}@accepting) { my $accepting_count = $zero; foreach my $state (@accepting) { if ($counts[$state]) { $accepting_count += $counts[$state]; } } if ($ret_type eq 'accepting') { push @ret, $accepting_count; } elsif ($ret_type eq 'columns') { push @ret, \@counts; } elsif ($ret_type eq 'rows') { foreach my $i (0 .. $#counts) { push @{$ret[$i]}, $counts[$i]; } } } last if $k == $max_len; my @new_counts = map {$zero} 0 .. $#states; foreach my $from_state (@states) { my $from_count = $counts[$from_state] || next; foreach my $symbol (@alphabet) { foreach my $to_state ($fa->successors($from_state, $symbol)) { ### add: "$from_count $from_state -> $to_state" $new_counts[$to_state] += $from_count; } } } @counts = @new_counts; } return @ret; } sub counts_starting { my ($fa, $zero) = @_; if (! defined $zero) { $zero = 0; } return [ map { $zero + ($fa->is_starting($_) ? 1 : 0) } 0 .. $fa->num_states-1 ]; } sub counts_next { my ($fa, $aref) = @_; # ENHANCE-ME: This is a bit slow. What's the right way to iterate all # transitions? my $zero = $aref->[0] * 0; my @new_counts = ($zero) x scalar(@$aref); my @alphabet = $fa->alphabet; foreach my $from_state ($fa->get_states) { my $from_count = $aref->[$from_state] || next; foreach my $symbol (@alphabet) { foreach my $to_state ($fa->epsilon_closure ($fa->successors($from_state, $symbol))) { $new_counts[$to_state] += $from_count; } } } return \@new_counts; } sub counts_accepting { my ($fa, $aref) = @_; my $ret = $aref->[0] * 0; foreach my $state ($fa->get_accepting) { $ret += $aref->[$state]; } return $ret; } # FIXME: Not right for non-accepting cycles. sub finite_max_length { my ($fa) = @_; ### finite_max_length() ... my @pending = $fa->get_starting; my %seen = map {$_=>1} @pending; my $ret = -1; my $len = 0; while (@pending) { if (grep {$fa->is_accepting($_)} @pending) { ### accepting ... $ret = $len; } $len++; @pending = $fa->epsilon_closure($fa->successors(\@pending)); ### to: @pending @pending = grep {! $seen{$_}++} @pending; } ### $ret return $ret; } #------------------------------------------------------------------------------ # FLAT temporary sub minimize { my ($flat, %options) = @_; my $name = eval { $flat->{'name'} }; if ($options{'verbose'}) { print "minimize ",$flat->{'name'}//''," ",$flat->num_states," states ..."; } $flat = $flat->as_dfa; $flat = $flat->as_min_dfa; if ($options{'verbose'}) { print "done, num states ",$flat->num_states,"\n"; } $flat->{'name'} = $name; return $flat; } # workaround for FLAT::DFA ->as_nfa() leaving itself blessed down in FLAT::DFA sub as_nfa { my ($fa) = @_; $fa = $fa->as_nfa; if ($fa->isa('FLAT::DFA')) { bless $fa, 'FLAT::NFA'; } return $fa; } # workaround for FLAT::DFA ->reverse() infinite recursion, can reverse in NFA sub reverse { my ($fa) = @_; if ($fa->isa('FLAT::DFA')) { $fa->MyFLAT::as_nfa($fa)->reverse->as_dfa; } else { $fa->reverse; } } # workaround for FLAT::DFA ->concat() infinite recursion, can reverse in NFA sub concat { my $fa = shift @_; my $want_dfa = $fa->isa('FLAT::DFA'); foreach my $f2 (@_) { $fa = $fa->MyFLAT::as_nfa->concat($f2->MyFLAT::as_nfa); } if ($want_dfa) { $fa = $fa->as_dfa; } return $fa; } # workaround for FLAT::DFA ->star() infinite recursion, can star in NFA sub star { my ($fa) = @_; if ($fa->isa('FLAT::DFA')) { $fa->MyFLAT::as_nfa($fa)->star->as_dfa; } else { $fa->star; } } #------------------------------------------------------------------------------ sub view { my ($fa) = @_; require MyGraphs; if ($fa->can('as_graphviz')) { # in FLAT::FA, not in FLAT::Regex MyGraphs::graphviz_view($fa->as_graphviz); } else { print $fa->as_string; } } sub FLAT_to_perl_re { my ($fa) = @_; my $str = $fa->as_perl_regex; $str =~ s/\Q?://g; return $str; } sub FLAT_check_is_equal { my ($f1, $f2, %options) = @_; my @names = ($f1->{'name'} // 'first', $f2->{'name'} // 'second'); if ($f1->equals($f2)) { print "$names[0] = $names[1], ok\n"; return; } { my $a1 = join(',',sort $f1->alphabet); my $a2 = join(',',sort $f2->alphabet); unless ($a1 eq $a2) { print "different alphabet: $a1\n"; print " alphabet: $a2\n"; } } my $radix = $options{'radix'} // do { my @labels = $f1->alphabet; scalar(@labels) }; print "$names[0] not equal $names[1]\n"; foreach my $which (1, 2) { my $extra = $f1->as_dfa->difference($f2->as_dfa); print "extra in $names[0] over $names[1]\n"; if ($extra->is_empty) { print " is_empty()\n"; } else { if ($extra->is_finite) { print " is_finite()\n"; } require Math::BaseCnv; if ($extra->contains('')) { print " [] zero length string\n"; } my $it = $extra->new_acyclic_string_generator; # my $it = $extra->new_deepdft_string_generator(20); my $count = 0; while (my $str = $it->()) { if (++$count > 20) { print " ... and more\n"; last; } my $n = Math::BaseCnv::cnv($str,$radix,10); print " $str N=$n\n"; } } @names = CORE::reverse @names; ($f1,$f2) = ($f2,$f1); } exit 1; } sub FLAT_check_is_subset { my ($fsub, $fsuper) = @_; if (! $fsub->as_dfa->is_subset_of($fsuper->as_dfa)) { my $f = $fsub->as_dfa->difference($fsuper->as_dfa); my $it = $f->new_acyclic_string_generator; if (defined(my $sub_name = $fsub->{'name'}) && defined(my $super_name = $fsuper->{'name'})) { print "$sub_name not subset of $super_name, "; } print "extras in supposed subset\n"; my $count = 0; while (my $str = $it->()) { if (++$count > 20) { print " ... and more\n"; last; } print " $str\n"; } exit 1; } my $fsub_name = $fsub->{'name'} // 'subset'; my $fsuper_name = $fsuper->{'name'} // 'superset'; print "$fsub_name subset of $fsuper_name, ok\n"; } sub FLAT_show_breadth { my ($flat, $width, $direction) = @_; $direction //= 'hightolow'; if (defined (my $name = $flat->{'name'})) { print "$name "; } print "contains ($direction, by breadth)\n"; if ($flat->is_empty) { print " is_empty()\n"; } elsif ($flat->is_finite) { print " is_finite()\n"; } my $count = 0; my $total = 0; my @alphabet = sort $flat->alphabet; my $radix = @alphabet; $total++; if ($flat->contains('')) { print " [empty string]\n"; $count++; } require Math::BaseCnv; foreach my $k (1 .. $width) { foreach my $n (0 .. $radix**$k-1) { my $str = Math::BaseCnv::cnv($n,10,$radix); $str = sprintf '%0*s', $k, $str; if ($direction eq 'lowtohigh') { $str = CORE::reverse $str; } $total++; if ($flat->contains($str)) { print " $str N=$n\n"; $count++; } } } print " count $count / $total\n"; } sub FLAT_show_transitions { my ($flat,$str) = @_; my @str = split //, $str; my $print_states = sub { if (@_ == 0) { print "(none)"; return; } my $join = ''; foreach my $state (@_) { print $join, $state, $flat->is_accepting($state) ? "*" : ''; $join = ','; } }; foreach my $initial ($flat->get_starting) { my $state = $initial; $print_states->($state); foreach my $char (@str) { print " ($char)"; my @next = $flat->successors($state,$char); if (! @next) { last; } $state = $next[0]; print "-> "; $print_states->(@next); } print "\n"; } } sub FLAT_check_accepting_remain_so { my ($flat) = @_; my @accepting = $flat->get_accepting; my @alphabet = $flat->alphabet; my $bad = 0; my $name = $flat->{'name'} // ''; foreach my $state (@accepting) { foreach my $char (@alphabet) { my @next = $flat->successors($state,$char); foreach my $to (@next) { if (! $flat->is_accepting($to)) { print "$name $state ($char) -> $to is no longer accepting\n"; $bad++; } } } } if ($bad) { exit 1; } print "$name accepting remain so, ok\n"; } sub FLAT_show_acyclics { my ($flat) = @_; my $it = $flat->new_acyclic_string_generator; if (defined (my $name = $flat->{'name'})) { print "$name "; } print "acyclics\n"; if ($flat->is_empty) { print " empty\n"; } my $count = 0; while (my $str = $it->()) { if (++$count > 8) { print " ... and more\n"; last; } print " $str\n"; } } sub FLAT_show_deep { my ($flat, $depth) = @_; my $it = $flat->new_deepdft_string_generator($depth // 5); print "depth $depth\n"; my $count = 0; while (my $str = $it->()) { if (++$count > 8) { print " ... and more\n"; last; } print " $str\n"; } } #------------------------------------------------------------------------------ sub set_name { my ($flat, $name) = @_; $flat->{'name'} = $name; return $flat; } #------------------------------------------------------------------------------ =pod =over =item C<$new_fa = $fa-Edigits_increment (key =E value, ...)> C<$fa> is a C or C accepting digit strings. Return a new FLAT (same DFA or NFA) which accepts numbers +1, or +/- a given increment. Key/value options are add => integer, default 1 radix => integer>=2, default from alphabet direction => "hightolow" (default) or "lowtohigh" Option C $add> is the increment to apply (default 1). This can be negative too. Option C $radix> is the digit radix. The default is taken from the digits appearing in C<$fa-Ealphabet> which is usually enough. The option can be used if C<$fa> might not have all digits appearing in its alphabet. Digit strings are taken as high to low. Option C "lowtohigh"> takes them low to high instead. Low to high is more efficient here since manipulations are at the low end (add the increment and carry up through low digits), but both work. An increment can increase string length, for example 999 -E 1000. If there are high 0s on a string then the carry propagates into them and does not change the length, so 00999 -E 01000. Negative increments do not decrease string length, so 1000 -> 0999. If C<$add> reduces a number below 0 then that string is quietly dropped. If the strings matched by C<$fa> represent a predicate, numbers with some property, then the returned C<$new_fa> is those N for which N-add has the property. This is since C<$new_fa> is +add from the originals. So to get a predicate testing whether N+1 has the property, apply an C -1>. An C of that and the original becomes a predicate for a pair N and N+1 both with the property and longer runs can be made by further intersects. ENHANCE-ME: Maybe a width option to stay in given number of digits, discard anything which would increment to bigger. Or a wraparound option to ignore carry above width for modulo radix^width. ENHANCE-ME: Maybe decrement should trim a high 0 digit. That would mean a set of strings without high 0s remains so on decrement. But if say infinite high 0s are present then wouldn't want to remove them. Perhaps when a decrement goes to 0 it could be checked for an all-0s accepting state above, and merge with it. This function works by modifying the digits matched in C<$fa>, low to high. For example if the starting state has a transition for low digit 4 then the C<$new_fa> has starting state with transition for digit 5 instead. At a given state there is a certain carry to propagate. At the starting states this is C<$add>, and later it will be smaller. Existing states are reckoned as carry 0. A new state is introduced for combinations of state and non-zero carry reached. Transitions in those new states are based on the originals. Where the original state has digit d the new state has (d+carry) mod 10 and goes to the original successor and new_carry = floor((4+carry)/10). If that new_carry is zero then this is the original successor state since the increment is now fully applied. If new_carry is non-zero then it's another new state for combination of state and carry. In a C any epsilon transitions are stepped across to find what digits in fact occur at the given state. In general an increment +1 propagates only up through digit 9s so that say 991 -> 002 (low to high). Often C<$fa> might match only a few initial 9s and so only a few new states introduced. ENHANCE-ME: Could have some generality by reckoning the carry as an arbitrary key or transform state, and go through $fa by a composition. Any such transformation can be made with a finite set of possible keys. =back =cut sub digits_increment { my ($fa, %options) = @_; ### digits_increment() ... # starting state is flip # in flip 0-bit successor as a 1-bit, and thereafter unchanged # 1-bit successor as a 0-bit, continue flip my $direction = $options{'direction'} || 'hightolow'; my $radix = $options{'radix'} || max($fa->alphabet)+1; my $nine = $radix-1; my $add = $options{'add'} // 1; ### $radix ### $nine my $is_dfa = $fa->isa('FLAT::DFA'); $fa = $fa->MyFLAT::as_nfa->clone; if ($direction eq 'hightolow') { $fa = $fa->reverse; } my %state_and_carry_to_new_state; require Tie::IxHash; tie %state_and_carry_to_new_state, 'Tie::IxHash'; { # states reachable by runs of 9s from starting states my @pending = map {[$_,$add]} $fa->get_starting; while (my $elem = shift @pending) { my ($state, $carry) = @$elem; unless (exists $state_and_carry_to_new_state{"$state,$carry"}) { my ($new_state) = $fa->add_states(1); ### reach: "state=$state new_state=$new_state carry=$carry" $state_and_carry_to_new_state{"$state,$carry"} = $new_state; if ($fa->is_starting($state) && $carry==$add) { $fa->set_starting($new_state); $fa->unset_starting($state); } foreach my $digit (0 .. $nine) { my ($new_carry,$new_digit) = _divrem($digit+$carry, $radix); if ($new_carry) { push @pending, map {[$_,$new_carry]} $fa->successors([$fa->epsilon_closure($state)],$digit); } } } } } ### %state_and_carry_to_new_state while (my ($state_and_carry, $new_state) = each %state_and_carry_to_new_state) { my ($state,$carry) = split /,/, $state_and_carry; ### setup: "state=$state carry=$carry new_state=$new_state" foreach my $digit (0 .. $nine) { my ($new_carry,$new_digit) = _divrem($digit+$carry, $radix); foreach my $successor ($fa->successors([$fa->epsilon_closure($state)], $digit)) { my $new_successor = ($new_carry ? $state_and_carry_to_new_state{"$successor,$new_carry"} : $successor); ### digit: "state=$state carry=$carry digit=$digit successor $successor" ### new : " new state $new_state new_digit=$new_digit with new_carry=$new_carry new_successor=$new_successor" $fa->add_transition ($new_state, $new_successor, $new_digit); } } if ($carry > 0 && $fa->is_accepting($state)) { # 99...99 accepting becomes 00..00 1 accepting, with a new state for # the additional carry ### carry above accepting: "carry=$carry" my $from_state; while ($carry) { $from_state = $new_state; ($new_state) = $fa->add_states(1); ($carry, my $digit) = _divrem($carry, $radix); $fa->add_transition($from_state, $new_state, $digit); ### transition: "$from_state -> $new_state" } $fa->set_accepting($new_state); ### accepting: $new_state } } if (defined $fa->{'name'}) { $fa->{'name'} =~ s{\+(\d+)$}{'+'.($1+1)}e or $fa->{'name'} .= '+1'; } if ($direction eq 'hightolow') { $fa = $fa->reverse; } if ($is_dfa) { $fa = $fa->as_dfa; } return $fa; } # sub successors_through_epsilon { # my ($fa, $state, $symbol) = @_; # return $fa->epsilon_closure($fa->successors($state,$symbol)); # } sub _divrem { my ($n,$d) = @_; my $r = $n % $d; return (($n-$r)/$d, $r); } #------------------------------------------------------------------------------ =item C<$new_lang = $lang-Eskip_initial ()> =item C<$new_lang = $lang-Eskip_final ()> Return a new regular language object, of the same type as C<$lang>, which matches the strings of C<$lang> with 1 initial or final symbol skipped. A string of 1 symbol in C<$lang> becomes the empty string in C<$new_lang>. The empty string in C<$lang> cannot have 1 symbol skipped so is ignored when forming C<$new_lang>. In a C, C works by changing the starting states to the immediate successors of the current starting states. For a C, if this results in multiple starts then they are converted to a single start by the usual C. C works by changing the accepting states to their immediate predecessors. No minimization is performed. It's possible changed starts might leave some states unreachable. It's possible changed accepting could leave various states never reaching an accept. ENHANCE-ME: maybe parameter $n to skip how many. =back =cut sub skip_initial { my ($fa) = @_; ### skip_initial(): $fa my $name = $fa->{'name'}; my $is_dfa = $fa->isa('FLAT::DFA'); $fa = $fa->MyFLAT::as_nfa->clone; # need NFA for new multiple starts my @states = $fa->get_starting; $fa->unset_starting(@states); ### starting: @states $fa->set_starting($fa->successors([$fa->epsilon_closure(@states)])); ### new starting: [ $fa->get_starting ] if ($is_dfa) { $fa = $fa->as_dfa; } if (defined $name) { $name =~ s{ skip initial( (\d+))?$}{' skip initial '.(($2||0)+1)}e or $name .= ' skip initial'; $fa->{'name'} = $name; } return $fa; } { package FLAT::Regex; sub MyFLAT_skip_initial { my $self = shift; $self->_from_op($self->op->MyFLAT_skip_initial(@_)); } sub MyFLAT_skip_final { my $self = shift; $self->_from_op($self->op->MyFLAT_skip_final(@_)); } } { package FLAT::Regex::Op::atomic; sub MyFLAT_skip_initial { my ($self) = @_; ### atomic MyFLAT_skip_initial: $self my $member = $self->members; return __PACKAGE__->new(defined $member && length($member) ? '' # symbol, becomes empty string : undef); # empty str or null regex, becomes null } *MyFLAT_skip_final = \&MyFLAT_skip_initial; # return a list of the initial symbols accepted sub MyFLAT_initial_symbols { my ($self) = @_; my $member = $self->members; return (defined $member && length($member) ? $member : ()); } } { package FLAT::Regex::Op::star; # skip_initial(X*) = skip_initial(X) X* # skip_final(X*) = X* skip_final(X) # or if X has no non-empty strings then return has no non-empty sub MyFLAT_skip_initial { my ($self) = @_; my $member = $self->members; return ($member->has_nonempty_string ? FLAT::Regex::Op::concat->new($member->MyFLAT_skip_initial, $self) : $member); } sub MyFLAT_skip_final { my ($self) = @_; my $member = $self->members; return ($member->has_nonempty_string ? FLAT::Regex::Op::concat->new($self, $member->MyFLAT_skip_final) : $member); } # initial_symbols(X*) = initial_symbols(X) sub MyFLAT_initial_symbols { my ($self) = @_; return $self->members->MyFLAT_initial_symbols; } } { package FLAT::Regex::Op::concat; # skip_initial(X Y Z) = skip_initial(X) Y Z # skip_final(X Y Z) = X Y skip_initial(Z) # any X, or Z, without a non-empty string is skipped sub MyFLAT_skip_initial { my ($self) = @_; my @members = $self->members; # skip initial members which are the empty string and nothing else while (@members >= 2 && ! $members[0]->is_empty && ! $members[0]->has_nonempty_string) { shift @members; } $members[0] = $members[0]->MyFLAT_skip_initial; return (ref $self)->new(@members); } sub MyFLAT_skip_final { my ($self) = @_; my @members = $self->members; # skip trailing members which are the empty string and nothing else while (@members >= 2 && ! $members[-1]->is_empty && ! $members[-1]->has_nonempty_string) { pop @members; } $members[-1] = $members[-1]->MyFLAT_skip_final; return (ref $self)->new(@members); } # initial_symbols(X Y Z) = initial_symbols(X) # or whichever of X,Y,Z first has a non-empty string sub MyFLAT_initial_symbols { my $self = shift; my @ret; foreach my $member ($self->members) { @ret = $member->MyFLAT_initial_symbols and last; } return @ret; } } { package FLAT::Regex::Op::alt; # skip_initial(X | Y) = skip_initial(X) | skip_initial(Y) # skip_final(X | Y) = skip_final(X) | skip_final(Y) sub MyFLAT_skip_initial { my $self = shift; return $self->MyFLAT__map_method('MyFLAT_skip_initial',@_); } sub MyFLAT_skip_final { my $self = shift; return $self->MyFLAT__map_method('MyFLAT_skip_final',@_); } # initial_symbols(X|Y) = union(initial_symbols(X), initial_symbols(Y)) sub MyFLAT_initial_symbols { my $self = shift; my %ret; foreach my $member ($self->members) { foreach my $symbol ($member->MyFLAT_initial_symbols) { $ret{$symbol} = 1; } } return keys %ret; } } { package FLAT::Regex::Op::shuffle; # can this be done better? sub MyFLAT__map_skip { my $self = shift; my $method = shift; my @members = $self->members; my @alts; foreach my $i (0 .. $#members) { if ($members[$i]->has_nonempty_string) { my @skip = @members; $skip[$i] = $skip[$i]->MyFLAT_skip_initial(@_); push @alts, __PACKAGE__->new(@skip); } } return (@alts ? FLAT::Regex::Op::alt->new (@alts) : FLAT::Regex::Op::atomic->new(undef)); } sub MyFLAT_skip_initial { my $self = shift; return $self->MyFLAT__map_skip('MyFLAT_skip_final',@_); } sub MyFLAT_skip_final { my $self = shift; return $self->MyFLAT__map_skip('MyFLAT_skip_final',@_); } # wrong # sub MyFLAT_skip_initial { # my $self = shift; # my %initial; # my @members = $self->members; # foreach $member (@members) { # my @symbols = $members[$i]->MyFLAT_initial_symbols or next; # @initial{@symbols} = (); # hash slice # $member = $member->MyFLAT_skip_initial(@_); # mutate array # } # if (%initial) { # # return (%initial # ? FLAT::Regex::Op::concat->new # (FLAT::Regex::Op::alt->new # (map {FLAT::Regex::Op::atomic->new($_)} keys %initial), # __PACKAGE__->new(@members)) # # : FLAT::Regex::Op::atomic->new(undef)) # # return $self->MyFLAT__map_skip('MyFLAT_skip_final',@_); # } } sub skip_final { my ($fa, %options) = @_; my $name = $fa->{'name'}; my $is_dfa = $fa->isa('FLAT::DFA'); $fa = $fa->MyFLAT::as_nfa ->MyFLAT::reverse ->MyFLAT::skip_initial(%options) ->MyFLAT::reverse; if ($is_dfa) { $fa = $fa->as_dfa; } if (defined $name) { $name =~ s{ skip final( (\d+))?$}{' skip final '.(($1||0)+1)}e or $name .= ' skip final'; $fa->{'name'} = $name; } return $fa; } # sub skip_initial_0s { # my ($fa) = @_; # my $s = $fa->MyFLAT::skip_initial; # } #------------------------------------------------------------------------------ # $fa is a FLAT::NFA or FLAT::DFA which matches strings of bits. # Return a new FLAT (same DFA or NFA) which accepts the same in base-4. # # MAYBE: a general transform of list of symbols -> single symbol # # lowtohigh or hightolow only affects how a high 0-bit # sub binary_to_base4 { my ($fa, %options) = @_; my $direction = $options{'direction'} || 'hightolow'; ### binary_to_base4(): $direction my $name = $fa->{'name'}; my $is_dfa = $fa->isa('FLAT::DFA'); if ($direction eq 'hightolow') { $fa = $fa->reverse; } my $new_fa = FLAT::DFA->new; my @state_to_new_state; my $state_to_new_state = sub { my ($state) = @_; my $new_state = $state_to_new_state[$state]; if (! defined $new_state) { ($new_state) = $new_fa->add_states(1); ### $new_state $state_to_new_state[$state] = $new_state; if ($fa->is_accepting($state)) { $new_fa->set_accepting($new_state); } } return $new_state; }; my @pending = $fa->get_starting; $new_fa->set_starting(map {$state_to_new_state->($_)} $fa->epsilon_closure(@pending)); my @state_done; while (@pending) { my $state = pop @pending; next if $state_done[$state]++; my $new_state = $state_to_new_state->($state); foreach my $bit0 (0,1) { my @successors = $fa->successors([$fa->epsilon_closure($state)], $bit0); foreach my $bit1 (0,1) { my @successors = $fa->successors([$fa->epsilon_closure(@successors)], $bit1); my $digit = $bit0 + 2*$bit1; foreach my $successor (@successors) { my $new_successor = $state_to_new_state->($successor); ### old: "bit0=$bit0 bit1=$bit1 $state to $successor" ### new: "digit=$digit $new_state to $new_successor" $new_fa->add_transition($new_state, $new_successor, $digit); push @pending, $successor; } } } } if ($direction eq 'hightolow') { $new_fa = $new_fa->reverse; } if ($is_dfa) { $new_fa = $new_fa->as_dfa; } if (defined $name) { $name =~ s{ skip final( (\d+))?$}{' skip final '.(($2||0)+1)}e or $name .= ' base-4'; $new_fa->{'name'} = $name; } return $new_fa; } # $fa is a FLAT::NFA or FLAT::DFA. # Return a new FLAT (same DFA or NFA) which accepts blocks of $n many symbols. # # New symbols are string concatenation of the existing, so for example # symbols a,b,c in blocks of 2 would have symbols aa,ab,ba,bb,etc. # # ENHANCE-ME: A separator string, or mapper func for blocks to new symbol. # sub blocks { my ($fa, $n, %options) = @_; my @alphabet = $fa->alphabet; my $num_symbols = scalar(@alphabet); my $num_blocks = $num_symbols ** $n; my @states = $fa->get_states; # clone with no transitions my $new_fa = (ref $fa)->new; $new_fa->add_states($fa->num_states); $new_fa->set_starting($fa->get_starting); $new_fa->set_accepting($fa->get_accepting); foreach my $state (@states) { ### $state foreach my $i (0 .. $num_blocks-1) { ### $i my $q = $i; my $block_symbol = ''; my @successors = ($state); foreach (1 .. $n) { my $r = $q % $num_symbols; $q = ($q-$r) / $num_symbols; my $symbol = $alphabet[$r]; $block_symbol .= $symbol; @successors = $fa->successors([$fa->epsilon_closure(@successors)], $symbol); } foreach my $successor (@successors) { ### new transition: "$state -> $successor label $block_symbol" $new_fa->add_transition($state, $successor, $block_symbol); } } } if (defined(my $name = $fa->{'name'})) { $name .= " blocks $n"; $new_fa->{'name'} = $name; } return $new_fa; } #------------------------------------------------------------------------------ sub as_perl { my ($fa, %options) = @_; my $str = ''; my $varname = $options{'varname'} // 'fa'; $str .= "my \$$varname = " . ref($fa) . "->new;\n"; my @states = sort {$a<=>$b} $fa->get_states; $str .= "\$$varname->add_states(" . scalar(@states) . ");\n"; $str .= "\$$varname->set_starting(" . join(',',$fa->get_starting) . ");\n"; $str .= "\$$varname->set_accepting(" . join(',',$fa->get_accepting) . ");\n"; foreach my $from (@states) { foreach my $to (@states) { my $t = $fa->get_transition($from,$to) // next; my @symbols = map {"'$_'"} $t->alphabet; $str .= "\$$varname->add_transition($from,$to,".join(',',@symbols).");\n"; } } } sub print_perl { my $fa = shift; print $fa->MyFLAT::as_perl(@_); } # $re is a Perl regexp, usually a qr/.../ form. # Return a string which is a FLAT style regexp. # Each char matched by $re is matched by the flat. # There's no scope for multi-char symbols in the flat. # Whitespace chars should not be matched by $re. # Regexp::Common::balanced used here probably needs new enough Perl. # sub perl_regexp_to_flat_regex { my ($re) = @_; my $str = "$re"; # (?opts:...) # x = ignore whitespace and comments # Assume for now that if present then it's whitespace everywhere, # which is not quite right. if ($str =~ s/\(\?\^([a-z]*):/(/) { my $opts = $1; if ($opts =~ /x/) { $str =~ tr/ \t\r\n//d; } } # ^ $ assumed always for now # would need a good idea of the alphabet $str =~ s/[\^\$]//g; # [123] char classes $str =~ s{\[([^\]]*)\]}{ '(' . join('|',split //, $1) . ')' }eg; # (| or |) empty alternative $str =~ s/\(\|/([]|/g; $str =~ s/\|\)/|[])/g; # X+ repeats, possibly nested while ($str =~ s{($RE{'balanced'}{-parens=>'()'}|[^)])\+}{$1$1*}o) {} # X? optional, possibly nested while ($str =~ s{($RE{'balanced'}{-parens=>'()'}|[^)])\?}{($1|[])}o) {} ### $str return $str; } sub flat_regex_to_perl_regexp { my ($str) = @_; $str =~ s/\[\]//g; return qr/^$str$/x; } #------------------------------------------------------------------------------ # Read and Write AT&T FSM Format # # AT&T format is transitions in lines like (and in no particular order) # # FromState ToState InputSymbol OutputSymbol # # States are numbered 0 upwards. 0 is the starting state. The accepting # states are one per line after the transition lines. # # The symbols are non-whitespace, and normally 0 means the "epsilon" # transition of an NFA. # sub ensure_states { my $fa = shift; foreach my $state (@_) { if ((my $more = ($state+1 - $fa->num_states)) > 0) { $fa->add_states($more); } } } # $filename contains an "AT&T" format finite state machine or finite state # transducer. Return a FLAT::NFA of it. Key/value options are # # epsilon_symbol => string, default 0 # no_epsilon_symbol => boolean, default false # # Symbol 0 in the file means an epsilon transition for the NFA and becomes # the FLAT style empty symbol ''. Another symbol can be given with # "epsilon_symbol", or no_epsilon_symbol => 1 for no epsilon. # sub read_att_file { my ($class, $filename, %options) = @_; open my $fh, '<', $filename or croak "Cannot read $filename: $!"; my $fa = $class->MyFLAT::read_att_fh($fh, %options); close $fh or croak "Error reading $filename: $!"; return $fa; } sub read_att_fh { my ($class, $fh, %options) = @_; ### $fh my $fa = $class->new; $fa->add_states(1); $fa->set_starting(0); my $epsilon_symbol = '0'; if (defined $options{'epsilon_symbol'}) { $epsilon_symbol = $options{'epsilon_symbol'}; } if ($options{'no_epsilon_symbol'}) { $epsilon_symbol = undef; } while (defined(my $line = readline $fh)) { chomp $line; if (my ($from,$to,$symbol) = $line =~ /^(\d+)\s+(\d+)\s+(\S+)/) { # next if $symbol eq '@_IDENTITY_SYMBOL_@'; $fa->MyFLAT::ensure_states($from, $to); if (defined $epsilon_symbol && $symbol eq $epsilon_symbol) { $symbol = ''; # FLAT epsilon transition } $symbol =~ s/\./_/g; $symbol =~ s/@/flag/g; ### transition: "$from $to $symbol" $fa->add_transition($from,$to,$symbol); } elsif (my ($state) = $line =~ /^(\d+)$/) { $fa->set_accepting($state); } else { croak "Unrecognised AT&T line: ",$line; } } return $fa; } # $fa is a FLAT::NFA or FLAT::DFA. # Write it in "AT&T" format finite state machine format to $filename. # # $fa must have a single starting state 0, since that is all the file format # allows. Apply some renumbering if necessary before calling here. For an # NFA, there's no need to convert entirely to a DFA, just renumber and make # state 0 have epsilon transitions to the actual desired start states. # # The key/value options are # # epsilon_symbol => string, default 0 # # Epsilon transitions are written to the file as symbol 0 in the usual way # for the file format, by default. The epsilon_symbol option can write # something else. If $fa is a DFA, or if it's an NFA without epsilons, then # this has no effect. # sub write_att_file { my ($fa, $filename, %options) = @_; open my $fh, '>', $filename or croak "Cannot write $filename: $!"; $fa->MyFLAT::write_att_fh ($fh, %options); close $fh or croak "Error writing $filename: $!"; return $fa; } sub write_att_fh { my ($fa, $fh, %options) = @_; my $epsilon_symbol = '0'; if (defined $options{'epsilon_symbol'}) { $epsilon_symbol = $options{'epsilon_symbol'}; } my @states = sort {$a<=>$b} $fa->get_states; my @starting = $fa->get_starting; unless (@starting==1 && $starting[0]==0) { croak "AT&T format must be single starting state 0"; } foreach my $from (@states) { foreach my $symbol (sort $fa->alphabet, '') { my $att_symbol = ($symbol eq '' ? $epsilon_symbol : $symbol); foreach my $to (sort {$a<=>$b} $fa->successors($from,$symbol)) { print $fh "$from\t$to\t$att_symbol\t$att_symbol\n"; } } } foreach my $state (sort {$a<=>$b} $fa->get_accepting) { print $fh $state,"\n"; } } #------------------------------------------------------------------------------ sub _DFA_to_Regex_union { return join('|', grep {defined} @_); } sub _DFA_to_Regex_parens { my ($re) = @_; return ($re eq '' ? '' : "($re)"); } sub _DFA_to_Regex_star { my ($re) = @_; return (defined $re && $re ne '' ? "($re)*" : ''); } # FLAT::DFA sub DFA_to_Regex { my ($fa) = @_; my @edges; my @states = $fa->get_states; my $starting = $states[-1]+1; my $accepting = $states[-1]+2; ### $starting ### $accepting foreach my $to ($fa->get_starting) { $edges[$starting]->[$to] = ''; } foreach my $from ($fa->get_accepting) { $edges[$from]->[$accepting] = ''; } foreach my $symbol ($fa->alphabet) { foreach my $from (@states) { foreach my $to ($fa->successors([$fa->epsilon_closure($from)], $symbol)) { $edges[$from]->[$to] = _DFA_to_Regex_union($edges[$from]->[$to], $symbol); } } } unshift @states, $starting, $accepting; ### @states ### @edges while (@states > 2) { my $s = pop @states; my $star = _DFA_to_Regex_star($edges[$s]->[$s]); ### $s ### $star foreach my $pre_state (@states) { my $pre_re = $edges[$pre_state]->[$s]; ### $pre_state ### $pre_re next unless defined $pre_re; $pre_re = _DFA_to_Regex_parens($pre_re); foreach my $post_state (@states) { my $post_re = $edges[$s]->[$post_state]; ### $post_state ### $post_re next unless defined $post_re; $post_re = _DFA_to_Regex_parens($post_re); $edges[$pre_state]->[$post_state] = _DFA_to_Regex_union($edges[$pre_state]->[$post_state], "$pre_re $star $post_re"); ### now: "$pre_state to $post_state is ".$edges[$pre_state]->[$post_state] } } undef $edges[$s]; } ### stop ... ### @states ### return: $edges[$starting]->[$accepting] my $ret = $edges[$starting]->[$accepting]; return (! defined $ret ? '#' : $ret); } sub FLAT_re_to_xfsm_re { my ($str) = @_; $str =~ tr/()0/[]z/; return $str; } #------------------------------------------------------------------------------ sub FLAT_transition_split { my ($fa, %options) = @_; my @alphabet = $fa->alphabet; my $symbols_func = $options{'symbols_func'} // do { my $symbols_map = $options{'symbols_map'} // {}; sub { my ($symbol) = @_; my $aref = $symbols_map->{$symbol}; return ($aref ? @$aref : ()); } }; my $new = (ref $fa)->new; $new->add_states($fa->num_states); $new->{'name'} = $fa->{'name'}; $new->set_accepting($fa->get_accepting); $new->set_starting($fa->get_starting); foreach my $symbol (@alphabet) { my @new_symbols = $symbols_func->($symbol); if (! @new_symbols) { @new_symbols = ($symbol); # unchanged } foreach my $state ($fa->get_states) { foreach my $old_to ($fa->successors($state, $symbol)) { ### split: "$state to $old_to symbol $symbol becomes ".join(' ',@new_symbols) my $from = $state; foreach my $i (0 .. $#new_symbols - 1) { my ($to) = $new->add_states(1); if ($options{'new_accepting_to'} && $fa->is_accepting($old_to)) { ### new accepting: $to $new->set_accepting($to); } $new->add_transition($from, $to, $new_symbols[$i]); $from = $to; } $new->add_transition($from, $old_to, $new_symbols[-1]); } } } return $new; } #------------------------------------------------------------------------------ sub optional_leading_0s { my ($f) = @_; $f = $f->MyFLAT::as_nfa; my $count = 0; for (;;) { my @starting = $f->get_starting; ### $count ### @starting last if $count == scalar(@starting); $count = scalar(@starting); my @new_starting = $f->successors([$f->epsilon_closure(@starting)],'0'); ### @new_starting $f->set_starting(@new_starting); } return $f->as_dfa; } # func => $func called # ($new_transmute,$new_symbol) = $func->($transmute,$symbol) # # $transmute is a string representing the current transmutation conditions. # sub transmute { my ($fa, %options) = @_; ### transmute() ... my $direction = $options{'direction'} || 'forward'; my $func = $options{'func'}; my $initial = $options{'initial'}; if (! defined $initial) { $initial = ''; } my $is_dfa = $fa->isa('FLAT::DFA'); $fa = $fa->MyFLAT::as_nfa->clone; if ($direction eq 'reverse') { $fa = $fa->reverse; } my @alphabet = $fa->alphabet; my $new_fa = (ref $fa)->new; my @state_and_transmute_to_new_state; my $find_new_state = sub { my ($state, $transmute) = @_; return ($state_and_transmute_to_new_state[$state]->{$transmute} //= do { my ($new_state) = $new_fa->add_states(1); if ($fa->is_starting($state) && $transmute eq $initial) { $new_fa->set_starting($new_state); } if ($fa->is_accepting($state)) { $new_fa->set_accepting($new_state); } $new_state; }); }; my @state_and_transmute_done; my @pending = map {[$_,$initial]} $fa->get_starting; while (my $elem = shift @pending) { my ($state,$transmute) = @$elem; ### elem: "state=$state transmute=$transmute" if ($state_and_transmute_done[$state]->{$transmute}++) { ### already seen ... next; } my $new_from = $find_new_state->($state,$transmute); foreach my $symbol (@alphabet) { my @to = $fa->successors([$fa->epsilon_closure($state)],$symbol) or next; my ($new_transmute,$new_symbol) = $func->($transmute,$symbol) or next; ### for transition: "symbol=$symbol new_symbol=$new_symbol new_transmute=$new_transmute" foreach my $to (@to) { my $new_to = $find_new_state->($to,$new_transmute); $new_fa->add_transition($new_from, $new_to, $new_symbol); push @pending, [$to, $new_transmute]; ### add new: "new_symbol=$new_symbol $new_from -> $new_to" } } } if ($direction eq 'reverse') { $new_fa = $new_fa->reverse; } if ($is_dfa) { $new_fa = $new_fa->as_dfa; } if (defined(my $name = $options{'name'})) { $new_fa->MyFLAT::set_name($name); } return $new_fa; } # add => integer, default 1 # radix => integer>=2, default from alphabet # direction => "hightolow" (default) or "lowtohigh" sub digits_multiply { my ($fa, %options) = @_; my $direction = $options{'direction'} || 'hightolow'; my $radix = $options{'radix'} || max($fa->alphabet)+1; my $mul = $options{'mul'} // 1; my $carry = $options{'add'} // 0; ### $radix ### $mul ### $carry return $fa->MyFLAT::transmute(initial => 0, direction => ($direction eq 'lowtohigh' ? 'forward' : 'reverse'), func => sub { my ($carry,$symbol) = @_; ### func: "carry=$carry symbol $symbol" return _divrem ($symbol*$mul+$carry, $radix); }); } #------------------------------------------------------------------------------ 1; __END__ Math-PlanePath-129/devel/fractions-tree.pl0000644000175000017500000000320011745170634016272 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl fractions-tree.pl # # Print the FractionsTree paths in tree form. # use 5.004; use strict; use Math::PlanePath::FractionsTree; foreach my $tree_type ('Kepler') { print "$tree_type tree\n"; my $path = Math::PlanePath::FractionsTree->new (tree_type => $tree_type); printf "%31s", ''; foreach my $n (1) { my ($x,$y) = $path->n_to_xy($n); print "$x/$y"; } print "\n"; printf "%15s", ''; foreach my $n (2 .. 3) { my ($x,$y) = $path->n_to_xy($n); printf "%-32s", "$x/$y"; } print "\n"; printf "%7s", ''; foreach my $n (4 .. 7) { my ($x,$y) = $path->n_to_xy($n); printf "%-16s", "$x/$y"; } print "\n"; printf "%3s", ''; foreach my $n (8 .. 15) { my ($x,$y) = $path->n_to_xy($n); printf "%-8s", "$x/$y"; } print "\n"; foreach my $n (16 .. 31) { my ($x,$y) = $path->n_to_xy($n); printf "%4s", "$x/$y"; } print "\n"; print "\n"; } exit 0; Math-PlanePath-129/devel/numseq.pl0000644000175000017500000005304213601512317014655 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2017, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::Trig 'pi'; # uncomment this to run the ### lines # use Smart::Comments; { # max turn Left etc require Math::NumSeq::PlanePathTurn; require Math::NumSeq::PlanePathDelta; my $planepath; $planepath = "TerdragonMidpoint,arms=6"; $planepath = "AnvilSpiral,wider=17"; $planepath = "QuintetCurve,arms=4"; $planepath = "OneOfEight,parts=wedge"; $planepath = "LCornerTree,parts=diagonal-1"; $planepath = "UlamWarburton,parts=octant_up"; $planepath = "TriangularHypot,points=hex_rotated"; $planepath = "TriangularHypot,points=hex_centred"; $planepath = "TriangularHypot,points=hex"; $planepath = "TriangularHypot,points=even"; $planepath = "PixelRings"; $planepath = "FilledRings"; $planepath = "MultipleRings,step=9,shape=polygon,n_start=0"; $planepath = "ChanTree,k=11,reduced=1"; $planepath = "DigitGroups,radix=5"; $planepath = "CfracDigits,radix=37"; $planepath = "GrayCode,radix=37"; $planepath = "CellularRule,rule=8"; $planepath = "LCornerTree,parts=1"; $planepath = "KochCurve"; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => $planepath, turn_type => 'TTurn3'); # $planepath = "FractionsTree"; # my $seq = Math::NumSeq::PlanePathDelta->new (planepath => $planepath, # delta_type => 'Dir4'); my $max = -99; my $min = 99; my $prev_i = undef; my %seen; for (1 .. 1000000) { my ($i, $value) = $seq->next; if (! defined $i) { print "no more values after i=$prev_i\n"; last; } # $value = -$value; next unless $value; if (! $seen{$value}++) { printf "%d %s new value\n", $i, $value; } # if ($value > $max) { # printf "%d %.5f new max\n", $i, $value; # $max = $value; # } # if ($value < $min) { # printf "%d %.5f new min\n", $i, $value; # $min = $value; # } $prev_i = $i; } exit 0; } { # when X neg, Y neg require Math::NumSeq::PlanePathCoord; my $planepath; $planepath = "AR2W2Curve,start_shape=A2rev"; $planepath = "BetaOmega,arms=1"; $planepath = "Math::PlanePath::SierpinskiArrowhead"; $planepath = "Math::PlanePath::FlowsnakeCentres,arms=1"; $planepath = "GosperSide"; $planepath = "FlowsnakeCentres,arms=3"; $planepath = "HexSpiral,wider=10"; $planepath = "Math::PlanePath::QuintetCentres,arms=1"; $planepath = "Math::PlanePath::R5DragonCurve,arms=1"; $planepath = "Math::PlanePath::R5DragonMidpoint,arms=2"; $planepath = "Math::PlanePath::AlternatePaper,arms=5"; $planepath = "ComplexPlus"; print "$planepath\n"; my $seq = Math::NumSeq::PlanePathCoord->new (planepath => $planepath); my $path = $seq->{'planepath_object'}; my ($x_negative_at_n, $y_negative_at_n, $sum_negative_at_n); for (my $n = $path->n_start; ; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($x < 0 && ! defined $x_negative_at_n) { $x_negative_at_n = $n; print "X negative $x_negative_at_n\n"; } if ($y < 0 && ! defined $y_negative_at_n) { $y_negative_at_n = $n; print "Y negative $y_negative_at_n\n"; } my $sum = $x+$y; if ($sum < 0 && ! defined $sum_negative_at_n) { $sum_negative_at_n = $n; print "Sum negative $sum_negative_at_n\n"; } last if defined $x_negative_at_n && defined $y_negative_at_n && defined $sum_negative_at_n; } exit 0; } { require Math::NumSeq::PlanePathCoord; foreach my $path_type (@{Math::NumSeq::PlanePathCoord->parameter_info_array->[0]->{'choices'}}) { my $class = "Math::PlanePath::$path_type"; ### $class eval "require $class; 1" or die; my @pinfos = $class->parameter_info_list; my $params = parameter_info_list_to_parameters(@pinfos); PAREF: foreach my $paref (@$params) { ### $paref my $path = $class->new(@$paref); my $seq = Math::NumSeq::PlanePathCoord->new(planepath_object => $path, coordinate_type => 'Sum'); foreach (1 .. 10) { $seq->next; } foreach (1 .. 1000) { my ($i, $value) = $seq->next; if (! defined $i || $value < $i) { next PAREF; } } print "$path_type ",join(',',@$paref),"\n"; } } exit 0; sub parameter_info_list_to_parameters { my @parameters = ([]); foreach my $info (@_) { info_extend_parameters($info,\@parameters); } return \@parameters; } sub info_extend_parameters { my ($info, $parameters) = @_; my @new_parameters; if ($info->{'name'} eq 'planepath') { my @strings; foreach my $choice (@{$info->{'choices'}}) { my $path_class = "Math::PlanePath::$choice"; Module::Load::load($path_class); my @parameter_info_list = $path_class->parameter_info_list; if ($path_class->isa('Math::PlanePath::Rows')) { push @parameter_info_list,{ name => 'width', type => 'integer', width => 3, default => '1', minimum => 1, }; } if ($path_class->isa('Math::PlanePath::Columns')) { push @parameter_info_list, { name => 'height', type => 'integer', width => 3, default => '1', minimum => 1, }; } my $path_parameters = parameter_info_list_to_parameters(@parameter_info_list); ### $path_parameters foreach my $aref (@$path_parameters) { my $str = $choice; while (@$aref) { $str .= "," . shift(@$aref) . '=' . shift(@$aref); } push @strings, $str; } } ### @strings foreach my $p (@$parameters) { foreach my $choice (@strings) { push @new_parameters, [ @$p, $info->{'name'}, $choice ]; } } @$parameters = @new_parameters; return; } if ($info->{'name'} eq 'arms') { print " skip parameter $info->{'name'}\n"; return; } if ($info->{'choices'}) { my @new_parameters; foreach my $p (@$parameters) { foreach my $choice (@{$info->{'choices'}}) { next if ($info->{'name'} eq 'rotation_type' && $choice eq 'custom'); push @new_parameters, [ @$p, $info->{'name'}, $choice ]; } } @$parameters = @new_parameters; return; } if ($info->{'type'} eq 'boolean') { my @new_parameters; foreach my $p (@$parameters) { foreach my $choice (0, 1) { push @new_parameters, [ @$p, $info->{'name'}, $choice ]; } } @$parameters = @new_parameters; return; } if ($info->{'type'} eq 'integer' || $info->{'name'} eq 'multiples') { ### $info my $max = ($info->{'minimum'}||-5)+10; if ($info->{'name'} eq 'straight_spacing') { $max = 2; } if ($info->{'name'} eq 'diagonal_spacing') { $max = 2; } if ($info->{'name'} eq 'radix') { $max = 17; } if ($info->{'name'} eq 'realpart') { $max = 3; } if ($info->{'name'} eq 'wider') { $max = 3; } if ($info->{'name'} eq 'modulus') { $max = 32; } if ($info->{'name'} eq 'polygonal') { $max = 32; } if ($info->{'name'} eq 'factor_count') { $max = 12; } if (defined $info->{'maximum'} && $max > $info->{'maximum'}) { $max = $info->{'maximum'}; } if ($info->{'name'} eq 'power' && $max > 6) { $max = 6; } my @new_parameters; foreach my $choice (($info->{'minimum'}||0) .. $max) { foreach my $p (@$parameters) { push @new_parameters, [ @$p, $info->{'name'}, $choice ]; } } @$parameters = @new_parameters; return; } if ($info->{'name'} eq 'fraction') { ### fraction ... my @new_parameters; foreach my $p (@$parameters) { my $radix = p_radix($p) || die; foreach my $den (995 .. 1021) { next if $den % $radix == 0; my $choice = "1/$den"; push @new_parameters, [ @$p, $info->{'name'}, $choice ]; } foreach my $num (2 .. 10) { foreach my $den ($num+1 .. 15) { next if $den % $radix == 0; next unless _coprime($num,$den); my $choice = "$num/$den"; push @new_parameters, [ @$p, $info->{'name'}, $choice ]; } } } @$parameters = @new_parameters; return; } print " skip parameter $info->{'name'}\n"; } } { # max Dir4 require Math::BaseCnv; # print 4-atan2(2,1)/atan2(1,1)/2,"\n"; require Math::NumSeq::PlanePathDelta; require Math::NumSeq::PlanePathTurn; my $realpart = 3; my $radix = $realpart*$realpart + 1; my $planepath; $planepath = "RationalsTree,tree_type=Drib"; $planepath = "GosperReplicate"; $planepath = "QuintetReplicate"; $planepath = "RationalsTree,tree_type=HCS"; $planepath = "ToothpickReplicate,parts=1"; $planepath = "CfracDigits,radix=2"; $planepath = "DiagonalRationals,direction=up"; $planepath = "OneOfEight,parts=wedge"; $planepath = "QuadricIslands"; $planepath = "WunderlichSerpentine"; $planepath = "ComplexMinus,realpart=3"; $planepath = "UlamWarburton,parts=4"; $planepath = "ToothpickTreeByCells,parts=two_horiz"; $planepath = "LCornerTreeByCells,parts=octant_up+1"; $planepath = "ChanTree,k=5"; $planepath = "ComplexPlus,realpart=2"; $planepath = "CfracDigits,radix=".($radix-1); $planepath = "GosperIslands"; $planepath = "ImaginaryHalf"; # ,digit_order=XnXY"; $planepath = "SquareReplicate"; $planepath = "GrayCode,radix=$radix,apply_type=Ts"; $planepath = "SquareReplicate"; $planepath = "ToothpickTree,parts=2"; $planepath = "ToothpickUpist"; $planepath = "CornerReplicate"; $radix = 3; $planepath = "ZOrderCurve,radix=$radix"; $planepath = "LCornerReplicate"; $planepath = "LCornerTree,parts=diagonal-1"; $planepath = "PowerArray,radix=$radix"; $planepath = "DigitGroups,radix=$radix"; $planepath = "FactorRationals,sign_encoding=negabinary"; $planepath = "GcdRationals,pairs_order=diagonals_up"; $planepath = "LTiling"; $planepath = "TriangularHypot,points=hex_rotated"; $planepath = "Hypot,points=all"; $planepath = "MultipleRings,step=3"; $planepath = "ArchimedeanChords"; $planepath = "DragonMidpoint"; $planepath = "HexSpiral,wider=1"; $planepath = "AlternatePaper"; $planepath = "VogelFloret"; $planepath = "MultipleRings,step=6,ring_shape=polygon"; $planepath = "PythagoreanTree,coordinates=MC,tree_type=UMT"; $planepath = "R5DragonMidpoint"; $planepath = "OctagramSpiral"; $planepath = "Columns,height=6"; $planepath = "SacksSpiral"; $planepath = "CellularRule,rule=6"; $planepath = "Z2DragonCurve"; $planepath = "WythoffPreliminaryTriangle"; $planepath = "UlamWarburton,parts=octant"; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => $planepath, # delta_type => 'dX', delta_type => 'Dir4', # delta_type => 'dTRadius', # delta_type => 'dRSquared', # delta_type => 'dDiffXY', # delta_type => 'TDir6', # delta_type => 'dAbsDiff', ); my $dx_seq = Math::NumSeq::PlanePathDelta->new (planepath => $planepath, delta_type => 'dX'); my $dy_seq = Math::NumSeq::PlanePathDelta->new (planepath => $planepath, delta_type => 'dY'); # my $seq = Math::NumSeq::PlanePathTurn->new (planepath => $planepath, # turn_type => 'Turn4', # ); # my $dx_seq = Math::NumSeq::PlanePathCoord->new (planepath => $planepath, # coordinate_type => 'X'); # my $dy_seq = Math::NumSeq::PlanePathCoord->new (planepath => $planepath, # coordinate_type => 'Y'); my $min = 99; my $max = -99; for (1 .. 10_000_000) { my ($i, $value) = $seq->next; # $seq->seek_to_i(2*$i+2); if ($value > $max) { my $dx = $dx_seq->ith($i); my $dy = $dy_seq->ith($i); my $prev_dx = $dx_seq->ith($i-1) // 'u'; my $prev_dy = $dy_seq->ith($i-1) // 'u'; my $ri = Math::BaseCnv::cnv($i,10,$radix); my $rdx = Math::BaseCnv::cnv($dx,10,$radix); my $rdy = Math::BaseCnv::cnv($dy,10,$radix); my $f = $dy && $dx/$dy; $max = $value; printf "max i=%d[%s] %.5f px=%s,py=%s dx=%s,dy=%s[%s,%s] %.3f\n", $i,$ri, $value, $prev_dx,$prev_dy, $dx,$dy, $rdx,$rdy, $f; } if ($value < $min) { my $dx = $dx_seq->ith($i); my $dy = $dy_seq->ith($i); my $prev_dx = $dx_seq->ith($i-1) // 'u'; my $prev_dy = $dy_seq->ith($i-1) // 'u'; my $ri = Math::BaseCnv::cnv($i,10,$radix); my $rdx = Math::BaseCnv::cnv($dx,10,$radix); my $rdy = Math::BaseCnv::cnv($dy,10,$radix); my $f = $dy && $dx/$dy; $min = $value; printf " min i=%d[%s] %.5f px=%s,py=%s dx=%s,dy=%s %.3f\n", $i,$ri, $value, $prev_dx,$prev_dy, $dx,$dy, $f; my $slope_dy_dx = ($dx == 0 ? 0 : $dy/$dx); printf " dy/dx=%.5f\n", $slope_dy_dx; } } exit 0; } { # dx,dy seen require Math::NumSeq::PlanePathCoord; my $planepath = "CellularRule,rule=2"; $planepath = "AR2W2Curve,start_shape=A2rev"; $planepath = "BetaOmega,arms=1"; $planepath = "Math::PlanePath::SierpinskiArrowhead"; $planepath = "PixelRings"; $planepath = "DiamondArms"; $planepath = "Math::PlanePath::QuintetCurve,arms=1"; $planepath = "Math::PlanePath::GreekKeySpiral,turns=3"; $planepath = "WunderlichSerpentine,radix=5,serpentine_type=coil"; $planepath = "KnightSpiral"; print "$planepath\n"; my $seq = Math::NumSeq::PlanePathCoord->new (planepath => $planepath); my $path = $seq->{'planepath_object'}; my %seen_dxdy; for (my $n = $path->n_start; ; $n++) { my ($dx,$dy) = $path->n_to_dxdy($n); unless ($seen_dxdy{"$dx,$dy"}++) { my $desc = ($dx == 1 && $dy == 0 ? 'E' : $dx == 2 && $dy == 0 ? 'E' : $dx == -1 && $dy == 0 ? 'W' : $dx == -2 && $dy == 0 ? 'W' : $dx == 0 && $dy == 1 ? 'N' : $dx == 0 && $dy == -1 ? 'S' : $dx == 1 && $dy == 1 ? 'NE' : $dx == -1 && $dy == 1 ? 'NW' : $dx == 1 && $dy == -1 ? 'SE' : $dx == -1 && $dy == -1 ? 'SW' : ''); print "$dx,$dy, # $desc N=$n\n"; } } exit 0; } { # min/max PlanePathCoord require Math::BaseCnv; require Math::NumSeq::PlanePathCoord; my $realpart = 3; my $radix = $realpart*$realpart + 1; my $planepath; $planepath = "MultipleRings,step=3"; $planepath = "MultipleRings,step=3,ring_shape=polygon"; my $seq = Math::NumSeq::PlanePathCoord->new (planepath => $planepath, coordinate_type => 'AbsDiff'); my $path = $seq->{'planepath_object'}; my $min = 99; my $max = -99; for (1 .. 10000000) { my ($i, $value) = $seq->next; # if ($value > $max) { # my $dx = $dx_seq->ith($i); # my $dy = $dy_seq->ith($i); # my $prev_dx = $dx_seq->ith($i-1) // 'u'; # my $prev_dy = $dy_seq->ith($i-1) // 'u'; # my $ri = Math::BaseCnv::cnv($i,10,$radix); # my $rdx = Math::BaseCnv::cnv($dx,10,$radix); # my $rdy = Math::BaseCnv::cnv($dy,10,$radix); # my $f = $dy && $dx/$dy; # $max = $value; # printf "max i=%d[%s] %.5f px=%s,py=%s dx=%s,dy=%s[%s,%s] %.3f\n", # $i,$ri, $value, # $prev_dx,$prev_dy, # $dx,$dy, $rdx,$rdy, $f; # } if ($value < $min) { my ($x,$y) = $path->n_to_xy($i); $min = $value; my $ri = Math::BaseCnv::cnv($i,10,$radix); printf " min i=%d[%s] %.5f x=%s,y=%s\n", $i,$ri, $value, $x,$y; } } exit 0; } { require Math::NumSeq::PlanePathDelta; for (my $a = 0; $a <= 360; $a += 5) { print "$a ",Math::NumSeq::PlanePathDelta::_dir360_to_tdir6($a),"\n"; } exit 0; } { # kronecker cf A215200 require Math::NumSeq::PlanePathCoord; foreach my $n (1 .. 10) { foreach my $k (1 .. $n) { my $x = $n - $k; my $y = $k; my $kron = Math::NumSeq::PlanePathCoord::_kronecker_symbol($x,$y); printf "%3d,", $kron; } print "\n"; } exit 0; } { # axis increasing my $radix = 4; my $rsquared = $radix * $radix; my $re = '.' x $radix; require Math::NumSeq::PlanePathN; my $planepath; $planepath = "AlternatePaperMidpoint,arms=7"; $planepath = "ImaginaryBase,radix=37"; $planepath = "ImaginaryHalf,radix=37"; $planepath = "DekkingCurve"; $planepath = "DekkingCentres"; $planepath = "LCornerReplicate"; $planepath = "LCornerTree,parts=3"; LINE_TYPE: foreach my $line_type ('X_axis', 'Y_axis', 'X_neg', 'Y_neg', 'Diagonal_SE', 'Diagonal_SW', 'Diagonal_NW', 'Diagonal', ) { my $seq = Math::NumSeq::PlanePathN->new ( planepath => $planepath, line_type => $line_type, ); ### $seq my $i_start = $seq->i_start; my $prev_value = -1; my $prev_i = -1; my $i_limit = 10000; my $i_end = $i_start + $i_limit; for my $i ($i_start .. $i_end) { my $value = $seq->ith($i); next if ! defined $value; ### $value if ($value <= $prev_value) { # print "$line_type_type decrease at i=$i value=$value cf prev=$prev\n"; my $path = $seq->{'planepath_object'}; my ($prev_x,$prev_y) = $path->n_to_xy($prev_value); my ($x,$y) = $path->n_to_xy($value); print "$line_type not N=$prev_value $prev_x,$prev_y N=$value $x,$y\n"; next LINE_TYPE; } $prev_i = $i; $prev_value = $value; } print "$line_type all increasing (to i=$prev_i)\n"; } exit 0; } { # PlanePathCoord increasing require Math::NumSeq::PlanePathCoord; my $planepath; $planepath = "SierpinskiTriangle,align=right"; COORDINATE_TYPE: foreach my $coordinate_type ('BitAnd', 'BitOr', 'BitXor', ) { my $seq = Math::NumSeq::PlanePathCoord->new ( planepath => $planepath, coordinate_type => $coordinate_type, ); ### $seq my $i_start = $seq->i_start; my $prev_value; my $prev_i; my $i_limit = 100000; my $i_end = $i_start + $i_limit; for my $i ($i_start .. $i_end) { my $value = $seq->ith($i); next if ! defined $value; ### $i ### $value if (defined $prev_value && $value < $prev_value) { # print "$coordinate_type_type decrease at i=$i value=$value cf prev=$prev\n"; my $path = $seq->{'planepath_object'}; my ($prev_x,$prev_y) = $path->n_to_xy($prev_value); my ($x,$y) = $path->n_to_xy($value); print "$coordinate_type not i=$i value=$value cf prev_value=$prev_value\n"; next COORDINATE_TYPE; } $prev_i = $i; $prev_value = $value; } print "$coordinate_type all increasing (to i=$prev_i)\n"; } exit 0; } { require Math::BigInt; my $x = Math::BigInt->new(8); my $y = Math::BigInt->new(-2); $x = (8); $y = (-2); my $z = $x ^ $y; print "$z\n"; printf "%b\n", $z & 0xFFF; if ((($x<0) ^ ($y<0)) != ($z<0)) { $z = Math::BigInt->new("$z"); $z = ($z - (1<<63)) + -(1<<63); } print "$z\n"; printf "%b\n", $z & 0xFFF; sub sign_extend { my ($n) = @_; return ($n - (1<<63)) + -(1<<63); } exit 0; } { my $pi = pi(); my %seen; foreach my $x (0 .. 100) { foreach my $y (0 .. 100) { my $factor; $factor = 1; $factor = sqrt(3); # next unless ($x&1) == ($y&1); $factor = sqrt(8); my $radians = atan2($y*$factor, $x); my $degrees = $radians / $pi * 180; my $frac = $degrees - int($degrees); if ($frac > 0.5) { $frac -= 1; } if ($frac < -0.5) { $frac += 1; } my $int = $degrees - $frac; next if $seen{$int}++; if ($frac > -0.001 && $frac < 0.001) { print "$x,$y $int ($degrees)\n"; } } } exit 0; } Math-PlanePath-129/devel/biguv.pl0000644000175000017500000000207311753117277014473 0ustar gggg#!/usr/bin/perl -w # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Inline 'C'; use Math::BigInt try => 'GMP'; # uncomment this to run the ### lines use Smart::Comments; my $big = - Math::BigInt->new(2) ** 65; ### $big print "big ",ref $big,"\n"; my $uv = touv($big); print "touv $uv\n"; my $nv = $big->numify; print "as_number $nv\n"; exit 0; __END__ __C__ unsigned touv(unsigned n) { return n; } Math-PlanePath-129/devel/t-square.pl0000644000175000017500000000467212255722606015123 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::PlanePath::Base::Digits 'round_down_pow'; { require Image::Base::GD; my $width = 810; my $height = 810; my $image = Image::Base::GD->new (-width => $width, -height => $height); $image->rectangle (0,0, $width-1,$height-1, 'black'); my $foreground = 'white'; # *---------* # | | # *----* . | # | # *----* *----* # | | # * * my $recurse; $recurse = sub { my ($x,$y, $dx,$dy, $level) = @_; if (--$level < 0) { $image->line($x,$y, $x+$dx,$y+$dy, $foreground); $x += $dx; $y += $dy; ($dx,$dy) = (-$dy,$dx); # rotate +90 $image->line($x,$y, $x+$dx,$y+$dy, $foreground); } else { $dx /= 2; $dy /= 2; $image->line($x,$y, $x+$dx,$y+$dy, $foreground); $x += $dx; $y += $dy; ($dx,$dy) = ($dy,-$dx); # rotate -90 $recurse->($x,$y, $dx,$dy, $level); $x += $dx; $y += $dy; ($dx,$dy) = (-$dy,$dx); # rotate +90 $x += $dx; $y += $dy; $recurse->($x,$y, $dx,$dy, $level); $x += $dx; $y += $dy; ($dx,$dy) = (-$dy,$dx); # rotate +90 $x += $dx; $y += $dy; $recurse->($x,$y, $dx,$dy, $level); $x += $dx; $y += $dy; ($dx,$dy) = (-$dy,$dx); # rotate +90 $x += $dx; $y += $dy; ($dx,$dy) = ($dy,-$dx); # rotate -90 $image->line($x,$y, $x+$dx,$y+$dy, $foreground); } }; my $scale = 2; my ($pow,$exp) = round_down_pow($height/$scale, 2); foreach my $level (0 .. $exp) { my $len = 2**$level * $scale; $recurse->(0, $height-1 - $len, $len,0, $level); } $image->save('/tmp/x.png'); system('xzgv /tmp/x.png'); exit 0; } Math-PlanePath-129/devel/exe-complex-minus.c0000644000175000017500000000640211701770574016543 0ustar gggg/* Copyright 2012 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . */ #include #include #include #include typedef unsigned long my_unsigned; typedef long long my_signed; #define MY_SIGNED_ABS llabs #define HYPOT_LIMIT 0x7FFFFFFF char * to_base (unsigned long long n, int radix) { static char str[256]; static char dstr[256]; int pos = sizeof(str)-1; do { int digit = n % radix; n /= radix; sprintf (dstr, "[%d]", digit); int dlen = strlen(dstr); pos -= dlen; memcpy (str+pos, dstr, dlen); } while (n); return str+pos; } int base_len (unsigned long long n, int radix) { int len = 0; while (n) { n /= radix; len++; } return len; } int main (void) { int realpart, level; for (realpart = 3; realpart < 10; realpart++) { int norm = realpart*realpart + 1; int level_limit = 20; if (realpart == 2) level_limit = 10; if (realpart == 3) level_limit = 9; if (realpart == 4) level_limit = 9; for (level = 0; level < level_limit; level++) { unsigned long long min_h = ~0ULL; my_unsigned min_n = 0; my_signed min_x = 0; my_signed min_y = 0; { my_unsigned lo = pow(norm, level); my_unsigned hi = lo * norm; printf ("%2d lo=%lu hi=%lu\n", level, lo, hi); my_unsigned n; for (n = lo; n < hi; n++) { my_signed x = 0; my_signed y = 0; my_signed bx = 1; my_signed by = 0; my_unsigned digits = n; while (digits != 0) { int digit = digits % norm; digits /= norm; x += digit * bx; y += digit * by; /* (bx,by) = (bx + i*by)*(i-$realpart) */ my_signed new_bx = bx*-realpart - by; my_signed new_by = bx + by*-realpart; bx = new_bx; by = new_by; } unsigned long long abs_x = MY_SIGNED_ABS(x); unsigned long long abs_y = MY_SIGNED_ABS(y); if (abs_x > HYPOT_LIMIT || abs_y > HYPOT_LIMIT) { continue; } unsigned long long h = abs_x*abs_x + abs_y*abs_y; /* printf ("%2d %lu %Ld,%Ld %LX\n", level, n, x,y, h); */ if (h < min_h) { min_h = h; min_n = n; min_x = abs_x; min_y = abs_y; } } } /* printf ("%lX %Ld,%Ld %s\n", min_n, min_x,min_y, */ /* binary(min_h)); */ printf ("%2d", level); printf (" %s [%d]", to_base(min_h,norm), base_len(min_h,norm)); printf ("\n"); /* printf ("\n"); */ } } return 0; } Math-PlanePath-129/devel/gcd-rationals-integer.pl0000644000175000017500000000325011702424166017527 0ustar gggg#!/usr/bin/perl -w # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min', 'max'; use Math::PlanePath::GcdRationals; my $height = 20; my $path = Math::PlanePath::GcdRationals->new; my $n_lo = $path->n_start; my $n_hi = $height*($height+1)/2 - 1; my @array; foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy ($n); my $int = int($x/$y); if ($int >= 10) { $int = 'z' } $array[$y]->[$x] = $int; } my $cell_width = max (map {length} grep {defined} map {@$_} grep {defined} @array); foreach my $y (reverse 1 .. $#array) { foreach my $x (1 .. $#{$array[$y]}) { my $int = $array[$y]->[$x]; if (! defined $int) { $int = ''; } printf '%*s', $cell_width, $int; } print "\n"; } print "\n"; foreach my $y (reverse 1 .. 20) { foreach my $x (1 .. $y) { my $int = Math::PlanePath::GcdRationals::_gcd($x,$y) - 1; if ($int >= 10) { $int = 'z' } print "$int"; } print "\n"; } exit 0; Math-PlanePath-129/devel/cont-frac.pl0000644000175000017500000000257411535000617015224 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.006; use strict; use warnings; use POSIX 'fmod'; use Math::Libm 'M_PI', 'M_E', 'hypot'; use Math::Trig 'pi'; use POSIX; # sqrt(pi*e/2) = 1 / (1+ 1/(1 + 2/(1+ 3/(1 + 4/(...))))) { use Math::BigFloat; my $rot; $rot = M_PI; $rot = sqrt(17); # $rot = Math::BigFloat->bpi(1000); # PI to 100 digits # $rot = Math::BigFloat->bsqrt(5); # $rot = (Math::BigFloat->bsqrt(5) +1) / 2; $rot = sqrt(M_PI() * M_E() / 2); $rot = 0.5772156649015328606065120; $rot = sqrt(5); foreach (1..30) { my $int = int($rot); my $frac = $rot - $int; print $int,"\n"; $rot = 1/$frac; } # use constant ROTATION => PHI; # use constant ROTATION => exit 0; } Math-PlanePath-129/devel/Makefile0000644000175000017500000000203012530306624014441 0ustar gggg# Copyright 2011, 2012, 2013, 2015 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # CFLAGS = -Wall -O0 -g CFLAGS = -Wall -O2 -DINLINE=inline -g LOADLIBES = -lm size: perl -e '$$/=undef; $$_=<>; \ s{(?<>g; \ s<""><" ">g; \ s{quit.*}{}s; \ s{\n}{}sg; \ print $$_,"\n",length($$_),"\n"' \ . use 5.010; use strict; use warnings; use Math::Factor::XS 0.39 'factors', 'prime_factors'; # version 0.39 for prime_factors() use List::MoreUtils 'uniq'; use Math::PlanePath::CoprimeColumns; { my $coprime = sub { my ($x,$y) = @_; return $x > 0 && Math::PlanePath::CoprimeColumns::_coprime($x,$y); }; foreach my $n (2*2*2*3, 3*3*3*5) { my $tot = Math::PlanePath::CoprimeColumns::_totient($n); my @factors = uniq(prime_factors($n)); my $factors_str = join(',',@factors); print "n=$n totient=$tot factors=$factors_str\n"; my @coprimes = grep {$coprime->($_,$n)} 0 .. $n-1; my @coprime_dots = map {($coprime->($_,$n)?'*':'_') .($_ % 3 == 2 ? ',' : '')} 0 .. $n-1; my $want_str = join(',',@coprimes); my $dots_str = join('',@coprime_dots); print "dots $dots_str\n"; print "want $want_str\n"; my @got; foreach my $i (0 .. $#coprimes) { my $c = $i+1; foreach my $f (@factors) { $c += int($i/($f-1)); } push @got, $c; } my $got_str = join(',',@got); my $diff = ($want_str eq $got_str ? '' : ' ***'); print "got $got_str$diff"; print "\n"; print "\n"; } exit 0; } { require Math::PlanePath::CoprimeColumns; my $n = 0; foreach my $x (3 .. 1000) { foreach my $y (1 .. $x-1) { $n += Math::PlanePath::CoprimeColumns::_coprime($x,$y); } my $square = $x*$x; my $frac = $n / $square; printf "%d %d %d %.3g\n", $x, $n, $square, $frac; } exit 0; } { require Math::PlanePath::CoprimeColumns; foreach my $x (2 .. 100) { my $n = 0; my @list; foreach my $y (1 .. $x-1) { if (Math::PlanePath::CoprimeColumns::_coprime($x,$y)) { $n++; push @list, $y; } } my $c = Math::PlanePath::CoprimeColumns::_totient_count($x); if ($c != $n) { die "x=$x tot $c step $n\n"; } printf "%d %d %s\n", $x, $n, join(',',@list); } exit 0; } sub _coprime { my ($x, $y) = @_; ### _coprime(): "$x,$y" if ($x < $y) { ($x,$y) = ($y,$x); } for (;;) { if ($y <= 0) { return 0; } if ($y == 1) { return 1; } $x %= $y; ($x,$y) = ($y,$x); } } Math-PlanePath-129/devel/complex-revolving.pl0000644000175000017500000000703511703471336017034 0ustar gggg#!/usr/bin/perl -w # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min', 'max'; # uncomment this to run the ### lines use Smart::Comments; { # min/max for level $|=1; require Math::PlanePath::ComplexRevolving; my $path = Math::PlanePath::ComplexRevolving->new; my $prev_max = 1; my @min = (1); for (my $level = 1; $level < 25; $level++) { my $n_start = 2**($level-1); my $n_end = 2**$level; my $min_hypot = 128*$n_end*$n_end; my $min_x = 0; my $min_y = 0; my $min_pos = ''; my $max_hypot = 0; my $max_x = 0; my $max_y = 0; my $max_pos = ''; # print "level $level n=$n_start .. $n_end\n"; foreach my $n ($n_start .. $n_end) { my ($x,$y) = $path->n_to_xy($n); my $h = $x*$x + $y*$y; if ($h < $min_hypot) { $min_hypot = $h; $min_pos = "$x,$y"; } if ($h > $max_hypot) { $max_hypot = $h; $max_pos = "$x,$y"; } } # print "$min_hypot,"; $min[$level] = $min_hypot; # print " min $min_hypot at $min_x,$min_y\n"; # print " max $max_hypot at $max_x,$max_y\n"; { my $factor = $min_hypot / $min[$level-1]; my $factor4_level = max($level-4,0); my $factor4 = $min_hypot / $min[max($factor4_level)]; # printf " min r^2 %5d", $min_hypot; printf " 0b%-20b", $min_hypot; # print " at $min_pos"; # print " factor $factor"; # print " factor[$factor4_level] $factor4"; # print " cf formula ", 2**($level-7), "\n"; print "\n"; } # { # my $factor = $max_hypot / $prev_max; # print " max r^2 $max_hypot 0b".sprintf('%b',$max_hypot)." at $max_pos factor $factor\n"; # } $prev_max = $max_hypot; } exit 0; } { require Math::PlanePath::ComplexRevolving; require Image::Base::Text; my $realpart = 2; my $radix = $realpart*$realpart + 1; my %seen; my $isize = 20; my $image = Image::Base::Text->new (-width => 2*$isize+1, -height => 2*$isize+1); foreach my $n (0 .. $radix**6) { my $x = 0; my $y = 0; my $bx = 1; my $by = 0; foreach my $digit (digits($n,$radix)) { if ($digit) { $x += $digit * $bx; $y += $digit * $by; ($bx,$by) = (-$by,$bx); # (bx+by*i)*i = bx*i - by, rotate +90 } # (bx,by) = (bx + i*by)*(i+$realpart) # ($bx,$by) = ($realpart*$bx - $by, $bx + $realpart*$by); } my $dup = ($seen{"$x,$y"}++ ? " dup" : ""); printf "%4d %2d,%2d%s\n", $n, $x,$y, $dup; if ($x > -$isize && $x < $isize && $y > -$isize && $y < $isize) { $image->xy($x+$isize,$y+$isize,'*'); } } $image->xy(0+$isize,0+$isize,'+'); $image->save_fh(\*STDOUT); exit 0; sub digits { my ($n, $radix) = @_; my @ret; while ($n) { push @ret, $n % $radix; $n = int($n/$radix); } return @ret; } } Math-PlanePath-129/devel/exe-complex-plus.c0000644000175000017500000000640011702125647016365 0ustar gggg/* Copyright 2012 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . */ #include #include #include #include typedef unsigned long my_unsigned; typedef long long my_signed; #define MY_SIGNED_ABS llabs #define HYPOT_LIMIT 0x7FFFFFFF char * to_base (unsigned long long n, int radix) { static char str[256]; static char dstr[256]; int pos = sizeof(str)-1; do { int digit = n % radix; n /= radix; sprintf (dstr, "[%d]", digit); int dlen = strlen(dstr); pos -= dlen; memcpy (str+pos, dstr, dlen); } while (n); return str+pos; } int base_len (unsigned long long n, int radix) { int len = 0; while (n) { n /= radix; len++; } return len; } int main (void) { int realpart, level; for (realpart = 3; realpart < 10; realpart++) { int norm = realpart*realpart + 1; int level_limit = 20; if (realpart == 2) level_limit = 10; if (realpart == 3) level_limit = 9; if (realpart == 4) level_limit = 9; for (level = 0; level < level_limit; level++) { unsigned long long min_h = ~0ULL; my_unsigned min_n = 0; my_signed min_x = 0; my_signed min_y = 0; { my_unsigned lo = pow(norm, level); my_unsigned hi = lo * norm; printf ("%2d lo=%lu hi=%lu\n", level, lo, hi); my_unsigned n; for (n = lo; n < hi; n++) { my_signed x = 0; my_signed y = 0; my_signed bx = 1; my_signed by = 0; my_unsigned digits = n; while (digits != 0) { int digit = digits % norm; digits /= norm; x += digit * bx; y += digit * by; /* (bx,by) = (bx + i*by)*(i+$realpart) */ my_signed new_bx = bx*realpart - by; my_signed new_by = bx + by*realpart; bx = new_bx; by = new_by; } unsigned long long abs_x = MY_SIGNED_ABS(x); unsigned long long abs_y = MY_SIGNED_ABS(y); if (abs_x > HYPOT_LIMIT || abs_y > HYPOT_LIMIT) { continue; } unsigned long long h = abs_x*abs_x + abs_y*abs_y; /* printf ("%2d %lu %Ld,%Ld %LX\n", level, n, x,y, h); */ if (h < min_h) { min_h = h; min_n = n; min_x = abs_x; min_y = abs_y; } } } /* printf ("%lX %Ld,%Ld %s\n", min_n, min_x,min_y, */ /* binary(min_h)); */ printf ("%2d", level); printf (" %s [%d]", to_base(min_h,norm), base_len(min_h,norm)); printf ("\n"); /* printf ("\n"); */ } } return 0; } Math-PlanePath-129/devel/run.pl0000644000175000017500000004631413773771520014172 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.006; use strict; use warnings; use POSIX qw(floor ceil); use Math::Libm; use List::Util qw(min max); use Module::Load; use Math::PlanePath::Base::Digits 'round_down_pow'; # uncomment this to run the ### lines # use Smart::Comments; { my $path_class; $path_class = 'Math::PlanePath::QuadricCurve'; $path_class = 'Math::PlanePath::LTiling'; $path_class = 'Math::PlanePath::TerdragonMidpoint'; $path_class = 'Math::PlanePath::SierpinskiArrowhead'; $path_class = 'Math::PlanePath::QuintetCentres'; $path_class = 'Math::PlanePath::HIndexing'; $path_class = 'Math::PlanePath::WunderlichSerpentine'; $path_class = 'Math::PlanePath::R5DragonMidpoint'; $path_class = 'Math::PlanePath::NxN'; $path_class = 'Math::PlanePath::NxNinv'; $path_class = 'Math::PlanePath::Dispersion'; $path_class = 'Math::PlanePath::KochSquareflakes'; $path_class = 'Math::PlanePath::HilbertSpiral'; $path_class = 'Math::PlanePath::GreekKeySpiral'; $path_class = 'Math::PlanePath::ComplexMinus'; $path_class = 'Math::PlanePath::GosperReplicate'; $path_class = 'Math::PlanePath::ComplexPlus'; $path_class = 'Math::PlanePath::CubicBase'; $path_class = 'Math::PlanePath::DigitGroups'; $path_class = 'Math::PlanePath::GrayCode'; $path_class = 'Math::PlanePath::ZOrderCurve'; $path_class = 'Math::PlanePath::ImaginaryBase'; $path_class = 'Math::PlanePath::KochCurve'; $path_class = 'Math::PlanePath::PixelRings'; $path_class = 'Math::PlanePath::TriangleSpiral'; $path_class = 'Math::PlanePath::HypotOctant'; $path_class = 'Math::PlanePath::SquareSpiral'; $path_class = 'Math::PlanePath::PowerArray'; $path_class = 'Math::PlanePath::ParabolicRuns'; $path_class = 'Math::PlanePath::DiagonalsOctant'; $path_class = 'Math::PlanePath::PyramidRows'; $path_class = 'Math::PlanePath::Corner'; $path_class = 'Math::PlanePath::ComplexRevolving'; $path_class = 'Math::PlanePath::DragonMidpoint'; $path_class = 'Math::PlanePath::ParabolicRows'; $path_class = 'Math::PlanePath::TriangularHypot'; $path_class = 'Math::PlanePath::SierpinskiArrowheadCentres'; $path_class = 'Math::PlanePath::DiamondSpiral'; $path_class = 'Math::PlanePath::DragonCurve'; $path_class = 'Math::PlanePath::KochelCurve'; $path_class = 'Math::PlanePath::FibonacciWordFractal'; $path_class = 'Math::PlanePath::CincoCurve'; $path_class = 'Math::PlanePath::WunderlichMeander'; $path_class = 'Math::PlanePath::AR2W2Curve'; $path_class = 'Math::PlanePath::AlternatePaperMidpoint'; $path_class = 'Math::PlanePath::BetaOmega'; $path_class = 'Math::PlanePath::FractionsTree'; $path_class = 'Math::PlanePath::R5DragonCurve'; $path_class = 'Math::PlanePath::GcdRationals'; $path_class = 'Math::PlanePath::Diagonals'; $path_class = 'Math::PlanePath::LToothpickTree'; $path_class = 'Math::PlanePath::CfracDigits'; $path_class = 'Math::PlanePath::BalancedArray'; $path_class = 'Math::PlanePath::FibonacciWordKnott'; $path_class = 'Math::PlanePath::LCornerReplicate'; $path_class = 'Math::PlanePath::HilbertCurve'; $path_class = 'Math::PlanePath::ImaginaryHalf'; $path_class = 'Math::PlanePath::R7DragonCurve'; $path_class = 'Math::PlanePath::GosperIslands'; $path_class = 'Math::PlanePath::ToothpickReplicate'; $path_class = 'Math::PlanePath::EToothpickTree'; $path_class = 'Math::PlanePath::Hypot'; $path_class = 'Math::PlanePath::SierpinskiCurve'; $path_class = 'Math::PlanePath::FlowsnakeCentres'; $path_class = 'Math::PlanePath::Flowsnake'; $path_class = 'Math::PlanePath::LToothpickTree'; $path_class = 'Math::PlanePath::AnvilSpiral'; $path_class = 'Math::PlanePath::FilledRings'; $path_class = 'Math::PlanePath::HexSpiral'; $path_class = 'Math::PlanePath::HexSpiralSkewed'; $path_class = 'Math::PlanePath::TwoOfEightByCells'; $path_class = 'Math::PlanePath::DivisibleColumns'; $path_class = 'Math::PlanePath::PeninsulaBridge'; $path_class = 'Math::PlanePath::PowerRows'; $path_class = 'Math::PlanePath::WythoffDifference'; $path_class = 'Math::PlanePath::WythoffTriangle'; $path_class = 'Math::PlanePath::WythoffArray'; $path_class = 'Math::PlanePath::SumFractions'; $path_class = 'Math::PlanePath::AztecDiamondRings'; $path_class = 'Math::PlanePath::TriangleSpiralSkewed'; $path_class = 'Math::PlanePath::CellularRule190'; $path_class = 'Math::PlanePath::CellularRule54'; $path_class = 'Math::PlanePath::OneOfEightByCells'; $path_class = 'Math::PlanePath::ZeckendorfTerms'; $path_class = 'Math::PlanePath::BinaryTerms'; $path_class = 'Math::PlanePath::LCornerTreeByCells'; $path_class = 'Math::PlanePath::UlamWarburtonOld'; $path_class = 'Math::PlanePath::LCornerTree'; $path_class = 'Math::PlanePath::ToothpickSpiral'; $path_class = 'Math::PlanePath::ChanTree'; $path_class = 'Math::PlanePath::RationalsTree'; $path_class = 'Math::PlanePath::PyramidSpiral'; $path_class = 'Math::PlanePath::CornerReplicate'; $path_class = 'Math::PlanePath::WythoffPreliminaryTriangle'; $path_class = 'Math::PlanePath::WythoffLines'; $path_class = 'Math::PlanePath::OctagramSpiral'; $path_class = 'Math::PlanePath::MPeaks'; $path_class = 'Math::PlanePath::KnightSpiral'; $path_class = 'Math::PlanePath::PentSpiralSkewed'; $path_class = 'Math::PlanePath::PentSpiral'; $path_class = 'Math::PlanePath::HeptSpiralSkewed'; $path_class = 'Math::PlanePath::FourReplicate'; $path_class = 'Math::PlanePath::DiagonalsAlternating'; $path_class = 'Math::PlanePath::ToothpickTreeByCells'; $path_class = 'Math::PlanePath::FactorRationals'; $path_class = 'Math::PlanePath::MultipleRings'; $path_class = 'Math::PlanePath::HTreeByCells'; $path_class = 'Math::PlanePath::ToothpickUpist'; $path_class = 'Math::PlanePath::HTree'; $path_class = 'Math::PlanePath::CCurve'; $path_class = 'Math::PlanePath::Z2DragonCurve'; $path_class = 'Math::PlanePath::Godfrey'; $path_class = 'Math::PlanePath::CellularRule'; $path_class = 'Math::PlanePath::CoprimeColumns'; $path_class = 'Math::PlanePath::DiagonalRationals'; $path_class = 'Math::PlanePath::OneOfEight'; $path_class = 'Math::PlanePath::PythagoreanTree'; $path_class = 'Math::PlanePath::UlamWarburtonQuarter'; $path_class = 'Math::PlanePath::UlamWarburton'; $path_class = 'Math::PlanePath::ToothpickTree'; $path_class = 'Math::PlanePath::DekkingCentres'; $path_class = 'Math::PlanePath::DekkingCurve'; $path_class = 'Math::PlanePath::SierpinskiTriangle'; $path_class = 'Math::PlanePath::HilbertSides'; $path_class = 'Math::PlanePath::SquaRecurve'; $path_class = 'Math::PlanePath::QuintetCurve'; $path_class = 'Math::PlanePath::SquareReplicate'; $path_class = 'Math::PlanePath::QuintetReplicate'; $path_class = 'Math::PlanePath::AlternateTerdragon'; $path_class = 'Math::PlanePath::TerdragonRounded'; $path_class = 'Math::PlanePath::TerdragonCurve'; $path_class = 'Math::PlanePath::AlternatePaper'; $path_class = 'Math::PlanePath::PeanoCurve'; $path_class = 'Math::PlanePath::PeanoDiagonals'; $path_class = 'Math::PlanePath::CornerAlternating'; my $lo = 0; my $hi = 64; Module::Load::load($path_class); my $path = $path_class->new ( n_start => 0, wider => 3, # arms => 1, # radix => 3, # numbering_type => 'rotate', # k=>5, # align => 'right', # parts => 'left', # direction => 'up', # coordinates => 'ST', # tree_type => 'UAD', # ring_shape => 'polygon', # step => 1, # sign_encoding => 'revbinary', # parts => 'wedge', # shift => 6, # pn_encoding => 'negabinary', # points => 'all_mul', # k => 4, # digit_order => 'HtoL', # digit_order => 'LtoH', # reduced => 1, # rule => 14, # x_start => 5, # y_start => 2, # divisor_type => 'proper', # reverse => 1, # tree_type => 'L', # sides=>3, # digit_order => 'XnYX', # radix => 2, # points => 'square_centred', # pairs_order => 'rows_reverse', # pairs_order => 'diagonals_up', # tree_type => 'HCS', # start => 'snowflake', # n_start=>37, # step => 5, # n_start => 37, # align => 'diagonal', # offset => -0.5, # turns => 1, # base => 7, # diagonal_length => 5, # apply_type => 'FS', # serpentine_type => '010_000', # straight_spacing => 3, # diagonal_spacing => 7, # arms => 7, # wider => 3, # realpart => 1, # mirror => 1, ); ### $path my %seen; my $n_start = $path->n_start; my $arms_count = $path->arms_count; my $path_ref = ref($path); print "n_start()=$n_start arms_count()=$arms_count $path_ref\n"; { my $num_roots = $path->tree_num_roots(); my @n_list = $path->tree_root_n_list(); print " $num_roots roots n=",join(',',@n_list),"\n"; } { require Data::Float; my $pos_infinity = Data::Float::pos_infinity(); my $neg_infinity = Data::Float::neg_infinity(); my $nan = Data::Float::nan(); $path->n_to_xy($pos_infinity); $path->n_to_xy($neg_infinity); $path->n_to_xy($nan); $path->xy_to_n(0,$pos_infinity); $path->xy_to_n(0,$neg_infinity); $path->xy_to_n(0,$nan); $path->xy_to_n($pos_infinity,0); $path->xy_to_n($neg_infinity,0); $path->xy_to_n($nan,0); $path->rect_to_n_range($pos_infinity,0,0,0); $path->rect_to_n_range($neg_infinity,0,0,0); $path->rect_to_n_range($nan,0,0,0); $path->rect_to_n_range(0,$pos_infinity,0,0); $path->rect_to_n_range(0,$neg_infinity,0,0); $path->rect_to_n_range(0,$nan,0,0); } for (my $i = $n_start+$lo; $i <= $hi; $i+=1) { #for (my $i = $n_start; $i <= $n_start + 800000; $i=POSIX::ceil($i*2.01+1)) { my ($x, $y) = $path->n_to_xy($i) or next; # next unless $x < 0; # abs($x)>abs($y) && $x > 0; my $dxdy = ''; my $diffdxdy = ''; my ($dx, $dy) = $path->n_to_dxdy($i); if (defined $dx && defined $dy) { my $d = Math::Libm::hypot($dx,$dy); $dxdy = sprintf "%.3f,%.3f(%.3f)", $dx,$dy,$d; } else { $dxdy='[undef]'; } my ($next_x, $next_y) = $path->n_to_xy($i+$arms_count); if (defined $next_x && defined $next_y) { my $want_dx = $next_x - $x; my $want_dy = $next_y - $y; if ($dx != $want_dx || $dy != $want_dy) { $diffdxdy = "dxdy(want $want_dx,$want_dy)"; } } my $rep = ''; my $xy = (defined $x ? $x : 'undef').','.(defined $y ? $y : 'undef'); if (defined $seen{$xy}) { $rep = "rep$seen{$xy}"; $seen{$xy} .= ",$i"; } else { $seen{$xy} = $i; } my @n_list = $path->xy_to_n_list ($x+.0, $y-.0); my $n_rev; if (@n_list) { $n_rev = join(',',@n_list); } else { $n_rev = 'norev'; } my $rev = ''; if (@n_list && $n_list[0] ne $seen{$xy}) { $rev = 'Rev'; } my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); my $range = ''; if ($n_hi < $i || $n_lo > $i) { $range = 'Range'; } my $n_children = ''; my @n_children = $path->tree_n_children ($i); if (@n_children) { $n_children = " c="; foreach my $n_child (@n_children) { my $n_parent = $path->tree_n_parent($n_child); if (! defined $n_parent || $n_parent != $i) { $n_children .= "***"; } $n_children .= $n_child; $n_children .= ","; } $n_children =~ s/,$//; } my $num_children = $path->tree_n_num_children($i); if (! defined $num_children || $num_children != scalar(@n_children)) { $n_children .= "numchildren***"; } my $depth = $path->tree_n_to_depth($i); if (defined $depth) { $n_children .= " d=$depth"; } my $baddepth = ''; if ($path->can('tree_n_to_depth') != Math::PlanePath->can('tree_n_to_depth')) { my $depth = $path->tree_n_to_depth($i); my $calc_depth = path_tree_n_to_depth_by_parents($path,$i); if (! defined $depth || $calc_depth != $depth) { $baddepth .= "ntodepth=$depth,parentcalc=$calc_depth"; } } my $flag = ''; if ($rev || $range || $diffdxdy || $baddepth) { $flag .= " ***$rev$range$diffdxdy$baddepth"; } if (! defined $n_lo) { $n_lo = 'undef'; } if (! defined $n_hi) { $n_hi = 'undef'; } my $iwidth = ($i == int($i) ? 0 : 2); printf "%.*f %7.3f,%7.3f %3s %s %s%s %s %s\n", $iwidth,$i, $x,$y, $n_rev, "${n_lo}_${n_hi}", $dxdy, $n_children, " $rep", $flag; # %.2f ($x*$x+$y*$y), } exit 0; } sub path_tree_n_to_depth_by_parents { my ($path, $n) = @_; if ($n < $path->n_start) { return undef; } my $depth = 0; for (;;) { my $parent_n = $path->tree_n_parent($n); last if ! defined $parent_n; if ($parent_n >= $n) { warn "Oops, tree parent $parent_n >= child $n in ", ref $path; return -1; } $n = $parent_n; $depth++; } return $depth; } __END__ { use Math::PlanePath::KochCurve; package Math::PlanePath::KochCurve; sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1) } if ($y2 < 0) { return (1,0); } $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1) } ### rect_to_n_range(): "$x1,$y1 $x2,$y2" my (undef, $top_level) = round_down_pow (max(2, abs($x1), abs($x2)), 3); $top_level += 2; ### $top_level my ($tx,$ty, $dir, $len); my $intersect_rect_p = sub { if ($dir < 0) { $dir += 6; } elsif ($dir > 5) { $dir -= 6; } my $left_x = $tx; my $peak_y = $ty; my $offset; if ($dir & 1) { # pointing downwards if ($dir == 1) { $left_x -= $len-1; # +1 to exclude left edge $peak_y += $len; } elsif ($dir == 3) { $left_x -= 2*$len; } else { $peak_y++; # exclude top edge } if ($peak_y < $y1) { ### all below ... return 0; } $offset = $y2 - $peak_y; } else { # pointing upwards if ($dir == 2) { $left_x -= 2*$len; } elsif ($dir == 4) { $left_x -= $len; $peak_y -= $len-1; # +1 exclude bottom edge } if ($peak_y > $y2) { ### all above ... return 0; } $offset = $peak_y - $y1; } my $right_x = $left_x + 2*($len-1); if ($offset > 0) { $left_x += $offset; $right_x -= $offset; } ### $offset ### $left_x ### $right_x ### result: ($left_x <= $x2 && $right_x >= $x1) return ($left_x <= $x2 && $right_x >= $x1); }; my @pending_tx = (0); my @pending_ty = (0); my @pending_dir = (0); my @pending_level = ($top_level); my @pending_n = (0); my $n_lo; for (;;) { if (! @pending_tx) { ### nothing in rectangle for low ... return (1,0); } $tx = pop @pending_tx; $ty = pop @pending_ty; $dir = pop @pending_dir; my $level = pop @pending_level; my $n = pop @pending_n; $len = 3**$level; ### pop for low ... ### n: sprintf('0x%X',$n) ### $level ### $len ### $tx ### $ty ### $dir unless (&$intersect_rect_p()) { next; } $level--; if ($level < 0) { $n_lo = $n; last; } $n *= 4; $len = 3**$level; ### descend: "len=$len" push @pending_tx, $tx+4*$len; push @pending_ty, $ty; push @pending_dir, $dir; push @pending_level, $level; push @pending_n, $n+3; push @pending_tx, $tx+3*$len; push @pending_ty, $ty; push @pending_dir, $dir-1; push @pending_level, $level; push @pending_n, $n+2; push @pending_tx, $tx+2*$len; push @pending_ty, $ty; push @pending_dir, $dir+1; push @pending_level, $level; push @pending_n, $n+1; push @pending_tx, $tx; push @pending_ty, $ty; push @pending_dir, $dir; push @pending_level, $level; push @pending_n, $n; } ### high ... @pending_tx = (0); @pending_ty = (0); @pending_dir = (0); @pending_level = ($top_level); @pending_n = (0); for (;;) { if (! @pending_tx) { ### nothing in rectangle for high ... return (1,0); } $tx = pop @pending_tx; $ty = pop @pending_ty; $dir = pop @pending_dir; my $level = pop @pending_level; my $n = pop @pending_n; ### pop for high ... ### n: sprintf('0x%X',$n) ### $level ### $len ### $tx ### $ty ### $dir $len = 3**$level; unless (&$intersect_rect_p()) { next; } $level--; if ($level < 0) { return ($n_lo, $n); } $n *= 4; $len = 3**$level; ### descend push @pending_tx, $tx; push @pending_ty, $ty; push @pending_dir, $dir; push @pending_level, $level; push @pending_n, $n; push @pending_tx, $tx+2*$len; push @pending_ty, $ty; push @pending_dir, $dir+1; push @pending_level, $level; push @pending_n, $n+1; push @pending_tx, $tx+3*$len; push @pending_ty, $ty; push @pending_dir, $dir-1; push @pending_level, $level; push @pending_n, $n+2; push @pending_tx, $tx+4*$len; push @pending_ty, $ty; push @pending_dir, $dir; push @pending_level, $level; push @pending_n, $n+3; } } } { require Math::PlanePath::KochSnowflakes; my $path = Math::PlanePath::KochSnowflakes->new; my @range = $path->rect_to_n_range (0,0, 0,2); ### @range exit 0; } { require Math::PlanePath::PixelRings; my $path = Math::PlanePath::PixelRings->new (wider => 0, # step => 0, #tree_type => 'UAD', #coordinates => 'PQ', ); ### xy: $path->n_to_xy(500) ### n: $path->xy_to_n(3,3) exit 0; } # cf turn_any_left() =over =item C<$lsr = $path-En_to_turn_LSR ($n)> Return the turn at C<$n> in the form 1 left turn 0 straight or 180 reverse or no move at all -1 right turn undef no turn exists at $n The path is taken to go in a line from C<$n-1> to C<$n> and the turn is then whether C<$n+1> is left, right, or on that line. C<$n> can be fractional. If there is no X,Y for any of the three points considered then the return is C. =item C<$lsr = $path-Eturn_LSR_minimum> =item C<$lsr = $path-Eturn_LSR_maximum> Return the minimum or maximum LSR value returned by C<$path-En_to_turn_LSR($n)> for integer N values in the path. If there are no turns at all then return C. =back Math-PlanePath-129/devel/squarecurve.pl0000644000175000017500000001670413010525150015707 0ustar gggg# Copyright 2016 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::PlanePath; =pod Math::PlanePath::DekkingCurve /m/e-curve-mckenna/ /m/e-curve-mckenna/64.png * 10--11 * * * n=5 <| v |> _ _ 9 10 11 13 14 dir=0 rev=0 * 9 12---*---*---* <| _ v _ <| 8 7 12 17 15 initial * *---* *---*---* dir=-1 rev=1 _ <| <| _ v 2 6 18 19 16 *---* * *---* * |> |> |> _ |> 1 3 5 21 20 * *---* *---* * |> v <| _ _ 0 4 22 23 24 0 *---*---* *---*---* n=7 | | * *---* *---*---*---* | | | | * *---*---* *---* * | | | | * *---*---*---* * *---* | | *---*---* *---* *---* | | | | * *---* * *---* * | | | | | | * *---*---* * *---* | | 0 *---*---*---* *---*---*---* <| v v v |> _ _ _ _ n=9 * *---* * *---*---*---*---* <| <| v |> |> |> _ _ <| * * *---* * *---*---* * <| <| _ v _ |> |> <| <| * *---*---*---* * *---* * <| _ _ v _ <| <| v <| *---*---*---*---* * *---*---* _ _ _ v <| <| _ v v *---*---*---* *---* *---*---* |> _ <| <| v _ _ v |> * *---* * * *---*---* * |> |> |> |> |> |> _ |> |> * * *---* * * *---* * |> |> v <| <| <| _ _ |> * *---*---*---* * *---*---* |> v v v <| _ _ _ _ 0 *---*---*---*---* 40---*---*---*--44 <| v v v v |> _ _ _ _ _ * *---*---* * 64---*---*---*---*---* <| <| v v |> |> | _ _ _ <| * * *--51 * * *---*---*---* 70 <| <| <| v _ |> |> |> _ <| <| n=11 * * *---*--47 * * *--83 * * 11*11 == 121 <| <| _ v _ <| <| <| <| <| <| * 57---*---*---*--61 * * *--81 * t=5 <| _ _ v _ v |> |> v <| 35---*---*---*--31 90---* *---*---*---* _ _ _ v <| <| _ _ v v v 5---*---*---*---9 * *---*---*---*---* |> _ _ v |> |> _ _ v v |> * 19---*---* * * *---*---*--09 * |> |> _ |> |> |> |> _ v |> |> * * 15--16 * * * *---* * * |> |> |> <| <| <| <| <| _ |> |> * * 14---*--12 * * * *---* * |> |> v v <| <| <| _ _ _ |> * 22---*---*---*--26 * *---*---*--99 |> v v v v <| _ _ _ _ _ 0 *---*---*---*---*-121 n=19 19 *---*---*---*---*---*---*---*---* <| v v v v v v v v |> _ _ _ _ _ _ _ _ _ 18 * *---*---*---*---*---*---* * *---*---*---*---*---*---*---*---*---* <| <| v v v v v v |> |> |> _ _ _ _ _ _ _ <| 17 * * *---*---*---*---* * * * *---*---*---*---*---*---*---* * <| <| <| v v v v |> |> |> |> |> _ _ _ _ _ <| <| 16 * * * *---*---* * * * * * *---*---*---*---*---* * * <| <| <| <| v v |> |> |> |> |> |> |> _ _ _ <| <| <| 15 * * * * *---* * * * * * * *---*---*---* * * * <| <| <| <| <| v _ |> |> |> |> |> |> |> _ <| <| <| <| 14 * * * * *---*---* * * * * * * *---* * * * * <| <| <| <| _ v _ <| <| <| <| <| <| <| <| <| <| <| <| 13 * * * *---*---*---*---* * * * * * * *---* * * * <| <| <| _ _ v _ v _ |> |> |> |> |> |> v <| <| <| 12 * * *---*---*---*---*---*---* * * * * *---*---*---* * * <| <| _ _ _ v _ v _ <| <| <| <| _ v v v <| <| 11 * *---*---*---*---*---*---*---*---* * * *---*---*---*---*---* * <| _ _ _ _ v _ v _ v <| <| _ _ v v v v <| 10 *---*---*---*---*---*---*---*---* *---* *---*---*---*---*---*---*---* _ _ _ _ _ v _ v <| <| v _ _ _ v v v v v 9 *---*---*---*---*---*---*---*---* * *---*---*---*---*---*---*---*---* |> _ _ _ _ v _ v |> |> v _ _ _ v v v v |> 8 * *---*---*---*---*---*---* * * *---*---*---*---*---*---*---* * |> |> _ _ _ v <| <| <| <| v _ _ _ v v v |> |> 7 * * *---*---*---*---* * * * * *---*---*---*---*---* * * |> |> |> _ _ v |> |> |> |> |> |> _ _ v v |> |> |> 6 * * * *---*---* * * * * * * *---*---*---* * * * |> |> |> |> _ |> |> |> |> |> |> |> |> _ v |> |> |> |> 5 * * * * *---* * * * * * * * *---* * * * * |> |> |> |> |> <| <| <| <| <| <| <| <| <| _ |> |> |> |> 4 * * * * *---*---* * * * * * * * *---* * * * |> |> |> |> v v <| <| <| <| <| <| <| _ _ _ |> |> |> 3 * * * *---*---*---*---* * * * * * *---*---*---* * * |> |> |> v v v v <| <| <| <| <| _ _ _ _ _ |> |> 2 * * *---*---*---*---*---*---* * * * *---*---*---*---*---* * |> |> v v v v v v <| <| <| _ _ _ _ _ _ _ |> 1 * *---*---*---*---*---*---*---*---* * *---*---*---*---*---*---*---* |> v v v v v v v v <| _ _ _ _ _ _ _ _ _ 0 0 *---*---*---*---*---*---*---*---*---* 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Douglas M. McKenna, 1978, as described in "SquaRecurves, E-Tours, Eddies, and Frenzies: Basic Families of Peano Curves on the Square Grid", in "The Lighter Side of Mathematics: Proceedings of the Eugene Strens Memorial Conference on Recreational Mathematics and its History", Mathematical Association of America, 1994, pages 49-73, ISBN 0-88385-516-X. =cut sub nbase_to_xydxdy { my ($n, $k) = @_; } { my $k = 9; my @x; my @y; foreach my $n (0 .. $k*$k-1) { my ($x,$y, $dx,$dy) = nbase_to_xydxdy ($n, $k); print "$x,$y\n"; # , $dx,$dy $x[$n] = $x; $y[$n] = $y; } exit 0; } Math-PlanePath-129/devel/archimedean.pl0000644000175000017500000002314212000752040015572 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::Libm 'hypot', 'asinh', 'M_PI', 'asin'; use POSIX (); use Math::PlanePath::Base::Generic 'round_nearest'; use Math::PlanePath::ArchimedeanChords; # uncomment this to run the ### lines use Smart::Comments; # set term x # plot [0:50] asinh(x),exp(log(x)*.4) { for (my $r = 1; $r < 1e38; $r *= 1.1) { my $theta = $r * 2*M_PI(); my $arc = spiral_arc_length($theta); my $circle = r_to_circles_arclength($r); # $circle = ($r*$r+1)*M_PI; # maybe printf "%2d %10.3g %10.3g %.3f\n", $r, $circle, $arc, ($circle<$arc); } exit 0; } # 1/4pi * (t * sqrt(1+t^2) + log(t+sqrt(1+t^2))) # # sqrt(1+t^2) <= t + 1/(2*t) # # 1/4pi * (t * sqrt(1+t^2) + asinh(t)) # <= 1/4pi * (t*(t+1/2t) + asinh(t)) # = 1/4pi * (2pi*r * (2pi*r+ 1/(4pi*r)) + asinh(2pi*r)) # = 1/2 * r * (2pi*r + 1/(4pi*r)) + 1/4pi * asinh(2pi*r)) # = pi * r * (r + 1/(8pi^2*r)) + 1/4pi * asinh(2pi*r)) # = pi * (r^2 + 1/8pi^2*r) + 1/4pi * asinh(2pi*r)) { sub r_to_circles_arclength { my ($r) = @_; return M_PI()*$r*($r+1); } sub spiral_arc_length { my ($theta) = @_; # theta in radians return (1/(4*M_PI())) * ($theta * sqrt(1+$theta**2) + asinh($theta)); # with $a = 1/2pi for unit spacing # return 0.5 * $a * ($theta * sqrt(1+$theta**2) + asinh($theta)); } sub total_chords { my ($r) = @_; my $sum = 0; foreach my $i (2 .. POSIX::ceil($r)) { $sum += circle_chords($i); } return $sum; } my $a = 1 / (2*M_PI()); print "a=$a\n"; print "r=",(2*M_PI)*$a,"\n"; for (my $r = 1; $r < 1e38; $r *= 1.5) { my $theta = $r * 2*M_PI(); my $arc = spiral_arc_length($theta); my $circle = r_to_circles_arclength($r); $circle = ($r*$r+1)*M_PI; my $chords = total_chords($r-1); printf "%2d %10.3g %10.3g %10.3g %.3f\n", $r, $chords, $circle, $arc, ($circle-$arc)/$r; } exit 0; } { require Math::Polynomial; require Math::BigRat; my $asin = Math::Polynomial->new (map # {Math::BigRat->new($_)} {eval $_} 0, 1, 0, '1/6', 0, '3/40', 0, '5/112', 0, '35/1152'); $asin->string_config({ascending=>1}); print "$asin\n"; my $r2 = $asin->new (0, 0.5); my $den = $asin->nest($r2); print "$den\n"; my $num = $asin->monomial(1); foreach (1 .. 40) { (my $q, $num) = $num->divmod($den); print "q=$q\n"; $num = $num->shift_up(1); } exit 0; } { sub circle_chords { my ($r) = @_; return M_PI() / asin(0.5/$r); } for (my $r = 1; $r < 100; $r++) { my $chords = circle_chords($r); printf "%2d %8.3g\n", $r, $chords; } exit 0; } { my $path = Math::PlanePath::ArchimedeanChords->new; my $prev_x = 1; my $prev_n = 0; my $i = 0; foreach my $n ($path->n_start .. 100000) { my ($x, $y) = $path->n_to_xy ($n); if ($x > 0 && $prev_x < 0) { $i++; my $diff = $n - $prev_n; my $avg = $diff / $i; print "$n $diff $avg\n"; $prev_n = $n; } $prev_x = $x; } exit 0; } { require Math::PlanePath::ArchimedeanChords; require Math::PlanePath::TheodorusSpiral; require Math::PlanePath::VogelFloret; #my $path = Math::PlanePath::VogelFloret->new; my $path = Math::PlanePath::ArchimedeanChords->new; ### $path my $n = $path->xy_to_n (600, 0); ### $n $n = $path->xy_to_n (600, 0); ### $n exit 0; } { require Math::Symbolic; use Math::Symbolic::Derivative; my $tree = Math::Symbolic->parse_from_string( # '(t*cos(t)-c)^2' # '(t*sin(t)-s)' # '(t+1)^2' # '(t+u)^2 + t^2' '(t+u)*cos(u)' ); # my $tree = Math::Symbolic->parse_from_string(); print "$tree\n"; my $derived = Math::Symbolic::Derivative::total_derivative($tree, 'u'); $derived = $derived->simplify; print "$derived\n"; exit 0; } # sub _chord_length { # my ($t1, $t2) = @_; # my $hyp = hypot(1,$theta); # return 0.5 * _A * ($theta*$hyp + asinh($theta)); # } sub step { my ($x, $y) = @_; my $r = hypot($x,$y); my $len = 1/$r; my ($x2, $y2); foreach (1 .. 5) { ($x2,$y2) = ($x - $y*$len, $y + $x*$len); # atan($y2,$x2) my $f = hypot($x-$x2, $y-$y2); $len /= $f; ### maybe: "$x2,$y2 $f" } return ($x2, $y2); } sub next_t { my ($t1, $prev_dt) = @_; my $t = $t1; # my $c1 = $t1 * cos($t1); # my $s1 = $t1 * sin($t1); # my $c1_2 = $c1*2; # my $s1_2 = $s1*2; # my $t1sqm = $t1*$t1 - 4*M_PI()*M_PI(); my $u = 2*M_PI()/$t; printf "estimate u=%.6f\n", $u; foreach (0 .. 10) { # my $slope = 2*($t + (-$c1-$s1*$t)*cos($t) + ($c1*$t-$s1)*sin($t)); # my $f = ( ($t*cos($t) - $c1) ** 2 # + ($t*sin($t) - $s1) ** 2 # - 4*M_PI()*M_PI() ); # my $slope = (2*($t*cos($t)-$c1)*(cos($t) - $t*sin($t)) # + 2*($t*sin($t)-$s1)*(sin($t) + $t*cos($t))); my $f = ($t+$u)**2 + $t**2 - 2*$t*($t+$u)*cos($u) - 4*M_PI()*M_PI(); my $slope = 2 * ( $t*(1-cos($u)) + $u + $t*($t+$u)*sin($u) ); my $sub = $f/$slope; $u -= $sub; # my $ct = cos($t); # my $st = sin($t); # my $f = (($t - $ct*$c1_2 - $st*$s1_2) * $t + $t1sqm); # my $slope = 2 * (($t*$ct - $c1) * ($ct - $t*$st) # + ($t*$st - $s1) * ($st + $t*$ct)); # my $sub = $f/$slope; # $t -= $sub; last if ($sub < 1e-15); printf ("h=%.6f d=%.6f sub=%.20f u=%.6f\n", $slope, $f, $sub, $u); } return $t + $u; } { my $t = 2*M_PI; my $prev_dt = 1; my $prev_x = 1; my $prev_y = 0; foreach (1 .. 50) { my $nt = next_t($t,$prev_dt); my $prev_dt = $nt - $t; $t = $nt; my $r = $t * (1 / (2*M_PI())); my $x = $r*cos($t); my $y = $r*sin($t); my $d = hypot($x-$prev_x, $y-$prev_y); my $pdest = 2*M_PI()/$t; printf "%d t=%.6f d=%.3g pdt=%.3f/%.3f\n", $_, $t, $d-1, $prev_dt, $pdest; $prev_x = $x; $prev_y = $y; } exit 0; } { my $t1 = 1 * 2*M_PI; my $t = $t1; my $r1 = $t / (2*M_PI); my $c = cos($t); my $s = sin($t); my $c1 = $t1 * cos($t1); my $s1 = $t1 * sin($t1); my $c1_2 = $c1*2; my $s1_2 = $s1*2; my $t1sqm = $t1*$t1 - 4*M_PI()*M_PI(); my $x1 = $r1*cos($t1); my $y1 = $r1*sin($t1); print "x1=$x1 y1=$y1\n"; $t += 1; # { # my $r2 = $t / (2*M_PI); # my $dist = ($t1*cos($t1) - $t*cos($t) ** 2 # + ($t1*sin($t1) - $t*sin($t)) ** 2 # - 4*M_PI()*M_PI()); # my $slope = (2*($t*cos($t)-$c1)*(cos($t) - $t*sin($t)) # + 2*($t*sin($t)-$s1)*(sin($t) + $t*cos($t))); # # my $slope = 2*($t + (-$c1-$s1*$t)*cos($t) + ($c1*$t-$s1)*sin($t)); # printf "d=%.6f slope=%.6f 1/slope=%.6f\n", $dist, $slope, 1/$slope; # } foreach (0 .. 10) { # my $slope = 2*($t + (-$c1-$s1*$t)*cos($t) + ($c1*$t-$s1)*sin($t)); # my $dist = ( ($t*cos($t) - $c1) ** 2 # + ($t*sin($t) - $s1) ** 2 # - 4*M_PI()*M_PI() ); # my $slope = (2*($t*cos($t)-$c1)*(cos($t) - $t*sin($t)) # + 2*($t*sin($t)-$s1)*(sin($t) + $t*cos($t))); my $ct = cos($t); my $st = sin($t); my $dist = (($t - $ct*$c1_2 - $st*$s1_2) * $t + $t1sqm); my $slope = 2 * (($t*$ct - $c1) * ($ct - $t*$st) + ($t*$st - $s1) * ($st + $t*$ct)); my $sub = $dist/$slope; $t -= $sub; printf ("h=%.6f d=%.6f sub=%.20f t=%.6f\n", $slope, $dist, $sub, $t); } my $r2 = $t / (2*M_PI); my $x2 = $r2 * cos($t); my $y2 = $r2 * sin($t); my $dist = hypot ($x1-$x2, $y1-$y2); printf ("d=%.6f dt=%.6f\n", $dist, $t - $t1); exit 0; } { my ($x, $y) = (1, 0); foreach (1 .. 3) { step ($x, $y); ### step to: "$x, $y" } exit 0; } { my $width = 79; my $height = 40; my $x_scale = 3; my $y_scale = 2; my $y_origin = int($height/2); my $x_origin = int($width/2); my $path = Math::PlanePath::ArchimedeanChords->new; my @rows = (' ' x $width) x $height; foreach my $n (0 .. 60) { my ($x, $y) = $path->n_to_xy ($n) or next; $x *= $x_scale; $y *= $y_scale; $x += $x_origin; $y = $y_origin - $y; # inverted $x -= length($n) / 2; $x = round_nearest ($x); $y = round_nearest ($y); if ($x >= 0 && $x < $width && $y >= 0 && $y < $height) { substr ($rows[$y], $x,length($n)) = $n; } } foreach my $row (@rows) { print $row,"\n"; } exit 0; } { foreach my $i (0 .. 50) { my $theta = Math::PlanePath::ArchimedeanChords::_inverse($i); my $length = Math::PlanePath::ArchimedeanChords::_arc_length($theta); printf "%2d %8.3f %8.3f\n", $i, $theta, $length; } exit 0; } Math-PlanePath-129/devel/alternat.l0000644000175000017500000000457413641544321015011 0ustar gggg; Copyright 2019, 2020 Kevin Ryde ; ; This file is part of Math-PlanePath. ; ; Math-PlanePath 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, or (at your option) any later ; version. ; ; Math-PlanePath 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 Math-PlanePath. If not, see . ; This file is named alternat.l since the file chooser in xfractint is ; limited to CP/M style 8.3 filenames. ; X is at the end of an even segment. That segment expands to a left ; turn in the middle and the existing turn after the segment flip L<->R. ; That flip is achieved by a "|" turn 180 degrees. ; ; Y is at the end of an odd segment. That segment expands to a right ; turn in the middle, and again existing turn after it flip L<->R. ; Alternate { Angle 4 ; 90 degrees Axiom FX X = X+FY| Y = X-FY| } ; X is at the end of an even segment. ; Y is at the end of an odd segment. ; Two expansions of each is ; ; R---R ; | | ; X segment .---L . Y segment . R---. ; | | ; L---L ; ; Existing turns are unchanged, since they are flipped left <-> right ; twice which is back to the same as they were. ; ; The "order" here is effectively doubled, so that say 8 for ; "Alternate" is equivalent to 4 for "Alternate2". ; Alternate2 { Angle 4 ; 90 degrees Axiom FX X = X+FY-FX-FY Y = X+FY+FX-FY } ; Same as "Alternate2" above, but each F there is doubled to F here, ; and the turns are given chamfers by two steps of 45 degrees and an F ; in between so + is +F+ and - is -F-. ; ; The doubled FF makes the side longer than the chamfer. ; A single F for both the sides and the chamfer would be a more rounded look. ; AlternateRound2 { Angle 8 ; 45 degrees Axiom FFX X = X+F+FFY-F-FFX-F-FFY Y = X+F+FFY+F+FFX-F-FFY } ; Local variables: ; compile-command: "xfractint type=lsystem lfile=alternat.l lname=Alternate params=8" ; End: Math-PlanePath-129/devel/square-spiral.gnuplot0000644000175000017500000000733712026721454017224 0ustar gggg#!/usr/bin/gnuplot # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # http://rosettacode.org/wiki/Spiral_matrix # http://rosettacode.org/wiki/Zig-zag_matrix # set terminal png # set terminal xterm n_to_xy(n) = \ (n <= 1 ? n : nd_to_xy(n, n_to_d(n))) nd_to_xy(n,d) = \ drem_to_xy(d, n - d_to_base(d)) n_to_d(n) = int ((2 + sqrt(int(4*n))) / 4) d_to_base(d) = 4 * d**2 # right vertical # top horizontal # left vertical # bottom horizontal # drem_to_xy(d,r) = \ (r < -2*d ? d + (r+3*d)*{0,1} \ : r < 0 ? -r + d*{-1,1} \ : r < 2*d ? -d + (d-r)*{0,1} \ : r-2*d + d*{-1,-1}) print n_to_d(0) print n_to_d(1) print n_to_d(2) print n_to_d(3) print n_to_d(4) print n_to_d(5) print '' print n_to_xy(0) print n_to_xy(1) print n_to_xy(2) print n_to_xy(3) print n_to_xy(4) print n_to_xy(5) print n_to_xy(6) print n_to_xy(7) print n_to_xy(8) print n_to_xy(9) # set xrange [0:36] # plot real(n_to_xy(x)) # pause 100 # set xrange [0:36] # plot x-d_to_base(n_to_d(x)) # pause 100 length=49 set trange [0:length] set samples 5*length+1 set parametric set key off plot real(n_to_xy(t)),imag(n_to_xy(t)) with labels pause 100 # # Return the position of the highest 1-bit in n. # # The least significant bit is position 0. # # For example n=13 is binary "1101" and the high bit is pos=3. # # If n==0 then the return is 0. # # Arranging the test as n>=2 avoids infinite recursion if n==NaN (any # # comparison involving NaN is always false). # # # high_bit_pos(n) = (n>=2 ? 1+high_bit_pos(int(n/2)) : 0) # # # Return 0 or 1 for the bit at position "pos" in n. # # pos==0 is the least significant bit. # # # bit(n,pos) = int(n / 2**pos) & 1 # # # dragon(n) returns a complex number which is the position of the # # dragon curve at integer point "n". n=0 is the first point and is at # # the origin {0,0}. Then n=1 is at {1,0} which is x=1,y=0, etc. If n # # is not an integer then the point returned is for int(n). # # # # The calculation goes by bits of n from high to low. Gnuplot doesn't # # have iteration in functions, but can go recursively from # # pos=high_bit_pos(n) down to pos=0, inclusive. # # # # mul() rotates by +90 degrees (complex "i") at bit transitions 0->1 # # or 1->0. add() is a vector (i+1)**pos for each 1-bit, but turned by # # factor "i" when in a "reversed" section of curve, which is when the # # bit above is also a 1-bit. # # # dragon(n) = dragon_by_bits(n, high_bit_pos(n)) # dragon_by_bits(n,pos) \ # = (pos>=0 ? add(n,pos) + mul(n,pos)*dragon_by_bits(n,pos-1) : 0) # # add(n,pos) = (bit(n,pos) ? (bit(n,pos+1) ? {0,1} * {1,1}**pos \ # : {1,1}**pos) \ # : 0) # mul(n,pos) = (bit(n,pos) == bit(n,pos+1) ? 1 : {0,1}) # # # Plot the dragon curve from 0 to "length" with line segments. # # "trange" and "samples" are set so the parameter t runs through # # integers t=0 to t=length inclusive. # # # # Any trange works, it doesn't have to start at 0. But must have # # enough "samples" that all integers t in the range are visited, # # otherwise vertices in the curve would be missed. # # Math-PlanePath-129/devel/rationals-tree.pl0000644000175000017500000011125513674617643016321 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2017, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use POSIX (); use List::Util 'sum'; use Math::PlanePath::Base::Digits 'round_down_pow', 'bit_split_lowtohigh', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use Math::PlanePath::RationalsTree; $|=1; # uncomment this to run the ### lines use Smart::Comments; { # lamplighter A154435 require Math::NumSeq::OEIS::File; my $lamp = Math::NumSeq::OEIS::File->new(anum=>'A154435'); { my $n = 0b100010010; my $l = $lamp->ith($n); printf "%3d %b\n", $n, $n; printf "= %b = %d\n\n", $l, $l; } foreach my $n (64 .. 127) { my $l = $lamp->ith($n); printf "%3d %b\n", $n, $n; printf "= %b = %d\n\n", $l, $l; } exit 0; } { # permutations in N row # HtoL SB, Bird, HCS # SB <-> Bird A258746 flip alternates # SB->HCS A003188 Gray A006068 Ungray # Bird->HCS A154436 lamplighter A154435 inv # # LtoH CW, Drib, AYT # CW <-> Drib A258996 flip alternates # CW -> AYT A153153 A153154 inv # Drib -> AYT A154438 A154437 inv # # Bird->CW = SB->Drib absent my %dir = (SB => 1, Bird => 1, HCS => 1, CW => 2, Drib => 2, AYT => 2); require Math::OEIS::Grep; my $choices = Math::PlanePath::RationalsTree->parameter_info_hash ->{'tree_type'}->{'choices'}; my %seen; foreach my $from_type (@$choices) { my $from_dir = $dir{$from_type} || next; my $from_path = Math::PlanePath::RationalsTree->new (tree_type => $from_type); foreach my $to_type (@$choices) { next if $from_type eq $to_type; my $to_dir = $dir{$to_type} || next; next if $from_dir == $to_dir; my $to_path = Math::PlanePath::RationalsTree->new (tree_type => $to_type); { my @values; foreach my $from_n (2 .. 25) { my ($x,$y) = $from_path->n_to_xy($from_n); my $to_n = $to_path->xy_to_n($x,$y); push @values, $to_n; } my $str = join(',',@values); my $name = "$from_type->$to_type"; if ($seen{$str}) { print "$from_type->$to_type duplicate of $seen{$str}\n"; next; } $seen{$str} = $name; print "$name\n"; Math::OEIS::Grep->search(array => \@values, name => $name); } if (0) { my $str = ''; foreach my $from_n (2 .. 25) { my ($x,$y) = $from_path->n_to_xy($from_n); my $to_n = $to_path->xy_to_n($x,$y); $to_n ^= $from_n; # $str .= "$to_n,"; $str .= sprintf '%d,', $to_n; } next if $seen{$str}++; print "$from_type->$to_type XOR http://oeis.org/search?q=$str\n"; } } print "\n"; } exit 0; } { # CW <-> Drib # low to high, phase shift encoding of 0<->1 transitions my $from = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my $to = Math::PlanePath::RationalsTree->new (tree_type => 'Bird'); # my $from = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); # my $to = Math::PlanePath::RationalsTree->new (tree_type => 'Drib'); foreach my $n (0b11100010000011111111110000111, # 0b10000000, # 0b11111111, # 0b1100110011, ) { my ($x, $y) = $from->n_to_xy($n) or die; my $l = $to->xy_to_n($x,$y) // die; my @bits = bit_split_lowtohigh($n); my $phase = 0; my $prev = 0; foreach my $i (reverse 0 .. $#bits-1) { # low to high # if ($bits[$i] != $prev) { $phase ^= 1; } $phase ^= $bits[$i] ^ $prev; $prev = $bits[$i]; $bits[$i] = $phase; $phase ^= 1; } my $f = digit_join_lowtohigh(\@bits,2); printf " %b = %d\n", $n, $n; printf "-> %b = %d\n", $l, $l; printf "-> %b = %d\n", $f, $f; print "\n"; } exit 0; } { # perms require Math::NumSeq::OEIS::File; my $perm = Math::NumSeq::OEIS::File->new(anum=>'A092569'); foreach my $n (32 .. 63) { my $l = $perm->ith($n) // last; printf "%3d %b\n", $n, $n; printf "= %b = %d\n", $l, $l; print "\n"; } exit 0; } { # X,Y list cf pythag odd,even require Math::PlanePath::RationalsTree; require Math::ContinuedFraction; foreach my $path (Math::PlanePath::RationalsTree->new(tree_type => 'CW'), Math::PlanePath::RationalsTree->new(tree_type => 'SB'), Math::PlanePath::RationalsTree->new(tree_type => 'HCS'), Math::PlanePath::RationalsTree->new(tree_type => 'AYT'), Math::PlanePath::RationalsTree->new(tree_type => 'Drib'), Math::PlanePath::RationalsTree->new(tree_type => 'Bird') ) { print "tree_type $path->{'tree_type'}\n"; foreach my $depth (0 .. 6) { foreach my $n ($path->tree_depth_to_n($depth) .. $path->tree_depth_to_n_end($depth)) { my ($x,$y) = $path->n_to_xy($n); my $flag = ''; if ($x%2 != $y%2) { $flag = ($x%2?'odd':'even').','.($y%2?'odd':'even'); } my $octant = ''; if ($y < $x) { $octant = 'octant'; } unless ($octant && $flag) { $octant = '.'; $flag = ''; } my $cfrac = Math::ContinuedFraction->from_ratio($x,$y); my $cfrac_str = $cfrac->to_ascii; printf "N=%5b %2d / %2d %10s %10s %25s\n", $n, $x,$y, $flag, $octant, $cfrac_str; if ($octant && $flag) { } $n++; } print "\n"; } } exit 0; } { # X,Y list CW # 1,1,3, 5,11,21,43 # 1,2,5,10,21,42,85 # P = X+Y Q=X X=Q Y=P-Q # # X,Y X+Y,X # / \ / \ # X,(X+Y) (X+Y),Y 2X+Y,X X+2Y,X+Y # / \ / \ / \ / \ # X,(2X+Y) 2X+Y,X+Y X+Y,X+2Y X+2Y,Y 3X+Y,2X+Y 3X+2Y,2X+Y 2X+3Y,X+Y X+3Y,X+2Y # # 1,1 # 1,2 2,1 # 1,3 3,2 2,3 3,1 # 1/4 4/3 3/5 5/2 2/5 5/3 3/4 4/1 # # X+Y,X 2,1 T # 3,1 3,2*U # 4,1*D 5,3 5,2*A 4,3*UU # 5,1 7,4*DU 8,3*UA 7,5 7,2*UD 8,5*AU 7,3 5,4*UUU # # 6,1*DD 9,5 11,4* 10,7* 11,3 13,8* 12,5*AA 9,7 9,2*AD 12,7* 13,5 11,8* 10,3* 11,7 9,4*DA 6,5* # # X+Y,Y 2,1 T # 3,2*U 3,1 # 4,3*UU 5,2 5,3*A 4,1*D # 5,4*UUU 7,3*DU 8,5*UA 7,2 7,5*UD 8,3*AU 7,4 5,1*UUU # # 6,5*DD 9,4 11,4* 10,7* 11,3 13,8* 12,5*AA 9,7 9,2*AD 12,7* 13,8 11,3* 10,7* 11,4* 9,5*DA 6,1* require Math::PlanePath::RationalsTree; require Math::PlanePath::PythagoreanTree; my $pythag = Math::PlanePath::PythagoreanTree->new (coordinates=>'PQ'); my $path = Math::PlanePath::RationalsTree->new(tree_type => 'AYT'); my $oe_total = 0; foreach my $depth (0 .. 6) { my $oe = 0; foreach my $n ($path->tree_depth_to_n($depth) .. $path->tree_depth_to_n_end($depth)) { my ($x,$y) = $path->n_to_xy($n); my $flag = ''; ($x,$y) = ($x+$y, $y); if ($x%2 != $y%2) { $flag = ($x%2?'odd':'even').','.($y%2?'odd':'even'); $oe += $flag ? 1 : 0; } my $octant = ''; if ($x >= $y) { $octant = 'octant'; } else { } my $pn = $pythag->xy_to_n($x,$y); if ($pn) { $pn = n_to_pythagstr($pn); } printf "N=%2d %2d / %2d %10s %10s %s\n", $n, $x,$y, $flag, $octant, $pn||''; $n++; } $oe_total += $oe; print "$oe $oe_total\n"; } sub n_to_pythagstr { my ($n) = @_; if ($n < 1) { return undef; } my ($pow, $exp) = round_down_pow (2*$n-1, 3); $n -= ($pow+1)/2; # offset into row my @digits = digit_split_lowtohigh($n,3); push @digits, (0) x ($exp - scalar(@digits)); # high pad to $exp many return '1-'.join('',reverse @digits); } exit 0; } { # CW successively per Moshe Newman require Math::PlanePath::GcdRationals; my $path = Math::PlanePath::RationalsTree->new(tree_type => 'CW'); my $p = 0; my $q = 1; foreach my $n (0 .. 30) { my ($x,$y) = $path->n_to_xy($n); $x ||= 0; $y ||= 0; my $diff = ($x!=$p || $y!=$q ? ' ***' : ''); print "$n $x,$y $p,$q$diff\n"; # f*q + r = p # f*q = p - r # q-r = 1 - (-p % q) # next = 1 / (2*floor(p/q) + 1 - p/q) # = q / (2*q*floor(p/q) + q - p) # = q / (2*q*f + q - p) # = q / (2*(p - r) + q - p) # = q / (2*p - 2*r + q - p) # = q / (p + q - 2*r) my ($f,$r) = Math::PlanePath::_divrem($p,$q); # ($p,$q) = ($q, ($q*(2*$f+1) - $p)); ($p,$q) = ($q, $p + $q - 2*($p % $q)); # ($p,$q) = ($q, $p-$r + 1 - (-$p % $q)); # my $g = Math::PlanePath::GcdRationals::_gcd($p,$q); # $p /= $g; # $q /= $g; } exit 0; } { # Pythagorean N search # CW A016789 3n+2. my $tree_type_aref = Math::PlanePath::RationalsTree->parameter_info_hash->{'tree_type'}->{'choices'}; foreach my $add (0 .. 3, -3 .. -1) { print "offset=$add\n"; foreach my $tree_type (@$tree_type_aref) { my $path = Math::PlanePath::RationalsTree->new(tree_type => $tree_type); my @values; for (my $n = 2; @values < 40; $n += 1) { my ($x,$y) = $path->n_to_xy($n); # next unless xy_is_pythagorean($x,$y); # next unless (($x^$y)&1) == 0; # odd/odd # next unless (($x^$y)&1) == 1; # odd/even or even/odd next unless ($x%2==1 && $y%2==0); push @values, $n+$add; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, name => "$tree_type plus $add"); } } exit 0; sub xy_is_pythagorean { my ($x,$y) = @_; return ($x>$y && ($x%2)!=($y%2)); } } { # Pythagorean N in binary my $tree_type_aref = Math::PlanePath::RationalsTree->parameter_info_hash->{'tree_type'}->{'choices'}; foreach my $tree_type (@$tree_type_aref) { print "$tree_type\n"; my $path = Math::PlanePath::RationalsTree->new(tree_type => $tree_type); for (my $n = 2; $n < 70; $n += 1) { my ($x,$y) = $path->n_to_xy($n); next unless xy_is_pythagorean($x,$y); # next unless (($x^$y)&1) == 0; # odd/odd # next unless (($x^$y)&1) == 1; # odd/even or even/odd # next unless ($x%2==1 && $y%2==0); printf "%7b\n", $n; } print "\n"; } exit 0; } { # X=1 or Y=1 row/column in binary my $tree_type_aref = Math::PlanePath::RationalsTree->parameter_info_hash->{'tree_type'}->{'choices'}; foreach my $tree_type (@$tree_type_aref) { { print "$tree_type Y=1 row\n"; my $path = Math::PlanePath::RationalsTree->new(tree_type => $tree_type); for (my $i = 2; $i < 20; $i += 1) { my $n = $path->xy_to_n($i,1); printf "%20b\n", $n; } print "\n"; } { print "$tree_type X=1 row\n"; my $path = Math::PlanePath::RationalsTree->new(tree_type => $tree_type); for (my $i = 2; $i < 20; $i += 1) { my $n = $path->xy_to_n(1,$i); printf "%20b\n", $n; } print "\n"; } } exit 0; } { # parity search my $tree_type_aref = Math::PlanePath::RationalsTree->parameter_info_hash->{'tree_type'}->{'choices'}; foreach my $mult (1,2) { foreach my $add (0, ($mult==2 ? -1 : ())) { foreach my $neg (0, 1) { print "$mult*N+$add neg=$neg\n"; foreach my $tree_type (@$tree_type_aref) { my $path = Math::PlanePath::RationalsTree->new(tree_type => $tree_type); my $str = ''; # for (my $n = 1030; $n < 1080; $n += 1) { for (my $n = 2; $n < 50; $n += 1) { my ($x,$y) = $path->n_to_xy($n); my $value = ($x ^ $y) & 1; $value *= $mult; $value += $add; if ($neg) { $value = -$value; } $str .= "$value,"; } print "$tree_type $str\n"; system "grep -e '$str' ~/OEIS/stripped"; print "\n"; } } } } exit 0; } { require Math::PlanePath::RationalsTree; # SB 11xxxx and 0 or 2 mod 3 # CS 3,5 mod 6 # L 0,4 mod 6 # groups 1,1,3,5,11,21,43,85,171,341,683,1365,2731 # A001045 Jacobsthal a(n-1)+2*a(n-2) # 3*a(n)+(-1)^n = 2^n # Inverse: floor(log_2(a(n))=n-2 for n>=2 # D. E. Knuth, Art of Computer Programming, Vol. 3, Sect. # 5.3.1, Eq. 13. On GCD # Arises in study of sorting by merge insertions and in # analysis of a method for computing GCDs - see Knuth # reference. my $tree_type_aref = Math::PlanePath::RationalsTree->parameter_info_hash->{'tree_type'}->{'choices'}; foreach my $tree_type (@$tree_type_aref) { print "$tree_type\n"; my $path = Math::PlanePath::RationalsTree->new (tree_type => $tree_type); my $count = 0; my $group = 0; my $prev_high_bit = 0b10; foreach my $n ($path->n_start .. 50000) { my ($x,$y) = $path->n_to_xy($n); next unless $x>$y && ($x%2)!=($y%2); # P>Q not both odd if (high_bit($n) != $prev_high_bit) { print "group $group\n"; $prev_high_bit = high_bit($n); $group = 0; } $group++; # printf "%7b, # %d\n", $n, sans_high_bit(sans_high_bit($n))%3; last if $count++ > 9000; } print "\n"; } exit 0; } { # X,Y list by levels require Math::PlanePath::RationalsTree; my $tree_type_aref = Math::PlanePath::RationalsTree->parameter_info_hash->{'tree_type'}->{'choices'}; foreach my $tree_type (@$tree_type_aref) { print "$tree_type\n"; my $path = Math::PlanePath::RationalsTree->new ( # tree_type => 'HCS', tree_type => $tree_type, # tree_type => 'CW', # tree_type => 'SB', ); my $non_monotonic = ''; foreach my $level (0 .. 6) { my $nstart = 2**$level; my $nend = 2**($level+1)-1; my $prev_x = 1; my $prev_y = 0; print "$nstart "; foreach my $n ($nstart .. $nend) { if ($n != $nstart) { print " "; } my ($x,$y) = $path->n_to_xy($n); next unless $x>$y && ($x%2)!=($y%2); # P>Q not both odd print "$x/$y"; unless (frac_lt($prev_y,$prev_x, $y,$x)) { $non_monotonic ||= "at $y/$x"; } $prev_x = $x; $prev_y = $y; } print "\n"; # print " non-monotonic $non_monotonic\n"; } } exit 0; } { # turn list with levels, or parity with levels require Math::NumSeq::PlanePathTurn; my $path = Math::PlanePath::RationalsTree->new(tree_type => 'SB'); my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'Right'); for (my $n = $seq->i_start; $n <= 16384; $n+=1) { # next if $n % 2; if (is_pow2($n)) { printf "\n%5d ", $n; } # my $turn = $seq->ith($n); my ($x,$y) = $path->n_to_xy($n); # my $turn = ($x ^ $y) & 1; my $turn = ($x&1) + 2*($y&1); # if ($n % 8 == 0) { print " "; } print "$turn"; } print "\n"; exit 0; } { # HCS vs Bird require Math::NumSeq::PlanePathTurn; my $hcs = Math::PlanePath::RationalsTree->new(tree_type => 'HCS'); my $bird = Math::PlanePath::RationalsTree->new(tree_type => 'Bird'); my $n = 0b1000000010000000000; my ($x,$y) = $hcs->n_to_xy($n); my $nb = $bird->xy_to_n($x,$y); printf "%10b\n", $n; printf "%10b\n", $nb; exit 0; } { # Minkowski question mark # # cf = [0,a1,a2,...] range 0to1 # (-1)^(k-1) # ? = sum ----------- # k 2^(a1+...+ak-1) # (-1)^(1-1)/2^a1 = 1/2^a1 = 0.000..001 binary # + (-1)^(1-2)/2^(a1+a2) = -1/2^(a1+a2) # = 0.0001 - 0.000000001 # = 0.000011111 # # 0to1 cf = [0,a0,a1,...] # ? = 2*(1 - 2^-a0 + 2^-(a0+a1) - 2^-(a0+a1+a2) + ...) # # ? = # # ?(1/k^n) = 1/2^(k^n-1) # ?(0) = 0 # ?(1/3) = 1/4 require Math::BaseCnv; require Math::BigRat; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); # ?(1/3)=1/4 ?(1/2)=1/2 ?(2/3)=3/4 foreach my $xy ('1/3', '1/2', '2/3') { my ($x,$y) = split m{/}, $xy; try ($x,$y); } foreach my $n ($path->n_start .. 64) { my ($x,$y) = $path->n_to_xy($n); try ($x,$y); } foreach my $xy ('1/3', '1/2', '2/3') { my ($x,$y) = split m{/}, $xy; try ($x,$y); } sub try { my ($x,$y) = @_; require Math::ContinuedFraction; my $cfrac = Math::ContinuedFraction->from_ratio($x,$y); my $cfrac_str = $cfrac->to_ascii; my $n = $path->xy_to_n($x,$y); my $nbits = Math::BaseCnv::cnv($n,10,2); my $mp = minkowski_by_path($x,$y); my $mc = minkowski_by_cfrac($x,$y); my $mpstr = to_binary($mp); my $mcstr = to_binary($mc); print "$x/$y $nbits p=$mp c=$mc $cfrac_str\n"; } # pow=2^level <= N # ? = (2*(N-pow) + 1) / pow # = (2N - 2pow + 1) / pow # = (2N+1)/pow - 2pow/pow # = (2N+1)/pow - 2 # = 2*((N+1/2)/pow - 1) sub minkowski_by_path { my ($x,$y) = @_; my $n = $path->xy_to_n($x,$y); my ($pow,$exp) = round_down_pow($n,2); return Math::BigRat->new(2*$n+1) / $pow - 2; return Math::BigRat->new(2*($n-$pow) + 1) / $pow; return (2*($n-$pow) + 1) / $pow; return (2*$pow-1 - $n) / $pow; return $n / (2*$pow); } # q0, q1, ... # 1 1 1 # ? = 2 * (1 - --- * (1 - ---- * (1 - ---- * (... # 2^q0 2^q1 2^q2 # sub minkowski_by_cfrac { my ($x,$y) = @_; require Math::ContinuedFraction; my $cfrac = Math::ContinuedFraction->from_ratio($x,$y); my $aref = $cfrac->to_array; # first to last ### $aref my $ret = 1; foreach my $q (reverse @$aref) { $ret = 1 - 1/Math::BigRat->new(2)**$q * $ret; } return 2*$ret; } # q0, q1, ... # (-1)^k # ? = sum ------------------- # k 2^(q0+q1+...qk - 1) sub minkowski_by_cfrac_cumul { my ($x,$y) = @_; require Math::ContinuedFraction; my $cfrac = Math::ContinuedFraction->from_ratio($x,$y); my $aref = $cfrac->to_array; ### $aref my $ret = 1; my $sign = Math::BigRat->new(1); my $pos = 0; foreach my $q (@$aref) { $sign = -$sign; $pos += $q; $ret += $sign / (Math::BigInt->new(2) ** $pos); } return 2*$ret; } # pow=2^level <= N # F = (2*(N-pow) + 1) / pow / 2 # = ((N-pow) + 1/2) / pow sub F_by_path { my ($x,$y) = @_; my $n = $path->xy_to_n($x,$y); my ($pow,$exp) = round_down_pow($n,2); return Math::BigRat->new(2*$n+1) / $pow - 2; return Math::BigRat->new(2*($n-$pow) + 1) / $pow; } # q0, q1, ... # # (-1)^k # F = sum ------------------- # k 2^(q0+q1+...qk) sub F_by_cfrac { my ($x,$y) = @_; require Math::ContinuedFraction; my $cfrac = Math::ContinuedFraction->from_ratio($x,$y); my $aref = $cfrac->to_array; ### $aref my $ret = 1; my $sign = Math::BigRat->new(1); my $pos = 0; foreach my $q (@$aref) { $sign = -$sign; $pos += $q; $ret += $sign / (Math::BigInt->new(2) ** $pos); } return $ret; } sub to_binary { my ($n) = @_; my $str = sprintf '%b', int($n); $n -= int($n); if ($n) { $str .= '.'; while ($n) { $n *= 2; if ($n >= 1) { $n -= 1; $str .= '1'; } else { $str .= '0'; } } } return $str; } exit 0; } { # A108356 partial sums # A108357 AYT 2N left or 2N+1 right within a row but not across it # (1+x^2+x^4)/(1-x^8) repeat of 10101000 # 8 7 6 5 4 3 2 1 0-1-2-3-4-5 # 0,0,0,0,1,0,1,0,1,0,0,0,0,0 # -1 0 0 0 0 0 0 0 1 -1 require Math::Polynomial; Math::Polynomial->string_config({ ascending => 1 }); my $num = Math::Polynomial->new(1,0,1,0,1); my $den = Math::Polynomial->new(1,0,0,0,0,0,0,0,-1); { my %seen; my $when = 1; for (;;) { $num <<= 1; my $q = $num / $den; $num %= $den; print "$q $num\n"; if (my $prev = $seen{$num}) { print "at $when repeat of $prev\n"; last; } $seen{$num} = $when++; } exit 0; } { $num <<= 270; $num /= $den; $num = -$num; print $num,"\n"; while ($num) { print $num->coeff(0); $num >>= 1; } print "\n"; exit 0; # 1010001010100010101000101010001010100010101000101010001010100010101 # 101010001010100010101000101010001010100010101000101010001010100010101000 } } { # turn search require Math::NumSeq::PlanePathTurn; my $tree_type_aref = Math::PlanePath::RationalsTree->parameter_info_hash->{'tree_type'}->{'choices'}; foreach my $mult (1,2) { foreach my $add (0, ($mult==2 ? 1 : ())) { foreach my $turn_type ('Left','Right','LSR') { foreach my $neg (0, ($turn_type eq 'LSR' ? 1 : ())) { print "$mult*N+$add $turn_type neg=$neg\n"; foreach my $tree_type (@$tree_type_aref) { my $path = Math::PlanePath::RationalsTree->new(tree_type => $tree_type); my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => $turn_type); my $str = ''; # foreach my $n (1030 .. 1080) { foreach my $n (2 .. 50) { my $value = $seq->ith($mult*$n+$add); if ($neg) { $value = -$value; } $str .= "$value,"; } print "$tree_type $str\n"; system "grep -e '$str' ~/OEIS/stripped"; print "\n"; } } } } } exit 0; } { # count 0-bits below high 1 # 1 2 3 4 5 6 7 8 # 0,1,0,2,1,0,0,3,2,1,1,0,0,0,0,4,3,2,2,1,1,1,1,0,0,0,0,0,0,0,0,5, # 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # SB int(x/y) 1,0,2,0,0,1,3,0,0,0, 0, 1, 1, 2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 1 # count # count high 1-bits, is +1 except at n=2^k # 0,1,1,2,1,1,2,3,1,1,1, 1, 2, 2, 3, 4, 1, 1, 1, 1, 1, 1, 1, 1, 2, foreach my $n (1 .. 32) { my $k = $n; while (! is_pow2($k)) { $k >>= 1; } my ($pow,$exp) = round_down_pow($k,2); print "$exp,"; } print "\n"; exit 0; sub is_pow2 { my ($n) = @_; while ($n > 1) { if ($n & 1) { return 0; } $n >>= 1; } return ($n == 1); } } { # HCS runs my $path = Math::PlanePath::RationalsTree->new (tree_type => 'HCS'); my ($x,$y) = $path->n_to_xy(0b10000001001001000); # \-----/\-/\-/\--/ # 7 3 3 4 # is [6, 3, 3, 5] ($x,$y) = $path->n_to_xy(0b11000001); # |\----/| # 1 6 1 # is [0, 6, 2] require Math::ContinuedFraction; my $cfrac = Math::ContinuedFraction->from_ratio($x,$y); my $cfrac_str = $cfrac->to_ascii; say $cfrac_str; exit 0; } { # A072726 numerator of rationals >= 1 with continued fractions even terms # A072727 denominator # A072728 numerator of rationals >= 1 with continued fraction terms 1,2 only # A072729 denominator require Math::NumSeq::OEIS; require Math::PlanePath::RationalsTree; my $num = Math::NumSeq::OEIS->new (anum => 'A072726'); my $den = Math::NumSeq::OEIS->new (anum => 'A072727'); my $tree_types = Math::PlanePath::RationalsTree->parameter_info_hash->{'tree_type'}->{'choices'}; my @paths = map { Math::PlanePath::RationalsTree->new (tree_type => $_) } @$tree_types; print " ",join(' ',@$tree_types),"\n"; foreach (1 .. 120) { (undef, my $x) = $num->next; (undef, my $y) = $den->next; print "$x/$y"; foreach my $path (@paths) { print " "; my $n = $path->xy_to_n($x,$y); if (! defined $n) { print "undef"; next; } printf '%b', $n; } print "\n"; } exit 0; } { # L-tree OFFSET=0 for 0/1 # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # A174981(n) num 0, 1, 1, 2, 3, 1, 2, 3, 5, 2, 5, 3, 4, 1, 3, # A002487(n+2) den 1, 2, 1, 3, 2, 3, 1, 4, 3, 5, 2, 5, 3, 4, 1, # A174980 den, stern variant my $path = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); foreach my $n (0 .. 15) { my ($x,$y) = $path->n_to_xy($n); $x //= 'undef'; $y //= 'undef'; my $ln = cw_to_l($n); print "$n $x,$y $ln\n"; } sub cw_to_l { my ($n) = @_; $n++; my ($pow,$exp) = round_down_pow($n,2); $n ^= $pow-1; # $n--; # $n |= $pow; return $n-1; } exit 0; } { # 49/22 # ### nbits apply CW: [ # ### '0', # ### '1', # ### '1', # ### '0', # ### '0', # ### '0', # ### '0', # ### '1', # ### '1' # ### ] # HCS # 49/22 # 27/22 X # 5/22 X # 5/17 Y # 5/12 Y # 5/7 Y # 5/2 Y # 3/2 X # 1/2 X # 1/1 Y # 1 . = 1 # 10 .0. = 2 # 100 .0.0. = 3 # 1000 .0.0.0. = 4 # 1 00 1000 10 1 # \/ \--/ \/ ^ # 2 4 2 2 # 0, # 1, . 1 # # 1/2 .0. = .. = 2 -> 2 = 1/2 # 2 .1. = 1,1 -> 0,2 = 0 + 1/(0+1/2) = 2 # # 3/2 .0.0. = ... = 3 = 0 + 1/3 # 1/3 .0.1. = ..1. = 2,1 -> 1,2 = 0+1/(1+1/2) = 2/3 # 2/3 .1.0. = .1.. = 1,2 -> 0,3 = 0+1/(0+1/3) = 3 # 3 .1.1. = 1,1,1 -> 0,1,2 = 0+1/(0+1/(1+1/2)) = 3/2 # # 100 .. 111 SB 1/3 2/3 3/2 3/1 # 5/2 4/3 5/3 1/4 2/5 3/4 3/5 4 1000 .. 1111 SB 1/4 .. 4/1 # CW: 224 11100000 3/16 [0, 5, 3] # HCS: 194 3.0000, 16.0000 194 1_4096 0.000,1.000(1.0000) c=388,389 # 194 = binary 11000010 # 0 5 3 # 1.1.0.0.0.0.1.0. = .1.....1.. = 1,5,2 -> 0,5,3 = 0+1/(5+1/3) = 3/16 # # AYT # 836 49.0000, 22.0000 836 1_268435456 # 1001000101 = 581 # 1101000100 = 836 # |\/\--/\/ # 22 4 2 my $x = 1; my $y = 1; foreach my $nbit (0,0, 1,0,0,0, 1,0, 1) { $y += $x; if (! $nbit) { ($x,$y) = ($y,$x); } } # foreach my $nbit (reverse 0,0, 1,0,0,0, 1,0, 1) { # # foreach my $nbit (reverse 0,0,0,0) { # $x += $y; # if ($nbit) { # ($x,$y) = ($y,$x); # } # } print "$x,$y\n"; require Math::ContinuedFraction; my $cfrac = Math::ContinuedFraction->from_ratio($x,$y); my $cfrac_str = $cfrac->to_ascii; print "$cfrac_str\n"; exit 0; } { # AYT vs continued fraction require Math::ContinuedFraction; require Math::BaseCnv; my $ayt = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); my $level = 10; foreach my $n (1 .. 2**$level) { my ($x,$y) = $ayt->n_to_xy($n); my $cfrac = Math::ContinuedFraction->from_ratio($x,$y); my $cfrac_str = $cfrac->to_ascii; my $nbits = Math::BaseCnv::cnv($n,10,2); printf "%3d %7s %2d/%-2d %s\n", $n, $nbits, $x,$y, $cfrac_str; } exit 0; } { require Math::ContinuedFraction; my $cfrac = Math::ContinuedFraction->from_ratio(29,42); my $cfrac_str = $cfrac->to_ascii; print "$cfrac_str\n"; exit 0; } { # lengths of frac or bits require Math::PlanePath::DiagonalRationals; require Math::PlanePath::CoprimeColumns; require Math::PlanePath::PythagoreanTree; foreach my $path (Math::PlanePath::DiagonalRationals->new, Math::PlanePath::CoprimeColumns->new, Math::PlanePath::PythagoreanTree->new(coordinates=>'PQ'), ) { print join(',', map{cfrac_length($path->n_to_xy($_))} 2 .. 32),"\n"; print join(',', map{bits_length ($path->n_to_xy($_))} 2 .. 32),"\n"; print "\n"; } exit 0; sub bits_length { my ($x,$y) = @_; return sum(0, Math::PlanePath::RationalsTree::_xy_to_quotients($x,$y)); } sub cfrac_length { my ($x,$y) = @_; my @quotients = Math::PlanePath::RationalsTree::_xy_to_quotients($x,$y); return scalar(@quotients); } } { # 167/3 require Math::BigInt; my $path = Math::PlanePath::RationalsTree->new; my $x = Math::BigInt->new(167); my $y = Math::BigInt->new(3); my $n = $path->xy_to_n($x,$y); print $n,"\n"; my $binstr = $n->as_bin; $binstr =~ s/0b//; print $binstr,"\n"; print length($binstr),"\n"; exit 0; } { my $cw = Math::PlanePath::RationalsTree->new(tree_type => 'CW'); my $ayt = Math::PlanePath::RationalsTree->new (tree_type => 'AYT'); my $level = 6; foreach my $cn (2**$level .. 2**($level+1)-1) { my ($cx,$cy) = $cw->n_to_xy($cn); my $an = $ayt->xy_to_n($cx,$cy); my ($z,$c) = cw_to_ayt($cn); my ($t,$u) = ayt_to_cw($an); printf "%5s %b %b %b(%b)%s %b(%b)%s\n", "$cx/$cy", $cn, $an, $z, $c, ($z == $an ? " eq" : ""), $t, $u, ($t == $cn ? " eq" : ""); } exit 0; sub cw_to_ayt { my ($c) = @_; my $z = 0; my $flip = 0; for (my $bit = 1; $bit <= (1 << ($level-1)); $bit <<= 1) { # low to high if ($flip) { $c ^= $bit; } if ($c & $bit) { } else { $z |= $bit; $flip ^= 1; } } $z += (1 << $level); $c &= (1 << $level) - 1; return $z,0; } sub ayt_to_cw { my ($a) = @_; $a &= (1 << $level) - 1; my $t = 0; my $flip = 0; for (my $bit = (1 << ($level-1)); $bit > 0; $bit >>= 1) { # high to low if ($a & $bit) { $a ^= $bit; $t |= $bit; $flip ^= 1; } else { } if ($flip) { $t ^= $bit; } } if (!$flip) { $t = ~$t; } $t &= (1 << $level) - 1; # mask to level $t += (1 << $level); # high 1-bit return ($t,$a); } } { require Math::ContinuedFraction; { my $cf = Math::ContinuedFraction->new([0,10,2,1,8]); print $cf->to_ascii,"\n"; print $cf->brconvergent(4),"\n"; } { my $cf = Math::ContinuedFraction->from_ratio(26,269); print $cf->to_ascii,"\n"; } exit 0; } { my $n = 12590; my $radix = 3; while ($n) { my $digit = $n % $radix; if ($digit == 0) { $digit = $radix; } $n -= $digit; ($n % $radix) == 0 or die; $n /= $radix; print "$digit"; } print "\n"; exit 0; } { my $n = 12590; my $radix = 3; my @digits = digit_split_lowtohigh($n,$radix); my $borrow = 0; foreach my $i (0 .. $#digits) { $digits[$i] -= $borrow; if ($digits[$i] <= 0) { $digits[$i] += $radix; $borrow = 1; } else { $borrow = 0; } } $borrow == 0 or die; print reverse(@digits),"\n"; exit 0; } { my $n = 0; my @digits = (1,2,1,3,1,3,3,2,2); my $power = 1; foreach my $digit (@digits) { $power *= 3; $n += $power * $digit; } print $n; exit 0; } { require Math::ContinuedFraction; my $sb = Math::PlanePath::RationalsTree->new(tree_type => 'SB'); for (my $n = $sb->n_start; $n < 3000; $n++) { my ($x,$y) = $sb->n_to_xy ($n); next if $x > $y; my $cf = Math::ContinuedFraction->from_ratio($x,$y); my $cfstr = $cf->to_ascii; my $cfaref = $cf->to_array; my $cflen = scalar(@$cfaref); my $nhex = sprintf '0x%X', $n; print "$nhex $n $x/$y $cflen $cfstr\n"; } exit 0; } { my $sb = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my $bird = Math::PlanePath::RationalsTree->new(tree_type => 'Bird'); my $level = 5; foreach my $an (2**$level .. 2**($level+1)-1) { my ($ax,$ay) = $sb->n_to_xy($an); my $bn = $bird->xy_to_n($ax,$ay); my ($z,$c) = sb_to_bird($an); my ($t,$u) = bird_to_sb($bn); printf "%5s %b %b %b(%b)%s %b(%b)%s\n", "$ax/$ay", $an, $bn, $z, $c, ($z == $bn ? " eq" : ""), $t, $u, ($t == $an ? " eq" : ""); } exit 0; sub sb_to_bird { my ($n) = @_; for (my $bit = (1 << ($level-1)); $bit > 0; $bit >>= 1) { # high to low $bit >>= 1; $n ^= $bit; } return $n,0; } sub bird_to_sb { my ($n) = @_; for (my $bit = (1 << ($level-1)); $bit > 0; $bit >>= 1) { # high to low $bit >>= 1; $n ^= $bit; } return $n,0; } sub ayt_to_bird { my ($a) = @_; ### bird_to_ayt(): sprintf "%b", $a my $z = 0; my $flip = 1; $a = _reverse($a); for (my $bit = 1; $bit <= (1 << ($level-1)); $bit <<= 1) { # low to high ### a bit: ($a & $bit) ### $flip if ($a & $bit) { if (! $flip) { $z |= $bit; } } else { $flip ^= 1; if ($flip) { $z |= $bit; } } ### z now: sprintf "%b", $z ### flip now: $flip } $z += (1 << $level); $a &= (1 << $level) - 1; return $z,0; } sub bird_to_ayt { my ($b) = @_; $b = _reverse($b); $b &= (1 << $level) - 1; my $t = 0; my $flip = 1; for (my $bit = (1 << ($level-1)); $bit > 0; $bit >>= 1) { # high to low if ($b & $bit) { if ($flip) { $t |= $bit; } $flip ^= 1; } else { if (! $flip) { $t |= $bit; } } # if ($flip) { $t ^= $bit; } } if (!$flip) { $t = ~$t; } $t &= (1 << $level) - 1; $t += (1 << $level); return ($t,0); # $b); } sub _reverse { my ($n) = @_; my $rev = 1; while ($n > 1) { $rev = 2*$rev + ($n % 2); $n = int($n/2); } return $rev; } } { # diatomic 0,1,1,2,1,3,2,3, 1,4,3,5,2,5,3,4, 1,5,4,7,3,8,5,7,2,7,5,8,3,7,4,5,1,6,5,9,4,11,7,10,3,11,8,13,5,12,7,9,2,9,7,12,5,13,8,11,3,10,7,11,4,9,5,6,1,7,6,11,5,14,9,13,4,15,11,18,7,17, my $ayt = Math::PlanePath::RationalsTree->new(tree_type => 'AYT'); foreach my $level (0 .. 3) { foreach my $n (2**$level .. 2**($level+1)-1) { my ($x,$y) = $ayt->n_to_xy($n); print "$x,"; } } print "\n"; my $prev_y = 1; foreach my $level (0 .. 5) { foreach my $n (reverse 2**$level .. 2**($level+1)-1) { my ($x,$y) = $ayt->n_to_xy($n); print "$n $x $y\n"; if ($x != $prev_y) { print "diff\n"; } $prev_y = $y; } } exit 0; } { require Math::PlanePath::RationalsTree; my $path = Math::PlanePath::RationalsTree->new; $, = ' '; say $path->xy_to_n (9,8); say $path->xy_to_n (2,3); say $path->rect_to_n_range (9,8, 2,3); exit 0; } { require Math::PlanePath::RationalsTree; my $path = Math::PlanePath::RationalsTree->new; require Math::BigInt; # my ($n_lo,$n_hi) = $path->xy_to_n (1000,0, 1500,200); my $n = $path->xy_to_n (Math::BigInt->new(1000),1); ### $n ### n: "$n" require Math::NumSeq::All; my $seq = Math::NumSeq::All->new; my $pred = $seq->pred($n); ### $pred exit 0; } { require Math::PlanePath::RationalsTree; my $cw = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); my $drib = Math::PlanePath::RationalsTree->new(tree_type => 'Drib'); my $level = 5; foreach my $an (2**$level .. 2**($level+1)-1) { my ($ax,$ay) = $cw->n_to_xy($an); my $bn = $drib->xy_to_n($ax,$ay); my ($z,$c) = cw_to_drib($an); my ($t,$u) = drib_to_cw($bn); printf "%5s %b %b %b(%b)%s %b(%b)%s\n", "$ax/$ay", $an, $bn, $z, $c, ($z == $bn ? " eq" : ""), $t, $u, ($t == $an ? " eq" : ""); } exit 0; sub cw_to_drib { my ($n) = @_; for (my $bit = 2; $bit <= (1 << ($level-1)); $bit <<= 2) { # low to high $n ^= $bit; } return $n,0; } sub drib_to_cw { my ($n) = @_; for (my $bit = 2; $bit <= (1 << ($level-1)); $bit <<= 2) { # low to high $n ^= $bit; } return $n,0; } } { require Math::PlanePath::RationalsTree; my $path = Math::PlanePath::RationalsTree->new ( tree_type => 'AYT', tree_type => 'CW', tree_type => 'SB', ); foreach my $y (reverse 1 .. 10) { foreach my $x (1 .. 10) { my $n = $path->xy_to_n($x,$y); if (! defined $n) { $n = '' } printf (" %4s", $n); } print "\n"; } exit 0; } { require Math::PlanePath::RationalsTree; my $path = Math::PlanePath::RationalsTree->new ( tree_type => 'AYT', tree_type => 'CW', tree_type => 'SB', ); foreach my $y (2 .. 10) { my $prev = 0; foreach my $x (1 .. 100) { my $n = $path->xy_to_n($x,$y) || next; if ($n < $prev) { print "not monotonic at X=$x,Y=$y n=$n prev=$prev\n"; } $prev = $n; } } exit 0; } sub frac_lt { my ($p1,$q1, $p2,$q2) = @_; return ($p1*$q2 < $p2*$q1); } Math-PlanePath-129/devel/hilbert-diamond.pl0000644000175000017500000000312712023454743016413 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.006; use strict; use warnings; use Image::Base::PNGwriter; use Math::PlanePath::HilbertCurve; #use Smart::Comments; my $width = 500; my $height = 500; my $image = Image::Base::PNGwriter->new (-width => $width, -height => $height); my $scale = 20; sub rotate { my ($x, $y) = @_; return ($scale * ($x + $y) + $scale, $scale * ($x - $y) + int($height/2)); } my $path = Math::PlanePath::HilbertCurve->new; my ($prev_x, $prev_y) = $path->n_to_xy(0); my ($prev_rx, $prev_ry) = rotate($prev_x, $prev_y); foreach my $n (1 .. 64) { my ($x, $y) = $path->n_to_xy($n); ### xy: "$x,$y" my ($rx, $ry) = rotate($x,$y); $image->line ($rx,$ry, $prev_rx,$prev_ry, 'white'); ### line: "$rx,$ry, $prev_rx,$prev_ry" ($prev_x, $prev_y) = ($x, $y); ($prev_rx, $prev_ry) = ($rx, $ry); } $image->save ('/tmp/x.png'); system ('xzgv /tmp/x.png'); exit 0; Math-PlanePath-129/devel/cellular-rule62.pl0000644000175000017500000000314311646221732016267 0ustar gggg#!/usr/bin/perl -w # Copyright 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use strict; my $rule = 57; my @table = map {($rule & (1<<$_)) ? 1 : 0} 0 .. 7; print join(',',@table),"\n"; my @mirror = (map { ($_&4?1:0)+($_&2?2:0)+($_&1?4:0) } 0 .. 7); print "join table ",oct('0b'. join('', map{$table[$_]} reverse 0 .. 7)),"\n"; print "join table ",oct('0b'. join('', map{$table[$mirror[$_]]} reverse 0 .. 7)),"\n"; # uncomment this to run the ### lines #use Devel::Comments; my @a = ([(0)x50, 1, (0)x50]); print_line(0); foreach my $level (1..20) { my $prev = $a[$level-1]; ### @a foreach my $i (1 .. $#$prev) { my $p = 4*($prev->[$i-1]||0) + 2*($prev->[$i]||0) + ($prev->[$i+1]||0); $a[$level]->[$i] = $table[$p]; } print_line($level); } sub print_line { my ($level) = @_; foreach my $i (0 .. $#{$a[$level]}) { my $c = $a[$level]->[$i]; if ($table[0]) { print $c ? ' ' : "*"; } else { print $c ? '*' : " "; } } print "\n"; } exit 0; Math-PlanePath-129/devel/l-tiling.pl0000644000175000017500000000532312142113042015052 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use strict; use Math::PlanePath::LTiling; # LTiling A112539 Half-baked Thue-Morse: at successive steps the sequence or its bit-inverted form is appended to itself. { # A139351 count 1-bits at even positions # A139352 count 1-bits at odd positions # A112539 1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,0,1,0,1,1,0,1,0, OFFSET=1 # A139351 0,1,0,1,1,2,1,2,0,1,0,1,1,2,1,2,1,2,1,2,2,3,2,3, OFFSET=0 # A139352 0,0,1,1,0,0,1,1,1,1,2,2,1,1,2,2,0,0,1,1,0,0,1,1, OFFSET=0 sub count_even_1_bits { my ($n) = @_; my $count = 0; while ($n) { $count += ($n & 1); $n >>= 2; } return $count; } foreach my $n (2 .. 30) { print count_even_1_bits($n),","; } print "\n"; exit 0; } { # X,Y parity # block 0, 2 unchanged 00 10 # block 1, 3 flip 01 11 # so flip by every second bit starting from lowest # # "middle" invert each # "ends" duplicate each # "all" 011 base then floor(n/3) inversions my $path = Math::PlanePath::LTiling->new; foreach my $n ($path->n_start .. 70) { my ($x,$y) = $path->n_to_xy($n); print(($x+$y)%2); } print "\n"; $path = Math::PlanePath::LTiling->new (L_fill => 'left'); foreach my $n ($path->n_start .. 70) { my ($x,$y) = $path->n_to_xy($n); print(($x+$y)%2); } print "\n"; $path = Math::PlanePath::LTiling->new (L_fill => 'upper'); foreach my $n ($path->n_start .. 70) { my ($x,$y) = $path->n_to_xy($n); print(($x+$y)%2); } print "\n"; $path = Math::PlanePath::LTiling->new (L_fill => 'ends'); for (my $n = $path->n_start+0; $n < 70; $n+=2) { my ($x,$y) = $path->n_to_xy($n); print(($x+$y)%2); } print "\n"; exit 0; } # # +-------+ # | | # | | # | | # | +---+ # | | | # | | +-+ # | | | | # +---+-| +-+-+---+ # | | | | | | # | +-| +---+ | | # | | | | | | # +-| +---+---+ | # | | | | | # | +---+ | | # | | | | # +---+---+-------+ # # # Math-PlanePath-129/devel/grep-various-values.pl0000644000175000017500000017345213777201031017277 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2019, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # DragonCurve,arms=2 boundary (by powers full diffs all): # 20,28,52,92,148,252,436,732,1236,2108,3572,6044 # match 20,28,52,92,148,252,436,732,1236,2108,3572,6044 # [HALF] # A052537 Expansion of (1-x)/(1-x-2x^3). # A052537 ,1,0,0,2,2,2,6,10,14,26,46,74,126,218,366,618,1054,1786,3022,5130,8702,14746,25006,42410,71902,121914,206734,350538,594366,1007834,1708910,2897642,4913310,8331130,14126414,23953034,40615294,68868122,116774190,198004778, # Rationals tree inter-row area # 2*area = A048487 a(n) = 5*2^n-4 T(4,n), array T given by A048483. # area = A051633 5*2^n - 2. # same A131051 Row sums of triangle A133805. # A126284 5*2^n-4*n-5 total*2 # # alt paper # A129284 A129150(n) / 4. # A129285 A129151(n) / 27. # Math::PlanePath::GosperReplicate unit hexagons boundary length # A178674 = 3^n+3 use 5.010; use strict; use List::Util 'min', 'max'; use List::Pairwise; use Math::BigInt try => 'GMP'; # for bignums in reverse-add steps use Math::BaseCnv; use Math::Libm 'hypot'; use Module::Load; use lib 'xt'; use MyOEIS; use Math::OEIS::Grep; $|=1; # uncomment this to run the ### lines # use Smart::Comments; { # permutation between two paths my $must_one; $must_one = 'ComplexMinus'; # one of "from" or "to" require Math::NumSeq::PlanePathCoord; my @choices = @{Math::NumSeq::PlanePathCoord->parameter_info_hash ->{'planepath'}->{'choices'}}; @choices = grep {$_ ne 'CellularRule'} @choices; @choices = grep {$_ ne 'Rows'} @choices; @choices = grep {$_ ne 'Columns'} @choices; @choices = grep {$_ ne 'ArchimedeanChords'} @choices; @choices = grep {$_ ne 'MultipleRings'} @choices; @choices = grep {$_ ne 'VogelFloret'} @choices; @choices = grep {$_ ne 'PythagoreanTree'} @choices; @choices = grep {$_ ne 'PeanoHalf'} @choices; @choices = grep {$_ !~ /SquaRecurve|EToothpick|LToothpick|Surround|Peninsula/} @choices; @choices = grep {$_ ne 'CornerReplicate'} @choices; @choices = grep {$_ ne 'ZOrderCurve'} @choices; unshift @choices, 'CornerReplicate', 'ZOrderCurve'; # @choices = ('PythagoreanTree'); my $num_choices = scalar(@choices); print "$num_choices choices\n"; my @path_objects; my %path_fullnames; foreach my $name (@choices) { my $class = "Math::PlanePath::$name"; Module::Load::load($class); my $parameters = parameter_info_list_to_parameters ($class->parameter_info_list); foreach my $p (@$parameters) { my $path_object = $class->new (@$p); push @path_objects, $path_object; $path_fullnames{$path_object} = "$name ".join(',',@$p); } } my $num_path_objects = scalar(@path_objects); print "total path objects $num_path_objects\n"; my $start_t = time(); my $t = $start_t-8; my $i = 0; # until ($path_objects[$i]->isa('Math::PlanePath::DiamondArms')) { # $i++; # } # while ($path_objects[$i]->isa('Math::PlanePath::PyramidSpiral')) { # $i++; # } my $start_permutations = $i * ($num_path_objects-1); my $num_permutations = $num_path_objects * ($num_path_objects-1); open DEBUG, '>/tmp/permutations.out' or die; select DEBUG or die; $| = 1; # autoflush select STDOUT or die; for ( ; $i <= $#path_objects; $i++) { my $from_path = $path_objects[$i]; my $from_fullname = $path_fullnames{$from_path}; my $n_start = $from_path->n_start; (my $from_modname = $from_fullname) =~ s/ .*//; ### $i ### $from_fullname ### $from_modname PATH: foreach my $j (0 .. $#path_objects) { if (time()-$t < 0 || time()-$t > 10) { my $upto_permutation = $i*$num_path_objects + $j || 1; my $rem_permutation = $num_permutations - ($start_permutations + $upto_permutation); my $done_permutations = ($upto_permutation-$start_permutations); my $percent = 100 * $done_permutations / $num_permutations || 1; my $t_each = (time() - $start_t) / $done_permutations; my $done_per_second = $done_permutations / (time() - $start_t); my $eta = int($t_each * $rem_permutation); my $s = $eta % 60; $eta = int($eta/60); my $m = $eta % 60; $eta = int($eta/60); my $h = $eta; my $eta_str = sprintf '%d:%02d:%02d', $h,$m,$s; print "$upto_permutation / $num_permutations est $eta_str (each $t_each)\n"; $t = time(); } next if $i == $j; my $to_path = $path_objects[$j]; next if $to_path->n_start != $n_start; my $to_fullname = $path_fullnames{$to_path}; (my $to_modname = $to_fullname) =~ s/ .*//; my $name = "$from_fullname -> $to_fullname"; if ($must_one) { next unless $from_modname eq $must_one || $to_modname eq $must_one; } print DEBUG "$name\n"; my $str = ''; my @values; foreach my $n ($n_start+2 .. $n_start+50) { my ($x,$y) = $from_path->n_to_xy($n) or next PATH; my $pn = $to_path->xy_to_n($x,$y) // next PATH; $str .= "$pn,"; push @values, $pn; } Math::OEIS::Grep->search(name => $name, array => \@values); } } exit 0; } { # binomials in path order require Math::PlanePath::BinaryTerms; *binomial = \&Math::PlanePath::BinaryTerms::_binomial; require Math::NumSeq::PlanePathCoord; foreach my $pathspec ('Diagonals', 'DiagonalsOctant', 'Corner', 'CornerAlternating', 'PowerArray', 'Staircase', 'StaircaseAlternating', ) { my $path = Math::NumSeq::PlanePathCoord::_planepath_name_to_object($pathspec); my $n_start = $path->n_start; my @values; for (my $n = Math::BigInt->new($n_start)+2; $n < $n_start+40; $n++) { my ($x,$y) = $path->n_to_xy($n) or last; push @values, binomial($x+$y,$y); } Math::OEIS::Grep->search(name => $pathspec, array => \@values); foreach my $i (1..$#values) { $values[$i] += $values[$i-1]; } Math::OEIS::Grep->search(name => "cumulative $pathspec", array => \@values); } exit 0; } { # rows and columns in a quadrant require Math::PlanePath::PowerArray; my $path = Math::PlanePath::PowerArray->new(radix=>3); foreach my $x (0 .. 5) { my @values = map {$path->xy_to_n(Math::BigInt->new($x), Math::BigInt->new($_))} 0 .. 10; Math::OEIS::Grep->search(name => "column x=$x", array => \@values); } exit; } { # net direction as total turn require Math::NumSeq::PlanePathTurn; my @choices = @{Math::NumSeq::PlanePathTurn->parameter_info_hash ->{'planepath'}->{'choices'}}; @choices = grep {$_ ne 'BinaryTerms'} @choices; # bit slow yet @choices = grep {$_ =~ /Curve/} @choices; my @turn_type_choices = @{Math::NumSeq::PlanePathTurn->parameter_info_hash ->{'turn_type'}->{'choices'}}; push @turn_type_choices, 'Turn4','Turn4n', 'TTurn6', 'TTurn6n', 'TTurn3'; # force # @turn_type_choices = 'TTurn3'; # force # @choices = ('ComplexMinus'); my %seen; foreach my $path_name (@choices) { my $path_class = "Math::PlanePath::$path_name"; Module::Load::load($path_class); my $parameters = parameter_info_list_to_parameters($path_class->parameter_info_list); PATH: foreach my $p ([], # paths with no parameters # @$parameters ) { print "\n$path_class ",join(',',@$p),"\n"; my $path = $path_class->new (@$p); foreach my $turn_type (@turn_type_choices) { my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => $turn_type); print "$turn_type\n"; my $name = "$path_name $turn_type ".join(',',@$p); my $dir = 0; my @values; my $all_zeros = 1; my $all_ones = 1; foreach (1 .. 40) { push @values, $dir; my ($i,$value) = $seq->next or last; $dir += $value; if ($value != 0) { $all_zeros = 0; } if ($value != 1) { $all_ones = 0; } } next if $all_zeros || $all_ones; shift @values; shift @values; next unless @values; print "$turn_type ",join(', ',@values),"\n"; Math::OEIS::Grep->search(name => $name, array => \@values); } } } exit 0; } { # N on axes my @dir8_to_dx = (1, 1, 0,-1, -1, -1, 0, 1); my @dir8_to_dy = (0, 1, 1, 1, 0, -1, -1,-1); my @dir8_to_line_type = ("X_axis", "Diagonal", "Y_axis", "Diagonal_NW", "X_neg", "Diagonal_SW", "Y_neg", "Diagonal_SE"); require Math::NumSeq::PlanePathCoord; require Math::NumSeq::PlanePathN; my @choices = @{Math::NumSeq::PlanePathCoord->parameter_info_hash ->{'planepath'}->{'choices'}}; @choices = grep {$_ ne 'BinaryTerms'} @choices; # bit slow yet @choices = grep {$_ =~ /Gray/} @choices; # force # @choices = ('ComplexMinus'); my %seen; foreach my $path_name (@choices) { print "$path_name\n"; my $path_class = "Math::PlanePath::$path_name"; Module::Load::load($path_class); my $parameters = parameter_info_list_to_parameters($path_class->parameter_info_list); PATH: foreach my $p (@$parameters) { my $path = $path_class->new (@$p); foreach my $dir (0 .. 7) { my $line_type = $dir8_to_line_type[$dir]; my $seq = Math::NumSeq::PlanePathN->new (planepath_object => $path, line_type => $line_type); my $anum = $seq->oeis_anum; print "$line_type seq anum ",($anum//'undef'),"\n"; next if defined $anum; my $name = "$path_name dir=$dir ".join(',',@$p); my $dx = $dir8_to_dx[$dir]; my $dy = $dir8_to_dy[$dir]; my $x = 2*$dx; my $y = 2*$dy; my @values; foreach my $i (4 .. 30) { my $value = $path->xy_to_n($x,$y) // last; push @values, $value; $x += $dx; $y += $dy; } next unless @values; Math::OEIS::Grep->search(name => $name, array => \@values); } } } exit 0; } { # X,Y at N=2^k require Math::NumSeq::PlanePathCoord; my @choices = @{Math::NumSeq::PlanePathCoord->parameter_info_hash ->{'planepath'}->{'choices'}}; @choices = grep {$_ ne 'CellularRule'} @choices; # @choices = grep {$_ ne 'Rows'} @choices; # @choices = grep {$_ ne 'Columns'} @choices; @choices = grep {$_ ne 'ArchimedeanChords'} @choices; @choices = grep {$_ ne 'TheodorusSpiral'} @choices; @choices = grep {$_ ne 'MultipleRings'} @choices; @choices = grep {$_ ne 'VogelFloret'} @choices; @choices = grep {$_ ne 'UlamWarburtonAway'} @choices; @choices = grep {$_ !~ /Hypot|ByCells|SumFractions|WythoffTriangle/} @choices; # @choices = grep {$_ ne 'PythagoreanTree'} @choices; # @choices = grep {$_ ne 'PeanoHalf'} @choices; @choices = grep {$_ !~ /EToothpick|LToothpick|Surround|Peninsula/} @choices; # # @choices = grep {$_ ne 'CornerReplicate'} @choices; @choices = grep {$_ ne 'HilbertSides'} @choices; unshift @choices, 'HilbertSides'; my $num_choices = scalar(@choices); print "$num_choices choices\n"; my @path_objects; my %path_fullnames; foreach my $name (@choices) { my $class = "Math::PlanePath::$name"; ### $class Module::Load::load($class); my $parameters = parameter_info_list_to_parameters ($class->parameter_info_list); foreach my $p (@$parameters) { my $path_object = $class->new (@$p); push @path_objects, $path_object; $path_fullnames{$path_object} = "$name ".join(',',@$p); } } my $num_path_objects = scalar(@path_objects); print "total path objects $num_path_objects\n"; my $start_t = time(); my $t = $start_t-8; my $i = 0; # until ($path_objects[$i]->isa('Math::PlanePath::TerdragonCurve')) { # $i++; # } for ( ; $i <= $#path_objects; $i++) { my $path = $path_objects[$i]; my $fullname = $path_fullnames{$path}; print "$fullname\n"; foreach my $coord_idx (0, 1) { my $fullname = $fullname." ".($coord_idx?'Y':'X'); my @values; for (my $k = Math::BigInt->new(1); $k <= 12; $k++) { my ($n_lo, $n_hi) = $path->level_to_n_range($k); $n_hi //= 2**$k; my @coords = $path->n_to_xy($n_hi); my $value = $coords[$coord_idx]; push @values, $value; } shift @values; Math::OEIS::Grep->search(array => \@values, name => $fullname); } } exit 0; } { # single, double, triple visited counts in levels require Math::NumSeq::PlanePathCoord; foreach my $elem ( # curves with overlaps only ['HilbertSides', 2], ['TerdragonCurve', 3], ['R5DragonCurve', 5], ['AlternatePaper', 4], ['AlternatePaper', 2], ['CCurve', 2], ['DragonCurve', 2], ) { my ($name, $radix) = @$elem; print "$name\n"; my $path = Math::NumSeq::PlanePathCoord::_planepath_name_to_object($name); my $n_start = $path->n_start; my (@singles, @doubles, @triples); foreach my $inc_type ('powers') { for (my $level = 3; ; $level++) { my $n_end = $radix**$level; last if $n_end > 20_000; last if @singles > 25; my @counts = path_n_to_visit_counts($path, $n_end); push @singles, $counts[0] || 0; push @doubles, $counts[1] || 0; push @triples, $counts[2] || 0; print "$level $n_end $singles[-1] $doubles[-1] $triples[-1]\n"; } { shift_off_zeros(\@singles); print join(',',@singles),"\n"; Math::OEIS::Grep->search(array => \@singles, name => 'singles'); } { shift_off_zeros(\@doubles); print join(',',@doubles),"\n"; Math::OEIS::Grep->search(array => \@doubles, name => 'doubles'); } if ($triples[-1]) { shift_off_zeros(\@triples); print join(',',@triples),"\n"; Math::OEIS::Grep->search(array => \@triples, name => 'triples'); } print "\n"; } } exit 0; sub path_n_to_visit_counts { my ($path, $n_end) = @_; my @counts; foreach my $n ($path->n_start .. $n_end) { my @n_list = $path->n_to_n_list($n); if ($n_list[0] == $n) { @n_list = grep {$_<=$n_end} @n_list; $counts[scalar(@n_list)]++; } } shift @counts; return @counts; } } { # X,Y repeat count require Math::NumSeq::PlanePathCoord; foreach my $elem ( # curves with overlaps only ['HilbertSides', 2], ['DragonCurve', 2], ['R5DragonCurve', 5], ['CCurve', 2], ['TerdragonCurve', 3, 'triangular'], ['AlternatePaper', 4], ['AlternatePaper', 2], ) { my ($name, $radix, $lattice_type) = @$elem; $lattice_type ||= 'square'; my $path = Math::NumSeq::PlanePathCoord::_planepath_name_to_object($name); print "$name\n"; { my @values; foreach my $n (15 .. 40) { my @n_list = $path->n_to_n_list($n); my $count = scalar(@n_list); push @values, $count; } print "\n$name counts:\n"; shift_off_zeros(\@values); print join(',',@values),"\n"; Math::OEIS::Grep->search(array => \@values); array_diffs(\@values); Math::OEIS::Grep->search(array => \@values, name => "diffs"); } if (0) { my @values; foreach my $level (3 .. 8) { my $count = 0; my $n_hi = $radix**($level+1) - 1; last if $n_hi > 50_000; foreach my $n ($radix**$level .. $n_hi) { my @n_list = $path->n_to_n_list($n); $count += scalar(@n_list); } push @values, $count; } # if ($diffs) { # foreach my $i (reverse 1 .. $#areas) { # $areas[$i] -= $areas[$i-1]; # } print "\n$name total in powers $radix\n"; shift_off_zeros(\@values); print join(',',@values),"\n"; Math::OEIS::Grep->search(array => \@values); print "\n"; array_diffs(\@values); Math::OEIS::Grep->search(array => \@values, name => "diffs"); } } exit 0; sub array_diffs { my ($aref) = @_; foreach my $i (0 .. $#$aref-1) { $aref->[$i] = $aref->[$i+1] - $aref->[$i]; } $#$aref--; } } { # NSEW segment counts # AlternatePaper A005418, A051437, A122746=A032085, A007179 # cf Wests A122746 = area increment # my $radix = 2; my $name = 'Math::PlanePath::CCurve'; $name = 'Math::PlanePath::AlternatePaper'; $name = 'Math::PlanePath::DragonCurve'; # A038503, A038504, A038505, A000749 same CCurve $name = 'Math::PlanePath::DragonMidpoint'; # x, x, 2*A038505, 2*A000749 $name = 'Math::PlanePath::TerdragonMidpoint'; $radix=3; # none $name = 'Math::PlanePath::PeanoCurve'; $radix=3; $name = 'Math::PlanePath::BetaOmega'; $radix=2; $name = 'Math::PlanePath::KochelCurve'; $radix=2; $name = 'Math::PlanePath::CincoCurve'; $radix=25; $name = 'Math::PlanePath::WunderlichMeander'; $radix=3; # none $name = 'Math::PlanePath::KochCurve'; $radix=4; # a=A087433,b,c=2*A081674,d,e=A081674,x $name = 'Math::PlanePath::KochCurve'; $radix=2; # a=A036557,e=A000773 $name = 'Math::PlanePath::DekkingCentres'; $radix=25; # NE=NW=SW=SE=A218728=sum 25^i $name = 'Math::PlanePath::HIndexing'; $radix=4; # A007583,A079319,A020988=(2/3)*(4^n-1),2*A006095 $name = 'Math::PlanePath::QuintetCurve'; $radix=5; # QuintetCentres $name = 'Math::PlanePath::QuadricCurve'; $radix=8; # 2*A063481, A013730=2^(3n+1), 2*A059409, A013730=2^(3n+1) $name = 'Math::PlanePath::WunderlichSerpentine,serpentine_type=coil'; $radix=3; # none $name = 'Math::PlanePath::SierpinskiCurve'; $radix=4; # A079319,A007581,A002450,A006095=(2^n-1)*(2^(n-1) -1)/3,A203241,A006095,A002450,A076024=(2^n+4)*(2^n-1)/6 $name = 'Math::PlanePath::SierpinskiCurveStair'; $radix=4; # A093069=Kynea,A099393,A060867,A020515 $name = 'Math::PlanePath::SierpinskiArrowheadCentres'; $radix=3; # West=A094555 $name = 'Math::PlanePath::SierpinskiArrowhead'; $radix=3; # West=A094555 $name = 'Math::PlanePath::FibonacciWordFractal'; $radix=2; $name = 'Math::PlanePath::AlternatePaperMidpoint'; # 2*A005418 cf fxtbook, A052957=2*A051437altN, A233411, A014236 $name = 'Math::PlanePath::DekkingCurve'; $radix=25; # North=South, West=A060870=Cinco.West=sum 5^i $name = 'Math::PlanePath::HilbertSpiral'; $radix=2; $name = 'Math::PlanePath::HilbertCurve'; $radix=4; # A083885, diff 4^k A123641 $name = 'Math::PlanePath::TerdragonCurve'; $radix=3; # A092236, A135254, A133474 $name = 'Math::PlanePath::R5DragonCurve'; $radix=5; # none require Math::NumSeq::PlanePathCoord; my $path = Math::NumSeq::PlanePathCoord::_planepath_name_to_object($name); my %count; my %count_arrays; my $n = 0; my @dxdy_strs = List::Pairwise::mapp {"$a,$b"} $path->_UNDOCUMENTED__dxdy_list; require Math::NumSeq::Fibonacci; require Math::NumSeq::Fibbinary; my $fib = Math::NumSeq::Fibonacci->new; my $fibbinary = Math::NumSeq::Fibbinary->new; foreach my $k (0 .. 10) { my $n_end = $radix**$k; # $n_end = $k; # $n_end = $fib->ith(2*$k); last if $n_end > 500_000; for ( ; $n < $n_end; $n++) { my ($dx,$dy) = $path->n_to_dxdy($n); $count{"$dx,$dy"}++; } printf "k=%2d ", $k; foreach my $dxdy (@dxdy_strs) { my $a = $count{$dxdy} || 0; my $aref = ($count_arrays{$dxdy} ||= []); # push @$aref, $a - $radix**($k-1); # diff from radix^k push @$aref, $a; # $a = $fibbinary->ith($a); my $ar = Math::BaseCnv::cnv($a,10,$radix); printf " %18s", $ar; } print "\n"; } my $trim = 1; foreach my $dxdy (@dxdy_strs) { my $aref = $count_arrays{$dxdy} || []; splice @$aref, 0, $trim; # @$aref = MyOEIS::first_differences(@$aref); print "$dxdy\n"; print "is ", join(',',@$aref),"\n"; Math::OEIS::Grep->search(array => \@$aref, name => $dxdy); } # print "\n"; # foreach my $k (0 .. $#a) { # my $h = int($k/2); # printf "%3d,", $d[$k]; # } # print "\n"; exit 0; } { # boundary and area, variations convex hull, minrectangle, etc # Terdragon convex hull 14 points # Dragon convex hull 10 points, arms=4 12 points require Math::Geometry::Planar; require Math::NumSeq::PlanePathCoord; foreach my $elem ( # curves with overlaps only ['TerdragonCurve', 3, 'triangular'], ['TerdragonCurve,arms=6', 3, 'triangular'], ['CCurve', 2], ['DragonCurve', 2], ['DragonCurve,arms=3', 2], ['DragonCurve,arms=2', 2], ['R5DragonCurve', 5], ['DragonCurve,arms=4', 2], ['AlternatePaper', 2], ['AlternatePaper', 4], ) { my ($name, $radix, $lattice_type) = @$elem; $lattice_type ||= 'square'; print "$name\n"; my $path = Math::NumSeq::PlanePathCoord::_planepath_name_to_object($name); my $n_start = $path->n_start; my $arms = $path->arms_count; foreach my $inc_type ('powers', '1', ) { foreach my $diffs ('', 'diffs') { foreach my $convex_type ( # 'bbox', 'minrectangle', # 'convex', # 'full', # ($inc_type eq 'powers' # ? ('left','right') # : ()), ) { my @areas; my @boundaries; for (my $level = ($inc_type eq 'powers' ? 0 : 3); ; $level++) { my $n_limit; if ($inc_type eq 'powers') { unless ((undef, $n_limit) = $path->level_to_n_range($level)) { print "no levels for ",ref $path,"\n"; next; } } else { $n_limit = $n_start + $level; } last if $n_limit > 20_000; last if @areas > 25; my $side = ($convex_type eq 'right' ? 'right' : $convex_type eq 'left' ? 'left' : 0); print "n_limit=$n_limit side=$side\n"; my $points = MyOEIS::path_boundary_points ($path, $n_limit, lattice_type => $lattice_type, side => $side); ### $n_limit ### $points my $area; my $convex_area; my $boundary; if (@$points <= 1) { $area = 0; $boundary = 0; } elsif (@$points == 2) { $area = 0; my $dx = $points->[0]->[0] - $points->[1]->[0]; my $dy = $points->[0]->[1] - $points->[1]->[1]; my $h = $dx*$dx + $dy*$dy*($lattice_type eq 'triangular' ? 3 : 0); $boundary = 2*sqrt($h); } else { my $polygon = Math::Geometry::Planar->new; $polygon->points($points); if (($convex_type eq 'convex' || $convex_type eq 'minrectangle') && @$points >= 5) { $polygon = $polygon->convexhull2; $points = $polygon->points; } if ($convex_type eq 'bbox') { $polygon = $polygon->bbox; $points = $polygon->points; } if ($convex_type eq 'minrectangle') { if (@$points <= 16) { print " ",points_str($points),"\n"; } $polygon = $polygon->minrectangle; $points = $polygon->points; } $area = $polygon->area; if ($lattice_type eq 'triangular') { foreach my $p (@$points) { $p->[1] *= sqrt(3); # $p->[0] *= 1/2; # $p->[1] *= sqrt(3)/2; } $polygon->points($points); } $boundary = $polygon->perimeter; } if ($convex_type eq 'right' || $convex_type eq 'left') { $boundary = scalar(@$points) - 1; # my ($end_x,$end_y) = $path->n_to_xy($n_limit); # $boundary -= hypot($end_x,$end_y); # $boundary = float_error($boundary); } push @areas, $area; push @boundaries, $boundary; my $notint = ($boundary == int($boundary) ? '' : ' (not int)'); my $num_points = scalar(@$points); print "$level $n_limit points=$num_points area=$area boundary=$boundary$notint $convex_type\n"; if (@$points <= 10) { print " ",points_str($points),"\n"; } if (0) { require Image::Base::GD; my $width = 800; my $height = 700; my $image = Image::Base::GD->new (-width => $width, -height => $height); $image->rectangle (0,0, $width-1,$height-1, 'black'); my $x_max = 0; my $x_min = 0; my $y_max = 0; my $y_min = 0; foreach my $p (@$points) { my ($x,$y) = @$p; $x_max = max($x_max, $x); $y_max = max($y_max, $y); $x_min = min($x_min, $x); $y_min = min($y_min, $y); } my $x_size = $x_max - $x_min; my $y_size = $y_max - $y_min; $x_size *= 1.1; $y_size *= 1.1; my $x_scale = $width / $x_size; my $y_scale = $height / ($y_size || 1); my $scale = min($x_scale,$y_scale); my $x_mid = ($x_min + $x_max) / 2; my $y_mid = ($y_min + $y_max) / 2; my $convert = sub { my ($x,$y) = @_; $x -= $x_mid; $y -= $y_mid; $x *= $scale; $y *= $scale; $x += $width/2; $y = $height/2 - $y; return ($x,$y); }; { my ($x,$y) = $convert->(0,0); $image->ellipse ($x-3,$y-3, $x+3,$y+3, 'white', 1); } foreach my $i (0 .. $#$points) { my ($x1,$y1) = @{$points->[$i-1]}; my ($x2,$y2) = @{$points->[$i]}; ($x1,$y1) = $convert->($x1,$y1); ($x2,$y2) = $convert->($x2,$y2); $image->line ($x1,$y1, $x2,$y2, 'white'); } $image->save('/tmp/x.png'); require IPC::Run; IPC::Run::run (['xzgv','/tmp/x.png']); } } if ($diffs) { foreach my $i (reverse 1 .. $#areas) { $areas[$i] -= $areas[$i-1]; } foreach my $i (reverse 1 .. $#boundaries) { $boundaries[$i] -= $boundaries[$i-1]; } shift @areas; shift @boundaries; } foreach my $alt_type (# 'even','odd', 'all') { my @areas = @areas; my @boundaries = @boundaries; if ($alt_type eq 'odd') { aref_keep_odds(\@areas); aref_keep_odds(\@boundaries); } if ($alt_type eq 'even') { aref_keep_evens(\@areas); aref_keep_evens(\@boundaries); } print "\n$name area (by $inc_type $convex_type $diffs $alt_type):\n"; shift_off_zeros(\@areas); print join(',',@areas),"\n"; Math::OEIS::Grep->search(array => \@areas); print "\n$name boundary (by $inc_type $convex_type $diffs $alt_type):\n"; shift_off_zeros(\@boundaries); print join(',',@boundaries),"\n"; Math::OEIS::Grep->search(array => \@boundaries); print "\n"; } } } } } exit 0; sub points_str { my ($points) = @_; ### points_str(): $points my $count = scalar(@$points); return "count=$count ".join(' ',map{join(',',@$_)}@$points) } # shift any leading zeros off @$aref sub shift_off_zeros { my ($aref) = @_; while (@$aref && ! $aref->[0]) { shift @$aref; } } sub aref_keep_odds { my ($aref) = @_; @$aref = map { $_ & 1 ? $aref->[$_] : () } 0 .. $#$aref; } sub aref_keep_evens { my ($aref) = @_; @$aref = map { $_ & 1 ? () : $aref->[$_] } 0 .. $#$aref; } BEGIN { my @dir6_to_dx = (2, 1,-1,-2, -1, 1); my @dir6_to_dy = (0, 1, 1, 0, -1,-1); sub path_boundary_points_triangular { my ($path, $n_limit) = @_; my @points; my $x = 0; my $y = 0; my $dir6 = 4; my @n_list = ($path->n_start); for (;;) { ### at: "$x, $y dir6 = $dir6" push @points, [$x,$y]; $dir6 -= 2; # rotate -120 foreach (1 .. 6) { $dir6 %= 6; my $dx = $dir6_to_dx[$dir6]; my $dy = $dir6_to_dy[$dir6]; my @next_n_list = $path->xy_to_n_list($x+$dx,$y+$dy); ### @next_n_list if (any_consecutive(\@n_list, \@next_n_list, $n_limit)) { @n_list = @next_n_list; $x += $dx; $y += $dy; last; } $dir6++; # +60 } if ($x == 0 && $y == 0) { last; } } return \@points; } } } { # N where two paths have same X,Y # path1 RationalsTree tree_type,SB # path2 RationalsTree tree_type,Drib # path1 RationalsTree tree_type,CW # path2 RationalsTree tree_type,Bird # values: 3,34,38,40,44,51,55,57,61,522,538,546,562,590,606,614,630,648,664,672,688,716,732,740,756,779,795,803,819,847 # path1 RationalsTree tree_type,SB # path2 RationalsTree tree_type,AYT # path1 RationalsTree tree_type,AYT # path2 RationalsTree tree_type,SB # values: 6,11,54,91,438,731,3510,5851,28086,46811 # octal 1333333,6666666,repeating # path1 RationalsTree tree_type,CW # path2 RationalsTree tree_type,HCS # path1 RationalsTree tree_type,HCS # path2 RationalsTree tree_type,CW # values: 5,14,45,118,365,950,2925,7606,23405,60854 # octal 166666,55555 repeating # path1 RationalsTree tree_type,AYT # path2 RationalsTree tree_type,Bird # path1 RationalsTree tree_type,Bird # path2 RationalsTree tree_type,AYT # values: 5,12,41,100,329,804,2633,6436,21065,51492 # octal 1444444,511111 repeating # path1 RationalsTree tree_type,HCS # path2 RationalsTree tree_type,Drib # path1 RationalsTree tree_type,Drib # path2 RationalsTree tree_type,HCS # values: 6,9,50,73,402,585,3218,4681,25746,37449 # octal 1111111,622222 repeating require Math::NumSeq::PlanePathCoord; my @choices = @{Math::NumSeq::PlanePathCoord->parameter_info_hash ->{'planepath'}->{'choices'}}; @choices = grep {$_ ne 'CellularRule'} @choices; @choices = grep {$_ ne 'Rows'} @choices; @choices = grep {$_ ne 'Columns'} @choices; @choices = grep {$_ ne 'ArchimedeanChords'} @choices; @choices = grep {$_ ne 'MultipleRings'} @choices; @choices = grep {$_ ne 'VogelFloret'} @choices; @choices = grep {$_ ne 'PythagoreanTree'} @choices; @choices = grep {$_ ne 'PeanoHalf'} @choices; @choices = grep {$_ !~ /EToothpick|LToothpick|Surround|Peninsula/} @choices; @choices = grep {$_ ne 'CornerReplicate'} @choices; @choices = grep {$_ ne 'ZOrderCurve'} @choices; unshift @choices, 'CornerReplicate', 'ZOrderCurve'; @choices = ('RationalsTree'); my $num_choices = scalar(@choices); print "$num_choices choices\n"; my @path_objects; my %path_fullnames; foreach my $name (@choices) { my $class = "Math::PlanePath::$name"; Module::Load::load($class); my $parameters = parameter_info_list_to_parameters ($class->parameter_info_list); foreach my $p (@$parameters) { my $path_object = $class->new (@$p); push @path_objects, $path_object; $path_fullnames{$path_object} = "$name ".join(',',@$p); } } my $num_path_objects = scalar(@path_objects); print "total path objects $num_path_objects\n"; my $start_t = time(); my $t = $start_t-8; my $i = 0; # until ($path_objects[$i]->isa('Math::PlanePath::DiamondArms')) { # $i++; # } # while ($path_objects[$i]->isa('Math::PlanePath::PyramidSpiral')) { # $i++; # } my $start_permutations = $i * ($num_path_objects-1); my $num_permutations = $num_path_objects * ($num_path_objects-1); open DEBUG, '>/tmp/permutations.out' or die; select DEBUG or die; $| = 1; # autoflush select STDOUT or die; for ( ; $i <= $#path_objects; $i++) { my $from_path = $path_objects[$i]; my $from_fullname = $path_fullnames{$from_path}; my $n_start = $from_path->n_start; PATH: foreach my $j (0 .. $#path_objects) { if (time()-$t < 0 || time()-$t > 10) { my $upto_permutation = $i*$num_path_objects + $j || 1; my $rem_permutation = $num_permutations - ($start_permutations + $upto_permutation); my $done_permutations = ($upto_permutation-$start_permutations); my $percent = 100 * $done_permutations / $num_permutations || 1; my $t_each = (time() - $start_t) / $done_permutations; my $done_per_second = $done_permutations / (time() - $start_t); my $eta = int($t_each * $rem_permutation); my $s = $eta % 60; $eta = int($eta/60); my $m = $eta % 60; $eta = int($eta/60); my $h = $eta; my $eta_str = sprintf '%d:%02d:%02d', $h,$m,$s; print "$upto_permutation / $num_permutations est $eta_str (each $t_each)\n"; $t = time(); } next if $i == $j; my $to_path = $path_objects[$j]; next if $to_path->n_start != $n_start; my $to_fullname = $path_fullnames{$to_path}; my $name = ("path1 $from_fullname\n" . "path2 $to_fullname\n"); print DEBUG "$name\n"; my $str = ''; my @values; my $gap = 0; for (my $n = $n_start+2; @values < 30 && $gap < 100_000; $n++) { my ($x1,$y1) = $from_path->n_to_xy($n) or next PATH; my ($x2,$y2) = $to_path->n_to_xy($n) or next PATH; if ($x1 == $x2 && $y1 == $y2) { push @values, $n; $gap = 0; } else { $gap++; } } if (@values < 5) { print DEBUG "only ",scalar(@values)," values: ",join(',',@values),"\n"; next; } print DEBUG "values: ",join(',',@values),"\n"; Math::OEIS::Grep->search(name => $name, array => \@values); print DEBUG "\n\n"; } } exit 0; } { # cross-product of successive dx,dy, being turn discriminant require Math::NumSeq::PlanePathCoord; my @choices = @{Math::NumSeq::PlanePathCoord->parameter_info_hash ->{'planepath'}->{'choices'}}; @choices = grep {$_ ne 'CellularRule'} @choices; my $num_choices = scalar(@choices); print "$num_choices choices\n"; my @path_objects; my %path_fullnames; foreach my $name (@choices) { my $class = "Math::PlanePath::$name"; Module::Load::load($class); my $parameters = parameter_info_list_to_parameters ($class->parameter_info_list); foreach my $p (@$parameters) { my $path_object = $class->new (@$p); push @path_objects, $path_object; $path_fullnames{$path_object} = "$name ".join(',',@$p); } } my $num_path_objects = scalar(@path_objects); print "total path objects $num_path_objects\n"; my %seen; foreach my $path (@path_objects) { my $fullname = $path_fullnames{$path}; print "$fullname\n"; my $n = $path->n_start + 2; my ($prev_dx,$prev_dy) = $path->n_to_dxdy($n) or next; my @values; for ($n++; @values < 30; $n++) { my ($dx,$dy) = $path->n_to_dxdy($n) or last; push @values, $dx * $prev_dy - $prev_dx * $dy; } print join(',', @values),"\n"; Math::OEIS::Grep->search(array => \@values, try_abs => 0); } exit 0; } { # boundary length by N, unit squares require Math::NumSeq::PlanePathCoord; my @choices = @{Math::NumSeq::PlanePathCoord->parameter_info_hash ->{'planepath'}->{'choices'}}; @choices = grep {$_ ne 'CellularRule'} @choices; @choices = grep {$_ ne 'ArchimedeanChords'} @choices; @choices = grep {$_ ne 'TheodorusSpiral'} @choices; @choices = grep {$_ ne 'MultipleRings'} @choices; @choices = grep {$_ ne 'VogelFloret'} @choices; @choices = grep {$_ ne 'UlamWarburtonAway'} @choices; @choices = grep {$_ !~ /Hypot|ByCells|SumFractions|WythoffTriangle/} @choices; @choices = grep {$_ ne 'PythagoreanTree'} @choices; # @choices = grep {$_ ne 'PeanoHalf'} @choices; @choices = grep {$_ !~ /EToothpick|LToothpick|Surround|Peninsula/} @choices; # # @choices = grep {$_ eq 'WythoffArray'} @choices; # @choices = grep {$_ ne 'ZOrderCurve'} @choices; # unshift @choices, 'CornerReplicate', 'ZOrderCurve'; my $num_choices = scalar(@choices); print "$num_choices choices\n"; @choices = ((grep {/Corner|Tri/} @choices), (grep {!/Corner|Tri/} @choices)); my @path_objects; my %path_fullnames; foreach my $name (@choices) { my $class = "Math::PlanePath::$name"; ### $class Module::Load::load($class); my $parameters = parameter_info_list_to_parameters ($class->parameter_info_list); foreach my $p (@$parameters) { my $path_object = $class->new (@$p); push @path_objects, $path_object; $path_fullnames{$path_object} = "$name ".join(',',@$p); } } my $num_path_objects = scalar(@path_objects); print "total path objects $num_path_objects\n"; my $start_t = time(); my $t = $start_t-8; my $i = 0; # until ($path_objects[$i]->isa('Math::PlanePath::DragonCurve')) { # $i++; # } my $start_permutations = $i * ($num_path_objects-1); my $num_permutations = $num_path_objects * ($num_path_objects-1); for ( ; $i <= $#path_objects; $i++) { my $path = $path_objects[$i]; my $fullname = $path_fullnames{$path}; print "$fullname\n"; my $x_minimum = $path->x_minimum; my $y_minimum = $path->y_minimum; my @values; my $boundary = 0; foreach my $n ($path->n_start .. 30) { $boundary += path_n_to_dboundary($path,$n); # $boundary += path_n_to_dsticks($path,$n); # $boundary += path_n_to_dhexboundary($path,$n); # $boundary += path_n_to_dhexsticks($path,$n); my $value = $boundary; push @values, $value; } shift @values; print join(',',@values),"\n"; Math::OEIS::Grep->search(array => \@values); print "\n"; } exit 0; } { # boundary of unit squares by powers require Math::NumSeq::PlanePathCoord; foreach my $elem ( # ['WythoffArray', 'zeck'], ['ComplexPlus', 1*1+1], ['ComplexPlus,realpart=2', 2*2+1], ['ComplexPlus,realpart=3', 3*3+1], ['ComplexMinus', 1*1+1], ['ComplexMinus,realpart=2', 2*2+1], ['ComplexMinus,realpart=3', 3*3+1], ['CCurve', 2], ['GosperReplicate',7, 'triangular'], ['Flowsnake',7, 'triangular'], ['FlowsnakeCentres',7, 'triangular'], ['PowerArray',2], ['PowerArray,radix=3',3], ['CubicBase',2, 'triangular'], ['CubicBase,radix=3',3, 'triangular'], ['TerdragonCurve', 3, 'triangular'], ['TerdragonMidpoint', 3, 'triangular'], ['QuintetCentres',5], ['QuintetCurve',5], ['AlternatePaperMidpoint', 2], ['R5DragonCurve', 5], ['DragonMidpoint', 2], ['AlternatePaper', 2], ['DragonCurve', 2], ) { my ($name, $radix, $lattice_type) = @$elem; $lattice_type ||= 'square'; print "$name (lattice=$lattice_type)\n"; my $path = Math::NumSeq::PlanePathCoord::_planepath_name_to_object($name); my $n_start = $path->n_start; my @boundaries; my $n = $n_start; my $boundary = 0; my $target = $radix; my $dboundary_func = ($lattice_type eq 'triangular' ? \&path_n_to_dhexboundary : \&path_n_to_dboundary); for (;; $n++) { ### at: "boundary=$boundary now consider N=$n" last if @boundaries > 20; if ($n > $target) { print "$target $boundary\n"; push @boundaries, $boundary; $target *= $radix; last if $target > 10_000; } $boundary += $dboundary_func->($path,$n); } print "$name unit squares boundary\n"; shift_off_zeros(\@boundaries); print join(',',@boundaries),"\n"; Math::OEIS::Grep->search(array => \@boundaries); print "\n"; } exit 0; } { # permutation of transpose require Math::NumSeq::PlanePathCoord; my @choices = @{Math::NumSeq::PlanePathCoord->parameter_info_hash ->{'planepath'}->{'choices'}}; @choices = grep {$_ ne 'BinaryTerms'} @choices; # bit slow yet my %seen; foreach my $path_name (@choices) { my $path_class = "Math::PlanePath::$path_name"; Module::Load::load($path_class); my $parameters = parameter_info_list_to_parameters($path_class->parameter_info_list); PATH: foreach my $p (@$parameters) { my $name = "$path_name ".join(',',@$p); my $path = $path_class->new (@$p); my @values; foreach my $n ($path->n_start+1 .. 35) { # my $value = (defined $path->tree_n_to_subheight($n) ? 1 : 0); my ($x,$y) = $path->n_to_xy($n) or next PATH; # # my $value = $path->xy_to_n($y,$x); # transpose # my $value = $path->xy_to_n(-$x,$y); # horiz mirror # my $value = $path->xy_to_n($x,-$y); # vert mirror # ($x,$y) = ($y,-$x); # rotate -90 # ($x,$y) = ($y,$x); # transpose # ($x,$y) = (-$y,$x); # rotate +90 my $value = $path->xy_to_n(-$y,-$x); # mirror across opp diagonal next PATH if ! defined $value; push @values, $value; } Math::OEIS::Grep->search(name => $name, array => \@values); } } exit 0; } { # tree row totals require Math::NumSeq::PlanePathCoord; my @choices = @{Math::NumSeq::PlanePathCoord->parameter_info_hash ->{'planepath'}->{'choices'}}; @choices = grep {$_ ne 'CellularRule'} @choices; @choices = grep {$_ ne 'UlamWarburtonAway'} @choices; # not working yet @choices = grep {$_ !~ /EToothpick|LToothpick|Surround|Peninsula/} @choices; my $num_choices = scalar(@choices); print "$num_choices choices\n"; my @path_objects; my %path_fullnames; foreach my $name (@choices) { my $class = "Math::PlanePath::$name"; ### $class Module::Load::load($class); my $parameters = parameter_info_list_to_parameters ($class->parameter_info_list); foreach my $p (@$parameters) { my $path_object = $class->new (@$p); push @path_objects, $path_object; $path_fullnames{$path_object} = "$name ".join(',',@$p); } } my $num_path_objects = scalar(@path_objects); print "total path objects $num_path_objects\n"; my $start_t = time(); my $t = $start_t-8; my $i = 0; # until ($path_objects[$i]->isa('Math::PlanePath::DragonCurve')) { # $i++; # } for ( ; $i <= $#path_objects; $i++) { my $path = $path_objects[$i]; next unless $path->x_negative || $path->y_negative; $path->is_tree($path) or next; my $fullname = $path_fullnames{$path}; print "$fullname (",ref $path,")\n"; my @x_total; my @y_total; my @sum_total; my @diff_total; my $target_depth = 0; my $target = $path->tree_depth_to_n_end($target_depth); for (my $n = $path->n_start; $n < 10_000; $n++) { my ($x,$y) = $path->n_to_xy($n); my $depth = $path->tree_n_to_depth($n); $x = abs($x); $y = abs($y); $x_total[$depth] += $x; $y_total[$depth] += $y; $sum_total[$depth] += $x + $y; $diff_total[$depth] += $x - $y; if ($n == $target) { print "$target_depth $x_total[$target_depth] $y_total[$target_depth]\n"; $target_depth++; last if $target_depth > 12; $target = $path->tree_depth_to_n_end($target_depth); } } $#x_total = $target_depth-1; $#y_total = $target_depth-1; $#sum_total = $target_depth-1; $#diff_total = $target_depth-1; print "X rows\n"; Math::OEIS::Grep->search(array => \@x_total); print "\n"; print "Y rows\n"; Math::OEIS::Grep->search(array => \@y_total); print "\n"; print "X+Y rows\n"; Math::OEIS::Grep->search(array => \@sum_total); print "\n"; print "X-Y rows\n"; Math::OEIS::Grep->search(array => \@diff_total); print "\n"; } exit 0; } BEGIN { my @dir6_to_dx = (2, 1,-1,-2, -1, 1); my @dir6_to_dy = (0, 1, 1, 0, -1,-1); # Return the change in boundary length when hexagon $n is added. # This is +6 if it's completely isolated, and 2 less for each neighbour # < $n since 1 side of the neighbour and 1 side of $n are then not # boundaries. # sub path_n_to_dhexboundary { my ($path, $n) = @_; my ($x,$y) = $path->n_to_xy($n) or return 0; my $dboundary = 6; foreach my $i (0 .. $#dir6_to_dx) { my $an = $path->xy_to_n($x+$dir6_to_dx[$i], $y+$dir6_to_dy[$i]); $dboundary -= 2*(defined $an && $an < $n); } ### $dboundary return $dboundary; } sub path_n_to_dhexsticks { my ($path, $n) = @_; my ($x,$y) = $path->n_to_xy($n) or return 0; my $dboundary = 6; foreach my $i (0 .. $#dir6_to_dx) { my $an = $path->xy_to_n($x+$dir6_to_dx[$i], $y+$dir6_to_dy[$i]); $dboundary -= (defined $an && $an < $n); } return $dboundary; } } { # path classes with or without n_start require Math::NumSeq::PlanePathCoord; my @choices = @{Math::NumSeq::PlanePathCoord->parameter_info_hash ->{'planepath'}->{'choices'}}; my (@with, @without); foreach my $name (@choices) { my $class = "Math::PlanePath::$name"; Module::Load::load($class); my $href = $class->parameter_info_hash; if ($href->{'n_start'}) { push @with, $class; } else { push @without, $class; } } foreach my $aref (\@without, \@with) { foreach my $class (@$aref) { my @pnames = map {$_->{'name'}} $class->parameter_info_list; my $href = $class->parameter_info_hash; my $w = ($href->{'n_start'} ? 'with' : 'without'); print " $class [$w] ",join(',',@pnames),"\n"; # print " ",join(', ',keys %$href),"\n"; } print "\n\n"; } exit 0; } { require Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new; my @values; foreach my $n (3 .. 32) { my ($x,$y) = $path->n_to_xy(2*$n); # push @values,-$x-1; my $transitions = transitions($n); push @values, (($transitions%4)/2); # push @values, $transitions; } my $values = join(',',@values); print "$values\n"; Math::OEIS::Grep->search(array=>\@values); exit 0; # transitions(2n)/2 = A069010 Number of runs of 1's sub transitions { my ($n) = @_; my $count = 0; while ($n) { $count += (($n & 3) == 1 || ($n & 3) == 2); $n >>= 1; } return $count } } { # tree row increments require Math::NumSeq::PlanePathCoord; my @choices = @{Math::NumSeq::PlanePathCoord->parameter_info_hash ->{'planepath'}->{'choices'}}; # @choices = grep {$_ ne 'CellularRule'} @choices; # @choices = grep {$_ ne 'Rows'} @choices; # @choices = grep {$_ ne 'Columns'} @choices; # @choices = grep {$_ ne 'ArchimedeanChords'} @choices; @choices = grep {$_ ne 'MultipleRings'} @choices; @choices = grep {$_ ne 'VogelFloret'} @choices; @choices = grep {$_ !~ /ByCells/} @choices; # @choices = grep {$_ ne 'PythagoreanTree'} @choices; # @choices = grep {$_ ne 'PeanoHalf'} @choices; # @choices = grep {$_ !~ /EToothpick|LToothpick|Surround|Peninsula/} @choices; # # @choices = grep {$_ ne 'CornerReplicate'} @choices; # @choices = grep {$_ ne 'ZOrderCurve'} @choices; # unshift @choices, 'CornerReplicate', 'ZOrderCurve'; my $num_choices = scalar(@choices); print "$num_choices choices\n"; my @path_objects; my %path_fullnames; foreach my $name (@choices) { my $class = "Math::PlanePath::$name"; ### $class Module::Load::load($class); my $parameters = parameter_info_list_to_parameters ($class->parameter_info_list); foreach my $p (@$parameters) { my $path_object = $class->new (@$p); push @path_objects, $path_object; $path_fullnames{$path_object} = "$name ".join(',',@$p); } } my $num_path_objects = scalar(@path_objects); print "total path objects $num_path_objects\n"; my $start_t = time(); my $t = $start_t-8; my $i = 0; # until ($path_objects[$i]->isa('Math::PlanePath::DiamondArms')) { # $i++; # } my $start_permutations = $i * ($num_path_objects-1); my $num_permutations = $num_path_objects * ($num_path_objects-1); for ( ; $i <= $#path_objects; $i++) { my $path = $path_objects[$i]; my $fullname = $path_fullnames{$path}; my $n_start = $path->n_start; $path->is_tree($path) or next; print "$fullname\n"; # if (time()-$t < 0 || time()-$t > 10) { # my $upto_permutation = $i*$num_path_objects + $j || 1; # my $rem_permutation = $num_permutations # - ($start_permutations + $upto_permutation); # my $done_permutations = ($upto_permutation-$start_permutations); # my $percent = 100 * $done_permutations / $num_permutations || 1; # my $t_each = (time() - $start_t) / $done_permutations; # my $done_per_second = $done_permutations / (time() - $start_t); # my $eta = int($t_each * $rem_permutation); # my $s = $eta % 60; $eta = int($eta/60); # my $m = $eta % 60; $eta = int($eta/60); # my $h = $eta; # print "$upto_permutation / $num_permutations est $h:$m:$s (each $t_each)\n"; # $t = time(); # } my $str = ''; my @values; foreach my $depth (1 .. 50) { # my $value = $path->tree_depth_to_width($depth) // next; my $value = $path->tree_depth_to_n($depth) % 2; $str .= "$value,"; push @values, $value; } if (defined (my $diff = constant_diff(@values))) { print "$fullname\n"; print " constant diff $diff\n"; next; } if (my $found = stripped_grep($str)) { print "$fullname match\n"; print " (",substr($str,0,60),"...)\n"; print $found; print "\n"; } } exit 0; } { # X,Y extents require Math::NumSeq::PlanePathCoord; my @choices = @{Math::NumSeq::PlanePathCoord->parameter_info_hash ->{'planepath'}->{'choices'}}; my $num_choices = scalar(@choices); print "$num_choices choices\n"; my @path_objects; my %path_fullnames; foreach my $name (@choices) { my $class = "Math::PlanePath::$name"; Module::Load::load($class); my $parameters = parameter_info_list_to_parameters ($class->parameter_info_list); foreach my $p (@$parameters) { my $path_object = $class->new (@$p); push @path_objects, $path_object; $path_fullnames{$path_object} = "$name ".join(',',@$p); } } my $num_path_objects = scalar(@path_objects); print "total path objects $num_path_objects\n"; my %seen; foreach my $path (@path_objects) { print $path_fullnames{$path},"\n"; my $any_x_neg = 0; my $any_y_neg = 0; my (@x,@y,@n); foreach my $n ($path->n_start+2 .. 50) { my ($x,$y) = $path->n_to_xy($n) or last; push @x, $x; push @y, $y; push @n, $n; $any_x_neg ||= ($x < 0); $any_y_neg ||= ($y < 0); } next unless $any_x_neg || $any_y_neg; foreach my $x_axis_pos ($any_y_neg ? -1 : (), 0, 1) { foreach my $x_axis_neg (($any_y_neg ? (-1) : ()), 0, ($any_x_neg ? (1) : ())) { foreach my $y_axis_pos ($any_x_neg ? -1 : (), 0, 1) { foreach my $y_axis_neg ($any_x_neg ? (-1) : (), 0, ($any_y_neg ? (1) : ())) { my $fullname = $path_fullnames{$path} . " Xpos=$x_axis_pos Xneg=$x_axis_neg Ypos=$y_axis_pos Yneg=$y_axis_neg"; my @values; my $str = ''; foreach my $i (0 .. $#x) { if (($x[$i]<=>0) == ($y[$i]<0 ? $y_axis_neg : $y_axis_pos) && ($y[$i]<=>0) == ($x[$i]<0 ? $x_axis_neg : $x_axis_pos) ) { push @values, $n[$i]; $str .= "$n[$i],"; } } next unless @values >= 5; if (my $prev_fullname = $seen{$str}) { print "$fullname\n"; print "repeat of $prev_fullname"; print "\n"; } else { if (my $found = stripped_grep($str)) { print "$fullname\n"; print " (",substr($str,0,20),"...)\n"; print $found; print "\n"; print "\n"; $seen{$str} = $fullname; } } } } } } } exit 0; } # sub stripped_grep { # my ($str) = @_; # my $find = `fgrep -e $str $ENV{HOME}/OEIS/stripped`; # my $ret = ''; # foreach my $line (split /\n/, $find) { # $ret .= "$line\n"; # my ($anum) = ($line =~ /^(A\d+)/) or die; # $ret .= `zgrep -e ^$anum $ENV{HOME}/OEIS/names.gz`; # } # return $ret; # } my $stripped; sub stripped_grep { my ($str) = @_; if (! $stripped) { require File::Map; my $filename = "$ENV{HOME}/OEIS/stripped"; File::Map::map_file ($stripped, $filename); print "File::Map file length ",length($stripped),"\n"; } my $ret = ''; my $pos = 0; for (;;) { $pos = index($stripped,$str,$pos); last if $pos < 0; my $start = rindex($stripped,"\n",$pos) + 1; my $end = index($stripped,"\n",$pos); my $line = substr($stripped,$start,$end-$start); $ret .= "$line\n"; my ($anum) = ($line =~ /^(A\d+)/); $anum || die "$anum not found"; $ret .= `zgrep -e ^$anum $ENV{HOME}/OEIS/names.gz`; $pos = $end; } return $ret; } #------------------------------------------------------------------------------ # ($inforef, $inforef, ...) sub parameter_info_list_to_parameters { my @parameters = ([]); foreach my $info (@_) { next if $info->{'name'} eq 'n_start'; info_extend_parameters($info,\@parameters); } return \@parameters; } sub info_extend_parameters { my ($info, $parameters) = @_; my @new_parameters; if ($info->{'name'} eq 'planepath') { my @strings; foreach my $choice (@{$info->{'choices'}}) { # next unless $choice =~ /DiamondSpiral/; # next unless $choice =~ /Gcd/; # next unless $choice =~ /LCorn|RationalsTree/; next unless $choice =~ /dragon/i; # next unless $choice =~ /SierpinskiArrowheadC/; # next unless $choice eq 'DiagonalsAlternating'; my $path_class = "Math::PlanePath::$choice"; Module::Load::load($path_class); my @parameter_info_list = $path_class->parameter_info_list; { my $path = $path_class->new; if (defined $path->{'n_start'} && ! $path_class->parameter_info_hash->{'n_start'}) { push @parameter_info_list,{ name => 'n_start', type => 'enum', choices => [0,1], default => $path->default_n_start, }; } } if ($path_class->isa('Math::PlanePath::Rows')) { push @parameter_info_list,{ name => 'width', type => 'integer', width => 3, default => '1', minimum => 1, }; } if ($path_class->isa('Math::PlanePath::Columns')) { push @parameter_info_list, { name => 'height', type => 'integer', width => 3, default => '1', minimum => 1, }; } my $path_parameters = parameter_info_list_to_parameters(@parameter_info_list); ### $path_parameters foreach my $aref (@$path_parameters) { my $str = $choice; while (@$aref) { $str .= "," . shift(@$aref) . '=' . shift(@$aref); } push @strings, $str; } } ### @strings foreach my $p (@$parameters) { foreach my $choice (@strings) { push @new_parameters, [ @$p, $info->{'name'}, $choice ]; } } @$parameters = @new_parameters; return; } if ($info->{'choices'}) { my @new_parameters; foreach my $p (@$parameters) { foreach my $choice (@{$info->{'choices'}}) { next if ($info->{'name'} eq 'serpentine_type' && $choice eq 'Peano'); next if ($info->{'name'} eq 'rotation_type' && $choice eq 'custom'); push @new_parameters, [ @$p, $info->{'name'}, $choice ]; } if ($info->{'name'} eq 'serpentine_type') { push @new_parameters, [ @$p, $info->{'name'}, '100_000_000' ]; push @new_parameters, [ @$p, $info->{'name'}, '101_010_101' ]; push @new_parameters, [ @$p, $info->{'name'}, '000_111_000' ]; push @new_parameters, [ @$p, $info->{'name'}, '111_000_111' ]; } } @$parameters = @new_parameters; return; } if ($info->{'type'} eq 'boolean') { my @new_parameters; foreach my $p (@$parameters) { foreach my $choice (0, 1) { push @new_parameters, [ @$p, $info->{'name'}, $choice ]; } } @$parameters = @new_parameters; return; } if ($info->{'type'} eq 'integer' || $info->{'name'} eq 'multiples') { my @choices; if ($info->{'name'} eq 'radix') { @choices = (2,3,10,16); } if ($info->{'name'} eq 'n_start') { @choices = (0,1); } if ($info->{'name'} eq 'x_start' || $info->{'name'} eq 'y_start') { @choices = ($info->{'default'}); } if (! @choices) { my $min = $info->{'minimum'} // -5; my $max = $min + 10; if (# $module =~ 'PrimeIndexPrimes' && $info->{'name'} eq 'level') { $max = 5; } # if ($info->{'name'} eq 'arms') { $max = 2; } if ($info->{'name'} eq 'rule') { $max = 255; } if ($info->{'name'} eq 'round_count') { $max = 20; } if ($info->{'name'} eq 'straight_spacing') { $max = 1; } if ($info->{'name'} eq 'diagonal_spacing') { $max = 1; } if ($info->{'name'} eq 'radix') { $max = 17; } if ($info->{'name'} eq 'realpart') { $max = 3; } if ($info->{'name'} eq 'wider') { $max = 1; } if ($info->{'name'} eq 'modulus') { $max = 32; } if ($info->{'name'} eq 'polygonal') { $max = 32; } if ($info->{'name'} eq 'factor_count') { $max = 12; } if ($info->{'name'} eq 'diagonal_length') { $max = 5; } if ($info->{'name'} eq 'height') { $max = 4; } if ($info->{'name'} eq 'width') { $max = 4; } if ($info->{'name'} eq 'k') { $max = 4; } if (defined $info->{'maximum'} && $max > $info->{'maximum'}) { $max = $info->{'maximum'}; } if ($info->{'name'} eq 'power' && $max > 6) { $max = 6; } @choices = ($min .. $max); } my @new_parameters; foreach my $choice (@choices) { foreach my $p (@$parameters) { push @new_parameters, [ @$p, $info->{'name'}, $choice ]; } } @$parameters = @new_parameters; return; } if ($info->{'name'} eq 'fraction') { ### fraction ... my @new_parameters; foreach my $p (@$parameters) { my $radix = p_radix($p) || die; foreach my $den (995 .. 1021) { next if $den % $radix == 0; my $choice = "1/$den"; push @new_parameters, [ @$p, $info->{'name'}, $choice ]; } foreach my $num (2 .. 10) { foreach my $den ($num+1 .. 15) { next if $den % $radix == 0; next unless _coprime($num,$den); my $choice = "$num/$den"; push @new_parameters, [ @$p, $info->{'name'}, $choice ]; } } } @$parameters = @new_parameters; return; } print " skip parameter $info->{'name'}\n"; } # return true if coprime sub _coprime { my ($x, $y) = @_; ### _coprime(): "$x,$y" if ($y > $x) { ($x,$y) = ($y,$x); } for (;;) { if ($y <= 1) { ### result: ($y == 1) return ($y == 1); } ($x,$y) = ($y, $x % $y); } } sub p_radix { my ($p) = @_; for (my $i = 0; $i < @$p; $i += 2) { if ($p->[$i] eq 'radix') { return $p->[$i+1]; } } return undef; } sub float_error { my ($x) = @_; if (abs($x - int($x)) < 0.000001) { return int($x); } else { return $x; } } __END__ Math-PlanePath-129/devel/hexhypot.pl0000644000175000017500000000305411566406004015216 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use Smart::Comments; { my $x = 2; my $y = 0; ### initial: "$x,$y" foreach (1 .. 6) { # ($x,$y) = (($x+3*$y)/-2, # 120 # ($x-$y)/2); # ($x,$y) = ((3*$y-$x)/2, # 2x120 # ($x+$y)/-2); # ($x,$y) = (($x-3*$y)/2, # 60 # ($x+$y)/2); ($x,$y) = (($x+3*$y)/2, # -60 ($y-$x)/2); ### to: "$x,$y" } exit 0; } { my @seen; foreach my $x (2 .. 20) { my $ylimit = $x/3; for (my $y = ($x&1); $y <= $ylimit; $y+=2) { my $h = 3*$y*$y + $x*$x; if ($seen[$h]) { print "duplicate $x,$y: $seen[$h]\n"; } else { $seen[$h] = "$x,$y"; } } } my @hypots = map {defined $seen[$_] ? $_ : ()} 0 .. $#seen; @hypots = sort {$a<=>$b} @hypots; $,=','; print @hypots,"\n"; exit 0; } Math-PlanePath-129/devel/gcd-rationals.pl0000644000175000017500000001110212307003455016063 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use Math::PlanePath::GcdRationals; use List::Util 'min', 'max'; # uncomment this to run the ### lines use Smart::Comments; { require Math::PlanePath::PyramidRows; my $path = Math::PlanePath::GcdRationals->new; my $rows = Math::PlanePath::PyramidRows->new (step => 1); require Math::NumSeq::OEIS; my $A167192 = Math::NumSeq::OEIS->new (anum => 'A167192'); my $A002260 = Math::NumSeq::OEIS->new (anum => 'A002260'); my $A002024 = Math::NumSeq::OEIS->new (anum => 'A002024'); foreach my $n (1 .. 40) { my ($x,$y) = $path->n_to_xy($n); # my ($i,$j) = $rows->n_to_xy($n); # $i++, $j++; my $i = $A002260->ith($n); my $j = $A002024->ith($n); my $g = Math::PlanePath::GcdRationals::_gcd($i,$j); my $jy = $j/$g; # my $ix = $j + ($i-$j)/$g; # my $ix = $j - $A167192->ith($n); my $ix = $A002024->ith($n) - $A167192->ith($n); my $diff = ($ix==$x && $jy==$y ? '' : ' ***'); print "$n $i,$j $x,$y $ix,$jy$diff\n"; # print "$x/$y, "; # print(($j-$i)/$g,","); } exit 0; # X = (i + j*(gcd-1)) / gcd # = (i + j*gcd-j) / gcd # = j + (i -j) / gcd } { # nhi roughly max(num,den)**2 my $path = Math::PlanePath::GcdRationals->new (pairs_order => "diagonals_down"); foreach my $n (1 .. 5000) { my ($x,$y) = $path->n_to_xy($n); # my $nhi = 2 * max($x,$y)**2; my $nhi = max($x,$y)**2; my $flag = ($nhi < $n ? '****' : ''); print "$n $nhi$flag\n"; } exit 0; } { require Math::PlanePath::DiagonalsOctant; require Math::PlanePath::PyramidRows; my $diag = Math::PlanePath::DiagonalsOctant->new; my $horiz = Math::PlanePath::PyramidRows->new (step => 1); my $gcd = Math::PlanePath::GcdRationals->new; my %seen; my @xy; foreach my $n (1 .. 99) { my ($hx,$hy) = $horiz->n_to_xy($n) or die; my $dn = $diag->xy_to_n($hx,$hy) // die; # my ($hx,$hy) = $diag->n_to_xy($n) or die; # my $dn = $horiz->xy_to_n($hx,$hy) // die; ### at: "n=$n hxy=$hx,$hy dn=$dn" if ($seen{$dn}) { die "saw $dn hxy=$hx,$hy from $seen{$dn} already"; } $seen{$dn} = "n=$n,hxy=$hx,$hy"; # $dn = $n; my ($x,$y) = $gcd->n_to_xy($dn); $xy[$x][$y] = $n; ### store: "n=$n at $x,$y" } foreach my $y (0 .. 10) { foreach my $x (0 .. 10) { printf "%3s", $xy[$x][$y]//'.'; } print "\n"; } exit 0; } { my $path = Math::PlanePath::GcdRationals->new; require Math::Prime::XS; my @primes = Math::Prime::XS::sieve_primes(10000); my $fmax = 0; foreach my $y (1 .. 5000) { foreach my $x (1 .. 5000) { my $n = $path->xy_to_n($x+1,$y+1) // next; my $est = ($x+$y)**2 + $x; my $f = $est / $n; if ($f > $fmax + .5) { print "$f\n"; $fmax = $f; } } } exit 0; } { my $path = Math::PlanePath::GcdRationals->new; foreach my $y (3 .. 50) { foreach my $x (3 .. 50) { my $n = $path->xy_to_n($x,$y) // next; my $slope = int($x/$y) + 1; my $g = $slope+1; my $fn = $x*$g + $y*$g*(($y-2)*$g + 1)/2; if ($n != $fn) { ### $n ### $fn ### $g ### $x ### $y my $int = int($x/$y); my $i = $x % $y; if ($i == 0) { $i = $y; $int--; } $int++; $i *= $int; } } } exit 0; } { my $path = Math::PlanePath::GcdRationals->new; foreach my $y (1 .. 500) { my $prev_n = 0; foreach my $x (1 .. 500) { my $n = $path->xy_to_n($x,$y) // next; if ($n <= $prev_n) { die "not monotonic $n cf $prev_n"; } $prev_n = $n; } } exit 0; } { my $path = Math::PlanePath::GcdRationals->new; print "N ="; foreach my $n (1 .. 11) { printf "%5d", $n; } print "\n"; print "X/Y ="; foreach my $n (1 .. 11) { my ($x,$y) = $path->n_to_xy($n); print " $x/$y," } print " etc\n"; exit 0; } Math-PlanePath-129/devel/flowsnake-levels.pl0000644000175000017500000000441611620710560016626 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.006; use strict; use warnings; use Smart::Comments; use Math::Libm 'M_PI', 'hypot'; use Math::Trig 'cartesian_to_cylindrical', 'cylindrical_to_cartesian'; use Math::PlanePath::Flowsnake; my $path = Math::PlanePath::Flowsnake->new; my $width = 300; my $height = 300; print <<"HERE"; ]> HERE my $equilateral = sqrt(3); my $xoffset = $width * .2; my $yoffset = $height * .5; my $xsize = $width * .3; my $yscale = .3; foreach my $level (4 .. 4) { my $linewidth = 1/$level; my $n_hi = 7**$level - 1; my $n_e = 7**($level-1)*6-1; my ($ex, $ey) = $path->n_to_xy($n_e); $ey *= $equilateral; my $angle = - atan2($ey,$ex); my $hypot = hypot ($ex,$ey); my $xfactor = $xsize / $hypot; my $yfactor = $height * .8 * $yscale / $hypot; my $s = sin($angle); my $c = cos($angle); my $points = ''; foreach my $n (0 .. $n_hi) { my ($x, $y) = $path->n_to_xy($n); $y *= $equilateral; # my ($r, $theta) = cartesian_to_cylindrical($x, $y, 0); # $r += $angle; # ($x, $y) = cylindrical_to_cartesian($r, $theta, 0); ($x, $y) = ($x * $c - $y * $s, $x * $s + $y * $c); $x = $x * $xfactor + $xoffset; $y = $y * $yfactor + $yoffset; $points .= "$x,$y "; } print ""; } print <<'HERE'; HERE Math-PlanePath-129/devel/junk/0002755000175000017500000000000014001441522013747 5ustar ggggMath-PlanePath-129/devel/junk/FibonacciSquareSpiral.pm0000644000175000017500000001043012145611505020521 0ustar gggg# Copyright 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Grows too quickly to be interesting. # # math-image --path=FibonacciSquareSpiral --lines --scale=10 # math-image --path=FibonacciSquareSpiral --output=numbers # #------------------------------------------------------------------------------ # # A138710 - abs(dX) but OFFSET=1 # # MyOEIS::compare_values # (anum => 'A138710', # func => sub { # my ($count) = @_; # my @got; # for (my $n = $path->n_start; @got < $count; $n++) { # my ($dx,$dy) = $path->n_to_dxdy($n); # push @got, abs($dx); # } # return \@got; # }); # # # A138709 - abs(dY) but OFFSET=1 # MyOEIS::compare_values # (anum => 'A138709', # func => sub { # my ($count) = @_; # my @got; # for (my $n = $path->n_start; @got < $count; $n++) { # my ($dx,$dy) = $path->n_to_dxdy($n); # push @got, abs($dy); # } # return \@got; # }); package Math::PlanePath::FibonacciSquareSpiral; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 104; use Math::PlanePath 37; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh'; # uncomment this to run the ### lines #use Devel::Comments; use constant default_n_start => 1; sub new { my $self = shift->SUPER::new (@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } sub n_to_xy { my ($self, $n) = @_; #### SquareSpiral n_to_xy: $n $n = $n - $self->{'n_start'}; # starting $n==0, warn if $n==undef if ($n < 0) { #### before n_start ... return; } my $f0 = int($n)*0; # inherit BigInt zero my $f1 = $f0 + 1; # inherit BigInt one my $x = 0; my $y = 0; my $dx = 1; my $dy = 0; while ($n > $f1) { $n -= $f1; $x += $dx * $f1; $y += $dy * $f1; ($f1,$f0) = ($f1+$f0,$f1); ($dx,$dy) = (-$dy,$dx); # rotate +90 } return ($n*$dx + $x, $n*$dy + $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### FibonacciSquareSpiral xy_to_n() ... return undef; } sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; return ($self->{'n_start'}, $self->{'n_start'} + max($x1,$x2,$y1,$y2)**2); } 1; __END__ =for stopwords eg Ryde =head1 NAME Math::PlanePath::FibonacciSquareSpiral -- spiral with Fibonacci number sides =head1 SYNOPSIS use Math::PlanePath::FibonacciSquareSpiral; my $path = Math::PlanePath::FibonacciSquareSpiral->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path is ... =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::FibonacciSquareSpiral-Enew ()> Create and return a new path object. =back =head1 SEE ALSO L, L =head1 HOME PAGE http://user42.tuxfamily.org/math-planepath/index.html =head1 LICENSE Copyright 2012, 2013 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/devel/hex-arms.pl0000644000175000017500000001270112014333612015062 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.006; use strict; use warnings; use List::Util qw(max); use Devel::Comments; { require Math::PlanePath::DiamondArms; my @max; my $path = Math::PlanePath::DiamondArms->new; foreach my $n (2 .. 10000) { my ($x,$y) = $path->n_to_xy($n); $x = abs($x); $y = abs($y); my $d = abs($x)+abs($y); $max[$d] ||= 0; $max[$d] = max($max[$d], $n); } ### @max exit 0; } { require Math::PlanePath::HexArms; my @max; my $path = Math::PlanePath::HexArms->new; foreach my $n (2 .. 10000) { my ($x,$y) = $path->n_to_xy($n); $x = abs($x); $y = abs($y); my $d = ($y >= $x ? $y # middle : ($x + $y)/2); # end $max[$d] ||= 0; $max[$d] = max($max[$d], $n); } ### @max exit 0; } { # cf A094268 smallest of N consecutive abundants # 5775 pair (3,4 mod 6) # 171078830 triplet (2,3,4 mod 6) # 141363708067871564084949719820472453374 first run of 4 consecutive # # cf A047802 first abundant not using the first N primes # my $limit = 33426748355; my $min = $limit; my $divsum = 1; for (my $p5 = 0; ; $p5++) { my $value = 5**$p5; last if $value > $limit; my $divsum = (5**($p5+1) - 1) / 4; for (my $p7 = 0; ; $p7++) { my $value = $value * 7**$p7; last if $value > $limit; my $divsum = $divsum * (7**($p7+1) - 1) / 6; for (my $p11 = 0; ; $p11++) { my $value = $value * 11**$p11; last if $value > $limit; my $divsum = $divsum * (11**($p11+1) - 1) / 10; for (my $p13 = 0; ; $p13++) { my $value = $value * 13**$p13; last if $value > $limit; my $divsum = $divsum * (13**($p13+1) - 1) / 12; for (my $p17 = 0; ; $p17++) { my $value = $value * 17**$p17; last if $value > $limit; my $divsum = $divsum * (17**($p17+1) - 1) / 16; for (my $p19 = 0; ; $p19++) { my $value = $value * 19**$p19; last if $value > $limit; my $divsum = $divsum * (19**($p19+1) - 1) / 18; for (my $p23 = 0; ; $p23++) { my $value = $value * 23**$p23; last if $value > $limit; my $divsum = $divsum * (23**($p23+1) - 1) / 22; for (my $p29 = 0; ; $p29++) { my $value = $value * 29**$p29; last if $value > $limit; my $divsum = $divsum * (29**($p29+1) - 1) / 28; for (my $p31 = 0; ; $p31++) { my $value = $value * 31**$p31; last if $value > $limit; my $divsum = $divsum * (31**($p31+1) - 1) / 30; if ($divsum > 2*$value) { print "value $value divsum $divsum\n"; print "$p5 $p7 $p11 $p13 $p17 $p19 $p23 $p29 $p31\n"; if ($value < $min) { print " smaller\n"; $min = $value; } print "\n"; last; } } } } } } } } } } exit 0; } { # 7^k divisors 1,...,7^k # sum = (7^(k+1)-1)/6 # sum/7^k = (7 - 1/7^k) / 6 # -> 7/6 # single 1,7 # sum = 7+1 = 8 # sum/7 = 8/7 # use Math::BigInt; require Math::Prime::XS; my @primes = Math::Prime::XS::sieve_primes(10000); my $prod = 1; my $value = 1; # for (my $i = 7; $i < 1000; $i += 6) { foreach my $i (@primes) { if (($i % 6) != 1 && ($i % 6) != 5 ) { next; } # my $f = $i/($i-1); my $f = ($i+1)/$i; $prod *= $f; $value *= $i; print "$i $prod\n"; if ($prod > 2) { last; } } print "value $value\n"; exit 0; } { # 7^k divisors 1,...,7^k = (7^(k+1)-1)/6 use Math::BigInt; foreach my $i (1 .. 200) { foreach my $j (0 .. 10) { foreach my $k (0 .. 10) { my $n = Math::BigInt->new(7)**$i * Math::BigInt->new(13)**$j * Math::BigInt->new(19)**$k; my $sd = (Math::BigInt->new(7)**($i+1) - 1) / 6 * (Math::BigInt->new(13)**($j+1) - 1) / 12 * (Math::BigInt->new(19)**($k+1) - 1) / 18; if ($sd >= 2*$n) { print "$i, $j $n $sd\n"; } } } } exit 0; } { require Math::NumSeq::Abundant; my $seq = Math::NumSeq::Abundant->new (hi => 5_000_000); my ($max_i, $max_value); while (my ($i, $value) = $seq->next) { # my $m = ($value % 6); # if ($m == 1 || $m == 5) { # print "$i $value is $m mod 6\n"; # } if ($value % 2) { print "$i $value odd\n"; } ($max_i, $max_value) = ($i, $value); } print "to $max_i $max_value\n"; exit 0; } Math-PlanePath-129/devel/chan-tree.pl0000644000175000017500000001237012155012130015201 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use List::Util 'min', 'max'; use Math::PlanePath::ChanTree; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh'; # uncomment this to run the ### lines use Smart::Comments; { # gcd vs count ternary 1 digits # ternary n_start=>1 # 1 2 # / | \ / | \ # 10 11 12 20 21 22 # / | \ # 100 101 102 require Math::PlanePath::GcdRationals; require Math::NumSeq::DigitCount; require Math::BaseCnv; my $seq = Math::NumSeq::DigitCount->new (digit => 1, radix => 3); my $k = 3; my $path = Math::PlanePath::ChanTree->new (k => $k, n_start => 1, ); my $n = $path->n_start; my $prevlen = 1; my $prev_gcd = 0; for (;; $n++) { my $nk = Math::BaseCnv::cnv($n,10,$k); my $len = length($nk); last if $len > 11; if ($len > $prevlen) { print "\n"; $prevlen = $len; } my ($x,$y) = $path->n_to_xy($n); my $gcd = Math::PlanePath::GcdRationals::_gcd($x,$y); my $offset3 = substr($nk,1); my $offset = Math::BaseCnv::cnv($offset3,$k,10); my $count = $seq->ith($offset); my $pow = 3 ** max($count,0); my $above = ($gcd == $pow && $count>0 ? " ===$pow" : $gcd > $pow ? ' ***' : ''); if ($gcd > $prev_gcd) { print "$n $nk $x / $y $gcd $pow (offset $offset) $above\n"; $prev_gcd = $gcd; } } exit 0; } { # X/Y list require Math::PlanePath::GcdRationals; require Math::PlanePath::PythagoreanTree; my $pyth = Math::PlanePath::PythagoreanTree->new (coordinates => 'PQ', tree_type => 'UAD'); require Math::BaseCnv; my $k = 3; my $path = Math::PlanePath::ChanTree->new (k => $k, n_start => 1, ); my $n = $path->n_start; my $prevlen = 1; for (;; $n++) { # my $depth = $path->tree_n_to_depth($n); # my $n_row = $path->tree_depth_to_n($depth); # my $n_end = $path->tree_depth_to_n_end($depth); # my $n_half = ($n_row + $n_end + 1)/2; # next unless $n >= $n_half; my $nk = Math::BaseCnv::cnv($n,10,$k); my $len = length($nk); last if $len > 5; if ($len > $prevlen) { print "\n"; $prevlen = $len; } my ($x,$y) = $path->n_to_xy($n); my $pyth_n = $pyth->xy_to_n($x,$y); my $pyth_n3; if (defined $pyth_n) { $pyth_n3 = Math::BaseCnv::cnv($pyth_n,10,$k); } $pyth_n //= 'none'; $pyth_n3 //= 'none'; my $gcd = Math::PlanePath::GcdRationals::_gcd($x,$y); my $xg = $x/$gcd; my $yg = $y/$gcd; print "$n $nk $x / $y $gcd reduced $xg,$yg $pyth_n3\n"; } exit 0; } { # 1 2 2 # 1 4 6 5 2 6 8 6 2 5 6 4 1 6 10 9 4 14 20 16 6 17 22 16 5 12 14 9 require Math::Polynomial; Math::Polynomial->string_config({ ascending => 1 }); sub make_poly_k4 { my ($level) = @_; my $pow = 4**$level; my $exp = 0; my $ret = 0; foreach my $coeff (1,2,2,1,2,2,1) { $ret += Math::Polynomial->monomial ($exp, $coeff); $exp += $pow; } return $ret; } print make_poly_k4(0),"\n"; print make_poly_k4(1),"\n"; my $poly = 1; foreach my $level (0 .. 4) { $poly *= make_poly_k4($level); foreach my $i (0 .. 30) { print " ",$poly->coeff($i); } print "\n"; } exit 0; } { # children formulas foreach my $k (3 .. 8) { my $half_ceil = int(($k+1) / 2); foreach my $digit (0 .. $k-1) { my $c1 = ($digit < $half_ceil ? $digit+1 : $k-$digit); my $c0 = ($digit <= $half_ceil ? $digit : $k-$digit+1); my $c2 = ($digit < $half_ceil-1 ? $digit+2 : $k-$digit-1); print "${c1}x + ${c0}y / ${c2}x + ${c1}y\n"; } print "\n"; } exit 0; } { # 1 2 3 2 1 4 7 8 5 2 7 12 13 8 3 8 13 12 7 2 5 8 7 4 1 6 11 14 9 4 15 require Math::Polynomial; Math::Polynomial->string_config({ ascending => 1 }); sub make_poly_k5 { my ($level) = @_; my $pow = 5**$level; my $exp = 0; my $ret = 0; foreach my $coeff (1,2,3,2,1,2,3,2,1) { $ret += Math::Polynomial->monomial ($exp, $coeff); $exp += $pow; } return $ret; } print make_poly_k5(0),"\n"; print make_poly_k5(1),"\n"; my $poly = 1; foreach my $level (0 .. 4) { $poly *= make_poly_k5($level); foreach my $i (0 .. 30) { print " ",$poly->coeff($i); } print "\n"; } # (1 + 2*x + 3*x^2 + 2*x^3 + x^4 + 2*x^5 + 3*x^6 + 2*x^7 + x^8) # * (1 + 2*x^5 + 3*x^10 + 2*x^15 + x^20 + 2*x^25 + 3*x^30 + 2*x^35 + x^40) # * (1 + 2*x^(25*1) + 3*x^(25*2) + 2*x^(25*3) + x^(25*4) + 2*x^(25*5) + 3*x^(25*6) + 2*x^(25*7) + x^(25*8)) exit 0; } Math-PlanePath-129/devel/sierpinski-curve.pl0000644000175000017500000003260712611353343016655 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use POSIX (); use Math::Trig 'pi'; use Math::PlanePath::SierpinskiCurve; # uncomment this to run the ### lines # use Smart::Comments; # Nlevel A146882 5*(4^(n+1)-1)/3 = 5*A002450 # =for Test-Pari-DEFINE Ytop(k,L) = if(k==0,0, (L+2)*2^(k-1) - 2) # # =for GP-Test Ytop(0,1) == 0 # # =for GP-Test Ytop(1,1) == 1 # # =for GP-Test Ytop(2,1) == 4 # # =for GP-Test Ytop(3,1) == 10 # # Ytop[k] = / 0 if k = 0 # \ (L+2)*2^(k-1) - 2 if k > 0 # = 0, 1, 4, 10, 22, 46, 94, 190, ... # for L=1 # # This is the same as C but # # Nlevel = ((3L+2)*4^level - 5) / 3 # Nlevel = 4^level - 1 + ((3L+2)*4^level - 5) / 3 - 4^level + 1 # Nlevel = 4^level - 1 + ((3L+2)*4^level - 5 - 3*4^level + 3) / 3 # Nlevel = 4^level - 1 + ((3L-1)*4^level - 2) / 3 # # # # For C = L and reckoning the first diagonal side N=0 to N=2L # as level 0, a level extends out to a triangle # # Nlevel = ((6L+4)*4^level - 4) / 3 # Xlevel = (L+2)*2^level - 1 # # For example level 2 in the default L=1 goes to N=((6*1+4)*4^2-4)/3=52 and # Xlevel=(1+2)*2^2-1=11. Or in the L=4 sample above level 1 is # N=((6*4+4)*4^1-4)/3=36 and Xlevel=(4+2)*2^1-1=11. # # The power-of-4 in Nlevel is per the plain C, with factor # 2L+1 for the points making the diagonal stair. The "/3" arises from the # extra points between replications. They become a power-of-4 series # # Nextras = 1+4+4^2+...+4^(level-1) = (4^level-1)/3 # # For example level 1 is Nextras=(4^1-1)/3=1, being point N=6 in the default # L=1. Or for level 2 Nextras=(4^2-1)/3=5 at N=6 and N=19,26,33,46. { # between two curves # (13*4^k - 7)/3 # = 2 15 67 275 1107 4435 17747 70995 283987 1135955 # not in OEIS # area=2 initial diamond is level=0 require Math::Geometry::Planar; my $path = Math::PlanePath::SierpinskiCurve->new (arms => 2); my @values; foreach my $level (0 .. 8) { my $n_hi = 4**($level+1) - 1; my @points; for (my $n = 0; $n <= $n_hi; $n+=2) { my ($x,$y) = $path->n_to_xy($n); push @points, [$x,$y]; } for (my $n = $n_hi; $n >= 0; $n-=2) { my ($x,$y) = $path->n_to_xy($n); push @points, [$x,$y]; } ### @points my $polygon = Math::Geometry::Planar->new; $polygon->points(\@points); my $area = $polygon->area; my $length = $polygon->perimeter; my $formula = two_area_by_formula($level); print "$level $length area $area $formula\n"; push @values, $area; } shift @values; require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; sub two_area_by_formula { my ($k) = @_; return (13*4**$k - 7) / 3; $k++; return (27*4**($k-1) - 18*2**($k-1) -14 * 4**($k-1) + 18 * 2**($k-1) - 4) / 3 - 1; return 9*4**($k-1) - 6*2**($k-1) + (-14 * 4**($k-1) + 18 * 2**($k-1) - 4) / 3 - 1; return 9*4**($k-1) - 6*2**($k-1) - (14 * 4**($k-1) - 18 * 2**($k-1) + 4) / 3 - 1; return 9*2**(2*$k-2) - 2*3*2**($k-1) + 1 - (14 * 4**($k-1) - 18 * 2**($k-1) + 4) / 3 - 2; return (3*2**($k-1) - 1)**2 - (14 * 4**($k-1) - 18 * 2**($k-1) + 4) / 3 - 2; return (3*2**($k-1) - 1)**2 - 4*(7 * 4**($k-1) - 9 * 2**($k-1) + 2) / 6 - 2; return (3*2**($k-1) - 2 + 1)**2 - 4*((7 * 4**($k-1) - 9 * 2**($k-1) + 2) / 6) - 2; return (3*2**($k-1) - 2 + 1)**2 - 4*area_by_formula($k-1) - 2; } } { # area under the curve # (7*4^k - 9*2^k + 2)/6 # require Math::Geometry::Planar; my $path = Math::PlanePath::SierpinskiCurve->new; my @values; foreach my $level (1 .. 10) { my ($n_lo, $n_hi) = $path->level_to_n_range($level); my @points; foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); push @points, [$x,$y]; } my $polygon = Math::Geometry::Planar->new; $polygon->points(\@points); my $area = $polygon->area; my $length = $polygon->perimeter; my $formula = area_by_formula($level); print "$level $length area $area $formula\n"; push @values, $area; } shift @values; require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; sub area_by_formula { my ($k) = @_; if ($k == 0) { return 0; } return (7 * 4**$k - 9 * 2**$k + 2) / 6; return (28 * 4**($k-1) - 18 * 2**($k-1) + 2) / 6; { return ( 4**($k-1) * 6 - 4**($k-1) + 1 + 9*4**($k-1) - 9*2**($k-1) )/3; } { return ( (4**($k-1) * 6 - 4**($k-1) + 1)/3 + 3*4**($k-1) - 3*2**($k-1)); } { return (4**($k-1) * 2 - (4**($k-1) - 1)/3 + 3*2* (4**($k-1) - 2**($k-1))/(4-2)); } { # i=k-2 # = 2*4^(k-1) + 3*2 * sum 4^i * 2^(k-2-i) - (4^(k-1) - 1)/3 # i=0 # = 2*4^(k-1) + 3*2 * (4^(k-1) - 2^(k-1))/(4-2) - (4^(k-2) - 1)/3 if ($k == 1) { return 2; } my $total = 4**($k-1) * 2; $total -= (4**($k-1) - 1)/3; $total += 3*2* (4**($k-1) - 2**($k-1))/(4-2); return $total; } { # i=k-2 # = 2*4^(k-1) + 3*2^2 * sum 4^i * 2^(k-3-i) - (4^(k-1) - 1)/3 # i=0 # = 2*4^(k-1) + 3*2^2 * (4^(k-1) - 2^(k-1))/(4-2) - (4^(k-2) - 1)/3 if ($k == 1) { return 2; } my $total = 4**($k-1) * 2; $total -= (4**($k-1) - 1)/3; foreach my $i (0 .. $k-2) { $total += 4**$i * (3 * 2**($k-1-$i)); } return $total; } { # A(2) = 4*A(1) + (3*2^(1) - 1) # = (3*2^(k-1) - 1) 0 + k-1 = k-1 # + 4 *(3*2^(k-2) - 1) 1 + k-2 = k-1 # + 4^2*(3*2^(k-3) - 1) 2 + k-2 = k-1 # + ... # + 4^(k-3)*(3*2^(2) - 1) Ytop(2) k-3 + 2 = k-1 # + 4^(k-2)*A(1) # i=k-2 # = 2*4^(k-1) + sum 4^i * (3*2^(k-1-i) - 1) # i=0 if ($k == 1) { return 2; } my $total = 4**($k-1) * 2; foreach my $i (0 .. $k-2) { $total += 4**$i * (3 * 2**($k-1-$i) - 1); } return $total; } { # A(k) = 4*A(k-1) + Ytop(k) + 1 k >= 2 # A(1) = 2 # A(0) = 0 # Ytop(k) = 3*2^(k-1) - 2 # A(k) = 4*A(k-1) + 3*2^(k-1) - 1 # if ($k == 1) { return 2; } return 4*area_by_formula($k-1) + 3 * 2**($k-1) - 1; } } } { # Y coordinate sequence require Math::NumSeq::PlanePathCoord; my $seq = Math::NumSeq::PlanePathCoord->new (planepath => 'SierpinskiCurve', coordinate_type => 'Sum'); my @values; for (1 .. 500) { my ($i,$value) = $seq->next; push @values, $value-1; } unduplicate(\@values); print "values: ", join(',', @values), "\n"; require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, verbose => 1); exit 0; sub unduplicate { my ($aref) = @_; my $i = 1; while ($i < $#$aref) { if ($aref->[$i] == $aref->[$i-1]) { splice @$aref, $i, 1; } else { $i++; } } } } { # dSumAbs require Math::NumSeq::PlanePathDelta; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => 'SierpinskiCurveStair,arms=6', delta_type => 'dSumAbs'); for (1 .. 300) { my ($i,$value) = $seq->next; print "$value,"; if ($i % 6 == 5) { print "\n"; } } exit 0; } { # A156595 Mephisto Waltz first diffs xor as turns require Tk; require Tk::CanvasLogo; require Math::NumSeq::MephistoWaltz; my $top = MainWindow->new; my $width = 1200; my $height = 800; my $logo = $top->CanvasLogo(-width => $width, -height => $height)->pack; my $turtle = $logo->NewTurtle('foo'); $turtle->LOGO_PU(); $turtle->LOGO_FD(- $height/2*.9); $turtle->LOGO_PD(); my $step = 5; $turtle->LOGO_FD($step); my $seq = Math::NumSeq::MephistoWaltz->new; my ($i,$prev) = $seq->next; for (;;) { my ($i,$value) = $seq->next; my $turn = $value ^ $prev; $prev = $value; last if $i > 10000; if ($turn) { $turtle->LOGO_FD($step); if ($i & 1) { $turtle->LOGO_RT(120); } else { $turtle->LOGO_LT(120); } } else { $turtle->LOGO_FD($step); } $logo->createArc($turtle->{x}+2, $turtle->{y}+2, $turtle->{x}-2, $turtle->{y}-2); } Tk::MainLoop(); exit; } { # dX,dY require Math::PlanePath::SierpinskiCurve; my $path = Math::PlanePath::SierpinskiCurve->new; foreach my $n (0 .. 32) { # my $n = $n + 1/256; my ($x,$y) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); my $sx = $x2-$x; my $sy = $y2-$y; my $sdir = dxdy_to_dir8($sx,$sy); my ($dx,$dy) = $path->_WORKING_BUT_HAIRY__n_to_dxdy($n); my $ddir = dxdy_to_dir8($dx,$dy); my $diff = ($dx != $sx || $dy != $sy ? ' ***' : ''); print "$n $x,$y $sx,$sy\[$sdir] $dx,$dy\[$ddir]$diff\n"; } # return 0..7 sub dxdy_to_dir8 { my ($dx, $dy) = @_; return atan2($dy,$dx) / atan2(1,1); if ($dx == 1) { if ($dy == 1) { return 1; } if ($dy == 0) { return 0; } if ($dy == -1) { return 7; } } if ($dx == 0) { if ($dy == 1) { return 2; } if ($dy == -1) { return 6; } } if ($dx == -1) { if ($dy == 1) { return 3; } if ($dy == 0) { return 4; } if ($dy == -1) { return 5; } } die 'oops'; } exit 0; } { # Mephisto Waltz 1/12 slice of plane require Tk; require Tk::CanvasLogo; require Math::NumSeq::MephistoWaltz; my $top = MainWindow->new; my $width = 1000; my $height = 800; my $logo = $top->CanvasLogo(-width => $width, -height => $height)->pack; my $turtle = $logo->NewTurtle('foo'); $turtle->LOGO_RT(45); $turtle->LOGO_PU(); $turtle->LOGO_FD(- $height*sqrt(2)/2*.9); $turtle->LOGO_PD(); $turtle->LOGO_RT(135); $turtle->LOGO_LT(30); my $step = 5; $turtle->LOGO_FD($step); my $seq = Math::NumSeq::MephistoWaltz->new; for (;;) { my ($i,$value) = $seq->next; last if $i > 10000; if ($value) { $turtle->LOGO_RT(60); $turtle->LOGO_FD($step); } else { $turtle->LOGO_LT(60); $turtle->LOGO_FD($step); } } Tk::MainLoop(); exit; } { require Tk; require Tk::CanvasLogo; require Math::NumSeq::PlanePathTurn; my $top = MainWindow->new(); my $logo = $top->CanvasLogo->pack; my $turtle = $logo->NewTurtle('foo'); my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'KochCurve', turn_type => 'Left'); $turtle->LOGO_RT(45); $turtle->LOGO_FD(10); for (;;) { my ($i,$value) = $seq->next; last if $i > 64; if ($value) { $turtle->LOGO_RT(45); $turtle->LOGO_FD(10); $turtle->LOGO_RT(45); $turtle->LOGO_FD(10); } else { $turtle->LOGO_LT(90); $turtle->LOGO_FD(10); $turtle->LOGO_LT(90); $turtle->LOGO_FD(10); } } Tk::MainLoop(); exit; } { # filled fraction require Math::PlanePath::SierpinskiCurve; require Number::Fraction; my $path = Math::PlanePath::SierpinskiCurve->new; foreach my $level (1 .. 20) { my $Ntop = 4**$level / 2 - 1; my ($x,$y) = $path->n_to_xy($Ntop); my $Xtop = 3*2**($level-1) - 1; $x == $Xtop or die "x=$x Xtop=$Xtop"; my $frac = $Ntop / ($x*($x-1)/2); print " $level $frac\n"; } my $nf = Number::Fraction->new(4,9); my $limit = $nf->to_num; print " limit $nf = $limit\n"; exit 0; } { # filled fraction require Math::PlanePath::SierpinskiCurveStair; require Number::Fraction; foreach my $L (1 .. 5) { print "L=$L\n"; my $path = Math::PlanePath::SierpinskiCurveStair->new (diagonal_length=>$L); foreach my $level (1 .. 10) { my $Nlevel = ((6*$L+4)*4**$level - 4) / 3; my ($x,$y) = $path->n_to_xy($Nlevel); my $Xlevel = ($L+2)*2**$level - 1; $x == $Xlevel or die "x=$x Xlevel=$Xlevel"; my $frac = $Nlevel / ($x*($x-1)/2); print " $level $frac\n"; } my $nf = Number::Fraction->new((12*$L+8),(3*$L**2+12*$L+12)); my $limit = $nf->to_num; print " limit $nf = $limit\n"; } exit 0; } { my $path = Math::PlanePath::SierpinskiCurve->new; my @rows = ((' ' x 79) x 64); foreach my $n (0 .. 3 * 3**4) { my ($x, $y) = $path->n_to_xy ($n); $x += 32; substr ($rows[$y], $x,1, '*'); } local $,="\n"; print reverse @rows; exit 0; } { my @rows = ((' ' x 64) x 32); foreach my $p (0 .. 31) { foreach my $q (0 .. 31) { next if ($p & $q); my $x = $p-$q; my $y = $p+$q; next if ($y >= @rows); $x += 32; substr ($rows[$y], $x,1, '*'); } } local $,="\n"; print reverse @rows; exit 0; } Math-PlanePath-129/devel/hilbert-sides.pl0000644000175000017500000001667313776741263016135 0ustar gggg#!/usr/bin/perl -w # Copyright 2015, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use FindBin; use List::Util 'min','max','sum'; use Math::PlanePath::HilbertCurve; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; { # HilbertSides number of overlap segments # singles 0,3,13,53,209,829,3297,13149,52513 # # doubles 0,0,1,5,23,97,399,1617,6511 # A109765 gf x/( (1-4*x)*(1-2*x)*(1+x) ) # recurrence_guess(OEIS_samples("A109765")) # 2/5*4^n - 1/3*2^n + vector_modulo([-1/15, 1/15],n) require Math::PlanePath::HilbertSides; my $path = Math::PlanePath::HilbertSides->new; my %count; my $n = 0; my ($prev_x,$prev_y) = $path->n_to_xy($n++); my (@v1,@v2); foreach my $k (0 .. 8) { my $limit = 4**$k; for ( ; $n < $limit; $n++) { my ($x,$y) = $path->n_to_xy($n); if ($x < $prev_x || $y < $prev_y) { $count{"$x,$y to $prev_x,$prev_y"}++; } else { $count{"$prev_x,$prev_y to $x,$y"}++; } ($prev_x,$prev_y) = ($x,$y); } my @hist = (0,0,0); foreach my $value (values %count) { $hist[$value]++; } ### @hist print "k=$k $hist[1], $hist[2]\n"; push @v1, $hist[1]; push @v2, $hist[2]; $hist[1] + 2*$hist[2] == 4**$k-1 or die; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@v1, name => "v1", verbose=>1); Math::OEIS::Grep->search(array => \@v2, name => "v2", verbose=>1); exit 0; # singles(k) = 9/10*4^k + 1/6*2^k - 1/15*(-1)^k - 1; # vector(8,k,k--; singles(k)) # gsingles(x) = (x^2)/(1 - 5*x + 2*x^2 + 8*x^3); # gf_terms(gsingles(x), 8) # doubles(k) = 1/10*4^k - 1/6*2^k + 1/15*(-1)^k; # vector(8,k,k--; doubles(k)) # gdoubles(x) = (x^2)/(1 - 5*x + 2*x^2 + 8*x^3); # gf_terms(gdoubles(x), 8) # vector(8,k,k--; singles(k)+doubles(k)) # sub sides_count_1s { # my ($k) = @_; # # even # + (1/15)/(1 + x) # + (-1/6)/(1 - 2*x) # + (1/10)/(1 - 4*x) # } } { # HilbertSides axis count # 3 | 5----6---7,9--10---11 # | | | | # 2 | 4----3 8 13---12 24---23 # | | | | # 1 | 2 14 17---18---19 22 # | | | | | | # Y=0 | 0----1 15---16 20---21 # # X 1,1,2,3,6,11,22,43,86 # A005578 a(n) = 2/3*2^n - 1/6*(-1)^n + 1/2 # # Y 0,0,1,2,5,10,21,42,85 # A000975 binary 1010... # # Xsegs(k) = 1/3*2^k + 1/2 + 1/6*(-1)^k; # Ysegs(k) = 1/3*2^k - 1/2 + 1/6*(-1)^k; # Xsegs(k) = if(k==0,1, k%2, Xsegs(k-1)+Ysegs(k-1), 2*Xsegs(k-1)); # Ysegs(k) = if(k==0,0, k%2, 2*Ysegs(k-1), Xsegs(k-1)+Ysegs(k-1)); # read("memoize.gp"); Xsegs = memoize(Xsegs); Ysegs = memoize(Ysegs); # vector(19,k,k--; Xsegs(k)) # vector(19,k,k--; Ysegs(k)) # vector(30,k,k--; Xsegs(k)) == vector(30,k,k--; Ysegs(k)+1) # D(k) = if(k==0,0, 4*D(k-1) + if(k%2,Ysegs(k-1),Xsegs(k-1))); # vector(9,k,k--; D(k)) require Math::PlanePath::HilbertSides; my $path = Math::PlanePath::HilbertSides->new; foreach my $axis (0, 1) { my @values; foreach my $k (0 .. 8) { my $count = 0; foreach my $i (0 .. 2**$k-1) { my $x = ($axis ? 0 : $i); my $y = ($axis ? $i : 0); my $n = $path->xy_to_n($x,$y) // next; my ($dx,$dy) = $path->n_to_dxdy($n); $dy == $axis || next; $count++; } print "k=$k ($axis) $count\n"; push @values, $count; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, name => "$axis", verbose=>1); } exit 0; } { # HilbertSides axis N # hex digit 0 1 E F my @table = ([ 0,1,9,9, 9,9,9,9, 9,9,9,9, 9,9,2,0 ], [ 1,9,9,2, 0,1,9,9, 9,9,9,9, 9,9,9,9 ], [ 9,9,9,9, 9,9,9,9, 9,9,2,0, 1,9,9,2 ]); # hex digit 0 3 4 5 A B C F # 0,F # x # /^ \^ # 1 // \\ B # //4 E\\ # v/ C v\ # 0,5 y <-------- z A,F # --------> # 3 sub n_is_side { my ($n, $side) = @_; my @digits = digit_split_lowtohigh($n,16); foreach my $digit (reverse @digits) { $side = $table[$side][$digit]; if ($side == 9) { return 0; } } return 1; } require Math::PlanePath::HilbertSides; my $path = Math::PlanePath::HilbertSides->new; foreach my $n (0 .. 2**16) { my ($x,$y) = $path->n_to_xy($n); my ($dx,$dy) = $path->n_to_dxdy($n); my $want_side = ($y == 0 && $dy == 0 ? 0 : $x == 0 && $dx == 0 ? 1 : next); my $got_side = (n_is_side($n,0) ? 0 : n_is_side($n,1) ? 1 : -1); $want_side == $got_side or die; } exit 0; } { # HilbertSides axis N # X=0,F 0x 1y Ez Fx y z # Y=4 0y 3z 4x 5y --x-- # Z=B Az Bx Cy Fz # 0,F # x # /^ \^ # 1 // \\ B # //4 E\\ # v/ C v\ # 0,5 y <-------- z A,F # --------> # 3 require Math::PlanePath::HilbertSides; my $path = Math::PlanePath::HilbertSides->new; my @values; foreach my $x (0 .. 255) { my $n = $path->xy_to_n($x,0) // next; $path->xy_to_n($x+1,0) // next; printf "%3x\n", $n; push @values, $n; } shift @values; shift @values; require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; } { # HilbertSides straight/not # turn left or right at 4^k # straight at 2^k # 3 | 5----6---7,9--10---11 # | | | | # 2 | 4----3 8 13---12 24---23 # | | | | # 1 | 2 14 17---18---19 22 # | | | | | | # Y=0 | 0----1 15---16 20---21 # A096268 # Straight # 0,1,0, 0,0,1,0, 1,0,1,0,0,0,1,0, 0,0,1,0,0,0,1,0,1,0,1,0,0,0,1,0,1,0,1,0, # vector(25,n,valuation(n,2)%2) # even,odd trailing 0 bits, period doubling sequence require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'HilbertSides', turn_type => 'Straight'); foreach (1 .. 40) { my ($i, $value) = $seq->next; print "$value," } print "\n"; exit 0; } { require Math::PlanePath::HilbertSides; my $path = Math::PlanePath::HilbertSides->new; my @want = ([0,0], [1,0], [1,1], [1,2], [0,2], [0,3], [1,3], [2,3], [2,2], [2,3], [3,3], [4,3], [4,2], [3,2], [3,1], [3,0], [4,0], ); foreach my $n (0 .. 16) { my ($x,$y) = $path->n_to_xy($n); my $diff = ($n > $#want || ($x == $want[$n]->[0] && $y == $want[$n]->[1]) ? '' : ' ***'); print "n=$n $x,$y$diff\n"; } exit 0; } Math-PlanePath-129/devel/a240025.l0000644000175000017500000000624713641470237014077 0ustar gggg; Copyright 2019, 2020 Kevin Ryde ; ; This file is part of Math-PlanePath. ; ; Math-PlanePath 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, or (at your option) any later ; version. ; ; Math-PlanePath 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 Math-PlanePath. If not, see . ; a240025.l -- Square Spiral by A240025 Turns. ; Kevin Ryde, April 2020 ; ; Name this file a240025.l and run from the command line ; ; xfractint type=lsystem lfile=a240025.l lname=SquareSpiral params=9 ; ; Or interactively, the usual key "t", choose type lsystem, "F6" files, ; "F6" again the current directory, choose a240025.l, etc. Must name ; the file just foo.l not foo.l.txt for it to appear in the file chooser. ; ; "lname" can be SquareSpiral or SquareSpiral2 which are the ; variations below. Interactively, "t" and choose type lsystem ; (again) goes to the available L-systems in the current file. ; ; "params=9" is the expansion level (order). This is the number of ; sides in the spiral here. Interactively, key "z" changes just the ; order. ; The symbol string generated is like ; ; S F T + F T + F F T + F F T + F F F T + F F F T + ; a(n) = 1 1 1 0 1 0 1 0 0 0 1 0 0 0 1 ; n = 0 1 2 3 4 5 6 7 8 9 10 11 144 ; ; F is draw forward. ; Turn a(n), for n>=1, is after each F. ; It is "+" for a(n)=1 turn, or nothing for a(n)=0 no turn. ; ; T is a non-drawing symbol. It precedes each "+" and its expansion ; increases the length of the preceding run of Fs which are a(n)=0s ; and which are a side of the square. ; The morphism given in the comments in A240025 has 1->0,1 which here ; would be a rule like "+ = F+". But Fractint doesn't allow rewrite ; of "+", hence T before each + to achieve the same result. ; ; Initial a(0)=1 is reckoned as a special start-of-sequence symbol S. ; It could have a turn "+" like other a(n)=1's, but that merely has ; the effect of turning the whole spiral by 90 degrees. Prefer to ; omit it so start directed East. ; ; The expansion of S is two sides of length 1, and they expand ; subsequently to two sides length 2, then two sides length 3, etc. SquareSpiral { Angle 4 ; 90 degrees Axiom S S = SFT+FT+ T = FT } ; A little variation can be made by putting T before each run of Fs ; instead of after. The symbol string generated is then like ; ; S T F + T F + T F F + T F F + T F F F + T F F F + ; ; T is still used to increase the length of the Fs, but the Fs following it. ; In this form, T is also at the start of the string which makes it a ; little less like the morphism 1->0,1. SquareSpiral2 { Angle 4 ; 90 degrees Axiom S S = STF+TF+ T = TF } ; Local variables: ; compile-command: "xfractint type=lsystem lfile=a240025.l lname=SquareSpiral params=9" ; End: Math-PlanePath-129/devel/mephisto-waltz.logo0000644000175000017500000000255212061163535016664 0ustar gggg#!/usr/bin/ucblogo ;; Copyright 2012 Kevin Ryde ;; ;; This file is part of Math-PlanePath. ;; ;; Math-PlanePath 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, or (at your option) any later ;; version. ;; ;; Math-PlanePath 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 Math-PlanePath. If not, see . ;; cf A156595 xor adjacent mephisto waltz ;; ;;----------------------------------------------------------------------------- to increment :var make :var (thing :var)+1 end to count.ternary.twos :n localmake "count 0 while [:n <> 0] [ if [(remainder :n 3) = 2] [increment "count]; make "n int(:n/3) ] output :count end to mephisto.turn.angle :n output ifelse ((modulo (count.ternary.twos :n) 2) = 0) [60] [-60] end to mephisto :steps right 90 left 45 penup back 300 right 90 pendown localmake "step.len 3 for [i 0 :steps 1] [ forward :step.len left mephisto.turn.angle :i ] end mephisto 2000 Math-PlanePath-129/devel/period-doubling.pl0000644000175000017500000000375613537342732016451 0ustar gggg#!/usr/bin/perl -w # Copyright 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use Carp 'croak'; use Math::PlanePath::KochCurve; { require Language::Logo; my $lo = Logo->new(update => 2, port => 8200 + (time % 100)); my $draw; # $lo->command("seth 135; backward 200; seth 90"); $lo->command("pendown; hideturtle; seth 90"); my $angle = 0; foreach my $n (1 .. 4096) { my $a = A309873($n); my $angle = $a*165; $lo->command("forward 13; left $angle"); } $lo->disconnect("Finished..."); exit 0; } sub A309873 { my ($n) = @_; $n >= 0 || croak; my $ret = 1; until ($n & 1) { $n >>= 1; $ret = -$ret; } return $ret; } { # A056832 All a(n) = 1 or 2; a(1) = 1; get next 2^k terms by repeating # first 2^k terms and changing last element so sum of first 2^(k+1) terms # is odd. # # Is lowest non-zero base4 digit(n) 1,3->a(n)=1 2->a(n)=2. # a(2^k) flips 1<->2 each time for low non-zero flipping 1<->2. # a(2^k) always flips because odd sum becomes even on duplicating. # my @a = (1); for my $i (1 .. 6) { push @a, @a; unless (sum(@a) & 1) { $a[-1] = 3-$a[-1]; # 2<->1 print "i=$i flip last\n"; } print @a,"\n"; } foreach my $i (1 .. 64) { my $d = base4_lowest_nonzero_digit($i); if ($d != 2) { $d = 1; } print $d; } print "\n"; exit 0; } Math-PlanePath-129/devel/grid.pl0000644000175000017500000000205411423250646014273 0ustar gggg#!/usr/bin/perl -w # Copyright 2010 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # if (($self->{'show'}||'') eq 'dump-list') { # foreach my $i (1 .. $n_pixels-1) { # my ($x, $y) = $path->n_to_xy ($i); # if (! defined $x) { last; } # $x += $x_origin; # $y += $y_origin; # printf "%d %s,%s\n", $i, $x//'undef', $y//'undef'; # } # exit 0; # } # Math-PlanePath-129/devel/hypot.pl0000644000175000017500000000511011767504527014520 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use POSIX (); use Math::PlanePath::Hypot; use Math::PlanePath::HypotOctant; { # even equivalence my $all = Math::PlanePath::HypotOctant->new (points => 'all'); my $even = Math::PlanePath::HypotOctant->new (points => 'even'); for (my $n = $all->n_start; $n < 300; $n++) { my ($ex,$ey) = $even->n_to_xy($n); my ($ax,$ay) = $all->n_to_xy($n); my $cx = ($ex+$ey)/2; my $cy = ($ex-$ey)/2; my $diff = ($cx!=$ax || $cy!=$ay ? ' **' : ''); print "$n $ax,$ay $cx,$cy$diff\n"; } exit 0; } { my %comb; my $limit = 1000; my $xlimit = int(sqrt($limit / 2)); foreach my $x (1 .. $xlimit) { foreach my $y (1 .. $x) { my $h = $x*$x+$y*$y; if ($h <= $limit) { $comb{$h} = 1; } } } my @all = sort {$a<=>$b} keys %comb; print join(',',@all),"\n"; print "all ",scalar(@all),"\n"; foreach my $h (keys %comb) { foreach my $j (keys %comb) { if ($j < $h && ($h % $j) == 0 && is_int(sqrt($h / $j))) { delete $comb{$h}; last; } } } my @comb = sort {$a<=>$b} keys %comb; print join(',',@comb),"\n"; print "count ",scalar(@comb),"\n"; exit 0; sub is_int { return $_[0] == int $_[0]; } } { my @seen_ways; for (my $s = 1; $s < 1000; $s++) { my $h = $s * $s; my @ways; for (my $x = 1; $x < $s; $x++) { my $y = sqrt($h - $x*$x); # if ($y < $x) { # last; # } if ($x >= $y && $y == int($y)) { push @ways, " $x*$x + $y*$y\n"; } } my $num_ways = scalar(@ways); $seen_ways[$num_ways] ||= $seen_ways[$num_ways] = "$s*$s = $h $num_ways ways\n" . join('',@ways); } print grep {defined} @seen_ways; exit 0; } { for (1 .. 1000) { Math::PlanePath::Hypot::_extend(); } # $,="\n"; # print map {$_//'undef'} @Math::PlanePath::Hypot::hypot_to_n; exit 0; } Math-PlanePath-129/devel/koch-snowflakes.pl0000644000175000017500000001766212375744415016470 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; use Math::PlanePath::KochSnowflakes; # uncomment this to run the ### lines # use Smart::Comments; { # num angles taking points pairwise # # one direction, so modulo 180 # can go outside: 3,12,102,906,7980 # not going outside: 3,12,90,666 # # both directions # can go outside: 3,18,155,1370 # not going outside: 3,18,124,917,6445 # 12, one direction always X increasing # 0,-1 12->6 south # 1,-3 12->7 # 1,-1 12->11 # 2,-1 14->7 # 3,-1 12->10 # 5,-1 14->9 # 1,0 14->13 east # 5,1 3,1 2,1 1,1 1,3 # # 12 # / \ # 14----13 11----10 # \ / # 15 9 # \ # 4---- 5 7---- 8 # \ / # 6 require Math::Geometry::Planar; my $path = Math::PlanePath::KochSnowflakes->new; my @values; require Math::PlanePath::GcdRationals; foreach my $level (0 .. 7) { my $n_level = 4**$level; my $n_end = 4**($level+1) - 1; my @points; foreach my $n ($n_level .. $n_end) { my ($x,$y) = $path->n_to_xy($n); push @points, [$x,$y]; } sub points_equal { my ($p1, $p2) = @_; return (abs($p1->[0] - $p2->[0]) < 0.00001 && abs($p1->[1] - $p2->[1]) < 0.00001); } # Return true if line $p1--$p2 is entirely within the snowflake. # If $p1--$p2 intersects any boundary segment then it's not entirely # within. # # -3,-1 ---- -1,-1 my $segment_inside = sub { my ($p1,$p2) = @_; ### segment_inside: join(',',@$p1).' '.join(',',@$p2) foreach my $i (0 .. $#points) { my $pint = Math::Geometry::Planar::SegmentIntersection([$p1,$p2, $points[$i-1],$points[$i]]) || next; ### intersects: join(',',@{$points[$i-1]}).' '.join(',',@{$points[$i]}).' at '.join(',',@$pint) if (points_equal($pint,$p1) || points_equal($pint,$p1) || points_equal($pint,$points[$i-1]) || points_equal($pint,$points[$i])) { ### is an endpoint ... next; } if (Math::Geometry::Planar::Colinear([$p1,$pint,$points[$i]])) { ### but colinear ... next; } ### no, crosses boundary ... return 0; } ### yes, all inside ... return 1; }; my %seen; foreach my $i (0 .. $#points) { foreach my $j ($i+1 .. $#points) { ### pair: "$i -- $j" my $p1 = $points[$i]; my $p2 = $points[$j]; my $dx = $p1->[0] - $p2->[0]; my $dy = $p1->[1] - $p2->[1]; my $rdx = $dx; my $rdy = $dy; if ($rdx < 0 || ($rdx == 0 && $rdy < 0)) { $rdx = -$rdx; $rdy = -$rdy; } my $g = Math::PlanePath::GcdRationals::_gcd($rdx,$rdy); $rdx /= $g; $rdy /= $g; next if $seen{"$rdx,$rdy"}; next unless $segment_inside->($p1, $p2); $seen{"$rdx,$rdy"} = 1; } } my $count = scalar(keys %seen); if ($count <= 12) { my @strs = keys %seen; @strs = sort strpoint_cmp_by_atan2 @strs; print join(' ',@strs),"\n"; } push @values, $count; print "$level $count\n"; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; sub strpoint_cmp_by_atan2 { my ($ax,$ay) = split /,/, $a; my ($bx,$by) = split /,/, $b; return atan2($ay,$ax) <=> atan2($by,$bx); } } { # num acute angle turns, being num right hand turns # A178789 my $path = Math::PlanePath::KochSnowflakes->new; my @values; require Math::NumSeq::PlanePathTurn; foreach my $level (0 .. 8) { my $n_level = 4**$level; my $n_end = 4**($level+1) - 1; my @x; my @y; foreach my $n ($n_level .. $n_end) { my ($x,$y) = $path->n_to_xy($n); push @x, $x; push @y, $y; } my $count = 0; foreach my $i (0 .. $#x) { my $dx = $x[$i-1] - $x[$i-2]; my $dy = $y[$i-1] - $y[$i-2]; my $next_dx = $x[$i] - $x[$i-1]; my $next_dy = $y[$i] - $y[$i-1]; my $tturn6 = Math::NumSeq::PlanePathTurn::_turn_func_TTurn6($dx,$dy, $next_dx,$next_dy); ### $tturn6 if ($tturn6 == 2 || $tturn6 == 4) { $count++; } } $count = ($n_end - $n_level + 1) - $count; # non-acute ones push @values, $count; my $calc = 4**$level + 2; print "$level $count $calc\n"; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; } { # num lines at 60deg angles, A110593 2*3^n # 3,6,18,54,162 # 18*3=54 # my $path = Math::PlanePath::KochSnowflakes->new; my @values; require Math::NumSeq::PlanePathDelta; foreach my $level (0 .. 7) { my $n_level = 4**$level; my $n_end = 4**($level+1) - 1; my %seen; foreach my $n ($n_level .. $n_end) { my ($x,$y) = $path->n_to_xy($n); my ($dx,$dy) = $path->n_to_dxdy($n); if ($n == $n_end) { my ($x2,$y2) = $path->n_to_xy($n_level); $dx = $x2 - $x; $dy = $y2 - $y; } my $tdir6 = Math::NumSeq::PlanePathDelta::_delta_func_TDir6($dx,$dy); my $value; if ($tdir6 % 3 == 0) { $value = "H-$y"; } elsif ($tdir6 % 3 == 1) { $value = "I-".($x-$y); } else { $value = "J-".($x+$y); } # print " $n $value\n"; $seen{$value} = 1; } my $count = scalar(keys %seen); push @values, $count; print "$level $count\n"; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; } { # area require Math::Geometry::Planar; my $path = Math::PlanePath::KochSnowflakes->new; my @values; my $prev_a_log = 0; my $prev_len_log = 0; # area of an equilateral triangle of side length 1 use constant AREA_EQUILATERAL_1 => sqrt(3)/4; foreach my $level (0 .. 7) { my $n_level = 4**$level; my $n_end = 4**($level+1) - 1; my @points; foreach my $n ($n_level .. $n_end) { my ($x,$y) = $path->n_to_xy($n); # ($x,$y) = triangular_to_unit_side($x,$y); push @points, [$x,$y]; } my $polygon = Math::Geometry::Planar->new; $polygon->points(\@points); my $a = $polygon->area; my $len = $polygon->perimeter; # $a /= 3**$len; # $len /= sqrt(3)**$len; my $a_log = log($a); my $len_log = log($len); my $d_a_log = $a_log - $prev_a_log; my $d_len_log = $len_log - $prev_len_log; my $f = $d_a_log / $d_len_log; # $a /= AREA_EQUILATERAL_1(); print "$level $a\n"; # print "$level $d_len_log $d_a_log $f\n"; push @values, $a; $prev_a_log = $a_log; $prev_len_log = $len_log; } shift @values; require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; sub triangular_to_unit_side { my ($x,$y) = @_; return ($x/2, $y*(sqrt(3)/2)); } } { # area print sqrt(48)/10,"\n"; my $sum = 0; foreach my $level (1 .. 10) { my $area = (1 + 3/9*$sum) * sqrt(3)/4; print "$level $area\n"; $sum += (4/9)**$level; } exit 0; } { # X axis N increasing my $path = Math::PlanePath::KochSnowflakes->new; my $prev_n = 0; foreach my $x (0 .. 10000000) { my $n = $path->xy_to_n($x,0) // next; if ($n < $prev_n) { print "decrease N at X=$x N=$n prev_N=$prev_n\n"; } $prev_n = $n; } } Math-PlanePath-129/devel/alternate-terdragon.pl0000644000175000017500000001270613244171164017315 0ustar gggg#!/usr/bin/perl -w # Copyright 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min', 'max'; use Math::PlanePath::AlternateTerdragon; use Math::PlanePath; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use List::Pairwise; use Math::BaseCnv; use lib 'xt'; use MyOEIS; # uncomment this to run the ### lines # use Smart::Comments; # # skip low zeros # # 1 left # # 2 right # ones(n) - ones(n+1) # 1*3^k left # 2*3^k right { foreach my $arms (1 .. 6) { print "arms=$arms\n"; my $path = Math::PlanePath::AlternateTerdragon->new (arms => $arms); my $sum_max = 0; my $sum_min = 0; my $diff_max = 0; my $diff_min = 0; my $sum_first_neg; my $diff_first_neg; foreach my $n (0 .. 10000) { my ($x,$y) = $path->n_to_xy($n); my $s = $x + $y; $sum_max = max($sum_max, $s); $sum_min = min($sum_min, $s); if (! defined $sum_first_neg && $s<0) { $sum_first_neg = $n; } my $d = $x - $y; $diff_max = max($diff_max, $d); $diff_min = min($diff_min, $d); if (! defined $diff_first_neg && $d<0) { $diff_first_neg = $n; } } $sum_first_neg //= 'none'; $diff_first_neg //= 'none'; print " sum $sum_min to $sum_max first neg at $sum_first_neg\n"; print " diff $diff_min to $diff_max first neg at $diff_first_neg\n"; } exit 0; } { # initial points picture for the POD require Image::Base::Text; my $path = Math::PlanePath::AlternateTerdragon->new; my $diagonal = 2; my $xscale = 6; my $yscale = 3; my $xmax = 7; my $ymin = -1; my $ymax = 2; my $width = ($xmax+4)*$xscale; my $height = ($ymax - $ymin + 2)*$yscale; ### size: "$width,$height" my $image = Image::Base::Text->new (-width => $width, -height => $height); $image->rectangle (0,0, $width-1,$height-1, ' '); my $transform = sub { my ($x,$y) = @_; return (($x+1)*$xscale+5, $height - ($y-($ymin-1))*$yscale); }; foreach my $y ($ymin .. $ymax) { my ($px,$py) = $transform->(0,$y); my $str = sprintf "Y=%-2d ", $y; my $offset = length($str) + 2; foreach my $i (0 .. length($str)-1) { $image->xy($px-$offset+$i, $py, substr($str,$i,1)); } } foreach my $y ($ymin .. $ymax) { foreach my $x (0 .. $xmax) { next if ($x+$y)%2; $path->xyxy_to_n_list_either($x,$y, $x+2,$y) or next; my ($px1,$py1) = $transform->($x,$y); my ($px2,$py2) = $transform->(min($x+2,$xmax),$y); ### line: "$x,$y pixels $px1 to $px2, $py1" foreach my $px ($px1 .. $px2) { ### char: "$px,$py1" $image->xy ($px, $py1, '-'); } } } foreach my $y ($ymin .. $ymax) { foreach my $x (0 .. $xmax) { next if ($x+$y) % 2; my @n_list = $path->xy_to_n_list($x,$y) or next; my $str = ' '.join(',',@n_list).' '; my ($px,$py) = $transform->($x,$y); ### at: "$x,$y pixels $px, $py" ### @n_list ### $str $py >= 0 || die; $py < $height || die; $px >= 0 || die; $px < $width || die; my $offset = int(length($str)/2); foreach my $i (0 .. length($str)-1) { $image->xy($px-$offset+$i, $py, substr($str,$i,1)); } foreach my $dx (-1,1) { foreach my $dy (-1,1) { $path->xyxy_to_n_list_either($x,$y, $x+$dx,$y+$dy) or next; $image->xy ($px+$diagonal*$dx, $py-$dy, ($dx*$dy > 0 ? '/' : '\\')); } } } } my $str = $image->save_string; print $str; exit 0; } { # arms=6 sample points for the POD my $path = Math::PlanePath::AlternateTerdragon->new (arms => 6); my $show = sub { my ($x,$y) = @_; my @n_list = $path->xy_to_n_list($x,$y); [join(',',@n_list)]; }; print " \\ / \\ / \\ / \\ / --- @{$show->(-1,1)} ----------------- @{$show->(1,1)} --- / \\ / \\ \\ / \\ / \\ / \\ / \\ / \\ / --- @{$show->(-2,0)} ------------- @{$show->(0,0)} -------------- @{$show->(2,0)} --- / \\ / \\ / \\ / \\ / \\ / \\ \\ / \\ / ---- @{$show->(-1,-1)} ---------------- @{$show->(1,-1)} --- / \\ / \\ / \\ / \\ "; exit 0; } { # segments by direction # A092236, A135254, A133474 # A057083 half term, offset from 3^k, A103312 similar my $path = Math::PlanePath::AlternateTerdragon->new; $path->n_to_xy(5); exit 0; } Math-PlanePath-129/devel/cellular-rule.pl0000644000175000017500000000711412042067504016115 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use strict; use Math::PlanePath::CellularRule; # uncomment this to run the ### lines use Smart::Comments; { # A169707 rule 750 my %grid; my $width = 40; my $height = 40; $grid{0,0} = 1; foreach my $level (0 .. 40) { print "level $level\n"; foreach my $y (reverse -$height .. $height) { foreach my $x (-$width .. $width) { print $grid{$x,$y} // ' '; } print "\n"; } my %new_grid = %grid; my $count_new = 0; foreach my $y (-$height .. $height) { foreach my $x (-$width .. $width) { my $count = (($grid{$x-1,$y} || 0) + ($grid{$x+1,$y} || 0) + ($grid{$x,$y-1} || 0) + ($grid{$x,$y+1} || 0)); # print "$level $x,$y count=$count\n"; if ($count == 1 || $count == 3) { $new_grid{$x,$y} = 1; $count_new++; } } } print "count new $count_new\n"; %grid = %new_grid; } exit 0; } { # compare path against Cellular::Automata::Wolfram values require Cellular::Automata::Wolfram; my $width = 50; my $x_offset = int($width/2)-1; my $num_of_gens = $x_offset - 1; RULE: foreach my $rule (0 .. 255) { my $path = Math::PlanePath::CellularRule->new(rule=>$rule); my $auto = Cellular::Automata::Wolfram->new (rule=>$rule, width=>$width, num_of_gens=>$num_of_gens); my $gens = $auto->{'gens'}; foreach my $y (0 .. $#$gens) { my $auto_str = $gens->[$y]; my $path_str = ''; foreach my $i (0 .. length($auto_str)-1) { my $x = $i - $x_offset; $path_str .= ($x < -$y || $x > $y ? substr($auto_str,$i,1) : $path->xy_is_visited($x,$y) ? '1' : '0'); } if ($auto_str ne $path_str) { print "$rule y=$y\n"; print "auto $auto_str\n"; print "path $path_str\n"; print "\n"; next RULE; } } } exit 0; } { my $rule = 124; my $path = Math::PlanePath::CellularRule->new(rule=>$rule); my @ys = (5..20); @ys = map{$_*2+1} @ys; my @ns = map{$path->xy_to_n(-$_,$_) }@ys; my @diffs = map {$ns[$_]-$ns[$_-1]} 1 .. $#ns; print "[",join(',',@diffs),"]\n"; my @dds = map {$diffs[$_]-$diffs[$_-1]} 1 .. $#diffs; print "[",join(',',@dds),"]\n"; exit 0; } { my $rule = 57; my $path = Math::PlanePath::CellularRule->new(rule=>$rule); my @ys = (5..20); @ys = map{$_*2+1} @ys; my @ns = map{$path->xy_to_n(-$_,$_) }@ys; my @diffs = map {$ns[$_]-$ns[$_-1]} 1 .. $#ns; print "[",join(',',@diffs),"]\n"; my @dds = map {$diffs[$_]-$diffs[$_-1]} 1 .. $#diffs; print "[",join(',',@dds),"]\n"; exit 0; } { my $rule = 57; my $path = Math::PlanePath::CellularRule->new(rule=>$rule); my @ys = (5..10); @ys = map{$_*2+1} @ys; print "[",join(',',@ys),"]\n"; print "[",join(',',map{$path->xy_to_n(-$_,$_) }@ys),"]\n"; exit 0; } Math-PlanePath-129/devel/triangle-spiral.pl0000644000175000017500000000544713641470115016453 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2015, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; # uncomment this to run the ### lines # use Smart::Comments; { # A023531 L-system my %to; %to = (S => 'STF+', # TF second form "TriangleSpiral2" in a023531.l T => 'TF', F => 'F', '+' => '+'); %to = (S => 'SFT+', # FT first form "TriangleSpiral" in a023531.l T => 'FT', F => 'F', '+' => '+'); my $str = 'S'; foreach (1 .. 7) { my $padded = $str; $padded =~ s/./$& /g; # spaces between symbols print "$padded\n"; $str =~ s{.}{$to{$&} // die}ge; } $str =~ s/F(?=[^+]*F)/F0/g; $str =~ s/F//g; $str =~ s/\+/1/g; $str =~ s/S|T//g; print $str,"\n"; require Math::NumSeq::OEIS; my $seq = Math::NumSeq::OEIS->new (anum => 'A023531'); my $want = ''; while (length($want) < length($str)) { my ($i,$value) = $seq->next; $want .= $value; } $str eq $want or die "oops"; print "end\n"; exit 0; } { # A010054 L-system my %to = (S => 'S+TF', T => 'TF', F => 'F', '+' => '+'); my $str = 'S+TF'; $str = 'S'; foreach (1 .. 7) { my $pad = $str; $pad =~ s/./$& /g; print "$pad\n"; $str =~ s{.}{$to{$&} // die}ge; } exit 0; } { # A010054 characteristic of triangular numbers # turn seq with Language::Logo require Math::NumSeq::OEIS; my $seq = Math::NumSeq::OEIS->new(anum=>'A010054'); # $seq->next; require Language::Logo; my $lo = Logo->new(update => 20, port=>8222); $lo->command("seth 0; forward 50; pendown; forward 200; backward 200; penup; backward 50; pendown"); for (;;) { logo_blob(5); my ($i,$value) = $seq->next; $lo->command("left ".($value*120)); if ($i > 0) { } $lo->command("forward 30"); } $lo->disconnect("Finished..."); exit 0; sub logo_blob { my ($size) = @_; my $half = $size/2; $lo->command(" penup; forward $half; pendown; left 90; forward $half; left 90; forward $size; left 90; forward $size; left 90; forward $size; left 90; forward $half; right 90; penup; backward $half; pendown "); } } Math-PlanePath-129/devel/peano.pl0000644000175000017500000006341213732004434014452 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'sum'; use Math::BaseCnv 'cnv'; use Math::PlanePath; use Math::PlanePath::PeanoCurve; use Math::PlanePath::PeanoDiagonals; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; # uncomment this to run the ### lines use Smart::Comments; { # PeanoDiagonals devel # N=15 33 # yx y=3 x->0 yrev=1 xrev=0 # N=125 1331 my $n = 7; my $radix = 3; my $path = Math::PlanePath::PeanoDiagonals->new (radix => $radix); my ($x,$y) = $path->n_to_xy($n); ### xy: "$x, $y" ### $n ### cnv: cnv($n,10,$radix) $x = 1; $y = 1; my @n_list = $path->xy_to_n_list($x,$y); ### @n_list exit 0; } { # PeanoDiagonals arrows for Tikz my $radix = 4; my $path = Math::PlanePath::PeanoDiagonals->new (radix => $radix); my $prev_x = 0; my $prev_y = 0; foreach my $n (0 .. $radix**4-1) { my ($x1,$y1) = $path->n_to_xy($n+.1); my ($x2,$y2) = $path->n_to_xy($n+.9); printf " \\fill (%.1f,%.1f) circle (.1); \\draw[my grey] (%.1f,%.1f) -- (%.1f,%.1f);\n", $x1,$y1, $prev_x,$prev_y, $x1,$y1; printf " \\draw[->] (%.1f,%.1f) -- (%.1f,%.1f);\n", $x1,$y1, $x2,$y2; ($prev_x,$prev_y) = ($x2,$y2); } exit 0; } { # PeanoDiagonals devel my $plain = Math::PlanePath::PeanoCurve->new (radix => 4); my $diag = Math::PlanePath::PeanoDiagonals->new (radix => 4); foreach my $n (0 .. 4**4) { my ($plain_x,$plain_y) = $plain->n_to_xy($n); my ($diag_x,$diag_y) = $diag->n_to_xy($n); printf "%6d %6d %d %d %3d %3d\n", $n, cnv($n,10,4), $diag_x-$plain_x, $diag_y-$plain_y, cnv($diag_x,10,4), cnv($diag_y,10,4); } exit 0; } # Uniform Grids # 4.1-O Wunderlich serpentine in diamond # bottom right between squares = Wunderlich Figure 3 # top left across diagonals = Mandelbrot page 62 # # 1.3-A Peano squares starting X direction { # PeanoDiagonals X axis # not in OEIS: 2,16,18,20,142,144,146,160,162,164,178,180,182,1276,1278 # half # not in OEIS: 1,8,9,10,71,72,73,80,81,82,89,90,91,638,639,640,647 # -----> <------ ------> # 3*9^k 6*9^k # base 9 digits 0,-2,2 # xx(n) = my(v=digits(n,3)); v=apply(d->if(d==0,-2,d==1,0,d==2,2), v); fromdigits(v,9); # vector(20,n,xx(n)) # Set(select(n->n>=0,vector(55,n,xx(n)))) == \ # [0,2,16,18,20,142,144,146,160,162,164,178,180,182,1276,1278] my $path = Math::PlanePath::PeanoDiagonals->new; foreach my $x (0 .. 81) { my $n = $path->xy_to_n($x,0) // next; my $n3 = cnv($n,10,3); my $n9 = cnv($n,10,9); print "n=$n $n3 $n9\n"; # print $n/2,","; } print "\n"; exit 0; } { # PeanoDiagonals other N my $path = Math::PlanePath::PeanoDiagonals->new; foreach my $n (1 .. 10) { my ($x,$y) = $path->n_to_xy($n); my @n_list = $path->xy_to_n_list($x,$y); @n_list <= 2 or die; my ($other) = grep {$_!=$n} @n_list; my $n3 = cnv($n,10,3); my $other3 = (defined $other ? cnv($other,10,3) : 'undef'); my $delta = (defined $other ? abs($other - $n) : undef); my $delta3 = (defined $delta ? cnv($delta,10,3) : 'undef'); my $by_func = PeanoDiagonals_other_n($n); my $by_func3 = (defined $by_func ? cnv($by_func,10,3) : 'undef'); $by_func //= 'undef'; my $diff = $other3 eq $by_func3 ? '' : ' ****'; print "n=$n $n3 other $other3 $by_func3$diff d=$delta3\n"; } print "\n"; exit 0; sub PeanoDiagonals_other_n { my ($n) = @_; ### PeanoDiagonals_other_n(): $n my @digits = digit_split_lowtohigh($n,3); my $c = 0; for (my $i = 0; $c>0 || $i <= $#digits; $i++) { $c += $digits[$i] || 0; my $d = $c % 3; ### at: "i=$i c=$c is d=$d" if ($d == 1) { $c += 4; $digits[$i] = _divrem_mutate($c,3); $c += $digits[++$i] || 0; $digits[$i] = _divrem_mutate($c,3); } elsif ($d == 2) { $c -= 4; $digits[$i] = _divrem_mutate($c,3); $c += $digits[++$i] || 0; $digits[$i] = _divrem_mutate($c,3); } else { $digits[$i] = _divrem_mutate($c,3); } } ### final: "c=$c digits ".join(',',@digits) if ($c < 0) { return undef; } $digits[scalar(@digits)] = $c; return digit_join_lowtohigh(\@digits,3); } } { my $path = Math::PlanePath::PeanoCurve->new; foreach my $x (0 .. 20) { print $path->xy_to_n($x,0),","; } print "\n"; foreach my $y (0 .. 20) { print $path->xy_to_n(0,$y),","; } print "\n"; exit 0; } { # Mephisto Waltz Picture require Image::Base::GD; my $size = 3**6; my $scale = 1; my $width = $size*$scale; my $height = $size*$scale; my $transform = sub { my ($x,$y) = @_; $x *= $scale; $y *= $scale; return ($x,$height-1-$y); }; my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); my $path = Math::PlanePath::PeanoCurve->new; my $image = Image::Base::GD->new (-height => $height, -width => $width); $image->rectangle(0,0, $width-1,$height-1, 'black'); require Math::NumSeq::MephistoWaltz; my $seq = Math::NumSeq::MephistoWaltz->new; foreach my $n (0 .. $size**2) { my ($x,$y) = $path->n_to_xy($n); my $value = $seq->ith($n); if ($value) { ($x,$y) = $transform->($x,$y); $image->rectangle($x,$y, $x+$scale-1, $y-($scale-1), 'white', 1); } } my $filename = '/tmp/mephisto-waltz.png'; $image->save($filename); require IPC::Run; IPC::Run::start(['xzgv',$filename],'&'); exit 0; } { # Cf segment substitution per Wunderlich alternating # 2---3 # | | # / / # *---1 5-4 8---* # / / # | | # 6---7 # turn(n) = my(m=n/9^valuation(n,9)); [1, -1,-1,-1, 1, 1, 1, -1][m%9]; # turn(n) = my(m=n/3^valuation(n,3)); (-1)^((m%3)+(n%3!=0)); # vector(27,n,turn(n)) # not A216430 only middle match # vector(100,n,turn(3*n)) # vector(20,n,turn(n)) # vector(20,n,(turn(n)+1)/2) # vector(20,n,(1-turn(n))/2) exit 0; } { # PeanoDiagonals Turns Morphism # turn(3*n)) == -turn(n) # turn(3*n+1)) == -(-1)^n # turn(3*n+2)) == (-1)^n # X = end of even # Y = end of odd my %expand = (X => 'X -FY +FX +FY +FX -FY -FX -FY +FX', Y => 'Y +FX -FY -FX -FY +FX +FY +FX -FY'); %expand = (X => 'Y +FX -FY', # applied an even number of times Y => 'X -FY +FX'); %expand = (X => 'X -FY +FX ++', Y => 'Y +FX -FY ++'); my $str = 'FX'; foreach (1 .. 8) { $str =~ s{[XY]}{$expand{$&}}eg; } print substr($str,0,60),"\n"; $str =~ s/[XY ]//g; $str =~ s/(\+\+)+$//; $str =~ s{[-+]+}{pm_str_net($&)}eg; $str =~ s/[^-+]//g; print substr($str,0,27),"\n"; my $path = Math::PlanePath::PeanoDiagonals->new; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my $max = 0; my $by_path = ''; for (1 .. length($str)) { my ($i,$value) = $seq->next; my $c = $value > 0 ? '+' : '-'; if ($i < 27) { print $c; } $by_path .= $c; } print "\n"; $str eq $by_path or die; exit 0; sub pm_str_net { my ($str) = @_; my $net = 0; foreach my $c (split //, $str) { if ($c eq '+') { $net++; } elsif ($c eq '-') { $net--; } else { die $c; } } $net %= 4; if ($net == 1) { return '+'; } if ($net == 3) { return '-'; } die "net $net"; } } { # turn LSR # plain: # signed 0,1,1,0,-1,-1,0,0,0,0,-1,-1,0,1,1,0,0,0,0,1,1,0,-1,-1,0,1,1,0,-1,-1, # signed 0,-1,-1,0,1,1,0,0,0,0,1,1,0,-1,-1,0,0,0,0,-1,-1,0,1,1,0,-1,-1,0,1,1, # ones 0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0,0,1,1,0,0,0, # zeros 1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,1,0,0,1,1,1,1,0,0,1,1,1, # diagturn(n) = my(v=digits(n,3)); sum(i=1,#v,v[i]!=1) my $radix = 4; my $path; $path = Math::PlanePath::PeanoDiagonals->new; $path = Math::PlanePath::PeanoCurve->new (radix => $radix); require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my $max = 0; for (1 .. 80) { my ($i,$value) = $seq->next; my $got = n_to_turn_LSR($i, $radix); $got = _UNDOCUMENTED__n_to_turn_LSR($path,$i); my $i3 = cnv($i,10,$radix); my $diff = $got==$value ? '' : ' ***'; printf "%2d %3s %d %d%s\n", $i,$i3, $value, $got, $diff; } print "signed "; $seq->rewind; for (1 .. 30) { my ($i,$value) = $seq->next; print $value,","; } print "\n"; print "signed "; $seq->rewind; for (1 .. 30) { my ($i,$value) = $seq->next; print -$value,","; } print "\n"; print "ones "; $seq->rewind; for (1 .. 30) { my ($i,$value) = $seq->next; print $value==1?1:0,","; } print "\n"; print "zeros "; $seq->rewind; for (1 .. 30) { my ($i,$value) = $seq->next; print $value==1?0:1,","; } print "\n"; exit 0; } { # Diagonals Pattern my $path = Math::PlanePath::PeanoDiagonals->new; $path->xy_to_n(0,0); $path->xy_to_n(2,0); # exit; my @slope; foreach my $n (0 .. 900) { my ($x,$y) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); my $dir = dxdy_to_dir8($x2-$x, $y2-$y); my $tx = $x+$x2; my $ty = $y+$y2; $slope[$tx]->[$ty] = $dir; if ($n < 10) { print "n=$n $x,$y to $x2,$y2 for $tx,$ty dir=$dir\n"; } } print "1,1 is $slope[1]->[1]\n"; foreach my $y (reverse 0 .. 27) { printf "y=%2d ", $y; # my $y = 2*$y+1; foreach my $x (0 .. 27) { # my $x = 2*$x+1; my $dir = $slope[$x]->[$y] // ''; printf '%3s', $dir; } print "\n"; } print " "; foreach my $x (0 .. 27) { printf '%3s', $x; } print "\n"; exit 0; # return 0..7 sub dxdy_to_dir8 { my ($dx, $dy) = @_; return atan2($dy,$dx) / atan2(1,1); if ($dx == 1) { if ($dy == 1) { return 1; } if ($dy == 0) { return 0; } if ($dy == -1) { return 7; } } if ($dx == 0) { if ($dy == 1) { return 2; } if ($dy == -1) { return 6; } } if ($dx == -1) { if ($dy == 1) { return 3; } if ($dy == 0) { return 4; } if ($dy == -1) { return 5; } } die 'oops'; } } # 8 60--61--62--63--64--65 78--79--80--... # | | | # 7 59--58--57 68--67--66 77--76--75 # | | | # 6 -1 54--55--56 69--70--71--72--73--74 # | # 5 -1 53--52--51 38--37--36--35--34--33 # | | | # 4 48--49--50 39--40--41 30--31--32 # | | | # 3 47--46--45--44--43--42 29--28--27 +1 # | # 2 6---7---8---9--10--11 24--25--26 +1 # | | | # 1 5---4---3 14--13--12 23--22--21 # | | | # Y=0 0---1---2 15--16--17--18--19--20 # 0 0 # +1 is low 0s to none # 1000 1001 # # 0 1 2 0 1 2 0 1 2 0 1 2 0 # \-/ \-/ \-/ \-/ # # GP-DEFINE A163536(n) = { # GP-DEFINE if(n%3==2,n++); # GP-DEFINE if(valuation(n,3)%2, 2-(n%2), 0); # GP-DEFINE } # my(v=OEIS_samples("A163536")); vector(#v,n, A163536(n)) == v # OEIS_samples("A163536") # vector(20,n, ceil(2*n/3)) # vector(20,n, valuation(n,3)%2) # GP-DEFINE A163536_b(n) = { # GP-DEFINE if(n%3==1,return(0)); # GP-DEFINE my(m=ceil(2*(n+1)/3)); # GP-DEFINE if(valuation(m\2,3)%2,0,2-(m\2)%2); # GP-DEFINE } # my(v=OEIS_samples("A163536")); vector(#v,n, A163536_b(n)) == v # vector(20,n, my(n=3*n-1, a=A163536(n)); if(a,-(-1)^a,0)) # vector(20,n, if(valuation(n,3)%2,0,-(-1)^n)) # for(n=1,27,my(n=n);print(n" "ceil(2*n/3)" "A163536(n)" "A163536_b(n))) # vector(20,n, A163536(n)) # vector(20,n, A163536(9*n)) # vector(20,n, A163536(81*n)) # # GP-DEFINE A163536_c(n) = { # GP-DEFINE if(n%3==1,return(0), # GP-DEFINE n%3==2,n++); # GP-DEFINE if(valuation(n,3)%2, 2-(n%2), 0); # GP-DEFINE } # my(v=OEIS_samples("A163536")); vector(#v,n, A163536_c(n)) == v # vector(20,n, A163536(n)) # # 5 4 2 10 # 8 6 0 10 # 11 8 2 10 # 14 10 1 10 # 17 12 0 10 # 20 14 1 10 # 23 16 2 10 # 26 18 1 10 # 29 20 2 10 # 32 22 1 10 # 35 24 0 10 # 38 26 1 10 # 41 28 2 10 # 44 30 0 10 # 47 32 2 10 # 50 34 1 10 # 53 36 2 10 # 56 38 1 10 # 59 40 2 10 # 62 42 0 10 # 65 44 2 10 # 68 46 1 10 # 71 48 0 10 # 74 50 1 10 # 77 52 2 10 # 80 54 0 10 # 83 56 2 10 # In odd bases, the parity of sum(@digits) is the parity of $n itself, # so no need for a full digit split (only examine the low end for low 0s). # sub _UNDOCUMENTED__n_to_turn_LSR { my ($self, $n) = @_; if ($n <= 0) { return undef; } my $radix = $self->{'radix'}; { my $r = $n % $radix; if ($r == $radix-1) { $n++; # ...222 and ...000 are same turns } elsif ($r != 0) { return 0; # straight ahead across rows, turn only at ends } } my $z = 1; until ($n % $radix) { # low 0s $z = !$z; $n /= $radix; } if ($z) { return 0; } # even number of low zeros return (($radix & 1 ? sum(digit_split_lowtohigh($n,$radix)) : $n) & 1 ? 1 : -1); } sub n_to_turn_LSR { my ($n,$radix) = @_; # { # if ($n % $radix != 0 # && $n % $radix != $radix-1) { # return 0; # } # # vector(20,n, ceil(2*n/3)) # # vector(20,n, floor((2*n+2)/3)) # $n = int((2*$n+2)/$radix); # } { if ($n % $radix == $radix-1) { $n++; } elsif ($n % $radix != 0) { return 0; } my @digits = digit_split_lowtohigh($n,$radix); my $turn = 1; while (@digits) { # low to high last if $digits[0]; $turn = -$turn; shift @digits; } if ($turn == 1) { return 0; } # even number of low zeros return (sum(@digits) & 1 ? -$turn : $turn); } { if ($n % $radix == $radix-1) { $n++; } elsif ($n % $radix != 0) { return 0; } my $low = 0; my $z = $n; while ($z % $radix == 0) { $low = 1-$low; $z /= $radix; } if ($low == 0) { return 0; # even num low 0s } return ($z % 2 ? 1 : -1); } { if ($n % $radix == $radix-1) { $n++; } while ($n % $radix**2 == 0) { $n /= $radix**2; } if ($n % $radix != 0) { return 0; } return diagonal_n_to_turn_LSR($n,$radix); } { my $turn = 1; my $turn2 = 1; my $m = $n; while ($m % $radix == $radix-1) { # odd low 2s is -1 $turn2 = -$turn2; $m = int($m/$radix); } my $z = $n; while ($z % $radix == 0) { # odd low 0s is -1 $turn = -$turn; $z /= $radix; } my $o = $n; if ($turn==$turn2) { return 0; } # return ($n % 2 ? 1 : -1); # my $opos = 0; # until ($o % 3 == 1) { # odd low 0s is -1 # $opos = 1-$opos; # $o = int($o/3); # } # if ($o==0) { return 0; } if ($n % 2) { # flip one or other $turn = -$turn; } else { $turn2 = -$turn2; } return ($turn+$turn2)/2; } { return (diagonal_n_to_turn_LSR($n,$radix) + diagonal_n_to_turn_LSR($n+1,$radix))/2; } } { # X=Y diagonal my $path = Math::PlanePath::PeanoCurve->new; foreach my $i (0 .. 20) { my $n = $path->xy_to_n($i,$i); printf "i=%3d %4s n=%3s %6s\n", $i,cnv($i,10,3), $n,cnv($n,10,3); } exit 0; } { # dx,dy on even radix require Math::BigInt; foreach my $radix (4, 2, 6, 8) { print "radix=$radix\n"; my $path = Math::PlanePath::PeanoCurve->new (radix => $radix); my $limit = 4000000000; { my %seen_dx; for my $len (0 .. 8) { for my $high (1 .. $radix-1) { my $n = Math::BigInt->new($high); foreach (1 .. $len) { $n *= $radix; $n += $radix-1; } my ($dx,$dy) = $path->n_to_dxdy($n); $dx = abs($dx); my ($x,$y) = $path->n_to_xy($n); my $xr = cnv($x,10,$radix); my $dr = cnv($dx,10,$radix); my $nr = cnv($n,10,$radix); print "N=$n [$nr] dx=$dx [$dr] x=[$xr]\n"; unless ($seen_dx{$dx}++) { } } } } { my %seen_dy; for my $len (0 .. 8) { for my $high (1 .. $radix-1) { my $n = Math::BigInt->new($high); foreach (1 .. $len) { $n *= $radix; $n += $radix-1; } my ($dx,$dy) = $path->n_to_dxdy($n); $dy = abs($dy); unless ($seen_dy{$dy}++) { my $dr = cnv($dy,10,$radix); my $nr = cnv($n,10,$radix); print "N=$n [$nr] dy=$dy [$dr]\n"; } } } } print "\n"; } exit 0; } { # abs(dY) = count low 2-digits, mod 2 # abs(dX) = opposite, 1-abs(dY) # x x # vertical when odd number of low 2s ..0222 # N+1 carry propagates to change ..1000 # y y # high y+1 complements x from 0->2 so X unchanged # Y becomes Y+1 02 -> 10, or if complement then Y-1 20 -> 12 # my $radix = 3; require Math::PlanePath::PeanoCurve; require Math::NumSeq::PlanePathDelta; require Math::NumSeq::DigitCountLow; require Math::BigInt; my $path = Math::PlanePath::PeanoCurve->new (radix => $radix); my $seq = Math::NumSeq::PlanePathDelta->new (planepath_object => $path, delta_type => 'AbsdX'); my $cnt = Math::NumSeq::DigitCountLow->new (radix => 3, digit => 2); foreach my $n (0 .. 40) { my ($dx,$dy) = $path->n_to_dxdy($n); my $absdx = abs($dx); my $absdy = abs($dy); my $c = $cnt->ith($n); my $by_c = $c & 1; my $diff = $absdy == $by_c ? '' : ' ***'; # my $n = $n+1; my $nr = cnv($n,10,$radix); printf "%3d %7s %2d,%2d low=%d%s\n", $n, $nr, abs($dx),abs($dy), $c, $diff; # print "$n,"; if ($absdx != 0) { } } exit 0; } { # Dir4 maximum my $radix = 6; require Math::PlanePath::PeanoCurve; require Math::NumSeq::PlanePathDelta; require Math::BigInt; my $path = Math::PlanePath::PeanoCurve->new (radix => $radix); my $seq = Math::NumSeq::PlanePathDelta->new (planepath_object => $path, delta_type => 'Dir4'); my $dir4_max = 0; foreach my $n (0 .. 600000) { # my $n = Math::BigInt->new(2)**$level - 1; my $dir4 = $seq->ith($n); if ($dir4 > $dir4_max) { $dir4_max = $dir4; my ($dx,$dy) = $path->n_to_dxdy($n); my $nr = cnv($n,10,$radix); printf "%7s %2b,\n %2b %8.6f\n", $nr, abs($dx),abs($dy), $dir4; } } exit 0; } { # axis increasing my $radix = 4; my $rsquared = $radix * $radix; my $re = '.' x $radix; require Math::NumSeq::PlanePathN; foreach my $line_type ('Y_axis', 'X_axis', 'Diagonal') { OUTER: foreach my $serpentine_num (0 .. 2**$rsquared-1) { my $serpentine_type = sprintf "%0*b", $rsquared, $serpentine_num; # $serpentine_type = reverse $serpentine_type; $serpentine_type =~ s/($re)/$1_/go; ### $serpentine_type my $seq = Math::NumSeq::PlanePathN->new ( planepath => "WunderlichSerpentine,radix=$radix,serpentine_type=$serpentine_type", line_type => $line_type, ); ### $seq # my $path = Math::NumSeq::PlanePathN->new # ( # e,radix=$radix,serpentine_type=$serpentine_type", # line_type => $line_type, # ); my $prev = -1; for (1 .. 1000) { my ($i, $value) = $seq->next; if ($value <= $prev) { # print "$line_type $serpentine_type decrease at i=$i value=$value cf prev=$prev\n"; # my $path = $seq->{'planepath_object'}; # my ($prev_x,$prev_y) = $path->n_to_xy($prev); # my ($x,$y) = $path->n_to_xy($value); # # print " N=$prev $prev_x,$prev_y N=$value $x,$y\n"; next OUTER; } $prev = $value; } print "$line_type $serpentine_type all increasing\n"; } } exit 0; } { # max Dir4 my $radix = 4; print 4-atan2(2,1)/atan2(1,1)/2,"\n"; require Math::NumSeq::PlanePathDelta; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => "PeanoCurve,radix=$radix", delta_type => 'Dir4'); my $dx_seq = Math::NumSeq::PlanePathDelta->new (planepath => "PeanoCurve,radix=$radix", delta_type => 'dX'); my $dy_seq = Math::NumSeq::PlanePathDelta->new (planepath => "PeanoCurve,radix=$radix", delta_type => 'dY'); my $max = 0; for (1 .. 10000000) { my ($i, $value) = $seq->next; # foreach my $k (1 .. 1000000) { # my $i = $radix ** (4*$k+3) - 1; # my $value = $seq->ith($i); if ($value > $max # || $i == 0b100011111 ) { my $dx = $dx_seq->ith($i); my $dy = $dy_seq->ith($i); my $ri = cnv($i,10,$radix); my $rdx = cnv($dx,10,$radix); my $rdy = cnv($dy,10,$radix); my $f = $dy ? $dx/$dy : -1; printf "%d %s %.5f %s %s %.3f\n", $i, $ri, $value, $rdx,$rdy, $f; $max = $value; } } exit 0; } __END__ #------------------------------------------------------------------------------ # xy_to_n() using pair of arrays for more symmetry ... # my @digits = digit_split_lowtohigh($n,$radix); # ### @digits # ### range: (scalar(@digits) | 1) # my @arrays = ([],[]); # my @rev = (0,0); # foreach my $i (reverse 0 .. $#digits) { # high to low # my $digit = $digits[$i]; # $rev[1-($i&1)] ^= $digit & 1; # $arrays[$i&1]->[$i>>1] = ($rev[$i&1] ? $radix_minus_1 - $digit : $digit); # } # ### final ... # ### @arrays # ### rev : join(' ',@rev) # # foreach my $i (0,1) { # # $arrays[$i]->[0] += $rev[$i]; # # } # my $zero = $n*0; # return map { digit_join_lowtohigh($arrays[$_], $radix, $zero) # + ($rev[$_] ? 1-$frac : $frac) } 0,1; #------------------------------------------------------------------------------ # n_to_xy() other ways: # my $radix = $self->{'radix'}; # my @ndigits = digit_split_lowtohigh($n,$radix); # # # high to low style # # # my $radix_minus_1 = $radix - 1; # my $xk = 0; # my $yk = 0; # my @ydigits; # my @xdigits; # # if (scalar(@ndigits) & 1) { # push @ndigits, 0; # so even number of entries # } # ### @ndigits # # for (my $i = $#ndigits >> 1; @ndigits; $i--) { # high to low # ### $i # { # my $ndigit = pop @ndigits; # high to low # $xk ^= $ndigit; # $ydigits[$i] = ($yk & 1 ? $radix_minus_1-$ndigit : $ndigit); # } # { # my $ndigit = pop @ndigits; # $yk ^= $ndigit; # $xdigits[$i] = ($xk & 1 ? $radix_minus_1-$ndigit : $ndigit); # } # } # # ### @xdigits # ### @ydigits # my $zero = ($n * 0); # inherit bignum 0 # return (digit_join_lowtohigh(\@xdigits, $radix, $zero), # digit_join_lowtohigh(\@ydigits, $radix, $zero)); # low to high style # # my $x = my $y = ($n * 0); # inherit bignum 0 # my $power = 1 + $x; # inherit bignum 1 # # while (@ndigits) { # N digits low to high # ### $power # { # my $ndigit = shift @ndigits; # low to high # if ($ndigit & 1) { # $y = $power-1 - $y; # 99..99 - Y # } # $x += $power * $ndigit; # } # @ndigits || last; # { # my $ndigit = shift @ndigits; # low to high # $y += $power * $ndigit; # $power *= $radix; # # if ($ndigit & 1) { # $x = $power-1 - $x; # } # } # } # return ($x, $y); #------------------------------------------------------------------------------ # Past docs before PeanoDiagonals # The is equivalent to the square form by drawing diagonal lines alternately # in the direction of the leading diagonal or opposite diagonal, per the ".." # marked lines in the following. # # +--------+--------+--------+ +--------+--------+--------+ # | .. | .. | .. | | | | | # |6 .. |7 .. |8 .. | | 6--------7--------8 | # | .. | .. | .. | | | | | | # +--------+--------+--------+ +----|---+--------+--------+ # | .. | .. | .. | | | | | | # | .. 5| .. 4| .. 3| | 5--------4--------3 | # | .. | .. | .. | | | | | | # +--------+--------+--------+ +--------+--------+----|---+ # | .. | .. | .. | | | | | | # |0 .. |1 .. |2 .. | | 0--------1--------2 | # | .. | .. | .. | | | | | # +--------+--------+--------+ +--------+--------+--------+ # # X==Y mod 2 "even" points leading-diagonal "/" # X!=Y mod 2 "odd" points opposite-diagonal "\" # # -----7 / # / \ / # 6 -----8 # | # | 4----- # \ / \ # 5----- 3 # | # -----1 | # / \ / # 0 -----2 #------------------------------------------------------------------------------ Math-PlanePath-129/devel/theodorus.gnuplot0000644000175000017500000000210111463457477016447 0ustar gggg# Copyright 2010 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . set terminal xterm # set xlabel "Days ago (0 today, 1 yesterday, etc)" 0, -2 # # something evil happens with "xtics axis", need dummy xlabel # set xlabel " " 0, -2 # set xrange [-0.5:39.5] # set xtics axis 5 # set mxtics 5 # set ylabel "Weight (percent)" # #set yrange [-5:55] #set format y "%.1f" #unset key #set style fill solid 1.0 #set boxwidth 0.6 relative plot "/tmp/theodorus.data" Math-PlanePath-129/devel/aztec-diamond.pl0000644000175000017500000000732112624206362016067 0ustar gggg#!/usr/bin/perl -w # Copyright 2015 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use POSIX (); use Math::Trig 'pi'; use Math::PlanePath::SierpinskiCurve; # uncomment this to run the ### lines # use Smart::Comments; { # drawing turn sequence Language::Logo require Language::Logo; require Math::NumSeq::OEIS; # A003982=0,1 characteristic of A001844=2n(n+1)+1 # constant A190406 # my $seq = Math::NumSeq::OEIS->new (anum => 'A003982'); # each leg 4 longer # 1, 0, 0, 0, # 1, 0, 0, 0, 0, 0, 0, 0, # 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 1, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, require Math::NumSeq::Squares; my $square = Math::NumSeq::Squares->new; my @value = (1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ); # A010052 charact of squares # 1, # 1, 0, 0, # 1, 0, 0, 0, 0, # 1, 0, 0, 0, 0, 0, 0, # 1, 0, 0, 0, 0, 0, 0, 0, 0, # 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # A047838 @value = (1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ); for (my $i = 0; $i <= $#value; $i++) { if ($value[$i]) { print $i+1,","; } } print "\n"; # exit 0; my $lo = Logo->new(update => 20, port=>8222+time()%100); $lo->command("pendown"); $lo->command("seth 0"); foreach my $n (1 .. 2560) { # my ($i, $value) = $seq->next or last; # 2n(n+1)+1 # my $i = $n+1; # my $value = $square->pred(2*$n+1); # my $i = $n+1; # my $value = $value[$i-1] // last; # i = floor(n^2/2)-1. # i+1 = floor(n^2/2) # 2i+2 = n^2 my $i = $n+1; my $value = A080827_pred($i); $lo->command("forward 10"); if (! $value) { if ($i & 1) { $lo->command("left 90"); } else { $lo->command("right 90"); } } } $lo->disconnect("Finished..."); exit 0; my $init; my %values; sub A080827_pred { my ($value) = @_; unless ($init) { require Math::NumSeq::OEIS; # # cf A047838 or A080827 giving Aztec diamond spiral # my $seq = Math::NumSeq::OEIS->new (anum => 'A080827'); # my $seq = Math::NumSeq::OEIS->new (anum => 'A047838'); while (my($i,$value) = $seq->next) { $values{$value} = 1; } $init = 1; } return $values{$value}; # return $seq->pred($value); } } Math-PlanePath-129/devel/imaginary-base.pl0000644000175000017500000001475012003102512016223 0ustar gggg#!/usr/bin/perl -w # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min', 'max'; use Math::PlanePath::Base::Generic 'is_infinite'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use Math::PlanePath::ImaginaryBase; # uncomment this to run the ### lines use Smart::Comments; { # nega min/max level my $radix = 3; # for (my $x2 = 0; $x2 < 100; $x2++) { # _negaradix_range_digits($radix, $x2,$x2); # } # for (my $x1 = -1; $x1 > -100; $x1--) { # _negaradix_range_digits($radix, $x1,$x1); # } for (my $x1 = 0; $x1 > -100; $x1--) { for (my $x2 = 0; $x2 < 1; $x2++) { my ($len, $level, $base) = Math::PlanePath::ImaginaryBase::_negaradix_range_level($x1,$x2, $radix); my $want_xmin = _level_to_xmin($level,$radix); my $want_xmax = _level_to_xmax($level,$radix); $len == $radix ** $level or die; print "$x1 $want_xmin len=$len, level=$level, base=$base\n"; # print "$x2 $want_xmax len=$len, level=$level, base=$base\n"; unless ($base <= $x1 && $base+$len > $x2) { print "$x1..$x2 got len=$len, level=$level, base=$base not cover\n"; } } } exit 0; } { my $radix = 4; foreach my $level (0 .. 10) { my $xmin = _level_to_xmin($level,$radix); my $xmax = _level_to_xmax($level,$radix); print "$level $xmin $xmax\n"; } # Xmin = r*(r-1) + r^2 + r^4 + r^6 # Xmax = r-1 + r^2 + r^4 + r^6 # Xmax = 1 + (4^(k+1) - 1) / (4-1) # k+1 = round down pow (X-1)*(R2-1) + 1 # 0,1,5,21 sub _level_to_xmax { my ($level, $radix) = @_; my $max = 0; for (my $i = 1; $i < $level; $i += 2) { $max += ($radix-1) * $radix ** ($i-1) } return $max; my $rsquared = $radix*$radix; # r2 = radix**2 return ($radix**(2*$level) - 1) / ($rsquared-1); return ($radix * $rsquared**$level + 1) / ($rsquared-1); } # -Xmin = 1 + R*(R2^(k+1) - 1) / (R2-1) # R2^(k+2) = (X-1)*(R2-1)*R + R2 # 0,-2,-10,-42 sub _level_to_xmin { my ($level, $radix) = @_; my $min = 0; for (my $i = 1; $i < $level; $i += 2) { $min -= ($radix-1) * $radix ** $i; } return $min; my $rsquared = $radix*$radix; # rsquared = radix**2 return 1 - ($radix**(2*$level+1) + 1) / ($rsquared-1); return 1 - ($radix * $rsquared**$level + 1) / ($rsquared-1); } exit 0; } { # nega min/max # my $radix = 4; # for (my $x2 = 0; $x2 < 100; $x2++) { # _negaradix_range_digits($radix, $x2,$x2); # } # for (my $x1 = -1; $x1 > -100; $x1--) { # _negaradix_range_digits($radix, $x1,$x1); # } for (my $x1 = 0; $x1 > -1; $x1--) { foreach my $x2 (0 .. 1) { my ($min_digits, $max_digits) = Math::PlanePath::ImaginaryBase::_negaradix_range_digits_lowtohigh($x1,$x2, $radix); my $min = digit_join_lowtohigh ($min_digits, $radix); my $max = digit_join_lowtohigh ($max_digits, $radix); my ($want_min, $want_max) = negaradix_index_range($x1,$x2, $radix); if ($min != $want_min || $max != $want_max) { print "$x1..$x2 got $min,$max want $want_min,$want_max\n"; print " min_digits ",join(',',@$min_digits),"\n"; print " max_digits ",join(',',@$max_digits),"\n"; } } } exit 0; } { # nega conversions my $radix = 2; my %seen; foreach my $n (0 .. 1024) { my $nega = index_to_negaradix($n,$radix); if ($seen{$nega}++) { print "duplicate nega=$nega\n"; } my $rev_n = negaradix_to_index($nega,$radix); if ($rev_n != $n) { print "rev_n=$rev_n want n=$n\n"; } print "$n $nega $rev_n\n"; } sub index_to_negaradix { my ($n, $radix) = @_; my $power = 1; my $ret = 0; while ($n) { my $digit = $n % $radix; # low to high $n = int($n/$radix); $ret += $power * $digit; $power *= -$radix; } return $ret; } sub negaradix_to_index { my ($n, $radix) = @_; my $power = 1; my $ret = 0; while ($n) { my $digit = $n % $radix; # low to high $ret += $power * $digit; $n = - int(($n-$digit)/$radix); $power *= $radix; } return $ret; } sub negaradix_index_range { my ($nega1, $nega2, $radix) = @_; my @indices = map {negaradix_to_index($_,$radix)} $nega1 .. $nega2; return (min(@indices), max(@indices)); } exit 0; } { # "**" operator for (my $n = 1; $n < 0xFFFFFFFF; $n = 2*$n+1) { my $cube = $n ** 3; my $mod = $cube % $n; print "$cube $mod\n"; } exit 0; } { # max Dir4 require Math::BaseCnv; print 4-atan2(2,1)/atan2(1,1)/2,"\n"; require Math::NumSeq::PlanePathDelta; my $radix = 8; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => "ImaginaryBase,radix=$radix", delta_type => 'Dir4'); my $dx_seq = Math::NumSeq::PlanePathDelta->new (planepath => "ImaginaryBase,radix=$radix", delta_type => 'dX'); my $dy_seq = Math::NumSeq::PlanePathDelta->new (planepath => "ImaginaryBase,radix=$radix", delta_type => 'dY'); my $max = 0; # for (1 .. 1000000) { # my ($i, $value) = $seq->next; foreach my $k (1 .. 1000000) { my $i = $radix ** (4*$k+3) - 1; my $value = $seq->ith($i); if ($value > $max) { my $dx = $dx_seq->ith($i); my $dy = $dy_seq->ith($i); my $ri = Math::BaseCnv::cnv($i,10,$radix); my $rdx = Math::BaseCnv::cnv($dx,10,$radix); my $rdy = Math::BaseCnv::cnv($dy,10,$radix); my $f = $dx/$dy; printf "%d %s %.5f %s %s %.3f\n", $i, $ri, $value, $rdx,$rdy, $f; $max = $value; } } exit 0; } # $aref->[0] high digit sub digit_join_hightolow { my ($aref, $radix, $zero) = @_; my $n = (defined $zero ? $zero : 0); foreach my $digit (@$aref) { $n *= $radix; $n += $digit; } return $n; } Math-PlanePath-129/devel/digit-groups.pl0000644000175000017500000001111212000734245015750 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh'; # uncomment this to run the ### lines use Smart::Comments; { # 2^64-1 base7 45012021522523134134601 # decimal less first digit 2635249153387078802 # 2635249153387078656 require Devel::Peek; my $n = ~0; my $radix = 7; my $digit = $n % $radix; ### $digit $n /= $radix; Devel::Peek::Dump($n); $n = int($n); Devel::Peek::Dump($n); require Math::PlanePath; my @digits = digit_split_lowtohigh(~0,$radix); ### @digits exit 0; } { # Diagonal require Math::BaseCnv; require Math::NumSeq::PlanePathN; my $seq = Math::NumSeq::PlanePathN->new (planepath=> 'DigitGroups', line_type => 'Diagonal'); foreach my $i (0 .. 150) { my ($i,$value) = $seq->next; my $v2 = Math::BaseCnv::cnv($value,10,2); printf "%4d %20s\n", $value, $v2; } print "\n"; exit 0; } { require Math::BaseCnv; require Math::PlanePath::DigitGroups; foreach my $radix (2 .. 7) { print "radix $radix\n"; my $path = Math::PlanePath::DigitGroups->new (radix => $radix); foreach my $coord_max (0 .. 25) { my $n_max = $path->xy_to_n(0,0); my $x_max = 0; my $y_max = 0; foreach my $x (0 .. $coord_max) { foreach my $y (0 .. $coord_max) { my $n = $path->xy_to_n($x,$y); ### got: "$x,$y $n" if ($n > $n_max) { $x_max = $x; $y_max = $y; $n_max = $n; } } } my $n_max_base = Math::BaseCnv::cnv($n_max,10,$radix); my ($n_lo, $n_hi) = $path->rect_to_n_range(0,0,$coord_max,$coord_max); my $n_hi_base = Math::BaseCnv::cnv($n_hi,10,$radix); print " $coord_max $x_max,$y_max n=$n_max [$n_max_base] cf nhi=$n_hi [$n_hi_base]\n"; } print "\n"; } exit 0; } { require Math::BaseCnv; require Math::PlanePath::DigitGroups; foreach my $radix (2 .. 7) { print "radix $radix\n"; my $path = Math::PlanePath::DigitGroups->new (radix => $radix); foreach my $exp (1 .. 5) { my $coord_min = $radix ** ($exp-1); my $coord_max = $radix ** $exp - 1; print " $coord_min $coord_max\n"; my $x_min = $coord_min; my $y_min = $coord_min; my $n_min = $path->xy_to_n($x_min,$y_min); foreach my $x ($coord_min .. $coord_max) { foreach my $y ($coord_min .. $coord_max) { my $n = $path->xy_to_n($x,$y); ### got: "$x,$y $n" if ($n < $n_min) { $x_min = $x; $y_min = $y; $n_min = $n; } } } my $n_min_base = Math::BaseCnv::cnv($n_min,10,$radix); my ($n_lo, $n_hi) = $path->rect_to_n_range(0,0,$coord_max,$coord_max); my $n_lo_base = Math::BaseCnv::cnv($n_lo,10,$radix); print " $exp $x_min,$y_min n=$n_min [$n_min_base] cf nlo=$n_lo [$n_lo_base]\n"; } print "\n"; } exit 0; } { require Math::BaseCnv; require Math::PlanePath::DigitGroups; foreach my $radix (2 .. 7) { print "radix $radix\n"; my $path = Math::PlanePath::DigitGroups->new (radix => $radix); foreach my $exp (1 .. 5) { my $coord_max = $radix ** $exp - 1; my $n_max = $path->xy_to_n(0,0); my $x_max = 0; my $y_max = 0; foreach my $x (0 .. $coord_max) { foreach my $y (0 .. $coord_max) { my $n = $path->xy_to_n($x,$y); ### got: "$x,$y $n" if ($n > $n_max) { $x_max = $x; $y_max = $y; $n_max = $n; } } } my $n_max_base = Math::BaseCnv::cnv($n_max,10,$radix); my ($n_lo, $n_hi) = $path->rect_to_n_range(0,0,$coord_max,$coord_max); my $n_hi_base = Math::BaseCnv::cnv($n_hi,10,$radix); print " $exp $x_max,$y_max n=$n_max [$n_max_base] cf nhi=$n_hi [$n_hi_base]\n"; } print "\n"; } exit 0; } Math-PlanePath-129/devel/imaginary-half.pl0000644000175000017500000000546512003406621016236 0ustar gggg#!/usr/bin/perl -w # Copyright 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min', 'max'; # uncomment this to run the ### lines #use Smart::Comments; { # Dir4 maximum my $radix = 3; require Math::PlanePath::ImaginaryHalf; require Math::NumSeq::PlanePathDelta; require Math::BigInt; require Math::BaseCnv; my $path = Math::PlanePath::ImaginaryHalf->new (radix => $radix); my $seq = Math::NumSeq::PlanePathDelta->new (planepath_object => $path, delta_type => 'Dir4'); my $dir4_max = 0; foreach my $n (0 .. 600000) { # my $n = Math::BigInt->new(2)**$level - 1; my $dir4 = $seq->ith($n); if ($dir4 > $dir4_max) { $dir4_max = $dir4; my ($dx,$dy) = $path->n_to_dxdy($n); my $nr = Math::BaseCnv::cnv($n,10,$radix); printf "%d %7s %2d,%2d %8.6f\n", $n,$nr, ($dx),($dy), $dir4; } } exit 0; } { # ProthNumbers require Math::BaseCnv; require Math::PlanePath::ImaginaryHalf; require Math::NumSeq::ProthNumbers; $|=1; my $radix = 2; my $path = Math::PlanePath::ImaginaryHalf->new (radix => $radix); my $seq = Math::NumSeq::ProthNumbers->new; my %seen; for (1 .. 200) { my (undef, $n) = $seq->next; my ($x, $y) = $path->n_to_xy($n); my $n2 = Math::BaseCnv::cnv($n,10,$radix); print "$x $y $n $n2\n"; $seen{$x} .= " ${y}[$n2]"; } foreach my $x (sort {$a<=>$b} keys %seen) { print "$x $seen{$x}\n"; } exit 0; } { # min/max extents require Math::BaseCnv; require Math::PlanePath::ImaginaryHalf; $|=1; my $radix = 3; my $path = Math::PlanePath::ImaginaryHalf->new (radix => $radix); my $xmin = 0; my $xmax = 0; my $ymax = 0; my $n = $path->n_start; for (my $level = 1; $level < 25; $level++) { my $n_next = $radix**$level; for ( ; $n < $n_next; $n++) { my ($x,$y) = $path->n_to_xy($n); $xmin = min ($xmin, $x); $xmax = max ($xmax, $x); $ymax = max ($ymax, $y); } printf "%2d %12s %12s %12s\n", $level, Math::BaseCnv::cnv($xmin,10,$radix), Math::BaseCnv::cnv($xmax,10,$radix), Math::BaseCnv::cnv($ymax,10,$radix); } exit 0; } Math-PlanePath-129/devel/alternate-paper-midpoint.pl0000644000175000017500000001521012023012405020233 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Usage: perl alternate-paper-midpoint.pl # # Print state tables used by Math::PlanePath::AlternatePaperMidpoint. # use 5.010; use strict; # uncomment this to run the ### lines #use Smart::Comments; sub print_table { my ($name, $aref) = @_; print "my \@$name = ("; my $entry_width = max (map {length($_//'')} @$aref); foreach my $i (0 .. $#$aref) { printf "%*s", $entry_width, $aref->[$i]//'undef'; if ($i == $#$aref) { print ");\n"; } else { print ","; if (($i % 16) == 15 || ($entry_width >= 3 && ($i % 4) == 3)) { print "\n ".(" " x length($name)); } elsif (($i % 4) == 3) { print " "; } } } } my @next_state; my @state_to_dxdy; sub make_state { my %param = @_; my $state = 0; $state <<= 1; $state |= delete $param{'nextturn'}; # high $state <<= 2; $state |= delete $param{'rot'}; $state <<= 1; $state |= delete $param{'prevbit'}; $state <<= 2; $state |= delete $param{'digit'}; # low if (%param) { die; } return $state; } sub state_string { my ($state) = @_; my $digit = $state & 3; $state >>= 2; my $prevbit = $state & 1; $state >>= 1; my $rot = $state & 3; $state >>= 2; my $nextturn = $state & 1; $state >>= 1; return "rot=$rot prevbit=$prevbit (digit=$digit)"; } foreach my $nextturn (0, 1) { foreach my $rot (0, 1, 2, 3) { foreach my $prevbit (0, 1) { my $state = make_state (nextturn => $nextturn, rot => $rot, prevbit => $prevbit, digit => 0); ### $state foreach my $digit (0 .. 3) { my $new_nextturn = $nextturn; my $new_prevbit = $digit; my $new_rot = $rot; if ($digit != $prevbit) { # count 0<->1 transitions $new_rot++; $new_rot &= 3; } # nextturn from bit above lowest 0 if ($digit == 0) { $new_nextturn = $prevbit ^ 1; } elsif ($digit == 1) { $new_nextturn = $prevbit; } elsif ($digit == 2) { $new_nextturn = 0; # 1-bit at odd position } my $dx = 1; my $dy = 0; if ($rot & 2) { $dx = -$dx; $dy = -$dy; } if ($rot & 1) { ($dx,$dy) = (-$dy,$dx); # rotate +90 } ### rot to: "$dx, $dy" my $next_dx = $dx; my $next_dy = $dy; if ($nextturn) { ($next_dx,$next_dy) = ($next_dy,-$next_dx); # right, rotate -90 } else { ($next_dx,$next_dy) = (-$next_dy,$next_dx); # left, rotate +90 } my $frac_dx = $next_dx - $dx; my $frac_dy = $next_dy - $dy; my $masked_state = $state & 0x1C; $state_to_dxdy[$masked_state] = $dx; $state_to_dxdy[$masked_state + 1] = $dy; $state_to_dxdy[$masked_state + 2] = $frac_dx; $state_to_dxdy[$masked_state + 3] = $frac_dy; my $next_state = make_state (nextturn => $new_nextturn, rot => $new_rot, prevbit => $new_prevbit, digit => 0); $next_state[$state+$digit] = $next_state; } } } } ### @next_state ### @state_to_dxdy ### next_state length: 4*(4*2*2 + 4*2) print "# next_state length ", scalar(@next_state), "\n"; print_table ("next_state", \@next_state); print_table ("state_to_dxdy", \@state_to_dxdy); print "\n"; { my @pending_state = (0, 4, 8, 12); # in 4 arm directions my $count = 0; my @seen_state; my $depth = 1; foreach my $state (@pending_state) { $seen_state[$state] = $depth; } while (@pending_state) { my @new_pending_state; foreach my $state (@pending_state) { $count++; ### consider state: $state foreach my $digit (0 .. 3) { my $next_state = $next_state[$state+$digit]; if (! $seen_state[$next_state]) { $seen_state[$next_state] = $depth; push @new_pending_state, $next_state; ### push: "$next_state depth $depth" } } $depth++; } @pending_state = @new_pending_state; } for (my $state = 0; $state < @next_state; $state += 2) { $seen_state[$state] ||= '-'; my $state_string = state_string($state); print "# used state $state depth $seen_state[$state] $state_string\n"; } print "used state count $count\n"; } exit 0; # # lowdigit # # my @state_to_dx = (1,1,0,0, # # -1,-1,0,1, # # -1,0,0,0, # # 1,0,0,1, # # ); # # my @state_to_dy = (0,0,1,1, # # 0,0,-1,0, # # 0,1,1,1, # # 0,-1,-1,0, # # ); # # my @state_to_dx = (1,1,0,0, # -1,-1,0,1, # -1,0,0,0, # 1,0,0,1, # ); # my @state_to_dy = (0,0,1,1, # 0,0,-1,0, # 0,1,1,1, # 0,-1,-1,0, # ); # # #use Smart::Comments; # # sub n_to_dxdy { # my ($self, $n) = @_; # ### AlternatePaperMidpoint n_to_dxdy(): $n # # if ($n < 0) { return; } # if (is_infinite($n)) { return ($n, $n); } # # my $arm = _divrem_mutate ($n, $self->{'arms'}); # ### $arm # ### $n # # my @digits = digit_split_lowtohigh($n,4); # while (@digits >= 2 && $digits[0] == 3) { # strip low 3s # shift @digits; # } # my $state = 0; # my $lowdigit = (shift @digits || 0); # foreach my $digit (reverse @digits) { # high to low # $state = $next_state[$state+$digit]; # } # ### $state # # ### $lowdigit # $state += $lowdigit; # my $dx = $state_to_dx[$state]; # my $dy = $state_to_dy[$state]; # # if ($arm & 1) { # ($dx,$dy) = ($dy,$dx); # transpose # } # if ($arm & 2) { # ($dx,$dy) = (-$dy,$dx); # rotate +90 # } # if ($arm & 4) { # $dx = - $dx; # rotate 180 # $dy = - $dy; # } # # # ### rotated return: "$dx,$dy" # return ($dx,$dy); # } # # # no Smart::Comments; Math-PlanePath-129/devel/sacks.pl0000644000175000017500000000204211617621117014447 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use warnings; # uncomment this to run the ### lines use Smart::Comments; { require Math::PlanePath::SacksSpiral; foreach my $i (0 .. 40) { my $n; $n = $i*$i + $i; $n = $i*$i; my ($x, $y) = Math::PlanePath::SacksSpiral->n_to_xy($n); printf "%d %d, %d\n", $i, $x, $y; } exit 0; } Math-PlanePath-129/devel/c-curve.pl0000644000175000017500000030354212767372166014736 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min','max','sum'; use Scalar::Util 'blessed'; use Math::BaseCnv; use List::Pairwise; use lib 'xt'; use Math::PlanePath::Base::Digits 'digit_join_lowtohigh'; use Math::BigInt try => 'GMP'; use Math::BigRat; use Math::Geometry::Planar (); use Module::Load; use POSIX 'ceil'; use MyOEIS; use Math::PlanePath::CCurve; my $path = Math::PlanePath::CCurve->new; *_divrem = \&Math::PlanePath::_divrem; our $seg_len; # uncomment this to run the ### lines # use Smart::Comments; # The total points along the horizontal, including the endpoints, is # # H[k] = a[k] + 1 + e[k] + 1 + a[k] # = 2*d[k-3] + 2*d[k-7] + 2 # The pairs of terms are the Jocobsthal sequence # # j[k+1] = j[k] + d[k-1] + j[k-2] + 2*j[k-3] { # right boundary N my $path = Math::PlanePath::CCurve->new; my %non_values; my %n_values; my @n_values; my @values; foreach my $k (8) { print "k=$k\n"; my $n_limit = 2**$k; foreach my $n (0 .. $n_limit-1) { $non_values{$n} = 1; } my $points = MyOEIS::path_boundary_points ($path, $n_limit, side => 'right'); for (my $i = 0; $i+1 <= $#$points; $i++) { my ($x,$y) = @{$points->[$i]}; my ($x2,$y2) = @{$points->[$i+1]}; # my @n_list = $path->xy_to_n_list($x,$y); my @n_list = path_xyxy_to_n($path, $x,$y, $x2,$y2); foreach my $n (@n_list) { delete $non_values{$n}; if ($n <= $n_limit) { $n_values{$n} = 1; } my $n2 = Math::BaseCnv::cnv($n,10,2); my $pred = 1; # $path->_UNDOCUMENTED__n_segment_is_right_boundary($n); my $diff = $pred ? '' : ' ***'; if ($k <= 8) { printf "%3d %*s%s\n", $n, $k, $n2, $diff; } } } @n_values = keys %n_values; @n_values = sort {$a<=>$b} @n_values; my @non_values = keys %non_values; @non_values = sort {$a<=>$b} @non_values; my $count = scalar(@n_values); print "count $count\n"; # push @values, $count; @values = @n_values; # if ($k <= 4) { # foreach my $n (@non_values) { # my $pred = $path->_UNDOCUMENTED__n_segment_is_right_boundary($n); # my $diff = $pred ? ' ***' : ''; # my $n2 = Math::BaseCnv::cnv($n,10,3); # print "non $n $n2$diff\n"; # } # } # @values = @non_values; # print "func "; # foreach my $i (0 .. $count-1) { # my $n = $path->_UNDOCUMENTED__right_boundary_i_to_n($i); # my $n2 = Math::BaseCnv::cnv($n,10,3); # print "$n2,"; # } # print "\n"; print "vals "; foreach my $i (0 .. $count-1) { my $n = $values[$i]; my $n2 = Math::BaseCnv::cnv($n,10,2); print "$n,"; } print "\n"; } # @values = MyOEIS::first_differences(@values); # shift @values; # shift @values; # shift @values; print join(',',@values),"\n"; Math::OEIS::Grep->search(array => \@values); exit 0; sub path_xyxy_to_n { my ($path, $x1,$y1, $x2,$y2) = @_; ### path_xyxy_to_n(): "$x1,$y1, $x2,$y2" my @n_list = $path->xy_to_n_list($x1,$y1); ### @n_list my $arms = $path->arms_count; foreach my $n (@n_list) { my ($x,$y) = $path->n_to_xy($n + $arms); if ($x == $x2 && $y == $y2) { return $n; } } return; } } { =head2 Two Curves Two curves can be placed back-to-back, as if starting from a line segment traversed in both directions. <--------- back-to-back ---------> lines For example to N=16, 11-----10-----9,7-----6------5 k=4 3 | | | 13-----12 8 4------3 2 | | 14 2 1 | | 15-----16 0------1 <- Y=0 1------0 16----15 <- Y=0 | | 2 14 -1 | | 3------4 8 12-----13 -2 | | | 5------6-----7,9----10-----11 -3 The boundary and area of this shape are Btwo[k] = / 2 if k=0 | 8*2^h - 8 if k even >= 2 \ 12*2^h - 8 if k odd = 2, 4, 8, 16, 24, 40, 56, 88, 120, 184, 248, 376, 504, 760, 1016, 1528, 2040, ... Atwo[k] = / 0 if k=0 | (7/2)*2^k - 7*2^h + 4 if k even >= 2 \ (7/2)*2^k - 10*2^h + 4 if k odd = 0, 1, 4, 12, 32, 76, 172, 372, 788, 1636, 3364, 6852, 13892, 28036, 56452, 113412, 227588 =for Test-Pari-DEFINE S(h) = 2^h =for Test-Pari-DEFINE Z(h) = 2*2^h-2 =for Test-Pari-DEFINE Btwo_samples = [ 2, 4, 8, 16, 24, 40, 56, 88, 120, 184, 248, 376, 504, 760, 1016, 1528, 2040 ] =for Test-Pari-DEFINE Btwo(k) = my(h=floor(k/2)); if(k==0, 2, if(k%2, 12*2^h-8, 8*2^h-8)) =for Test-Pari-DEFINE Btwo_from_SZ(k) = my(h=floor(k/2)); if(k==0, 2, if(k%2, 4*S(h)+4*Z(h), 4*S(h)+4*Z(h-1))) =for GP-Test vector(length(Btwo_samples), k, Btwo(k-1)) == Btwo_samples =for GP-Test vector(50, k, Btwo(k-1)) == vector(50, k, Btwo_from_SZ(k-1)) The straight and zigzag parts are the two middle sides of the right and convex hull shapes shown above. So the boundary Btwo[k] = 4*S[h] + 4*Z[h-1] for k even >= 2 = 4*(2^h) + 4*(2*2^(h-1) - 2) = 8*2^h - 8 Btwo[k] = 4*S[h] + 4*Z[h] for k odd = 4*(2^h) + 4*(2*2^h - 2) = 12*2^h - 8 The area can be calculated from the enclosing square S[h]+Z[h-1] from which subtract the four zigzag triangles at the corners. Atwo[k] = 4*(S[h]+Z[h-1])^2 + 4*Z[h-1]/2*(Z[h-1]/2 + 1)/2 for k even >= 2 Atwo[k] = 4*(S[h]+Z[h])^2 + 4*Z[h]/2 *(Z[h]/2 + 1)/2 for k odd =for Test-Pari-DEFINE Atwo_samples = [ 0, 1, 4, 12, 32, 76, 172, 372, 788, 1636, 3364, 6852, 13892, 28036, 56452, 113412, 227588 ] =for Test-Pari-DEFINE Atwo(k) = my(h=floor(k/2)); if(k==0, 0, if(k%2, (7/2)*2^k - 10*2^h + 4, (7/2)*2^k - 7*2^h + 4)) =for Test-Pari-DEFINE Atwo_from_SZ_even(h) = (S(h)+Z(h-1))^2 - 4*Z(h-1)/2*(Z(h-1)/2 + 1)/2 =for Test-Pari-DEFINE Atwo_from_SZ_odd(h) = (S(h)+Z(h))^2 - 4*Z(h)/2*(Z(h)/2 + 1)/2 =for Test-Pari-DEFINE Atwo_from_SZ(k) = my(h=floor(k/2)); if(k==0, 0, if(k%2, Atwo_from_SZ_odd(h), Atwo_from_SZ_even(h))) =for GP-Test vector(length(Atwo_samples), k, Atwo(k-1)) == Atwo_samples =for GP-Test vector(50, k, Atwo(k-1)) == vector(50, k, Atwo_from_SZ(k-1)) =cut # area # = (2^h + 2*2^(h-1)-2)^2 - 4*(2*2^(h-1) - 2)/2*((2*2^(h-1) - 2)/2 + 1)/2 # = (2^h + 2^h-2)^2 - 4*(2^h - 2)/2*((2^h - 2)/2 + 1)/2 # = (x + x-2)^2 - 4*(x - 2)/2*((x - 2)/2 + 1)/2 # = 7/2*x^2 - 7*x + 4 # = (7/2)*2^k - 7*2^h + 4 # odd # = (2^h + 2*2^h-2)^2 - 4*(2*2^h - 2)/2*((2*2^h - 2)/2 + 1)/2 # = (x + 2*x-2)^2 - 4*(2*x - 2)/2*((2*x - 2)/2 + 1)/2 # = 7*x^2 - 10*x + 4 # = (7/2)*2^k - 10*2^h + 4 # 2 back-to-back boundary N=0 to 2^k each # # h=0 n=4^h=1 boundary=2 # h=2 n=4^h=16 boundary=6*4=24 area=36-4=32 # 2^h/4 zig (2^h/4 - 2)*2 # *--------------* # | | # *--* *--* # | | side 2^h/4 same by symmetry # + + # # total # # A159741 8*(2^n-1) whole # A028399 2^n - 4 half cf A173033 # A000918 2^n - 2 quarter # 7------6------5 k=3 3 straight # | | = 2^h # 7-----8,8 4------3 2 # | | zig # 6 2 1 = 2*2^h-2 # | | # 5------4 0,0-----1 <- Y=0 # | | # 3------2------1 # h=0 n=2*4^h=2 boundary=4 # h=1 n=2*4^h=8 boundary=16 area=12 # h=2 n=2*4^h=32 boundary=40 # h=3 n=2*4^h=128 boundary=88 # total 4*(2^h) + 4*(2*2^h - 2) # = 3*2^h-8 # A182461 whole except 4 a(n) = a(n-1)*2+8 16,40,88, # A131128 3*2^n - 4 half # A033484 3*2^n - 2 quarter # A153893 3*2^n - 1 eighth h>=1 require MyOEIS; my @values; foreach my $k (0 .. 16) { my $n_end = 2**$k; my $h = int($k/2); my ($n1, $n2) = ($k % 2 ? diagonal_4k_axis_n_ends($h) : width_4k_axis_n_ends($h)); my ($x1,$y1) = $path->n_to_xy ($n1); my ($x2,$y2) = $path->n_to_xy ($n2); my $points = MyOEIS::path_boundary_points_ft($path, $n_end, $x1,$y1, $x2,$y2, side => 'right', dir => $h, ); if (@$points < 30) { print "k=$k from N=$n1 $x1,$y1 to N=$n2 $x2,$y2\n"; print " ",points_str($points),"\n"; } my $boundary = 2 * (scalar(@$points) - 1); my $area; if (@$points > 2) { my $planar = Math::Geometry::Planar->new; $planar->points($points); $area = 2 * $planar->area; } else { $area = 0; } # push @values, $boundary; push @values, $area; print "$h B=$boundary A=$area n=$n1 xy=$x1,$y1 to n=$n2 xy=$x2,$y2 limit $n_end\n"; } print join(',',@values),"\n"; shift @values; shift @values; Math::OEIS::Grep->search(array => \@values); exit 0; sub points_str { my ($points) = @_; ### points_str(): $points my $count = scalar(@$points); return "count=$count ".join(' ',map{join(',',@$_)}@$points) } } { my @sdir = (2,2,0,-2, -2,-2,0,2); sub s0_by_formula { my ($k) = @_; { my $h = int($k/2); return 2**$k/4 + $sdir[$k%8]*2**$h/4; } { # (1/4)*(2^k + (1-I)^k + (1+I)^k)) require Math::Complex; return (2**$k / 2 + (Math::Complex->new(1,-1)**$k * Math::Complex->new(1,-1) + Math::Complex->new(1,1) **$k * Math::Complex->new(1,1)) / 4); } } my @s1dir = (0,2,2,2, 0,-2,-2,-2); sub s1_by_formula { my ($k) = @_; my $h = int($k/2); return 2**$k/4 + $sdir[($k-2)%8]*2**$h/4; } my @s2dir = (0,2,2,2, 0,-2,-2,-2); sub s2_by_formula { my ($k) = @_; my $h = int($k/2); return 2**$k/4 + $sdir[($k-4)%8]*2**$h/4; } sub s3_by_formula { my ($k) = @_; my $h = int($k/2); return 2**$k/4 + $sdir[($k-6)%8]*2**$h/4; } # print " 1, 1, 1, 1, 2, 6, 16, 36, 72,136,256,496,992,2016,4096,8256,16512,32896,65536,\n"; # M0 # print " 0, 1, 2, 3, 4, 6, 12, 28, 64,136,272,528,1024,2016,4032,8128,16384,32896,65792,\n"; # M1 # print " 0, 0, 1, 3, 6, 10, 16, 28, 56,120,256,528,1056,2080,4096,8128,16256,32640,65536,\n"; # M2 print " 0, 0, 0, 1, 4, 10, 20, 36, 64,120,240,496,1024,2080,4160,8256,16384,32640,65280,\n"; # M3 foreach my $k (0 .. 17) { printf "%3d,", s3_by_formula($k); } exit 0; } { # triangle area parts by individual recurrence # e[k+8] = e[k+7] + 2*e[k+6] - e[k+4] + e[k+3] + 2*e[k+1] + 4*e[k] # 1,0,0,0,0,0,0,2,6,10,22,40,80,156,308,622,1242,2494,4994,9988,19988,39952,79904,159786 my @e = (1,0,0,0,0,0,0,2); foreach my $k (0 .. 15) { push @e, $e[-1] + 2*$e[-2] - $e[-4] + $e[-5] + 2*$e[-7] + 4*$e[-8]; } print join(",",@e),"\n"; exit 0; } { # area parts by a-z recurrence # # a 0,0,0,2,4,8,16,30,60,116,232,466,932,1872,3744,7494 # c 0,1,1,1,1,2,4,8,18,39,79,159,315,628,1250,2494 # e 1,0,0,0,0,0,0,2,6,10,22,40,80,156,308,622 # g 0,0,0,0,0,0,0,2,2,6,10,20,40,76,156,310 # b 0,0,1,1,3,5,10,20,38,78,155,311,625,1247,2500,4994 # d 0,0,0,1,1,3,5,10,20,38,78,155,311,625,1247,2500 # f 0,0,0,0,1,1,3,5,10,20,38,78,155,311,625,1247 # h 0,0,0,0,0,1,1,3,5,10,20,38,78,155,311,625 # i 0,0,0,0,0,0,1,1,3,5,10,20,38,78,155,311 # # [4] # [2] # [0] # [1] # [-1] # [0] # [2] # [1] # x^8 - (4*x^7 + 2*x^6 + 0*x^5 + 1*x^4 + -1*x^3 + 0*x^2 + 2*x + 1) # # 2,6,10,22,40,80,156,308,622,1242,2494,4994,9988,19988,39952,79904,159786,319550,639122,1278222,2556512,5113048,10226116,20452300,40904486 # # 4*2 + 2*6 + 0*10 + 22 - 40 + 0*80 + 2*156 + 308 # a*x^2*g(x) + b*x*g(x) - g(x) = initial # (-2 - 4*x)/(-1 + 1*x + 2*x^2 + 0*x^3 - x^4 + x^5 + 0*x^6 + 2*x^7 + 4*x^8) # # my (@a,@b,@c,@d,@e,@f,@g,@h,@i); my $a = 0; my $b = 0; my $c = 0; my $d = 0; my $e = 1; my $f = 0; my $g = 0; my $h = 0; my $i = 0; my @values; foreach my $k (0 .. 15) { print "$a $b $c $d $e $f $g $h $i\n"; push @a, $a; push @b, $b; push @c, $c; push @d, $d; push @e, $e; push @f, $f; push @g, $g; push @h, $h; push @i, $i; if ($k % 2) { push @values, $b; } else { push @values, $b; } ( $a, $b, $c, $d, $e, $f, $g, $h, $i) = (2*$d+2*$b, $a+$c, $c+$e+$f+$h, $b, 2*$g+2*$i, $d, 2*$i, $f, $h); $k < 2 || $e == 4*$i[-1 -1] + 2*$i[0 -1] or die; $k < 6 || $e == 4*$b[-5 -1] + 2*$b[-4 -1] or die; $k < 2 || $a == 2*$b[-1 -1] + 2*$b[0 -1] or die; $k < 2 || $f == $b[-1 -1] or die; $k < 3 || $h == $b[-2 -1] or die; $k < 7 || $c == ($c[0 -1] + 4*$b[-6 -1] + 2*$b[-5 -1] + $b[-2 -1] + $b[-3 -1]) or die; $k < 8 || $b == $b[-1] + 2*$b[-2] + 0 - $b[-4] + $b[-5] + 0 + 2*$b[-7] + 4*$b[-8] or die; } shift @values; while (@values && $values[0] == 0) { shift @values; } shift @values; shift @values; print join(",",@values),"\n"; Math::OEIS::Grep->search(array => \@values); print "a ",join(',',@a),"\n"; print "b ",join(',',@b),"\n"; print "c ",join(',',@c),"\n"; print "d ",join(',',@d),"\n"; print "e ",join(',',@e),"\n"; print "f ",join(',',@f),"\n"; print "g ",join(',',@g),"\n"; print "h ",join(',',@h),"\n"; print "i ",join(',',@i),"\n"; my $t = $a + 2*$b + 2*$c + 2*$d + $e + 2*$f + $g + 2*$h + 2*$i; # $a+$b+$c+$d+$e+$f+$g+$h+$i; $a /= $t; $b /= $t; $c /= $t; $d /= $t; $e /= $t; $f /= $t; $g /= $t; $h /= $t; $i /= $t; printf "%.5f %.5f %.5f %.5f %.5f %.5f %.5f %.5f %.5f\n", $a, $b, $c, $d, $e, $f, $g, $h, $i; printf "%.5f %.5f %.5f %.5f %.5f %.5f %.5f %.5f %.5f\n", $a/$i, $b/$i, $c/$i, $d/$i, $e/$i, $f/$i, $g/$i, $h/$i, $i/$i; printf "sum %f %.5f\n", $t, $a + 2*$b + 2*$c + 2*$d + $e + 2*$f + $g + 2*$h + 2*$i; printf "above %.5f\n", $a+2*$b+2*$c+2*$d+$e; printf "below %.5f\n", 2*$f+$g+2*$h+2*$i; printf "peak above %.5f\n", $a+2*$b+2*$c; printf "peak below %.5f\n", +2*$d + $e + 2*$f+$g+2*$h+2*$i; @values = (); $seg_len = 1; $|=1; foreach my $k (0 .. 10) { my @count = ((0) x 21); my $n_end = 4**$k; my $x = 0; my $y = 0; foreach my $n (0 .. $n_end-1) { my ($dx,$dy) = $path->n_to_dxdy($n); { my ($x,$y) = div_90($x,$y, $k); my ($dx,$dy) = div_90($dx,$dy, $k); ($x,$y) = (-$x,-$y); # rotate 180 ($dx,$dy) = (-$dx,-$dy); # rotate 180 # $x -= 1; if ($k < 2) { print "$x,$y $dx,$dy\n"; } my $part = seg_to_part($x,$y,$dx,$dy); $count[$part]++; } $x += $dx; $y += $dy; } if ($k < 0) { print "end $x,$y\n"; ($x,$y) = div_90($x,$y, $k); ($x,$y) = (-$x,-$y); # rotate 180 print "end rot $x,$y\n"; printcounts(\@count); print "\n"; } my $value = $count[1]; push @values, $value; print "$value,"; } print "\n"; print join(",",@values),"\n"; Math::OEIS::Grep->search(array => \@values); exit 0; sub div_90 { my ($x,$y, $n) = @_; foreach (1 .. $n) { ($x,$y) = ($y,-$x); # rotate -90 $x /= 2; $y /= 2; } return ($x,$y); } } { # points count on axes # x axis k even A052953 Expansion of 2*(1-x-x^2)/((x-1)(2x-1)(1+x)). # A128209 Jacobsthal numbers(A001045) + 1. # A001045 a(n) = a(n-1)+2*a(n-2) x/(1-x-2*x^2) (1-2x)(1+x) # A001045 Jacobsthal x/(1-x-2*x^2) near 2^n/3 # axis a my @values; $seg_len = 1; $|=1; foreach my $k (0 .. 18) { my $n_end = 2**$k; my $xaxis = 0; my $yaxis = 0; my $xpos = 0; my $ypos = 0; my $xneg = 0; my $yneg = 0; foreach my $n (0 .. $n_end) { my ($x,$y) = $path->n_to_xy($n); # foreach (1 .. $k) { # ($x,$y) = ($y,-$x); # rotate -90 # } if ($x == 0) { $yaxis++;} if ($y == 0) { $xaxis++;} if ($y == 0 && $x > 0) { $xpos++; } if ($y == 0 && $x < 0) { $xneg++; } if ($x == 0 && $y > 0) { $ypos++; } if ($x == 0 && $y < 0) { $yneg++; } if ($k < 2) { print "$n xy=$x,$y\n"; } } print "k=$k $xaxis $yaxis $xpos $xneg $ypos $yneg\n"; my $value = $xpos; push @values, $value; # print "$value,"; } print "\n"; print join(",",@values),"\n"; Math::OEIS::Grep->search(array => \@values); exit 0; } { # d alts gf # da[k+1] = d[k] + d[k-2] + d[k-3] + 2*d[k-4] + 1 # d alts = 1,2,4,8,17,34,68,136,273,546,1092,2184,4369,8738,17476,34952 # A083593 Expansion of 1/((1-2*x)*(1-x^4)). (1-x)*(1+x)*(1+x^2) # G(x) - 2*x^4*G(x) - x^3*G(x) - x^2*G(x) - x*G(x) - 1/(1-x) = 0 # G(x)*(1 - x - x^2 - x^3 - 2*x^4) # G(x) = 1/(1-x)/(1 - x - x^2 - x^3 - 2*x^4) # G(x) = 1/( (1-x) * (1-2*x) * (1-x^4) ) # G(x) = 1/( (1-2*x) * (1-x)^2 * (1+x) * (1+x^2) ) # W(x) = G(x^2) + x*G(x^2) # W(x) = (1 + x)/( (1-x^2)*(1+x^2)*(1-2*x^2)*(1+x^4) ) # G(x) = A/(1-2*x) + B/(1-x) + C/(1-x)^2 + D/(1+x) + (E+F*x)/(1+x^2) # (-A + -2*B + 2*D + 2*F)*x^5 # + (A + B + 2*C + -5*D + 2*E - 3*F)*x^4 # + (C + 6*D + -3*E - F)*x^3 # + (C + -6*D + -E + 3*F)*x^2 # + (A + 2*B + C + 4*D + 3*E - F)*x # + (-A + -B + -C + -D - E) # matsolve([-1,-2,0,2,0,2; 1,1,2,-5,2,-3; 0,0,1,6,-3,-1; 0,0,1,-6,-1,3; 1,2,1,4,3,-1; -1,-1,-1,-1,-1,0],[0;0;0;0;0;1]) # [-32/15] # [7/8] # [1/4] # [-1/24] # [1/20] # [-3/20] # # G(x) = 32/15/(1-2*x) - 7/8/(1-x) - 1/4/(1-x)^2 + 1/24/(1+x) + (-1/20 + 3/20*x)/(1+x^2) require Math::Polynomial; my $p = Math::Polynomial->new(1,2,4,8,17,34,68,136,273,546,1092,2184,4369,8738,17476,34952); my $q = Math::Polynomial->new(1,-1,-1,-1,-2); my $ones = Math::Polynomial->new(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1); print $q*$p-$ones,"\n"; exit 0; } { # d alts by recurrence # da[k+1] = d[k] + d[k-2] + d[k-3] + 2*d[k-4] + 1 my @d = (1,2,4,8); foreach (1 .. 20) { push @d, $d[-1] + $d[-2] + $d[-3] + 2*$d[-4] + 1; } print join(',',@d),"\n"; exit 0; } { # 8 axes directions by recurrence my (@a,@b,@c,@d,@e,@f,@g,@h); my $a = 0; my $b = 0; my $c = 0; my $d = 1; my $e = 0; my $f = 0; my $g = 0; my $h = 0; foreach my $i (0 .. 30) { print "$a $b $c $d $e $f $g $h\n"; ( $a, $b, $c, $d, $e, $f, $g, $h) = ($b, $c, $d, $a+$c+$e+$g+1, 2*$f, $g, $h, $a); push @a,$a; push @b,$b; push @c,$c; push @d,$d; push @e,$e; push @f,$f; push @g,$g; push @h,$h; $i < 2 || $a[-1] == $b[-2] or die; $i < 3 || $a[-1] == $c[-3] or die; $i < 4 || $a[-1] == $d[-4] or die; $i < 2 || $e[-1] == 2*$f[-2] or die; $i < 3 || $e[-1] == 2*$g[-3] or die; $i < 4 || $e[-1] == 2*$h[-4] or die; $i < 5 || $e[-1] == 2*$a[-5] or die; $i < 6 || $e[-1] == 2*$b[-6] or die; $i < 7 || $e[-1] == 2*$c[-7] or die; $i < 8 || $e[-1] == 2*$d[-8] or die; $i < 2 || $d[-1] == $a[-1 -1] + $c[-1 -1] + $e[-1 -1] + $g[-1 -1] + 1 or die; $i < 4 || $d[-1] == $d[-4 -1] + $d[-2 -1] + $e[-1 -1] + $g[-1 -1] + 1 or die; $i < 8 || $d[-1] == $d[-1 -2] + $d[-3 -2] + $d[-5 -2] + 2*$d[-7 -2] + 1 or die; # d[k+1] = a[k] + b[k] + e[k] + g[k] + 1 # # d[k+1] = d[k-2] + d[k-3] + d[k-5] + 2*d[k-7] + 1 } # 0 1 2 3 4 5 6 7 8 # @d = (1,1,2,2,4,4,8,8,17); # foreach my $i (8 .. 20) { # push @d, $d[$i-1] + $d[$i-3] + $d[$i-5] + 2*$d[$i-7] + 1; # d[i+1] # } # print join(',',@d),"\n"; my @values; for (my $i = 0; $i <= $#d; $i+=2) { push @values, $d[$i]; # push @values, 2*$a[$i] + $e[$i] + 2; } print join(',',@values),"\n"; Math::OEIS::Grep->search(array => \@values); exit 0; } { # 1---0 k=0 # # 1 k=1 d=1 # / \ # 2 0 # # 3-2-1 k=2 c=1 # | | d=1 # 4 0 # # * * # |\ /| # | \ / | a[k+1] = b # | ca ca | b = c # | \|/ | c = d # * | --*-- | * d = a+c + e+g + 1 # \ | /|\ | / e = 2*f # c d eg eg d / f[k+1] = g # \ | / \ | c g = h # \|/ \|/ h = a # *-----b---*--f-*-f--*--b------* # /|\ /|\ # a | g g | a # / h \ / h \ # / | \ / | \ # * | * | * # | / \ | # | / \ | # | / \ | # |/ \| # * * # # a[k+1] = b[k] = c[k-1] = d[k-2] # e[k+1] = 2*f[k] = 2*g[k-1] = 2*h[k-2] = 2*a[k-3] = 2*b[k-4] = 2*c[k-5] = 2*d[k-6] # g[k+1] = h[k] = a[k-1] = b[k-2] = c[k-3] = d[k-4] # d[k+1] = a[k] + c[k] + e[k] + g[k] + 1 # = d[k-3] + d[k-1] + 2*d[k-7] + d[k-5] + 1 # d[k+1] = d[k-1] + d[k-3] + d[k-5] + 2*d[k-7] + 1 # d=1,1,2,2,4,4,8,8,17,17,34,34,68,68,136,136,273,273,546 # 2*1 + 2 + 4 + 8 + 1 = 17 # * * # |\ /| # | \ / | # | \ / | # * | * | * # \ | / \ | / # b c d d c b # \|/ \|/ # ----a---*---e---*---a----- # /|\ /|\ # h g f f g h # / | \ / | \ # * | * | * # | / \ | # | / \ | # |/ \| # * * =pod X positive Xpos[8i+0] = e[8i+0] + a[8i+0] + 1 = 2*d[8i-7] + d[8i-3] + 1 Xpos[8i+1] = d[8i+1] Xpos[8i+2] = c[8i+2] = d[8i+1] Xpos[8i+3] = b[8i+3] = d[8i+1] Xpos[8i+4] = a[8i+4] = d[8i+1] Xpos[8i+5] = h[8i+5] = d[8i+1] Xpos[8i+6] = g[8i+6] = d[8i+1] Xpos[8i+7] = f[8i+7] = d[8i+1] 1,1,1,1,1,1,1,1, 7, 17,17,17,17,17,17,17, 103, 273,273 X axis X[4i+0] = 2*a[4i+0] + e[4i+0] = 2*d[4i-3] + 2*d[4i-7] X[4i+1] = d[4i+1] + h[4i+1] = d[4i+1] + d[4i-3] X[4i+2] = c[4i+2] + g[4i+2] = d[4i+1] + d[4i-3] X[4i+3] = b[4i+3] + f[4i+3] = d[4i+1] + d[4i-3] 2,2,2,2, 4, 6,6,6, 12, 22,22,22, 44, 86,86,86, 172, 342,342,342, 684 =cut my ($len,$x,$y); my $xy_to_part = sub { if ($y == 0 && $x > 0) { return 0; } # a if ($x == $y && $x > 0) { return 1; } # b if ($x == 0 && $y > 0) { return 2; } # c if ($x == -$y && $y > 0) { return 3; } # d if ($x < 0 && $x > -$len) { return 4; } # e if ($x == $y && $x < 0) { return 5; } # f if ($x == 0 && $y < 0) { return 6; } # g if ($x == -$y && $y < 0) { return 7; } # h return 10; }; my @counts; $seg_len = 1; $|=1; my $i = 0; for (my $k = 1; $k < 20; $k += 1, $i++) { my $n_end = 2**$k; $len = 2**ceil($k/2); my $rot = (int($k/2)+2) % 4; my $fortyfive = $k % 2; foreach my $part (0 .. 10) { $counts[$part][$i] = 0; } foreach my $n (0 .. $n_end) { ($x,$y) = $path->n_to_xy($n); foreach (1 .. $rot) { ($x,$y) = ($y,-$x); # rotate -90 } if ($fortyfive) { ($x,$y) = ($x+$y, $y-$x); # rotate -45 } my $part = $xy_to_part->($x,$y, $len); $counts[$part][$i]++; if ($k < 3 || $n == $n_end) { # print "$n xy=$x,$y part=$part\n"; } } print "k=$k ",join(' ',map{$counts[$_][$i]}0 .. 7)," len=$len\n"; } print "\n"; foreach my $part (0 .. 7) { my $aref = $counts[$part]; my @values = @$aref; shift @values; while (@values && $values[0] == 0) { shift @values; } shift @values; print "part=$part ",join(",",@values),"\n"; if (@values) { Math::OEIS::Grep->search(array => \@values); } } exit 0; } { # single, double etc point counts my $n = $path->n_start; my %seen; my @counts; foreach my $k (0 .. 20) { $counts[1][$k] = 0; $counts[2][$k] = 0; $counts[3][$k] = 0; $counts[4][$k] = 0; my $n_end = 2**$k; while ($n < $n_end) { my ($x,$y) = $path->n_to_xy ($n); $seen{"$x,$y"}++; $n++; } foreach my $seen (values %seen) { $counts[$seen][$k]++; } print "$k $counts[1][$k] $counts[2][$k] $counts[3][$k] $counts[4][$k]\n"; } foreach my $s (1 .. 4) { my @values = @{$counts[$s]}; while (@values && $values[0] == 0) { shift @values; } shift @values; shift @values; shift @values; shift @values; print "s=$s\n"; print join(",",@values),"\n"; Math::OEIS::Grep->search(array => \@values); } exit 0; } { # area parts of fractal # a b(2) c(2) d(2) e f(2) g h(2) i(2) # 0.36364 0.24242 0.12121 0.12121 0.03030 0.06061 0.01515 0.03030 0.01515 # # 0.22857 0.15238 0.07619 0.07619 0.01905 0.03810 0.00952 0.01905 0.00952 # 24 + 16 + 8 + 8+ 2 + 4 + 1 + 2 + 1 = 66 # exit 0; } { sub seg_to_quad { my ($x,$y,$dx,$dy, $a,$b,$c,$d) = @_; ### seg_to_quad(): "x=$x y=$y dx=$dx dy=$dy" if ($x < 0) { ### x neg ... return undef; } if ($x == 0) { if ($dy < 0) { ### x=0 and leftward notch ... return undef; } if ($dx < 0) { ### x=0 and downward notch ... return undef; } } if ($y < 0) { ### y neg ... return undef; } if ($y == 0) { if ($dx > 0) { ### y=0 and downward notch ... return undef; } if ($dy < 0) { ### y=0 and leftward notch ... return undef; } } # *---------* # |\ a /| # | \ / | # | \ / | # | \ / | # |b * d| # | / \ | # | / \ | # | / \ | # |/ c \| # *---------* my $s = $x+$y; my $cd = ($x > $y || ($x == $y && ($dx > 0 # downward notch || $dy < 0 # leftward notch ))); my $ad = ($s > $seg_len || ($s == $seg_len && ($dx > 0 # downward notch || $dy > 0 # rightward notch ))); ### at: "cd=$cd ad=$ad" if ($cd) { if ($ad) { return $d; } else { return $c; } } else { if ($ad) { return $a; } else { return $b; } } } sub seg_to_part { my ($x,$y,$dx,$dy) = @_; if (defined (my $part = seg_to_quad($x,$y,$dx,$dy, 20,4,7,20))) { return $part; } $x += $seg_len; if (defined (my $part = seg_to_quad($x,$y,$dx,$dy, 0,2,6,3))) { return $part; } $x += $seg_len; if (defined (my $part = seg_to_quad($x,$y,$dx,$dy, 20,20,5,1))) { return $part; } $x -= 2*$seg_len; $y += $seg_len; if (defined (my $part = seg_to_quad($x,$y,$dx,$dy, 10,14,20,20))) { return $part; } $x += $seg_len; if (defined (my $part = seg_to_quad($x,$y,$dx,$dy, 9,12,20,13))) { return $part; } $x += $seg_len; if (defined (my $part = seg_to_quad($x,$y,$dx,$dy, 8,20,20,11))) { return $part; } return 20; } sub printcounts { my ($count) = @_; my $total = sum(@$count); printf " | %6d | total %d\n", $count->[0], $total; printf " %6d | %6d %6d | %6d\n", $count->[1], $count->[2], $count->[3], $count->[4]; printf "%6d | %6d | %6d\n", $count->[5], $count->[6], $count->[7]; print "-------------------------------------------------\n"; printf "%6d | %6d | %6d\n", $count->[8], $count->[9], $count->[10]; printf " %6d | %6d %6d | %6d [%d]\n", $count->[11], $count->[12], $count->[13], $count->[14], $count->[20];; } } { # area as triangle spread, counted from path # len 16384 # half 8192 # # 61356740 40904429 20452175 20452175 40904429 20452243 5113048 20452243 10226127 2556512 10226127 5113058 2556546 2556546 5113058 # total 268435456 # sum 268435456 # | 61356740 | # 40904429 | 20452175 20452175 | 40904429 # 20452243 | 5113048 | 20452243 # ------------------------------------------------- # 10226127 | 2556512 | 10226127 # 5113058 | 2556546 2556546 | 5113058 # *---------* # /|\ 0|0 /|\ # / | \ | / | \ # / | \ | / | \ # / 1|2 \ / 3|4 \ # *--- | ---*-- | --* # / \ 1|2 / \ 3|4 / \ # / \ | / \ | / \ # / | \ | / | \ | / | \ # / 5|5 \|/ 6|6 \|/ 7|7 \ # *---------*---------*---------* # \ 8|8 /|\ 9|9 /|\ 10|10 / # \ | / | \ | / | \ | / # \ / | \ / | \ / # \ / 11|12 \ /13 |14 \ / # *-- | --*-- | --* # \ 11|12 / \13 |14 / # \ | / \ | / # \ | / \ | / # \|/ \|/ # * * # # * * <- Y=4 # 2 0 0 3 # 11-----10-----9,7-----6------5 . # 1 1| 0|0 |4 4 # . 13-----12 8 4------3 . <- Y=2 # 1 | | 4 # 14 | . . | 2 # 5 | | 7 # 15-----16 0------1 <- Y=0 # 8 len=4 10 # my $k = 3*8 + 4; my $len = 2**($k/2); my $half = $len/2; my $n_end = 2**$k; my $x = 0; my $y = 0; my ($dx,$dy); print "len $len\n"; print "half $half\n"; my $path = Math::PlanePath::CCurve->new; { my ($x,$y) = $path->n_to_xy($n_end); $x == -$len or die "$x"; $y == 0 or die; } my @count = ((0) x 15); my $mx = 0; my $my = 0; foreach my $n (0 .. $n_end-1) { ($dx,$dy) = $path->n_to_dxdy($n); $x = $mx; $y = $my; my $part = seg_to_part($x,$y,$dx,$dy); # print "x=$mx y=$my s=",$mx+$my," dx=$dx dy=$dy part $part\n"; $count[$part]++; $mx += $dx; $my += $dy; } print "\n"; print join(' ',@count),"\n"; print "total $n_end\n"; printcounts(\@count); exit 0; } { # *---------* *---------* # /|\ 0|0 /|\ /|\ | /|\ # / | \ | / | \ 1/ | \2 | 3/ | \4 # / | \ | / | \ / | 0\ | /0 | \ # / 1|2 \ / 3|4 \ / | \ / | \ # *--- | ---*-- | --* *--- | ---*-- | --* # / \ 1|2 / \ 3|4 / \ / \ | / \ | / \ # / \ | / \ | / \ 5/ \5 | 6/ \6 | 7/ \7 # / | \ | / | \ | / | \ / | 1\ | /2 | 3\ | /4 | \ # / 5|5 \|/ 6|6 \|/ 7|7 \ / | \|/ | \|/ | \ # *---------*---------*---------* *---------*---------*---------* # \ 8|8 /|\ 9|9 /|\ 10|10 / \ | /|\ | /|\ | / # \ | / | \ | / | \ | / \ |11/ | \12|13/ | \14| / # \ / | \ / | \ / 8\ /8 | 9\ /9 |10\ /10 # \ / 11|12 \ /13 |14 \ / \ / | \ / | \ / # *-- | --*-- | --* *-- | --*-- | --* # \ 11|12 / \13 |14 / \ | / \ | / # \ | / \ | / \ | / \ | / # \ | / \ | / 11\ | /12 13\ | /14 # \|/ \|/ \|/ \|/ # * * * * # # 6 -> left 2bit 2,9 # expanded a b c d e f g h i j k l m n p # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 my $left_bitperm = [13, 6,3,12,9, 0,2,8, 3,6,11, 2,0,5,1]; my $right_bitperm = [12, 9,13,2,6, 10,3,0, 14,6,2, 4,7,0,3]; # # unexpanded # # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 # my $left = [9, 3,6,9,12, 3,6,11, 0,2,8, 0,2,1,5]; # my $right = [8, 13,9,6,2, 14,6, 2, 10,3,0, 7,4,3,0]; my @mask = map {1<<$_} 0 .. $#$left_bitperm; my $bitperm = sub { my ($n, $perm) = @_; my $new = 0; foreach my $i (0 .. $#$perm) { if ($n & $mask[$perm->[$i]]) { $new |= $mask[$i]; } } return $new; }; my @left = map {$bitperm->($_,$left_bitperm)} 0 .. 0x7FFF; my @right = map {$bitperm->($_,$right_bitperm)} 0 .. 0x7FFF; require Graph::Easy; my $graph = Graph::Easy->new(timeout => 9999); my %seen; # my %reverse; my @pending = ($mask[6]); @pending = @mask; while (@pending) { # last if scalar(keys %seen) > 20; my $n = pop @pending; next if $seen{$n}; $seen{$n} = 1; my $l = $left[$n]; my $r = $right[$n]; # push @{$reverse{$l}}, "$n.L"; # push @{$reverse{$r}}, "$n.R"; printf "%015b -> left %015b right %015b\n", $n, $l, $r; push @pending, $l, $r; my $n_name = $n; my $l_name = $l; my $r_name = $r; # my $n_name = sprintf '%02X', $n; # my $l_name = sprintf '%02X', $l; # my $r_name = sprintf '%02X', $r; if ($n & $mask[6]) { if ($n != $l) { $graph->add_edge_once($n_name,$l_name); } if ($n != $r) { $graph->add_edge_once($n_name,$r_name); } } # if (($n & $mask[6]) # && ($l & $mask[6]) # && ($r & $mask[6]) # ) { # # $graph->add_edge_once($n_name,$l_name); # # $graph->add_edge_once($n_name,$r_name); # } # if (($n & $mask[6]) && ($r & $mask[6])) { # } } my @seen = sort {$a<=>$b} keys %seen; print "count seen ",scalar(@seen),"\n"; foreach my $i (0 .. $#seen) { $seen{$seen[$i]} = $i; } { # always-on my %not_always_on; foreach my $n (@seen) { if (($n & $mask[6]) == 0) { $not_always_on{$n} = 1; } } print "count OFF ",scalar(keys %not_always_on),"\n"; my $more = 1; while ($more) { $more = 0; foreach my $n (@seen) { unless ($not_always_on{$n}) { my $l = $left[$n]; my $r = $right[$n]; if ($not_always_on{$l} || $not_always_on{$r}) { $not_always_on{$n} = 1; $more++; } } } print " pass $more excluded\n"; } my %always_on; foreach my $n (@seen) { unless ($not_always_on{$n}) { $always_on{$n} = 1; } } print "count ON ",scalar(keys %always_on),", not always ON ",scalar(keys %not_always_on),"\n"; foreach my $n (@seen) { if ($always_on{$n}) { printf " ON always %15b\n", $n; } } } { # always-off my %not_always_off; foreach my $n (@seen) { if ($n & $mask[6]) { $not_always_off{$n} = 1; } } print "count ON ",scalar(keys %not_always_off),"\n"; my $more = 1; while ($more) { $more = 0; foreach my $n (@seen) { unless ($not_always_off{$n}) { my $l = $left[$n]; my $r = $right[$n]; if ($not_always_off{$l} || $not_always_off{$r}) { $not_always_off{$n} = 1; $more++; } } } print " pass $more excluded\n"; } my %always_off; foreach my $n (@seen) { unless ($not_always_off{$n}) { $always_off{$n} = 1; } } print "count always-OFF ",scalar(keys %always_off),", not always OFF ",scalar(keys %not_always_off),"\n"; foreach my $n (@seen) { if ($always_off{$n}) { printf " OFF always %015b\n", $n; } } } { foreach my $n (@seen) { if ($left[$n] == 0 || $right[$n] == 0) { print "to zero $n -> $left[$n] $right[$n]\n"; } } } { foreach my $n (@seen) { if ($left[$n] == 0x7FFF || $right[$n] == 0x7FFF) { print "to ones $n -> $left[$n] $right[$n]\n"; } } } { foreach my $n (@seen) { if ($left[$n] == $n || $right[$n] == $n) { print "to self $n -> $left[$n] $right[$n]\n"; } } } { # row reductions my @m; my $end = $#seen; my $printrow = sub { my ($r) = @_; print "row $r = "; foreach my $i (0 .. $end) { if ($m[$r][$i]) { print "$m[$r][$i]"; } print ","; } print "\n"; }; my $printcol = sub { my ($c) = @_; print "column $c = "; foreach my $i (0 .. $end) { if ($m[$i][$c]) { print "$m[$i][$c]"; } print ","; } print "\n"; }; foreach my $i (0 .. $end) { $m[$i][$i] = Math::BigRat->new(1); my $n = $seen[$i]; foreach my $t ($left[$n], $right[$n]) { my $ti = $seen{$t} || 0; if ($ti != 0 && $ti != $end) { $m[$i][$ti] ||= 0; $m[$i][$ti] -= Math::BigRat->new("-1/2"); } } } $m[$end][$end+1] = Math::BigRat->new(1); print "weights\n"; $printcol->($end+1); foreach my $c (0 .. $end) { print "column $c\n"; foreach my $i (0 .. $c-1) { if ($m[$c][$i]) { $printrow->($c); die "oops not zero $c, $i"; } } if ($m[$c][$c] == 0) { $printrow->($c); die " is zero"; } if ($m[$c][$c] != 1) { my $f = 1 / $m[$c][$c]; my $count = 0; foreach my $i (0 .. $end+1) { if ($m[$c][$i]) { $m[$c][$i] *= $f; $count++; } } print " mul $f ($count terms)\n"; } print " weight ",$m[$c][$end+1]||0,"\n"; $m[$c][$c] == 1 or die " diagonal not one ",$m[$c][$c]; foreach my $r ($c+1 .. $end) { my $f = $m[$r][$c]; if ($f) { my $count = 0; foreach my $i (0 .. $end+1) { my $d = ($m[$r][$i] || 0) - $f * ($m[$c][$i] || 0); if ($d == 0) { delete $m[$r][$i]; } else { $m[$r][$i] = $d; $count++; } } print " row $r sub $c * $f ($count terms)\n"; } } } print "weights\n"; $printcol->($end+1); foreach my $c (reverse 0 .. $end) { print "column $c\n"; $m[$c][$c] == 1 or die " diagonal not one ",$m[$c][$c]; my $count = 0; foreach my $r (0 .. $c-1) { my $f = delete $m[$r][$c]; if ($f) { $m[$r][$end+1] ||= 0; $m[$r][$end+1] -= $f ; $count++; } } print " ($count terms)\n"; } print "weights\n"; $printcol->($end+1); foreach my $i (0 .. $end) { my $n = $seen[$i]; my $w = $m[$i][$end+1] || 0; print " $n => Math::BigRat->new('$w'),\n"; } foreach my $n (@mask) { my $i = $seen{$n}; if (defined $i) { print "mask $n weight $m[$i][$end+1]\n"; } } } exit; if (0) { print "weight\n"; my %weight; $weight{0} = 0; $weight{0x7FFF} = 1; my %weight_const; my %weight_n; my %weight_factor; my $more = 1; while ($more) { $more = 0; foreach my $n (@seen) { unless (defined $weight{$n}) { my $l = $left[$n]; my $r = $right[$n]; if (defined $weight{$l} && defined $weight{$r}) { $weight{$n} = $weight{$l} + $weight{$r}; $more = 1; delete $weight_const{$n}; delete $weight_n{$n}; delete $weight_factor{$n}; } elsif (defined $weight_n{$n} && $weight_n{$n} == $n) { # w = c + w*f # w = c/(1-f) $weight{$n} = $weight_const{$n} / (1 - $weight_factor{$n}); $more = 1; } elsif (! defined $weight_n{$n} && defined $weight{$l}) { $weight_const{$n} = $weight{$l}/2; $weight_n{$n} = $r; $weight_factor{$n} = 1/2; $more = 1; } elsif (! defined $weight_n{$n} && defined $weight{$r}) { $weight_const{$n} = $weight{$r}/2; $weight_n{$n} = $l; $weight_factor{$n} = 1/2; $more = 1; } elsif (defined (my $w = $weight_n{$n})) { if (defined $weight{$w}) { $weight{$n} = $weight_const{$n} + $weight{$w}*$weight_factor{$n}; delete $weight_const{$n}; delete $weight_n{$n}; delete $weight_factor{$n}; $more = 1; } elsif (defined $weight_n{$w}) { # c + f*(c2+f2*x) # = c+f*c2 + f*f2*x $weight_const{$n} += $weight_const{$w} * $weight_factor{$n}; $weight_n{$n} = $weight_n{$w}; $weight_factor{$n} *= $weight_factor{$w}; $more = 1; } } } } print " pass $more, factors ",scalar(keys %weight_factor),", final ",scalar(keys %weight),"\n"; } foreach my $n (sort {$a<=>$b} keys %weight_n) { printf "%X -> %f + %f * %X\n", $n, $weight_const{$n}, $weight_factor{$n}, $weight_n{$n}; } # print join(' ',map{sprintf '%X', $_} sort {$a<=>$b} keys %weight),"\n"; } if (0) { # w[0] - (w[0]/2 + w[0]/2) = 0 # w[f] - (w[f]/2 + w[f]/2) = 0 # w[6] = 0.5*w[2] + 0.5*w[3] # w[0] = 0 # w[32767] = 1 # w = W*w + F # w = (I-W)^-1 * F # open my $fh, '>', '/tmp/x.gp'; print $fh "allocatemem(230000000)\n"; print $fh "W=["; my $sep = ''; print "before ",scalar(@seen),"\n"; # @seen = grep {$_ != 0} @seen; # @seen = grep {$left[$_] || $right[$_]} @seen; print "reduced ",scalar(@seen),"\n"; foreach my $n (@seen) { my $l = $left[$n]; my $r = $right[$n]; foreach my $i (@seen) { print $fh $sep, ($i==$n ? "1" : "0"), ($i == $l && $i != 0 && $i != 0x7FFF ? "-1/2" : ""), ($i == $r && $i != 0 && $i != 0x7FFF ? "-1/2" : ""); $sep = ','; } $sep = "; \\\n"; printf "w[%4X] - (w[%4X]/2 + w[%4X]/2) = 0\n", $n,$l,$r; } print $fh "];"; print $fh " F=["; $sep = ''; foreach my $i (@seen) { print $fh $sep, $i==0x7FFF ? 1 : 0; $sep = ';'; } print $fh "];"; # print $fh " matdet(W)\n"; print $fh " mattranspose(matsolve(W,F))\n"; # print $fh "W^-1 * F\n"; $|=1; # autoflush system ('gp < /tmp/x.gp'); } $graph->rename_node(32767, "*"); # { # # merge on->on,on # # my $all_successors_on = sub { # my ($node) = @_; # foreach my $successor ($node->successors) { # if (! ($successor->label & $mask[6])) { # return 0; # } # } # return 1; # }; # my $more = 1; # my $depth = 0; # while ($more) { # # print $graph->as_ascii; # print "depth $depth\n"; # $more = 0; # foreach my $node ($graph->nodes) { # if ($all_successors_on->($node)) { # # my @successsors = $node->successors; # if (! @successsors) { # print " del ",$node->label,"\n"; # $graph->del_node($node); # $more = 1; # } # } # $depth++; # } # my $num_nodes = $graph->nodes; # print "merged to $num_nodes\n"; # } { my $graphviz = $graph->as_graphviz(); require File::Slurp; File::Slurp::write_file('/tmp/c-curve.dot', $graphviz); } #print $graph->as_ascii; print "type ",$graph->type,"\n"; print "is_simple: ",$graph->is_simple ? "yes\n" : "no\n"; print "roots: ",join(' ', map{$_->name} $graph->source_nodes), "\n"; if (0) { # delete sinks print "count nodes ",scalar($graph->nodes),"\n"; my $more = 1; my $depth = 0; while ($more) { # print $graph->as_ascii; print "depth $depth\n"; $more = 0; foreach my $node ($graph->nodes) { my @successsors = $node->successors; if (! @successsors) { print " del ",$node->label,"\n"; $graph->del_node($node); $more = 1; } } $depth++; } my $num_nodes = $graph->nodes; print "remaining $num_nodes\n"; } exit; { my $txt = $graph->as_txt; require File::Slurp; File::Slurp::write_file('/tmp/c-curve.txt', $txt); } exit 0; } { # convex hull # A007283 3*2^n require Math::Geometry::Planar; my @values; my @points; my $n = $path->n_start; foreach my $k (0 .. 14) { my $n_end = 2**$k; while ($n <= $n_end) { push @points, [ $path->n_to_xy($n) ]; $n++; } my ($area, $boundary); if (@points < 3) { $area = 0; $boundary = 2; } else { my $polygon = Math::Geometry::Planar->new; $polygon->points([@points]); if (@points > 3) { $polygon = $polygon->convexhull2; } my $points = $polygon->points; $area = blessed($polygon) && $polygon->area; $boundary = blessed($polygon) && $polygon->perimeter; } my $bstr = to_root_sum($boundary); my ($a,$b) = $path->_UNDOCUMENTED_level_to_hull_boundary_sqrt2($k); my $len = $path->_UNDOCUMENTED_level_to_hull_boundary($k); my $ar = $path->_UNDOCUMENTED_level_to_hull_area($k); # print "$k $boundary = $bstr $a $b\n"; printf "%6.3f\n", $len; #print "$k $area $ar\n"; # print "$ar, "; if (! ($k & 1)) { push @values, $area; } } while (! is_integer($values[0])) { shift @values; } # shift @values; # shift @values; # shift @values; print join(",",@values),"\n"; Math::OEIS::Grep->search(array => \@values); exit 0; sub is_integer { my ($n) = @_; return ($n == int($n)); } # k even # S[h] # --------- # / \ Z[h-1] # / \ # | | S[h-1] # \ / Z[h-2] # -- -- # width = S[h] + 2*(Z[h-1]/2) # = 2^h + 2*2^(h-1)-2 # = 2*2^h - 2 # height = S[h-1] + Z[h-1]/2 + Z[h-2]/2 # = 2^(h-1) + (2*2^(h-1)-2)/2 + (2*2^(h-2)-2)/2 # = 2^(h-1) + 2^(h-1)-1 + 2^(h-2)-1 # = 2^(h-1) + 2^(h-1) + 2^(h-2) - 2 # = 5*2^(h-2) - 2 # upper corner = (Z[h-1]/2) # = 2^(h-1) - 1 # lower corner = (Z[h-2]/2) # = 2^(h-2) - 1 # area = width*height - upper^2 - lower^2 # = (2*2^h - 2)*(5*2^(h-2) - 2) - (2^(h-1) - 1)^2 - (2^(h-2) - 1)^2 # = (8*2^(h-2) - 2)*(5*2^(h-2) - 2) - (2*2^(h-2) - 1)^2 - (2^(h-2) - 1)^2 # = (8*p - 2)*(5*p - 2) - (2*p - 1)^2 - (p - 1)^2 # = 35*p^2 - 20*p + 2 # = 35*2^(2h-4) - 20*2^(h-2) + 2 # = 35*2^(k-4) - 20*2^(h-2) + 2 # = 35*2^(k-4) - 5*2^h + 2 # k odd # S[h] # ---- # Z[h-1] / \ middle Z[h] # S[h-1] | \ # \ \ # | S[h] # | # \ / Z[h-1] # -- # S[h-1] # # width = S[h] + Z[h]/2 + Z[h-1]/2 # = 2^h + 2^h-1 + 2^(h-1)-1 # = 5*2^(h-1) - 2 # = 5/2*p - 2 # height = Z[h]/2 + S[h] + Z[h-1]/2 # = width # UL = Z[h-1]/2 = 2^(h-1) - 1 = p/2-1 # UR = Z[h]/2 = 2^h - 1 = p-1 # BL = width - Z[h-1]/2 - S[h-1] # = S[h] + Z[h]/2 + Z[h-1]/2 - Z[h-1]/2 - S[h-1] # = Z[h]/2 + S[h] - S[h-1] # = p-1 + p - p/2 # BR = Z[h-1]/2 = 2^(h-1) - 1 = p/2-1 # area = width*height - UL^2/2 - UR^2/2 - BL^2/2 - BR^2/2 # = (5/2*p - 2)^2 - (p/2-1)^2/2 - (p-1)^2/2 - (p-1 + p - p/2)^2/2 - (p/2-1)^2/2 # = 35/8*p^2 - 13/2*p + 2 # x = a + b*sqrt(2) sub to_root_sum { my ($x) = @_; if (! defined $x) { return 'undef' } foreach my $b (0 .. int($x)) { my $a = $x - $b*sqrt(2); my $a_int = int($a+.5); if (abs($a - $a_int) < 0.00000001) { return "$a_int + $b*sqrt(2)"; } } return "$x"; } } { # total boundary vs recurrence # # B[k] = / 7*2^h - 2k - 6 + 55/4*2^h + 28h - 130 if k even # \ 10*2^h - 2k - 6 + 78/4*2^h + 28h - 116 if k odd # # = / 83/4 * 2^h + 12k - 136 if k even k >= 6 # \ 118/4 * 2^h + 12k - 136 if k odd # # B[k] = 2*B[k-1] + B[k-2] - 4*B[k-3] + 2*B[k-3] my @want = (2,4,8,16,30,56,102,184,292,444,648,940,1336,1908,2688,3820,5368,7620,10704,15196,21352,30324); my $B; $B = sub { my ($k) = @_; if ($k < 6) { return $want[$k]; } my $h = int($k/2); if ($k % 2 == 0) { return 83/4 * 2**$h + 12*$k - 136; # 83+35 = 118 } else { return 118/4 * 2**$h + 12*$k - 136; } }; $B = sub { my ($k) = @_; if ($k < 10) { return $want[$k]; } return ($B->($k-4) * 2 + $B->($k-3) * -4 + $B->($k-2) * 1 + $B->($k-1) * 2); }; # $B = sub { # my ($k) = @_; # return MyOEIS::path_boundary_length($path, 2**$k); # }; $|=1; foreach my $k (0 .. $#want) { my $want = $want[$k]; my $got = $B->($k); my $diff = $want - $got; print "$k $want $got $diff\n"; # print "$got,"; } exit 0; } { # left boundary vs recurrence # L[k] = 4*L[k-1] - 5*L[k-2] + 2*L[k-3] k >= 6 # x^3 - 4*x^2 + 5*x - 2 = (x-1)^2 * (x-2) so a*2^k + b*k + c # # explicit L[2*h] = 55/4 * 2^h + 28*h - 130 # h>=3 # explicit L[2*h+1] = 78/4 * 2^h + 28*h - 116 # my @want = (1,4,16,64,202,450,918,1826,3614,7162,14230,28338,56526,112874,225542,450850,901438,1802586); my @want = (2,8,32,124,308,648,1300,2576,5100,10120,20132,); # left 2k+1 my $L; $L = sub { my ($k) = @_; if ($k < 6) { return $want[$k]; } return ($L->($k-3) * 2 + $L->($k-2) * -5 + $L->($k-1) * 4); }; $L = sub { my ($k) = @_; if ($k < 3) { return $want[$k]; } return 78/4*2**$k + 28*$k - 116; }; # $L = sub { # my ($k) = @_; # return MyOEIS::path_boundary_length($path, 2*4**$k, side => 'left'); # }; $|=1; foreach my $k (0 .. $#want) { my $want = $want[$k]; my $got = $L->($k); my $diff = $want - $got; print "$k $want $got $diff\n"; # print "$got,"; } exit 0; } { # right boundary formula vs recurrence # R[k] = 2*R[k-1] + R[k-2] - 4*R[k-3] + 2*R[k-4] # # R[2k] = 4*R[2k-2] - 5*R[2k-4] + 2*R[2k-6] # R[2k+1] = 4*R[2k-1] - 5*R[2k-3] + 2*R[2k-5] my $R; $R = sub { my ($k) = @_; if ($k < 4) { return R_formula($k); } return (2*$R->($k-4) - 4*$R->($k-3) + $R->($k-2) + 2*$R->($k-1)); }; require Memoize; $R = Memoize::memoize($R); my $R2; $R2 = sub { my ($k) = @_; if ($k < 3) { return R_formula(2*$k); } return (2*$R2->($k-3) - 5*$R2->($k-2) + 4*$R2->($k-1)); }; require Memoize; $R2 = Memoize::memoize($R2); my $R2P1; $R2P1 = sub { my ($k) = @_; if ($k < 3) { return R_formula(2*$k+1); } return (2*$R2P1->($k-3) - 5*$R2P1->($k-2) + 4*$R2P1->($k-1)); }; require Memoize; $R2P1 = Memoize::memoize($R2P1); foreach my $k (0 .. 50) { # my $want = R_formula($k); # print "$k $want ",$R->($k),"\n"; my $want = R_formula(2*$k); print "$k $want ",$R2->($k),"\n"; } exit 0; } { # right outer boundary with sqrt(2) sub S_formula { my ($h) = @_; return 2**$h; }; sub Z_formula { my ($h) = @_; return 2*2**$h - 2; }; my $S_cum = sub { # sum S[0] .. S[h] inclusive my ($h) = @_; return 2**($h+1) - 1; }; my $Z_cum = sub { # sum Z[0] .. Z[h] inclusive my ($h) = @_; return 2*(2**($h+1) - 1) - 2*($h+1); }; my $S_inR = sub { my ($k) = @_; my ($h, $rem) = Math::PlanePath::_divrem($k,2); if ($rem) { return 2*$S_cum->($h); } else { return 2*$S_cum->($h-1) + S_formula($h); } }; my $Z_inR = sub { my ($k) = @_; my ($h, $rem) = Math::PlanePath::_divrem($k,2); if ($rem) { return 2*$Z_cum->($h-1) + Z_formula($h); } else { return 2*$Z_cum->($h-1); } }; my $R_bySZ = sub { my ($k) = @_; return $S_inR->($k) + $Z_inR->($k); }; { my $total = 0; foreach my $h (0 .. 10) { $total == $S_cum->($h-1) or die; $total += S_formula($h); } } { my $total = 0; foreach my $h (0 .. 10) { $total == $Z_cum->($h-1) or die; $total += Z_formula($h); } } # { # print $S_cum->(-1),"\n"; # foreach my $h (0 .. 10) { # print "+ ",S_formula($h), " = ",$S_cum->($h),"\n"; # } # } { foreach my $k (0 .. 10) { my $s = $S_inR->($k); my $z = $S_inR->($k); my $rby = $R_bySZ->($k); my $rformula = R_formula($k); # print "$k $s + $z = $rby rf=$rformula\n"; $rby == $rformula or die "$k $rby $rformula"; } } { foreach my $k (0 .. 100) { my $s = $S_inR->($k); my $z = $S_inR->($k); my $t = sqrt(2)**$k; my $f = ($s + $z/sqrt(2)) / $t; print "$k $s + $z f=$f\n"; } print "2+ 2*sqrt(2)=",2 + 2*sqrt(2),"\n"; # k odd print "3+3/2*sqrt(2)=",3+1.5*sqrt(2),"\n"; # k even } exit 0; } { # right outer boundary sub R_formula { my ($k) = @_; my $h = int($k/2); return ($k & 1 ? 10*2**$h - 2*$k - 6 # yes : 7*2**$h - 2*$k - 6); # yes if ($k & 1) { my $j = ($k+1)/2; return 5*2**$j - 4*$j - 4; # yes return 10*2**$h - 4*$h - 8; # yes return 2*2**$h + (2*$k-2)*(2**$h-1) - 4*($h-2)*2**$h - 8; # yes { my $r = 0; foreach my $i (1 .. $h-1) { # yes $r += $i * 2**$i; } return 2*2**$h + (2*$k-2)*(2**$h-1) - 4*$r; } { my $r = 0; foreach my $i (0 .. $h-1) { $r += (2*$k-2 - 4*$i) * 2**$i; } return 2*2**$h + $r } { my $r = 0; my $pow = 1; while ($k >= 3) { ### t: 2*$k-2 $r += (2*$k-2) * $pow; $pow *= 2; $k -= 2; } return $r + 2*$pow; } } else { my $h = $k/2; { return 7*2**$h - 4*$h - 6; # yes return (2*$k-1) * 2**$h - 2*$k + 2 - 4*(($h-1-1)*2**($h-1+1) + 2); } { # right[k] = 2k-2 + 2*right[k-2] termwise, yes my $r = 0; foreach my $i (0 .. $h-1) { $r += $i*2**$i; } return (2*$k-1) * 2**$h - 2*$k + 2 - 4*$r; } { # right[k] = 2k-2 + 2*right[k-2] termwise, yes my $r = 0; my $pow = 1; while ($k > 0) { $r += (2*$k-2) * $pow; $pow *= 2; $k -= 2; } return $r + $pow; } return ($h-2) *2**$h; } }; my ($Scum_recurrence, $Zcum_recurrence, $R_recurrence); $Scum_recurrence = sub { my ($k) = @_; if ($k == 0) { return 0; } if ($k == 1) { return 0; } return 2*$Scum_recurrence->($k-2) + $k-1; # yes return $Zcum_recurrence->($k-1); # yes }; $Zcum_recurrence = sub { my ($k) = @_; if ($k == 0) { return 0; } if ($k == 1) { return 1; } return 2*$Zcum_recurrence->($k-2) + $k; # yes return 2*$Scum_recurrence->($k-1) + $k; # yes }; $R_recurrence = sub { my ($k) = @_; if ($k == 0) { return 1; } if ($k == 1) { return 2; } return 2*$R_recurrence->($k-2) + 2*$k-2; }; for (my $k = 0; $k < 15; $k++) { print R_formula($k),", "; } print "\n"; require MyOEIS; my $path = Math::PlanePath::CCurve->new; foreach my $k (0 .. 17) { my $n_end = 2**$k; my $p = MyOEIS::path_boundary_length($path, $n_end, side => 'right'); # my $b = $B->($k); my $srec = $Scum_recurrence->($k); my $zrec = $Zcum_recurrence->($k); my $rszrec = $srec + $zrec + 1; my $rrec = $R_recurrence->($k); # my $t = $T->($k); # my $u = $U->($k); # my $u2 = $U2->($k); # my $u_lr = $U_from_LsubR->($k); # my $v = $V->($k); my ($s, $z) = path_S_and_Z($path, $n_end); my $r = $s + $z + 1; my $rformula = R_formula($k); my $drformula = $r - $rformula; # next unless $k & 1; print "$k $p $s $z $r $srec $zrec $rszrec $rrec $rformula small by=$drformula\n"; } exit 0; sub path_S_and_Z { my ($path, $n_end) = @_; ### path_S_and_Z(): $n_end my $s = 0; my $z = 0; my $x = 1; my $y = 0; my ($dx,$dy) = (1,0); my ($target_x,$target_y) = $path->n_to_xy($n_end); until ($x == $target_x && $y == $target_y) { ### at: "$x, $y $dx,$dy" ($dx,$dy) = ($dy,-$dx); # rotate -90 if (path_xy_is_visited_within ($path, $x+$dx,$y+$dy, $n_end)) { $z++; } else { ($dx,$dy) = (-$dy,$dx); # rotate +90 if (path_xy_is_visited_within ($path, $x+$dx,$y+$dy, $n_end)) { $s++; } else { ($dx,$dy) = (-$dy,$dx); # rotate +90 $z++; path_xy_is_visited_within ($path, $x+$dx,$y+$dy, $n_end) or die; } } $x += $dx; $y += $dy; } return ($s, $z); } sub path_xy_is_visited_within { my ($path, $x,$y, $n_end) = @_; my @n_list = $path->xy_to_n_list($x,$y); foreach my $n (@n_list) { if ($n <= $n_end) { return 1; } } return 0; } } { # diagonal N endpoints search my @values; foreach my $k (0 .. 10) { my ($n1, $n2) = diagonal_4k_axis_n_ends($k); my ($x1,$y1) = $path->n_to_xy ($n1); my ($x2,$y2) = $path->n_to_xy ($n2); foreach (1 .. $k) { ($x1,$y1) = ($y1,-$x1); # rotate -90 ($x2,$y2) = ($y2,-$x2); # rotate -90 } push @values, $n2; printf "$n1 xy=$x1,$y1 $n2 xy=$x2,$y2 %b %b\n", $n1, $n2; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; sub diagonal_4k_axis_n_ends { my ($k) = @_; if ($k == 0) { return (0, 2); } my $start = 2*(4**($k-1)-1)/3; return ($start, 2*4**$k - $start); } } { # diagonal N endpoints search my @values; foreach my $k (0 .. 10) { my $n_limit = 2*4**$k; my $dx = 1; my $dy = 1; foreach (-1 .. $k) { ($dx,$dy) = (-$dy,$dx); # rotate +90 } my $x = 0; my $y = 0; foreach my $i (0 .. $n_limit/2) { my $try_x = $i*$dx; my $try_y = $i*$dy; if (my @n_list = $path->xy_to_n_list($try_x,$try_y)) { if ($n_list[0] <= $n_limit) { $x = $try_x; $y = $try_y; } } } # my $x = (4**$k-1)/3; # my $y = $x; # foreach (0 .. $k) { # # ($x,$y) = (-$y,$x); # rotate +90 # ($x,$y) = (-$x,-$y); # rotate 180 # } # $x = 2**($k)-1 - $x; # $y = $x; my @n_list = $path->xy_to_n_list($x,$y); push @values, $n_list[0]; my $n_list_str = join(',',@n_list); printf "$k $n_limit xy=$x,$y $n_list_str %b\n", $n_list[0]; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; } { # axis N endpoints my @values; foreach my $k (0 .. 10) { my ($n1, $n2) = width_4k_axis_n_ends($k); my ($x1,$y1) = $path->n_to_xy ($n1); my ($x2,$y2) = $path->n_to_xy ($n2); foreach (1 .. $k) { ($x1,$y1) = ($y1,-$x1); # rotate -90 ($x2,$y2) = ($y2,-$x2); # rotate -90 } push @values, $n2; printf "$n1 xy=$x1,$y1 $n2 xy=$x2,$y2 %b %b\n", $n1, $n2; } require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values); exit 0; # 0,1, 5,21, 85,... binary 1, 101, 10101, ... # (4^(k-1)-1)/3 for k>=1 # = (4^k-4)/12 # # 1,4,15,59,235,... # binary 1, 100, 1111, 111011, 11101011, 1110101011 # A199210 (11*4^n+1)/3. # 4^k - (4^k-4)/12 # = (12*4^k - 4^k + 4)/12 # = (11*4^k + 4)/12 # = (11*4^(k-1) + 1)/3 # sub width_4k_axis_n_ends { my ($k) = @_; if ($k == 0) { return (0, 1); } my $start = (4**($k-1)-1)/3; return ($start, 4**$k - $start); } } { # X,Y extents at 4^k my $path = Math::PlanePath::CCurve->new; my $x_min = 0; my $y_min = 0; my $x_max = 0; my $y_max = 0; my $target = 2; my @w_max; my @w_min; my @h_max; my @h_min; my $rot = 3; foreach my $n (0 .. 2**16) { my ($x,$y) = $path->n_to_xy ($n); $x_min = min($x+$y,$x_min); $x_max = max($x+$y,$x_max); $y_min = min($y-$x,$y_min); $y_max = max($y-$x,$y_max); if ($n == $target) { my $w_min = $x_min; my $w_max = $x_max; my $h_min = $y_min; my $h_max = $y_max; foreach (1 .. $rot) { ($w_max,$w_min, $h_max,$h_min) = ($h_max,$h_min, -$w_min,-$w_max); } push @w_min, $w_min; push @h_min, $h_min; push @w_max, $w_max; push @h_max, $y_max; if (1) { printf "xy=%9b,%9b w -%9b to %9b h -%9b to %9b\n", abs($x),abs($y), abs($w_min),$w_max, abs($h_min),$h_max; } print "xy=$x,$y w $w_min to $w_max h $h_min to $h_max\n"; # print "xy=$x,$y x $x_min to $x_max y $y_min to $y_max\n\n"; $target *= 4; $rot++; } } require Math::OEIS::Grep; # Math::OEIS::Grep->search(array => \@w_min, name => "w_min"); # Math::OEIS::Grep->search(array => \@h_min); # Math::OEIS::Grep->search(array => \@w_max); shift @h_max; shift @h_max; Math::OEIS::Grep->search(array => \@h_max, name => "h_max"); exit 0; } { # X,Y to N by dividing # # *--* # | # * * 0,1 1,1 # | # *==* -1,0 0,0 1,0 # | # * * 0,-1 1,-1 # | # *--* # my $path = Math::PlanePath::CCurve->new; my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); my @dir4_to_ds = ( 1, 1, -1, -1); # ds = dx+dy my @dir4_to_dd = (-1, 1, 1, -1); # ds = dy-dx my $n_at = 1727; my ($x,$y) = $path->n_to_xy ($n_at); print "n=$n_at $x,$y\n"; my @n_list; my $n_list_str = ''; foreach my $anti (0) { foreach my $dir (0, 1, 2, 3) { print "dir=$dir anti=$anti\n"; my $dx = $dir4_to_dx[$dir]; my $dy = $dir4_to_dy[$dir]; my $arm = 0; my ($x,$y) = ($x,$y); my $s = $x + $y; my $d = $y - $x; my $ds = $dir4_to_ds[$dir]; my $dd = $dir4_to_dd[$dir]; my @nbits; for (;;) { my $nbits = join('',reverse @nbits); print "$x,$y bit=",$s%2," $nbits\n"; if ($s >= -1 && $s <= 1 && $d >= -1 && $d <= 1) { # five final positions # . 0,1 . ds,dd # | # -1,0--0,0--1,0 # | # . 0,-1 . # if ($s == $ds && $d == $dd) { push @nbits, 1; $s -= $ds; $d -= $dd; } if ($s==0 && $d==0) { my $n = digit_join_lowtohigh(\@nbits, 2, 0); my $nbits = join('',reverse @nbits); print "n=$nbits = $n\n"; push @n_list, $n; $n_list_str .= "${n}[dir=$dir,anti=$anti], "; last; } $arm += dxdy_to_dir4($x,$y); print "not found, arm=$arm\n"; last; } my $bit = $s % 2; push @nbits, $bit; if ($bit) { # if (($x == 0 && ($y == 1 || $y == -1)) # || ($y == 0 && ($x == 1 || $x == -1))) { # if ($x != $dx || $y != $dy) { $x -= $dx; $y -= $dy; # $s -= ($dx + $dy); # $d -= ($dy - $dx); $s -= $ds; $d -= $dd; ($dx,$dy) = ($dy,-$dx); # rotate -90 ($ds,$dd) = ($dd,-$ds); # rotate -90 $arm++; } # undo expand on right, normal curl anti-clockwise: # divide i+1 = mul (i-1)/(i^2 - 1^2) # = mul (i-1)/-2 # is (i*y + x) * (i-1)/-2 # x = (-x - y)/-2 = (x + y)/2 # y = (-y + x)/-2 = (y - x)/2 # # undo expand on left, curl clockwise: # divide 1-i = mul (1+i)/(1 - i^2) # = mul (1+i)/2 # is (i*y + x) * (i+1)/2 # x = (x - y)/2 # y = (y + x)/2 # ### assert: (($x+$y)%2)==0 ($x,$y) = ($anti ? ($d/-2, $s/2) : ($s/2, $d/2)); ($s,$d) = (($s + $d)/2, ($d - $s)/2); last if @nbits > 20; } print "\n"; } } print "$n_list_str\n"; print join(', ', @n_list),"\n"; @n_list = sort {$a<=>$b} @n_list; print join(', ', @n_list),"\n"; foreach my $n (@n_list) { my $count = count_1_bits($n) % 4; printf "%b %d\n", $n, $count; } exit 0; sub dxdy_to_dir4 { my ($dx, $dy) = @_; if ($dx > 0) { return 0; } # east if ($dx < 0) { return 2; } # west if ($dy > 0) { return 1; } # north if ($dy < 0) { return 3; } # south } # S=X+Y S = S # D=Y-X Y = (S+D)/2 # # S=X+Y # X=S-Y # # newX,newY = (X+Y)/2, (Y-X)/2 # = (S-Y+Y)/2, (Y-(S-Y))/2 # = S/2, (Y-S+Y)/2 # = S/2, (2Y-S)/2 # newS = S/2 + (2Y-S)/2 # = Y # newY = (2Y-S)/2 } { # arms visits my $k = 3; my $path = Math::PlanePath::CCurve->new; my $n_hi = 256 * 8 ** $k; my $len = 2 ** $k; my @points; my $plot = sub { my ($x,$y, $n) = @_; ### plot: "$x,$y" if ($x == 0 && $y == 0) { $points[$x][$y] = '8'; } if ($x >= 0 && $x <= 2*$len && $y >= 0 && $y <= 2*$len) { # $points[$x][$y] .= sprintf '%d,', $n; $points[$x][$y] .= sprintf '*', $n; } }; foreach my $n (0 .. $n_hi) { my ($x,$y) = $path->n_to_xy ($n); foreach (0, 1) { foreach (1 .. 4) { ($x,$y) = (-$y,$x); # rotate +90 $plot->($x, $y, $n); } $y = -$y; } } foreach my $y (reverse 0 .. 2*$len) { printf "%2d: ", $y; foreach my $x (0 .. 2*$len) { printf ' %4s', $points[$x][$y] // '-'; } print "\n"; } printf " "; foreach my $x (0 .. 2*$len) { printf ' %4s', $x; } print "\n"; exit 0; } { # quad point visits by tiling # *------*-----* # | | # N=4^k N=0 # # 4 inward square, 4 outward square my $k = 3; my $path = Math::PlanePath::CCurve->new; my $len = 2 ** $k; my $rot = (2 - $k) % 4; ### $rot my @points; my $plot = sub { my ($x,$y, $n) = @_; ### plot: "$x,$y" if ($x >= 0 && $x <= 2*$len && $y >= 0 && $y <= 2*$len) { # $points[$x][$y] .= sprintf '%d,', $n; $points[$x][$y] .= sprintf '*', $n; } }; foreach my $n (0 .. 4**$k-1) { my ($x,$y) = $path->n_to_xy ($n); ### at: "$x,$y n=$n" foreach (1 .. $rot) { ($x,$y) = (-$y,$x); # rotate +90 } ### rotate to: "$x,$y" $x += $len; ### X shift to: "$x,$y" foreach my $x_offset (0, $len, # -$len, ) { foreach my $y_offset (0, $len, # -$len, ) { ### horiz: "$x,$y" $plot->($x+$x_offset, $y+$y_offset, $n); { my ($x,$y) = (-$x,-$y); # rotate 180 $x += $len; ### rotated: "$x,$y" $plot->($x+$x_offset,$y+$y_offset, $n); } my ($x,$y) = (-$y,$x); # rotate +90 # ### vert: "$x,$y" $plot->($x+$x_offset,$y+$y_offset, $n); { my ($x,$y) = (-$x,-$y); # rotate 180 $y += $len; # ### rotated: "$x,$y" $plot->($x+$x_offset,$y+$y_offset, $n); } } } } foreach my $y (reverse 0 .. 2*$len) { printf "%2d: ", $y; foreach my $x (0 .. 2*$len) { printf ' %4s', $points[$x][$y] // '-'; } print "\n"; } exit 0; } { # repeat points my $path = Math::PlanePath::CCurve->new; my %seen; my @first; foreach my $n (0 .. 2**16 - 1) { my ($x, $y) = $path->n_to_xy ($n); my $xy = "$x,$y"; my $count = ++$seen{$xy}; if (! $first[$count]) { $first[$count] = $xy; printf "count=%d first N=%d %b\n", $count, $n,$n; } } ### @first foreach my $xy (@first) { $xy or next; my ($x,$y) = split /,/, $xy; my @n_list = $path->xy_to_n_list($x,$y); print "$xy N=",join(', ',@n_list),"\n"; } my @count; while (my ($key,$visits) = each %seen) { $count[$visits]++; if ($visits > 4) { print "$key $visits\n"; } } ### @count exit 0; } { # repeat edges my $path = Math::PlanePath::CCurve->new; my ($prev_x,$prev_y) = $path->n_to_xy (0); my %seen; foreach my $n (1 .. 2**24 - 1) { my ($x, $y) = $path->n_to_xy ($n); my $min_x = min($x,$prev_x); my $min_y = min($y,$prev_y); my $max_x = max($x,$prev_x); my $max_y = max($y,$prev_y); my $xy = "$min_x,$min_y--$max_x,$max_y"; my $count = ++$seen{$xy}; if ($count > 2) { printf "count=%d third N=%d %b\n", $count, $n,$n; } $prev_x = $x; $prev_y = $y; } exit 0; } { # A047838 1, 3, 7, 11, 17, 23, 31, 39, 49, 59, 71, 83, 97, 111, 127, 143, # A080827 1, 3, 5, 9, 13, 19, 25, 33, 41, 51, 61, 73, 85, 99, 113, 129, require Image::Base::Text; my $width = 60; my $height = 30; my $w2 = int(($width+1)/2); my $h2 = int($height/2); my $image = Image::Base::Text->new (-width => $width, -height => $height); my $x = $w2; my $y = $h2; my $dx = 1; my $dy = 0; foreach my $i (2 .. 102) { $image->xy($x,$y,'*'); if ($dx) { $x += $dx; $image->xy($x,$y,'-'); $x += $dx; $image->xy($x,$y,'-'); $x += $dx; } else { $y += $dy; $image->xy($x,$y,'|'); $y += $dy; } my $value = A080827_pred($i); if (! $value) { if ($i & 1) { ($dx,$dy) = ($dy,-$dx); } else { ($dx,$dy) = (-$dy,$dx); } } } $image->save('/dev/stdout'); exit 0; } { # _rect_to_level() require Math::PlanePath::CCurve; foreach my $x (0 .. 16) { my ($len,$level) = Math::PlanePath::CCurve::_rect_to_level(0,0,$x,0); $len = $len*$len-1; print "$x $len $level\n"; } foreach my $x (0 .. 16) { my ($len,$level) = Math::PlanePath::CCurve::_rect_to_level(0,0,0,$x); $len = $len*$len-1; print "$x $len $level\n"; } foreach my $x (0 .. 16) { my ($len,$level) = Math::PlanePath::CCurve::_rect_to_level(0,0,-$x,0); $len = $len*$len-1; print "$x $len $level\n"; } foreach my $x (0 .. 16) { my ($len,$level) = Math::PlanePath::CCurve::_rect_to_level(0,0,0,-$x); $len = $len*$len-1; print "$x $len $level\n"; } exit 0; } __END__ w[ 0] = 0 w[ 1] - (w[1020]/2 + w[2080]/2) = 0 w[ 2] = w[1000]/2/2/2/2 w[ 4] - (w[ 840]/2 + w[ 408]/2) = 0 w[ 6] - (w[4840]/2 + w[ 408]/2) = 0 w[ 7] - (w[5860]/2 + w[2488]/2) = 0 w[ 8] - (w[ 104]/2 + w[4040]/2) = 0 w[ 10] = w[2000]/2/2/2/2 w[ 18] - (w[ 104]/2 + w[4840]/2) = 0 w[ 19] - (w[1124]/2 + w[68C0]/2) = 0 w[ 20] = w[2000]/2 w[ 22] = w[6000]/2 w[ 23] - (w[7020]/2 + w[2080]/2) = 0 w[ 40] - (w[ 202]/2 + w[ 210]/2) = 0 w[ 80] = w[1000]/2 w[ 90] - (w[1800]/2) = 0 w[ 91] - (w[1020]/2 + w[3880]/2) = 0 w[ 100] = w[1000]/2/2 w[ 104] - (w[ 8C0]/2 + w[ 408]/2) = 0 w[ 105] - (w[18E0]/2 + w[2488]/2) = 0 w[ 11C] - (w[ 9C4]/2 + w[4C48]/2) = 0 w[ 11D] - (w[19E4]/2 + w[6CC8]/2) = 0 w[ 120] = w[2080]/2 w[ 121] - (w[30A0]/2 + w[2080]/2) = 0 w[ 126] - (w[68C0]/2 + w[ 408]/2) = 0 w[ 127] - (w[78E0]/2 + w[2488]/2) = 0 w[ 194] - (w[ 8C0]/2 + w[1C08]/2) = 0 w[ 195] - (w[18E0]/2 + w[3C88]/2) = 0 w[ 200] - (w[2000]/2/2/2/2/2 + w[1000]/2/2/2/2/2) = 0 w[ 202] - (w[1020]/2/2/2/2 + w[1000]/2/2/2/2/2) = 0 w[ 210] - (w[2000]/2/2/2/2/2 + w[4400]/2/2) = 0 w[ 212] - (w[1020]/2/2/2/2 + w[4400]/2/2) = 0 w[ 213] - (w[5030]/2 + w[2882]/2) = 0 w[ 310] - (w[ 90]/2 + w[4400]/2/2) = 0 w[ 311] - (w[10B0]/2 + w[2882]/2) = 0 w[ 316] - (w[48D0]/2 + w[ C0A]/2) = 0 w[ 317] - (w[58F0]/2 + w[2C8A]/2) = 0 w[ 400] = w[2000]/2/2 w[ 408] - (w[ 104]/2 + w[4060]/2) = 0 w[ 409] - (w[1124]/2 + w[60E0]/2) = 0 w[ 40E] - (w[4944]/2 + w[4468]/2) = 0 w[ 40F] - (w[5964]/2 + w[64E8]/2) = 0 w[ 42A] - (w[6104]/2 + w[4060]/2) = 0 w[ 42B] - (w[7124]/2 + w[60E0]/2) = 0 w[ 480] = w[1020]/2 w[ 481] - (w[1020]/2 + w[30A0]/2) = 0 w[ 498] - (w[ 104]/2 + w[5860]/2) = 0 w[ 499] - (w[1124]/2 + w[78E0]/2) = 0 w[ 50C] - (w[ 9C4]/2 + w[4468]/2) = 0 w[ 50D] - (w[19E4]/2 + w[64E8]/2) = 0 w[ 528] - (w[2184]/2 + w[4060]/2) = 0 w[ 529] - (w[31A4]/2 + w[60E0]/2) = 0 w[ 52E] - (w[69C4]/2 + w[4468]/2) = 0 w[ 52F] - (w[79E4]/2 + w[64E8]/2) = 0 w[ 584] - (w[ 8C0]/2 + w[1428]/2) = 0 w[ 585] - (w[18E0]/2 + w[34A8]/2) = 0 w[ 59C] - (w[ 9C4]/2 + w[5C68]/2) = 0 w[ 59D] - (w[19E4]/2 + w[7CE8]/2) = 0 w[ 602] - (w[1020]/2/2/2/2 + w[6000]/2/2) = 0 w[ 603] - (w[5030]/2 + w[20A2]/2) = 0 w[ 61A] - (w[4114]/2 + w[4862]/2) = 0 w[ 61B] - (w[5134]/2 + w[68E2]/2) = 0 w[ 706] - (w[48D0]/2 + w[ 42A]/2) = 0 w[ 707] - (w[58F0]/2 + w[24AA]/2) = 0 w[ 718] - (w[ 194]/2 + w[4862]/2) = 0 w[ 719] - (w[11B4]/2 + w[68E2]/2) = 0 w[ 71E] - (w[49D4]/2 + w[4C6A]/2) = 0 w[ 71F] - (w[59F4]/2 + w[6CEA]/2) = 0 w[ 800] = w[2000]/2/2/2 w[ 802] = w[4400]/2 w[ 803] - (w[5420]/2 + w[2080]/2) = 0 w[ 840] - (w[ 602]/2 + w[ 210]/2) = 0 w[ 848] - (w[ 706]/2 + w[4250]/2) = 0 w[ 849] - (w[1726]/2 + w[62D0]/2) = 0 w[ 8C0] - (w[ 602]/2 + w[1210]/2) = 0 w[ 8C1] - (w[1622]/2 + w[3290]/2) = 0 w[ 8D8] - (w[ 706]/2 + w[5A50]/2) = 0 w[ 8D9] - (w[1726]/2 + w[7AD0]/2) = 0 w[ 900] = w[1020]/2/2 w[ 901] - (w[14A0]/2 + w[2080]/2) = 0 w[ 922] = w[6480]/2 w[ 923] - (w[74A0]/2 + w[2080]/2) = 0 w[ 94C] - (w[ FC6]/2 + w[4658]/2) = 0 w[ 94D] - (w[1FE6]/2 + w[66D8]/2) = 0 w[ 9C4] - (w[ EC2]/2 + w[1618]/2) = 0 w[ 9C5] - (w[1EE2]/2 + w[3698]/2) = 0 w[ 9DC] - (w[ FC6]/2 + w[5E58]/2) = 0 w[ 9DD] - (w[1FE6]/2 + w[7ED8]/2) = 0 w[ A42] - (w[4612]/2 + w[ 212]/2) = 0 w[ A43] - (w[5632]/2 + w[2292]/2) = 0 w[ A5A] - (w[4716]/2 + w[4A52]/2) = 0 w[ A5B] - (w[5736]/2 + w[6AD2]/2) = 0 w[ B12] - (w[4490]/2 + w[4400]/2/2) = 0 w[ B13] - (w[54B0]/2 + w[2882]/2) = 0 w[ B46] - (w[4ED2]/2 + w[ 61A]/2) = 0 w[ B47] - (w[5EF2]/2 + w[269A]/2) = 0 w[ B5E] - (w[4FD6]/2 + w[4E5A]/2) = 0 w[ B5F] - (w[5FF6]/2 + w[6EDA]/2) = 0 w[ C0A] - (w[4504]/2 + w[4060]/2) = 0 w[ C0B] - (w[5524]/2 + w[60E0]/2) = 0 w[ D08] - (w[ 584]/2 + w[4060]/2) = 0 w[ D09] - (w[15A4]/2 + w[60E0]/2) = 0 w[ D2A] - (w[6584]/2 + w[4060]/2) = 0 w[ D2B] - (w[75A4]/2 + w[60E0]/2) = 0 w[ EC2] - (w[4612]/2 + w[1232]/2) = 0 w[ EC3] - (w[5632]/2 + w[32B2]/2) = 0 w[ EDA] - (w[4716]/2 + w[5A72]/2) = 0 w[ EDB] - (w[5736]/2 + w[7AF2]/2) = 0 w[ F1A] - (w[4594]/2 + w[4862]/2) = 0 w[ F1B] - (w[55B4]/2 + w[68E2]/2) = 0 w[ FC6] - (w[4ED2]/2 + w[163A]/2) = 0 w[ FC7] - (w[5EF2]/2 + w[36BA]/2) = 0 w[ FDE] - (w[4FD6]/2 + w[5E7A]/2) = 0 w[ FDF] - (w[5FF6]/2 + w[7EFA]/2) = 0 w[1000] - (w[ 8]/2 + w[ 1]/2) = 0 w[1004] - (w[ 848]/2 + w[ 409]/2) = 0 w[1005] - (w[1868]/2 + w[2489]/2) = 0 w[1020] - (w[2008]/2 + w[ 1]/2) = 0 w[1021] - (w[3028]/2 + w[2081]/2) = 0 w[1038] - (w[210C]/2 + w[4841]/2) = 0 w[1039] - (w[312C]/2 + w[68C1]/2) = 0 w[10B0] - (w[2008]/2 + w[1801]/2) = 0 w[10B1] - (w[3028]/2 + w[3881]/2) = 0 w[1124] - (w[28C8]/2 + w[ 409]/2) = 0 w[1125] - (w[38E8]/2 + w[2489]/2) = 0 w[113C] - (w[29CC]/2 + w[4C49]/2) = 0 w[113D] - (w[39EC]/2 + w[6CC9]/2) = 0 w[11B4] - (w[28C8]/2 + w[1C09]/2) = 0 w[11B5] - (w[38E8]/2 + w[3C89]/2) = 0 w[1210] - (w[ 18]/2 + w[ 803]/2) = 0 w[1211] - (w[1038]/2 + w[2883]/2) = 0 w[1232] - (w[6018]/2 + w[ 803]/2) = 0 w[1233] - (w[7038]/2 + w[2883]/2) = 0 w[1314] - (w[ 8D8]/2 + w[ C0B]/2) = 0 w[1315] - (w[18F8]/2 + w[2C8B]/2) = 0 w[1336] - (w[68D8]/2 + w[ C0B]/2) = 0 w[1337] - (w[78F8]/2 + w[2C8B]/2) = 0 w[140C] - (w[ 94C]/2 + w[4469]/2) = 0 w[140D] - (w[196C]/2 + w[64E9]/2) = 0 w[1428] - (w[210C]/2 + w[4061]/2) = 0 w[1429] - (w[312C]/2 + w[60E1]/2) = 0 w[14A0] - (w[2008]/2 + w[1021]/2) = 0 w[14A1] - (w[3028]/2 + w[30A1]/2) = 0 w[14B8] - (w[210C]/2 + w[5861]/2) = 0 w[14B9] - (w[312C]/2 + w[78E1]/2) = 0 w[152C] - (w[29CC]/2 + w[4469]/2) = 0 w[152D] - (w[39EC]/2 + w[64E9]/2) = 0 w[15A4] - (w[28C8]/2 + w[1429]/2) = 0 w[15A5] - (w[38E8]/2 + w[34A9]/2) = 0 w[15BC] - (w[29CC]/2 + w[5C69]/2) = 0 w[15BD] - (w[39EC]/2 + w[7CE9]/2) = 0 w[1618] - (w[ 11C]/2 + w[4863]/2) = 0 w[1619] - (w[113C]/2 + w[68E3]/2) = 0 w[1622] - (w[6018]/2 + w[ 23]/2) = 0 w[1623] - (w[7038]/2 + w[20A3]/2) = 0 w[163A] - (w[611C]/2 + w[4863]/2) = 0 w[163B] - (w[713C]/2 + w[68E3]/2) = 0 w[171C] - (w[ 9DC]/2 + w[4C6B]/2) = 0 w[171D] - (w[19FC]/2 + w[6CEB]/2) = 0 w[1726] - (w[68D8]/2 + w[ 42B]/2) = 0 w[1727] - (w[78F8]/2 + w[24AB]/2) = 0 w[173E] - (w[69DC]/2 + w[4C6B]/2) = 0 w[173F] - (w[79FC]/2 + w[6CEB]/2) = 0 w[1800] - (w[ 408]/2 + w[ 1]/2) = 0 w[1801] - (w[1428]/2 + w[2081]/2) = 0 w[1806] - (w[4C48]/2 + w[ 409]/2) = 0 w[1807] - (w[5C68]/2 + w[2489]/2) = 0 w[1868] - (w[270E]/2 + w[4251]/2) = 0 w[1869] - (w[372E]/2 + w[62D1]/2) = 0 w[18E0] - (w[260A]/2 + w[1211]/2) = 0 w[18E1] - (w[362A]/2 + w[3291]/2) = 0 w[18F8] - (w[270E]/2 + w[5A51]/2) = 0 w[18F9] - (w[372E]/2 + w[7AD1]/2) = 0 w[1920] - (w[2488]/2 + w[ 1]/2) = 0 w[1921] - (w[34A8]/2 + w[2081]/2) = 0 w[1926] - (w[6CC8]/2 + w[ 409]/2) = 0 w[1927] - (w[7CE8]/2 + w[2489]/2) = 0 w[196C] - (w[2FCE]/2 + w[4659]/2) = 0 w[196D] - (w[3FEE]/2 + w[66D9]/2) = 0 w[19E4] - (w[2ECA]/2 + w[1619]/2) = 0 w[19E5] - (w[3EEA]/2 + w[3699]/2) = 0 w[19FC] - (w[2FCE]/2 + w[5E59]/2) = 0 w[19FD] - (w[3FEE]/2 + w[7ED9]/2) = 0 w[1A62] - (w[661A]/2 + w[ 213]/2) = 0 w[1A63] - (w[763A]/2 + w[2293]/2) = 0 w[1A7A] - (w[671E]/2 + w[4A53]/2) = 0 w[1A7B] - (w[773E]/2 + w[6AD3]/2) = 0 w[1B10] - (w[ 498]/2 + w[ 803]/2) = 0 w[1B11] - (w[14B8]/2 + w[2883]/2) = 0 w[1B16] - (w[4CD8]/2 + w[ C0B]/2) = 0 w[1B17] - (w[5CF8]/2 + w[2C8B]/2) = 0 w[1B32] - (w[6498]/2 + w[ 803]/2) = 0 w[1B33] - (w[74B8]/2 + w[2883]/2) = 0 w[1B66] - (w[6EDA]/2 + w[ 61B]/2) = 0 w[1B67] - (w[7EFA]/2 + w[269B]/2) = 0 w[1B7E] - (w[6FDE]/2 + w[4E5B]/2) = 0 w[1B7F] - (w[7FFE]/2 + w[6EDB]/2) = 0 w[1C08] - (w[ 50C]/2 + w[4061]/2) = 0 w[1C09] - (w[152C]/2 + w[60E1]/2) = 0 w[1C0E] - (w[4D4C]/2 + w[4469]/2) = 0 w[1C0F] - (w[5D6C]/2 + w[64E9]/2) = 0 w[1D28] - (w[258C]/2 + w[4061]/2) = 0 w[1D29] - (w[35AC]/2 + w[60E1]/2) = 0 w[1D2E] - (w[6DCC]/2 + w[4469]/2) = 0 w[1D2F] - (w[7DEC]/2 + w[64E9]/2) = 0 w[1EE2] - (w[661A]/2 + w[1233]/2) = 0 w[1EE3] - (w[763A]/2 + w[32B3]/2) = 0 w[1EFA] - (w[671E]/2 + w[5A73]/2) = 0 w[1EFB] - (w[773E]/2 + w[7AF3]/2) = 0 w[1F18] - (w[ 59C]/2 + w[4863]/2) = 0 w[1F19] - (w[15BC]/2 + w[68E3]/2) = 0 w[1F1E] - (w[4DDC]/2 + w[4C6B]/2) = 0 w[1F1F] - (w[5DFC]/2 + w[6CEB]/2) = 0 w[1F3A] - (w[659C]/2 + w[4863]/2) = 0 w[1F3B] - (w[75BC]/2 + w[68E3]/2) = 0 w[1FE6] - (w[6EDA]/2 + w[163B]/2) = 0 w[1FE7] - (w[7EFA]/2 + w[36BB]/2) = 0 w[1FFE] - (w[6FDE]/2 + w[5E7B]/2) = 0 w[1FFF] - (w[7FFE]/2 + w[7EFB]/2) = 0 w[2000] - (w[ 1]/2 + w[ 4]/2) = 0 w[2008] - (w[ 105]/2 + w[4044]/2) = 0 w[2009] - (w[1125]/2 + w[60C4]/2) = 0 w[2080] - (w[ 1]/2 + w[1004]/2) = 0 w[2081] - (w[1021]/2 + w[3084]/2) = 0 w[2086] - (w[4841]/2 + w[140C]/2) = 0 w[2087] - (w[5861]/2 + w[348C]/2) = 0 w[20A2] - (w[6001]/2 + w[1004]/2) = 0 w[20A3] - (w[7021]/2 + w[3084]/2) = 0 w[210C] - (w[ 9C5]/2 + w[444C]/2) = 0 w[210D] - (w[19E5]/2 + w[64CC]/2) = 0 w[2184] - (w[ 8C1]/2 + w[140C]/2) = 0 w[2185] - (w[18E1]/2 + w[348C]/2) = 0 w[21A0] - (w[2081]/2 + w[1004]/2) = 0 w[21A1] - (w[30A1]/2 + w[3084]/2) = 0 w[21A6] - (w[68C1]/2 + w[140C]/2) = 0 w[21A7] - (w[78E1]/2 + w[348C]/2) = 0 w[2202] - (w[4011]/2 + w[ 6]/2) = 0 w[2203] - (w[5031]/2 + w[2086]/2) = 0 w[2292] - (w[4011]/2 + w[1806]/2) = 0 w[2293] - (w[5031]/2 + w[3886]/2) = 0 w[2306] - (w[48D1]/2 + w[ 40E]/2) = 0 w[2307] - (w[58F1]/2 + w[248E]/2) = 0 w[2390] - (w[ 91]/2 + w[1806]/2) = 0 w[2391] - (w[10B1]/2 + w[3886]/2) = 0 w[2396] - (w[48D1]/2 + w[1C0E]/2) = 0 w[2397] - (w[58F1]/2 + w[3C8E]/2) = 0 w[2488] - (w[ 105]/2 + w[5064]/2) = 0 w[2489] - (w[1125]/2 + w[70E4]/2) = 0 w[248E] - (w[4945]/2 + w[546C]/2) = 0 w[248F] - (w[5965]/2 + w[74EC]/2) = 0 w[24AA] - (w[6105]/2 + w[5064]/2) = 0 w[24AB] - (w[7125]/2 + w[70E4]/2) = 0 w[258C] - (w[ 9C5]/2 + w[546C]/2) = 0 w[258D] - (w[19E5]/2 + w[74EC]/2) = 0 w[25A8] - (w[2185]/2 + w[5064]/2) = 0 w[25A9] - (w[31A5]/2 + w[70E4]/2) = 0 w[25AE] - (w[69C5]/2 + w[546C]/2) = 0 w[25AF] - (w[79E5]/2 + w[74EC]/2) = 0 w[260A] - (w[4115]/2 + w[4066]/2) = 0 w[260B] - (w[5135]/2 + w[60E6]/2) = 0 w[269A] - (w[4115]/2 + w[5866]/2) = 0 w[269B] - (w[5135]/2 + w[78E6]/2) = 0 w[270E] - (w[49D5]/2 + w[446E]/2) = 0 w[270F] - (w[59F5]/2 + w[64EE]/2) = 0 w[2798] - (w[ 195]/2 + w[5866]/2) = 0 w[2799] - (w[11B5]/2 + w[78E6]/2) = 0 w[279E] - (w[49D5]/2 + w[5C6E]/2) = 0 w[279F] - (w[59F5]/2 + w[7CEE]/2) = 0 w[2882] - (w[4401]/2 + w[1004]/2) = 0 w[2883] - (w[5421]/2 + w[3084]/2) = 0 w[28C8] - (w[ 707]/2 + w[5254]/2) = 0 w[28C9] - (w[1727]/2 + w[72D4]/2) = 0 w[2980] - (w[ 481]/2 + w[1004]/2) = 0 w[2981] - (w[14A1]/2 + w[3084]/2) = 0 w[29A2] - (w[6481]/2 + w[1004]/2) = 0 w[29A3] - (w[74A1]/2 + w[3084]/2) = 0 w[29CC] - (w[ FC7]/2 + w[565C]/2) = 0 w[29CD] - (w[1FE7]/2 + w[76DC]/2) = 0 w[2A4A] - (w[4717]/2 + w[4256]/2) = 0 w[2A4B] - (w[5737]/2 + w[62D6]/2) = 0 w[2ADA] - (w[4717]/2 + w[5A56]/2) = 0 w[2ADB] - (w[5737]/2 + w[7AD6]/2) = 0 w[2B4E] - (w[4FD7]/2 + w[465E]/2) = 0 w[2B4F] - (w[5FF7]/2 + w[66DE]/2) = 0 w[2B92] - (w[4491]/2 + w[1806]/2) = 0 w[2B93] - (w[54B1]/2 + w[3886]/2) = 0 w[2BDE] - (w[4FD7]/2 + w[5E5E]/2) = 0 w[2BDF] - (w[5FF7]/2 + w[7EDE]/2) = 0 w[2C8A] - (w[4505]/2 + w[5064]/2) = 0 w[2C8B] - (w[5525]/2 + w[70E4]/2) = 0 w[2D88] - (w[ 585]/2 + w[5064]/2) = 0 w[2D89] - (w[15A5]/2 + w[70E4]/2) = 0 w[2DAA] - (w[6585]/2 + w[5064]/2) = 0 w[2DAB] - (w[75A5]/2 + w[70E4]/2) = 0 w[2ECA] - (w[4717]/2 + w[5276]/2) = 0 w[2ECB] - (w[5737]/2 + w[72F6]/2) = 0 w[2F9A] - (w[4595]/2 + w[5866]/2) = 0 w[2F9B] - (w[55B5]/2 + w[78E6]/2) = 0 w[2FCE] - (w[4FD7]/2 + w[567E]/2) = 0 w[2FCF] - (w[5FF7]/2 + w[76FE]/2) = 0 w[3028] - (w[210D]/2 + w[4045]/2) = 0 w[3029] - (w[312D]/2 + w[60C5]/2) = 0 w[3084] - (w[ 849]/2 + w[140D]/2) = 0 w[3085] - (w[1869]/2 + w[348D]/2) = 0 w[30A0] - (w[2009]/2 + w[1005]/2) = 0 w[30A1] - (w[3029]/2 + w[3085]/2) = 0 w[312C] - (w[29CD]/2 + w[444D]/2) = 0 w[312D] - (w[39ED]/2 + w[64CD]/2) = 0 w[31A4] - (w[28C9]/2 + w[140D]/2) = 0 w[31A5] - (w[38E9]/2 + w[348D]/2) = 0 w[3222] - (w[6019]/2 + w[ 7]/2) = 0 w[3223] - (w[7039]/2 + w[2087]/2) = 0 w[3290] - (w[ 19]/2 + w[1807]/2) = 0 w[3291] - (w[1039]/2 + w[3887]/2) = 0 w[32B2] - (w[6019]/2 + w[1807]/2) = 0 w[32B3] - (w[7039]/2 + w[3887]/2) = 0 w[3326] - (w[68D9]/2 + w[ 40F]/2) = 0 w[3327] - (w[78F9]/2 + w[248F]/2) = 0 w[3394] - (w[ 8D9]/2 + w[1C0F]/2) = 0 w[3395] - (w[18F9]/2 + w[3C8F]/2) = 0 w[33B6] - (w[68D9]/2 + w[1C0F]/2) = 0 w[33B7] - (w[78F9]/2 + w[3C8F]/2) = 0 w[348C] - (w[ 94D]/2 + w[546D]/2) = 0 w[348D] - (w[196D]/2 + w[74ED]/2) = 0 w[34A8] - (w[210D]/2 + w[5065]/2) = 0 w[34A9] - (w[312D]/2 + w[70E5]/2) = 0 w[35AC] - (w[29CD]/2 + w[546D]/2) = 0 w[35AD] - (w[39ED]/2 + w[74ED]/2) = 0 w[362A] - (w[611D]/2 + w[4067]/2) = 0 w[362B] - (w[713D]/2 + w[60E7]/2) = 0 w[3698] - (w[ 11D]/2 + w[5867]/2) = 0 w[3699] - (w[113D]/2 + w[78E7]/2) = 0 w[36BA] - (w[611D]/2 + w[5867]/2) = 0 w[36BB] - (w[713D]/2 + w[78E7]/2) = 0 w[372E] - (w[69DD]/2 + w[446F]/2) = 0 w[372F] - (w[79FD]/2 + w[64EF]/2) = 0 w[379C] - (w[ 9DD]/2 + w[5C6F]/2) = 0 w[379D] - (w[19FD]/2 + w[7CEF]/2) = 0 w[37BE] - (w[69DD]/2 + w[5C6F]/2) = 0 w[37BF] - (w[79FD]/2 + w[7CEF]/2) = 0 w[3880] - (w[ 409]/2 + w[1005]/2) = 0 w[3881] - (w[1429]/2 + w[3085]/2) = 0 w[3886] - (w[4C49]/2 + w[140D]/2) = 0 w[3887] - (w[5C69]/2 + w[348D]/2) = 0 w[38E8] - (w[270F]/2 + w[5255]/2) = 0 w[38E9] - (w[372F]/2 + w[72D5]/2) = 0 w[39A0] - (w[2489]/2 + w[1005]/2) = 0 w[39A1] - (w[34A9]/2 + w[3085]/2) = 0 w[39A6] - (w[6CC9]/2 + w[140D]/2) = 0 w[39A7] - (w[7CE9]/2 + w[348D]/2) = 0 w[39EC] - (w[2FCF]/2 + w[565D]/2) = 0 w[39ED] - ((1/2 + ((1/2 + w[6FDF]/2)/2 + 1/2)/2)/2 + (w[1B7F]/2 + 1/2)/2) = 0 w[3A6A] - (w[671F]/2 + w[4257]/2) = 0 w[3A6B] - (w[773F]/2 + w[62D7]/2) = 0 w[3AFA] - (w[671F]/2 + w[5A57]/2) = 0 w[3AFB] - (w[773F]/2 + w[7AD7]/2) = 0 w[3B6E] - (w[6FDF]/2 + w[465F]/2) = 0 w[3B6F] - (1/2 + w[66DF]/2) = 0 w[3B90] - (w[ 499]/2 + w[1807]/2) = 0 w[3B91] - (w[14B9]/2 + w[3887]/2) = 0 w[3B96] - (w[4CD9]/2 + w[1C0F]/2) = 0 w[3B97] - (w[5CF9]/2 + w[3C8F]/2) = 0 w[3BB2] - (w[6499]/2 + w[1807]/2) = 0 w[3BB3] - (w[74B9]/2 + w[3887]/2) = 0 w[3BFE] - (w[6FDF]/2 + w[5E5F]/2) = 0 w[3BFF] = (1/2 + (w[5F7F]/2 + 1/2)/2) w[3C88] - (w[ 50D]/2 + w[5065]/2) = 0 w[3C89] - (w[152D]/2 + w[70E5]/2) = 0 w[3C8E] - (w[4D4D]/2 + w[546D]/2) = 0 w[3C8F] - (w[5D6D]/2 + w[74ED]/2) = 0 w[3DA8] - (w[258D]/2 + w[5065]/2) = 0 w[3DA9] - (w[35AD]/2 + w[70E5]/2) = 0 w[3DAE] - (w[6DCD]/2 + w[546D]/2) = 0 w[3DAF] - (w[7DED]/2 + w[74ED]/2) = 0 w[3EEA] - (w[671F]/2 + w[5277]/2) = 0 w[3EEB] - (w[773F]/2 + w[72F7]/2) = 0 w[3F98] - (w[ 59D]/2 + w[5867]/2) = 0 w[3F99] - (w[15BD]/2 + w[78E7]/2) = 0 w[3F9E] - (w[4DDD]/2 + w[5C6F]/2) = 0 w[3F9F] - (w[5DFD]/2 + w[7CEF]/2) = 0 w[3FBA] - (w[659D]/2 + w[5867]/2) = 0 w[3FBB] - (w[75BD]/2 + w[78E7]/2) = 0 w[3FEE] - (w[6FDF]/2 + w[567F]/2) = 0 w[3FEF] = (1/2 + ((1/2 + w[6FDF]/2)/2 + 1/2)/2) w[4000] = w[1000]/2/2/2 w[4010] = w[1020]/2/2/2 w[4011] - (w[1020]/2 + w[2980]/2) = 0 w[4040] - (w[ 202]/2 + w[ 310]/2) = 0 w[4044] - (w[ A42]/2 + w[ 718]/2) = 0 w[4045] - (w[1A62]/2 + w[2798]/2) = 0 w[4060] - (w[2202]/2 + w[ 310]/2) = 0 w[4061] - (w[3222]/2 + w[2390]/2) = 0 w[4066] - (w[6A42]/2 + w[ 718]/2) = 0 w[4067] - (w[7A62]/2 + w[2798]/2) = 0 w[4114] - (w[ 8C0]/2 + w[ D08]/2) = 0 w[4115] - (w[18E0]/2 + w[2D88]/2) = 0 w[4250] - (w[ 212]/2 + w[ B12]/2) = 0 w[4251] - (w[1232]/2 + w[2B92]/2) = 0 w[4256] - (w[4A52]/2 + w[ F1A]/2) = 0 w[4257] - (w[5A72]/2 + w[2F9A]/2) = 0 w[4370] - (w[2292]/2 + w[ B12]/2) = 0 w[4371] - (w[32B2]/2 + w[2B92]/2) = 0 w[4376] - (w[6AD2]/2 + w[ F1A]/2) = 0 w[4377] - (w[7AF2]/2 + w[2F9A]/2) = 0 w[4400] = w[2080]/2/2 w[4401] - (w[1020]/2 + w[21A0]/2) = 0 w[444C] - (w[ B46]/2 + w[4778]/2) = 0 w[444D] - (w[1B66]/2 + w[67F8]/2) = 0 w[4468] - (w[2306]/2 + w[4370]/2) = 0 w[4469] - (w[3326]/2 + w[63F0]/2) = 0 w[446E] - (w[6B46]/2 + w[4778]/2) = 0 w[446F] - (w[7B66]/2 + w[67F8]/2) = 0 w[4490] = w[1920]/2 w[4491] - (w[1020]/2 + w[39A0]/2) = 0 w[4504] - (w[ 8C0]/2 + w[ 528]/2) = 0 w[4505] - (w[18E0]/2 + w[25A8]/2) = 0 w[4594] - (w[ 8C0]/2 + w[1D28]/2) = 0 w[4595] - (w[18E0]/2 + w[3DA8]/2) = 0 w[4612] - (w[1020]/2/2/2/2 + w[6480]/2/2) = 0 w[4613] - (w[5030]/2 + w[29A2]/2) = 0 w[4658] - (w[ 316]/2 + w[4B72]/2) = 0 w[4659] - (w[1336]/2 + w[6BF2]/2) = 0 w[465E] - (w[4B56]/2 + w[4F7A]/2) = 0 w[465F] - (w[5B76]/2 + w[6FFA]/2) = 0 w[4716] - (w[48D0]/2 + w[ D2A]/2) = 0 w[4717] - (w[58F0]/2 + w[2DAA]/2) = 0 w[4778] - (w[2396]/2 + w[4B72]/2) = 0 w[4779] - (w[33B6]/2 + w[6BF2]/2) = 0 w[477E] - (w[6BD6]/2 + w[4F7A]/2) = 0 w[477F] - (w[7BF6]/2 + w[6FFA]/2) = 0 w[4840] - (w[ 602]/2 + w[ 310]/2) = 0 w[4841] - (w[1622]/2 + w[2390]/2) = 0 w[4862] - (w[6602]/2 + w[ 310]/2) = 0 w[4863] - (w[7622]/2 + w[2390]/2) = 0 w[48D0] - (w[ 602]/2 + w[1B10]/2) = 0 w[48D1] - (w[1622]/2 + w[3B90]/2) = 0 w[4944] - (w[ EC2]/2 + w[ 718]/2) = 0 w[4945] - (w[1EE2]/2 + w[2798]/2) = 0 w[4966] - (w[6EC2]/2 + w[ 718]/2) = 0 w[4967] - (w[7EE2]/2 + w[2798]/2) = 0 w[49D4] - (w[ EC2]/2 + w[1F18]/2) = 0 w[49D5] - (w[1EE2]/2 + w[3F98]/2) = 0 w[4A52] - (w[4612]/2 + w[ B12]/2) = 0 w[4A53] - (w[5632]/2 + w[2B92]/2) = 0 w[4B56] - (w[4ED2]/2 + w[ F1A]/2) = 0 w[4B57] - (w[5EF2]/2 + w[2F9A]/2) = 0 w[4B72] - (w[6692]/2 + w[ B12]/2) = 0 w[4B73] - (w[76B2]/2 + w[2B92]/2) = 0 w[4C48] - (w[ 706]/2 + w[4370]/2) = 0 w[4C49] - (w[1726]/2 + w[63F0]/2) = 0 w[4C6A] - (w[6706]/2 + w[4370]/2) = 0 w[4C6B] - (w[7726]/2 + w[63F0]/2) = 0 w[4CD8] - (w[ 706]/2 + w[5B70]/2) = 0 w[4CD9] - (w[1726]/2 + w[7BF0]/2) = 0 w[4D4C] - (w[ FC6]/2 + w[4778]/2) = 0 w[4D4D] - (w[1FE6]/2 + w[67F8]/2) = 0 w[4D6E] - (w[6FC6]/2 + w[4778]/2) = 0 w[4D6F] - (w[7FE6]/2 + w[67F8]/2) = 0 w[4DDC] - (w[ FC6]/2 + w[5F78]/2) = 0 w[4DDD] - (w[1FE6]/2 + w[7FF8]/2) = 0 w[4E5A] - (w[4716]/2 + w[4B72]/2) = 0 w[4E5B] - (w[5736]/2 + w[6BF2]/2) = 0 w[4ED2] - (w[4612]/2 + w[1B32]/2) = 0 w[4ED3] - (w[5632]/2 + w[3BB2]/2) = 0 w[4F5E] - (w[4FD6]/2 + w[4F7A]/2) = 0 w[4F5F] - (w[5FF6]/2 + w[6FFA]/2) = 0 w[4F7A] - (w[6796]/2 + w[4B72]/2) = 0 w[4F7B] - (w[77B6]/2 + w[6BF2]/2) = 0 w[4FD6] - (w[4ED2]/2 + w[1F3A]/2) = 0 w[4FD7] - (w[5EF2]/2 + w[3FBA]/2) = 0 w[5030] - (w[2008]/2 + w[ 901]/2) = 0 w[5031] - (w[3028]/2 + w[2981]/2) = 0 w[5064] - (w[2A4A]/2 + w[ 719]/2) = 0 w[5065] - (w[3A6A]/2 + w[2799]/2) = 0 w[5134] - (w[28C8]/2 + w[ D09]/2) = 0 w[5135] - (w[38E8]/2 + w[2D89]/2) = 0 w[5254] - (w[ A5A]/2 + w[ F1B]/2) = 0 w[5255] - (w[1A7A]/2 + w[2F9B]/2) = 0 w[5276] - (w[6A5A]/2 + w[ F1B]/2) = 0 w[5277] - (w[7A7A]/2 + w[2F9B]/2) = 0 w[5374] - (w[2ADA]/2 + w[ F1B]/2) = 0 w[5375] - (w[3AFA]/2 + w[2F9B]/2) = 0 w[5420] - (w[2008]/2 + w[ 121]/2) = 0 w[5421] - (w[3028]/2 + w[21A1]/2) = 0 w[546C] - (w[2B4E]/2 + w[4779]/2) = 0 w[546D] - (w[3B6E]/2 + w[67F9]/2) = 0 w[54B0] - (w[2008]/2 + w[1921]/2) = 0 w[54B1] - (w[3028]/2 + w[39A1]/2) = 0 w[5524] - (w[28C8]/2 + w[ 529]/2) = 0 w[5525] - (w[38E8]/2 + w[25A9]/2) = 0 w[55B4] - (w[28C8]/2 + w[1D29]/2) = 0 w[55B5] - (w[38E8]/2 + w[3DA9]/2) = 0 w[5632] - (w[6018]/2 + w[ 923]/2) = 0 w[5633] - (w[7038]/2 + w[29A3]/2) = 0 w[565C] - (w[ B5E]/2 + w[4F7B]/2) = 0 w[565D] - (w[1B7E]/2 + w[6FFB]/2) = 0 w[567E] - (w[6B5E]/2 + w[4F7B]/2) = 0 w[567F] - (w[7B7E]/2 + w[6FFB]/2) = 0 w[5736] - (w[68D8]/2 + w[ D2B]/2) = 0 w[5737] - (w[78F8]/2 + w[2DAB]/2) = 0 w[577C] - (w[2BDE]/2 + w[4F7B]/2) = 0 w[577D] - (w[3BFE]/2 + w[6FFB]/2) = 0 w[5860] - (w[260A]/2 + w[ 311]/2) = 0 w[5861] - (w[362A]/2 + w[2391]/2) = 0 w[5866] - (w[6E4A]/2 + w[ 719]/2) = 0 w[5867] - (w[7E6A]/2 + w[2799]/2) = 0 w[58F0] - (w[260A]/2 + w[1B11]/2) = 0 w[58F1] - (w[362A]/2 + w[3B91]/2) = 0 w[5964] - (w[2ECA]/2 + w[ 719]/2) = 0 w[5965] - (w[3EEA]/2 + w[2799]/2) = 0 w[59F4] - (w[2ECA]/2 + w[1F19]/2) = 0 w[59F5] - (w[3EEA]/2 + w[3F99]/2) = 0 w[5A50] - (w[ 61A]/2 + w[ B13]/2) = 0 w[5A51] - (w[163A]/2 + w[2B93]/2) = 0 w[5A56] - (w[4E5A]/2 + w[ F1B]/2) = 0 w[5A57] - (w[5E7A]/2 + w[2F9B]/2) = 0 w[5A72] - (w[661A]/2 + w[ B13]/2) = 0 w[5A73] - (w[763A]/2 + w[2B93]/2) = 0 w[5B54] - (w[ EDA]/2 + w[ F1B]/2) = 0 w[5B55] - (w[1EFA]/2 + w[2F9B]/2) = 0 w[5B70] - (w[269A]/2 + w[ B13]/2) = 0 w[5B71] - (w[36BA]/2 + w[2B93]/2) = 0 w[5B76] - (w[6EDA]/2 + w[ F1B]/2) = 0 w[5B77] - (w[7EFA]/2 + w[2F9B]/2) = 0 w[5C68] - (w[270E]/2 + w[4371]/2) = 0 w[5C69] - (w[372E]/2 + w[63F1]/2) = 0 w[5C6E] - (w[6F4E]/2 + w[4779]/2) = 0 w[5C6F] - (w[7F6E]/2 + w[67F9]/2) = 0 w[5CF8] - (w[270E]/2 + w[5B71]/2) = 0 w[5CF9] - (w[372E]/2 + w[7BF1]/2) = 0 w[5D6C] - (w[2FCE]/2 + w[4779]/2) = 0 w[5D6D] - (w[3FEE]/2 + w[67F9]/2) = 0 w[5DFC] - (w[2FCE]/2 + w[5F79]/2) = 0 w[5DFD] - (w[3FEE]/2 + w[7FF9]/2) = 0 w[5E58] - (w[ 71E]/2 + w[4B73]/2) = 0 w[5E59] - (w[173E]/2 + w[6BF3]/2) = 0 w[5E5E] - (w[4F5E]/2 + w[4F7B]/2) = 0 w[5E5F] - (w[5F7E]/2 + w[6FFB]/2) = 0 w[5E7A] - (w[671E]/2 + w[4B73]/2) = 0 w[5E7B] - (w[773E]/2 + w[6BF3]/2) = 0 w[5EF2] - (w[661A]/2 + w[1B33]/2) = 0 w[5EF3] - (w[763A]/2 + w[3BB3]/2) = 0 w[5F5C] - (w[ FDE]/2 + w[4F7B]/2) = 0 w[5F5D] - (w[1FFE]/2 + w[6FFB]/2) = 0 w[5F78] - (w[279E]/2 + w[4B73]/2) = 0 w[5F79] - (w[37BE]/2 + w[6BF3]/2) = 0 w[5F7E] - (w[6FDE]/2 + w[4F7B]/2) = 0 w[5F7F] - (w[7FFE]/2 + w[6FFB]/2) = 0 w[5FF6] - (w[6EDA]/2 + w[1F3B]/2) = 0 w[5FF7] - (w[7EFA]/2 + w[3FBB]/2) = 0 w[6000] - (w[ 1]/2 + w[ 104]/2) = 0 w[6001] - (w[1021]/2 + w[2184]/2) = 0 w[6018] - (w[ 105]/2 + w[4944]/2) = 0 w[6019] - (w[1125]/2 + w[69C4]/2) = 0 w[60C4] - (w[ A43]/2 + w[171C]/2) = 0 w[60C5] - (w[1A63]/2 + w[379C]/2) = 0 w[60E0] - (w[2203]/2 + w[1314]/2) = 0 w[60E1] - (w[3223]/2 + w[3394]/2) = 0 w[60E6] - (w[6A43]/2 + w[171C]/2) = 0 w[60E7] - (w[7A63]/2 + w[379C]/2) = 0 w[6104] - (w[ 8C1]/2 + w[ 50C]/2) = 0 w[6105] - (w[18E1]/2 + w[258C]/2) = 0 w[611C] - (w[ 9C5]/2 + w[4D4C]/2) = 0 w[611D] - (w[19E5]/2 + w[6DCC]/2) = 0 w[62D0] - (w[ 213]/2 + w[1B16]/2) = 0 w[62D1] - (w[1233]/2 + w[3B96]/2) = 0 w[62D6] - (w[4A53]/2 + w[1F1E]/2) = 0 w[62D7] - (w[5A73]/2 + w[3F9E]/2) = 0 w[63F0] - (w[2293]/2 + w[1B16]/2) = 0 w[63F1] - (w[32B3]/2 + w[3B96]/2) = 0 w[63F6] - (w[6AD3]/2 + w[1F1E]/2) = 0 w[63F7] - (w[7AF3]/2 + w[3F9E]/2) = 0 w[6480] - (w[ 1]/2 + w[1124]/2) = 0 w[6481] - (w[1021]/2 + w[31A4]/2) = 0 w[6498] - (w[ 105]/2 + w[5964]/2) = 0 w[6499] - (w[1125]/2 + w[79E4]/2) = 0 w[64CC] - (w[ B47]/2 + w[577C]/2) = 0 w[64CD] - (w[1B67]/2 + w[77FC]/2) = 0 w[64E8] - (w[2307]/2 + w[5374]/2) = 0 w[64E9] - (w[3327]/2 + w[73F4]/2) = 0 w[64EE] - (w[6B47]/2 + w[577C]/2) = 0 w[64EF] - (w[7B67]/2 + w[77FC]/2) = 0 w[6584] - (w[ 8C1]/2 + w[152C]/2) = 0 w[6585] - (w[18E1]/2 + w[35AC]/2) = 0 w[659C] - (w[ 9C5]/2 + w[5D6C]/2) = 0 w[659D] - (w[19E5]/2 + w[7DEC]/2) = 0 w[6602] - (w[4011]/2 + w[ 126]/2) = 0 w[6603] - (w[5031]/2 + w[21A6]/2) = 0 w[661A] - (w[4115]/2 + w[4966]/2) = 0 w[661B] - (w[5135]/2 + w[69E6]/2) = 0 w[6692] - (w[4011]/2 + w[1926]/2) = 0 w[6693] - (w[5031]/2 + w[39A6]/2) = 0 w[66D8] - (w[ 317]/2 + w[5B76]/2) = 0 w[66D9] - (w[1337]/2 + w[7BF6]/2) = 0 w[66DE] - (w[4B57]/2 + w[5F7E]/2) = 0 w[66DF] - (w[5B77]/2 + w[7FFE]/2) = 0 w[6706] - (w[48D1]/2 + w[ 52E]/2) = 0 w[6707] - (w[58F1]/2 + w[25AE]/2) = 0 w[671E] - (w[49D5]/2 + w[4D6E]/2) = 0 w[671F] - (w[59F5]/2 + w[6DEE]/2) = 0 w[6796] - (w[48D1]/2 + w[1D2E]/2) = 0 w[6797] - (w[58F1]/2 + w[3DAE]/2) = 0 w[67F8] - (w[2397]/2 + w[5B76]/2) = 0 w[67F9] - (w[33B7]/2 + w[7BF6]/2) = 0 w[67FE] - (w[6BD7]/2 + w[5F7E]/2) = 0 w[67FF] - (w[7BF7]/2 + w[7FFE]/2) = 0 w[68C0] - (w[ 603]/2 + w[1314]/2) = 0 w[68C1] - (w[1623]/2 + w[3394]/2) = 0 w[68D8] - (w[ 707]/2 + w[5B54]/2) = 0 w[68D9] - (w[1727]/2 + w[7BD4]/2) = 0 w[68E2] - (w[6603]/2 + w[1314]/2) = 0 w[68E3] - (w[7623]/2 + w[3394]/2) = 0 w[69C4] - (w[ EC3]/2 + w[171C]/2) = 0 w[69C5] - (w[1EE3]/2 + w[379C]/2) = 0 w[69DC] - (w[ FC7]/2 + w[5F5C]/2) = 0 w[69DD] - (w[1FE7]/2 + w[7FDC]/2) = 0 w[69E6] - (w[6EC3]/2 + w[171C]/2) = 0 w[69E7] - (w[7EE3]/2 + w[379C]/2) = 0 w[6A42] - (w[4613]/2 + w[ 316]/2) = 0 w[6A43] - (w[5633]/2 + w[2396]/2) = 0 w[6A5A] - (w[4717]/2 + w[4B56]/2) = 0 w[6A5B] - (w[5737]/2 + w[6BD6]/2) = 0 w[6AD2] - (w[4613]/2 + w[1B16]/2) = 0 w[6AD3] - (w[5633]/2 + w[3B96]/2) = 0 w[6B46] - (w[4ED3]/2 + w[ 71E]/2) = 0 w[6B47] - (w[5EF3]/2 + w[279E]/2) = 0 w[6B5E] - (w[4FD7]/2 + w[4F5E]/2) = 0 w[6B5F] - (w[5FF7]/2 + w[6FDE]/2) = 0 w[6BD6] - (w[4ED3]/2 + w[1F1E]/2) = 0 w[6BD7] - (w[5EF3]/2 + w[3F9E]/2) = 0 w[6BF2] - (w[6693]/2 + w[1B16]/2) = 0 w[6BF3] - (w[76B3]/2 + w[3B96]/2) = 0 w[6CC8] - (w[ 707]/2 + w[5374]/2) = 0 w[6CC9] - (w[1727]/2 + w[73F4]/2) = 0 w[6CEA] - (w[6707]/2 + w[5374]/2) = 0 w[6CEB] - (w[7727]/2 + w[73F4]/2) = 0 w[6DCC] - (w[ FC7]/2 + w[577C]/2) = 0 w[6DCD] - (w[1FE7]/2 + w[77FC]/2) = 0 w[6DEE] - (w[6FC7]/2 + w[577C]/2) = 0 w[6DEF] - (w[7FE7]/2 + w[77FC]/2) = 0 w[6E4A] - (w[4717]/2 + w[4376]/2) = 0 w[6E4B] - (w[5737]/2 + w[63F6]/2) = 0 w[6EC2] - (w[4613]/2 + w[1336]/2) = 0 w[6EC3] - (w[5633]/2 + w[33B6]/2) = 0 w[6EDA] - (w[4717]/2 + w[5B76]/2) = 0 w[6EDB] - (w[5737]/2 + w[7BF6]/2) = 0 w[6F4E] - (w[4FD7]/2 + w[477E]/2) = 0 w[6F4F] - (w[5FF7]/2 + w[67FE]/2) = 0 w[6FC6] - (w[4ED3]/2 + w[173E]/2) = 0 w[6FC7] - (w[5EF3]/2 + w[37BE]/2) = 0 w[6FDE] - (w[4FD7]/2 + w[5F7E]/2) = 0 w[6FDF] - (w[5FF7]/2 + w[7FFE]/2) = 0 w[6FFA] - (w[6797]/2 + w[5B76]/2) = 0 w[6FFB] - (w[77B7]/2 + w[7BF6]/2) = 0 w[7020] - (w[2009]/2 + w[ 105]/2) = 0 w[7021] - (w[3029]/2 + w[2185]/2) = 0 w[7038] - (w[210D]/2 + w[4945]/2) = 0 w[7039] - (w[312D]/2 + w[69C5]/2) = 0 w[70E4] - (w[2A4B]/2 + w[171D]/2) = 0 w[70E5] - (w[3A6B]/2 + w[379D]/2) = 0 w[7124] - (w[28C9]/2 + w[ 50D]/2) = 0 w[7125] - (w[38E9]/2 + w[258D]/2) = 0 w[713C] - (w[29CD]/2 + w[4D4D]/2) = 0 w[713D] - (w[39ED]/2 + w[6DCD]/2) = 0 w[72D4] - (w[ A5B]/2 + w[1F1F]/2) = 0 w[72D5] - (w[1A7B]/2 + w[3F9F]/2) = 0 w[72F6] - (w[6A5B]/2 + w[1F1F]/2) = 0 w[72F7] - (w[7A7B]/2 + w[3F9F]/2) = 0 w[73F4] - (w[2ADB]/2 + w[1F1F]/2) = 0 w[73F5] - (w[3AFB]/2 + w[3F9F]/2) = 0 w[74A0] - (w[2009]/2 + w[1125]/2) = 0 w[74A1] - (w[3029]/2 + w[31A5]/2) = 0 w[74B8] - (w[210D]/2 + w[5965]/2) = 0 w[74B9] - (w[312D]/2 + w[79E5]/2) = 0 w[74EC] - (w[2B4F]/2 + w[577D]/2) = 0 w[74ED] - (w[3B6F]/2 + ((1/2 + (w[5F7F]/2 + 1/2)/2)/2 + 1/2)/2) = 0 w[75A4] - (w[28C9]/2 + w[152D]/2) = 0 w[75A5] - (w[38E9]/2 + w[35AD]/2) = 0 w[75BC] - (w[29CD]/2 + w[5D6D]/2) = 0 w[75BD] - (w[39ED]/2 + w[7DED]/2) = 0 w[7622] - (w[6019]/2 + w[ 127]/2) = 0 w[7623] - (w[7039]/2 + w[21A7]/2) = 0 w[763A] - (w[611D]/2 + w[4967]/2) = 0 w[763B] - (w[713D]/2 + w[69E7]/2) = 0 w[76B2] - (w[6019]/2 + w[1927]/2) = 0 w[76B3] - (w[7039]/2 + w[39A7]/2) = 0 w[76DC] - (w[ B5F]/2 + w[5F7F]/2) = 0 w[76DD] = (w[1B7F]/2 + 1/2) w[76FE] - (w[6B5F]/2 + w[5F7F]/2) = 0 w[76FF] = ((1/2 + w[6FDF]/2)/2 + 1/2) w[7726] - (w[68D9]/2 + w[ 52F]/2) = 0 w[7727] - (w[78F9]/2 + w[25AF]/2) = 0 w[773E] - (w[69DD]/2 + w[4D6F]/2) = 0 w[773F] - (w[79FD]/2 + w[6DEF]/2) = 0 w[77B6] - (w[68D9]/2 + w[1D2F]/2) = 0 w[77B7] - (w[78F9]/2 + w[3DAF]/2) = 0 w[77FC] - (w[2BDF]/2 + w[5F7F]/2) = 0 w[77FD] = ((1/2 + (w[5F7F]/2 + 1/2)/2)/2 + 1/2) w[78E0] - (w[260B]/2 + w[1315]/2) = 0 w[78E1] - (w[362B]/2 + w[3395]/2) = 0 w[78E6] - (w[6E4B]/2 + w[171D]/2) = 0 w[78E7] - (w[7E6B]/2 + w[379D]/2) = 0 w[78F8] - (w[270F]/2 + w[5B55]/2) = 0 w[78F9] - (w[372F]/2 + w[7BD5]/2) = 0 w[79E4] - (w[2ECB]/2 + w[171D]/2) = 0 w[79E5] - (w[3EEB]/2 + w[379D]/2) = 0 w[79FC] - (w[2FCF]/2 + w[5F5D]/2) = 0 w[79FD] - ((1/2 + ((1/2 + w[6FDF]/2)/2 + 1/2)/2)/2 + (w[1FFF]/2 + 1/2)/2) = 0 w[7A62] - (w[661B]/2 + w[ 317]/2) = 0 w[7A63] - (w[763B]/2 + w[2397]/2) = 0 w[7A7A] - (w[671F]/2 + w[4B57]/2) = 0 w[7A7B] - (w[773F]/2 + w[6BD7]/2) = 0 w[7AD0] - (w[ 61B]/2 + w[1B17]/2) = 0 w[7AD1] - (w[163B]/2 + w[3B97]/2) = 0 w[7AD6] - (w[4E5B]/2 + w[1F1F]/2) = 0 w[7AD7] - (w[5E7B]/2 + w[3F9F]/2) = 0 w[7AF2] - (w[661B]/2 + w[1B17]/2) = 0 w[7AF3] - (w[763B]/2 + w[3B97]/2) = 0 w[7B66] - (w[6EDB]/2 + w[ 71F]/2) = 0 w[7B67] - (w[7EFB]/2 + w[279F]/2) = 0 w[7B7E] - (w[6FDF]/2 + w[4F5F]/2) = 0 w[7B7F] = (1/2 + w[6FDF]/2) w[7BD4] - (w[ EDB]/2 + w[1F1F]/2) = 0 w[7BD5] - (w[1EFB]/2 + w[3F9F]/2) = 0 w[7BF0] - (w[269B]/2 + w[1B17]/2) = 0 w[7BF1] - (w[36BB]/2 + w[3B97]/2) = 0 w[7BF6] - (w[6EDB]/2 + w[1F1F]/2) = 0 w[7BF7] - (w[7EFB]/2 + w[3F9F]/2) = 0 w[7CE8] - (w[270F]/2 + w[5375]/2) = 0 w[7CE9] - (w[372F]/2 + w[73F5]/2) = 0 w[7CEE] - (w[6F4F]/2 + w[577D]/2) = 0 w[7CEF] - ((1/2 + w[67FF]/2)/2 + ((1/2 + (w[5F7F]/2 + 1/2)/2)/2 + 1/2)/2) = 0 w[7DEC] - (w[2FCF]/2 + w[577D]/2) = 0 w[7DED] - ((1/2 + ((1/2 + w[6FDF]/2)/2 + 1/2)/2)/2 + ((1/2 + (w[5F7F]/2 + 1/2)/2)/2 + 1/2)/2) = 0 w[7E6A] - (w[671F]/2 + w[4377]/2) = 0 w[7E6B] - (w[773F]/2 + w[63F7]/2) = 0 w[7ED8] - (w[ 71F]/2 + w[5B77]/2) = 0 w[7ED9] - (w[173F]/2 + w[7BF7]/2) = 0 w[7EDE] - (w[4F5F]/2 + w[5F7F]/2) = 0 w[7EDF] = (w[5F7F]/2 + 1/2) w[7EE2] - (w[661B]/2 + w[1337]/2) = 0 w[7EE3] - (w[763B]/2 + w[33B7]/2) = 0 w[7EFA] - (w[671F]/2 + w[5B77]/2) = 0 w[7EFB] - (w[773F]/2 + w[7BF7]/2) = 0 w[7F6E] - (w[6FDF]/2 + w[477F]/2) = 0 w[7F6F] = (1/2 + w[67FF]/2) w[7FDC] - (w[ FDF]/2 + w[5F7F]/2) = 0 w[7FDD] = (w[1FFF]/2 + 1/2) w[7FE6] - (w[6EDB]/2 + w[173F]/2) = 0 w[7FE7] - (w[7EFB]/2 + w[37BF]/2) = 0 w[7FF8] - (w[279F]/2 + w[5B77]/2) = 0 w[7FF9] - (w[37BF]/2 + w[7BF7]/2) = 0 w[7FFE] - (w[6FDF]/2 + w[5F7F]/2) = 0 w[7FFF] = 1 Math-PlanePath-129/devel/hilbert.pl0000644000175000017500000004524113776740233015015 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2014, 2015, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use FindBin; use List::Util 'min','max','sum'; use Math::PlanePath::HilbertCurve; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; { # Thue-Morse Constant fixed point of Hilbert Curve # HAKMEM 122 (Schroeppel, Gosper) on the constant series.html # HAKMEM 115 (Gosper) topology.html # fixed point Hilbert(T) = T + i, so x(T)=T and Y(T)=1 # because deleting even bit positions of T is no change # # GP-DEFINE ThueMorse(n) = hammingweight(n)%2; # vector(6,k, fromdigits(vector(2^k,n,ThueMorse(n)),2)) # not in OEIS: 3, 13, 211, 54061, 3542953171, 15216868001456509741 # # vector(6,k, fromdigits(vector(2^k-1,n,ThueMorse(n)),2)) # 1, 6, 105, 27030, 1771476585, 7608434000728254870 # A048707 numerators of convergents # # vector(12,len, fromdigits(vector(len,n,ThueMorse(n)),2)) # 1, 3, 6, 13, 26, 52, 105, 211, 422, 844, 1689, 3378 # A019300 terms as binary # # even number of bits # vector(10,len, fromdigits(vector(2*len,n,ThueMorse(n)),2)) # not in OEIS: 3, 13, 52, 211, 844, 3378, 13515, 54061, 216244, 864978 # # multiple of 4 number of bits # vector(8,len, fromdigits(vector(4*len,n,ThueMorse(n)),2)) # not in OEIS: 13, 211, 3378, 54061, 864978, 13839660, 221434573, 3542953171 { # A014571 = 0.41245403364 # not in OEIS: 0.5875459663 # A321071 = 0.8249080672 # not in OEIS: 0.17509193 # # HAKMEM 4 times for another non-regular continued fraction: # 1 3 15 255 65535 # 2 - - * - * -- * --- * ----- * ... # 2 4 16 256 65536 # 2 - prod(i=0,6,((2^(2^i)-1)/2^(2^i)))*1.0 # 1.64981613456043039 # 0.412454033640... my $k = 32; my $len = 2*$k-1; my $c = ThueMorseConstant_len($len); print $c/2.0**$len/2,"\n"; print 1-$c/2.0**$len/2,"\n"; print $c/2.0**$len,"\n"; print 1-$c/2.0**$len,"\n"; print $c/2.0**$len*2,"\n"; # not in OEIS: 0.82489013671875 # not in OEIS: 0.17510986328125 } my $path = Math::PlanePath::HilbertCurve->new; foreach my $k (0 .. 12) { # my $k = 1<<$k; my $n = ThueMorseConstant_len(2*$k-1); my ($x,$y) = $path->n_to_xy($n); if ($k % 2 == 1) { ($x,$y) = ($y,$x); } printf "%-24b %-12b %-12b\n", $n, $x, $y; # even terms # printf "%-24b\n", # digit_join_lowtohigh([map {ThueMorse(2*$_)} reverse 1 .. $k], 2); } exit 0; sub ThueMorseConstant_len { my ($len) = @_; return digit_join_lowtohigh([map {ThueMorse($_)} reverse 1 .. $len], 2); } sub ThueMorse { my ($n) = @_; sum(0,digit_split_lowtohigh($n,2)) % 2; } sub Paper_len { my ($len) = @_; return digit_join_lowtohigh([map {Paper($_)} reverse 1 .. $len], 2); } sub Paper { my ($n) = @_; for (;;) { die if $n==0; if ($n&1) { return 1 - (($n>>1)&1); } $n >>= 1; } } } { # E.H. Moore Cycle of Hilbert Curves # 2*4^k - 1 # area inside half less 1 unshift @INC, "$FindBin::Bin/../../dragon/tools"; require MyPlanar; my $path = Math::PlanePath::HilbertCurve->new; my @values; foreach my $k (0 .. 9) { my ($n_lo,$n_hi) = $path->level_to_n_range($k); ### $n_hi my $y_end = 2**$k; my $points = [ map {[$path->n_to_xy($_)]} $n_lo .. $n_hi ]; if ($k % 2 == 0) { ### transpose ... $points = MyPlanar::points_transpose($points); } ### points: $points $points->[-1]->[0] == 0 or die; $points->[-1]->[1] == $y_end-1 or die; ### p0: $points->[0] ### p0x: $points->[0]->[0] my $neg = MyPlanar::points_xyscale($points, -1,1); $neg = MyPlanar::points_move($neg, -1,0); @$neg = reverse @$neg; ### neg: $neg $points = [ @$points, @{MyPlanar::points_move($points, 0, $y_end)}, @{MyPlanar::points_move($neg, 0, $y_end)}, @$neg ]; ### $points my $A = MyPlanar::points_to_area($points); print "$A,"; push @values, $A; } print "\n"; require Math::OEIS::Grep; Math::OEIS::Grep->search(array => \@values, verbose=>1); exit 0; } { # HilbertSides X+Y = HilbertCurve X+Y # 3 | 5----6---7,9--10---11 # | | | | # 2 | 4----3 8 13---12 24---23 # | | | | # 1 | 2 14 17---18---19 22 # | | | | | | # Y=0 | 0----1 15---16 20---21 # 3 | 5----6 9---10 # | | | | | # 2 | 4 7----8 11 24 # | | | | # 1 | 3----2 13---12 17---18 23---22 # | | | | | | # Y=0 | 0----1 14---15---16 19---20---21 # +---------------------------------- require Math::PlanePath::HilbertSides; my $curve = Math::PlanePath::HilbertCurve->new; my $sides = Math::PlanePath::HilbertSides->new; foreach my $n (0 .. 10000) { my ($cx,$cy) = $curve->n_to_xy($n); my ($sx,$sy) = $sides->n_to_xy($n); my $cdiff = $cx-$cy; my $sdiff = $sx-$sy; $cdiff == $sdiff or die; } exit 0; } { require Math::NumSeq::PlanePathCoord; require Math::PlanePath::AR2W2Curve; foreach my $start_shape (@{Math::PlanePath::AR2W2Curve ->parameter_info_hash->{'start_shape'}->{'choices'}}) { my $hseq = Math::NumSeq::PlanePathCoord->new (planepath => 'HilbertCurve', coordinate_type => 'RSquared'); my $aseq = Math::NumSeq::PlanePathCoord->new (planepath => "AR2W2Curve,start_shape=$start_shape", coordinate_type => 'RSquared'); foreach my $i ($hseq->i_start .. 10000) { if ($hseq->ith($i) != $aseq->ith($i)) { print "$start_shape different at $i\n"; last; } } } exit 0; } { # Cycles in Hilbert <-> ZOrder require Math::PlanePath::ZOrderCurve; my $hilbert = Math::PlanePath::HilbertCurve->new; my $zorder = Math::PlanePath::ZOrderCurve->new; sub zorder_perm { my ($n) = @_; my ($x, $y) = $zorder->n_to_xy ($n); return $hilbert->xy_to_n ($x, $y); } sub cycle_length { my ($n) = @_; my %seen; my $count = 1; my $p = $n; for (;;) { $p = zorder_perm($p); if ($p == $n) { last; } $count++; } return $count; } foreach my $n (0 .. 128) { my $perm = zorder_perm($n); my $len = cycle_length($n); print "$n $perm $len\n"; } exit 0; } { require Math::BaseCnv; require Math::NumSeq::PlanePathDelta; my $seq = Math::NumSeq::PlanePathDelta->new (delta_type => 'Dir4', planepath => 'HilbertCurve'); foreach my $n (0 .. 256) { my $n4 = Math::BaseCnv::cnv($n,10,4); my $want = $seq->ith($n); my $got = dir_try($n); my $str = ($want == $got ? '' : ' ***'); printf "%2d %3s %d %d%s\n", $n, $n4, $want, $got, $str; } exit 0; # my @next_state = (4,0,0,12, 0,4,4,8, 12,8,8,4, 8,12,12,0); # my @digit_to_x = (0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0); # my @digit_to_y = (0,0,1,1, 0,1,1,0, 1,1,0,0, 1,0,0,1); # dx dy dir # 0 +1 0 0,1,2 4,0,0,12 0=XYswap dir^1 3 y=-x dir^3 low^1 or ^3 # 4 0 +1 1,0,3 0,4,4,8 3 x=-y # 8 -1 0 2,3,0 12,8,8,4, # 12 0 -1 3,2,1 8,12,12,0 0=XYswap dir^1 # [012]3333 # [123]0000 # p = count 3s 0 if dx+dy=-1 so dx=-1 or dy=-1 SW, 1 if dx+dy=1 NE # m = count 3s in -n 0 if dx-dy=-1 NW, 1 if dx-dy=1 SE # 1023200 neg = 2310133+1 = 2310200 count 0s except trailing 0s sub dir_try { my ($n) = @_; ### dir_try(): $n # Jorg fxtbook # p = count 3s 0 if dx+dy=-1 so dx=-1 or dy=-1 SW, 1 if dx+dy=1 NE # m = count 3s in -n 0 if dx-dy=-1 NW, 1 if dx-dy=1 SE # 1023200 neg = 2310133+1 = 2310200 count 0s except trailing 0s $n++; my $p = count_3s($n) & 1; my $m = count_3s((-$n) & 0xFF) & 1; ### n : sprintf "%8b", $n ### neg: sprintf "%8b", (-$n) & 0xFF ### $p ### $m if ($p == 0) { if ($m == 0) { return 0; # E } else { return 1; # S } } else { if ($m == 0) { return 3; # N } else { return 2; # W } } # my $state = 0; # my @digits = digits($n); # if (@digits & 1) { # # $state ^= 1; # } # # unshift @digits, 0; # ### @digits # # my $flip = 0; # my $dir = 0; # for (;;) { # if (! @digits) { # return $flip; # } # $dir = pop @digits; # if ($dir == 3) { # $flip ^= 1; # } else { # last; # } # } # if ($flip) { # $dir = 1-$dir; # } # # while (@digits) { # my $digit = pop @digits; # ### at: "state=$state digit=$digit dir=$dir" # # if ($digit == 0) { # } # if ($digit == 1) { # $dir = 1-$dir; # } # if ($digit == 2) { # $dir = 1-$dir; # } # if ($digit == 3) { # $dir = $dir+2; # } # } # # ### $dir # return $dir % 4; # works ... # # while (@digits && $digits[-1] == 3) { # $state ^= 1; # pop @digits; # } # # if (@digits) { # # push @digits, $digits[-1]; # # } # # while (@digits > 1) { # my $digit = shift @digits; # ### at: "state=$state digit=$digit dir=$dir" # # if ($digit == 0) { # } # if ($digit == 1) { # $state ^= 1; # } # if ($digit == 2) { # $state ^= 1; # } # if ($digit == 3) { # $state ^= 2; # } # } # # ### $state # ### $digit # my $dir = $digits[0] // return $state^1; # if ($state & 1) { # $dir = 1-$dir; # } # if ($state & 2) { # $dir = $dir+2; # } # ### $dir # # # ### $dir # return $dir % 4; # my $digit = $digits[-1]; # if ($digit == 0) { # $dir = 0; # } # if ($digit == 1) { # $dir = 2; # } # if ($digit == 2) { # $dir = 1; # } # if ($digit == 3) { # $dir = 1; # } # if (@digits & 1) { # $dir = 1-$dir; # } # my $ret = $dir; # # while (@digits) { # my $digit = shift @digits; # if ($digit == 0) { # $dir = 1-$dir; # $ret = $dir; # } # if ($digit == 1) { # $ret = $dir; # } # if ($digit == 2) { # $ret = $dir; # } # if ($digit == 3) { # $dir = $dir + 2; # } # } # return $ret % 4; # $ret = 0; # while (($n & 3) == 3) { # $n >>= 2; # $ret ^= 1; # } # # my $digit = ($n & 3); # $n >>= 2; # if ($digit == 0) { # } # if ($digit == 1) { # $ret++; # } # if ($digit == 2) { # $ret += 2; # } # if ($digit == 3) { # } # # while ($n) { # my $digit = ($n & 3); # $n >>= 2; # # if ($digit == 0) { # $ret = 1-$ret; # } # if ($digit == 1) { # $ret = -$ret; # # $ret = 1-$ret; # } # if ($digit == 2) { # $ret = 1-$ret; # } # if ($digit == 3) { # $ret = $ret + 2; # } # } # return $ret % 4; # # # # if (($n & 3) == 3) { # while (($n & 15) == 15) { # $n >>= 4; # } # if (($n & 3) == 3) { # $ret = 1; # } # $n >>= 2; # } elsif (($n & 3) == 1) { # $ret = 0; # $n >>= 2; # } elsif (($n & 3) == 2) { # $ret = 2; # $n >>= 2; # } # # while ($n) { # if (($n & 3) == 0) { # $ret ^= 1; # } # if (($n & 3) == 3) { # $ret ^= 2; # } # $n >>= 2; # } # return $ret; } sub digits { my ($n) = @_; my @ret; while ($n) { unshift @ret, $n & 3; $n >>= 2; } ; # || @ret&1 return @ret; } sub count_3s { my ($n) = @_; my $count = 0; while ($n) { $count += (($n & 3) == 3); $n >>= 2; $count += (($n & 3) == 3); $n >>= 2; } return $count; } } { my $path = Math::PlanePath::HilbertCurve->new; my @range = $path->rect_to_n_range (1,2, 2,4); ### @range exit 0; } { my $path = Math::PlanePath::HilbertCurve->new; sub want { my ($n) = @_; my ($x1,$y1) = $path->n_to_xy($n); my ($x2,$y2) = $path->n_to_xy($n+1); return ($x2-$x1, $y2-$y1); } sub try { my ($n) = @_; ### try(): $n while (($n & 15) == 15) { $n >>= 4; } my $pos = 0; my $mask = 16; while ($n >= $mask) { $pos += 4; $mask <<= 4; } ### $pos my $dx = 1; my $dy = 0; ### d initial: "$dx,$dy" while ($pos >= 0) { my $bits = ($n >> $pos) & 15; ### $bits if ($bits == 1 || $bits == 2 || $bits == 3 || $bits == 4 || $bits == 8 ) { ($dx,$dy) = ($dy,$dx); ### d swap to: "$dx,$dy" } elsif ($bits == 2 || $bits == 12 ) { $dx = -$dx; $dy = -$dy; ### d invert: "$dx,$dy" } elsif ($bits == 2 || $bits == 10 || $bits == 11 || $bits == 13 ) { ($dx,$dy) = ($dy,$dx); $dx = -$dx; $dy = -$dy; ### d swap and invert: "$dx,$dy" } elsif ($bits == 0 || $bits == 5 ) { ### d unchanged } $pos -= 4; } return ($dx,$dy); } sub Wtry { my ($n) = @_; ### try(): $n my $pos = 0; my $mask = 16; while ($n >= $mask) { $pos += 4; $mask <<= 4; } ### $pos my $dx = 1; my $dy = 0; ### d initial: "$dx,$dy" while ($pos >= 0) { my $bits = ($n >> $pos) & 15; ### $bits if ($bits == 1 || $bits == 3 || $bits == 4 || $bits == 8 ) { ($dx,$dy) = ($dy,$dx); ### d swap to: "$dx,$dy" } elsif ($bits == 2 || $bits == 12 ) { $dx = -$dx; $dy = -$dy; ### d invert: "$dx,$dy" } elsif ($bits == 2 || $bits == 6 || $bits == 10 || $bits == 11 || $bits == 13 ) { ($dx,$dy) = ($dy,$dx); $dx = -$dx; $dy = -$dy; ### d swap and invert: "$dx,$dy" } elsif ($bits == 0 || $bits == 5 ) { ### d unchanged } $pos -= 4; } return ($dx,$dy); } sub ZZtry { my ($n) = @_; my $dx = 0; my $dy = 1; do { my $bits = $n & 3; if ($bits == 0) { ($dx,$dy) = ($dy,$dx); ### d swap: "$dx,$dy" } elsif ($bits == 1) { # ($dx,$dy) = ($dy,$dx); # ### d swap: "$dx,$dy" } elsif ($bits == 2) { ($dx,$dy) = ($dy,$dx); ### d swap: "$dx,$dy" $dx = -$dx; $dy = -$dy; ### d invert: "$dx,$dy" } elsif ($bits == 3) { ### d unchanged } my $prevbits = $bits; $n >>= 2; return ($dx,$dy) if ! $n; $bits = $n & 3; if ($bits == 0) { ### d unchanged } elsif ($bits == 1) { ($dx,$dy) = ($dy,$dx); ### d swap: "$dx,$dy" } elsif ($bits == 2) { if ($prevbits >= 2) { } # $dx = -$dx; # $dy = -$dy; ($dx,$dy) = ($dy,$dx); ### d swap: "$dx,$dy" } elsif ($bits == 3) { ($dx,$dy) = ($dy,$dx); ### d invert and swap: "$dx,$dy" } $n >>= 2; } while ($n); return ($dx,$dy); } my @n_to_next_i = (4, 0, 0, 8, # i=0 0, 4, 4, 12, # i=4 12, 8, 8, 0, # i=8 8, 12, 12, 4, # i=12 ); my @n_to_x = (0, 1, 1, 0, # i=0 0, 0, 1, 1, # i=4 1, 1, 0, 0, # i=8 1, 0, 0, 1, # i=12 ); my @n_to_y = (0, 0, 1, 1, # i=0 0, 1, 1, 0, # i=4 1, 0, 0, 1, # i=8 1, 1, 0, 0, # i=12 ); my @i_to_dx = (1, 0, -1, 3, 0, 1, 0, 7,-1, 1, 10, 0,-1, 0,1,15); my @i_to_dy = (0, 1, 0, 3, 1, 0, -1, 7, 0, 0, 10, 1, 0,-1,0,15); # unswapped # my @i_to_dx = (1, 0, -1, 3, 0, 1, 0, 7,-1, 1, 10, 0,-1, 0,1,15); # my @i_to_dy = (0, 1, 0, 3, 1, 0, -1, 7, 0, 0, 10, 1, 0,-1,0,15); # my @i_to_dx = (0 .. 15); # my @i_to_dy = (0 .. 15); sub Xtry { my ($n) = @_; ### HilbertCurve n_to_xy(): $n ### hex: sprintf "%#X", $n return if $n < 0; my $x = my $y = ($n * 0); # inherit my $pos = 0; { my $pow = $x + 4; # inherit while ($n >= $pow) { $pow <<= 2; $pos += 2; } } ### $pos my $dx = 9; my $dy = 9; my $i = ($pos & 2) << 1; my $t; while ($pos >= 0) { my $nbits = (($n >> $pos) & 3); $t = $i + $nbits; $x = ($x << 1) | $n_to_x[$t]; $y = ($y << 1) | $n_to_y[$t]; ### $pos ### $i ### bits: ($n >> $pos) & 3 ### $t ### n_to_x: $n_to_x[$t] ### n_to_y: $n_to_y[$t] ### next_i: $n_to_next_i[$t] ### x: sprintf "%#X", $x ### y: sprintf "%#X", $y # if ($nbits == 0) { # } els if ($nbits == 3) { if ($pos & 2) { ($dx,$dy) = ($dy,$dx); } } else { ($dx,$dy) = ($i_to_dx[$t], $i_to_dy[$t]); } $i = $n_to_next_i[$t]; $pos -= 2; } print "final i $i\n"; return ($dx,$dy); } sub base4 { my ($n) = @_; my $ret = ''; do { $ret .= ($n & 3); } while ($n >>= 2); return reverse $ret; } foreach my $n (0 .. 256) { my $n4 = base4($n); my ($wdx,$wdy) = want($n); my ($tdx,$tdy) = try($n); my $diff = ($wdx!=$tdx || $wdy!=$tdy ? " ***" : ""); print "$n $n4 $wdx,$wdy $tdx,$tdy $diff\n"; } exit 0; # Jorg fxtbook # p=dx+dy +/-1 # m=dx-dy +/-1 # # p = count 3s in N, odd/even # m = count 3s in -N, odd/even # # p==m is dx # p!=m then p is dy } Math-PlanePath-129/devel/quintet-replicate.pl0000644000175000017500000001454712766725546017040 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2016 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.006; use strict; use warnings; use Math::BaseCnv 'cnv'; use Math::Libm 'M_PI', 'hypot'; use Math::PlanePath::QuintetReplicate; $|=1; { # X+Y maximum NE in level # k=0 0 0 0 0 # k=1 1,2 1,2 1 1 # k=2 6,7 11,12 4 4 # k=3 31,32 111,112 11 21 # k=4 156,157 1111,1112 24 44 # k=5 2656,2657 41111,41112 55 210 # k=6 15156,15157 441111,441112 134 1014 # k=7 77656,77657 4441111,4441112 295 2140 # k=8 312031,312032 34441111,34441112 602 4402 # k=9 1483906,1483907 334441111,334441112 1465 21330 # k=10 7343281,7343282 3334441111,3334441112 3382 102012 my $path = Math::PlanePath::QuintetReplicate->new; foreach my $k (0 .. 10) { my ($n_lo,$n_hi) = $path->level_to_n_range($k); my $sum_max = 0; my @n_max; foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); my $sum = $x+$y; if ($sum == $sum_max) { push @n_max, $n; } elsif ($sum > $sum_max) { $sum_max = $sum; @n_max = ($n); } } my @n5 = map {cnv($_,10,5)} @n_max; my $sum5 = cnv($sum_max,10,5); print "k=$k ",join(',',@n_max)," ",join(',',@n5)," $sum_max $sum5\n"; } exit 0; } { require Math::BigInt; Math::BigInt->import(try => 'GMP'); my $path = Math::PlanePath::QuintetReplicate->new; my $n_max = Math::BigInt->new(0); my $pow = Math::BigInt->new(1); foreach my $k (0 .. 50) { my $x_max = 0; my $new_n_max = 0; foreach my $d (1 .. 4) { my $try_n = $n_max + $pow*$d; my ($x,$y) = $path->n_to_xy($try_n); if ($x > $x_max) { $x_max = $x; $new_n_max = $try_n; } } $n_max = $new_n_max; my $n5 = cnv($n_max,10,5); my $x5 = cnv($x_max,10,5); print "k=$k $n_max\n $n5\n X=$x_max $x5\n"; $pow *= 5; } exit 0; } { # X maximum in level # k=0 0 0 0 0 # k=1 1 1 1 1 # k=2 6 11 3 3 # k=3 106 411 7 12 # k=4 606 4411 18 33 # k=5 3106 44411 42 132 # k=6 15606 444411 83 313 # k=7 62481 3444411 200 1300 # k=8 296856 33444411 478 3403 # k=9 1468731 333444411 1005 13010 # k=10 5374981 2333444411 2204 32304 # high digit 1 at b^k, so I^(d-1)*b^k # when angle atan(1/2) steps past 90 deg # atan(1/2)*180/Pi \\ 26.565 deg # frac(x) = x-floor(x); # a=(Pi/2)/atan(1/2) # a=atan(1/2)/(Pi/2) # a=2*atan(1/2)/Pi # 1/h+1/a # h = Pi/(2*atan(2)) # vector(20,n,n--; (floor((a+0)*n+1/2))) # vector(20,n,n--; frac(n/a)) # vector(20,n, (-sum(n=0,n-1,frac((n+1+1/2)/a)new; foreach my $k (0 .. 10) { my ($n_lo,$n_hi) = $path->level_to_n_range($k); my $x_max = 0; my $n_max = 0; foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); if ($x > $x_max) { $x_max = $x; $n_max = $n; } } my $n5 = cnv($n_max,10,5); my $x5 = cnv($x_max,10,5); print "k=$k $n_max $n5 $x_max $x5\n"; } exit 0; } { my $level = 6; my $path = Math::PlanePath::QuintetReplicate->new; my ($n_lo, $n_hi) = $path->level_to_n_range($level); print "\\fill foreach \\p in {"; foreach my $n ($n_lo .. $n_hi) { my ($x,$y) = $path->n_to_xy($n); print "($x,$y),"; } print "} "; print "{ \\p rectangle +(1,1) };\n"; exit 0; } { # min/max for level require Math::BaseCnv; require Math::PlanePath::QuintetReplicate; my $path = Math::PlanePath::QuintetReplicate->new; my $prev_min = 1; my $prev_max = 1; my @mins; for (my $level = 0; $level < 20; $level++) { my $n_start = 5**$level; my $n_end = 5**($level+1) - 1; my $min_hypot = 128*$n_end*$n_end; my $min_x = 0; my $min_y = 0; my $min_pos = ''; my $max_hypot = 0; my $max_x = 0; my $max_y = 0; my $max_pos = ''; print "level $level n=$n_start .. $n_end\n"; foreach my $n ($n_start .. $n_end) { my ($x,$y) = $path->n_to_xy($n); my $h = $x*$x + $y*$y; # my $h = abs($x) + abs($y); if ($h < $min_hypot) { my $n5 = Math::BaseCnv::cnv($n,10,5) . '[5]'; $min_hypot = $h; $min_pos = "$x,$y $n $n5"; } if ($h > $max_hypot) { my $n5 = Math::BaseCnv::cnv($n,10,5) . '[5]'; $max_hypot = $h; $max_pos = "$x,$y $n $n5"; } } # print " min $min_hypot at $min_x,$min_y\n"; # print " max $max_hypot at $max_x,$max_y\n"; # { # my $factor = $min_hypot / $prev_min; # my $base5 = Math::BaseCnv::cnv($min_hypot,10,5) . '[5]'; # print " min $min_hypot $base5 at $min_pos factor $factor\n"; # } { my $factor = $max_hypot / $prev_max; my $base5 = Math::BaseCnv::cnv($max_hypot,10,5) . '[5]'; print " max $max_hypot $base5 at $max_pos factor $factor\n"; } $prev_min = $min_hypot; $prev_max = $max_hypot; push @mins, $min_hypot; } print join(',',@mins),"\n"; exit 0; } { # Dir4 maximum require Math::PlanePath::QuintetReplicate; require Math::NumSeq::PlanePathDelta; require Math::BigInt; require Math::BaseCnv; my $path = Math::PlanePath::QuintetReplicate->new; my $seq = Math::NumSeq::PlanePathDelta->new (planepath_object => $path, delta_type => 'Dir4'); my $dir4_max = 0; foreach my $n (0 .. 600000) { # my $n = Math::BigInt->new(2)**$level - 1; my $dir4 = $seq->ith($n); if ($dir4 > $dir4_max) { $dir4_max = $dir4; my ($dx,$dy) = $path->n_to_dxdy($n); my $n5 = Math::BaseCnv::cnv($n,10,5); printf "%7s %2b,\n %2b %8.6f\n", $n5, abs($dx),abs($dy), $dir4; } } exit 0; } Math-PlanePath-129/devel/iterator.pl0000644000175000017500000001704711604443335015207 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.006; use strict; use warnings; =over =item C<($n, $x, $y) = $path-Enext()> =item C<($n, $x, $y) = $path-Enext_nxy()> =item C<($n, $x, $y) = $path-Epeek()> =item C<$path-Erewind()> =item C<$path-Eseek_to_n($n)> =item C<$n = $path-Etell_n($n)> =item C<($n,$x,$y) = $path-Etell_nxy($n)> =back =cut use Math::PlanePath; { package Math::PlanePath; no warnings 'redefine'; sub new { my $class = shift; my $self = bless { @_ }, $class; $self->rewind; return $self; } sub rewind { my ($self) = @_; $self->seek_to_n($self->n_start); } sub seek_to_n { my ($self, $n) = @_; $self->{'n'} = $n; } sub tell_n { my ($self, $n) = @_; return $self->{'n'}; } sub next_nxy { my ($self) = @_; my $n = $self->{'n'}++; return ($n, $self->n_to_xy($n)); } sub peek_nxy { my ($self) = @_; my $n = $self->{'n'}; return ($n, $self->n_to_xy($n)); } } { use Math::PlanePath::ZOrderCurve; package Math::PlanePath::ZOrderCurve; # sub seek_to_n { # my ($self, $n) = @_; # ($self->{'x'},$self->{'y'}) = $self->n_to_xy($self->{'n'} = $n); # $self->{'bx'} = $x; # $self->{'by'} = $y; # $self->{'a'} = [ \$self->{'x'}, \$self->{'y'} ]; # $self->{'i'} = 1; # ### ZOrderCurve seek_to_n(): $self # } # sub next_nxy { # my ($self) = @_; # $self->{'a'}->[($self->{'i'} ^= 1)]++; # return (++$self->{'n'}, $self->{'x'}, $self->{'y'}); # } # sub peek_nxy { # my ($self) = @_; # return ($self->{'n'} + 1, # $self->{'x'} + !$self->{'i'}, # $self->{'y'} + $self->{'i'}); # } } { use Math::PlanePath::Rows; package Math::PlanePath::Rows; sub seek_to_n { my ($self, $n) = @_; $self->{'n'} = --$n; my $width = $self->{'width'}; $self->{'px'} = ($n % $width) - 1; $self->{'py'} = int ($n / $width); ### seek_to_n: $self } sub next_nxy { my ($self) = @_; my $x = ++$self->{'px'}; if ($x >= $self->{'width'}) { $x = $self->{'px'} = 0; $self->{'py'}++; } return (++$self->{'n'}, $x, $self->{'py'}); } sub peek_nxy { my ($self) = @_; if ((my $x = $self->{'px'} + 1) < $self->{'width'}) { return ($self->{'n'}+1, $x, $self->{'py'}); } else { return ($self->{'n'}+1, 0, $self->{'py'}+1); } } } { use Math::PlanePath::Diagonals; package Math::PlanePath::Diagonals; # N = (1/2 d^2 + 1/2 d + 1) # = (1/2*$d**2 + 1/2*$d + 1) # = ((0.5*$d + 0.5)*$d + 1) # d = -1/2 + sqrt(2 * $n + -7/4) sub seek_to_n { my ($self, $n) = @_; $self->{'n'} = $n; my $d = $self->{'d'} = int (-.5 + sqrt(2*$n - 1.75)); $n -= $d*($d+1)/2 + 1; $self->{'px'} = $n - 1; $self->{'py'} = $d - $n + 1; ### Diagonals seek_to_n(): $self } sub next_nxy { my ($self) = @_; my $x = ++$self->{'px'}; my $y = --$self->{'py'}; if ($y < 0) { $x = $self->{'px'} = 0; $y = $self->{'py'} = ++$self->{'d'}; } return ($self->{'n'}++, $x, $y); } sub peek_nxy { my ($self) = @_; if (my $y = $self->{'py'}) { return ($self->{'n'}, $self->{'px'}+1, $y-1); } else { return ($self->{'n'}, 0, $self->{'d'}+1); } } } { use Math::PlanePath::SquareSpiral; package Math::PlanePath::SquareSpiral; sub next_nxy { my ($self) = @_; ### next(): $self my $x = ($self->{'x'} += $self->{'dx'}); my $y = ($self->{'y'} += $self->{'dy'}); unless ($self->{'side'}--) { ### turn ($self->{'dx'},$self->{'dy'}) = (-$self->{'dy'},$self->{'dx'}); # left $self->{'side'} = (($self->{'d'} += ($self->{'grow'} ^= 1)) - 1) + ($self->{'dx'} && $self->{'wider'}); ### grow applied: $self->{'grow'} ### d now: $self->{'d'} ### side now: $self->{'side'} ### dx,dy now: "$self->{'dx'},$self->{'dy'}" } ### return: 'n='.$self->{'n'}.' '.($self->{'x'} + $self->{'dx'}).','.($self->{'y'} + $self->{'dy'}) return ($self->{'n'}++, $x, $y); } sub peek_nxy { my ($self) = @_; # ### peek(): $self return ($self->{'n'}, $self->{'x'} + $self->{'dx'}, $self->{'y'} + $self->{'dy'}); } # N = (1/2 d^2 + 1/2 d + 1) # = (1/2*$d**2 + 1/2*$d + 1) # = ((0.5*$d + 0.5)*$d + 1) # d = -1/2 + sqrt(2 * $n + -7/4) sub seek_to_n { my ($self, $n) = @_; ### SquareSpiral seek_to_n: $n $self->{'n'} = $n; my $d = $self->{'d'} = int (1/2 + sqrt(1 * $n + -3/4)); $n -= (($d - 1)*$d + 1); ### $d ### half d: int($d/2) ### remainder: $n my $dx; my $dy; my $y = - int($d/2); my $x = $y + $n; if ($self->{'grow'} = ($n < $d)) { ### horizontal $dx = 1; $dy = 0; } else { ### vertical $n -= $d; $dx = 0; $dy = 1; ($x, $y) = ($y + $d, $x - $d); } if ($d & 1) { } else { ### negate for even d from: "$x,$y" $dx = - $dx; $dy = - $dy; $x = -$x; $y = -$y; } $self->{'side'} = $d - $n; $self->{'dx'} = $dx; $self->{'dy'} = $dy; $self->{'x'} = $x - $dx; $self->{'y'} = $y - $dy; # if ($n == 3) { # $self->{'side'} = 2; # $self->{'grow'} = 1; # $self->{'d'} = 2; # $self->{'dx'} = -1; # $self->{'dy'} = 0; # $self->{'x'} = 2; # $self->{'y'} = 1; # return; # } # # $self->{'n'} = $n; # $self->{'side'} = 1; # $self->{'grow'} = 0; # $self->{'d'} = 0; # $self->{'dx'} = 1; # $self->{'dy'} = 0; # $self->{'x'} = -1; # $self->{'y'} = 0; ### SquareSpiral seek_to_n(): $self } } use Smart::Comments; foreach my $class ('Math::PlanePath::SquareSpiral', 'Math::PlanePath::Diagonals', 'Math::PlanePath::Rows', ) { my $path = $class->new (width => 5); foreach my $n_start_offset (0 .. 30) { my $want_n = $path->n_start; if ($n_start_offset) { $want_n += $n_start_offset; $path->seek_to_n ($want_n); } ### $class ### $n_start_offset foreach my $i (0 .. 100) { my ($peek_n, $peek_x, $peek_y) = $path->peek; my ($got_n, $got_x, $got_y) = $path->next; my ($want_x, $want_y) = $path->n_to_xy($want_n); if ($want_n != $got_n) { ### $want_n ### $got_n die "x"; } if ($want_x != $got_x) { ### $want_n ### $want_x ### $got_x die "x"; } if ($want_y != $got_y) { ### $want_n ### $want_y ### $got_y die "x"; } if ($peek_n != $want_n) { ### $peek_n ### $want_n die "x"; } if ($peek_x != $want_x) { ### $want_n ### $peek_x ### $want_x die "x"; } if ($peek_y != $want_y) { ### $want_n ### $peek_y ### $want_y die "x"; } $want_n++; } } } Math-PlanePath-129/devel/misc.pl0000644000175000017500000000216412400452234014274 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.010; use strict; use warnings; { # BigFloat log() use Math::BigFloat; my $b = Math::BigFloat->new(3)**64; my $log = log($b); my $log3 = $log/log(3); # $b->blog(undef,100); print "$b\n$log\n$log3\n"; exit 0; } { # BigInt log() use Math::BigInt; use Math::BigFloat; my $b = Math::BigInt->new(1025); my $log = log($b); $b->blog(undef,100); print "$b $log\n"; exit 0; } Math-PlanePath-129/devel/divisible-columns.pl0000644000175000017500000000245611775424704017014 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; { require Math::PlanePath::DivisibleColumns; my $path = Math::PlanePath::DivisibleColumns->new; $path->xy_to_n(2000,2000); foreach my $k (3 .. 1000) { # my $total = 0; # my $limit = int(sqrt($k)); # foreach my $i (1 .. $limit) { # $total += int($k/$i); # } # $total = 2*$total - $limit*$limit; my $n = $path->xy_to_n($k,$k); my (undef, $nhi) = $path->rect_to_n_range(0,0,$k,$k); my $total = Math::PlanePath::DivisibleColumns::_count_divisors_cumulative($k); printf "%d %d,%d %d\n", $k, $n,$nhi, $total; } exit 0; } Math-PlanePath-129/devel/exe-complex-revolving.c0000644000175000017500000000554511701165116017420 0ustar gggg/* Copyright 2012 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . */ #include #include #include #include typedef unsigned long my_unsigned; typedef long long my_signed; #define MY_SIGNED_ABS llabs #define HYPOT_LIMIT 0x7FFFFFFF char * binary (unsigned long long n) { static char str[sizeof(n)*8+1]; int pos = sizeof(str)-1; do { str[pos] = (n&1)+'0'; n >>= 1; pos--; } while (n); return str+pos+1; } int main (void) { int level; for (level = 0; level < 8*sizeof(my_unsigned)-1; level++) { unsigned long long min_h = ~0ULL; my_unsigned min_n = 0; my_signed min_x = 0; my_signed min_y = 0; { my_unsigned lo = (my_unsigned)1 << level; my_unsigned hi = (my_unsigned)1 << (level+1); /* printf ("%2d lo=%lu hi=%lu\n", level, lo, hi); */ my_unsigned n; for (n = lo; n < hi; n++) { my_signed x = 0; my_signed y = 0; my_signed bx = 1; my_signed by = 0; my_unsigned bits; for (bits = n; bits != 0; bits >>= 1) { if (bits & 1) { x += bx; y += by; /* (bx,by) * i, rotate +90 */ my_signed new_bx = -by; my_signed new_by = bx; bx = new_bx; by = new_by; } /* (bx,by) * (i+1) */ my_signed new_bx = bx-by; my_signed new_by = bx+by; bx = new_bx; by = new_by; } unsigned long long abs_x = MY_SIGNED_ABS(x); unsigned long long abs_y = MY_SIGNED_ABS(y); if (abs_x > HYPOT_LIMIT || abs_y > HYPOT_LIMIT) { continue; } unsigned long long h = abs_x*abs_x + abs_y*abs_y; /* printf ("%2d %lu %Ld,%Ld %LX\n", level, n, x,y, h); */ if (h < min_h) { min_h = h; min_n = n; min_x = abs_x; min_y = abs_y; } } } /* printf ("%lX %Ld,%Ld %s\n", min_n, min_x,min_y, */ /* binary(min_h)); */ printf ("%2d", level); char *binary_str = binary(min_h); int binary_len = strlen(binary_str); printf (" %s [%d]", binary(min_h), binary_len); printf ("\n"); /* printf ("\n"); */ } return 0; } Math-PlanePath-129/lib/0002755000175000017500000000000014001441521012446 5ustar ggggMath-PlanePath-129/lib/Math/0002755000175000017500000000000014001441522013340 5ustar ggggMath-PlanePath-129/lib/Math/NumSeq/0002755000175000017500000000000014001441522014550 5ustar ggggMath-PlanePath-129/lib/Math/NumSeq/PlanePathDelta.pm0000644000175000017500000045650413774433072017771 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # maybe: # # dTRadius, dTRSquared of the radii # dTheta360, maybe from Dir360 # matching Dir4,TDir6 # dStep dTStep dHypot # StepDist StepSquared # StepTDist StepTSquared # StepRadius # StepRSquared # dLength # dDist dDSquared # dTDist dTDSquared # Dist DSquared # TDist TDSquared package Math::NumSeq::PlanePathDelta; use 5.004; use strict; use Carp 'croak'; use List::Util 'max'; use vars '$VERSION','@ISA'; $VERSION = 129; use Math::NumSeq; use Math::NumSeq::Base::IterateIth; @ISA = ('Math::NumSeq::Base::IterateIth', 'Math::NumSeq'); use Math::NumSeq::PlanePathCoord; *_planepath_name_to_object = \&Math::NumSeq::PlanePathCoord::_planepath_name_to_object; # uncomment this to run the ### lines # use Smart::Comments; use constant 1.02; # various underscore constants below use constant characteristic_smaller => 1; sub description { my ($self) = @_; if (ref $self) { return "Coordinate change $self->{'delta_type'} on path $self->{'planepath'}"; } else { # class method return 'Coordinate changes from a PlanePath'; } } use constant::defer parameter_info_array => sub { [ Math::NumSeq::PlanePathCoord::_parameter_info_planepath(), { name => 'delta_type', display => 'Delta Type', type => 'enum', default => 'dX', choices => ['dX','dY', 'AbsdX','AbsdY', 'dSum','dSumAbs', 'dDiffXY','dDiffYX','dAbsDiff', 'dRadius','dRSquared', # 'dTRadius','dTRSquared', 'Dir4','TDir6', # 'Dist','DSquared', # 'TDist','TDSquared', ], description => 'Coordinate change or direction to take from the path.', }, ]; }; #------------------------------------------------------------------------------ sub oeis_anum { my ($self) = @_; ### PlanePathCoord oeis_anum() ... my $planepath_object = $self->{'planepath_object'}; my $delta_type = $self->{'delta_type'}; { my $key = Math::NumSeq::PlanePathCoord::_planepath_oeis_anum_key($self->{'planepath_object'}); my $i_start = $self->i_start; if ($i_start != $self->default_i_start) { ### $i_start ### cf n_start: $planepath_object->n_start $key .= ",i_start=$i_start"; } ### planepath: ref $planepath_object ### $key ### whole table: $planepath_object->_NumSeq_Delta_oeis_anum ### key href: $planepath_object->_NumSeq_Delta_oeis_anum->{$key} if (my $anum = $planepath_object->_NumSeq_Delta_oeis_anum->{$key}->{$delta_type}) { return $anum; } } return undef; } #------------------------------------------------------------------------------ sub new { ### NumSeq-PlanePathDelta new(): @_ my $self = shift->SUPER::new(@_); $self->{'planepath_object'} ||= _planepath_name_to_object($self->{'planepath'}); { my $delta_type = $self->{'delta_type'}; ($self->{'delta_func'} = $self->can("_delta_func_$delta_type")) or ($self->{'n_func'} = $self->can("_n_func_$delta_type")) or croak "Unrecognised delta_type: ",$delta_type; } $self->rewind; return $self; } sub default_i_start { my ($self) = @_; my $planepath_object = $self->{'planepath_object'} # nasty hack allow no 'planepath_object' when SUPER::new() calls rewind() || return 0; return $planepath_object->n_start; } sub i_start { my ($self) = @_; my $planepath_object = $self->{'planepath_object'} || return 0; return $planepath_object->n_start; } # Old code keeping a previous X,Y to take a delta from. # # sub rewind { # my ($self) = @_; # # my $planepath_object = $self->{'planepath_object'} || return; # $self->{'i'} = $self->i_start; # undef $self->{'x'}; # $self->{'arms_count'} = $planepath_object->arms_count; # } # sub next { # my ($self) = @_; # ### NumSeq-PlanePathDelta next(): $self->{'i'} # ### n_next: $self->{'n_next'} # # my $planepath_object = $self->{'planepath_object'}; # my $i = $self->{'i'}++; # my $x = $self->{'x'}; # my $y; # if (defined $x) { # $y = $self->{'y'}; # } else { # ($x, $y) = $planepath_object->n_to_xy ($i) # or return; # } # # my $arms = $self->{'arms_count'}; # my ($next_x, $next_y) = $planepath_object->n_to_xy($i + $arms) # or return; # my $value = &{$self->{'delta_func'}}($x,$y, $next_x,$next_y); # # if ($arms == 1) { # $self->{'x'} = $next_x; # $self->{'y'} = $next_y; # } # return ($i, $value); # } sub ith { my ($self, $i) = @_; ### NumSeq-PlanePathDelta ith(): $i my $planepath_object = $self->{'planepath_object'}; if (my $func = $self->{'n_func'}) { return &$func($planepath_object,$i); } if (my ($dx, $dy) = $planepath_object->n_to_dxdy($i)) { return &{$self->{'delta_func'}}($dx,$dy); } return undef; } sub _delta_func_dX { my ($dx,$dy) = @_; return $dx; } sub _delta_func_dY { my ($dx,$dy) = @_; return $dy; } sub _delta_func_AbsdX { my ($dx,$dy) = @_; return abs($dx); } sub _delta_func_AbsdY { my ($dx,$dy) = @_; return abs($dy); } sub _delta_func_dSum { my ($dx,$dy) = @_; return $dx+$dy; } sub _delta_func_dDiffXY { my ($dx,$dy) = @_; return $dx-$dy; } sub _delta_func_dDiffYX { my ($dx,$dy) = @_; return $dy-$dx; } # (abs(x2)+abs(y2)) - (abs(x1)+abs(y1)) # = abs(x2)-abs(x1) + abs(y2)-+abs(y1) # = dAbsX + dAbsY sub _n_func_dSumAbs { my ($path, $n) = @_; ### _n_func_dSumAbs(): $n my ($x1,$y1) = $path->n_to_xy($n) or return undef; my ($x2,$y2) = $path->n_to_xy($n + $path->arms_count) or return undef; ### coords: "x1=$x1 y1=$y1 x2=$x2 y2=$y2" ### result: (abs($x2)+abs($y2)) - (abs($x1)+abs($y1)) return (abs($x2)+abs($y2)) - (abs($x1)+abs($y1)); } # abs(x2-y2) - abs(x1-y1) sub _n_func_dAbsDiff { my ($path, $n) = @_; my ($x1,$y1) = $path->n_to_xy($n) or return undef; my ($x2,$y2) = $path->n_to_xy($n + $path->arms_count) or return undef; return abs($x2-$y2) - abs($x1-$y1); } sub _n_func_dRadius { my ($path, $n) = @_; if (defined (my $r1 = $path->n_to_radius($n))) { if (defined (my $r2 = $path->n_to_radius($n + $path->arms_count))) { return ($r2 - $r1); } } return undef; } *_n_func_dRSquared = \&_path_n_to_drsquared; sub _path_n_to_drsquared { my ($path, $n) = @_; # dRSquared = (x2^2+y2^2) - (x1^2+y1^2) if (defined (my $r1 = $path->n_to_rsquared($n))) { if (defined (my $r2 = $path->n_to_rsquared($n + $path->arms_count))) { return ($r2 - $r1); } } return undef; } # Projection onto X,Y slope # # dX e # +---------------------B # | | + / # | | + / slope S = Y/X # dY | +| / dY/e = S # | + | / H e = dY/S # | + | / H = sqrt(dY^2 + dY^2/S^2) # | + | / H = dY * sqrt(1 + 1/S^2) # | + w |/ # X,Y A__-----------C p/h = S # / --__ / p = S*h # / p --__ /h w = dX-e = dX-dY/S # / & # / / # / / # # h^2+p^2 = w^2 # h^2 + S^2*h^2 = (dX-dY/S)^2 # h^2 = (dX-dY/S)^2/(1+S^2) # h = (dX-dY/S)/sqrt(1+S^2) # # H+h = dY * sqrt(1 + 1/S^2) + (dX-dY/S)/sqrt(1+S^2) # = dX/sqrt(1+S^2) # + dY * (sqrt(1 + 1/S^2) - 1/S*1/sqrt(1+S^2)) # S*sqrt(1 + 1/S^2)/S - 1/S*1/sqrt(1+S^2)) # sqrt(S^2 + 1)/S - 1/S * 1/sqrt(1+S^2) # (1+S^2)/S * 1/*sqrt(1+S^2) - 1/S * 1/sqrt(1+S^2) # (1+S^2 - 1)/S * 1/sqrt(1+S^2) # (S^2)/S * 1/sqrt(1+S^2) # S/sqrt(1+S^2) # # dTRadius -> (dX + S*dY) / sqrt(1+S^2) *_n_func_dTRadius = \&_path_n_to_dtradius; *_n_func_dTRSquared = \&_path_n_to_dtrsquared; # dTRadius = sqrt(x2^2+3*y2^2) - sqrt(x1^2+3*y1^2) sub _path_n_to_dtradius { my ($path, $n) = @_; if (defined (my $r1 = Math::NumSeq::PlanePathCoord::_path_n_to_tradius($path,$n)) && defined (my $r2 = Math::NumSeq::PlanePathCoord::_path_n_to_tradius($path, $n + $path->arms_count))) { return ($r2 - $r1); } return undef; } # dTRSquared = (x2^2+3*y2^2) - (x1^2+3*y1^2) sub _path_n_to_dtrsquared { my ($path, $n) = @_; if (defined (my $r1 = Math::NumSeq::PlanePathCoord::_path_n_to_trsquared($path,$n)) && defined (my $r2 = Math::NumSeq::PlanePathCoord::_path_n_to_trsquared($path, $n + $path->arms_count))) { return ($r2 - $r1); } return undef; } sub _delta_func_Dist { return sqrt(_delta_func_DSquared(@_)); } sub _delta_func_DSquared { my ($dx,$dy) = @_; return $dx*$dx + $dy*$dy; } sub _delta_func_TDist { return sqrt(_delta_func_TDSquared(@_)); } sub _delta_func_TDSquared { my ($dx,$dy) = @_; return $dx*$dx + 3*$dy*$dy; } sub _delta_func_Dir4 { my ($dx,$dy) = @_; ### _delta_func_Dir4(): "$dx,$dy" ### 360 is: _delta_func_Dir360($dx,$dy) return _delta_func_Dir360($dx,$dy) / 90; } sub _delta_func_TDir6 { my ($dx,$dy) = @_; ### _delta_func_TDir6(): "$dx,$dy" return _delta_func_TDir360($dx,$dy) / 60; } sub _delta_func_Dir8 { my ($dx,$dy) = @_; return _delta_func_Dir360($dx,$dy) / 45; } use constant 1.02; # for leading underscore use constant _PI => 2*atan2(1,0); sub _delta_func_Dir360 { my ($dx,$dy) = @_; ### _delta_func_Dir360(): "$dx,$dy" if ($dy == 0) { ### dy=0 ... return ($dx >= 0 ? 0 : 180); } if ($dx == 0) { ### dx=0 ... return ($dy > 0 ? 90 : 270); } if ($dx > 0) { if ($dx == $dy) { return 45; } if ($dx == -$dy) { return 315; } } else { if ($dx == $dy) { return 225; } if ($dx == -$dy) { return 135; } } my $radians_to_degrees; # don't atan2() on BigInt, go to BigFloat foreach ($dx, $dy) { if (ref $_ && ($_->isa('Math::BigInt') || $_->isa('Math::BigRat'))) { require Math::BigFloat; $_ = Math::BigFloat->new($_); # 180/pi with pi done in BigFloat configured precision $radians_to_degrees ||= do { require Math::PlanePath::MultipleRings; 180 / Math::PlanePath::MultipleRings::_pi($_); }; } } $radians_to_degrees ||= 180 / _PI; ### $radians_to_degrees ### $dx ### $dy ### atan2: atan2($dy,$dx) # atan2() returns -PI <= a <= PI and perlfunc says atan2(0,0) is "not well # defined" (though glibc gives 0). Add 360 to negatives to give 0<=dir<360. # my $degrees = atan2($dy,$dx) * $radians_to_degrees; ### $degrees return ($degrees < 0 ? $degrees + 360 : $degrees); } sub _delta_func_TDir360 { my ($dx,$dy) = @_; ### _delta_func_TDir360(): "$dx,$dy" if ($dy == 0) { return ($dx >= 0 ? 0 : 180); } if ($dx == 0) { return ($dy > 0 ? 90 : 270); } if ($dx > 0) { if ($dx == 3*$dy) { return 30; } if ($dx == $dy) { return 60; } if ($dx == -$dy) { return 300; } if ($dx == -3*$dy) { return 330; } } else { if ($dx == -$dy) { return 120; } if ($dx == -3*$dy) { return 150; } if ($dx == 3*$dy) { return 210; } if ($dx == $dy) { return 240; } } # Crib: atan2() returns -PI <= a <= PI, and is supposedly "not well # defined", though glibc gives 0 # my $degrees = atan2($dy*sqrt(3), $dx) * (180 / _PI); return ($degrees < 0 ? $degrees + 360 : $degrees); } #------------------------------------------------------------------------------ sub characteristic_integer { my ($self) = @_; my $method = "_NumSeq_Delta_$self->{'delta_type'}_integer"; return $self->{'planepath_object'}->$method(); } sub characteristic_increasing { my ($self) = @_; ### PlanePathDelta characteristic_increasing() ... my $planepath_object = $self->{'planepath_object'}; my $func; return (($func = ($planepath_object->can("_NumSeq_Delta_$self->{'delta_type'}_increasing") || ($self->{'delta_type'} eq 'DSquared' && $planepath_object->can("_NumSeq_Delta_Dist_increasing")) || ($self->{'delta_type'} eq 'TDSquared' && $planepath_object->can("_NumSeq_Delta_TDist_increasing")))) ? $planepath_object->$func() : undef); # unknown } sub characteristic_non_decreasing { my ($self) = @_; ### PlanePathDelta characteristic_non_decreasing() ... if (defined (my $values_min = $self->values_min)) { if (defined (my $values_max = $self->values_max)) { if ($values_min == $values_max) { # constant seq is non-decreasing return 1; } } } my $planepath_object = $self->{'planepath_object'}; my $func; return (($func = ($planepath_object->can("_NumSeq_Delta_$self->{'delta_type'}_non_decreasing") || ($self->{'delta_type'} eq 'DSquared' && $planepath_object->can("_NumSeq_Delta_Dist_non_decreasing")) || ($self->{'delta_type'} eq 'TDSquared' && $planepath_object->can("_NumSeq_Delta_TDist_non_decreasing")))) ? $planepath_object->$func() : $self->characteristic_increasing); # increasing means non_decreasing too } sub _dir360_to_tdir6 { my ($a) = @_; if ($a % 90 == 0) { # 0,90,180,270 -> 0, 1.5, 3, 4.5 return $a / 60; } if ($a % 45 == 0) { # 45, 135, 225, 315 -> 1, 2, 4, 5 return ($a+45)/90 + ($a < 180 ? 0 : 1); } if ($a == 30) { return 0.75; } if ($a == 150) { return 2.25; } if ($a == 210) { return 3.75; } if ($a == 330) { return 5.25; } $a *= _PI/180; # degrees to radians my $tdir6 = atan2(sin($a)*sqrt(3), cos($a)) * (3/_PI); # radians to 6 return ($tdir6 < 0 ? $tdir6 + 6 : $tdir6); } sub _dxdy_to_dir4 { my ($dx,$dy) = @_; ### _dxdy_to_dir4(): "$dx,$dy" if ($dy == 0) { ### dy=0 ... return ($dx == 0 ? 4 : $dx > 0 ? 0 : 2); } if ($dx == 0) { ### dx=0 ... return ($dy > 0 ? 1 : 3); } if ($dx > 0) { if ($dx == $dy) { return 0.5; } if ($dx == -$dy) { return 3.5; } } else { if ($dx == $dy) { return 2.5; } if ($dx == -$dy) { return 1.5; } } # don't atan2() in bigints if (ref $dx && $dx->isa('Math::BigInt')) { $dx = $dx->numify; } if (ref $dy && $dy->isa('Math::BigInt')) { $dy = $dy->numify; } # Crib: atan2() returns -PI <= a <= PI, and perlfunc says atan2(0,0) is # "not well defined", though glibc gives 0 # ### atan2: atan2($dy,$dx) my $dir4 = atan2($dy,$dx) * (2 / _PI); ### $dir4 return ($dir4 < 0 ? $dir4 + 4 : $dir4); } { my %values_min = (dX => 'dx_minimum', dY => 'dy_minimum', AbsdX => 'absdx_minimum', AbsdY => 'absdy_minimum', dSum => 'dsumxy_minimum', dDiffXY => 'ddiffxy_minimum', ); sub values_min { my ($self) = @_; my $planepath_object = $self->{'planepath_object'}; if (my $method = ($values_min{$self->{'delta_type'}} || $planepath_object->can("_NumSeq_Delta_$self->{'delta_type'}_min"))) { return $planepath_object->$method(); } return undef; } } { my %values_max = (dX => 'dx_maximum', dY => 'dy_maximum', AbsdX => 'absdx_maximum', AbsdY => 'absdy_maximum', dSum => 'dsumxy_maximum', dDiffXY => 'ddiffxy_maximum', ); sub values_max { my ($self) = @_; my $planepath_object = $self->{'planepath_object'}; if (my $method = ($values_max{$self->{'delta_type'}} || $planepath_object->can("_NumSeq_Delta_$self->{'delta_type'}_max"))) { return $planepath_object->$method(); } return undef; } } { package Math::PlanePath; use constant _NumSeq_Delta_oeis_anum => {}; #------------ # dX,dY use constant _NumSeq_Delta_dX_integer => 1; # usually use constant _NumSeq_Delta_dY_integer => 1; #------------ # AbsdX,AbsdY sub _NumSeq_Delta_AbsdX_integer { $_[0]->_NumSeq_Delta_dX_integer } sub _NumSeq_Delta_AbsdY_integer { $_[0]->_NumSeq_Delta_dY_integer } #------------ # dSum sub _NumSeq_Delta_dSum_integer { my ($self) = @_; return ($self->_NumSeq_Delta_dX_integer && $self->_NumSeq_Delta_dY_integer); } #------------ # dSumAbs sub _NumSeq_Delta_dSumAbs_min { my ($self) = @_; if (! $self->x_negative && ! $self->y_negative) { return $self->dsumxy_minimum; } return undef; } sub _NumSeq_Delta_dSumAbs_max { my ($self) = @_; if (! $self->x_negative && ! $self->y_negative) { return $self->dsumxy_maximum; } return undef; } *_NumSeq_Delta_dSumAbs_integer = \&_NumSeq_Delta_dSum_integer; #------------ # dDiffXY *_NumSeq_Delta_dDiffXY_integer = \&_NumSeq_Delta_dSum_integer; #------------ # dDiffYX sub _NumSeq_Delta_dDiffYX_min { my ($self) = @_; if (defined (my $m = $self->ddiffxy_maximum)) { return -$m; } return undef; } sub _NumSeq_Delta_dDiffYX_max { my ($self) = @_; if (defined (my $m = $self->ddiffxy_minimum)) { return -$m; } return undef; } sub _NumSeq_Delta_dDiffYX_integer { return $_[0]->_NumSeq_Delta_dDiffXY_integer; } #------------ # dAbsDiff *_NumSeq_Delta_dAbsDiff_integer = \&_NumSeq_Delta_dDiffYX_integer; #------------ # dRadius, dRSquared use constant _NumSeq_Delta_dRadius_min => undef; use constant _NumSeq_Delta_dRadius_max => undef; use constant _NumSeq_Delta_dRadius_integer => 0; use constant _NumSeq_Delta_dRSquared_min => undef; use constant _NumSeq_Delta_dRSquared_max => undef; *_NumSeq_Delta_dRSquared_integer = \&_NumSeq_Delta_dDiffYX_integer; #------------ # dTRadius, dTRSquared use constant _NumSeq_Delta_dTRadius_min => undef; use constant _NumSeq_Delta_dTRadius_max => undef; use constant _NumSeq_Delta_dTRadius_integer => 0; use constant _NumSeq_Delta_dTRSquared_min => undef; use constant _NumSeq_Delta_dTRSquared_max => undef; *_NumSeq_Delta_dTRSquared_integer = \&_NumSeq_Delta_dDiffYX_integer; #------------ # Dir4 sub _NumSeq_Delta_Dir4_min { my ($self) = @_; return Math::NumSeq::PlanePathDelta::_dxdy_to_dir4 ($self->dir_minimum_dxdy); } sub _NumSeq_Delta_Dir4_max { my ($self) = @_; return Math::NumSeq::PlanePathDelta::_dxdy_to_dir4 ($self->dir_maximum_dxdy); } sub _NumSeq_Dir4_max_is_supremum { my ($self) = @_; return ($self->_NumSeq_Delta_Dir4_max == 4); } use constant _NumSeq_Dir4_min_is_infimum => 0; sub _NumSeq_Delta_Dir4_integer { my ($self) = @_; { my @_UNDOCUMENTED__dxdy_list = $self->_UNDOCUMENTED__dxdy_list; for (my $i = 0; $i < $#_UNDOCUMENTED__dxdy_list; $i+=2) { unless (_dxdy_is_dir4($_UNDOCUMENTED__dxdy_list[$i], $_UNDOCUMENTED__dxdy_list[$i+1])) { return 0; } } } my ($dx,$dy) = $self->dir_minimum_dxdy; if ($dx && $dy) { return 0; } # diagonal ($dx,$dy) = $self->dir_maximum_dxdy; return ! (($dx && $dy) # diagonal || ($dx==0 && $dy==0)); # supremum } #------------ # TDir6 sub _NumSeq_Delta_TDir6_min { my ($self) = @_; return Math::NumSeq::PlanePathDelta::_dir360_to_tdir6 ($self->_NumSeq_Delta_Dir4_min * 90); } sub _NumSeq_Delta_TDir6_max { my ($self) = @_; return Math::NumSeq::PlanePathDelta::_dir360_to_tdir6 ($self->_NumSeq_Delta_Dir4_max * 90); } sub _NumSeq_TDir6_max_is_supremum { return $_[0]->_NumSeq_Dir4_max_is_supremum; } sub _NumSeq_TDir6_min_is_infimum { return $_[0]->_NumSeq_Dir4_min_is_infimum; } sub _NumSeq_Delta_TDir6_integer { my ($self) = @_; { my @_UNDOCUMENTED__dxdy_list = $self->_UNDOCUMENTED__dxdy_list; for (my $i = 0; $i < @_UNDOCUMENTED__dxdy_list; $i+=2) { unless (_dxdy_is_tdir6($_UNDOCUMENTED__dxdy_list[$i], $_UNDOCUMENTED__dxdy_list[$i+1])) { return 0; } } } my ($dx,$dy) = $self->dir_minimum_dxdy; if ($dy != 0 && abs($dx) != abs($dy)) { return 0; } # not diagonal or horiz ($dx,$dy) = $self->dir_maximum_dxdy; return ! (($dy != 0 && abs($dx) != abs($dy)) # not diagonal or horiz || ($dx==0 && $dy==0)); # supremum } sub _dxdy_is_dir4 { my ($dx,$dy) = @_; return ($dx == 0 || $dy == 0); } sub _dxdy_is_tdir6 { my ($dx,$dy) = @_; return ($dy == 0 || abs($dx)==abs($dy)); } #------------ sub _NumSeq_Delta_Dist_min { my ($self) = @_; sqrt($self->_NumSeq_Delta_DSquared_min); } sub _NumSeq_Delta_Dist_max { my ($self) = @_; my $max; return (defined ($max = $self->_NumSeq_Delta_DSquared_max) ? sqrt($max) : undef); } sub _NumSeq_Delta_TDist_min { my ($self) = @_; sqrt($self->_NumSeq_Delta_TDSquared_min); } sub _NumSeq_Delta_TDist_max { my ($self) = @_; my $max; return (defined ($max = $self->_NumSeq_Delta_TDSquared_max) ? sqrt($max) : undef); } # Default Dist min from AbsdX,AbsdY min. # Subclass must overridde if those minimums don't occur together. sub _NumSeq_Delta_DSquared_min { my ($self) = @_; my $dx = $self->absdx_minimum; my $dy = $self->absdy_minimum; return _max (1, $dx*$dx + $dy*$dy); } sub _NumSeq_Delta_TDSquared_min { my ($self) = @_; my $dx = $self->absdx_minimum; my $dy = $self->absdy_minimum; return _max (1, $dx*$dx + 3*$dy*$dy); } # Default Dist max from AbsdX,AbsdY max, if maximums exist. # Subclass must overridde if those maximums don't occur together. sub _NumSeq_Delta_DSquared_max { my ($self) = @_; if (defined (my $dx = $self->absdx_maximum) && defined (my $dy = $self->absdy_maximum)) { return ($dx*$dx + $dy*$dy); } else { return undef; } } sub _NumSeq_Delta_TDSquared_max { my ($self) = @_; if (defined (my $dx = $self->absdx_maximum) && defined (my $dy = $self->absdy_maximum)) { return ($dx*$dx + 3*$dy*$dy); } else { return undef; } } *_NumSeq_Delta_DSquared_integer = \&_NumSeq_Delta_dSum_integer; *_NumSeq_Delta_TDSquared_integer = \&_NumSeq_Delta_dSum_integer; use constant _NumSeq_Delta_Dir360_min => 0; use constant _NumSeq_Delta_Dir360_max => 360; } { package Math::PlanePath::Base::NSEW; use constant _NumSeq_Delta_dSum_integer => 1; use constant _NumSeq_Delta_dSumAbs_integer => 1; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dDiffXY_integer => 1; use constant _NumSeq_Delta_dAbsDiff_integer => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_DSquared_max => 1; # NSEW unit steps } { package Math::PlanePath::SquareSpiral; # at X=-k,Y=k TRad = sqrt((-k)^2 + 3*k^2) # = 2k # to X=-k,Y=k-1 TRad = sqrt((-k)^2 + 3*(k-1)^2) # = sqrt(4*k^2 - 6k + 3) # dTRad = sqrt(4*k^2 - 6k + 3) - 2k # -> 1.5 # # -k, k*sqrt(3) * arc approaches straight line # |\ hypot = sqrt(3) # | \ angle=30,30,120 stretched from 45deg # | /\ tan 30 = x / (sqrt(3)/2) # |/ \ sqrt(3)*sqrt(3)/2 = x # -k, (k-1)*sqrt(3) * . x = 3/2 # \ . # .. . # O # use constant _NumSeq_Delta_dTRadius_min => -3/2; use constant _NumSeq_Delta_dTRadius_max => 3/2; use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_dTRadius_max_is_supremum => 1; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; use constant _NumSeq_Delta_oeis_anum => { 'wider=0,n_start=1' => { AbsdY => 'A079813', # k 0s then k 1s plus initial 1 is abs(dY) # OEIS-Catalogue: A079813 planepath=SquareSpiral delta_type=AbsdY }, }; } { package Math::PlanePath::GreekKeySpiral; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; sub _NumSeq_Delta_dTRadius_min { my ($self) = @_; return ($self->{'turns'} == 0 ? -1.5 # per SquareSpiral : - sqrt(3)); } use constant _NumSeq_Delta_dTRadius_max => sqrt(3); use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_dTRadius_max_is_supremum => 1; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } { package Math::PlanePath::PyramidSpiral; use constant _NumSeq_Delta_AbsdX_non_decreasing => 1; # constant absdx=1 use constant _NumSeq_Delta_dSumAbs_min => -2; # near diagonal, eg. N=10 use constant _NumSeq_Delta_dSumAbs_max => 2; # near diagonal, eg. N=4 use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; # use constant _NumSeq_Delta_dRadius_min => -sqrt(2); # use constant _NumSeq_Delta_dRadius_max => sqrt(2); use constant _NumSeq_Delta_TDir6_integer => 1; use constant _NumSeq_Delta_DSquared_max => 2; use constant _NumSeq_Delta_oeis_anum => { 'n_start=0' => { dX => 'A339265', # runs +1,-1, OFFSET=0 # OEIS-Catalogue: A339265 planepath=PyramidSpiral,n_start=0 delta_type=dX }, }; } { package Math::PlanePath::TriangleSpiral; use constant _NumSeq_Delta_dSumAbs_min => -2; use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_TDir6_integer => 1; use constant _NumSeq_Delta_DSquared_min => 2; use constant _NumSeq_Delta_DSquared_max => 4; use constant _NumSeq_Delta_TDSquared_min => 4; # triangular use constant _NumSeq_Delta_TDSquared_max => 4; # triangular use constant _NumSeq_Delta_TDist_non_decreasing => 1; # triangular } { package Math::PlanePath::TriangleSpiralSkewed; use constant _NumSeq_Delta_dSumAbs_min => -2; use constant _NumSeq_Delta_dSumAbs_max => 2; { my %_NumSeq_Delta_dAbsDiff_min = (left => -2, # North-West right => -1, # N up => -1, # W down => -2); # North-West sub _NumSeq_Delta_dAbsDiff_min { my ($self) = @_; return $_NumSeq_Delta_dAbsDiff_min{$self->{'skew'}}; } } { my %_NumSeq_Delta_dAbsDiff_max = (left => 2, # South-East right => 1, # S up => 1, # S down => 2); # South-East sub _NumSeq_Delta_dAbsDiff_max { my ($self) = @_; return $_NumSeq_Delta_dAbsDiff_max{$self->{'skew'}}; } } use constant _NumSeq_Delta_DSquared_max => 2; # A204435 f(i,j)=((i+j )^2 mod 3), antidiagonals # A204437 f(i,j)=((i+j+1)^2 mod 3), antidiagonals # A204439 f(i,j)=((i+j+2)^2 mod 3), antidiagonals # gives 0s at every third antidiagonal use constant _NumSeq_Delta_oeis_anum => { 'skew=left,n_start=1' => { AbsdX => 'A204439', AbsdY => 'A204437', # OEIS-Catalogue: A204439 planepath=TriangleSpiralSkewed,skew=left delta_type=AbsdX # OEIS-Catalogue: A204437 planepath=TriangleSpiralSkewed,skew=left delta_type=AbsdY }, 'skew=right,n_start=1' => { AbsdX => 'A204435', AbsdY => 'A204437', # OEIS-Catalogue: A204435 planepath=TriangleSpiralSkewed,skew=right delta_type=AbsdX # OEIS-Other: A204437 planepath=TriangleSpiralSkewed,skew=right delta_type=AbsdY }, 'skew=up,n_start=1' => { AbsdX => 'A204439', AbsdY => 'A204435', # OEIS-Other: A204439 planepath=TriangleSpiralSkewed,skew=up delta_type=AbsdX # OEIS-Other: A204435 planepath=TriangleSpiralSkewed,skew=up delta_type=AbsdY }, 'skew=down,n_start=1' => { AbsdX => 'A204435', AbsdY => 'A204439', # OEIS-Other: A204435 planepath=TriangleSpiralSkewed,skew=down delta_type=AbsdX # OEIS-Other: A204439 planepath=TriangleSpiralSkewed,skew=down delta_type=AbsdY }, }; } { package Math::PlanePath::DiamondSpiral; use constant _NumSeq_Delta_AbsdX_non_decreasing => 1; # constant absdx=1 use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_DSquared_max => 2; use constant _NumSeq_Delta_TDir6_integer => 1; use constant _NumSeq_Delta_oeis_anum => { 'n_start=1' => { AbsdX => 'A000012', # all 1s, starting OFFSET=1 # OEIS-Other: A000012 planepath=DiamondSpiral delta_type=AbsdX }, 'n_start=0' => { dSumAbs => 'A003982', # characteristic of A001844 Y_neg axis # catalogue here in absence of anything else in NumSeq # OEIS-Catalogue: A003982 planepath=DiamondSpiral,n_start=0 delta_type=dSumAbs }, }; } { package Math::PlanePath::AztecDiamondRings; use constant _NumSeq_Delta_dSumAbs_min => -1; # change of diamond use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_dRadius_min => -1; use constant _NumSeq_Delta_dRadius_max => 1; use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_Delta_dTRadius_min => - sqrt(3); use constant _NumSeq_Delta_dTRadius_max => sqrt(3); use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_dTRadius_max_is_supremum => 1; use constant _NumSeq_Delta_oeis_anum => { 'n_start=0' => { AbsdY => 'A023532', # 0 at n=k*(k+3)/2, 1 otherwise # OEIS-Catalogue: A023532 planepath=AztecDiamondRings,n_start=0 delta_type=AbsdY }, }; } { package Math::PlanePath::PentSpiral; use constant _NumSeq_Delta_dSumAbs_min => -2; use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_dAbsDiff_min => -3; use constant _NumSeq_Delta_dAbsDiff_max => 3; use constant _NumSeq_Delta_dRadius_min => -2; use constant _NumSeq_Delta_dRadius_max => 2; # at N=1 use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_Delta_dTRadius_min => -2; use constant _NumSeq_Delta_dTRadius_max => 2; use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_Delta_DSquared_min => 2; use constant _NumSeq_Delta_DSquared_max => 5; } { package Math::PlanePath::PentSpiralSkewed; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_dRadius_min => -1; use constant _NumSeq_Delta_dRadius_max => 1; # at N=1 use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_Delta_dTRadius_min => - sqrt(3); use constant _NumSeq_Delta_dTRadius_max => sqrt(3); use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_dTRadius_max_is_supremum => 1; use constant _NumSeq_Delta_DSquared_max => 2; } { package Math::PlanePath::HexSpiral; use constant _NumSeq_Delta_dSumAbs_min => -2; use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; sub _NumSeq_Delta_dRadius_min { my ($self) = @_; return ($self->{'wider'} < 2 ? -sqrt(2) : -2); # exact -2 along X axis initial horizontal } use constant _NumSeq_Delta_dRadius_max => 2; sub _NumSeq_dRadius_min_is_infimum { my ($self) = @_; return ($self->{'wider'} < 2); } sub _NumSeq_Delta_dTRadius_min { my ($self) = @_; return ($self->{'wider'} < 2 ? -1 : -2); # exact -2 along X axis initial horizontal } use constant _NumSeq_Delta_dTRadius_max => 2; # N=1 along X axis *_NumSeq_dTRadius_min_is_infimum = \&_NumSeq_dRadius_min_is_infimum; use constant _NumSeq_Delta_TDir6_integer => 1; use constant _NumSeq_Delta_DSquared_min => 2; use constant _NumSeq_Delta_DSquared_max => 4; use constant _NumSeq_Delta_TDist_non_decreasing => 1; # triangular use constant _NumSeq_Delta_TDSquared_max => 4; # triangular } { package Math::PlanePath::HexSpiralSkewed; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_dRadius_min => -1; use constant _NumSeq_Delta_dRadius_max => 1; sub _NumSeq_dRadius_min_is_infimum { my ($self) = @_; return ($self->{'wider'} == 0); } use constant _NumSeq_Delta_dTRadius_min => -1.5; use constant _NumSeq_Delta_dTRadius_max => sqrt(3); use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_dTRadius_max_is_supremum => 1; use constant _NumSeq_Delta_DSquared_max => 2; } { package Math::PlanePath::HeptSpiralSkewed; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_dTRadius_min => -1.5; use constant _NumSeq_Delta_dTRadius_max => sqrt(3); use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_dTRadius_max_is_supremum => 1; use constant _NumSeq_Delta_DSquared_max => 2; } { package Math::PlanePath::OctagramSpiral; use constant _NumSeq_Delta_dSumAbs_min => -2; use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; # dTRadius -> (dX + S*dY) / sqrt(1+S^2) # S = 2*sqrt(3)/1 = sqrt(12) # dX = 1 # dY = sqrt(3) # dTRadius = (1+sqrt(12)*sqrt(3)) / sqrt(13) # = 7/sqrt(13) # = 1.941450686788301927064196067 # # 1/2 # *-----------* # | . # | . # sqrt(3) | . # | . H = sqrt(13)/2 # | . # | . # 1/2 |. # *---------* # . h # p . . # * # H = sqrt(sqrt(3)^2 + (1/2)^2) = sqrt(13)/2 # p/h = sqrt(3)/(1/2) # p = sqrt(3)/(1/2) * h # h^2 + p^2 = (1/2)^2 # h^2 + (sqrt(3)/(1/2))^2 * h^2 = (1/2)^2 # h^2 + 12*h^2 = (1/2)^2 # h^2 = 1/52 # h = 1/sqrt(52) # H+h = sqrt(13)/2 + 1/sqrt(52) # = 1.941450686788301927064196067 # cf x=1000000000; y=2*x; sqrt(x^2+3*y^2) - sqrt((x-1)^2+3*(y-1)^2) # = 1.941450686756299992650077330 # use constant _NumSeq_Delta_dTRadius_max => sqrt(13)/2 + 1/sqrt(52); use constant _NumSeq_Delta_dTRadius_min => - _NumSeq_Delta_dTRadius_max; use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_dTRadius_max_is_supremum => 1; use constant _NumSeq_Delta_DSquared_max => 2; } { package Math::PlanePath::AnvilSpiral; use constant _NumSeq_Delta_AbsdX_non_decreasing => 1; # constant use constant _NumSeq_Delta_dSumAbs_min => -2; use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_TDir6_integer => 1; use constant _NumSeq_Delta_DSquared_max => 2; use constant _NumSeq_Delta_oeis_anum => { 'wider=0,n_start=0' => { AbsdX => 'A000012', # all 1s, OFFSET=0 # OEIS-Other: A000012 planepath=AnvilSpiral,n_start=0 delta_type=AbsdX }, }; } { package Math::PlanePath::KnightSpiral; use constant _NumSeq_Delta_dSumAbs_min => -3; use constant _NumSeq_Delta_dSumAbs_max => 3; use constant _NumSeq_Delta_dAbsDiff_min => -3; use constant _NumSeq_Delta_dAbsDiff_max => 3; use constant _NumSeq_Delta_DSquared_min => 2*2+1*1; # dX=1,dY=2 use constant _NumSeq_Delta_DSquared_max => 2*2+1*1; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_min => 2*2 + 3*1*1; # dX=2,dY=1 use constant _NumSeq_Delta_TDSquared_max => 1*1 + 3*2*2; # dX=1,dY=2 } { package Math::PlanePath::CretanLabyrinth; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; } { package Math::PlanePath::SquareArms; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_dRadius_min => -1/sqrt(2); use constant _NumSeq_Delta_dRadius_max => 1; use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_Delta_dTRadius_min => -1.5; use constant _NumSeq_Delta_dTRadius_max => sqrt(3); # at N=1 use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; # vertical } { package Math::PlanePath::DiamondArms; # diag always use constant _NumSeq_Delta_AbsdX_non_decreasing => 1; # constant absdx=1 use constant _NumSeq_Delta_AbsdY_non_decreasing => 1; # constant absdy=1 use constant _NumSeq_Delta_dSumAbs_min => 0; # only outwards use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_dRadius_min => -1; use constant _NumSeq_Delta_dRadius_max => sqrt(2); use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_Delta_dTRadius_min => -sqrt(3); use constant _NumSeq_Delta_dTRadius_max => 2; # at N=1 use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_Delta_TDir6_integer => 1; use constant _NumSeq_Delta_DSquared_min => 2; # diagonal always use constant _NumSeq_Delta_DSquared_max => 2; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_min => 4; # diagonal always use constant _NumSeq_Delta_TDSquared_max => 4; use constant _NumSeq_Delta_TDist_non_decreasing => 1; } { package Math::PlanePath::HexArms; use constant _NumSeq_Delta_dSumAbs_min => -2; use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_TDir6_integer => 1; use constant _NumSeq_Delta_dRadius_min => - sqrt(2); # diagonal use constant _NumSeq_Delta_dRadius_max => sqrt(10) - sqrt(2); # at N=4 use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_Delta_dTRadius_min => -1; use constant _NumSeq_Delta_dTRadius_max => 2; # at N=1 use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_Delta_DSquared_min => 2; use constant _NumSeq_Delta_DSquared_max => 4; use constant _NumSeq_Delta_TDSquared_max => 4; # triangular use constant _NumSeq_Delta_TDist_non_decreasing => 1; # triangular } { package Math::PlanePath::SacksSpiral; use constant _NumSeq_Delta_dX_integer => 0; use constant _NumSeq_Delta_dY_integer => 0; use constant _NumSeq_Delta_dSumAbs_min => - 2*atan2(1,0); # -pi use constant _NumSeq_Delta_dSumAbs_max => 2*atan2(1,0); # +pi use constant _NumSeq_AbsdX_min_is_infimum => 1; use constant _NumSeq_Delta_dRadius_min => 0; use constant _NumSeq_Delta_dRadius_max => 1; # at N=0 horiz along X axis use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_Delta_dRSquared_min => 1; # always R^2+1 use constant _NumSeq_Delta_dRSquared_max => 1; use constant _NumSeq_Delta_dRSquared_integer => 1; use constant _NumSeq_Delta_Dist_increasing => 1; # each step bigger } { package Math::PlanePath::VogelFloret; use constant _NumSeq_Delta_dX_integer => 0; use constant _NumSeq_Delta_dY_integer => 0; use constant _NumSeq_AbsdX_min_is_infimum => 1; use constant _NumSeq_AbsdY_min_is_infimum => 1; use constant _NumSeq_Delta_dRadius_min => 0; # diagonal sub _NumSeq_Delta_dRadius_max { my ($self) = @_; return ($self->n_to_radius($self->n_start + 1) - $self->n_to_radius($self->n_start)); } use constant _NumSeq_dRadius_min_is_infimum => 1; # R1^2 = (sqrt(N) * radius_factor)^2 # = N * radius_factor^2 # R2^2 = (N+1) * radius_factor^2 # R2^2-R1^2 = radius_factor^2 constant # sub _NumSeq_Delta_dRSquared_min { my ($self) = @_; return ($self->{'radius_factor'} ** 2); } *_NumSeq_Delta_dRSquared_max = \&_NumSeq_Delta_dRSquared_min; sub _NumSeq_Delta_dRSquared_integer { my ($self) = @_; my $rf_squared = $self->{'radius_factor'} ** 2; return ($rf_squared == int($rf_squared)); } use constant _NumSeq_Dir4_min_is_infimum => 1; use constant _NumSeq_Dir4_max_is_supremum => 1; } { package Math::PlanePath::TheodorusSpiral; use constant _NumSeq_Delta_dX_integer => 0; use constant _NumSeq_Delta_dY_integer => 0; use constant _NumSeq_dX_min_is_infimum => 1; use constant _NumSeq_dY_min_is_infimum => 1; use constant _NumSeq_dSum_min_is_infimum => 1; use constant _NumSeq_dSum_max_is_supremum => 1; use constant _NumSeq_Delta_dSumAbs_min => -1; # supremum vert/horiz use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_dSumAbs_min_is_infimum => 1; use constant _NumSeq_dDiffXY_min_is_infimum => 1; use constant _NumSeq_dDiffXY_max_is_supremum => 1; use constant _NumSeq_Delta_dAbsDiff_min => -sqrt(2); # supremum diagonal use constant _NumSeq_Delta_dAbsDiff_max => sqrt(2); use constant _NumSeq_dAbsDiff_min_is_infimum => 1; use constant _NumSeq_dAbsDiff_max_is_supremum => 1; use constant _NumSeq_Delta_dRadius_min => 0; use constant _NumSeq_Delta_dRadius_max => 1; # at N=0 horiz along X axis use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_Delta_dRSquared_min => 1; # always R^2+1 use constant _NumSeq_Delta_dRSquared_max => 1; use constant _NumSeq_Delta_dRSquared_integer => 1; use constant _NumSeq_Delta_dTRadius_min => -sqrt(2); use constant _NumSeq_Delta_dTRadius_max => 1; # at N=0 horiz along X axis use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_Delta_DSquared_max => 1; # constant 1 use constant _NumSeq_Delta_Dist_non_decreasing => 1; # constant 1 use constant _NumSeq_Delta_TDSquared_max => 3; # vertical } { package Math::PlanePath::ArchimedeanChords; use constant _NumSeq_Delta_dX_integer => 0; use constant _NumSeq_Delta_dY_integer => 0; use constant _NumSeq_dX_min_is_infimum => 1; use constant _NumSeq_AbsdX_min_is_infimum => 1; use constant _NumSeq_dY_min_is_infimum => 1; use constant _NumSeq_dY_max_is_supremum => 1; use constant _NumSeq_dSum_min_is_infimum => 1; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_dSumAbs_min_is_infimum => 1; use constant _NumSeq_dDiffXY_min_is_infimum => 1; use constant _NumSeq_Delta_dAbsDiff_min => -sqrt(2); # supremum when diagonal use constant _NumSeq_Delta_dAbsDiff_max => sqrt(2); use constant _NumSeq_dAbsDiff_min_is_infimum => 1; use constant _NumSeq_Delta_dRadius_min => 0; use constant _NumSeq_Delta_dRadius_max => 1; # at N=0 use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_Delta_dRSquared_min => -1/(4*atan2(1,1)); # -1/pi use constant _NumSeq_Delta_dRSquared_max => 1; # at N=0 use constant _NumSeq_Delta_DSquared_max => 1; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; # supremum use constant _NumSeq_TDSquared_max_is_supremum => 1; use constant _NumSeq_Dir4_max_is_supremum => 1; } { package Math::PlanePath::MultipleRings; sub _NumSeq_Delta__step_is_0 { my ($self) = @_; return ($self->{'step'} == 0); # constant when column only } #--------- # dX sub _NumSeq_dX_min_is_infimum { my ($self) = @_; if ($self->{'step'} == 0) { return 0; # horizontal only, exact } return 1; # infimum } sub _NumSeq_dX_max_is_supremum { my ($self) = @_; return ($self->{'step'} <= 6 ? 0 : 1); # supremum } *_NumSeq_Delta_dX_non_decreasing = \&_NumSeq_Delta__step_is_0; # constant dX=1,dY=0 *_NumSeq_Delta_dX_integer = \&_NumSeq_Delta__step_is_0; #--------- # dY *_NumSeq_dY_max_is_supremum = \&_NumSeq_dX_min_is_infimum; *_NumSeq_dY_min_is_infimum = \&_NumSeq_dX_min_is_infimum; *_NumSeq_Delta_dY_non_decreasing = \&_NumSeq_Delta__step_is_0; *_NumSeq_Delta_dY_integer = \&_NumSeq_Delta__step_is_0; #--------- # AbsdX sub _NumSeq_AbsdX_min_is_infimum { my ($self) = @_; if ($self->{'step'} == 1) { return 0; # horizontal only } if ($self->{'step'} % 2 == 1) { return 0; # any odd num sides has left vertical dX=0 exactly } return $self->_NumSeq_dX_min_is_infimum; } *_NumSeq_Delta_AbsdX_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; #--------- # AbsdY sub _NumSeq_Delta_AbsdY_non_decreasing { my ($self) = @_; if ($self->{'ring_shape'} eq 'polygon' && $self->{'step'} == 4) { return 1; # abs(dY) constant } return $self->_NumSeq_Delta_dY_non_decreasing; } #--------- # dSum *_NumSeq_dSum_max_is_supremum = \&_NumSeq_dX_min_is_infimum; *_NumSeq_dSum_min_is_infimum = \&_NumSeq_dX_min_is_infimum; *_NumSeq_Delta_dSum_non_decreasing = \&_NumSeq_Delta__step_is_0; #--------- # dDiffXY *_NumSeq_dDiffXY_min_is_infimum = \&_NumSeq_dX_min_is_infimum; *_NumSeq_dDiffXY_max_is_supremum = \&_NumSeq_dX_min_is_infimum; *_NumSeq_Delta_dDiffXY_non_decreasing = \&_NumSeq_Delta__step_is_0; #--------- # dDiffYX *_NumSeq_Delta_dDiffYX_non_decreasing = \&_NumSeq_Delta__step_is_0; #--------- # dSumAbs *_NumSeq_Delta_dSumAbs_non_decreasing = \&_NumSeq_Delta__step_is_0; #--------- # dAbsDiff sub _NumSeq_Delta_dAbsDiff_min { my ($self) = @_; return ($self->{'step'} == 0 ? 1 # always dX=+1,dY=0 so d(abs(X-Y))=1 always # FIXME: side length some maximum? : undef); } sub _NumSeq_Delta_dAbsDiff_max { my ($self) = @_; return ($self->{'step'} == 0 ? 1 # always dX=+1,dY=0 so d(abs(X-Y))=1 always # FIXME: side length some maximum? : undef); } *_NumSeq_Delta_dAbsDiff_non_decreasing = \&_NumSeq_Delta__step_is_0; #--------- # dRadius,dRSquared sub _NumSeq_Delta_dRadius_min { my ($self) = @_; return ($self->{'step'} == 0 ? 1 # step=0 always dRadius=+1 : $self->{'ring_shape'} eq 'circle' ? 0 # within circle dRadius=0 : undef); } sub _NumSeq_Delta_dRadius_max { my ($self) = @_; return ($self->{'step'} == 0 ? 1 # always dRadius=+1 : $self->{'ring_shape'} eq 'circle' ? 1 : undef); } sub _NumSeq_Delta_dRadius_integer { my ($self) = @_; return ($self->{'step'} <= 1 || $self->{'step'} == 6); } *_NumSeq_Delta_dRSquared_min = \&_NumSeq_Delta_dRadius_min; *_NumSeq_Delta_dRSquared_increasing = \&_NumSeq_Delta__step_is_0; # step==0 *_NumSeq_Delta_dRSquared_integer = \&_NumSeq_Delta_dRadius_integer; #--------- # dTRadius,dTRSquared # step odd vertical sub _NumSeq_Delta_dTRadius_min { my ($self) = @_; return ($self->{'step'} == 0 ? 1 # step=0 always dTRadius=+1 : undef); } sub _NumSeq_Delta_dTRadius_max { my ($self) = @_; return ($self->{'step'} == 0 ? 1 # always dTRadius=+1 : undef); } *_NumSeq_Delta_dTRadius_integer = \&_NumSeq_Delta__step_is_0; *_NumSeq_Delta_dTRSquared_min = \&_NumSeq_Delta_dTRadius_min; *_NumSeq_Delta_dTRSquared_increasing = \&_NumSeq_Delta__step_is_0; *_NumSeq_Delta_dTRSquared_integer = \&_NumSeq_Delta__step_is_0; #--------- # DSquared sub _NumSeq_Delta_DSquared_max { my ($self) = @_; return ($self->{'step'} == 0 ? 1 # horizontal only : $self->{'step'} <= 6 ? ((8*atan2(1,1)) / $self->{'step'}) ** 2 # step > 6, between rings : ((0.5/_PI()) * $self->{'step'}) ** 2); } *_NumSeq_Delta_Dist_non_decreasing = \&_NumSeq_Delta__step_is_0; *_NumSeq_Delta_TDist_non_decreasing = \&_NumSeq_Delta__step_is_0; #----------- # Dir4,TDir6 *_NumSeq_Delta_Dir4_non_decreasing = \&_NumSeq_Delta__step_is_0; *_NumSeq_Delta_TDir6_non_decreasing = \&_NumSeq_Delta__step_is_0; *_NumSeq_Delta_Dir4_integer = \&_NumSeq_Delta__step_is_0; *_NumSeq_Delta_TDir6_integer = \&_NumSeq_Delta__step_is_0; use constant _NumSeq_Delta_oeis_anum => { # MultipleRings step=0 is trivial X=N,Y=0 'step=0,ring_shape=circle' => { dX => 'A000012', # all 1s dY => 'A000004', # all-zeros Dir4 => 'A000004', # all zeros, East TDir6 => 'A000004', # all zeros, East # OEIS-Other: A000012 planepath=MultipleRings,step=0 delta_type=dX # OEIS-Other: A000004 planepath=MultipleRings,step=0 delta_type=dY # OEIS-Other: A000004 planepath=MultipleRings,step=0 delta_type=Dir4 # OEIS-Other: A000004 planepath=MultipleRings,step=0 delta_type=TDir6 }, }; } { package Math::PlanePath::PixelRings; # NSEW+diag use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_DSquared_max => 5; # dx=2,dy=1 at jump N=5 to N=6 } { package Math::PlanePath::FilledRings; # NSEW+diag use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_DSquared_max => 2; } { package Math::PlanePath::Hypot; use constant _NumSeq_Delta_dRadius_min => 0; use constant _NumSeq_Delta_dRSquared_min => 0; { my %_NumSeq_Delta_dRadius_max = (all => 1, even => sqrt(2), # N=1 odd => sqrt(5)-1, # N=4 0,1 -> 2,1 ); sub _NumSeq_Delta_dRadius_max { my ($self) = @_; return $_NumSeq_Delta_dRadius_max{$self->{'points'}}; } } # approaches horizontal use constant _NumSeq_Dir4_max_is_supremum => 1; sub _NumSeq_Delta_DSquared_min { my ($self) = @_; return ($self->{'points'} eq 'all' ? 1 # dX=1,dY=0 : 2); # dX=1,dY=1 } sub _NumSeq_Delta_TDSquared_min { my ($self) = @_; return ($self->{'points'} eq 'all' ? 1 # dX=1,dY=0 : 4); # dX=1,dY=1 } } { package Math::PlanePath::HypotOctant; use constant _NumSeq_Delta_dRadius_min => 0; use constant _NumSeq_Delta_dRSquared_min => 0; { my %_NumSeq_Delta_dRadius_max = (all => 1, # N=1 even => sqrt(2), # N=1 odd => sqrt(5)-1, # N=1 1,0 -> 2,1 ); sub _NumSeq_Delta_dRadius_max { my ($self) = @_; return $_NumSeq_Delta_dRadius_max{$self->{'points'}}; } } sub _NumSeq_Delta_DSquared_min { my ($self) = @_; return ($self->{'points'} eq 'all' ? 1 # dX=1,dY=0 : 2); # dX=1,dY=1 } sub _NumSeq_Delta_TDSquared_min { my ($self) = @_; return ($self->{'points'} eq 'all' ? 1 # dX=1,dY=0 : 4); # dX=1,dY=1 } use constant _NumSeq_Delta_TDir6_integer => 0; } { package Math::PlanePath::TriangularHypot; # approaches horizontal use constant _NumSeq_Dir4_max_is_supremum => 1; # non-decreasing TRadius use constant _NumSeq_Delta_dTRadius_min => 0; { my %_NumSeq_Delta_dTRadius_max = (odd => 1, all => 1, # at N=1 ); sub _NumSeq_Delta_dTRadius_max { my ($self) = @_; return $_NumSeq_Delta_dTRadius_max{$self->{'points'}} || 2; } } sub _NumSeq_dTRadius_max_is_supremum { my ($self) = @_; return $self->{'points'} eq 'odd'; } use constant _NumSeq_Delta_dTRSquared_min => 0; sub _NumSeq_Delta_DSquared_min { my ($self) = @_; return ($self->{'points'} eq 'all' ? 1 # dX=1,dY=0 : 2); # dX=1,dY=1 } sub _NumSeq_Delta_TDSquared_min { my ($self) = @_; return ($self->{'points'} eq 'all' ? 1 # dX=1,dY=0 : 4); # dX=1,dY=1 } } { package Math::PlanePath::PythagoreanTree; { my %_NumSeq_Delta_dRadius_integer = ('AB' => 1, # Radius=C 'SM' => 1, # Radius=C ); sub _NumSeq_Delta_dRadius_integer { my ($self) = @_; return $_NumSeq_Delta_dRadius_integer{$self->{'coordinates'}}; } } { my %Dir4_min_is_infimum = ('BC,UAD' => 1, 'SM,UAD' => 1, 'SC,UAD' => 1, 'MC,UAD' => 1, 'AB,FB' => 1, 'AC,FB' => 1, 'BC,FB' => 1, 'PQ,FB' => 1, 'SM,FB' => 1, 'SC,FB' => 1, 'MC,FB' => 1, 'AC,UMT' => 1, 'SM,UMT' => 1, 'SC,UMT' => 1, ); sub _NumSeq_Dir4_min_is_infimum { my ($self) = @_; return $Dir4_min_is_infimum{"$self->{'coordinates'},$self->{'tree_type'}"}; } } { my %Dir4_max_is_supremum = ('BC,UAD' => 1, 'SM,UAD' => 1, 'SC,UAD' => 1, 'MC,UAD' => 1, 'AB,FB' => 1, 'AC,FB' => 1, 'PQ,FB' => 1, 'SM,FB' => 1, 'SC,FB' => 1, 'MC,FB' => 1, 'AB,UMT' => 1, 'BC,UMT' => 1, 'SM,UMT' => 1, 'SC,UMT' => 1, 'MC,UMT' => 1, ); sub _NumSeq_Dir4_max_is_supremum { my ($self) = @_; return $Dir4_max_is_supremum{"$self->{'coordinates'},$self->{'tree_type'}"}; } } use constant _NumSeq_Delta_TDir6_integer => 0; } { package Math::PlanePath::RationalsTree; { my %Dir4_min_is_infimum = (Drib => 1); sub _NumSeq_Dir4_min_is_infimum { my ($self) = @_; return $Dir4_min_is_infimum{$self->{'tree_type'}}; } } { my %Dir4_max_is_supremum = (CW => 1, AYT => 1, Drib => 1, L => 1); sub _NumSeq_Dir4_max_is_supremum { my ($self) = @_; return $Dir4_max_is_supremum{$self->{'tree_type'}}; } } use constant _NumSeq_Delta_TDir6_integer => 0; # vertical use constant _NumSeq_Delta_oeis_anum => { 'tree_type=L' => { dY => 'A070990', # Stern diatomic differences OFFSET=0 # OEIS-Catalogue: A070990 planepath=RationalsTree,tree_type=L delta_type=dY }, # 'tree_type=CW' => # { # # dY => 'A070990', # Stern diatomic first diffs, except it starts i=0 # # where RationalsTree N=1. dX is same, but has extra leading 0. # }, }; } { package Math::PlanePath::FractionsTree; use constant _NumSeq_Dir4_max_is_supremum => 1; } { package Math::PlanePath::ChanTree; sub _NumSeq_Dir4_min_is_infimum { my ($self) = @_; return ($self->{'k'} == 2 || ($self->{'k'} & 1) == 0 ? 0 # k=2 or k odd : 1); # k even } use constant _NumSeq_Dir4_max_is_supremum => 1; } { package Math::PlanePath::DiagonalRationals; use constant _NumSeq_TDSquared_min => 3; } { package Math::PlanePath::FactorRationals; use constant _NumSeq_Dir4_min_is_infimum => 1; use constant _NumSeq_Dir4_max_is_supremum => 1; } { package Math::PlanePath::CfracDigits; # radix=1 N=1 has dir4=0 # radix=2 N=5628 has dir4=0 dx=9,dy=0 # radix=3 N=1189140 has dir4=0 dx=1,dy=0 # radix=4 N=169405 has dir4=0 dx=2,dy=0 # always eventually 0 ? sub _NumSeq_Dir4_min_is_infimum { my ($self) = @_; return ($self->{'radix'} > 4); } use constant _NumSeq_Dir4_max_is_supremum => 1; use constant _NumSeq_Delta_Dir4_integer => 0; } { package Math::PlanePath::GcdRationals; sub _NumSeq_Delta_TDSquared_min { my ($self) = @_; return ($self->{'pairs_order'} eq 'diagonals_down' ? 3 # at N=1 vert : 1); # at N=4 horiz } use constant _NumSeq_Delta_TDir6_integer => 0; # vertical } { package Math::PlanePath::PeanoCurve; *_NumSeq_Delta_dAbsDiff_min = \&dx_minimum; *_NumSeq_Delta_dAbsDiff_max = \&dx_maximum; *_NumSeq_Delta_DSquared_max = \&dx_maximum; sub _NumSeq_Delta_Dist_non_decreasing { my ($self) = @_; return ($self->{'radix'} % 2 ? 1 # odd : 0); # even, jumps about } sub _NumSeq_Delta_TDSquared_max { my ($self) = @_; return ($self->{'radix'} % 2 ? 3 # odd, vertical : undef); # even, unlimited } sub _NumSeq_Delta_Dir4_integer { my ($self) = @_; return ($self->{'radix'} % 2 ? 1 # odd, continuous path : 0); # even, jumps } sub _NumSeq_Dir4_max_is_supremum { my ($self) = @_; return ($self->{'radix'} % 2 ? 0 # odd : 1); # even, supremum } # use constant _NumSeq_Delta_oeis_anum => # { 'radix=3' => # { # # Not quite, extra initial 0 # # AbsdX => 'A014578', # 1 - count low 0-digits, mod 2 # # # OEIS-Catalogue: A014578 planepath=PeanoCurve delta_type=AbsdX # # # # Not quite, OFFSET n=1 cf N=0 # # # # A163534 is 0=east,1=south,2=west,3=north treated as down page, # # # # which corrsponds to 1=north (incr Y), 3=south (decr Y) for # # # # directions of the PeanoCurve planepath here # # # Dir4 => 'A163534', # # # # OEIS-Catalogue: A163534 planepath=PeanoCurve delta_type=Dir4 # # # # # delta a(n)-a(n-1), so initial dx=0 at i=0 ... # # # dX => 'A163532', # # # # OEIS-Catalogue: A163532 planepath=PeanoCurve delta_type=dX # # # dY => 'A163533', # # # # OEIS-Catalogue: A163533 planepath=PeanoCurve delta_type=dY # }, # }; } # { package Math::PlanePath::PeanoDiagonals; # } { package Math::PlanePath::WunderlichSerpentine; sub _NumSeq_Delta_dAbsDiff_min { return $_[0]->dsumxy_minimum; } sub _NumSeq_Delta_dAbsDiff_max { return $_[0]->dsumxy_maximum; } # radix=2 0101 is straight NSEW parts, other evens are diagonal sub _NumSeq_Delta_Dir4_integer { my ($self) = @_; return (($self->{'radix'} % 2) || join('',@{$self->{'serpentine_array'}}) eq '0101' ? 1 # odd, continuous path : 0); # even, jumps } sub _NumSeq_Dir4_max_is_supremum { my ($self) = @_; return (($self->{'radix'} % 2) || join('',@{$self->{'serpentine_array'}}) eq '0101' ? 0 # odd, South : 1); # even, supremum } *_NumSeq_Delta_DSquared_max = \&Math::PlanePath::PeanoCurve::_NumSeq_Delta_DSquared_max; *_NumSeq_Delta_Dist_non_decreasing = \&Math::PlanePath::PeanoCurve::_NumSeq_Delta_Dist_non_decreasing; *_NumSeq_Delta_TDSquared_max = \&Math::PlanePath::PeanoCurve::_NumSeq_Delta_TDSquared_max; } { package Math::PlanePath::HilbertCurve; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; # only approached as steps towards origin are only at X=2 or Y=2 not on axes use constant _NumSeq_Delta_dRadius_max => 1; # at N=0 use constant _NumSeq_Delta_dRadius_min => -1; use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; # 'Math::PlanePath::HilbertCurve' => # { # # Not quite, OFFSET=1 at origin, cf path N=0 # # # A163540 is 0=east,1=south,2=west,3=north for drawing down the page, # # # which corresponds to 1=north,3=south per the HilbertCurve planepath # # Dir4 => 'A163540', # # # OEIS-Catalogue: A163540 planepath=HilbertCurve delta_type=Dir4 # # Not quite, # A163538 path(n)-path(n-1) starting i=0 with path(-1)=0 for # first value 0 # # dX => 'A163538', # # # OEIS-Catalogue: A163538 planepath=HilbertCurve delta_type=dX # # dY => 'A163539', # # # OEIS-Catalogue: A163539 planepath=HilbertCurve delta_type=dY # # # # cf A163541 absolute direction, transpose X,Y # # would be N=0,E=1,S=2,W=3 # }, } { package Math::PlanePath::HilbertSides; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; # only approached as steps towards origin are only at X=2 or Y=2 not on axes use constant _NumSeq_Delta_dRadius_max => 1; # at N=0 use constant _NumSeq_Delta_dRadius_min => -1; use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_Delta_oeis_anum => { '' => { AbsdX => 'A010059', # 1 - Thue-Morse binary parity AbsdY => 'A010060', # Thue-Morse binary parity # OEIS-Other: A010059 planepath=HilbertSides delta_type=AbsdX # OEIS-Other: A010060 planepath=HilbertSides delta_type=AbsdY }, }; } { package Math::PlanePath::HilbertSpiral; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_dRadius_max => 1; # at N=0 use constant _NumSeq_Delta_dRadius_min => -1; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } # { package Math::PlanePath::HilbertMidpoints; # use constant _NumSeq_Delta_DSquared_min => 2; # use constant _NumSeq_Delta_DSquared_max => 4; # } { package Math::PlanePath::ZOrderCurve; use constant _NumSeq_Delta_dRadius_max => 1; # diagonal up towards Y axis # X=1,Y TRsq = sqrt(1+3*Y^2) # X=0,Y+1 TRsq = sqrt(1+3*(Y+1)^2) # dTRsq = sqrt(1+3*(Y+1)^2) - sqrt(1+3*Y^2) # -> sqrt(3*(Y+1)^2) - sqrt(3*Y^2) # = sqrt(3)*(sqrt((Y+1)^2) - sqrt(Y^2)) # -> sqrt(3) # as Y -> infinity use constant _NumSeq_Delta_dTRadius_max => sqrt(3); use constant _NumSeq_dTRadius_max_is_supremum => 1; use constant _NumSeq_Delta_TDir6_integer => 0; # verticals } { package Math::PlanePath::GrayCode; sub _NumSeq_Delta_dSumAbs_min { return $_[0]->dsumxy_minimum; } sub _NumSeq_Delta_dSumAbs_max { return $_[0]->dsumxy_maximum; } sub _NumSeq_Delta_dAbsDiff_min { return $_[0]->ddiffxy_minimum; } sub _NumSeq_Delta_dAbsDiff_max { return $_[0]->ddiffxy_maximum; } { my %Dir4_integer = (reflected => { TsF => 1, FsT => 1, Ts => 1, Fs => 1, }, modular => { TsF => 1, Ts => 1, }, ); sub _NumSeq_Delta_Dir4_integer { my ($self) = @_; my $gray_type = ($self->{'radix'} == 2 ? 'reflected' : $self->{'gray_type'}); return $Dir4_integer{$gray_type}->{$self->{'apply_type'}}; } } use constant _NumSeq_Delta_TDir6_integer => 0; sub _NumSeq_Delta_Dist_non_decreasing { my ($self) = @_; return ($self->{'radix'} % 2 && $self->{'gray_type'} eq 'reflected' && ($self->{'apply_type'} eq 'TsF' || $self->{'apply_type'} eq 'FsT') ? 1 # PeanoCurve style NSEW only : 0); } } { package Math::PlanePath::ImaginaryBase; # Dir4 radix=2 goes south-east at # N=2^3-1=7 # N=2^7-1=127 # N=2^11-1=2047 # N=2^15-1=32767 # dx=0x555555 # dy=-0xAAAAAB # approaches dx=1,dy=-2 # # radix=3 # dy=dx+1 approches SE # # radix=4 dx/dy=1.5 # radix=5 dx/dy=2 # dx/dy=(radix-1)/2 use constant _NumSeq_Dir4_max_is_supremum => 1; use constant _NumSeq_Delta_TDir6_integer => 0; } { package Math::PlanePath::ImaginaryHalf; { my %_NumSeq_Dir4_min_is_infimum = (XYX => 0, XXY => 0, YXX => 1, # dX=big,dY=1 XnYX => 1, # dX=big,dY=1 XnXY => 0, # dX=1,dY=0 at N=1 YXnX => 1, # dX=big,dY=1 ); sub _NumSeq_Dir4_min_is_infimum { my ($self) = @_; return $_NumSeq_Dir4_min_is_infimum{$self->{'digit_order'}}; } } use constant _NumSeq_Dir4_max_is_supremum => 1; use constant _NumSeq_Delta_TDir6_integer => 0; } { package Math::PlanePath::CubicBase; use constant _NumSeq_Delta_DSquared_min => 4; # at X=0 to X=2 # direction supremum maybe at # dx=-0b 1001001001001001... = - (8^k-1)/7 # dy=-0b11011011011011011... = - (3*8^k-1)/7 # which is # dx=-1, dy=-3 use constant _NumSeq_Dir4_max_is_supremum => 1; use constant _NumSeq_Delta_TDSquared_min => 4; # at N=0 dX=2,dY=1 } # { package Math::PlanePath::Flowsnake; # # (inherits from FlowsnakeCentres) # # # Not quite, A261180 OFFSET=1 whereas n_start=0 here # # use constant _NumSeq_Delta_oeis_anum => # # { 'n_start=1' => # # { TDir6 => 'A261180', # # # OEIS-Catalogue: A261180 planepath=Flowsnake delta_type=TDir6 # # }, # # }; # } { package Math::PlanePath::FlowsnakeCentres; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; # dRadius_min at arms=1 N=8591 # arms=2 N=85 # arms=3 N=127 use constant _NumSeq_Delta_dRadius_min => -2; use constant _NumSeq_Delta_dRadius_max => 2; # at N=0 use constant _NumSeq_Delta_dTRadius_min => -2; # along X axis dX=-2,dY=0 use constant _NumSeq_Delta_dTRadius_max => 2; # at N=0 use constant _NumSeq_Delta_DSquared_min => 2; use constant _NumSeq_Delta_DSquared_max => 4; use constant _NumSeq_Delta_TDist_non_decreasing => 1; # triangular use constant _NumSeq_Delta_TDSquared_max => 4; # triangular } { package Math::PlanePath::GosperReplicate; # maximum angle N=34 dX=3,dY=-1, it seems } { package Math::PlanePath::GosperIslands; use constant _NumSeq_Delta_DSquared_min => 2; use constant _NumSeq_Delta_TDir6_integer => 0; # between islands } { package Math::PlanePath::GosperSide; use constant _NumSeq_Delta_dSumAbs_min => -2; # diagonals use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_DSquared_min => 2; use constant _NumSeq_Delta_DSquared_max => 4; # use constant _NumSeq_Delta_oeis_anum => # 'Math::PlanePath::GosperSide' => # 'Math::PlanePath::TerdragonCurve' => # A062756 is total turn starting OFFSET=0, count of ternary 1 digits. # Dir6 would be total%6, or 2*(total%3) for Terdragon, suspect such a # modulo version not in OEIS. } { package Math::PlanePath::KochCurve; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_dTRadius_min => -2; use constant _NumSeq_Delta_dTRadius_max => 2; use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_Delta_DSquared_min => 2; use constant _NumSeq_Delta_DSquared_max => 4; use constant _NumSeq_Delta_oeis_anum => { '' => { AbsdY => 'A011655', # 0,1,1 repeating # OEIS-Catalogue: A011655 planepath=KochCurve delta_type=AbsdY }, }; } { package Math::PlanePath::KochPeaks; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dTRadius_min => -2; use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_Delta_DSquared_min => 2; } { package Math::PlanePath::KochSnowflakes; use constant _NumSeq_Delta_dX_integer => 1; use constant _NumSeq_Delta_dY_integer => 0; # initial Y=+2/3 use constant _NumSeq_Delta_dTRadius_min => -2; use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_Delta_DSquared_min => 2; # step diag or 2straight use constant _NumSeq_Delta_Dir4_integer => 0; # diagonals use constant _NumSeq_Delta_TDir6_integer => 0; # between rings } { package Math::PlanePath::KochSquareflakes; use constant _NumSeq_Delta_dX_integer => 0; # initial non-integers use constant _NumSeq_Delta_dY_integer => 0; use constant _NumSeq_Delta_dTRadius_min => -2; use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_Delta_TDir6_integer => 0; # between rings } { package Math::PlanePath::QuadricCurve; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } { package Math::PlanePath::QuadricIslands; use constant _NumSeq_Delta_dX_integer => 0; # initial 0.5s use constant _NumSeq_Delta_dY_integer => 0; # minimum unbounded jumping to next ring use constant _NumSeq_Delta_dSum_integer => 1; # 0.5+0.5 integer # maximum unbounded jumping to next ring use constant _NumSeq_Delta_dSumAbs_min => -1; # at N=5 use constant _NumSeq_Delta_dSumAbs_integer => 1; # 0.5+0.5 integer use constant _NumSeq_Delta_dDiffXY_integer => 1; # dDiffXY=+1 or -1 # dAbsDiff=+1 or -1 # jump to next ring is along leading diagonal so dAbsDiff bounded use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_dAbsDiff_integer => 1; # 0.5-0.5 integer use constant _NumSeq_Delta_Dir4_integer => 0; # between islands } { package Math::PlanePath::SierpinskiCurve; sub _NumSeq_Delta_dSumAbs_min { return $_[0]->dsumxy_minimum; } sub _NumSeq_Delta_dSumAbs_max { return $_[0]->dsumxy_maximum; } sub _NumSeq_Delta_dAbsDiff_min { return $_[0]->ddiffxy_minimum; } sub _NumSeq_Delta_dAbsDiff_max { return $_[0]->ddiffxy_maximum; } sub _NumSeq_Delta_Dir4_integer { my ($self) = @_; return ($self->{'diagonal_spacing'} == 0); } sub _NumSeq_Delta_TDir6_integer { my ($self) = @_; return ($self->{'straight_spacing'} == 0); } use List::Util; sub _NumSeq_Delta_DSquared_min { my ($self) = @_; return List::Util::min ($self->{'straight_spacing'} ** 2, 2 * $self->{'diagonal_spacing'} ** 2); } sub _NumSeq_Delta_DSquared_max { my ($self) = @_; return List::Util::max ($self->{'straight_spacing'} ** 2, 2 * $self->{'diagonal_spacing'} ** 2); } sub _NumSeq_Delta_TDSquared_min { my ($self) = @_; return List::Util::min($self->{'straight_spacing'}, 2 * $self->{'diagonal_spacing'}) ** 2; } sub _NumSeq_Delta_TDSquared_max { my ($self) = @_; return List::Util::max(3 * $self->{'straight_spacing'} ** 2, # vertical 4 * $self->{'diagonal_spacing'} ** 2); } # use constant _NumSeq_Delta_oeis_anum => # 'arms=1,straight_spacing=1,diagonal_spacing=1' => # { # # # Not quite, A127254 has extra initial 1 # # AbsdY => 'A127254', # 0 at 2*position of "odious" odd number 1-bits # # # OEIS-Catalogue: A127254 planepath=SierpinskiCurve delta_type=AbsdY # }, } { package Math::PlanePath::SierpinskiCurveStair; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; use constant _NumSeq_Delta_oeis_anum => { 'arms=1' => { AbsdX => 'A059841', # 1,0 repeating AbsdY => 'A000035', # 0,1 repeating # OEIS-Other: A059841 planepath=SierpinskiCurveStair delta_type=AbsdX # OEIS-Other: A000035 planepath=SierpinskiCurveStair delta_type=AbsdY # # OEIS-Other: A059841 planepath=SierpinskiCurveStair,diagonal_length=2 delta_type=AbsdX # OEIS-Other: A059841 planepath=SierpinskiCurveStair,diagonal_length=3 delta_type=AbsdX # OEIS-Other: A000035 planepath=SierpinskiCurveStair,diagonal_length=2 delta_type=AbsdY # OEIS-Other: A000035 planepath=SierpinskiCurveStair,diagonal_length=3 delta_type=AbsdY }, }; } { package Math::PlanePath::SierpinskiTriangle; use constant _NumSeq_Delta_DSquared_min => 2; sub _NumSeq_Delta_dSumAbs_min { return $_[0]->dsumxy_minimum; } sub _NumSeq_Delta_dSumAbs_max { return $_[0]->dsumxy_maximum; } sub _NumSeq_Dir4_max_is_supremum { my ($self) = @_; return ($self->{'align'} ne 'diagonal'); } use constant _NumSeq_Delta_Dir4_integer => 0; # between rows use constant _NumSeq_Delta_TDir6_integer => 0; # between rows } { package Math::PlanePath::SierpinskiArrowhead; { my %_NumSeq_Delta_dSumAbs_min = (triangular => -2, left => -2, right => -2, diagonal => -1, ); sub _NumSeq_Delta_dSumAbs_min { my ($self) = @_; return $_NumSeq_Delta_dSumAbs_min{$self->{'align'}}; } } { my %_NumSeq_Delta_dSumAbs_max = (triangular => 2, left => 2, right => 2, diagonal => 1, ); sub _NumSeq_Delta_dSumAbs_max { my ($self) = @_; return $_NumSeq_Delta_dSumAbs_max{$self->{'align'}}; } } sub _NumSeq_Delta_dAbsDiff_min { return $_[0]->ddiffxy_minimum; } sub _NumSeq_Delta_dAbsDiff_max { return $_[0]->ddiffxy_maximum; } use constant _NumSeq_Delta_dTRadius_min => -2; sub _NumSeq_Delta_dTRadius_max { my ($self) = @_; return ($self->{'align'} eq 'diagonal' ? sqrt(3) : 2); } use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_Delta_DSquared_min => 2; use constant _NumSeq_Delta_DSquared_max => 4; use constant _NumSeq_Delta_TDist_non_decreasing => 1; # triangular use constant _NumSeq_Delta_TDSquared_max => 4; # triangular } { package Math::PlanePath::SierpinskiArrowheadCentres; *_NumSeq_Delta_dSumAbs_min = \&Math::PlanePath::SierpinskiArrowhead::_NumSeq_Delta_dSumAbs_min; *_NumSeq_Delta_dSumAbs_max = \&Math::PlanePath::SierpinskiArrowhead::_NumSeq_Delta_dSumAbs_max; sub _NumSeq_Delta_dAbsDiff_min { return $_[0]->ddiffxy_minimum; } sub _NumSeq_Delta_dAbsDiff_max { return $_[0]->ddiffxy_maximum; } use constant _NumSeq_Delta_dTRadius_min => -2; sub _NumSeq_Delta_dTRadius_max { my ($self) = @_; return ($self->{'align'} eq 'diagonal' ? sqrt(3) : 2); } use constant _NumSeq_dTRadius_min_is_infimum => 1; sub _NumSeq_Delta_dDSquared_min { my ($self) = @_; return ($self->{'align'} eq 'triangular' ? 2 : 1); } sub _NumSeq_Delta_dDSquared_max { my ($self) = @_; return ($self->{'align'} eq 'triangular' ? 4 : 2); } } { package Math::PlanePath::DragonCurve; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_dRadius_min => -1; use constant _NumSeq_Delta_dRadius_max => 1; use constant _NumSeq_Delta_dTRadius_min => - sqrt(3); use constant _NumSeq_Delta_dTRadius_max => sqrt(3); use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; use constant _NumSeq_Delta_oeis_anum => { 'arms=1' => { AbsdX => 'A059841', # 1,0 repeating AbsdY => 'A000035', # 0,1 repeating Dir4 => 'A246960', # direction # OEIS-Other: A059841 planepath=DragonCurve delta_type=AbsdX # OEIS-Other: A000035 planepath=DragonCurve delta_type=AbsdY # OEIS-Catalogue: A246960 planepath=DragonCurve delta_type=Dir4 }, 'arms=3' => { AbsdX => 'A059841', # 1,0 repeating AbsdY => 'A000035', # 0,1 repeating # OEIS-Other: A059841 planepath=DragonCurve,arms=3 delta_type=AbsdX # OEIS-Other: A000035 planepath=DragonCurve,arms=3 delta_type=AbsdY }, # 'arms=2' => $href,# 0,1,1,0 'arms=4' => { AbsdY => 'A165211', # 0,1,0,1, 1,0,1,0, repeating # OEIS-Other: A165211 planepath=DragonCurve,arms=4 delta_type=AbsdY }, }; } { package Math::PlanePath::DragonRounded; use constant _NumSeq_Delta_dSumAbs_min => -2; use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; # dRadius infimum/supremum because diagonals only occur on "odd" diagonals, # never on the X=Y diagonal, so never have step by sqrt(2) exactly use constant _NumSeq_Delta_dRadius_min => -sqrt(2); use constant _NumSeq_Delta_dRadius_max => sqrt(2); use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_dRadius_max_is_supremum => 1; use constant _NumSeq_Delta_dTRadius_min => -2; use constant _NumSeq_Delta_dTRadius_max => 2; use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_dTRadius_max_is_supremum => 1; use constant _NumSeq_Delta_DSquared_max => 2; use constant _NumSeq_Delta_oeis_anum => { 'arms=1' => { AbsdX => 'A152822', # 1,1,0,1 repeating AbsdY => 'A166486', # 0,1,1,1 repeating # OEIS-Catalogue: A166486 planepath=DragonRounded delta_type=AbsdY # OEIS-Catalogue: A152822 planepath=DragonRounded delta_type=AbsdX }, }; } { package Math::PlanePath::DragonMidpoint; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_dRadius_min => -1; # at N=1534 use constant _NumSeq_Delta_dRadius_max => 1; # at N=0 use constant _NumSeq_Delta_dRadius_min_n => 1534; use constant _NumSeq_Delta_dTRadius_min => - sqrt(3); use constant _NumSeq_Delta_dTRadius_max => sqrt(3); use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; # use constant _NumSeq_Delta_oeis_anum => # '' => # { # # Not quite, has n=N+2 and extra initial 0 at n=1 # # AbsdY => 'A073089', # }, } { package Math::PlanePath::R5DragonCurve; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_dRadius_min => -1; use constant _NumSeq_Delta_dRadius_max => 1; use constant _NumSeq_Delta_dTRadius_min => - sqrt(3); use constant _NumSeq_Delta_dTRadius_max => sqrt(3); use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; use constant _NumSeq_Delta_oeis_anum => { do { my $href = { AbsdX => 'A059841', # 1,0 repeating AbsdY => 'A000035', # 0,1 repeating }; ('arms=1' => $href, 'arms=3' => $href, ); # OEIS-Other: A059841 planepath=R5DragonCurve delta_type=AbsdX # OEIS-Other: A000035 planepath=R5DragonCurve delta_type=AbsdY # OEIS-Other: A059841 planepath=R5DragonCurve,arms=3 delta_type=AbsdX # OEIS-Other: A000035 planepath=R5DragonCurve,arms=3 delta_type=AbsdY }, 'arms=4' => { AbsdY => 'A165211', # 0,1,0,1, 1,0,1,0, repeating # OEIS-Other: A165211 planepath=R5DragonCurve,arms=4 delta_type=AbsdY }, }; } { package Math::PlanePath::R5DragonMidpoint; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_dRadius_min => -1; use constant _NumSeq_Delta_dRadius_max => 1; use constant _NumSeq_Delta_dTRadius_min => - sqrt(3); # at N=11 on Y axis dY=-1 use constant _NumSeq_Delta_dTRadius_max => sqrt(3); # at N=1348 on Y neg axis dY=-1 use constant _NumSeq_Delta_dTRadius_max_n => 1348; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } { package Math::PlanePath::CCurve; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; use constant _NumSeq_Delta_oeis_anum => { '' => { AbsdX => 'A010059', # Thue-Morse binary parity AbsdY => 'A010060', # 1-bit count mod 2, DigitSumModulo Thue-Morse Dir4 => 'A179868', # 1-bit count mod 4, DigitSumModulo # OEIS-Catalogue: A010059 planepath=CCurve delta_type=AbsdX # OEIS-Other: A010060 planepath=CCurve delta_type=AbsdY # OEIS-Other: A179868 planepath=CCurve delta_type=Dir4 }, }; } { package Math::PlanePath::AlternatePaper; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_dRadius_min => -1; use constant _NumSeq_Delta_dRadius_max => 1; use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; use constant _NumSeq_Delta_oeis_anum => { 'arms=1' => { AbsdY => 'A000035', # 0,1 repeating dSum => 'A020985', # GRS dSumAbs => 'A020985', # GRS # OEIS-Other: A000035 planepath=AlternatePaper delta_type=AbsdY # OEIS-Other: A020985 planepath=AlternatePaper delta_type=dSum # OEIS-Other: A020985 planepath=AlternatePaper delta_type=dSumAbs # dX_every_second_point_skipping_zeros => 'A020985', # GRS # # ie. Math::NumSeq::GolayRudinShapiro }, 'arms=4' => { dSum => 'A020985', # GRS # OEIS-Other: A020985 planepath=AlternatePaper,arms=4 delta_type=dSum }, }; } { package Math::PlanePath::AlternatePaperMidpoint; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } { package Math::PlanePath::TerdragonCurve; use constant _NumSeq_Delta_dSumAbs_min => -2; use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_dRadius_min => -2; # at N=157 use constant _NumSeq_Delta_dRadius_max => 2; # at N=0 use constant _NumSeq_Delta_dTRadius_min => -2; use constant _NumSeq_Delta_dTRadius_max => 2; use constant _NumSeq_Delta_TDir6_integer => 1; use constant _NumSeq_Delta_DSquared_min => 2; use constant _NumSeq_Delta_DSquared_max => 4; use constant _NumSeq_Delta_TDist_non_decreasing => 1; # triangular use constant _NumSeq_Delta_TDSquared_max => 4; # triangular } { package Math::PlanePath::TerdragonRounded; use constant _NumSeq_Delta_dSumAbs_min => -2; use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_dRadius_min => -2; # at N=314 use constant _NumSeq_Delta_dRadius_max => 2; # at N=0 use constant _NumSeq_Delta_dTRadius_min => -2; use constant _NumSeq_Delta_dTRadius_max => 2; use constant _NumSeq_Delta_TDir6_integer => 1; use constant _NumSeq_Delta_DSquared_min => 2; use constant _NumSeq_Delta_DSquared_max => 4; use constant _NumSeq_Delta_TDist_non_decreasing => 1; # triangular use constant _NumSeq_Delta_TDSquared_max => 4; # triangular } { package Math::PlanePath::TerdragonMidpoint; use constant _NumSeq_Delta_dSumAbs_min => -2; use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; # dRadius infimum/supremum because horizontals only occur on Y odd, so # never have step by 2 exactly along X axis use constant _NumSeq_Delta_dRadius_min => -2; use constant _NumSeq_Delta_dRadius_max => 2; use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_dRadius_max_is_supremum => 1; use constant _NumSeq_Delta_dTRadius_min => -2; use constant _NumSeq_Delta_dTRadius_max => 2; use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_dTRadius_max_is_supremum => 1; use constant _NumSeq_Delta_TDir6_integer => 1; use constant _NumSeq_Delta_DSquared_min => 2; use constant _NumSeq_Delta_DSquared_max => 4; use constant _NumSeq_Delta_TDist_non_decreasing => 1; # triangular use constant _NumSeq_Delta_TDSquared_max => 4; # triangular } { package Math::PlanePath::AlternateTerdragon; use constant _NumSeq_Delta_dSumAbs_min => -2; use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_dRadius_min => -2; # at N=...? use constant _NumSeq_Delta_dRadius_max => 2; # at N=0 use constant _NumSeq_Delta_dTRadius_min => -2; use constant _NumSeq_Delta_dTRadius_max => 2; use constant _NumSeq_Delta_TDir6_integer => 1; use constant _NumSeq_Delta_DSquared_min => 2; use constant _NumSeq_Delta_DSquared_max => 4; use constant _NumSeq_Delta_TDist_non_decreasing => 1; # triangular use constant _NumSeq_Delta_TDSquared_max => 4; # triangular } { package Math::PlanePath::ComplexPlus; use constant _NumSeq_Dir4_max_is_supremum => 1; use constant _NumSeq_Delta_Dir4_integer => 0; } { package Math::PlanePath::ComplexMinus; use constant _NumSeq_Dir4_max_is_supremum => 1; use constant _NumSeq_Delta_Dir4_integer => 0; } { package Math::PlanePath::ComplexRevolving; use constant _NumSeq_Dir4_max_is_supremum => 1; use constant _NumSeq_Delta_Dir4_integer => 0; } { package Math::PlanePath::Rows; sub _NumSeq_Delta_dX_non_decreasing { my ($self) = @_; return ($self->{'width'} <= 1 ? 1 # single column only, dX=0 always : 0); } sub _NumSeq_Delta_AbsdX_non_decreasing { my ($self) = @_; return ($self->{'width'} <= 2); # 1 or 2 is constant 0 or 1 } # abs(X-Y) move towards and then away from X=Y diagonal by +1 and -1 in row, # then at row end to Y axis goes # from X=width-1, Y=k AbsDiff = abs(k-(width-1)) # to X=0, Y=k+1 AbsDiff = k+1 # dAbsDiff = k+1 - abs(k-(width-1)) # when k>=width-1 dAbsDiff = k+1 - (k-(width-1)) # = k+1 - k + (width-1) # = 1 + width-1 # = width # when k<=width-1 dAbsDiff = k+1 - ((width-1)-k) # = k+1 - (width-1) + k # = 2k+1 - width + 1 # = 2k+2 - width # at k=0 dAbsDiff = 2-width # at k=width-1 dAbsDiff = 2*(width-1)+2 - width # = 2*width - 2 + 2 - width # = width # minimum = 2-width or -1 # maximum = width # sub _NumSeq_Delta_dAbsDiff_min { my ($self) = @_; if ($self->{'width'} == 1) { return 1; } # constant dAbsDiff=1 return List::Util::min(-1, 2 - $self->{'width'}); } sub _NumSeq_Delta_dAbsDiff_max { my ($self) = @_; return $self->{'width'}; } *_NumSeq_Delta_dY_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_AbsdY_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_dSum_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_Dir4_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_TDir6_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_Dist_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_TDist_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_dDiffXY_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_dDiffYX_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_dAbsDiff_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_dSumAbs_non_decreasing # width=1 is dSumAbs=constant = \&_NumSeq_Delta_dX_non_decreasing; sub _NumSeq_Delta_dRadius_min { my ($self) = @_; return 2 - $self->{'width'}; } use constant _NumSeq_Delta_dRadius_max => 1; # at N=1 *_NumSeq_Delta_dRadius_integer = \&_NumSeq_Delta_dX_non_decreasing; sub _NumSeq_Delta_dRSquared_min { my ($self) = @_; return ($self->{'width'} == 1 ? 1 : $self->{'width'} == 2 ? 0 : undef); } use constant _NumSeq_Delta_dRSquared_integer => 1; *_NumSeq_Delta_dRSquared_increasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_dRSquared_increasing = \&_NumSeq_Delta_dX_non_decreasing; # end of first row X=w-1,Y=0 TRadius = w-1 # start next X=0, Y=1 TRadius = sqrt(3) # dTRadius = sqrt(3)-(w-1) # also horizontal 0 which is minimum for width=2 # # maximum prev row to next row dY=+1 is dTRadius -> sqrt(3) # sub _NumSeq_Delta_dTRadius_min { my ($self) = @_; my $width = $self->{'width'}; return ($width == 2 ? 0 : sqrt(3)+1 - $width); } sub _NumSeq_dTRadius_min_is_infimum { my ($self) = @_; my $width = $self->{'width'}; return ($width == 2 ? 1 # infimum : 0); # exact } use constant _NumSeq_Delta_dTRadius_max => sqrt(3); use constant _NumSeq_dTRadius_max_is_supremum => 1; # end of row X=w-1,Y TRsq = (w-1)^2 + 3Y^2 # start next X=0, Y+1 TRsqNext = 3(Y+1)^2 # dTRsq = 3Y^2 + 6Y + 3 - (w-1)^2 - 3Y^2 # = 6Y+3 - (w-1)^2 # minimum at Y=0 is 3-(w-1)^2 = 2+2w-w^2 # w=1 min=3 # w=2 min=2 # w=3 min=-1 # also if w>=2 then 0,0 to 1,0 is dTRsq=1 sub _NumSeq_Delta_dTRSquared_min { my ($self) = @_; my $width = $self->{'width'}; return ($width == 2 ? 1 # N=Nstart 0,0 to 1,0 : (2-$width)*$width+2); } *_NumSeq_Delta_dTRSquared_increasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_Dir4_integer = \&_NumSeq_Delta_dX_non_decreasing; sub _NumSeq_Delta_TDir6_integer { my ($self) = @_; return ($self->{'width'} == 2); # E and NW } use constant _NumSeq_Delta_oeis_anum => { 'n_start=1,width=1' => { dX => 'A000004', # all zeros, X=0 always dY => 'A000012', # all 1s Dir4 => 'A000012', # all 1s, North # OEIS-Other: A000004 planepath=Rows,width=1 delta_type=dX # OEIS-Other: A000012 planepath=Rows,width=1 delta_type=dY # OEIS-Other: A000012 planepath=Rows,width=1 delta_type=Dir4 }, 'n_start=0,width=2' => { dX => 'A033999', # 1,-1 repeating, OFFSET=0 dRSquared => 'A124625', # 1,0,1,2,1,4,1,6,1,8 ones and evens OFFSET=0 TDir6 => 'A010673', # 0,2 repeating, OFFSET=0 # catalogued here pending perhaps simpler implementation elsewhere # OEIS-Catalogue: A033999 planepath=Rows,width=2,n_start=0 delta_type=dX # OEIS-Catalogue: A124625 planepath=Rows,width=2,n_start=0 delta_type=dRSquared # OEIS-Catalogue: A010673 planepath=Rows,width=2,n_start=0 delta_type=TDir6 }, 'n_start=1,width=3' => { dX => 'A061347', # 1,1,-2 repeating OFFSET=1 # OEIS-Catalogue: A061347 planepath=Rows,width=3 delta_type=dX }, 'n_start=0,width=3' => { dSum => 'A131561', # 1,1,-1 repeating dSumAbs => 'A131561', # same # OEIS-Catalogue: A131561 planepath=Rows,width=3,n_start=0 delta_type=dSum # OEIS-Other: A131561 planepath=Rows,width=3,n_start=0 delta_type=dSumAbs # dY => 'A022003', # 0,0,1 repeating, decimal of 1/999 # # OEIS-Other: A022003 planepath=Rows,width=3 delta_type=dY }, 'n_start=1,width=4' => { dY => 'A011765', # 0,0,0,1 repeating, starting OFFSET=1 # OEIS-Other: A011765 planepath=Rows,width=4 delta_type=dY }, # OFFSET # 'n_start=1,width=6' => # { dY => 'A172051', # 0,0,0,0,0,1 repeating decimal 1/999999 # # OEIS-Other: A172051 planepath=Rows,width=6 delta_type=dY # }, }; } { package Math::PlanePath::Columns; sub _NumSeq_Delta_AbsdY_non_decreasing { my ($self) = @_; return ($self->{'height'} <= 2); # 1 or 2 is constant } # same as Rows dAbsDiff sub _NumSeq_Delta_dAbsDiff_min { my ($self) = @_; if ($self->{'height'} == 1) { return 1; } # constant dAbsDiff=1 return List::Util::min(-1, 2 - $self->{'height'}); } sub _NumSeq_Delta_dAbsDiff_max { my ($self) = @_; return $self->{'height'}; } # same as Rows sub _NumSeq_Delta_dRadius_min { my ($self) = @_; return 2 - $self->{'height'}; } use constant _NumSeq_Delta_dRadius_max => 1; # at N=1 *_NumSeq_Delta_dRadius_integer = \&_NumSeq_Delta_dX_non_decreasing; sub _NumSeq_Delta_dRSquared_min { my ($self) = @_; return ($self->{'height'} == 1 ? 1 : $self->{'height'} == 2 ? 0 : undef); } use constant _NumSeq_Delta_dRSquared_integer => 1; *_NumSeq_Delta_dRSquared_increasing = \&_NumSeq_Delta_dX_non_decreasing; # end of first column X=0,Y=h-1 TRadius = sqrt(0 + 3*(h-1)^2) # start next X=1,Y=0 TRadius = sqrt(1 + 0) # min dTRadius = 1 - sqrt(3)*(h-1) # = (1+sqrt(3)) - h*sqrt(3) sub _NumSeq_Delta_dTRadius_min { my ($self) = @_; my $height = $self->{'height'}; return ($height == 1 ? 1 # constant increment 1 : (-sqrt(3))*$height + (1+sqrt(3))); } sub _NumSeq_Delta_dTRadius_max { my ($self) = @_; return ($self->{'height'} == 1 ? 1 # constant increment 1 : sqrt(3)); } *_NumSeq_Delta_dTRadius_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_dTRadius_integer = \&_NumSeq_Delta_dX_non_decreasing; # end of column X, Y=h-1 TRsq = X^2 + 3(h-1)^2 # start next X+1,Y=0 TRsqNext = (X+1)^2 # dTRsq = 2X+1 - 3(h-1)^2 # minimum at X=0 is 1-3*(h-1)^2 = -3*h^2 + 6h - 2 # h=1 min=1 # h=2 min=-2 sub _NumSeq_Delta_dTRSquared_min { my ($self) = @_; my $height = $self->{'height'}; return (-3*$height + 6)*$height - 2; } *_NumSeq_Delta_dTRSquared_increasing = \&_NumSeq_Delta_dX_non_decreasing; sub _NumSeq_Delta_TDSquared_min { my ($self) = @_; return ($self->{'height'} == 1 ? 1 # horizontal : 3); # vertical } sub _NumSeq_Delta_dX_non_decreasing { my ($self) = @_; return ($self->{'height'} == 1); # constant when column only } *_NumSeq_Delta_dY_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_AbsdX_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_dSum_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_dDiffXY_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_dDiffYX_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_dAbsDiff_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_dSumAbs_non_decreasing # height=1 is dSumAbs=constant = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_Dir4_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_TDir6_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_Dist_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_TDist_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_Dir4_integer = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_TDir6_integer = \&_NumSeq_Delta_dX_non_decreasing; use constant _NumSeq_Delta_oeis_anum => { 'n_start=1,height=1' => { dX => 'A000012', # all 1s dY => 'A000004', # all zeros, Y=0 always Dir4 => 'A000004', # all zeros, East TDir6 => 'A000004', # all zeros, East # OEIS-Other: A000012 planepath=Columns,height=1 delta_type=dX # OEIS-Other: A000004 planepath=Columns,height=1 delta_type=dY # OEIS-Other: A000004 planepath=Columns,height=1 delta_type=Dir4 # OEIS-Other: A000004 planepath=Columns,height=1 delta_type=TDir6 }, 'n_start=0,height=2' => { dY => 'A033999', # 1,-1 repeating dSum => 'A059841', # 1,0 repeating, 1-n mod 2 dSumAbs => 'A059841', # same dRSquared => 'A124625', # 1,0,1,2,1,4,1,6,1,8 ones and evens OFFSET=0 # OEIS-Other: A033999 planepath=Columns,height=2,n_start=0 delta_type=dY # OEIS-Other: A059841 planepath=Columns,height=2,n_start=0 delta_type=dSum # OEIS-Other: A059841 planepath=Columns,height=2,n_start=0 delta_type=dSumAbs # OEIS-Other: A124625 planepath=Columns,height=2,n_start=0 delta_type=dRSquared }, 'n_start=0,height=3' => { dSum => 'A131561', # 1,1,-1 repeating dSumAbs => 'A131561', # same # OEIS-Other: A131561 planepath=Columns,height=3,n_start=0 delta_type=dSum # OEIS-Other: A131561 planepath=Columns,height=3,n_start=0 delta_type=dSumAbs }, 'n_start=1,height=3' => { dY => 'A061347', # 1,1,-2 repeating # OEIS-Other: A061347 planepath=Columns,height=3 delta_type=dY # dX => 'A022003', # 0,0,1 repeating from frac 1/999 # # OEIS-Other: A022003 planepath=Columns,height=3 delta_type=dX }, 'n_start=1,height=4' => { dX => 'A011765', # 0,0,0,1 repeating, starting OFFSET=1 # OEIS-Other: A011765 planepath=Columns,height=4 delta_type=dX }, # OFFSET # 'n_start=1,height=6' => # { dX => 'A172051', # 0,0,0,1 repeating, starting n=0 # # OEIS-Other: A172051 planepath=Columns,height=6 delta_type=dX # }, }; } { package Math::PlanePath::Diagonals; # -2 # | 0 dSumAbs # \ | / # \ /|\ / # \/ | \/ 0 # /\ | /\ # / \|/ \ # ------+-------- # |\ / # | \/ +2 # | /\ # |/ # | # within diagonal from X=Xstart+p Y=Ystart+k-p # to X=Xstart+p+1 Y=Ystart+k-p-1 # abs(Xstart+p+1)-abs(Xstart+p) # if X1=Xstart+p>=0 then X2=Xstart+p+1>=0 # dAbsX = (Xstart+p+1)-(Xstart+p) = +1 # if X1=Xstart+p<0 then X2=Xstart+p+1<=0 # dAbsX = -(Xstart+p+1) - (-(Xstart+p)) = -1 # X1<0 occurs when Xstart<=-1 # # abs(Ystart+k-p-1)-abs(Ystart+k-p) # if Y2=Ystart+k-p-1>=0 then Y1=Ystart+k-p>=0 # dAbsY = (Ystart+k-p-1)-(Ystart+k-p) = -1 # if Y2=Ystart+k-p-1<0 then Y1=Ystart+k-p<=0 # dAbsY = -(Ystart+k-p-1) - (-(Ystart+k-p)) = +1 # Y2<0 occurs when Ystart<=-1 # # within diagonal dAbsX = (Xstart>=0 ? 1 : -1) # dAbsY = (Ystart>=0 ? -1 : 1) # is dSumAbs_min = # # towards or away X=Y is dSumAbs=+/-2 # end of diagonal X=Xstart+k Y=Ystart # to X=Xstart Y=Ystart+k+1 # if Xstart>=0 and Ystart>=0 then dSumAbs=dSum=1 always # # if Xstart<0 then from abs(X) = -(Xstart+k) # to abs(X) = -Xstart # dSumAbs = (-Xstart + Ystart+k+1) - (-(Xstart+k) + Ystart) # = 2*k+1 # until (Xstart+k)>=0 and then # dSumAbs = (-Xstart + Ystart+k+1) - (Xstart+k + Ystart) # = -2*Xstart+1 which is >0 # so dSumAbs_max = -2*Xstart+1 # # if Ystart<0 then from abs(Y) = -Ystart # to abs(Y) = -(Ystart+k+1) # dSumAbs = (Xstart + -(Ystart+k+1)) - (Xstart+k + -Ystart) # = -2*k-1 # until (Ystart+k+1)>=0 and then # dSumAbs = (Xstart + (Ystart+k+1)) - (Xstart+k + -Ystart) # = 2*Ystart+1 which is <0 # so dSumAbs_min = 2*Ystart+1 # sub _NumSeq_Delta_dSumAbs_min { my ($self) = @_; my $x_start = $self->{'x_start'}; my $y_start = $self->{'y_start'}; if ($self->{'direction'} eq 'up') { ($x_start,$y_start) = ($y_start,$x_start); } return List::Util::min(($x_start < 0 ? -2 : 0), 2*($y_start-List::Util::min($x_start,0)) + 1); } sub _NumSeq_Delta_dSumAbs_max { my ($self) = @_; my $x_start = $self->{'x_start'}; my $y_start = $self->{'y_start'}; if ($self->{'direction'} eq 'up') { ($x_start,$y_start) = ($y_start,$x_start); } return List::Util::max(($y_start < 0 ? 2 : 1), -2*($x_start-List::Util::min($y_start,0)) + 1); } # step 2 along opp diagonal, except end of row jumping back up goes # # | T step = 2*(F-Xstart)+1 # | \ X=Y F = Ystart # | |\ / eg. Xstart=20 Ystart=10 # | | \ / step = 2*(10-20)+1 = -19 # | +--F # | / # | / # | / # |/ # +-------- sub _NumSeq_Delta_dAbsDiff_min { my ($self) = @_; return List::Util::min (-2, # towards X=Y diagonal ($self->{'direction'} eq 'down' ? 2 : -2) * ($self->{'y_start'} - $self->{'x_start'}) + 1); } sub _NumSeq_Delta_dAbsDiff_max { my ($self) = @_; return List::Util::max (2, # away from X=Y diagonal ($self->{'direction'} eq 'down' ? 2 : -2) * ($self->{'y_start'} - $self->{'x_start'}) + 1); } # * R1=sqrt(X^2+Y^2) # \ R2=sqrt((X+1)^2+(Y-1)^2) # \ R1-R2 -> 1 # * # use constant _NumSeq_Delta_dRadius_min => -1; use constant _NumSeq_Delta_dRadius_max => 1; # * R1=X^2+Y^2 # \ R2=(X+1)^2+(Y-1)^2 # \ R2-R1 = X^2+2X+1 - (Y^2-2Y+1) - X^2 - Y^2 # * = 2X+1 - 2*Y^2 + 2Y - 1 unbounded # # use constant _NumSeq_Delta_dRSquared_min => undef; # use constant _NumSeq_Delta_dRSquared_max => undef; use constant _NumSeq_Delta_Dir4_integer => 0; # diagonals sub _NumSeq_Delta_TDSquared_min { my ($self) = @_; return ($self->{'direction'} eq 'down' ? 3 # N=1 dX=0,dY=1 vertical : 1); # N=1 dX=0,dY=1 horizontal } use constant _NumSeq_Delta_oeis_anum => { 'direction=down,n_start=1,x_start=0,y_start=0' => { dY => 'A127949', # OEIS-Catalogue: A127949 planepath=Diagonals delta_type=dY }, 'direction=up,n_start=1,x_start=0,y_start=0' => { dX => 'A127949', # OEIS-Other: A127949 planepath=Diagonals,direction=up delta_type=dX }, 'direction=down,n_start=0,x_start=0,y_start=0' => { AbsdY => 'A051340', dSum => 'A023531', # characteristic "1" at triangulars dSumAbs => 'A023531', # same # OEIS-Catalogue: A051340 planepath=Diagonals,n_start=0 delta_type=AbsdY # OEIS-Other: A023531 planepath=Diagonals,n_start=0 delta_type=dSum # OEIS-Other: A023531 planepath=Diagonals,n_start=0 delta_type=dSumAbs }, 'direction=up,n_start=0,x_start=0,y_start=0' => { AbsdX => 'A051340', dSum => 'A023531', # characteristic "1" at triangulars dSumAbs => 'A023531', # same # OEIS-Other: A051340 planepath=Diagonals,direction=up,n_start=0 delta_type=AbsdX # OEIS-Other: A023531 planepath=Diagonals,direction=up,n_start=0 delta_type=dSum # OEIS-Other: A023531 planepath=Diagonals,direction=up,n_start=0 delta_type=dSumAbs # Almost AbsdY=>'A051340' too, but path starts initial 0,1,1 whereas # A051340 starts 1,1,2 }, }; } { package Math::PlanePath::DiagonalsAlternating; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_DSquared_max => 2; use constant _NumSeq_Delta_oeis_anum => { 'n_start=0' => { dSum => 'A023531', # characteristic "1" at triangulars dSumAbs => 'A023531', # same # OEIS-Other: A023531 planepath=DiagonalsAlternating,n_start=0 delta_type=dSum # OEIS-Other: A023531 planepath=DiagonalsAlternating,n_start=0 delta_type=dSumAbs }, }; } { package Math::PlanePath::DiagonalsOctant; sub _NumSeq_Delta_dAbsDiff_min { my ($self) = @_; return ($self->{'direction'} eq 'down' ? -2 # "down" : undef); # "up" } sub _NumSeq_Delta_dAbsDiff_max { my ($self) = @_; return ($self->{'direction'} eq 'down' ? undef # "down" : 2); # "up" } sub _NumSeq_Delta_TDSquared_min { my ($self) = @_; return ($self->{'direction'} eq 'down' ? 3 # N=1 dX=0,dY=1 vertical : 1); # N=1 dX=0,dY=1 horizontal } } { package Math::PlanePath::MPeaks; use constant _NumSeq_Delta_dSumAbs_min => -2; use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_dRadius_min => -sqrt(2); use constant _NumSeq_Delta_dRadius_max => sqrt(2); use constant _NumSeq_Delta_Dir4_integer => 0; # diagonals use constant _NumSeq_Delta_TDir6_integer => 0; # verticals use constant _NumSeq_Delta_TDSquared_min => 3; # vertical } { package Math::PlanePath::Staircase; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_dTRadius_min => - sqrt(3); use constant _NumSeq_Delta_Dir4_integer => 0; # going back to Y axis } { package Math::PlanePath::StaircaseAlternating; use constant _NumSeq_Delta_dAbsDiff_min => -1; { my %_NumSeq_Delta_dAbsDiff_max = (jump => 2, # at endpoint square => 1); # always NSEW sub _NumSeq_Delta_dAbsDiff_max { my ($self) = @_; return $_NumSeq_Delta_dAbsDiff_max{$self->{'end_type'}}; } } use constant _NumSeq_Delta_dTRadius_min => - sqrt(3); use constant _NumSeq_dTRadius_min_is_infimum => 1; { my %_NumSeq_Delta_dTRadius_max = (jump => undef, square => sqrt(3)); sub _NumSeq_Delta_dTRadius_max { my ($self) = @_; return $_NumSeq_Delta_dTRadius_max{$self->{'end_type'}}; } } { my %DSquared_max = (jump => 4, square => 1); sub _NumSeq_Delta_DSquared_max { my ($self) = @_; return $DSquared_max{$self->{'end_type'}}; } } { my %Dist_non_decreasing = (jump => 0, square => 1); # NSEW always sub _NumSeq_Delta_Dist_non_decreasing { my ($self) = @_; return $Dist_non_decreasing{$self->{'end_type'}}; } } { my %TDSquared_max = (jump => 12, square => 3); sub _NumSeq_Delta_TDSquared_max { my ($self) = @_; return $TDSquared_max{$self->{'end_type'}}; } } } { package Math::PlanePath::Corner; # X=k+wider, Y=0 has abs(X-Y)=k+wider # X=0, Y=k+1 has abs(X-Y)=k+1 # dAbsDiff = (k+1)-(k+wider) # = 1-wider # and also dAbsDiff=-1 when going towards X=Y diagonal use List::Util; sub _NumSeq_Delta_dAbsDiff_min { my ($self) = @_; return List::Util::min(-1, 1-$self->{'wider'}); } use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_dRadius_min => - sqrt(2); use constant _NumSeq_Delta_dRadius_max => 1; use constant _NumSeq_Delta_Dir4_integer => 0; # between gnomons # use constant _NumSeq_Delta_oeis_anum => # { 'wider=0,n_start=0' => # { dSumAbs => 'A000012', # all ones, OFFSET=0 # # OEIS-Other: A000012 planepath=Corner delta_type=dSumAbs # }, # }; } { package Math::PlanePath::CornerAlternating; use constant _NumSeq_Delta_dRadius_min => - sqrt(2); use constant _NumSeq_Delta_dRadius_max => 1; use constant _NumSeq_Delta_oeis_anum => { 'wider=0,n_start=0' => { dDiffXY => 'A339265', # runs +1,-1, OFFSET=0 # OEIS-Other: A339265 planepath=CornerAlternating,n_start=0 delta_type=dDiffXY }, }; } { package Math::PlanePath::PyramidRows; sub _NumSeq_Delta__step_is_0 { my ($self) = @_; return ($self->{'step'} == 0); # constant when column only } *_NumSeq_Delta_dSum_non_decreasing = \&_NumSeq_Delta__step_is_0; # constant when column only # align=right # X>=0 so SumAbs=Sum # within row X increasing dSum=1 # end row decrease by big # minimum = undef # maximum = 1 # # align=left # within dSumAbs=-1 towards Y axis then dSumAbs=1 away # end row X=0,Y=k SumAbs=k # to X=-step*(k+1),Y=k+1 SumAbs=step*(k+1) + (k+1) # dSumAbs = step*(k+1) + (k+1) - k # = step*k + step + k + 1 - k # = step*(k+1) + 1 big positive # minimum = -1 # maximum = undef # # align=centre, step=even # within dSumAbs=-1 towards Y axis then dSumAbs=1 away # end row X=k*step/2, Y=k SumAbs=k*step/2 + k # to X=-step/2*(k+1),Y=k+1 SumAbs=step/2*(k+1) + k+1 # dSumAbs = step/2*(k+1) + k+1 - (k*step/2 + k) # = step/2*(k+1) + k+1 - k*step/2 - k # = step/2*(k+1) +1 - k*step/2 # = step/2 +1 # minimum = -1 # maximum = step/2 +1 # # align=centre, step=odd # f=floor(step/2) c=ceil(step/2)=f+1 # within dSumAbs=-1 towards Y axis then dSumAbs=1 away # end row X=k*c, Y=k SumAbs=k*c + k # to X=-f*(k+1),Y=k+1 SumAbs=f*(k+1) + k+1 # dSumAbs = f*(k+1) + k+1 - (k*c + k) # = f*(k+1) + k+1 - k*(f+1) - k # = f*k +f + k+1 - k*f - k - k # = f + 1 - k # = (step+1)/2 - k # minimum = big negative # maximum = floor(step/2) + 1 when k=0 first end row # sub _NumSeq_Delta_dSumAbs_min { my ($self) = @_; if ($self->{'step'} == 0) { return 1; # step=0 constant dSumAbs=1 } if ($self->{'align'} eq 'left' || ($self->{'align'} eq 'centre' && $self->{'step'} % 2 == 0)) { return -1; # towards Y axis } return undef; # big negatives } sub _NumSeq_Delta_dSumAbs_max { my ($self) = @_; if ($self->{'step'} == 0 || $self->{'align'} eq 'right') { return 1; } if ($self->{'align'} eq 'centre') { return int($self->{'step'}/2) + 1; } return undef; } # abs(X-Y) move towards and then away from X=Y diagonal by +1 and -1 in row # # align=left # towards X=Y diagonal so dAbsDiff=-1 # from X=0,Y=k AbsDiff = k # to X=-(k+1)*step,Y=k+1 AbsDiff = k+1 - (-(k+1)*step) # dAbsDiff = k+1 - (-(k+1)*step) - k # = step*(k+1) + 1 big positive # # align=right # step<=1 only towards X=Y diagonal dAbsDiff=-1 # step>=2 away from X=Y diagonal dAbsDiff=+1 # from X=k*step,Y=k AbsDiff = k*step - k # to X=0,Y=k+1 AbsDiff = k+1 # dAbsDiff = k+1 - (k*step - k) # = -(step-2)*k + 1 # step=1 dAbsDiff = k+1 big positive # step=2 dAbsDiff = 1 # step=3 dAbsDiff = -k + 1 big negative # sub _NumSeq_Delta_dAbsDiff_min { my ($self) = @_; if ($self->{'step'} == 0) { # constant N dY=1 return 1; } if ($self->{'align'} eq 'right' && $self->{'step'} >= 3) { return undef; # big negative } return -1; } sub _NumSeq_Delta_dAbsDiff_max { my ($self) = @_; if ($self->{'step'} == 0) { # constant N dY=1 return 1; } if ($self->{'align'} eq 'right' && $self->{'step'} >= 2) { return 1; } return undef; } *_NumSeq_Delta_dAbsDiff_non_decreasing = \&_NumSeq_Delta__step_is_0; *_NumSeq_Delta_AbsdX_non_decreasing = \&_NumSeq_Delta__step_is_0; *_NumSeq_Delta_AbsdY_non_decreasing = \&_NumSeq_Delta__step_is_0; *_NumSeq_Delta_dDiffXY_non_decreasing = \&_NumSeq_Delta__step_is_0; *_NumSeq_Delta_dDiffYX_non_decreasing = \&_NumSeq_Delta__step_is_0; *_NumSeq_Delta_dSumAbs_non_decreasing = \&_NumSeq_Delta__step_is_0; sub _NumSeq_Delta_dRadius_min { my ($self) = @_; return ($self->{'step'} == 0 ? 1 : $self->{'step'} == 1 ? undef : -1/sqrt(2)); } sub _NumSeq_Delta_dRadius_max { my ($self) = @_; return ($self->{'step'} == 0 ? 1 : sqrt(2)); } *_NumSeq_Delta_dRadius_integer = \&_NumSeq_Delta__step_is_0; sub _NumSeq_Delta_dRSquared_min { my ($self) = @_; return ($self->{'step'} == 0 ? 1 : undef); } sub _NumSeq_Delta_dRSquared_max { my ($self) = @_; return ($self->{'step'} == 0 ? undef : undef); } *_NumSeq_Delta_dRSquared_increasing = \&_NumSeq_Delta__step_is_0; sub _NumSeq_Delta_dTRadius_min { my ($self) = @_; return ($self->{'step'} == 0 ? sqrt(3) : $self->{'align'} eq 'centre' && $self->{'step'} % 2 == 0 ? - sqrt(3) : undef); } sub _NumSeq_Delta_dTRadius_max { my ($self) = @_; return ($self->{'step'} <= 1 ? sqrt(3) : $self->{'align'} eq 'centre' && $self->{'step'} % 2 == 1 ? 2 # : $self->{'align'} eq 'centre' && $self->{'step'} % 2 == 0 ? 2 : undef); } sub _NumSeq_Delta_dTRSquared_min { my ($self) = @_; return ($self->{'step'} == 0 ? 3 # step=0 vertical line : undef); } *_NumSeq_Delta_dTRSquared_increasing = \&_NumSeq_Delta__step_is_0; use constant _NumSeq_Delta_TDir6_integer => 0; sub _NumSeq_Delta_DSquared_max { my ($self) = @_; return ($self->{'step'} == 0 ? 1 # X=0 vertical only : undef); } sub _NumSeq_Dir4_max_is_supremum { my ($self) = @_; return ($self->{'step'} == 0 ? 0 # north only, exact : 1); # supremum, west and 1 up } sub _NumSeq_Delta_Dir4_integer { my ($self) = @_; return ($self->{'step'} == 0 ? 1 # North only, integer : 0); # otherwise fraction } sub _NumSeq_Delta_dX_non_decreasing { my ($self) = @_; return ($self->{'step'} == 0); # step=0 is dX=0,dY=1 always } *_NumSeq_Delta_dY_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_Dir4_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_TDir6_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_Dist_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; *_NumSeq_Delta_TDist_non_decreasing = \&_NumSeq_Delta_dX_non_decreasing; use constant _NumSeq_Delta_oeis_anum => { # PyramidRows step=0 is trivial X=0,Y=N do { my $href = { dX => 'A000004', # all zeros, X=0 always dY => 'A000012', # all 1s Dir4 => 'A000012', # all 1s, North }; ('step=0,align=centre,n_start=1' => $href, 'step=0,align=right,n_start=1' => $href, 'step=0,align=left,n_start=1' => $href, ); # OEIS-Other: A000004 planepath=PyramidRows,step=0 delta_type=dX # OEIS-Other: A000012 planepath=PyramidRows,step=0 delta_type=dY # OEIS-Other: A000012 planepath=PyramidRows,step=0 delta_type=Dir4 }, # PyramidRows step=1 do { # n_start=1 my $href = { dDiffYX => 'A127949', dAbsDiff => 'A127949', # Y>=X so same as dDiffYX }; ('step=1,align=centre,n_start=1' => $href, 'step=1,align=right,n_start=1' => $href, ); # OEIS-Other: A127949 planepath=PyramidRows,step=1 delta_type=dDiffYX # OEIS-Other: A127949 planepath=PyramidRows,step=1 delta_type=dAbsDiff # OEIS-Other: A127949 planepath=PyramidRows,step=1,align=right delta_type=dDiffYX # OEIS-Other: A127949 planepath=PyramidRows,step=1,align=right delta_type=dAbsDiff }, do { # n_start=0 my $href = { dY => 'A023531', # 1,0,1,0,0,1,etc, 1 if n==k(k+3)/2 AbsdY => 'A023531', # abs(dy) same # Not quite, A167407 has an extra initial 0 # dDiffXY => 'A167407', }; ('step=1,align=centre,n_start=0' => $href, 'step=1,align=right,n_start=0' => $href, ); # OEIS-Catalogue: A023531 planepath=PyramidRows,step=1,n_start=0 delta_type=dY # OEIS-Other: A023531 planepath=PyramidRows,step=1,n_start=0 delta_type=AbsdY # OEIS-Other: A023531 planepath=PyramidRows,step=1,align=right,n_start=0 delta_type=dY # OEIS-Other: A023531 planepath=PyramidRows,step=1,align=right,n_start=0 delta_type=AbsdY }, 'step=1,align=left,n_start=0' => { dY => 'A023531', # 1,0,1,0,0,1,etc, 1 if n==k(k+3)/2 AbsdY => 'A023531', # abs(dy) same # OEIS-Other: A023531 planepath=PyramidRows,step=1,align=left,n_start=0 delta_type=dY # OEIS-Other: A023531 planepath=PyramidRows,step=1,align=left,n_start=0 delta_type=AbsdY }, # 'step=2,align=centre,n_start=0' => # { # # Not quite, extra initial 0 # # dDiffXY => 'A010052', # }, }; } { package Math::PlanePath::PyramidSides; use constant _NumSeq_Delta_dSumAbs_min => 0; # unchanged on diagonal use constant _NumSeq_Delta_dSumAbs_max => 1; # step to next diagonal use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_dRadius_min => -1; use constant _NumSeq_Delta_dRadius_max => 1; use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_Delta_dTRadius_min => - sqrt(3); use constant _NumSeq_Delta_dTRadius_max => sqrt(3); use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_dTRadius_max_is_supremum => 1; use constant _NumSeq_Delta_Dir4_integer => 0; # diagonals use constant _NumSeq_Delta_TDir6_integer => 1; use constant _NumSeq_Delta_oeis_anum => { 'n_start=1' => { AbsdY => 'A049240', # 0=square,1=non-square # OEIS-Catalogue: A049240 planepath=PyramidSides delta_type=AbsdY # Not quite, extra initial 1 in A010052 # dSumAbs => 'A010052', 1 at n=square }, }; } { package Math::PlanePath::CellularRule; sub _NumSeq_Delta_dSum_non_decreasing { my ($self) = @_; return (($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? 1 # is constant dSum=+1 : undef); } sub _NumSeq_Delta_dSumAbs_min { my ($self) = @_; return (($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? 1 # is constant dSum=+1 : ($self->{'rule'} & 0x5F) == 0x0E # left line 2 ? -1 : $self->{'rule'} == 7 ? -1 : $self->{'rule'} == 9 ? -2 : $self->{'rule'}==11 || $self->{'rule'}==43 ? -1 : $self->{'rule'} == 15 ? -1 : $self->{'rule'}==19 ? -1 : $self->{'rule'}==21 ? -1 : ($self->{'rule'} & 0x9F) == 0x17 # 0x17,...,0x7F ? -1 : $self->{'rule'}==31 ? -1 : $self->{'rule'}==41 ? -1 : $self->{'rule'}==47 ? -1 : $self->{'rule'}==62 ? -2 : $self->{'rule'}==65 ? -2 : $self->{'rule'}==69 ? -2 : $self->{'rule'}==70 || $self->{'rule'}==198 ? -2 : $self->{'rule'}==77 ? -2 : $self->{'rule'}==78 ? -2 : undef); } sub _NumSeq_Delta_dSumAbs_max { my ($self) = @_; return (($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? 1 # is constant dSum=+1 : ($self->{'rule'} & 0x5F) == 0x0E # left line 2 ? 3 : $self->{'rule'} == 9 ? 3 : $self->{'rule'}==11 || $self->{'rule'}==43 ? 3 : $self->{'rule'} == 13 ? 2 : $self->{'rule'} == 15 ? 3 : $self->{'rule'}==28 || $self->{'rule'}==156 ? 2 : $self->{'rule'}==47 ? 3 : $self->{'rule'}==77 ? 3 : undef); } sub _NumSeq_Delta_dAbsDiff_min { my ($self) = @_; return (($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? -1 : ($self->{'rule'} & 0x5F) == 0x0E # left line 2 ? -1 : ($self->{'rule'} & 0xDF) == 3 # rule=3,35 ? -3 : $self->{'rule'} == 5 ? -2 : $self->{'rule'} == 7 ? -1 : $self->{'rule'} == 9 ? -2 : ($self->{'rule'} & 0xDF) == 11 # rule=11,43 ? -1 : $self->{'rule'} == 13 ? -2 : $self->{'rule'} == 15 ? -1 : ($self->{'rule'} & 0xDF) == 17 # rule=17,49 ? -3 : $self->{'rule'} == 19 ? -2 : $self->{'rule'} == 21 ? -1 : ($self->{'rule'} & 0x97) == 23 # rule=23,31,55,63,87,95,119,127 ? -1 : $self->{'rule'} == 27 ? -2 : $self->{'rule'} == 29 ? -2 : undef); } sub _NumSeq_Delta_dAbsDiff_max { my ($self) = @_; return (($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? 1 : ($self->{'rule'} & 0x5F) == 0x0E # left line 2 ? 3 : undef); } sub _NumSeq_Delta_dTRadius_min { my ($self) = @_; return ( # X=0,Y=1 TRadius=sqrt(3) to X=1,Y=1 TRadius=sqrt(1+3)=2 ($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? 2-sqrt(3) : undef); } sub _NumSeq_Delta_dTRadius_max { my ($self) = @_; return (($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? sqrt(3) : undef); } sub _NumSeq_Delta_dTRSquared_min { my ($self) = @_; return (($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? 1 : undef); } sub _NumSeq_Dir4_max_is_supremum { my ($self) = @_; return (($self->{'rule'} & 0x5F) == 0x54 # right line 2 || ($self->{'rule'} & 0x5F) == 0x0E # left line 2 ? 0 : 1); # supremum } sub _NumSeq_Delta_Dir4_integer { my ($self) = @_; return (($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? 1 # N,E only : 0); # various diagonals } sub _NumSeq_Delta_dY_non_decreasing { my ($self) = @_; return (($self->{'rule'} & 0x17) == 0 # single cell only ? 1 : 0); } use constant _NumSeq_Delta_TDir6_integer => 0; # usually } { package Math::PlanePath::CellularRule::OneTwo; use constant _NumSeq_Dir4_max_is_supremum => 0; use constant _NumSeq_Delta_dSum_non_decreasing => 0; sub _NumSeq_Delta_dSumAbs_min { my ($self) = @_; return ($self->{'align'} eq 'left' ? -1 : 1); } sub _NumSeq_Delta_dSumAbs_max { my ($self) = @_; return ($self->{'align'} eq 'left' ? 3 : 2); } use constant _NumSeq_Delta_dAbsDiff_min => -1; sub _NumSeq_Delta_dAbsDiff_max { my ($self) = @_; return ($self->{'align'} eq 'left' ? 3 : 1); } { my %_NumSeq_Delta_dRadius_min = (left => - sqrt(2)/2, right => sqrt(2)-1); sub _NumSeq_Delta_dRadius_min { my ($self) = @_; return $_NumSeq_Delta_dRadius_min{$self->{'align'}}; } } { # left max # * # \ # *---* # my %_NumSeq_Delta_dRadius_max = (left => sqrt(2)*3/2, right => sqrt(2)); sub _NumSeq_Delta_dRadius_max { my ($self) = @_; return $_NumSeq_Delta_dRadius_max{$self->{'align'}}; } } sub _NumSeq_Delta_dRSquared_min { my ($self) = @_; return ($self->{'align'} eq 'left' ? undef : 1); } # H = sqrt(dX^2 + 3*dY^2) # p/h = X/Y*sqrt(3) = sqrt(3)/S S = Y/X slope # p = h*sqrt(3)/S # h^2 + p^2 = 3*dY^2 # h^2 + h^2*3/S^2 = 3*dY^2 # h^2(1 + 3/S^2) = 3*dY^2 # h^2 = 3*dY^2 / (1 + 3/S^2) # h = dY * sqrt(3 / (1 + 3/S^2)) # h = dY * sqrt(1 / (1/3 + 1/S^2)) # dY=1, S=-1, h=sqrt(3)/2 # Left horiz X=-k,Y=k to X=-k+1,Y=k # dTRadius = sqrt((k-1)^2 + 3k^2) - sqrt(k^2 + 3k^2) # = sqrt(k^2-2k+1 + 3k^2) - sqrt(k^2 + 3k^2) # = sqrt(4k^2 -2k + 1) - sqrt(4k^2) # # *------------* # . 1 + | # . + | # . + p |sqrt(3) # . | # H=sqrt(1+3) . | # H=2 . | # . | # h * # # p/h = 1/sqrt(3) # p = h / sqrt(3) # h^2 + p^2 = 3 # h^2 + h^2*1/3 = 3 # h^2*4/3 = 3 # h^2 = 9/4 # h = 3/2 # H-h = 2 - 3/2 = 1/2 # sub _NumSeq_Delta_dTRadius_min { my ($self) = @_; return ($self->{'align'} eq 'left' ? -1/2 : undef); } use constant _NumSeq_dTRadius_min_is_infimum => 1; # Left 2,1 up # dX=2 # * _ * # . ---___ dY*sqrt(3) # H . ---__ # whole . * # .1 + | # . + | # + p | dX*S*sqrt(3) - dY*sqrt(3) # . | # h. | # .| # * # H^2 = dX^2 + (sqrt(3)*S*dX)^2 # = 4 + 3*4 = 16 H=4 # # p/h = 1/(S/sqrt(3)) = sqrt(3)/S # p = h*S/sqrt(3) # h^2 + p^2 = (dX*S*sqrt(3) - dY*sqrt(3))^2 # h^2 + p^2 = 3*(dX*S - dY)^2 # h^2 + S^2/3 * h^2 = 3*(dX*S - dY)^2 # h^2 = 3*(dX*S - dY)^2 / (1 + S^2/3) # h^2 = 9/4 # H-h = sqrt(dX^2 + 3*(dX*S)^2) - sqrt(3*(dX*S - dY)^2 / (1 + S^2/3)) # sub _NumSeq_Delta_dTRadius_max { my ($self) = @_; return ($self->{'align'} eq 'left' ? 5/2 : 2); # at N=3 NE diagonal up } sub _NumSeq_dTRadius_max_is_supremum { my ($self) = @_; return ($self->{'align'} eq 'left'); } sub _NumSeq_Delta_dTRSquared_min { my ($self) = @_; return ($self->{'align'} eq 'right' ? 1 : undef); } use constant _NumSeq_Delta_oeis_anum => { 'align=right,n_start=0' => { dSumAbs => 'A177702', # 1,1,2 repeating, OFFSET=0 # OEIS-Catalogue: A177702 planepath=CellularRule,rule=20,n_start=0 delta_type=dSumAbs }, 'align=left,n_start=0' => { AbsdX => 'A177702', # 1,1,2 repeating, OFFSET=0 dSum => 'A102283', # 0,1,-1 repeating, OFFSET=0 dSumAbs => 'A131756', # 2,-1,3 repeating, OFFSET=0 # OEIS-Other: A177702 planepath=CellularRule,rule=6,n_start=0 delta_type=AbsdX # OEIS-Catalogue: A102283 planepath=CellularRule,rule=6,n_start=0 delta_type=dSum # OEIS-Catalogue: A131756 planepath=CellularRule,rule=6,n_start=0 delta_type=dSumAbs }, }; } { package Math::PlanePath::CellularRule::Two; use constant _NumSeq_Dir4_max_is_supremum => 0; use constant _NumSeq_Delta_dSum_non_decreasing => 0; sub _NumSeq_Delta_dSumAbs_min { my ($self) = @_; return ($self->{'align'} eq 'left' ? -1 : 1); } sub _NumSeq_Delta_dSumAbs_max { my ($self) = @_; return ($self->{'align'} eq 'left' ? 3 : 1); } use constant _NumSeq_Delta_dAbsDiff_min => -1; sub _NumSeq_Delta_dAbsDiff_max { my ($self) = @_; return ($self->{'align'} eq 'left' ? 3 : 1); } { my %_NumSeq_Delta_dRadius_min = (left => - sqrt(2)/2, right => sqrt(2)-1); sub _NumSeq_Delta_dRadius_min { my ($self) = @_; return $_NumSeq_Delta_dRadius_min{$self->{'align'}}; } } { my %_NumSeq_Delta_dRadius_max = (left => sqrt(2)*3/2, right => 1); # at N=1 sub _NumSeq_Delta_dRadius_max { my ($self) = @_; return $_NumSeq_Delta_dRadius_max{$self->{'align'}}; } } # dRsquared 2*k^2 - ((k-1)^2 + k^2) = 2*k-1 # (k^2 + (k+1)^2) - 2*k^2 = 2*k-1 sub _NumSeq_Delta_dRSquared_min { my ($self) = @_; return ($self->{'align'} eq 'left' ? undef : 1); } sub _NumSeq_Delta_dRSquared_non_decreasing { my ($self) = @_; return ($self->{'align'} eq 'right'); } sub _NumSeq_Delta_dTRadius_min { my ($self) = @_; return ($self->{'align'} eq 'left' ? -1/2 : undef); } use constant _NumSeq_dTRadius_min_is_infimum => 1; sub _NumSeq_Delta_dTRadius_max { my ($self) = @_; return ($self->{'align'} eq 'left' ? 5/2 : 2); # at N=3 NE diagonal up } sub _NumSeq_dTRadius_max_is_supremum { my ($self) = @_; return ($self->{'align'} eq 'left'); } sub _NumSeq_Delta_dTRSquared_min { my ($self) = @_; return ($self->{'align'} eq 'right' ? 1 : undef); } sub _NumSeq_Delta_Dir4_integer { my ($self) = @_; return ($self->{'align'} eq 'right'); } use constant _NumSeq_Delta_oeis_anum => { 'align=left,n_start=0' => { dSum => 'A062157', # 0 then 1,-1 repeating # OEIS-Catalogue: A062157 planepath=CellularRule,rule=14,n_start=0 delta_type=dSum # OEIS-Other: A062157 planepath=CellularRule,rule=174,n_start=0 delta_type=dSum }, 'align=right,n_start=0' => { dRSquared => 'A109613', # 1,1,3,3,5,5 odd repeat, OFFSET+0 # OEIS-Catalogue: A109613 planepath=CellularRule,rule=84,n_start=0 delta_type=dRSquared # OEIS-Other: A109613 planepath=CellularRule,rule=244,n_start=0 delta_type=dRSquared }, }; } { package Math::PlanePath::CellularRule::Line; sub _NumSeq_Delta_dSumAbs_min { my ($self) = @_; return abs($self->{'sign'}) + 1; # dX=abs(sign),dY=1 always } *_NumSeq_Delta_dSumAbs_max = \&_NumSeq_Delta_dSumAbs_min; # constant left => 2 # centre => 1 # right => 0 sub _NumSeq_Delta_dAbsDiff_min { my ($self) = @_; return 1-$self->{'sign'}; } *_NumSeq_Delta_dAbsDiff_max = \&_NumSeq_Delta_dAbsDiff_min; sub _NumSeq_Delta_dTRadius_min { my ($self) = @_; return ($self->{'align'} eq 'centre' ? sqrt(3) # centre => sqrt(3) constant : 2); # left,right => 2 constant } *_NumSeq_Delta_dTRadius_max = \&_NumSeq_Delta_dTRadius_min; sub _NumSeq_Delta_dTRadius_integer { my ($self) = @_; return ($self->{'align'} ne 'centre'); } sub _NumSeq_Delta_dTRSquared_min { my ($self) = @_; return ($self->{'align'} eq 'centre' ? 3 # centre => 3 : 4); # left,right => 4 } use constant _NumSeq_Delta_dTRSquared_increasing => 1; sub _NumSeq_Delta_DSquared_min { my ($path) = @_; return abs($path->{'sign'}) + 1; } *_NumSeq_Delta_DSquared_max = \&_NumSeq_Delta_DSquared_min; use constant _NumSeq_Dir4_max_is_supremum => 0; use constant _NumSeq_TDir6_max_is_supremum => 0; sub _NumSeq_Delta_Dir4_integer { my ($self) = @_; return ($self->{'sign'} == 0 ? 1 # vertical Dir4=1 : 0); # left,right Dir4=0.5 or 1.5 } sub _NumSeq_Delta_TDir6_integer { my ($self) = @_; return ($self->{'sign'} == 0 ? 0 # vertical TDir6=1.5 : 1); # left,right Tdir6=1 or 2 } use constant _NumSeq_Delta_dX_non_decreasing => 1; # constant use constant _NumSeq_Delta_dY_non_decreasing => 1; # constant use constant _NumSeq_Delta_AbsdX_non_decreasing => 1; # constant use constant _NumSeq_Delta_AbsdY_non_decreasing => 1; # constant use constant _NumSeq_Delta_dSum_non_decreasing => 1; # constant use constant _NumSeq_Delta_dSumAbs_non_decreasing => 1; # constant use constant _NumSeq_Delta_dDiffXY_non_decreasing => 1; # constant use constant _NumSeq_Delta_dDiffYX_non_decreasing => 1; # constant use constant _NumSeq_Delta_dAbsDiff_non_decreasing => 1; # constant use constant _NumSeq_Delta_Dir4_non_decreasing => 1; # constant use constant _NumSeq_Delta_TDir6_non_decreasing => 1; # constant use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDist_non_decreasing => 1; } { package Math::PlanePath::CellularRule::OddSolid; use constant _NumSeq_Delta_dSumAbs_min => -2; use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_IntXY_min => -1; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Dir4_max_is_supremum => 1; use constant _NumSeq_Delta_Dir4_integer => 0; # between rows use constant _NumSeq_Delta_DSquared_min => 2; use constant _NumSeq_Delta_TDir6_integer => 0; # between rows } { package Math::PlanePath::CellularRule54; use constant _NumSeq_Dir4_max_is_supremum => 1; use constant _NumSeq_Delta_Dir4_integer => 0; # between rows use constant _NumSeq_Delta_TDir6_integer => 0; # between rows } { package Math::PlanePath::CellularRule57; use constant _NumSeq_Delta_dAbsDiff_min => -3; use constant _NumSeq_Dir4_max_is_supremum => 1; use constant _NumSeq_Delta_Dir4_integer => 0; # between rows use constant _NumSeq_Delta_TDir6_integer => 0; # between rows } { package Math::PlanePath::CellularRule190; use constant _NumSeq_Delta_dSumAbs_min => -2; # towards Y axis dX=+2 use constant _NumSeq_Delta_dSumAbs_max => 2; # away Y axis dX=+2 use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Dir4_max_is_supremum => 1; use constant _NumSeq_Delta_Dir4_integer => 0; # between rows use constant _NumSeq_Delta_TDir6_integer => 0; # between rows } { package Math::PlanePath::UlamWarburton; # minimum dir=0 at N=1 use constant _NumSeq_Delta_DSquared_min => 2; # diagonal use constant _NumSeq_Delta_TDSquared_min => 4; # diagonal # always diagonal slope=+/-1 within depth level. parts=2 is horizontal # between levels, but parts=1 or parts=4 are other slopes between levels. sub _NumSeq_Delta_TDir6_integer { my ($self) = @_; return ($self->{'parts'} eq '2'); } } { package Math::PlanePath::UlamWarburtonQuarter; use constant _NumSeq_Delta_Dir4_integer => 0; # N=1 North-East use constant _NumSeq_Delta_TDir6_integer => 0; # N=3 North } { package Math::PlanePath::CoprimeColumns; use constant _NumSeq_Delta_TDir6_integer => 0; # between verticals } { package Math::PlanePath::DivisibleColumns; use constant _NumSeq_Delta_TDir6_integer => 0; # between verticals } # { package Math::PlanePath::File; # # FIXME: analyze points for dx/dy min/max etc # } { package Math::PlanePath::QuintetCurve; # NSEW # inherit QuintetCentres, exceptions: use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } { package Math::PlanePath::QuintetCentres; # NSEW+diag use constant _NumSeq_Delta_dSumAbs_min => -2; use constant _NumSeq_Delta_dSumAbs_max => 2; use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_DSquared_max => 2; } { package Math::PlanePath::QuintetReplicate; # N=1874 Dir4=3.65596 # N=9374 Dir4=3.96738, etc # Dir4 supremum at 244...44 base 5 use constant _NumSeq_Dir4_max_is_supremum => 1; } { package Math::PlanePath::AR2W2Curve; # NSEW+diag use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_DSquared_max => 2; } { package Math::PlanePath::KochelCurve; # NSEW use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } { package Math::PlanePath::BetaOmega; # NSEW use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } { package Math::PlanePath::DekkingCurve; # NSEW # segments on the X or Y axes always step away from the origin, so # dRadius_max==1 and dRadius_min only approaches -1 use constant _NumSeq_Delta_dRadius_min => -1; use constant _NumSeq_Delta_dRadius_max => 1; use constant _NumSeq_dRadius_min_is_infimum => 1; use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } { package Math::PlanePath::DekkingCentres; # NSEW+diag use constant _NumSeq_Delta_dAbsDiff_min => -2; use constant _NumSeq_Delta_dAbsDiff_max => 2; use constant _NumSeq_Delta_DSquared_max => 2; # The X=Y leading diagonal has both forward and backward segments. # First forward is at X=Y=22. First reverse is at X=Y=12. use constant _NumSeq_Delta_dRadius_min => -sqrt(2); use constant _NumSeq_Delta_dRadius_max => sqrt(2); } { package Math::PlanePath::CincoCurve; # NSEW use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } { package Math::PlanePath::WunderlichMeander; # NSEW use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } { package Math::PlanePath::HIndexing; # NSEW use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } { package Math::PlanePath::DigitGroups; use constant _NumSeq_Dir4_max_is_supremum => 1; # almost full way } # { package Math::PlanePath::CornerReplicate; # sub _UNTESTED__NumSeq_Delta_dSum_pred { # my ($path, $value) = @_; # # target 1,-1,-3,-7,-15,etc # # negatives value = -(2^k-1) for k>=1 so -1,-3,-7,-15,etc # # -value = 2^k-1 # # 1-value = 2^k = 2,4,8,etc # $value = 1-$value; # 0,2,4,8,16,etc # if ($value == 0) { return 1; } # original $value=1 # return ($value >= 2 && _is_pow2($value)); # } # sub _is_pow2 { # my ($n) = @_; # my ($pow,$exp) = round_down_pow ($n, 2); # return ($n == $pow); # } # } { package Math::PlanePath::SquareReplicate; use constant _NumSeq_Delta_Dir4_integer => 0; } { package Math::PlanePath::FibonacciWordFractal; # NSEW use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; use constant _NumSeq_Delta_oeis_anum => { '' => { AbsdX => 'A171587', # diagonal variant # OEIS-Catalogue: A171587 planepath=FibonacciWordFractal delta_type=AbsdX }, }; } { package Math::PlanePath::LTiling; use constant _NumSeq_Dir4_max_is_supremum => 1; # almost full way sub _NumSeq_Delta_DSquared_min { my ($self) = @_; return ($self->{'L_fill'} eq 'middle' ? 5 # N=2 dX=2,dY=1 : 1); } sub _NumSeq_Delta_TDSquared_min { my ($self) = @_; return ($self->{'L_fill'} eq 'middle' ? 7 # N=2 dX=2,dY=1 : 1); } } { package Math::PlanePath::WythoffArray; use constant _NumSeq_Delta_TDSquared_min => 1; } { package Math::PlanePath::WythoffPreliminaryTriangle; use constant _NumSeq_Dir4_min_is_infimum => 1; } { package Math::PlanePath::PowerArray; # at N=1to2 either dX=1,dY=0 if radix=2 or dX=0,dY=1 if radix>2 sub _NumSeq_Delta_TDSquared_min { my ($self) = @_; return ($self->{'radix'} == 2 ? 1 # dX=1,dY=0 : 3); # dX=0,dY=1 } use constant _NumSeq_Delta_oeis_anum => { 'radix=2' => { # Not quite, OFFSET=0 # AbsdX => 'A050603', # add1c(n,2) # # OEIS-Catalogue: A050603 planepath=PowerArray,radix=2 delta_type=AbsdX # # # Not quite, starts OFFSET=0 (even though A001511 starts OFFSET=1) # # # vs n_start=1 here # # dX => 'A094267', # first diffs of count low 0s # # # OEIS-Catalogue: A094267 planepath=PowerArray,radix=2 # # # # Not quite, starts OFFSET=0 values 0,1,-1,2 as diffs of A025480 # # # 0,0,1,0,2, vs n_start=1 here doesn't include 0 # # dY => 'A108715', # first diffs of odd part of n # # # OEIS-Catalogue: A108715 planepath=PowerArray,radix=2 delta_type=dY }, }; } { package Math::PlanePath::ToothpickTree; { my %_NumSeq_Dir4_max_is_supremum = (3 => 1, 2 => 1, 1 => 1, ); sub _NumSeq_Dir4_max_is_supremum { my ($self) = @_; return $_NumSeq_Dir4_max_is_supremum{$self->{'parts'}}; } } use constant _NumSeq_Delta_Dir4_integer => 0; } { package Math::PlanePath::ToothpickReplicate; use constant _NumSeq_Dir4_max_is_supremum => 1; } { package Math::PlanePath::ToothpickUpist; use constant _NumSeq_Delta_Dir4_integer => 0; # diagonal between rows use constant _NumSeq_Delta_TDir6_integer => 0; # diagonal between rows } { package Math::PlanePath::LCornerTree; { my %_NumSeq_Dir4_max_is_supremum = (4 => 0, 3 => 1, 2 => 1, 1 => 1, octant => 0, ); sub _NumSeq_Dir4_max_is_supremum { my ($self) = @_; return $_NumSeq_Dir4_max_is_supremum{$self->{'parts'}}; } } use constant _NumSeq_Delta_Dir4_integer => 0; use constant _NumSeq_Delta_TDir6_integer => 0; } { package Math::PlanePath::ToothpickSpiral; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_dTRadius_min => - sqrt(3); use constant _NumSeq_Delta_dTRadius_max => sqrt(3); use constant _NumSeq_dTRadius_min_is_infimum => 1; use constant _NumSeq_dTRadius_max_is_supremum => 1; use constant _NumSeq_Delta_oeis_anum => { 'n_start=0' => { AbsdX => 'A000035', # 0,1 repeating AbsdY => 'A059841', # 1,0 repeating }, }; } { package Math::PlanePath::LCornerReplicate; use constant _NumSeq_Dir4_max_is_supremum => 1; } { package Math::PlanePath::OneOfEight; { my %_NumSeq_Dir4_max_is_supremum = (4 => 0, 1 => 1, octant => 0, '3mid' => 1, '3side' => 1, ); sub _NumSeq_Dir4_max_is_supremum { my ($self) = @_; return $_NumSeq_Dir4_max_is_supremum{$self->{'parts'}}; } } use constant _NumSeq_Delta_Dir4_integer => 0; use constant _NumSeq_Delta_TDir6_integer => 0; } 1; __END__ # sub pred { # my ($self, $value) = @_; # # my $planepath_object = $self->{'planepath_object'}; # my $figure = $planepath_object->figure; # if ($figure eq 'square') { # if ($value != int($value)) { # return 0; # } # } elsif ($figure eq 'circle') { # return 1; # } # # my $delta_type = $self->{'delta_type'}; # if ($delta_type eq 'X') { # if ($planepath_object->x_negative) { # return 1; # } else { # return ($value >= 0); # } # } elsif ($delta_type eq 'Y') { # if ($planepath_object->y_negative) { # return 1; # } else { # return ($value >= 0); # } # } elsif ($delta_type eq 'Sum') { # if ($planepath_object->x_negative || $planepath_object->y_negative) { # return 1; # } else { # return ($value >= 0); # } # } # # return undef; # } =for stopwords Ryde dX dY dX+dY dX-dY dSum dDiffXY DiffXY dDiffYX dAbsDiff AbsDiff TDir6 Math-NumSeq Math-PlanePath NumSeq SquareSpiral PlanePath AbsdX AbsdY NSEW boolean dSumAbs SumAbs ENWS dRadius dRSquared RSquared supremum OEIS =head1 NAME Math::NumSeq::PlanePathDelta -- sequence of changes and directions of PlanePath coordinates =head1 SYNOPSIS use Math::NumSeq::PlanePathDelta; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => 'SquareSpiral', delta_type => 'dX'); my ($i, $value) = $seq->next; =head1 DESCRIPTION This is a tie-in to present coordinate changes and directions from a C module in the form of a NumSeq sequence. The C choices are "dX" change in X coordinate "dY" change in Y coordinate "AbsdX" abs(dX) "AbsdY" abs(dY) "dSum" change in X+Y, equals dX+dY "dSumAbs" change in abs(X)+abs(Y) "dDiffXY" change in X-Y, equals dX-dY "dDiffYX" change in Y-X, equals dY-dX "dAbsDiff" change in abs(X-Y) "dRadius" change in Radius sqrt(X^2+Y^2) "dRSquared" change in RSquared X^2+Y^2 "Dir4" direction 0=East, 1=North, 2=West, 3=South "TDir6" triangular 0=E, 1=NE, 2=NW, 3=W, 4=SW, 5=SE In each case the value at i is per C<$path-En_to_dxdy($i)>, being the change from N=i to N=i+1, or from N=i to N=i+arms for paths with multiple "arms" (thus following the arm). i values start from the usual C<$path-En_start()>. =head2 AbsdX,AbsdY If a path always steps NSEW by 1 then AbsdX and AbsdY behave as a boolean indicating horizontal or vertical step, NSEW steps by 1 gives AbsdX = 0 vertical AbsdY = 0 horizontal 1 horizontal 1 vertical If a path includes diagonal steps by 1 then those diagonals are a non-zero delta, so the indication is then NSEW and diagonals steps by 1 gives AbsdX = 0 vertical AbsdY = 0 horizontal 1 non-vertical 1 non-horizontal ie. horiz or diag ie. vert or diag =head2 dSum "dSum" is the change in X+Y and is also simply dX+dY since dSum = (Xnext+Ynext) - (X+Y) = (Xnext-X) + (Ynext-Y) = dX + dY The sum X+Y counts anti-diagonals, as described in L. dSum is therefore a move between diagonals, or 0 if a step stays within the same diagonal. \ \ ^ dSum > 0 dSum = step dist to North-East \/ /\ dSum < 0 v \ \ =head2 dSumAbs "dSumAbs" is the change in the abs(X)+abs(Y) sum, dSumAbs = (abs(Xnext)+abs(Ynext)) - (abs(X)+abs(Y)) As described in L, SumAbs is a "Manhattan" or "taxi-cab" distance from the origin, or equivalently a move between diamond-shaped rings. For example C follows a diamond shape ring around and so has dSumAbs=0 until stepping out to each next diamond with dSumAbs=1. A path might make a big X,Y jump which is only a small change in SumAbs. For example C in its default step=2 from the end of one row to the start of the next has dSumAbs=2. =head2 dDiffXY and dDiffYX "dDiffXY" is the change in DiffXY = X-Y, which is also simply dX-dY since dDiffXY = (Xnext-Ynext) - (X-Y) = (Xnext-X) - (Ynext-Y) = dX - dY The difference X-Y counts diagonals downwards to the south-east as described in L. dDiffXY is therefore movement between those diagonals, or 0 if a step stays within the same diagonal. dDiffXY < 0 / \ / dDiffXY = step dist to South-East \/ /\ / v / dDiffXY > 0 "dDiffYX" is the negative of dDiffXY. Whether X-Y or Y-X is desired depends on which way you want to measure diagonals, or which way around to have the sign for the changes. dDiffYX is based on Y-X and so counts diagonals upwards to the North-West. =head2 dAbsDiff "dAbsDiff" is the change in AbsDiff = abs(X-Y). AbsDiff can be interpreted geometrically as distance from the leading diagonal, as described in L. dAbsDiff is therefore movement closer to or further away from that leading diagonal, measuring perpendicular to it. / X=Y line / / ^ / \ / * dAbsDiff move towards or away from X=Y line |/ \ --o-- v /| / When an X,Y jumps from one side of the diagonal to the other dAbsDiff is still the change in distance from the diagonal. So for example if X,Y is followed by the mirror point Y,X then dAbsDiff=0. That sort of thing happens for example in the C path when jumping from the end of one run to the start of the next. In the C case it's a move just 1 further away from the X=Y centre line even though it's a big jump in overall distance. =head2 dRadius, dRSquared "dRadius" and "dRSquared" are the change in the Radius and RSquared as described in L. dRadius = next_Radius - Radius dRSquared = next_RSquared - RSquared dRadius can be interpreted geometrically as movement towards (negative values) or away from (positive values) the origin, ignoring direction. Notice that dRadius is not sqrt(dRSquared), since sqrt(n^2-t^2) != n-t unless n or t is zero. Here would mean a step either going to or coming from the origin 0,0. =head2 Dir4 "Dir4" is the curve step direction as an angle in the range 0 E= Dir4 E 4. The cardinal directions E,N,W,S are 0,1,2,3. Angles in between are a fraction. Dir4 = atan2(dY,dX) scaled as range 0 <= Dir4 < 4 1.5 1 0.5 \ | / \|/ 2 ----o---- 0 /|\ / | \ 2.5 3 3.5 If a row such as Y=-1,XE0 just below the X axis is visited then the Dir4 approaches 4, without ever reaching it. The C<$seq-Evalue_maximum()> is 4 in this case, as a supremum. =head2 TDir6 "TDir6" is the curve step direction 0 E= TDir6 E 6 taken in the triangular style of L. So dX=1,dY=1 is taken to be 60 degrees which is TDir6=1. 2 1.5 1 TDir6 \ | / \|/ 3 ---o--- 0 /|\ / | \ 4 4.5 5 Angles in between the six cardinal directions are fractions. North is 1.5 and South is 4.5. The direction angle is calculated as if dY was scaled by a factor sqrt(3) to make the lattice into equilateral triangles, or equivalently as a circle stretched vertically by sqrt(3) to become an ellipse. TDir6 = atan2(dY*sqrt(3), dX) in range 0 <= TDir6 < 6 Notice that angles on the axes dX=0 or dY=0 are not changed by the sqrt(3) factor. So TDir6 has ENWS 0, 1.5, 3, 4.5 which is steps of 1.5. Verticals North and South normally don't occur in the triangular lattice paths which go by unit steps, but TDir6 can be applied on any path. The sqrt(3) factor increases angles in the middle of the quadrants. For example dX=1,dY=1 becomes TDir6=1 whereas a plain angle would be only 45/360*6=0.75 in the same 0 to 6 scale. The sqrt(3) is a continuous scaling, so a plain angle and a TDir6 are a one-to-one mapping. As the direction progresses through the quadrant TDir6 grows first faster and then slower than the plain angle. =cut # TDir6 = atan2(dY*sqrt(3), dX) # Dir6 = atan2(dY, dX) # TDir6 = Dir6 * atan2(dY*sqrt(3), dX)/atan2(dY, dX) =pod =head1 FUNCTIONS See L for behaviour common to all sequence classes. =over 4 =item C<$seq = Math::NumSeq::PlanePathDelta-Enew (key=Evalue,...)> Create and return a new sequence object. The options are planepath string, name of a PlanePath module planepath_object PlanePath object delta_type string, as described above C can be either the module part such as "SquareSpiral" or a full class name "Math::PlanePath::SquareSpiral". =item C<$value = $seq-Eith($i)> Return the change at N=$i in the PlanePath. =item C<$i = $seq-Ei_start()> Return the first index C<$i> in the sequence. This is the position C<$seq-Erewind()> returns to. This is C<$path-En_start()> from the PlanePath. =back =head1 BUGS Some path sequences don't have C and are not available through L entry due to the path C not matching the OEIS "offset". Paths with an C parameter have suitable adjustments applied, but those without are omitted from the L mechanism presently. =head1 SEE ALSO L, L, L, L L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/NumSeq/PlanePathN.pm0000644000175000017500000036421213774021652017124 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Maybe: # "Turn" N of turn positions # $path->n_next_turn($n) package Math::NumSeq::PlanePathN; use 5.004; use strict; use Carp 'croak'; use constant 1.02; use vars '$VERSION','@ISA'; $VERSION = 129; use Math::NumSeq; @ISA = ('Math::NumSeq'); use Math::NumSeq::PlanePathCoord; # uncomment this to run the ### lines # use Smart::Comments; sub description { my ($self) = @_; if (ref $self) { return "N values on $self->{'line_type'} of path $self->{'planepath'}"; } else { # class method return 'N values from a PlanePath'; } } use constant::defer parameter_info_array => sub { return [ Math::NumSeq::PlanePathCoord::_parameter_info_planepath(), { name => 'line_type', display => 'Line Type', type => 'enum', default => 'X_axis', choices => ['X_axis', 'Y_axis', 'X_neg', 'Y_neg', 'Diagonal', 'Diagonal_NW', 'Diagonal_SW', 'Diagonal_SE', 'Depth_start', 'Depth_end', ], description => 'The axis or line to take path N values from.', }, ]; }; #------------------------------------------------------------------------------ my %oeis_anum = ( # MultipleRings,step=0 -- integers 1,2,3, etc, but starting i=0 ); sub oeis_anum { my ($self) = @_; ### PlanePathN oeis_anum() ... my $planepath_object = $self->{'planepath_object'}; { my $key = Math::NumSeq::PlanePathCoord::_planepath_oeis_anum_key($self->{'planepath_object'}); my $i_start = $self->i_start; if ($i_start != $self->default_i_start) { ### $i_start ### cf n_start: $planepath_object->n_start $key .= ",i_start=$i_start"; } ### planepath: ref $planepath_object ### $key ### whole table: $planepath_object->_NumSeq_N_oeis_anum ### key href: $planepath_object->_NumSeq_N_oeis_anum->{$key} if (my $anum = $planepath_object->_NumSeq_N_oeis_anum->{$key}->{$self->{'line_type'}}) { return $anum; } if (my $anum = $planepath_object->_NumSeq_N_oeis_all_anum->{$self->{'line_type'}}) { return $anum; } } { my $key = Math::NumSeq::PlanePathCoord::_planepath_oeis_key($planepath_object); my $i_start = $self->i_start; if ($i_start != $self->default_i_start) { ### $i_start ### cf n_start: $planepath_object->n_start $key .= ",i_start=$i_start"; } ### $key ### hash: $oeis_anum{$key} return $oeis_anum{$key}->{$self->{'line_type'}}; } } #------------------------------------------------------------------------------ sub default_i_start { my ($self) = @_; my $planepath_object = $self->{'planepath_object'} # nasty hack allow no 'planepath_object' when SUPER::new() calls rewind() || return 0; my $method = "_NumSeq_$self->{'line_type'}_i_start"; if (my $func = $planepath_object->can($method)) { return $planepath_object->$func(); } return 0; } sub new { my $self = shift->SUPER::new(@_); my $planepath_object = ($self->{'planepath_object'} ||= Math::NumSeq::PlanePathCoord::_planepath_name_to_object($self->{'planepath'})); ### $planepath_object my $line_type = $self->{'line_type'}; ### i_func name: "i_func_$line_type" $self->{'i_func'} = $self->can("i_func_$line_type") || croak "Unrecognised line_type: ",$line_type; $self->{'pred_func'} = $self->can("pred_func_$line_type") || croak "Unrecognised line_type: ",$line_type; if (my $func = $planepath_object->can("_NumSeq_${line_type}_step")) { $self->{'i_step'} = $planepath_object->$func(); } elsif ($planepath_object->_NumSeq_A2() && ($line_type eq 'X_axis' || $line_type eq 'Y_axis' || $line_type eq 'X_neg' || $line_type eq 'Y_neg')) { $self->{'i_step'} = 2; } else { $self->{'i_step'} = 1; } ### i_step: $self->{'i_step'} # for use in pred() $self->{'i_start'} = $self->i_start; $self->rewind; return $self; } sub rewind { my ($self) = @_; $self->{'i'} = $self->i_start; } sub next { my ($self) = @_; ### NumSeq-PlanePathN next(): "i=$self->{'i'}" my $i = $self->{'i'}; my $n = &{$self->{'i_func'}} ($self, $i); ### $n if (! defined $n) { ### i_func returns undef, no value ... return; } # secret experimental automatic bigint to preserve precision if (! ref $n && $n > 0xFF_FFFF) { $n = &{$self->{'i_func'}}($self,_to_bigint($i)); } return ($self->{'i'}++, $n); } sub _to_bigint { my ($n) = @_; # stringize to avoid UV->BigInt bug in Math::BigInt::GMP version 1.37 return _bigint()->new("$n"); } # or maybe check for new enough for uv->mpz fix use constant::defer _bigint => sub { # no choice of back-end here, leave that to the mainline require Math::BigInt; return 'Math::BigInt'; }; sub ith { my ($self, $i) = @_; ### NumSeq-PlanePathN ith(): $i return &{$self->{'i_func'}}($self, $i); } sub i_func_X_axis { my ($self, $i) = @_; my $path_object = $self->{'planepath_object'}; return $path_object->xy_to_n ($i * $self->{'i_step'}, $path_object->_NumSeq_X_axis_at_Y); } sub i_func_Y_axis { my ($self, $i) = @_; ### i_func_Y_axis(): "i=$i" ### X: $self->{'planepath_object'}->_NumSeq_Y_axis_at_X ### Y: $i * $self->{'i_step'} my $path_object = $self->{'planepath_object'}; return $path_object->xy_to_n ($path_object->_NumSeq_Y_axis_at_X, $i * $self->{'i_step'}); } sub i_func_X_neg { my ($self, $i) = @_; ### i_func_X_neg(): $i my $path_object = $self->{'planepath_object'}; return $path_object->xy_to_n (-$i * $self->{'i_step'}, $path_object->_NumSeq_X_axis_at_Y); } sub i_func_Y_neg { my ($self, $i) = @_; my $path_object = $self->{'planepath_object'}; return $path_object->xy_to_n ($path_object->_NumSeq_Y_axis_at_X, - $i * $self->{'i_step'}); } sub i_func_Diagonal { my ($self, $i) = @_; my $path_object = $self->{'planepath_object'}; return $path_object->xy_to_n ($i + $path_object->_NumSeq_Diagonal_X_offset, $i); } sub i_func_Diagonal_NW { my ($self, $i) = @_; my $path_object = $self->{'planepath_object'}; return $path_object->xy_to_n (-$i + $path_object->_NumSeq_Diagonal_X_offset, $i); } sub i_func_Diagonal_SW { my ($self, $i) = @_; my $path_object = $self->{'planepath_object'}; return $path_object->xy_to_n (-$i + $path_object->_NumSeq_Diagonal_X_offset, -$i); } sub i_func_Diagonal_SE { my ($self, $i) = @_; my $path_object = $self->{'planepath_object'}; return $path_object->xy_to_n ($i + $path_object->_NumSeq_Diagonal_X_offset, -$i); } sub i_func_Depth_start { my ($self, $i) = @_; ### i_func_Depth_start(): "i=$i" return $self->{'planepath_object'}->tree_depth_to_n($i); } sub i_func_Depth_end { my ($self, $i) = @_; return $self->{'planepath_object'}->tree_depth_to_n_end($i); } #------------------------------------------------------------------------------ sub pred { my ($self, $value) = @_; ### PlanePathN pred(): $value my $planepath_object = $self->{'planepath_object'}; unless ($value == int($value)) { return 0; } my ($x,$y) = $planepath_object->n_to_xy($value) or return 0; return &{$self->{'pred_func'}} ($self, $x,$y, $value); } sub pred_func_X_axis { my ($self, $x,$y) = @_; return ($x >= $self->{'i_start'} && $y == $self->{'planepath_object'}->_NumSeq_X_axis_at_Y); } sub pred_func_Y_axis { my ($self, $x,$y) = @_; return ($x == $self->{'planepath_object'}->_NumSeq_Y_axis_at_X && $y >= $self->{'i_start'}); } sub pred_func_X_neg { my ($self, $x,$y) = @_; return ($x <= - $self->{'i_start'} && $y == 0); } sub pred_func_Y_neg { my ($self, $x,$y) = @_; return ($x == 0 && $y <= - $self->{'i_start'}); } sub pred_func_Diagonal { my ($self, $x,$y) = @_; $x -= $self->{'planepath_object'}->_NumSeq_Diagonal_X_offset; return ($x >= $self->{'i_start'} && $x == $y); } sub pred_func_Diagonal_NW { my ($self, $x,$y) = @_; return ($x <= - $self->{'i_start'} && $x == -$y); } sub pred_func_Diagonal_SW { my ($self, $x,$y) = @_; return ($x <= - $self->{'i_start'} && $x == $y); } sub pred_func_Diagonal_SE { my ($self, $x,$y) = @_; return ($x >= $self->{'i_start'} && $x == -$y); } sub pred_func_Depth_start { my ($self, $x,$y, $n) = @_; return path_tree_n_is_depth_start($self->{'planepath_object'}, $n); } sub pred_func_Depth_end { my ($self, $x,$y, $n) = @_; return path_tree_n_is_depth_end($self->{'planepath_object'}, $n); } # Return true if $n is the start of a depth level. sub path_tree_n_is_depth_start { my ($path, $n) = @_; my $depth = $path->tree_n_to_depth($n); return (defined $depth && $n == $path->tree_depth_to_n($depth)); } # Return true if $n is the end of a depth level. sub path_tree_n_is_depth_end { my ($path, $n) = @_; my $depth = $path->tree_n_to_depth($n); return (defined $depth && $n == $path->tree_depth_to_n_end($depth)); } #------------------------------------------------------------------------------ use constant characteristic_integer => 1; # integer Ns sub characteristic_increasing { my ($self) = @_; ### PlanePathN characteristic_increasing(): $self my $method = "_NumSeq_$self->{'line_type'}_increasing"; my $planepath_object = $self->{'planepath_object'}; ### planepath_object: ref $planepath_object ### $method ### can code: $planepath_object->can($method) ### result: $planepath_object->can($method) && $planepath_object->$method() return $planepath_object->can($method) && $planepath_object->$method(); } sub characteristic_increasing_from_i { my ($self) = @_; ### PlanePathN characteristic_increasing_from_i(): $self my $planepath_object = $self->{'planepath_object'}; my $method = "_NumSeq_$self->{'line_type'}_increasing_from_i"; ### $method if ($method = $planepath_object->can($method)) { ### can: $method return $planepath_object->$method(); } return ($self->characteristic('increasing') ? $self->i_start : undef); } sub characteristic_non_decreasing { my ($self) = @_; ### PlanePathN characteristic_non_decreasing() ... my $planepath_object = $self->{'planepath_object'}; my $method = "_NumSeq_$self->{'line_type'}_non_decreasing"; return (($planepath_object->can($method) && $planepath_object->$method()) || $self->characteristic_increasing); } sub values_min { my ($self) = @_; ### PlanePathN values_min() ... my $method = "_NumSeq_$self->{'line_type'}_min"; my $planepath_object = $self->{'planepath_object'}; if (my $coderef = $planepath_object->can($method)) { ### $coderef return $planepath_object->$coderef(); } return $self->ith($self->i_start); } sub values_max { my ($self) = @_; my $method = "_NumSeq_$self->{'line_type'}_max"; my $planepath_object = $self->{'planepath_object'}; if (my $coderef = $planepath_object->can($method)) { return $planepath_object->$coderef(); } return undef; } { package Math::PlanePath; sub _NumSeq_X_axis_i_start { my ($self) = @_; my $x_minimum = $self->x_minimum; if (defined $x_minimum && $x_minimum > 0) { return int($x_minimum); } return 0; } sub _NumSeq_Y_axis_i_start { my ($self) = @_; ### _NumSeq_Y_axis_i_start() ... my $y_minimum = $self->y_minimum; if (defined $y_minimum && $y_minimum > 0) { return int($y_minimum); } return 0; } sub _NumSeq_Y_axis_at_X { my ($self) = @_; my $x_minimum = $self->x_minimum; if (defined $x_minimum && $x_minimum > 0) { return $x_minimum; } return 0; } sub _NumSeq_X_axis_at_Y { my ($self) = @_; my $y_minimum = $self->y_minimum; if (defined $y_minimum && $y_minimum > 0) { return $y_minimum; } return 0; } # i_start = Xminimum - Xoffset # gives Xstart = i_start + Xoffset = Xminimum to start at Xminimum use List::Util; sub _NumSeq_Diagonal_i_start { my ($self) = @_; my $x_minimum = $self->x_minimum; my $y_minimum = $self->y_minimum; return List::Util::max (int(($x_minimum||0) - $self->_NumSeq_Diagonal_X_offset), int($y_minimum||0), 0); } use constant _NumSeq_Diagonal_X_offset => 0; use constant _NumSeq_N_oeis_anum => {}; use constant _NumSeq_N_oeis_all_anum => {}; use constant _NumSeq_Depth_start_increasing => 1; use constant _NumSeq_Depth_end_increasing => 1; # sub _NumSeq_pred_X_axis { # my ($path, $value) = @_; # return ($value == int($value) # && ($path->x_negative || $value >= 0)); # } # sub _NumSeq_pred_Y_axis { # my ($path, $value) = @_; # return ($value == int($value) # && ($path->y_negative || $value >= 0)); # } } { package Math::PlanePath::SquareSpiral; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; sub _NumSeq_X_neg_increasing { my ($self) = @_; return ($self->{'wider'} == 0); } sub _NumSeq_X_neg_increasing_from_i { my ($self) = @_; ### SquareSpiral _NumSeq_X_neg_increasing_from_i(): $self # wider=0 from X=0 # wider=1 from X=-1 # wider=2 from X=-1 return int(($self->{'wider'}+1)/2); } use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; sub _NumSeq_X_neg_min { # not the value at X=0,Y=0 if wider>0 my ($self) = @_; return $self->n_start; } use constant _NumSeq_N_oeis_anum => { 'wider=0,n_start=1' => { X_axis => 'A054552', # spoke E, 4n^2 - 3n + 1 Y_neg => 'A033951', # spoke S, 4n^2 + 3n + 1 Diagonal_NW => 'A053755', # 4n^2 + 1 Diagonal_SE => 'A016754', # (2n+1)^2 # OEIS-Catalogue: A054552 planepath=SquareSpiral # OEIS-Catalogue: A033951 planepath=SquareSpiral line_type=Y_neg # OEIS-Catalogue: A053755 planepath=SquareSpiral line_type=Diagonal_NW # OEIS-Catalogue: A016754 planepath=SquareSpiral line_type=Diagonal_SE # # OEIS-Other: A054552 planepath=GreekKeySpiral,turns=0 # OEIS-Other: A033951 planepath=GreekKeySpiral,turns=0 line_type=Y_neg # OEIS-Other: A053755 planepath=GreekKeySpiral,turns=0 line_type=Diagonal_NW # OEIS-Other: A016754 planepath=GreekKeySpiral,turns=0 line_type=Diagonal_SE # Not quite, these have OFFSET=1 whereas path start X=0 # # Y_axis => 'A054556', # spoke N # # X_neg => 'A054567', # spoke W # # Diagonal => 'A054554', # spoke NE # # Diagonal_SW => 'A054569', # spoke NE # # # OEIS-Catalogue: A054556 planepath=SquareSpiral line_type=Y_axis # # # OEIS-Catalogue: A054554 planepath=SquareSpiral line_type=Diagonal }, 'wider=0,n_start=0' => { X_axis => 'A001107', Y_axis => 'A033991', Y_neg => 'A033954', # second 10-gonals Diagonal => 'A002939', Diagonal_NW => 'A016742', # 10-gonals average, 4*n^2 Diagonal_SW => 'A002943', # OEIS-Other: A001107 planepath=SquareSpiral,n_start=0 # OEIS-Catalogue: A033991 planepath=SquareSpiral,n_start=0 line_type=Y_axis # OEIS-Other: A033954 planepath=SquareSpiral,n_start=0 line_type=Y_neg # OEIS-Catalogue: A002939 planepath=SquareSpiral,n_start=0 line_type=Diagonal # OEIS-Other: A016742 planepath=SquareSpiral,n_start=0 line_type=Diagonal_NW # OEIS-Catalogue: A002943 planepath=SquareSpiral,n_start=0 line_type=Diagonal_SW }, 'wider=1,n_start=1' => { Diagonal_SW => 'A069894', # OEIS-Catalogue: A069894 planepath=SquareSpiral,wider=1 line_type=Diagonal_SW }, 'wider=1,n_start=0' => { Diagonal_SW => 'A016754', # odd squares # OEIS-Other: A016754 planepath=SquareSpiral,wider=1,n_start=0 line_type=Diagonal_SW }, }; } { package Math::PlanePath::GreekKeySpiral; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; sub _NumSeq_X_neg_increasing { my ($self) = @_; return ($self->{'turns'} == 0); # when SquareSpiral style } *_NumSeq_Y_neg_increasing = \&_NumSeq_X_neg_increasing; sub _NumSeq_Diagonal_increasing { my ($self) = @_; return ($self->{'turns'} <= 1); } sub _NumSeq_Diagonal_NW_increasing { my ($self) = @_; return ($self->{'turns'} == 0); } *_NumSeq_Diagonal_SW_increasing = \&_NumSeq_Diagonal_increasing; sub _NumSeq_Diagonal_SE_increasing { my ($self) = @_; return ($self->{'turns'} <= 2); } use constant _NumSeq_N_oeis_anum => { 'turns=0' => (Math::PlanePath::SquareSpiral ->_NumSeq_N_oeis_anum->{'wider=0,n_start=1'} || die "Oops, SquareSpiral NumSeq PlanePathN not found"), }; } { package Math::PlanePath::PyramidSpiral; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'n_start=1' => { X_axis => 'A054552', # square spiral spoke E, 4n^2 - 3n + 1 Diagonal_SE => 'A033951', # square spiral spoke S, 4n^2 + 3n + 1 # OEIS-Other: A054552 planepath=PyramidSpiral # OEIS-Other: A033951 planepath=PyramidSpiral line_type=Diagonal_SE }, 'n_start=0' => { X_axis => 'A001107', # decagonal Y_axis => 'A002939', X_neg => 'A033991', Y_neg => 'A002943', Diagonal_SW => 'A007742', Diagonal_SE => 'A033954', # decagonal second kind # OEIS-Other: A001107 planepath=PyramidSpiral,n_start=0 # OEIS-Other: A002939 planepath=PyramidSpiral,n_start=0 line_type=Y_axis # OEIS-Other: A033991 planepath=PyramidSpiral,n_start=0 line_type=X_neg # OEIS-Other: A002943 planepath=PyramidSpiral,n_start=0 line_type=Y_neg # OEIS-Other: A007742 planepath=PyramidSpiral,n_start=0 line_type=Diagonal_SW # OEIS-Other: A033954 planepath=PyramidSpiral,n_start=0 line_type=Diagonal_SE }, 'n_start=2' => { Diagonal_SE => 'A185669', # OEIS-Catalogue: A185669 planepath=PyramidSpiral,n_start=2 line_type=Diagonal_SE }, }; } { package Math::PlanePath::TriangleSpiral; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'n_start=1' => { X_axis => 'A117625', # step by 2 each time Y_neg => 'A006137', # step by 2 each time Diagonal_SW => 'A064225', Diagonal_SE => 'A081267', # OEIS-Other: A117625 planepath=TriangleSpiral # OEIS-Other: A064225 planepath=TriangleSpiral line_type=Diagonal_SW # OEIS-Other: A081267 planepath=TriangleSpiral line_type=Diagonal_SE # # Not quite, starts value=3 at n=0 which is path Y=1 # Diagonal => 'A064226', # and duplicate in A081269 }, 'n_start=0' => { Y_axis => 'A062741', # 3*pentagonal, Y even Diagonal => 'A062708', # reading in direction 0,2,... Diagonal_SW => 'A062725', # reading in direction 0,7,... Diagonal_SE => 'A062728', # 11-gonal "second" per Math::NumSeq::Polygonal # OEIS-Catalogue: A062741 planepath=TriangleSpiral,n_start=0 line_type=Y_axis # OEIS-Catalogue: A062708 planepath=TriangleSpiral,n_start=0 line_type=Diagonal # OEIS-Catalogue: A062725 planepath=TriangleSpiral,n_start=0 line_type=Diagonal_SW # OEIS-Other: A062728 planepath=TriangleSpiral,n_start=0 line_type=Diagonal_SE # but spaced 2 apart ... # X_axis => 'A051682', # 11-gonals per Math::NumSeq::Polygonal # # OEIS-Other: A051682 planepath=TriangleSpiral,n_start=0 # X_axis }, }; } { package Math::PlanePath::TriangleSpiralSkewed; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; # ENHANCE-ME: All these variously rotated for skew=right,up,down use constant _NumSeq_N_oeis_anum => { 'skew=left,n_start=1' => { X_axis => 'A117625', X_neg => 'A006137', Y_neg => 'A064225', Diagonal => 'A081589', Diagonal_SW => 'A038764', Diagonal_SE => 'A081267', # OEIS-Catalogue: A117625 planepath=TriangleSpiralSkewed # OEIS-Catalogue: A006137 planepath=TriangleSpiralSkewed line_type=X_neg # OEIS-Catalogue: A064225 planepath=TriangleSpiralSkewed line_type=Y_neg # OEIS-Catalogue: A081589 planepath=TriangleSpiralSkewed line_type=Diagonal # OEIS-Catalogue: A038764 planepath=TriangleSpiralSkewed line_type=Diagonal_SW # OEIS-Catalogue: A081267 planepath=TriangleSpiralSkewed line_type=Diagonal_SE # OEIS-Catalogue: A081274 planepath=TriangleSpiralSkewed line_type=Diagonal_SW # # Not quite, starts OFFSET=0 value=3 but that is at path Y=1 # Y_axis => 'A064226', # and duplicate in A081269 }, 'skew=left,n_start=0' => { X_axis => 'A051682', # 11-gonals per Math::NumSeq::Polygonal Y_axis => 'A062708', # reading in direction 0,2,... Y_neg => 'A062725', # reading in direction 0,7,... Diagonal_SE => 'A062728', # 11-gonal "second" per Math::NumSeq::Polygonal Diagonal_SW => 'A081266', # OEIS-Other: A051682 planepath=TriangleSpiralSkewed,n_start=0 # X_axis # OEIS-Other: A062708 planepath=TriangleSpiralSkewed,n_start=0 line_type=Y_axis # OEIS-Other: A062725 planepath=TriangleSpiralSkewed,n_start=0 line_type=Y_neg # OEIS-Other: A062728 planepath=TriangleSpiralSkewed,n_start=0 line_type=Diagonal_SE # OEIS-Catalogue: A081266 planepath=TriangleSpiralSkewed,n_start=0 line_type=Diagonal_SW }, }; } { package Math::PlanePath::DiamondSpiral; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'n_start=1' => { X_axis => 'A130883', # 2*n^2-n+1 X_neg => 'A084849', # 2*n^2+n+1 Y_axis => 'A058331', # 2*n^2 + 1 Y_neg => 'A001844', # centred squares 2n(n+1)+1 # OEIS-Catalogue: A130883 planepath=DiamondSpiral # OEIS-Other: A084849 planepath=DiamondSpiral line_type=X_neg # OEIS-Catalogue: A058331 planepath=DiamondSpiral line_type=Y_axis # OEIS-Other: A001844 planepath=DiamondSpiral line_type=Y_neg }, 'n_start=0' => { X_axis => 'A000384', # 2*n^2-n, hexagonal numbers X_neg => 'A014105', # 2*n^2+n, hexagonal numbers second kind Y_axis => 'A001105', # 2*n^2 Y_neg => 'A046092', # 2n(n+1) = 4*triangular # OEIS-Other: A000384 planepath=DiamondSpiral,n_start=0 # OEIS-Other: A014105 planepath=DiamondSpiral,n_start=0 line_type=X_neg # OEIS-Other: A001105 planepath=DiamondSpiral,n_start=0 line_type=Y_axis # OEIS-Other: A046092 planepath=DiamondSpiral,n_start=0 line_type=Y_neg }, }; } { package Math::PlanePath::DiamondArms; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; } { package Math::PlanePath::AztecDiamondRings; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'n_start=1' => { X_axis => 'A001844', # centred squares 2n(n+1)+1 # OEIS-Other: A001844 planepath=AztecDiamondRings # Not quite, A000384 has extra value=0 # Y_axis => 'A000384', # hexagonal numbers }, 'n_start=0' => { X_axis => 'A046092', # 4*triangular Diagonal => 'A139277', # x*(8*x+5) # OEIS-Other: A046092 planepath=AztecDiamondRings,n_start=0 # OEIS-Other: A139277 planepath=AztecDiamondRings,n_start=0 line_type=Diagonal }, }; } { package Math::PlanePath::PentSpiral; use constant _NumSeq_X_axis_step => 2; # not A2 grid, but X axis steps by 2 use constant _NumSeq_X_neg_step => 2; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'n_start=1' => { X_axis => 'A192136', # (5*n^2-3*n+2)/2 X_neg => 'A116668', # (5n^2 + n + 2)/2 Diagonal_SE => 'A005891', # centred pentagonal (5n^2+5n+2)/2 # OEIS-Other: A192136 planepath=PentSpiral # OEIS-Other: A116668 planepath=PentSpiral line_type=X_neg # OEIS-Other: A005891 planepath=PentSpiral line_type=Diagonal_SE # Not quite, A134238 OFFSET=1 vs start X=0 here # Diagonal_SW => 'A134238', }, 'n_start=0' => { X_axis => 'A000566', # heptagonals Y_axis => 'A005476', Diagonal_SE => 'A028895', # 5*triangular # OEIS-Other: A000566 planepath=PentSpiral,n_start=0 # OEIS-Other: A005476 planepath=PentSpiral,n_start=0 line_type=Y_axis # OEIS-Other: A028895 planepath=PentSpiral,n_start=0 line_type=Diagonal_SE }, }; } { package Math::PlanePath::PentSpiralSkewed; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'n_start=1' => { X_axis => 'A192136', # (5*n^2-3*n+2)/2 X_neg => 'A116668', # (5n^2 + n + 2)/2 Diagonal_NW => 'A158187', # 10*n^2 + 1 Diagonal_SE => 'A005891', # centred pentagonal (5n^2+5n+2)/2 # OEIS-Catalogue: A192136 planepath=PentSpiralSkewed # OEIS-Catalogue: A116668 planepath=PentSpiralSkewed line_type=X_neg # OEIS-Catalogue: A158187 planepath=PentSpiralSkewed line_type=Diagonal_NW # OEIS-Catalogue: A005891 planepath=PentSpiralSkewed line_type=Diagonal_SE # Not quite, A140066 OFFSET=1 but path start Y=0 here # Y_axis => 'A140066', # (5n^2-11n+8)/2 but from Y=0 so using (n-1) # Not quite, A134238 OFFSET=1 but path start Y=0 here # Y_neg => 'A134238', # # OEIS-Catalogue: A134238 planepath=PentSpiralSkewed line_type=Y_neg }, 'n_start=0' => { X_axis => 'A000566', # heptagonals Y_axis => 'A005476', X_neg => 'A005475', Diagonal_NW => 'A033583', # 10*n^2 Diagonal_SE => 'A028895', # 5*triangular # OEIS-Other: A000566 planepath=PentSpiralSkewed,n_start=0 # OEIS-Catalogue: A005476 planepath=PentSpiralSkewed,n_start=0 line_type=Y_axis # OEIS-Catalogue: A005475 planepath=PentSpiralSkewed,n_start=0 line_type=X_neg # OEIS-Other: A033583 planepath=PentSpiralSkewed,n_start=0 line_type=Diagonal_NW # OEIS-Catalogue: A028895 planepath=PentSpiralSkewed,n_start=0 line_type=Diagonal_SE # Not quite, A147875 OFFSET=1 vs start Y=0 here # Y_neg => 'A147875', # second heptagonals # # OEIS-Other: A147875 planepath=PentSpiralSkewed,n_start=0 line_type=Y_neg }, }; } { package Math::PlanePath::HexSpiral; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; *_NumSeq_X_neg_increasing = \&Math::PlanePath::SquareSpiral::_NumSeq_X_neg_increasing; *_NumSeq_X_neg_increasing_from_i = \&Math::PlanePath::SquareSpiral::_NumSeq_X_neg_increasing_from_i; *_NumSeq_X_neg_min = \&Math::PlanePath::SquareSpiral::_NumSeq_X_neg_min; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'wider=0,n_start=1' => { X_axis => 'A056105', # first spoke 3n^2-2n+1 Diagonal => 'A056106', # second spoke 3n^2-n+1 Diagonal_NW => 'A056107', # third spoke 3n^2+1 X_neg => 'A056108', # fourth spoke 3n^2+n+1 Diagonal_SW => 'A056109', # fifth spoke 3n^2+2n+1 Diagonal_SE => 'A003215', # centred hexagonal numbers # OEIS-Other: A056105 planepath=HexSpiral # OEIS-Other: A056106 planepath=HexSpiral line_type=Diagonal # OEIS-Other: A056107 planepath=HexSpiral line_type=Diagonal_NW # OEIS-Other: A056108 planepath=HexSpiral line_type=X_neg # OEIS-Other: A056109 planepath=HexSpiral line_type=Diagonal_SW # OEIS-Other: A003215 planepath=HexSpiral line_type=Diagonal_SE }, 'wider=0,n_start=0' => { X_axis => 'A000567', # octagonal numbers X_neg => 'A049451', Diagonal => 'A049450', Diagonal_NW => 'A033428', # octagonal numbers first,second average Diagonal_SW => 'A045944', # octagonal numbers second Diagonal_SE => 'A028896', # OEIS-Other: A000567 planepath=HexSpiral,n_start=0 # OEIS-Other: A049451 planepath=HexSpiral,n_start=0 line_type=X_neg # OEIS-Catalogue: A049450 planepath=HexSpiral,n_start=0 line_type=Diagonal # OEIS-Other: A033428 planepath=HexSpiral,n_start=0 line_type=Diagonal_NW # OEIS-Other: A045944 planepath=HexSpiral,n_start=0 line_type=Diagonal_SW # OEIS-Catalogue: A028896 planepath=HexSpiral,n_start=0 line_type=Diagonal_SE }, }; } { package Math::PlanePath::HexSpiralSkewed; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; *_NumSeq_X_neg_increasing = \&Math::PlanePath::SquareSpiral::_NumSeq_X_neg_increasing; *_NumSeq_X_neg_increasing_from_i = \&Math::PlanePath::SquareSpiral::_NumSeq_X_neg_increasing_from_i; *_NumSeq_X_neg_min = \&Math::PlanePath::SquareSpiral::_NumSeq_X_neg_min; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'wider=0,n_start=1' => { X_axis => 'A056105', # first spoke 3n^2-2n+1 Y_axis => 'A056106', # second spoke 3n^2-n+1 Diagonal_NW => 'A056107', # third spoke 3n^2+1 X_neg => 'A056108', # fourth spoke 3n^2+n+1 Y_neg => 'A056109', # fifth spoke 3n^2+2n+1 Diagonal_SE => 'A003215', # centred hexagonal numbers # OEIS-Catalogue: A056105 planepath=HexSpiralSkewed # OEIS-Catalogue: A056106 planepath=HexSpiralSkewed line_type=Y_axis # OEIS-Catalogue: A056108 planepath=HexSpiralSkewed line_type=X_neg # OEIS-Catalogue: A056109 planepath=HexSpiralSkewed line_type=Y_neg # OEIS-Catalogue: A056107 planepath=HexSpiralSkewed line_type=Diagonal_NW # OEIS-Other: A003215 planepath=HexSpiralSkewed line_type=Diagonal_SE }, 'wider=0,n_start=0' => { X_axis => 'A000567', # octagonal numbers Y_axis => 'A049450', X_neg => 'A049451', Y_neg => 'A045944', # octagonal numbers second Diagonal => 'A062783', Diagonal_NW => 'A033428', # octagonal numbers first,second average Diagonal_SW => 'A063436', Diagonal_SE => 'A028896', # OEIS-Other: A000567 planepath=HexSpiralSkewed,n_start=0 # OEIS-Other: A049450 planepath=HexSpiralSkewed,n_start=0 line_type=Y_axis # OEIS-Catalogue: A049451 planepath=HexSpiralSkewed,n_start=0 line_type=X_neg # OEIS-Other: A045944 planepath=HexSpiralSkewed,n_start=0 line_type=Y_neg # OEIS-Catalogue: A062783 planepath=HexSpiralSkewed,n_start=0 line_type=Diagonal # OEIS-Other: A033428 planepath=HexSpiralSkewed,n_start=0 line_type=Diagonal_NW # OEIS-Catalogue: A063436 planepath=HexSpiralSkewed,n_start=0 line_type=Diagonal_SW # OEIS-Other: A028896 planepath=HexSpiralSkewed,n_start=0 line_type=Diagonal_SE }, # wider=1 X_axis almost 3*n^2 but not initial X=0 value # wider=1 Y_axis almost A049451 twice pentagonal but not initial X=0 # wider=2 Y_axis almost A028896 6*triangular but not initial Y=0 }; } { package Math::PlanePath::HexArms; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; } { package Math::PlanePath::HeptSpiralSkewed; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_N_oeis_anum => { # 'n_start=1' => # { # # Not quite, OFFSET=1 vs path start X=Y=0 # # Y_axis => 'A140065', # (7n^2 - 17n + 12)/2 but starting Y=0 not n=1 # # Diagonal_NW => 'A140063', # # Diagonal_SE => 'A069099', # }, 'n_start=0' => { X_axis => 'A001106', # 9-gonals Y_axis => 'A218471', X_neg => 'A022265', Y_neg => 'A179986', # second 9-gonals Diagonal => 'A195023', Diagonal_NW => 'A022264', Diagonal_SW => 'A186029', Diagonal_SE => 'A024966', # OEIS-Other: A001106 planepath=HeptSpiralSkewed,n_start=0 # OEIS-Catalogue: A022265 planepath=HeptSpiralSkewed,n_start=0 line_type=X_neg # OEIS-Catalogue: A218471 planepath=HeptSpiralSkewed,n_start=0 line_type=Y_axis # OEIS-Other: A179986 planepath=HeptSpiralSkewed,n_start=0 line_type=Y_neg # OEIS-Catalogue: A195023 planepath=HeptSpiralSkewed,n_start=0 line_type=Diagonal # OEIS-Catalogue: A022264 planepath=HeptSpiralSkewed,n_start=0 line_type=Diagonal_NW # OEIS-Catalogue: A186029 planepath=HeptSpiralSkewed,n_start=0 line_type=Diagonal_SW # OEIS-Catalogue: A024966 planepath=HeptSpiralSkewed,n_start=0 line_type=Diagonal_SE }, }; } { package Math::PlanePath::OctagramSpiral; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'n_start=1' => { Diagonal_SE => 'A194268', # OEIS-Other: A194268 planepath=OctagramSpiral line_type=Diagonal_SE # Not quite, but A125201 doesn't have initial N=1 for path origin # X_axis => 'A125201' }, 'n_start=0' => { X_axis => 'A051870', # 18-gonals Y_axis => 'A139273', X_neg => 'A139275', Y_neg => 'A139277', Diagonal => 'A139272', Diagonal_NW => 'A139274', Diagonal_SW => 'A139276', Diagonal_SE => 'A139278', # second 18-gonals # OEIS-Other: A051870 planepath=OctagramSpiral,n_start=0 # OEIS-Catalogue: A139273 planepath=OctagramSpiral,n_start=0 line_type=Y_axis # OEIS-Catalogue: A139275 planepath=OctagramSpiral,n_start=0 line_type=X_neg # OEIS-Catalogue: A139277 planepath=OctagramSpiral,n_start=0 line_type=Y_neg # OEIS-Catalogue: A139272 planepath=OctagramSpiral,n_start=0 line_type=Diagonal # OEIS-Catalogue: A139274 planepath=OctagramSpiral,n_start=0 line_type=Diagonal_NW # OEIS-Catalogue: A139276 planepath=OctagramSpiral,n_start=0 line_type=Diagonal_SW # OEIS-Other: A139278 planepath=OctagramSpiral,n_start=0 line_type=Diagonal_SE }, }; } { package Math::PlanePath::AnvilSpiral; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; *_NumSeq_X_neg_increasing = \&Math::PlanePath::SquareSpiral::_NumSeq_X_neg_increasing; *_NumSeq_X_neg_increasing_from_i = \&Math::PlanePath::SquareSpiral::_NumSeq_X_neg_increasing_from_i; *_NumSeq_X_neg_min = \&Math::PlanePath::SquareSpiral::_NumSeq_X_neg_min; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'wider=0,n_start=1' => { X_axis => 'A033570', # odd pentagonals (2n+1)*(3n+1) Diagonal => 'A033568', # odd second pentagonals Diagonal_SE => 'A085473', # OEIS-Catalogue: A033570 planepath=AnvilSpiral # OEIS-Catalogue: A033568 planepath=AnvilSpiral line_type=Diagonal # OEIS-Catalogue: A085473 planepath=AnvilSpiral line_type=Diagonal_SE # Not quite, A136392 OFFSET=1 value=1 whereas path start Y=0 value=1 # Y_neg => 'A136392', # 1,9,29,61, 6n^2-10n+5 }, 'wider=0,n_start=1,i_start=1' => { Y_axis => 'A126587', # points within 3,4,5 triangle, starting value=3 # OEIS-Catalogue: A126587 planepath=AnvilSpiral line_type=Y_axis i_start=1 }, 'wider=0,n_start=0' => { X_axis => 'A211014', # 14-gonal second Y_axis => 'A139267', # 2*octagonal X_neg => 'A049452', # alternate pentagonals Y_neg => 'A033580', # 4*second pentagonals Diagonal => 'A051866', # 14-gonals Diagonal_NW => 'A094159', # 3*hexagonal Diagonal_SW => 'A049453', Diagonal_SE => 'A195319', # 3*second hexagonal # OEIS-Other: A211014 planepath=AnvilSpiral,n_start=0 # OEIS-Other: A051866 planepath=AnvilSpiral,n_start=0 line_type=Diagonal # OEIS-Catalogue: A139267 planepath=AnvilSpiral,n_start=0 line_type=Y_axis # OEIS-Catalogue: A049452 planepath=AnvilSpiral,n_start=0 line_type=X_neg # OEIS-Catalogue: A033580 planepath=AnvilSpiral,n_start=0 line_type=Y_neg # OEIS-Catalogue: A094159 planepath=AnvilSpiral,n_start=0 line_type=Diagonal_NW # OEIS-Catalogue: A049453 planepath=AnvilSpiral,n_start=0 line_type=Diagonal_SW # OEIS-Catalogue: A195319 planepath=AnvilSpiral,n_start=0 line_type=Diagonal_SE }, # Not quite, A051866 starts value=0 which is at X=-1 # 'wider=1,n_start=0' => # { X_axis => 'A051866', # # OEIS-Other: A051866 planepath=AnvilSpiral,wider=1,n_start=0 # }, # 'wider=1,n_start=0' => # { Diagonal_NW_minus_1 => 'A033569', # (2*n-1)*(3*n+1) starting -1 # # XX-Other: A033569 planepath=AnvilSpiral,wider=1,n_start=-1 line_type=Diagonal_NW # }, # 'wider=2,n_start=1' => # { # Not quite, A033581 initial value=2 whereas path start N=0 # # Y_axis => 'A033581', # 6*n^2 is 14-gonals pairs average in Math::NumSeq::Polygonal # # # OEIS-Other: A033581 planepath=AnvilSpiral,wider=2 line_type=Y_axis # }, }; } { package Math::PlanePath::KnightSpiral; use constant _NumSeq_Diagonal_increasing => 1; # low then high use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; } { package Math::PlanePath::CretanLabyrinth; use constant _NumSeq_X_axis_increasing => 1; } { package Math::PlanePath::SquareArms; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; } { package Math::PlanePath::SacksSpiral; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; # when touched use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; # when touched use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; # SacksSpiral X_axis -- squares (i-1)^2, starting from i=1 value=0 } { package Math::PlanePath::VogelFloret; use constant _NumSeq_X_axis_increasing => 1; # when touched use constant _NumSeq_Y_axis_increasing => 1; # when touched use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; # when touched use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; } { package Math::PlanePath::TheodorusSpiral; use constant _NumSeq_X_axis_increasing => 1; # when touched use constant _NumSeq_Y_axis_increasing => 1; # when touched use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; # when touched use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; } { package Math::PlanePath::ArchimedeanChords; use constant _NumSeq_X_axis_increasing => 1; # when touched use constant _NumSeq_Y_axis_increasing => 1; # when touched use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; # when touched use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; } { package Math::PlanePath::MultipleRings; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; # when touched use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; # when touched use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; } { package Math::PlanePath::PixelRings; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; # where covered use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; } { package Math::PlanePath::FilledRings; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; # use constant _NumSeq_N_oeis_anum => # { # # Not quite, A036704 OFFSET=0 value=1,9,21 vs X=0 value=0,1,9,21 # 'n_start=0' => # { X_axis => 'A036704', # count points norm <= n+1/2 # }, # }; } { package Math::PlanePath::Hypot; sub _NumSeq_X_axis_i_start { my ($self) = @_; ### _NumSeq_X_axis_i_start() ... return ($self->{'points'} eq 'odd' ? 1 # X=0,Y=0 not visited : 0); } sub _NumSeq_X_neg_i_start { my ($self) = @_; ### _NumSeq_X_axis_i_start() ... return ($self->{'points'} eq 'odd' ? -1 # X=0,Y=0 not visited : 0); } *_NumSeq_Y_axis_i_start = \&_NumSeq_X_axis_i_start; *_NumSeq_Y_neg_i_start = \&_NumSeq_X_neg_i_start; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'points=all,n_start=0' => { X_axis => 'A051132', # count points < n^2 # OEIS-Catalogue: A051132 planepath=Hypot,n_start=0 }, }; } { package Math::PlanePath::HypotOctant; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; *_NumSeq_X_axis_i_start = \&Math::PlanePath::Hypot::_NumSeq_X_axis_i_start; use constant _NumSeq_N_oeis_anum => { 'points=even' => { Diagonal => 'A036702', # count points |z|<=n for 0<=b<=a # OEIS-Catalogue: A036702 planepath=HypotOctant,points=even line_type=Diagonal }, }; } { package Math::PlanePath::TriangularHypot; sub _NumSeq_X_axis_i_start { my ($self) = @_; return ($self->{'points'} eq 'odd' || $self->{'points'} eq 'hex_centred' ? 1 # X=0,Y=0 not visited : 0); } *_NumSeq_Diagonal_i_start = \&_NumSeq_X_axis_i_start; *_NumSeq_Diagonal_SE_i_start = \&_NumSeq_X_axis_i_start; sub _NumSeq_X_neg_i_start { my ($self) = @_; return - $self->_NumSeq_X_axis_i_start; } *_NumSeq_Diagonal_NW_i_start = \&_NumSeq_X_axis_i_start; *_NumSeq_Diagonal_SW_i_start = \&_NumSeq_X_axis_i_start; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; } { package Math::PlanePath::PythagoreanTree; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_N_oeis_all_anum => { Depth_start => 'A007051', # (3^n+1)/2 # OEIS-Catalogue: A007051 planepath=PythagoreanTree line_type=Depth_start # Not quite, Depth_end=(3^(n+1)-1)/2, so is n+1 # Depth_end => 'A003462', # (3^n-1)/2 }; } { package Math::PlanePath::RationalsTree; use constant _NumSeq_X_axis_increasing => 1; sub _NumSeq_Y_axis_increasing { my ($self) = @_; return ($self->{'tree_type'} eq 'L' ? 0 : 1); } sub _NumSeq_Y_axis_increasing_from_i { my ($self) = @_; return ($self->{'tree_type'} eq 'L' ? 2 : 1); } use constant _NumSeq_Y_axis_min => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'tree_type=SB' => { Depth_start => 'A000079', # powers-of-2 # RationalsTree SB -- X_axis 2^n-1 but starting X=1 # RationalsTree SB,CW -- Y_axis A000079 2^n but starting Y=1 }, 'tree_type=CW' => { Depth_start => 'A000079', # powers-of-2 }, 'tree_type=Bird' => { X_axis => 'A081254', # local max sumdisttopow2(m)/m^2 Depth_start => 'A000079', # powers-of-2 # OEIS-Catalogue: A081254 planepath=RationalsTree,tree_type=Bird # OEIS-Other: A000079 planepath=RationalsTree,tree_type=Bird line_type=Depth_start # RationalsTree Bird -- Y_axis almost A000975 10101 101010 no # consecutive equal bits, but start=1 }, 'tree_type=Drib' => { X_axis => 'A086893', # pos of fibonacci F(n+1)/F(n) in Stern diatomic Depth_start => 'A000079', # powers-of-2 # OEIS-Catalogue: A086893 planepath=RationalsTree,tree_type=Drib # Drib Y_axis # Not quite, A061547 OFFSET=1 value=0 cf path Y=1 value N=1 # Y_axis => 'A061547'# derangements or alternating bits plus pow4 }, 'tree_type=AYT' => { Depth_start => 'A000079', # powers-of-2 # RationalsTree AYT -- Y_axis A083318 2^n+1 but starting Y=1 }, 'tree_type=HCS' => { Depth_start => 'A000079', # powers-of-2 # RationalsTree HCS # Not quite, A000079 OFFSET=0 value=1 cf here X=1 N=1 # X_axis => 'A000079', # powers 2^X # Not quite, A007283 OFFSET=0 and doesn't have extra N=1 at Y=1 # Y_axis => 'A007283', # 3*2^n starting OFFSET=0 value=3 }, }; } { package Math::PlanePath::FractionsTree; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_X_offset => -1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Y_axis_i_start => 2; } { package Math::PlanePath::ChanTree; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_X_axis_i_start => 1; use constant _NumSeq_Y_axis_i_start => 1; # start at Y=1 use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'k=2,n_start=1' => { Depth_start => 'A000079', # powers-of-2 # OEIS-Other: A000079 planepath=ChanTree,n_start=1,k=2 line_type=Depth_start # Depth_end is 2^k-1 A000225, or 2^k-2 A000918, but without initial # 0 or -1. }, 'k=2,n_start=0' => { Depth_start => 'A000225', # 2^k-1 # OEIS-Other: A000225 planepath=ChanTree,k=2 line_type=Depth_start }, 'k=3,n_start=1' => { Depth_start => 'A000244', # powers-of-3 # OEIS-Other: A000244 planepath=ChanTree,n_start=1 line_type=Depth_start }, 'k=4,n_start=1' => { Depth_start => 'A000302', # powers-of-4 # OEIS-Other: A000302 planepath=ChanTree,n_start=1,k=4 line_type=Depth_start }, 'k=5,n_start=1' => { Depth_start => 'A000351', # powers-of-5 # OEIS-Other: A000351 planepath=ChanTree,n_start=1,k=5 line_type=Depth_start }, 'k=10,n_start=1' => { Depth_start => 'A011557', # powers-of-10 # OEIS-Other: A011557 planepath=ChanTree,n_start=1,k=10 line_type=Depth_start }, }; } { package Math::PlanePath::DiagonalRationals; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_axis_i_start => 1; use constant _NumSeq_Y_axis_i_start => 1; # Diagonal => 'A002088', # cumulative totient # Not quite, start X=1 value=1 cf seq OFFSET=0 value=0 } { package Math::PlanePath::FactorRationals; use constant _NumSeq_X_axis_i_start => 1; sub _NumSeq_X_axis_increasing { my ($self) = @_; # perfect squares along X axis of even/odd return $self->{'factor_coding'} eq 'even/odd'; } use constant _NumSeq_Y_axis_i_start => 1; sub _NumSeq_Y_axis_increasing { my ($self) = @_; # perfect squares along Y axis of odd/even return $self->{'factor_coding'} eq 'odd/even'; } use constant _NumSeq_N_oeis_anum => { 'factor_coding=even/odd' => { Y_axis => 'A102631', # n^2/(squarefree kernel) # OEIS-Catalogue: A102631 planepath=FactorRationals line_type=Y_axis # # Not quite, OFFSET=0 value 0 whereas start X=Y=1 value 1 here # X_axis => 'A000290', # squares 0,1,4,9 # # OEIS-Other: A000290 planepath=FactorRationals }, 'factor_coding=odd/even' => { X_axis => 'A102631', # n^2/(squarefree kernel) # OEIS-Other: A102631 planepath=FactorRationals,factor_coding=odd/even }, }; } { package Math::PlanePath::GcdRationals; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_axis_i_start => 1; use constant _NumSeq_Y_axis_i_start => 1; # GcdRationals # Not quite, starts X=1 # X_axis => triangular row # Not quite, starts X=1 here cf OFFSET=0 in A000124 # Y_axis => 'A000124', # triangular+1 # # GcdRationals,pairs_order=diagonals_down # Not quite, start X=1 here cf A000290 starts OFFSET=1 # X_axis => 'A000290', # Y=1 row, perfect squares # Not quite, A033638 starts two ones 1,1,... # Y_axis => 'A033638', # quarter-squares + 1 # # GcdRationals,pairs_order=diagonals_up # Not quite, A002061 starts two ones 1,1, # X_axis => 'A002061', # Not quite, X=1 column squares+pronic, but no initial 0,0 of A002620 # Y_axis => 'A002620', # X=1 column # Not quite, starting value=2 here # Diagonal_above => 'A002522', # Y=X+1 diagonal, squares+1 } { package Math::PlanePath::CfracDigits; # 1 # diagonal Y/(Y+1) = 0 + ----- # 1 + 1/Y # q0=1 q1=Y # N = 3,Y-1 in 1,2,3 # = 1,0,Y-1 in 0,1,2 # use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_X_offset => -1; use constant _NumSeq_Y_axis_increasing => 1; # radix without digit 0 } { package Math::PlanePath::PeanoCurve; sub _NumSeq_X_axis_increasing { my ($self) = @_; return ($self->{'radix'} % 2); } *_NumSeq_Y_axis_increasing = \&_NumSeq_X_axis_increasing; use constant _NumSeq_N_oeis_anum => { 'radix=3' => { X_axis => 'A163480', # axis same as initial direction Y_axis => 'A163481', # axis opp to initial direction Diagonal => 'A163343', }, # OEIS-Catalogue: A163480 planepath=PeanoCurve # OEIS-Catalogue: A163481 planepath=PeanoCurve line_type=Y_axis # OEIS-Catalogue: A163343 planepath=PeanoCurve line_type=Diagonal # OEIS-Other: A163480 planepath=GrayCode,apply_type=TsF,radix=3 # OEIS-Other: A163481 planepath=GrayCode,apply_type=TsF,radix=3 line_type=Y_axis # OEIS-Other: A163343 planepath=GrayCode,apply_type=TsF,radix=3 line_type=Diagonal # OEIS-Other: A163480 planepath=GrayCode,apply_type=FsT,radix=3 # OEIS-Other: A163481 planepath=GrayCode,apply_type=FsT,radix=3 line_type=Y_axis # OEIS-Other: A163343 planepath=GrayCode,apply_type=FsT,radix=3 line_type=Diagonal }; } # { package Math::PlanePath::PeanoDiagonals; # } { package Math::PlanePath::WunderlichSerpentine; sub _NumSeq_X_axis_increasing { my ($self) = @_; if ($self->{'radix'} % 2) { return 1; # odd radix always increasing } # FIXME: depends on the serpentine_type bits return 0; } sub _NumSeq_Y_axis_increasing { my ($self) = @_; if ($self->{'radix'} % 2) { return 1; # odd radix always increasing } # FIXME: depends on the serpentine_type bits return 0; } } { package Math::PlanePath::HilbertCurve; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { '' => { X_axis => 'A163482', Y_axis => 'A163483', Diagonal => 'A062880', # base 4 digits 0,2 only # OEIS-Catalogue: A163482 planepath=HilbertCurve # OEIS-Catalogue: A163483 planepath=HilbertCurve line_type=Y_axis # OEIS-Other: A062880 planepath=HilbertCurve line_type=Diagonal }, }; } { package Math::PlanePath::HilbertSides; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { '' => { Diagonal => 'A062880', # base 4 digits 0,2 only # OEIS-Other: A062880 planepath=HilbertSides line_type=Diagonal }, }; } { package Math::PlanePath::HilbertSpiral; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; } { package Math::PlanePath::ZOrderCurve; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'radix=2' => { X_axis => 'A000695', # base 4 digits 0,1 only (RadixConversion) Y_axis => 'A062880', # base 4 digits 0,2 only Diagonal => 'A001196', # base 4 digits 0,3 only # OEIS-Other: A000695 planepath=ZOrderCurve # OEIS-Catalogue: A062880 planepath=ZOrderCurve line_type=Y_axis # OEIS-Catalogue: A001196 planepath=ZOrderCurve line_type=Diagonal }, 'radix=3' => { X_axis => 'A037314', # base 9 digits 0,1,2 only Diagonal => 'A338086', # base 9 digits 0,4,8 only # OEIS-Catalogue: A037314 planepath=ZOrderCurve,radix=3 # OEIS-Catalogue: A338086 planepath=ZOrderCurve,radix=3 line_type=Diagonal }, 'radix=3,i_start=1' => { Y_axis => 'A208665', # base 9 digits 0,3,6 only, starting OFFSET=1 value=3 # OEIS-Catalogue: A208665 planepath=ZOrderCurve,radix=3 i_start=1 line_type=Y_axis # ZOrderCurve dir=2 radix,3: match 6,27,30,33,54,57,60,243,246,249,270,273,276,297,300 # A208665 Numbers that match odd ternary polynomials; see Comments. # A208665 ,3,6,27,30,33,54,57,60,243,246,249,270,273,276,297,300,303,486,489,492,513,516,519,540,543,546,2187,2190,2193,2214,2217,2220,2241,2244,2247,2430,2433,2436,2457,2460,2463,2484,2487,2490,2673,2676,2679, # base 9 digits 0,3,6 only }, 'radix=10' => { X_axis => 'A051022', # base 10 insert 0s, for digits 0 to 9 base 100 Diagonal => 'A338754', # base 10 duplicate digits # OEIS-Catalogue: A051022 planepath=ZOrderCurve,radix=10 # OEIS-Catalogue: A338754 planepath=ZOrderCurve,radix=10 line_type=Diagonal }, }; } { package Math::PlanePath::GrayCode; # X axis increasing for: # radix=2 TsF,Fs # radix=3 reflected TsF,FsT # radix=3 modular TsF,Fs # radix=4 reflected TsF,Fs # radix=4 modular TsF,Fs # radix=5 reflected TsF,FsT # radix=5 modular TsF,Fs # sub _NumSeq_X_axis_increasing { my ($self) = @_; if ($self->{'gray_type'} eq 'modular' || $self->{'radix'} == 2) { return ($self->{'apply_type'} eq 'TsF' || $self->{'apply_type'} eq 'Fs'); } if ($self->{'radix'} & 1) { return ($self->{'apply_type'} eq 'TsF' || $self->{'apply_type'} eq 'FsT'); } else { return ($self->{'apply_type'} eq 'TsF' || $self->{'apply_type'} eq 'Fs'); } } *_NumSeq_Y_axis_increasing = \&_NumSeq_X_axis_increasing; # Diagonal increasing for: # radix=2 FsT,Ts # radix=3 reflected Ts,Fs # radix=3 modular FsT # radix=4 reflected FsT,Ts # radix=4 modular FsT # radix=5 reflected Ts,Fs # radix=5 modular FsT sub _NumSeq_Diagonal_increasing { my ($self) = @_; if ($self->{'radix'} & 1) { if ($self->{'gray_type'} eq 'modular') { return ($self->{'apply_type'} eq 'FsT'); # odd modular } else { return ($self->{'apply_type'} eq 'Ts' || $self->{'apply_type'} eq 'Fs'); # odd reflected } } if ($self->{'gray_type'} eq 'reflected' || $self->{'radix'} == 2) { return ($self->{'apply_type'} eq 'FsT' || $self->{'apply_type'} eq 'Ts'); # even reflected } else { return ($self->{'apply_type'} eq 'FsT'); # even modular } } use constant _NumSeq_N_oeis_anum => { 'apply_type=TsF,gray_type=reflected,radix=3' => (Math::PlanePath::PeanoCurve->_NumSeq_N_oeis_anum->{'radix=3'} || die "Oops, SquareSpiral NumSeq PlanePathN not found"), 'apply_type=FsT,gray_type=reflected,radix=3' => (Math::PlanePath::PeanoCurve->_NumSeq_N_oeis_anum->{'radix=3'} || die "Oops, SquareSpiral NumSeq PlanePathN not found"), # GrayCode radix=2 TsF==Fs reflected==modular do { my $href = { Y_axis => 'A001196', # base 4 digits 0,3 only }; ('apply_type=TsF,gray_type=reflected,radix=2' => $href, 'apply_type=Fs,gray_type=reflected,radix=2' => $href, 'apply_type=TsF,gray_type=modular,radix=2' => $href, 'apply_type=Fs,gray_type=modular,radix=2' => $href, ); # OEIS-Other: A001196 planepath=GrayCode,apply_type=TsF line_type=Y_axis # OEIS-Other: A001196 planepath=GrayCode,apply_type=Fs line_type=Y_axis # OEIS-Other: A001196 planepath=GrayCode,apply_type=TsF,gray_type=modular line_type=Y_axis # OEIS-Other: A001196 planepath=GrayCode,apply_type=Fs,gray_type=modular line_type=Y_axis }, # GrayCode radix=2 Ts==FsT reflected==modular do { my $href = { Diagonal => 'A062880', # base 4 digits 0,2 only }; ('apply_type=Ts,gray_type=reflected,radix=2' => $href, 'apply_type=Ts,gray_type=modular,radix=2' => $href, 'apply_type=FsT,gray_type=reflected,radix=2' => $href, 'apply_type=FsT,gray_type=modular,radix=2' => $href, ); # OEIS-Other: A062880 planepath=GrayCode,apply_type=Ts line_type=Diagonal # OEIS-Other: A062880 planepath=GrayCode,apply_type=Ts,gray_type=modular line_type=Diagonal # OEIS-Other: A062880 planepath=GrayCode,apply_type=FsT line_type=Diagonal # OEIS-Other: A062880 planepath=GrayCode,apply_type=FsT,gray_type=modular line_type=Diagonal }, # GrayCode radix=3 sT==sF reflected # N split then toGray giving Y=0 means N ternary 010202 etc # N split then toGray giving X=Y means N ternary pairs 112200 do { my $href = { X_axis => 'A163344', # central Peano/4, base9 digits 0,1,2 only Diagonal => 'A163343', # central diagonal of Peano, base9 0,4,8 }; ('apply_type=sT,gray_type=reflected,radix=3' => $href, 'apply_type=sF,gray_type=reflected,radix=3' => $href, ); # OEIS-Catalogue: A163344 planepath=GrayCode,apply_type=sT,radix=3 # OEIS-Other: A163344 planepath=GrayCode,apply_type=sF,radix=3 # OEIS-Other: A163343 planepath=GrayCode,apply_type=sT,radix=3 line_type=Diagonal # OEIS-Other: A163343 planepath=GrayCode,apply_type=sF,radix=3 line_type=Diagonal }, 'apply_type=FsT,gray_type=modular,radix=3,i_start=1' => { Diagonal => 'A208665', # base 9 digits 0,3,6 # OEIS-Other: A208665 planepath=GrayCode,apply_type=FsT,gray_type=modular,radix=3 line_type=Diagonal i_start=1 }, }; } # { package Math::PlanePath::ImaginaryBase; # } { package Math::PlanePath::ImaginaryHalf; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'radix=2,digit_order=YXX' => { Y_axis => 'A033045', # base 8 digits 0,1 only # OEIS-Other: A033045 planepath=ImaginaryHalf,digit_order=YXX line_type=Y_axis }, 'radix=2,digit_order=YXnX' => { Y_axis => 'A033045', # base 8 digits 0,1 only # OEIS-Other: A033045 planepath=ImaginaryHalf,digit_order=YXnX line_type=Y_axis }, }; } # { package Math::PlanePath::CubicBase; # } { package Math::PlanePath::DekkingCurve; sub _NumSeq_X_axis_increasing { my ($self) = @_; # arms=4 has various interleaving return $self->{'arms'} < 4; } sub _NumSeq_X_neg_increasing { my ($self) = @_; # arms=3 has various interleaving return $self->{'arms'} < 3; } sub _NumSeq_Y_axis_increasing { my ($self) = @_; # arms=2 has various interleaving return $self->{'arms'} < 2; } sub _NumSeq_Y_neg_increasing { my ($self) = @_; # arms=4 has various interleaving return $self->{'arms'} < 4; } } { package Math::PlanePath::DekkingCentres; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; } { package Math::PlanePath::CincoCurve; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; } { package Math::PlanePath::BetaOmega; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; } { package Math::PlanePath::KochelCurve; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; } { package Math::PlanePath::AR2W2Curve; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; } { package Math::PlanePath::WunderlichMeander; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; } # { package Math::PlanePath::Flowsnake; # } # { package Math::PlanePath::FlowsnakeCentres; # # inherit from Flowsnake # } # { package Math::PlanePath::GosperIslands; # } # { package Math::PlanePath::GosperSide; # } { package Math::PlanePath::KochCurve; use constant _NumSeq_X_axis_increasing => 1; # when touched use constant _NumSeq_Y_axis_increasing => 1; # when touched use constant _NumSeq_Diagonal_increasing => 1; # when touched } { package Math::PlanePath::KochPeaks; use constant _NumSeq_X_axis_increasing => 1; # when touched use constant _NumSeq_Y_axis_increasing => 1; # when touched use constant _NumSeq_X_neg_increasing => 1; # when touched # Diagonal never touched } { package Math::PlanePath::KochSnowflakes; use constant _NumSeq_X_axis_increasing => 1; # when touched use constant _NumSeq_Y_axis_increasing => 1; # when touched use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; # when touched use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; } { package Math::PlanePath::KochSquareflakes; use constant _NumSeq_X_axis_increasing => 1; # when touched use constant _NumSeq_Y_axis_increasing => 1; # when touched use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; # when touched use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; } { package Math::PlanePath::QuadricCurve; use constant _NumSeq_X_axis_increasing => 1; # when touched use constant _NumSeq_Diagonal_increasing => 1; # two values only } { package Math::PlanePath::QuadricIslands; # FIXME: pred() on Diagonal_SW doesn't notice 0.5 square use constant _NumSeq_X_axis_increasing => 1; # when touched use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 0; use constant _NumSeq_Y_neg_increasing_from_i => 1; # after 3,2,8 use constant _NumSeq_Y_neg_min => 2; # at X=-1,Y=0 rather than X=0,Y=0 use constant _NumSeq_Diagonal_SW_increasing => 0; use constant _NumSeq_Diagonal_SW_min => 1; } { package Math::PlanePath::SierpinskiTriangle; use constant _NumSeq_X_axis_increasing => 1; # for "diagonal" style use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; # low 10111=23 increment to 11000=24 # 10111 ones=4 width=2^4 # cf A160722 is 3*A006046-2*n, drawing three Sierpinski triangles # http://www.polprimos.com/imagenespub/polca722.jpg # use constant _NumSeq_N_oeis_anum => { #--------- # i_start=0, n_start=0 'align=triangular,n_start=0' => { Diagonal_NW => 'A006046', Depth_start => 'A006046', # OEIS-Other: A006046 planepath=SierpinskiTriangle line_type=Diagonal_NW # OEIS-Other: A006046 planepath=SierpinskiTriangle line_type=Depth_start }, 'align=right,n_start=0' => { Y_axis => 'A006046', Depth_start => 'A006046', # OEIS-Catalogue: A006046 planepath=SierpinskiTriangle,align=diagonal,n_start=0 line_type=Y_axis }, 'align=left,n_start=0' => { Diagonal_NW => 'A006046', Depth_start => 'A006046', # OEIS-Other: A006046 planepath=SierpinskiTriangle,align=left,n_start=0 line_type=Diagonal_NW }, 'align=diagonal,n_start=0' => { Y_axis => 'A006046', Depth_start => 'A006046', # OEIS-Other: A006046 planepath=SierpinskiTriangle,align=diagonal,n_start=0 line_type=Y_axis }, #--------- # i_start=1, n_start=0 # starting OFFSET=1 value=2,4,8,10 so missing N=0 at Y=0, hence i_start=1 'align=triangular,n_start=0,i_start=1' => { Diagonal => 'A074330', Depth_end => 'A074330', # OEIS-Catalogue: A074330 planepath=SierpinskiTriangle line_type=Diagonal i_start=1 # OEIS-Other: A074330 planepath=SierpinskiTriangle line_type=Depth_end i_start=1 }, 'align=right,n_start=0,i_start=1' => { Diagonal => 'A074330', Depth_end => 'A074330', # OEIS-Other: A074330 planepath=SierpinskiTriangle,align=right line_type=Diagonal i_start=1 }, 'align=left,n_start=0,i_start=1' => { Y_axis => 'A074330', Depth_end => 'A074330', # OEIS-Other: A074330 planepath=SierpinskiTriangle,align=left line_type=Y_axis i_start=1 }, 'align=diagonal,n_start=0,i_start=1' => { X_axis => 'A074330', Depth_end => 'A074330', # OEIS-Other: A074330 planepath=SierpinskiTriangle,align=diagonal i_start=1 }, }; } { package Math::PlanePath::SierpinskiArrowhead; use constant _NumSeq_Y_axis_increasing => 1; # when touched use constant _NumSeq_Diagonal_increasing => 1; # when touched use constant _NumSeq_Diagonal_NW_increasing => 1; # align="diagonal" is X increasing, other align is single origin point only use constant _NumSeq_X_axis_increasing => 1; } { package Math::PlanePath::SierpinskiArrowheadCentres; use constant _NumSeq_Y_axis_increasing => 1; # never touched ? use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; # align="diagonal" is X increasing, other align is single origin point only use constant _NumSeq_X_axis_increasing => 1; } { package Math::PlanePath::SierpinskiCurve; use constant _NumSeq_X_axis_increasing => 1; # when touched use constant _NumSeq_X_axis_i_start => 1; # but not all cells visited use constant _NumSeq_Y_axis_increasing => 1; # when touched use constant _NumSeq_Y_axis_i_start => 1; # but not all cells visited use constant _NumSeq_X_neg_increasing => 1; # arms use constant _NumSeq_Y_neg_increasing => 1; # arms use constant _NumSeq_Diagonal_increasing => 1; # when touched use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; } { package Math::PlanePath::SierpinskiCurveStair; use constant _NumSeq_X_axis_increasing => 1; # when touched use constant _NumSeq_Y_axis_increasing => 1; # when touched use constant _NumSeq_X_neg_increasing => 1; # arms use constant _NumSeq_Y_neg_increasing => 1; # arms use constant _NumSeq_Diagonal_increasing => 1; # when touched use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; } { package Math::PlanePath::HIndexing; use constant _NumSeq_X_axis_increasing => 1; # when touched use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; # when touched } # { package Math::PlanePath::DragonCurve; # } # { package Math::PlanePath::DragonRounded; # } # { package Math::PlanePath::DragonMidpoint; # } { package Math::PlanePath::AlternatePaper; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; # arms use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; # selecting the smaller N on the negative axes gives increasing, maybe use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'arms=1' => { X_axis => 'A000695', # base 4 digits 0,1 only Diagonal => 'A062880', # base 4 digits 0,2 only # OEIS-Other: A000695 planepath=AlternatePaper # OEIS-Other: A062880 planepath=AlternatePaper line_type=Diagonal }, # FIXME: not sure what to do when multiple-visited points on axes # 'arms=2' => # { X_axis => 'A062880', # base 4 digits 0,2 only # Y_axis => 'A145812', # base 4 digits 0,2 only except low digit 1,3 only # # OEIS-Other: A062880 planepath=AlternatePaper,arms=2 # # OEIS-Catalogue: A145812 planepath=AlternatePaper,arms=2 line_type=Y_axis # }, # 'arms=3' => # { X_axis => 'A001196', # base 4 digits 0,3 only # # OEIS-Other: A001196 planepath=AlternatePaper,arms=3 # }, # # alt paper arms=4 diagonal, arms=8 x axis # A127988 # base 8 digits 0,4 only }; } { package Math::PlanePath::AlternatePaperMidpoint; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; # arms use constant _NumSeq_Diagonal_SE_increasing => 1; # arms } # { package Math::PlanePath::TerdragonCurve; # } # { package Math::PlanePath::TerdragonRounded; # } # { package Math::PlanePath::TerdragonMidpoint; # } # { package Math::PlanePath::AlternateTerdragon; # } # { package Math::PlanePath::R5DragonCurve; # } # { package Math::PlanePath::R5DragonMidpoint; # } # { package Math::PlanePath::CCurve; # } # { package Math::PlanePath::ComplexPlus; # } { package Math::PlanePath::ComplexMinus; use constant _NumSeq_N_oeis_anum => { 'realpart=1' => { X_axis => 'A066321', # binary base i-1 X_neg => 'A256441', # binary base i-1 # OEIS-Catalogue: A066321 planepath=ComplexMinus # OEIS-Catalogue: A256441 planepath=ComplexMinus line_type=X_neg # cf A066323 count of 1-bits in N on X axis }, }; } # { package Math::PlanePath::ComplexRevolving; # } { package Math::PlanePath::Rows; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Y_neg_min => undef; # negatives use constant _NumSeq_Y_neg_max => 1; # negatives # secret negatives # (w-1)*(w-1)-1 # = w^2-2w+1-1 # = w(w-2) sub _NumSeq_Diagonal_SE_min { my ($self) = @_; return ($self->{'width'}-2)*$self->{'width'}; } use constant _NumSeq_N_oeis_anum => { 'n_start=0,width=1' => { Y_axis => 'A001477', # integers 0,1,2,3,etc # OEIS-Other: A001477 planepath=Rows,width=1,n_start=0 line_type=Y_axis }, 'n_start=1,width=2' => { Y_axis => 'A005408', # odd 2n+1 # OEIS-Other: A005408 planepath=Rows,width=2 line_type=Y_axis }, 'n_start=1,width=3' => { Y_axis => 'A016777', # 3n+1 # OEIS-Catalogue: A016777 planepath=Rows,width=3 line_type=Y_axis }, 'n_start=1,width=4' => { Y_axis => 'A016813', # 4n+1 # OEIS-Catalogue: A016813 planepath=Rows,width=4 line_type=Y_axis }, 'n_start=1,width=5' => { Y_axis => 'A016861', # 5n+1 # OEIS-Catalogue: A016861 planepath=Rows,width=5 line_type=Y_axis }, 'n_start=1,width=6' => { Y_axis => 'A016921', # 6n+1 # OEIS-Catalogue: A016921 planepath=Rows,width=6 line_type=Y_axis }, 'n_start=1,width=7' => { Y_axis => 'A016993', # 7n+1 # OEIS-Catalogue: A016993 planepath=Rows,width=7 line_type=Y_axis }, }; } { package Math::PlanePath::Columns; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_X_neg_min => undef; # negatives use constant _NumSeq_X_neg_max => 1; # negatives sub _NumSeq_Diagonal_NW_min { my ($self) = @_; # secret negatives return ($self->{'height'}-2)*$self->{'height'}; } use constant _NumSeq_N_oeis_anum => { 'n_start=0,height=1' => { X_axis => 'A001477', # integers 0,1,2,3,etc # OEIS-Other: A001477 planepath=Columns,height=1,n_start=0 }, 'n_start=1,height=2' => { X_axis => 'A005408', # odd 2n+1 # OEIS-Other: A005408 planepath=Columns,height=2 }, 'n_start=1,height=3' => { X_axis => 'A016777', # 3n+1 # OEIS-Other: A016777 planepath=Columns,height=3 }, 'n_start=1,height=4' => { X_axis => 'A016813', # 4n+1 # OEIS-Other: A016813 planepath=Columns,height=4 }, 'n_start=1,height=5' => { X_axis => 'A016861', # 5n+1 # OEIS-Other: A016861 planepath=Columns,height=5 }, 'n_start=1,height=6' => { X_axis => 'A016921', # 6n+1 # OEIS-Other: A016921 planepath=Columns,height=6 }, 'n_start=1,height=7' => { X_axis => 'A016993', # 7n+1 # OEIS-Other: A016993 planepath=Columns,height=7 }, }; } { package Math::PlanePath::Diagonals; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'direction=down,n_start=1,x_start=0,y_start=0' => { # Diagonals X_axis -- triangular 1,3,6,etc, but starting i=0 value=1 Y_axis => 'A000124', # triangular+1 = n*(n+1)/2+1 Diagonal => 'A001844', # centred squares 2n(n+1)+1 # OEIS-Catalogue: A000124 planepath=Diagonals line_type=Y_axis # OEIS-Catalogue: A001844 planepath=Diagonals line_type=Diagonal }, 'direction=up,n_start=1,x_start=0,y_start=0' => { X_axis => 'A000124', # triangular+1 = n*(n+1)/2+1 Diagonal => 'A001844', # centred squares 2n(n+1)+1 # OEIS-Other: A000124 planepath=Diagonals,direction=up # OEIS-Other: A001844 planepath=Diagonals,direction=up line_type=Diagonal }, 'direction=down,n_start=0,x_start=0,y_start=0' => { X_axis => 'A000096', # n*(n+3)/2 Y_axis => 'A000217', # triangular n*(n+1)/2 # OEIS-Other: A000096 planepath=Diagonals,n_start=0 # OEIS-Other: A000217 planepath=Diagonals,n_start=0 line_type=Y_axis }, 'direction=up,n_start=0,x_start=0,y_start=0' => { X_axis => 'A000217', # triangular n*(n+1)/2 Y_axis => 'A000096', # n*(n+3)/2 # OEIS-Other: A000217 planepath=Diagonals,direction=up,n_start=0 # OEIS-Other: A000096 planepath=Diagonals,direction=up,n_start=0 line_type=Y_axis }, }; } { package Math::PlanePath::DiagonalsAlternating; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'n_start=1' => { Diagonal => 'A001844', # centred squares 2n(n+1)+1 # OEIS-Other: A001844 planepath=DiagonalsAlternating line_type=Diagonal # Not quite, extra initial 1 or 0 # X_axis => 'A128918', # Y_axis => 'A131179', }, 'n_start=0' => { Diagonal => 'A046092', # 2*triangular # OEIS-Other: A046092 planepath=DiagonalsAlternating,n_start=0 line_type=Diagonal }, }; } { package Math::PlanePath::DiagonalsOctant; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { # 'direction=down,n_start=1' => # { # Not quite, starting i=0 for square=1 cf A000290 starts 0 # # Diagonal => 'A000290', # squares # # Not quite, A033638 extra initial 1 # # Y_axis => 'A033638', # quarter squares + 1, 1,1,2,3,5,7,10,13 # } # 'direction=up,n_start=1' => # { # # Not quite, A002061 extra initial 1 # # Diagonal => 'A002061', # 1,1,3,7,13,21,31,43 # } 'direction=down,n_start=0' => { Diagonal => 'A005563', # n*(n+2) 0,3,8,15,24 # OEIS-Other: A005563 planepath=DiagonalsOctant,n_start=0 line_type=Diagonal # # Not quite, extra initial 0 # # Y_axis => 'A002620', # quarter squares 0,0,1,2,4,6,9,12, }, 'direction=up,n_start=0' => { Diagonal => 'A002378', # pronic 0,2,6,12,20 # OEIS-Other: A002378 planepath=DiagonalsOctant,direction=up,n_start=0 line_type=Diagonal # # Not quite, starts n=1 value=0 whereas start Y=0 value=0 here # # Y_axis => 'A024206', # 0,1,3,5,8,11,15 }, }; } { package Math::PlanePath::MPeaks; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 0; use constant _NumSeq_X_neg_increasing_from_i => 1; use constant _NumSeq_X_neg_min => 1; # at X=-1,Y=0 rather than X=0,Y=0 use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing_from_i => 1; use constant _NumSeq_Diagonal_NW_min => 2; # at X=-1,Y=1 # MPeaks -- X_axis A045944 matchstick n(3n+2) but initial N=3 # MPeaks -- Diagonal,Y_axis hexagonal first,second spoke, but starting # from 3 } { package Math::PlanePath::Staircase; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'n_start=1' => { Diagonal => 'A084849', # OEIS-Other: A084849 planepath=Staircase line_type=Diagonal }, 'n_start=0' => { Diagonal => 'A014105', # second hexagonals # OEIS-Other: A014105 planepath=Staircase,n_start=0 line_type=Diagonal }, 'n_start=2' => { Diagonal => 'A096376', # OEIS-Catalogue: A096376 planepath=Staircase,n_start=2 line_type=Diagonal # Not quite, A128918 has extra initial 1,1 # X_axis => 'A128918', }, }; } { package Math::PlanePath::StaircaseAlternating; sub _NumSeq_X_axis_increasing { my ($self) = @_; return ($self->{'end_type'} eq 'square' ? 1 : 0); # backs-up } *_NumSeq_Y_axis_increasing = \&_NumSeq_X_axis_increasing; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'end_type=jump,n_start=1' => { Diagonal => 'A084849', # OEIS-Other: A084849 planepath=StaircaseAlternating line_type=Diagonal }, 'end_type=jump,n_start=0' => { Diagonal => 'A014105', # second hexagonals # OEIS-Other: A014105 planepath=StaircaseAlternating,n_start=0 line_type=Diagonal }, 'end_type=jump,n_start=2' => { Diagonal => 'A096376', # OEIS-Other: A096376 planepath=StaircaseAlternating,n_start=2 line_type=Diagonal }, 'end_type=square,n_start=1' => { Diagonal => 'A058331', # OEIS-Other: A058331 planepath=StaircaseAlternating,end_type=square line_type=Diagonal }, 'end_type=square,n_start=0' => { Diagonal => 'A001105', # OEIS-Other: A001105 planepath=StaircaseAlternating,end_type=square,n_start=0 line_type=Diagonal }, }; } { package Math::PlanePath::Corner; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'wider=0,n_start=1' => { Y_axis => 'A002522', # n^2+1 # OEIS-Other: A002522 planepath=Corner line_type=Y_axis }, 'wider=0,n_start=0' => { X_axis => 'A005563', # (n+1)^2-1 Y_axis => 'A000290', # squares Diagonal => 'A002378', # pronic # OEIS-Other: A005563 planepath=Corner,n_start=0 # OEIS-Other: A000290 planepath=Corner,n_start=0 line_type=Y_axis # OEIS-Other: A002378 planepath=Corner,n_start=0 line_type=Diagonal }, 'wider=0,n_start=2' => { Y_axis => 'A059100', # n^2+2 Diagonal => 'A014206', # pronic+2 # OEIS-Catalogue: A059100 planepath=Corner,n_start=2 line_type=Y_axis # OEIS-Catalogue: A014206 planepath=Corner,n_start=2 line_type=Diagonal }, 'wider=1,n_start=0' => { Y_axis => 'A002378', # pronic Diagonal => 'A005563', # (n+1)^2-1 # OEIS-Other: A002378 planepath=Corner,wider=1,n_start=0 line_type=Y_axis # OEIS-Other: A005563 planepath=Corner,wider=1,n_start=0 line_type=Diagonal }, 'wider=1,n_start=2' => { Y_axis => 'A014206', # pronic+2 # OEIS-Other: A014206 planepath=Corner,wider=1,n_start=2 line_type=Y_axis }, 'wider=2,n_start=0' => { Y_axis => 'A005563', # (n+1)^2-1 Diagonal => 'A028552', # n(n+3) # OEIS-Other: A005563 planepath=Corner,wider=2,n_start=0 line_type=Y_axis # OEIS-Catalogue: A028552 planepath=Corner,wider=2,n_start=0 line_type=Diagonal }, 'wider=2,n_start=1' => { Diagonal => 'A028387', # n(n+3)+1 = n + (n+1)^2 # OEIS-Catalogue: A028387 planepath=Corner,wider=2,n_start=1 line_type=Diagonal }, 'wider=3,n_start=0' => { Y_axis => 'A028552', # n(n+3) # OEIS-Other: A028552 planepath=Corner,wider=3,n_start=0 line_type=Y_axis }, }; } { package Math::PlanePath::CornerAlternating; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'wider=0,n_start=1' => { X_axis => 'A081346', # picture in A081344 Y_axis => 'A081345', # OEIS-Catalogue: A081346 planepath=CornerAlternating # OEIS-Catalogue: A081345 planepath=CornerAlternating line_type=Y_axis }, 'wider=0,n_start=0' => { Diagonal => 'A002378', # pronic # OEIS-Other: A002378 planepath=CornerAlternating,n_start=0 line_type=Diagonal }, 'wider=0,n_start=2' => { Diagonal => 'A014206', # pronic # OEIS-Other: A014206 planepath=CornerAlternating,n_start=2 line_type=Diagonal }, 'wider=1,n_start=1' => { X_axis => 'A081347', # maze, picture in A081344 Y_axis => 'A081348', Diagonal => 'A080335', # OEIS-Catalogue: A081347 planepath=CornerAlternating,wider=1 # OEIS-Catalogue: A081348 planepath=CornerAlternating,wider=1 line_type=Y_axis # OEIS-Catalogue: A080335 planepath=CornerAlternating,wider=1 line_type=Diagonal }, 'wider=2,n_start=1' => { X_axis => 'A081350', # another maze, picture in A081349 Y_axis => 'A081351', Diagonal => 'A081352', # OEIS-Catalogue: A081350 planepath=CornerAlternating,wider=2 # OEIS-Catalogue: A081351 planepath=CornerAlternating,wider=2 line_type=Y_axis # OEIS-Catalogue: A081352 planepath=CornerAlternating,wider=2 line_type=Diagonal }, }; } { package Math::PlanePath::PyramidRows; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; # when covered, or single use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_N_oeis_anum => { # PyramidRows step=1 do { my $href = { Y_axis => 'A000124', # triangular+1 = n*(n+1)/2+1 }; ('step=1,align=centre,n_start=1' => $href, 'step=1,align=right,n_start=1' => $href); # OEIS-Other: A000124 planepath=PyramidRows,step=1 line_type=Y_axis # OEIS-Other: A000124 planepath=PyramidRows,step=1,align=right line_type=Y_axis }, 'step=1,align=left,n_start=1' => { Diagonal_NW => 'A000124', # triangular+1 = n*(n+1)/2+1 # OEIS-Other: A000124 planepath=PyramidRows,step=1,align=left line_type=Diagonal_NW }, do { my $href = { Y_axis => 'A000217', # triangular }; ('step=1,align=centre,n_start=0' => $href, 'step=1,align=right,n_start=0' => $href); # OEIS-Other: A000217 planepath=PyramidRows,step=1,n_start=0 line_type=Y_axis # OEIS-Other: A000217 planepath=PyramidRows,step=1,align=right,n_start=0 line_type=Y_axis }, 'step=2,align=centre,n_start=1' => { Diagonal_NW => 'A002522', # n^2+1 # OEIS-Other: A002522 planepath=PyramidRows,step=2 line_type=Diagonal_NW # Not quite, n_start=1 means squares starting from 1 whereas A000290 # starts from 0 # Diagonal => 'A000290', }, 'step=2,align=centre,n_start=0' => { Y_axis => 'A002378', # pronic Diagonal => 'A005563', Diagonal_NW => 'A000290', # squares # OEIS-Other: A002378 planepath=PyramidRows,step=2,n_start=0 line_type=Y_axis # OEIS-Other: A005563 planepath=PyramidRows,step=2,n_start=0 line_type=Diagonal # OEIS-Other: A000290 planepath=PyramidRows,step=2,n_start=0 line_type=Diagonal_NW }, 'step=2,align=right,n_start=0' => { Y_axis => 'A000290', # squares Diagonal => 'A002378', # pronic # OEIS-Other: A000290 planepath=PyramidRows,step=2,align=right,n_start=0 line_type=Y_axis # OEIS-Other: A002378 planepath=PyramidRows,step=2,align=right,n_start=0 line_type=Diagonal }, 'step=2,align=left,n_start=0' => { Y_axis => 'A005563', Diagonal_NW => 'A002378', # pronic # OEIS-Other: A005563 planepath=PyramidRows,step=2,align=left,n_start=0 line_type=Y_axis # OEIS-Other: A002378 planepath=PyramidRows,step=2,align=left,n_start=0 line_type=Diagonal_NW }, 'step=2,align=centre,n_start=2' => { Diagonal_NW => 'A059100', # n^2+2 # OEIS-Other: A059100 planepath=PyramidRows,step=2,n_start=2 line_type=Diagonal_NW }, 'step=2,align=right,n_start=2' => { Y_axis => 'A059100', # n^2+2 # OEIS-Other: A059100 planepath=PyramidRows,step=2,align=right,n_start=2 line_type=Y_axis }, 'step=3,align=centre,n_start=1' => { Y_axis => 'A104249', Diagonal_NW => 'A143689', # OEIS-Catalogue: A104249 planepath=PyramidRows,step=3 line_type=Y_axis # OEIS-Catalogue: A143689 planepath=PyramidRows,step=3 line_type=Diagonal_NW # Not quite OFFSET=1 cf start i=0 here # Diagonal => 'A005448', # # OEIS-Catalogue: A005448 planepath=PyramidRows,step=3 line_type=Diagonal }, 'step=3,align=right,n_start=1' => { Y_axis => 'A143689', Diagonal => 'A104249', # OEIS-Other: A143689 planepath=PyramidRows,step=3,align=right line_type=Y_axis # OEIS-Other: A104249 planepath=PyramidRows,step=3,align=right line_type=Diagonal # Not quite OFFSET=1 cf start i=0 here # Diagonal => 'A005448', # # OEIS-Catalogue: A005448 planepath=PyramidRows,step=3 line_type=Diagonal }, 'step=3,align=centre,n_start=0' => { Y_axis => 'A005449', # second pentagonal n*(3n+1)/2 Diagonal_NW => 'A000326', # pentagonal n(3n-1)/2 # OEIS-Other: A005449 planepath=PyramidRows,step=3,n_start=0 line_type=Y_axis # OEIS-Other: A000326 planepath=PyramidRows,step=3,n_start=0 line_type=Diagonal_NW }, 'step=3,align=right,n_start=0' => { Y_axis => 'A000326', # pentagonal n(3n-1)/2 Diagonal => 'A005449', # second pentagonal n*(3n+1)/2 # OEIS-Other: A000326 planepath=PyramidRows,step=3,align=right,n_start=0 line_type=Y_axis # OEIS-Other: A005449 planepath=PyramidRows,step=3,align=right,n_start=0 line_type=Diagonal }, 'step=4,align=centre,n_start=1' => { Y_axis => 'A084849', Diagonal => 'A001844', Diagonal_NW => 'A058331', # OEIS-Catalogue: A084849 planepath=PyramidRows,step=4 line_type=Y_axis # OEIS-Other: A001844 planepath=PyramidRows,step=4 line_type=Diagonal # OEIS-Other: A058331 planepath=PyramidRows,step=4 line_type=Diagonal_NW }, 'step=4,align=right,n_start=1' => { Diagonal => 'A058331', # OEIS-Other: A058331 planepath=PyramidRows,step=4,align=right line_type=Diagonal }, 'step=4,align=left,n_start=1' => { Diagonal_NW => 'A001844', # OEIS-Other: A001844 planepath=PyramidRows,step=4,align=left line_type=Diagonal_NW }, 'step=4,align=centre,n_start=0' => { Y_axis => 'A014105', # second hexagonal Diagonal => 'A046092', # 4*triangular Diagonal_NW => 'A001105', # 2*n^2 # OEIS-Other: A014105 planepath=PyramidRows,step=4,n_start=0 line_type=Y_axis # OEIS-Catalogue: A046092 planepath=PyramidRows,step=4,n_start=0 line_type=Diagonal # OEIS-Other: A001105 planepath=PyramidRows,step=4,n_start=0 line_type=Diagonal_NW }, 'step=4,align=right,n_start=0' => { Y_axis => 'A000384', # 2*n^2-n, hexagonal numbers Diagonal => 'A001105', # OEIS-Other: A000384 planepath=PyramidRows,step=4,align=right,n_start=0 line_type=Y_axis # OEIS-Other: A001105 planepath=PyramidRows,step=4,align=right,n_start=0 line_type=Diagonal }, 'step=4,align=left,n_start=0' => { Diagonal_NW => 'A046092', # 4*triangular # OEIS-Other: A046092 planepath=PyramidRows,step=4,align=left,n_start=0 line_type=Diagonal_NW }, # TODO PyramidRows,step=5 n_start=0 'step=5,align=centre,n_start=1' => { Y_axis => 'A116668', # OEIS-Other: A116668 planepath=PyramidRows,step=5 line_type=Y_axis }, 'step=6,align=centre,n_start=1' => { Diagonal_NW => 'A056107', Y_axis => 'A056108', Diagonal => 'A056109', # OEIS-Other: A056107 planepath=PyramidRows,step=6 line_type=Diagonal_NW # OEIS-Other: A056108 planepath=PyramidRows,step=6 line_type=Y_axis # OEIS-Other: A056109 planepath=PyramidRows,step=6 line_type=Diagonal }, 'step=8,align=centre,n_start=1' => { Diagonal_NW => 'A053755', # OEIS-Other: A053755 planepath=PyramidRows,step=8 line_type=Diagonal_NW }, 'step=9,align=centre,n_start=1' => { Y_axis => 'A006137', Diagonal => 'A038764', # OEIS-Other: A006137 planepath=PyramidRows,step=9 line_type=Y_axis # OEIS-Other: A038764 planepath=PyramidRows,step=9 line_type=Diagonal }, }; } { package Math::PlanePath::PyramidSides; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'n_start=1' => { X_neg => 'A002522', Diagonal => 'A033951', # OEIS-Catalogue: A002522 planepath=PyramidSides line_type=X_neg # OEIS-Other: A033951 planepath=PyramidSides line_type=Diagonal # # X_axis -- squares (x+1)^2, but starting i=0 value=1 } }; } { package Math::PlanePath::CellularRule; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'rule=5,n_start=1' => { Y_axis => 'A061925', # ceil(n^2/2)+1 # OEIS-Catalogue: A061925 planepath=CellularRule,rule=5 line_type=Y_axis }, # rule 84,116,212,244 two-wide right line do { my $tworight = { Diagonal => 'A005408', # odds 2n+1 }; ('rule=84,n_start=1' => $tworight, 'rule=116,n_start=1' => $tworight, 'rule=212,n_start=1' => $tworight, 'rule=244,n_start=1' => $tworight, ); # OEIS-Other: A005408 planepath=CellularRule,rule=84 line_type=Diagonal # OEIS-Other: A005408 planepath=CellularRule,rule=116 line_type=Diagonal # OEIS-Other: A005408 planepath=CellularRule,rule=212 line_type=Diagonal # OEIS-Other: A005408 planepath=CellularRule,rule=244 line_type=Diagonal }, 'rule=77,n_start=1' => { Y_axis => 'A000124', # triangular+1 # OEIS-Other: A000124 planepath=CellularRule,rule=77 line_type=Y_axis }, 'rule=177,n_start=1' => { Diagonal => 'A000124', # triangular+1 # OEIS-Other: A000124 planepath=CellularRule,rule=177 line_type=Diagonal }, 'rule=185,n_start=1' => { Diagonal => 'A002522', # n^2+1 # OEIS-Other: A002522 planepath=CellularRule,rule=185 line_type=Diagonal }, 'rule=189,n_start=1' => { Y_axis => 'A002522', # n^2+1 # OEIS-Other: A002522 planepath=CellularRule,rule=189 line_type=Y_axis }, # PyramidRows step=1,align=left # OEIS-Other: A000124 planepath=CellularRule,rule=206 line_type=Diagonal_NW # OEIS-Other: A000124 planepath=CellularRule,rule=238 line_type=Diagonal_NW do { my $solidgapright = { Diagonal => 'A002522', # n^2+1 }; ('rule=209,n_start=1' => $solidgapright, 'rule=241,n_start=1' => $solidgapright, ); # OEIS-Other: A002522 planepath=CellularRule,rule=209 line_type=Diagonal # OEIS-Other: A002522 planepath=CellularRule,rule=241 line_type=Diagonal }, 'rule=29,n_start=1' => { Y_axis => 'A000124', # triangular+1 # OEIS-Other: A000124 planepath=CellularRule,rule=29 line_type=Y_axis }, 'rule=221,n_start=1' => { Y_axis => 'A002522', # n^2+1 # OEIS-Other: A002522 planepath=CellularRule,rule=221 line_type=Y_axis }, 'rule=229,n_start=1' => { Y_axis => 'A002522', # n^2+1 # OEIS-Other: A002522 planepath=CellularRule,rule=229 line_type=Y_axis }, # # rule=13 Y axis # # rule=28,156 # Y_axis => 'A002620', quarter squares floor(n^2/4) but diff start # Diagonal => 'A024206', quarter squares - 1, but diff start # # A000027 naturals integers 1 upwards, but OFFSET=1 cf start Y=0 here # # central column only # 'rule=4' => # { Y_axis => 'A000027', # 1 upwards # # OEIS-Other: A000027 planepath=CellularRule,rule=4 line_type=Y_axis # }, # # # right line only rule=16,24,48,56,80,88,112,120,144,152,176,184,208,216,240,248 # Not quite A000027 OFFSET=1 vs start X=Y=0 here # 'rule=16' => # { Y_axis => 'A000027', # 1 upwards # # OEIS-Other: A000027 planepath=CellularRule,rule=16 line_type=Diagonal # }, }; } { package Math::PlanePath::CellularRule::Line; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; } { package Math::PlanePath::CellularRule::OneTwo; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; # use constant _NumSeq_N_oeis_anum => # { 'align=left,n_start=1' => # { # Not quite, OFFSET=1 cf coordinate X=0 here # Diagonal_NW => 'A001651', # not divisible by 3 # }, # { 'align=right,n_start=1' => # Not quite, A032766 0 or 1 mod 3, but it starts OFFSET=0 value=0 # whereas path start 1,3,4,etc without initial 0 # Diagonal => 'A032766', # # }; } { package Math::PlanePath::CellularRule::Two; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'align=right,n_start=1' => { Diagonal => 'A005408', # odd 2n+1 # OEIS-Other: A005408 planepath=CellularRule,rule=84 line_type=Diagonal }, }; } { package Math::PlanePath::CellularRule::OddSolid; # rule=50,58,114,122,178,179,186,242,250 pyramid every second point use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'n_start=1' => { Diagonal_NW => 'A000124', # triangular+1 # OEIS-Other: A000124 planepath=CellularRule,rule=50 line_type=Diagonal_NW # OEIS-Other: A000124 planepath=CellularRule,rule=58 line_type=Diagonal_NW # OEIS-Other: A000124 planepath=CellularRule,rule=114 line_type=Diagonal_NW # OEIS-Other: A000124 planepath=CellularRule,rule=122 line_type=Diagonal_NW # OEIS-Other: A000124 planepath=CellularRule,rule=178 line_type=Diagonal_NW # OEIS-Other: A000124 planepath=CellularRule,rule=179 line_type=Diagonal_NW # OEIS-Other: A000124 planepath=CellularRule,rule=186 line_type=Diagonal_NW # OEIS-Other: A000124 planepath=CellularRule,rule=242 line_type=Diagonal_NW # OEIS-Other: A000124 planepath=CellularRule,rule=250 line_type=Diagonal_NW # # Not quite, starts value=0 # Diagonal => 'A000217', # triangular numbers but diff start }, }; } { package Math::PlanePath::CellularRule54; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; } { package Math::PlanePath::CellularRule57; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; } { package Math::PlanePath::CellularRule190; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'mirror=0,n_start=0' => { Diagonal_NW => 'A006578', # triangular and quarter square # OEIS-Catalogue: A006578 planepath=CellularRule190,n_start=0 line_type=Diagonal_NW }, 'mirror=1,n_start=0' => { Diagonal_NW => 'A006578', # triangular and quarter square # OEIS-Other: A006578 planepath=CellularRule190,mirror=1,n_start=0 line_type=Diagonal_NW }, }; } { package Math::PlanePath::UlamWarburton; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'parts=4,n_start=0' => { Depth_start => 'A147562', # cells ON after n stages # OEIS-Catalogue: A147562 planepath=UlamWarburton,n_start=0 line_type=Depth_start }, 'parts=2,n_start=0' => { Depth_start => 'A183060', # num cells ON, starting from 0 X_axis => 'A183060', # X_axis == Depth_start # OEIS-Catalogue: A183060 planepath=UlamWarburton,parts=2,n_start=0 line_type=Depth_start # OEIS-Other: A183060 planepath=UlamWarburton,parts=2,n_start=0 }, 'parts=1,n_start=1' => { Depth_end => 'A151922', # num cells ON at end of depth=n Y_axis => 'A151922', # Y_axis == Depth_end # OEIS-Catalogue: A151922 planepath=UlamWarburton,parts=1,n_start=1 line_type=Depth_end # OEIS-Other: A151922 planepath=UlamWarburton,parts=1,n_start=1 line_type=Y_axis }, }; } { package Math::PlanePath::UlamWarburtonQuarter; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; # low 10111=23 increment to 11000=24 # 4^2*3+4*3^2+1*3^3 = 111 123 # 4^3*3 = 192 150 +27 = 3^3 use constant _NumSeq_N_oeis_anum => { 'parts=1,n_start=1' => { Depth_end => 'A151920', # 3^count1bits(n), OFFSET=0 1,2,5,6,9 # OEIS-Catalogue: A151920 planepath=UlamWarburtonQuarter line_type=Depth_end }, }; } { package Math::PlanePath::CoprimeColumns; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_X_offset => 1; # CoprimeColumns # X_axis => 'A002088', # cumulative totient but start X=1 value=0; # Diagonal A015614 cumulative-1 but start X=1 value=1 } { package Math::PlanePath::DivisibleColumns; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; # Not quite, X_axis => 'A006218' but path start X=1 cf OFFSET=0, # Not quite, Diagonal => 'A077597' but path start X=1 cf OFFSET=0 } # { package Math::PlanePath::File; # # File points from a disk file # # FIXME: analyze points for min/max # } # { package Math::PlanePath::QuintetCurve; # } # { package Math::PlanePath::QuintetCentres; # # inherit QuintetCurve # } { package Math::PlanePath::CornerReplicate; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { '' => { X_axis => 'A000695', # base 4 digits 0,1 only Y_axis => 'A001196', # base 4 digits 0,3 only Diagonal => 'A062880', # base 4 digits 0,2 only # OEIS-Other: A000695 planepath=CornerReplicate # OEIS-Other: A001196 planepath=CornerReplicate line_type=Y_axis # OEIS-Other: A062880 planepath=CornerReplicate line_type=Diagonal }, }; } { package Math::PlanePath::DigitGroups; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'radix=2,i_start=1' => { X_axis => 'A084471', # 0 -> 00 in binary, starting OFFSET=1 # OEIS-Catalogue: A084471 planepath=DigitGroups,radix=2 i_start=1 }, }; } { package Math::PlanePath::FibonacciWordFractal; use constant _NumSeq_X_axis_increasing => 1; # when touched use constant _NumSeq_Y_axis_increasing => 1; # when touched use constant _NumSeq_Diagonal_increasing => 1; # when touched } { package Math::PlanePath::LTiling; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'L_fill=middle' => { Diagonal => 'A062880', # base 4 digits 0,2 only # OEIS-Other: A062880 planepath=LTiling line_type=Diagonal }, }; } { package Math::PlanePath::WythoffArray; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_N_oeis_anum => { 'x_start=1,y_start=1' => { Y_axis => 'A003622', # spectrum of phi 1,4,6,9 Diagonal => 'A020941', # diagonal, OFFSET=1 # OEIS-Catalogue: A003622 planepath=WythoffArray,x_start=1,y_start=1 line_type=Y_axis # OEIS-Catalogue: A020941 planepath=WythoffArray,x_start=1,y_start=1 line_type=Diagonal # Y=1 every second => 'A005248', # every second Lucas number }, # # Not quite, extra initial 0,1 in A000045 Fibonaccis # # X_axis => 'A000045', # # # Y=1 row X=0,2,4,etc => 'A005248', # every second Lucas number }; } { package Math::PlanePath::WythoffPreliminaryTriangle; use constant _NumSeq_Y_axis_i_start => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_N_oeis_anum => { '' => { Y_axis => 'A173027', # OEIS-Other: A173027 planepath=WythoffPreliminaryTriangle line_type=Y_axis }, }; } { package Math::PlanePath::PowerArray; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; # cf Not quite A168183 non-multiples-of-9, A168186 non-multiples-of-12 # are values on Y axis, except OFFSET=1 value=1, whereas path start Y=0 # value=1 use constant _NumSeq_N_oeis_anum => { 'radix=2' => { X_axis => 'A000079', # powers 2^X Y_axis => 'A005408', # odd 2n+1 Diagonal => 'A014480', # (2n+1)*2^n starting n=0 # OEIS-Other: A000079 planepath=PowerArray # OEIS-Other: A005408 planepath=PowerArray line_type=Y_axis # OEIS-Catalogue: A014480 planepath=PowerArray line_type=Diagonal }, 'radix=3' => { X_axis => 'A000244', # powers 3^X # OEIS-Other: A000244 planepath=PowerArray,radix=3 # # Not quite, OFFSET=1 cf path start Y=0 # Y_axis => 'A001651', # non multiples of 3 }, 'radix=4' => { X_axis => 'A000302', # powers 4^X # OEIS-Other: A000302 planepath=PowerArray,radix=4 }, 'radix=5' => { X_axis => 'A000351', # powers 5^X # OEIS-Other: A000351 planepath=PowerArray,radix=5 }, 'radix=10' => { X_axis => 'A011557', # powers 10^X # OEIS-Other: A011557 planepath=PowerArray,radix=10 # Not quite, A067251 OFFSET=1 value=1 whereas path Y=0 N=value=1 # Y_axis => 'A067251', # no trailing 0 digits # # OEIS-Catalogue: A067251 planepath=PowerArray,radix=10 line_type=Y_axis }, }; } #------------------------------------------------------------------------------ # Math-PlanePath-Toothpick { package Math::PlanePath::ToothpickTree; # X axis has N=0 or N=0,1 only, except for parts=octant axis at Y=1 sub _NumSeq_X_axis_increasing { my ($self) = @_; return ($self->{'parts'} ne 'octant'); } # Y axis has N=0,N=1 only, except for parts=octant_up axis at X=1 sub _NumSeq_Y_axis_increasing { my ($self) = @_; return ($self->{'parts'} ne 'octant_up'); } use constant _NumSeq_Y_neg_increasing => 1; # N=0,N=2 only use constant _NumSeq_Diagonal_increasing => 1; # growth steps along diags use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; # catalogued in Math::NumSeq::OEIS::Catalogue::Plugin::PlanePathToothpick use constant _NumSeq_N_oeis_anum => { 'parts=4' => { Depth_start => 'A139250', # OEIS-Other: A139250 planepath=ToothpickTree,parts=4 line_type=Depth_start }, 'parts=3' => { Depth_start => 'A153006', # OEIS-Other: A153006 planepath=ToothpickTree,parts=3 line_type=Depth_start }, 'parts=2' => { Depth_start => 'A152998', # OEIS-Other: A152998 planepath=ToothpickTree,parts=2 line_type=Depth_start }, 'parts=1' => { Depth_start => 'A153000', # OEIS-Other: A153000 planepath=ToothpickTree,parts=1 line_type=Depth_start }, 'parts=wedge' => { Depth_start => 'A160406', # OEIS-Other: A160406 planepath=ToothpickTree,parts=wedge line_type=Depth_start }, 'parts=two_horiz' => { Depth_start => 'A160158', # OEIS-Other: A160158 planepath=ToothpickTree,parts=two_horiz line_type=Depth_start }, }; } { package Math::PlanePath::ToothpickReplicate; use constant _NumSeq_Y_axis_increasing => 1; # N=0,N=1 only sub _NumSeq_Y_neg_increasing { my ($self) = @_; return ($self->{'parts'} == 3 ? 0 # replication twist : 1); # N=0,N=2 only } use constant _NumSeq_Diagonal_increasing => 1; # replicate along diags use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; } { package Math::PlanePath::ToothpickUpist; use constant _NumSeq_Y_axis_increasing => 1; # rows increasing use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; # catalogued in Math::NumSeq::OEIS::Catalogue::Plugin::PlanePathToothpick use constant _NumSeq_N_oeis_anum => { '' => { Depth_start => 'A151566', # OEIS-Other: A151566 planepath=ToothpickUpist line_type=Depth_start }, }; } { package Math::PlanePath::ToothpickSpiral; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; # catalogued in Math::NumSeq::OEIS::Catalogue::Plugin::PlanePathToothpick use constant _NumSeq_N_oeis_anum => { 'n_start=1' => { Diagonal => 'A014634', # odd-index hexagonals Diagonal_NW => 'A033567', # Diagonal_SW => 'A185438', # Diagonal_SE => 'A188135', # # OEIS-Other: A014634 planepath=ToothpickSpiral line_type=Diagonal # OEIS-Other: A033567 planepath=ToothpickSpiral line_type=Diagonal_NW # OEIS-Other: A185438 planepath=ToothpickSpiral line_type=Diagonal_SW # OEIS-Other: A188135 planepath=ToothpickSpiral line_type=Diagonal_SE }, 'n_start=0' => { Diagonal => 'A033587', # Diagonal_SW => 'A014635', # even-index hexagonals Diagonal_SE => 'A033585', # Diagonal_NW => 'A139271', # OEIS-Other: A033587 planepath=ToothpickSpiral,n_start=0 line_type=Diagonal # OEIS-Other: A014635 planepath=ToothpickSpiral,n_start=0 line_type=Diagonal_SW # OEIS-Other: A033585 planepath=ToothpickSpiral,n_start=0 line_type=Diagonal_SE # OEIS-Other: A139271 planepath=ToothpickSpiral,n_start=0 line_type=Diagonal_NW }, }; } { package Math::PlanePath::LCornerTree; sub _NumSeq_X_axis_increasing { my ($self) = @_; return $self->{'parts'} ne 'diagonal-1'; } { my %_NumSeq_Y_axis_increasing = ('octant+1' => 1, # two points only 'diagonal-1' => 1, ); sub _NumSeq_Y_axis_increasing { my ($self) = @_; return $_NumSeq_Y_axis_increasing{$self->{'parts'}}; } } sub _NumSeq_X_neg_increasing { my ($self) = @_; return ($self->{'parts'} eq 'wedge' # two points N=0,N=1 || $self->{'parts'} eq 'wedge+1' # three points N=0,1,7 || $self->{'parts'} eq 'diagonal'); } # parts=diagonal has minimum N=0 at X=0,Y=-1, so explicit Y_neg minimum use constant _NumSeq_Y_neg_min => 0; sub _NumSeq_Y_neg_increasing { my ($self) = @_; return $self->{'parts'} ne 'diagonal'; } use constant _NumSeq_Diagonal_increasing => 1; # growth along diags use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; # catalogued in Math::NumSeq::OEIS::Catalogue::Plugin::PlanePathToothpick use constant _NumSeq_N_oeis_anum => { 'parts=4' => { Depth_start => 'A160410', # 4 * cumulative 3^count1bits(n) # OEIS-Other: A160410 planepath=LCornerTree line_type=Depth_start }, 'parts=3' => { Depth_start => 'A160412', # 3 * cumulative 3^count1bits(n) # OEIS-Other: A160412 planepath=LCornerTree,parts=3 line_type=Depth_start }, 'parts=diagonal-1' => { Depth_start => 'A183148', # half-plane triplet toothpicks # OEIS-Other: A183148 planepath=LCornerTree,parts=diagonal-1 line_type=Depth_start # No, N=1 start of depth=1 is at X=1,Y=0 not on SE diagonal # Diagonal_SE => 'A183148', }, # Not quite, A130665=1,4,7,16 offset=0 whereas Nend=0,1,4,7,16 depth=0 # has extra initial 0. # 'parts=1' => # { Depth_end => 'A130665', # cumulative 3^count1bits(d), starting a(0)=1 # # OEIS-Catalogue: A130665 planepath=LCornerTree,parts=1,n_start=-1 line_type=Depth_end i_offset=1 # }, }; } { package Math::PlanePath::LCornerReplicate; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; # replicate along diags # catalogued in Math::NumSeq::OEIS::Catalogue::Plugin::PlanePathToothpick use constant _NumSeq_N_oeis_anum => { '' => { Diagonal => 'A062880', # base 4 digits 0,2 only # OEIS-Other: A062880 planepath=LCornerReplicate line_type=Diagonal }, }; } { package Math::PlanePath::OneOfEight; use constant _NumSeq_X_axis_increasing => 1; use constant _NumSeq_X_neg_increasing => 1; use constant _NumSeq_Y_axis_increasing => 1; use constant _NumSeq_Y_neg_increasing => 1; use constant _NumSeq_Diagonal_increasing => 1; use constant _NumSeq_Diagonal_NW_increasing => 1; use constant _NumSeq_Diagonal_SW_increasing => 1; use constant _NumSeq_Diagonal_SE_increasing => 1; # catalogued in Math::NumSeq::OEIS::Catalogue::Plugin::PlanePathToothpick use constant _NumSeq_N_oeis_anum => { 'parts=4' => { Depth_start => 'A151725', # OEIS-Other: A151725 planepath=OneOfEight line_type=Depth_start }, 'parts=1' => { Depth_start => 'A151735', # OEIS-Other: A151735 planepath=OneOfEight,parts=1 line_type=Depth_start }, 'parts=3mid' => { Depth_start => 'A170880', # OEIS-Other: A170880 planepath=OneOfEight,parts=3mid line_type=Depth_start }, 'parts=3side' => { Depth_start => 'A170879', # OEIS-Other: A170879 planepath=OneOfEight,parts=3side line_type=Depth_start }, }; } { package Math::PlanePath::HTree; # clockwise around each sub-tree so N increases along X axis use constant _NumSeq_X_axis_increasing => 1; } #------------------------------------------------------------------------------ { package Math::PlanePath; use constant _NumSeq_A2 => 0; } { package Math::PlanePath::TriangleSpiral; use constant _NumSeq_A2 => 1; } { package Math::PlanePath::HexSpiral; use constant _NumSeq_A2 => 1; } { package Math::PlanePath::HexArms; use constant _NumSeq_A2 => 1; } { package Math::PlanePath::TriangularHypot; use constant _NumSeq_A2 => 1; } { package Math::PlanePath::Flowsnake; use constant _NumSeq_A2 => 1; # and FlowsnakeCentres inherits } 1; __END__ =for stopwords Ryde Math-PlanePath SquareSpiral lookup PlanePath ie =head1 NAME Math::NumSeq::PlanePathN -- sequence of N values from PlanePath module =head1 SYNOPSIS use Math::NumSeq::PlanePathN; my $seq = Math::NumSeq::PlanePathN->new (planepath => 'SquareSpiral', line_type => 'X_axis'); my ($i, $value) = $seq->next; =head1 DESCRIPTION This module presents N values from a C as a sequence. The default is the X axis, or the C parameter (a string) can choose among "X_axis" X axis (positive part) "Y_axis" Y axis (positive part) "X_neg" X negative axis "Y_neg" Y negative axis "Diagonal" leading diagonal X=i, Y=i "Diagonal_NW" north-west diagonal X=-i, Y=i "Diagonal_SW" south-west diagonal X=-i, Y=-i "Diagonal_SE" south-east diagonal X=i, Y=-i "Depth_start" first N at depth=i "Depth_end" last N at depth=i For example the C X axis starts i=0 with values 1, 2, 11, 28, 53, 86, etc. "X_neg", "Y_neg", "Diagonal_NW", etc, on paths which don't traverse negative X or Y have just a single value from X=0,Y=0. The behaviour on paths which visit only some of the points on the respective axis is unspecified as yet, as is behaviour on paths with repeat points, such as the C. =head1 FUNCTIONS See L for behaviour common to all sequence classes. =over 4 =item C<$seq = Math::NumSeq::PlanePathN-Enew (key=Evalue,...)> Create and return a new sequence object. The options are planepath string, name of a PlanePath module planepath_object PlanePath object line_type string, as described above C can be either the module part such as "SquareSpiral" or a full class name "Math::PlanePath::SquareSpiral". =item C<$value = $seq-Eith($i)> Return the N value at C<$i> in the PlanePath. C<$i> gives a position on the respective C, so the X,Y to lookup a C<$value=N> is X,Y line_type ----- --------- $i, 0 "X_axis" 0, $i "Y_axis" -$i, 0 "X_neg" 0, -$i "Y_neg" $i, $i "Diagonal" $i, -$i "Diagonal_NW" -$i, -$i "Diagonal_SW" $i, -$i "Diagonal_SE" =item C<$bool = $seq-Epred($value)> Return true if C<$value> occurs in the sequence. This means C<$value> is an integer N which is on the respective C, ie. that C<($path-En_to_xy($value)> is on the line type. =back =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/NumSeq/PlanePathCoord.pm0000644000175000017500000064516613774014675020016 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::NumSeq::PlanePathCoord; use 5.004; use strict; use Carp 'croak'; use constant 1.02; # various underscore constants below use List::Util; #use List::Util 'max','min'; *max = \&Math::PlanePath::_max; *min = \&Math::PlanePath::_min; use vars '$VERSION','@ISA'; $VERSION = 129; use Math::NumSeq; @ISA = ('Math::NumSeq'); use Math::PlanePath 124; # v.124 for n_to_n_list() *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite'; # uncomment this to run the ### lines # use Smart::Comments; sub description { my ($self) = @_; if (ref $self) { return "Coordinate $self->{'coordinate_type'} values from path $self->{'planepath'}"; } else { # class method return 'Coordinate values from a PlanePath'; } } use constant::defer parameter_info_array => sub { my $choices = [ 'X', 'Y', 'Sum', 'SumAbs', 'Product', 'DiffXY', 'DiffYX', 'AbsDiff', 'Radius', 'RSquared', 'TRadius', 'TRSquared', 'IntXY', 'FracXY', 'BitAnd', 'BitOr', 'BitXor', 'Min','Max', 'MinAbs','MaxAbs', 'GCD', 'Depth', 'SubHeight', 'NumChildren','NumSiblings', 'RootN', 'IsLeaf','IsNonLeaf', # Maybe: # 'ExperimentalRowOffset', # 'ExperimentalMinAbsTri','ExperimentalMaxAbsTri', # 'ExperimentalAbsX', # 'ExperimentalAbsY', # 'DiffXY/2', # 'ExperimentalDiffXYsquared', # 'ExperimentalDiffYXsquares', # 'ExperimentalParity', # 'ExperimentalNumerator','ExperimentalDenominator', # 'ExperimentalLeafDistance', # 'ExperimentalGcdDivisions', # 'ExperimentalKroneckerSymbol', # 'ExperimentalMulDist', # 'ExperimentalHammingDist', # 'ExperimentalNumOverlap', # # 'ExperimentalNeighbours3', # NumNeighbours # 'ExperimentalNeighbours4', # 'ExperimentalNeighbours4d', # 'ExperimentalNeighbours6', # 'ExperimentalNeighbours8', # # 'ExperimentalVisitNum', # 'ExperimentalVisitCount', # 'ExperimentalRevisit', # 'ExperimentalPairsXY','ExperimentalPairsYX', ]; return [ _parameter_info_planepath(), { name => 'coordinate_type', display => 'Coordinate Type', type => 'enum', default => 'X', choices => $choices, choices_display => $choices, description => 'The coordinate or combination to take from the path.', }, ]; }; use constant::defer _parameter_info_planepath => sub { # require Module::Util; # cf ...::Generator->path_choices() order # my @choices = sort map { s/.*:://; # if (length() > $width) { $width = length() } # $_ } # Module::Util::find_in_namespace('Math::PlanePath'); # my @choices = Module::Find::findsubmod('Math::PlanePath'); # @choices = grep {$_ ne 'Math::PlanePath'} @choices; # my $choices = ...::Generator->path_choices_array; # foreach (@$choices) { # if (length() > $width) { $width = length() } # } require File::Spec; require Scalar::Util; my $width = 0; my %names; foreach my $dir (@INC) { next if ! defined $dir || ref $dir; # next if ref $dir eq 'CODE' # subr # || ref $dir eq 'ARRAY' # array of subr and more # || Scalar::Util::blessed($dir); opendir DIR, File::Spec->catdir ($dir, 'Math', 'PlanePath') or next; while (my $name = readdir DIR) { # basename of .pm files, and not emacs .#Foo.pm lockfiles $name =~ s/^([^.].*)\.pm$/$1/ or next; if (length($name) > $width) { $width = length($name) } $names{$name} = 1; } closedir DIR; } my $choices = [ sort keys %names ]; return { name => 'planepath', display => 'PlanePath Class', type => 'string', default => $choices->[0], choices => $choices, width => $width + 5, description => 'PlanePath module name.', }; }; #------------------------------------------------------------------------------ sub oeis_anum { my ($self) = @_; ### PlanePathCoord oeis_anum() ... my $planepath_object = $self->{'planepath_object'}; my $coordinate_type = $self->{'coordinate_type'}; if ($coordinate_type eq 'ExperimentalAbsX') { if (! $planepath_object->x_negative) { $coordinate_type = 'X'; } } elsif ($coordinate_type eq 'ExperimentalAbsY') { if (! $planepath_object->y_negative) { $coordinate_type = 'Y'; } } if ($planepath_object->isa('Math::PlanePath::Rows')) { if ($coordinate_type eq 'X') { return _oeis_anum_modulo($planepath_object->{'width'}); } } elsif ($planepath_object->isa('Math::PlanePath::Columns')) { if ($coordinate_type eq 'Y') { return _oeis_anum_modulo($planepath_object->{'height'}); } } { my $key = Math::NumSeq::PlanePathCoord::_planepath_oeis_anum_key($self->{'planepath_object'}); my $i_start = $self->i_start; if ($i_start != $self->default_i_start) { ### $i_start ### cf n_start: $planepath_object->n_start $key .= ",i_start=$i_start"; } ### planepath: ref $planepath_object ### $key ### whole table: $planepath_object->_NumSeq_Coord_oeis_anum ### key href: $planepath_object->_NumSeq_Coord_oeis_anum->{$key} if (my $anum = $planepath_object->_NumSeq_Coord_oeis_anum->{$key}->{$coordinate_type}) { return $anum; } } # all-zeros if (defined (my $values_min = $self->values_min)) { if (defined (my $values_max = $self->values_max)) { if ($values_min == 0 && $values_max == 0) { return 'A000004'; # all 0s } if ($values_min == 2 && $values_max == 2) { return 'A007395'; # all 2s } } } return undef; } sub _oeis_anum_modulo { my ($modulus) = @_; require Math::NumSeq::Modulo; return Math::NumSeq::Modulo->new(modulus=>$modulus)->oeis_anum; } sub _planepath_oeis_key { my ($path) = @_; ### PlanePathCoord _planepath_oeis_key() ... return join(',', ref($path), (map { # nasty hack to exclude SierpinskiCurveStair diagonal_length $_->{'name'} eq 'diagonal_length' ? () : do { my $value = $path->{$_->{'name'}}; if ($_->{'type'} eq 'boolean') { $value = ($value ? 1 : 0); } ### $_ ### $value ### gives: "$_->{'name'}=$value" (defined $value ? "$_->{'name'}=$value" : ()) } } _planepath_oeis_anum_parameter_info_list($path))); } sub _planepath_oeis_anum_key { my ($path) = @_; ### PlanePathCoord _planepath_oeis_key() ... return join(',', (map { # nasty hack to exclude SierpinskiCurveStair diagonal_length $_->{'name'} eq 'diagonal_length' ? () : do { my $value = $path->{$_->{'name'}}; if ($_->{'type'} eq 'boolean') { $value = ($value ? 1 : 0); } ### $_ ### $value ### gives: "$_->{'name'}=$value" (defined $value ? "$_->{'name'}=$value" : ()) } } _planepath_oeis_anum_parameter_info_list($path))); } sub _planepath_oeis_anum_parameter_info_list { my ($path) = @_; my @parameter_info_list = $path->_NumSeq_override_parameter_info_list; unless (@parameter_info_list) { @parameter_info_list = ($path->parameter_info_list, $path->_NumSeq_extra_parameter_info_list); } return @parameter_info_list; } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); my $planepath_object = ($self->{'planepath_object'} ||= _planepath_name_to_object($self->{'planepath'})); ### coordinate func: '_coordinate_func_'.$self->{'coordinate_type'} { my $key = $self->{'coordinate_type'}; $key =~ s{/}{div}; $self->{'coordinate_func'} = $planepath_object->can("_NumSeq_Coord_${key}_func") || $self->can("_coordinate_func_$key") || croak "Unrecognised coordinate_type: ",$self->{'coordinate_type'}; } $self->rewind; ### $self return $self; } sub _planepath_name_to_object { my ($name) = @_; ### PlanePathCoord _planepath_name_to_object(): $name ($name, my @args) = split /,+/, $name; unless ($name =~ /^Math::PlanePath::/) { $name = "Math::PlanePath::$name"; } ### $name require Module::Load; Module::Load::load ($name); return $name->new (map {/(.*?)=(.*)/} @args); # width => $options{'width'}, # height => $options{'height'}, } sub default_i_start { my ($self) = @_; my $planepath_object = $self->{'planepath_object'} # nasty hack allow no 'planepath_object' when SUPER::new() calls rewind() || return 0; return $planepath_object->n_start; } sub i_start { my ($self) = @_; return (defined $self->{'i_start'} ? $self->{'i_start'} # nasty hack allow no 'planepath_object' when SUPER::new() calls # rewind() : $self->{'planepath_object'} && $self->{'planepath_object'}->n_start); } sub rewind { my ($self) = @_; $self->{'i'} = $self->i_start; } sub next { my ($self) = @_; ### NumSeq-PlanePathCoord next(): "i=$self->{'i'}" my $i = $self->{'i'}++; if (defined (my $value = &{$self->{'coordinate_func'}}($self, $i))) { return ($i, $value); } else { return; } } sub ith { my ($self, $i) = @_; ### NumSeq-PlanePathCoord ith(): $i return &{$self->{'coordinate_func'}}($self,$i); } use constant _INFINITY => do { my $x = 999; foreach (1 .. 20) { $x *= $x; } $x; }; sub _coordinate_func_X { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return $x; } sub _coordinate_func_Y { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return $y; } sub _coordinate_func_Sum { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return $x + $y; } sub _coordinate_func_SumAbs { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return abs($x) + abs($y); } sub _coordinate_func_Product { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return $x * $y; } sub _coordinate_func_DiffXY { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return $x - $y; } sub _coordinate_func_DiffXYdiv2 { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return ($x - $y) / 2; } sub _coordinate_func_DiffYX { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return $y - $x; } sub _coordinate_func_AbsDiff { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return abs($x - $y); } sub _coordinate_func_Radius { my ($self, $n) = @_; return $self->{'planepath_object'}->n_to_radius($n); } sub _coordinate_func_RSquared { my ($self, $n) = @_; return $self->{'planepath_object'}->n_to_rsquared($n); } sub _coordinate_func_TRadius { my ($self, $n) = @_; return _path_n_to_tradius ($self->{'planepath_object'}, $n); } sub _coordinate_func_TRSquared { my ($self, $n) = @_; return _path_n_to_trsquared ($self->{'planepath_object'}, $n); } sub _path_n_to_tradius { my ($path, $n) = @_; # TRadius = sqrt(x^2+3*y^2) my $trsquared = _path_n_to_trsquared($path,$n); return (defined $trsquared ? sqrt($trsquared) : undef); } sub _path_n_to_trsquared { my ($path, $n) = @_; # TRSquared = x^2+3*y^2 my ($x, $y) = $path->n_to_xy($n) or return undef; return $x*$x + $y*$y*3; } sub _coordinate_func_Depth { my ($self, $n) = @_; return $self->{'planepath_object'}->tree_n_to_depth($n); } sub _coordinate_func_NumChildren { my ($self, $n) = @_; return $self->{'planepath_object'}->tree_n_num_children($n); } sub _coordinate_func_IsLeaf { my ($self, $n) = @_; ### _coordinate_func_IsLeaf(): $n my $num_children = $self->{'planepath_object'}->tree_n_num_children($n); # undef, 0 or 1 return (defined $num_children ? ($num_children == 0 ? 1 : 0) : undef); } sub _coordinate_func_IsNonLeaf { my ($self, $n) = @_; ### _coordinate_func_IsLeaf(): $n my $num_children = $self->{'planepath_object'}->tree_n_num_children($n); # undef, 0 or 1 return (defined $num_children ? ($num_children == 0 ? 0 : 1) : undef); } use Math::PlanePath::GcdRationals; use POSIX 'fmod'; sub _coordinate_func_GCD { my ($self, $n) = @_; # FIXME: Maybe re-run with bigrat if X or Y not integers. if ($self->{'planepath_object'}->isa('Math::PlanePath::KochSnowflakes') && $n <= 3) { return 1/3; } my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; $x = abs($x); $y = abs($y); if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } if ($x == int($x) && $y == int($y)) { return Math::PlanePath::GcdRationals::_gcd($x,$y); } if ($x == 0) { return $y; } if ($y > $x) { $y = fmod($y,$x); } for (;;) { ### assert: $x >= 1 if ($y == 0) { return $x; # gcd(x,0)=x } if ($y < 0.00001) { return 0; } ($x,$y) = ($y, fmod($x,$y)); } } sub _coordinate_func_RootN { my ($self, $n) = @_; return $self->{'planepath_object'}->tree_n_root($n); } # math-image --values=PlanePathCoord,coordinate_type=SubHeight,planepath=ThisPath --path=SierpinskiTriangle --scale=10 sub _coordinate_func_SubHeight { my ($self, $n) = @_; ### _coordinate_func_SubHeight(): $n my $height = $self->{'planepath_object'}->tree_n_to_subheight($n); return (defined $height ? $height : _INFINITY); } # rounding towards zero sub _coordinate_func_IntXY { my ($self, $n) = @_; ### _coordinate_func_IntXY(): $n if (my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n)) { return _xy_to_IntXY($x,$y); } return undef; } sub _xy_to_IntXY { my ($x, $y) = @_; ### xy: "x=$x y=$y" if ($y < 0) { $y = -$y; $x = -$x; } if ($y == 0) { return _INFINITY; # X/0 = infinity } if ($y == int($y)) { ### done in integers, no floating point ... my $r = $x % $y; ### $r if ($x < 0 && $r > 0) { $r -= $y; } ### assert: (($x>=0)&&($r>=0)) || (($x<=0)&&($r<=0)) $x -= $r; ### assert: ($x % $y) == 0 return int($x / $y); } return int($x/$y); } sub _coordinate_func_FracXY { my ($self, $n) = @_; ### _coordinate_func_FracXY(): $n my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; ### xy: "x=$x y=$y" if ($y < 0) { $y = -$y; $x = -$x; } if ($y == 0) { return 0; # X/0 = infinity + frac=0 } if ($y == int($y)) { ### done in integers ... my $r = $x % $y; if ($x < 0 && $r > 0) { $r -= $y; } # EXPERIMENTAL: # bigint/bigint as bigrat, otherwise promote to bigfloat if (ref $r && $r->isa('Math::BigInt')) { if (ref $y && $y->isa('Math::BigInt')) { require Math::BigRat; return Math::BigRat->new($r) / $y; } $r = $r->as_float; } else { if (ref $y && $y->isa('Math::BigInt')) { $y = $y->as_float; } } return $r/$y; } else { my $f = $x/$y; return $f - int($x/$y); } } # Math::BigInt in perl 5.6.0 has and/or/xor sub _op_and { $_[0] & $_[1] } sub _op_or { $_[0] | $_[1] } sub _op_xor { $_[0] ^ $_[1] } sub _coordinate_func_BitAnd { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return _bitwise_by_parts($x,$y, \&_op_and); } sub _coordinate_func_BitOr { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return _bitwise_by_parts($x,$y, \&_op_or); } sub _coordinate_func_BitXor { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return _bitwise_by_parts($x,$y, \&_op_xor); } use constant 1.02 _UV_MAX_PLUS_1 => do { my $pow = 1.0; my $uv = ~0; while ($uv) { $uv >>= 1; $pow *= 2.0; } $pow }; sub _bitwise_by_parts { my ($x, $y, $opfunc) = @_; ### _bitwise_by_parts(): $x, $y if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } # Positive integers in UV range plain operator. # Any ref is Math::BigInt or whatever left to its operator overloads. if (ref $x || ref $y || ($x == int($x) && $y == int($y) && $x >= 0 && $y >= 0 && $x < _UV_MAX_PLUS_1 && $x < _UV_MAX_PLUS_1)) { return &$opfunc($x,$y); } $x *= 65536.0; $x *= 65536.0; $x = int($x); $y *= 65536.0; $y *= 65536.0; $y = int($y); my @ret; # low to high while ($x >= 1 || $x < -1 || $y >= 1 || $y < -1) { ### $x ### $y my $xpart = $x % 65536.0; if ($xpart < 0) { $xpart += 65536.0; } $x = ($x - $xpart) / 65536.0; my $ypart = $y % 65536.0; if ($ypart < 0) { $ypart += 65536.0; } $y = ($y - $ypart) / 65536.0; ### xpart: $xpart . sprintf(' %04X',$xpart) ### ypart: $ypart . sprintf(' %04X',$ypart) push @ret, &$opfunc($xpart,$ypart); } my $ret = (&$opfunc($x<0,$y<0) ? -1 : 0); ### @ret ### $x ### $y ### $ret foreach my $rpart (reverse @ret) { # high to low $ret = 65536.0*$ret + $rpart; } ### ret joined: $ret $ret /= 65536.0; $ret /= 65536.0; ### ret final: $ret return $ret; } use constant 1.02 _IV_MIN => - (~0 >> 1) - 1; sub _sign_extend { my ($n) = @_; return ($n - (- _IV_MIN)) + _IV_MIN; } use constant 1.02 _UV_NUMBITS => do { my $uv = ~0; my $count = 0; while ($uv) { $uv >>= 1; $count++; last if $count >= 1024; } $count }; sub _frac_to_int { my ($x) = @_; $x -= int($x); return int(abs($x)*(2**_UV_NUMBITS())); } sub _int_to_frac { my ($x) = @_; return $x / (2**_UV_NUMBITS()); } sub _coordinate_func_Min { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return min($x,$y); } sub _coordinate_func_Max { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return max($x,$y); } sub _coordinate_func_MinAbs { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return min(abs($x),abs($y)); } sub _coordinate_func_MaxAbs { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return max(abs($x),abs($y)); } sub _coordinate_func_NumSiblings { my ($self, $n) = @_; return path_tree_n_num_siblings($self->{'planepath_object'}, $n); } # ENHANCE-ME: if $n==NaN would like to return NaN, maybe sub path_tree_n_num_siblings { my ($path, $n) = @_; $n = $path->tree_n_parent($n); return (defined $n ? $path->tree_n_num_children($n) - 1 # not including self : 0); # any tree root considered to have no siblings } #------------------------------------------------------------------------------ # UNTESTED/EXPERIMENTAL # n_start = i_start is X(n) # n_start + 1 is Y(n) etc # # floor((n - nstart)/2) + nstart # = floor((n - nstart)/2 + nstart) # = floor((n + nstart)/2) # xy = n - nstart mod 2 # = n - nstart + 2*nstart mod 2 # = n + nstart mod 2 # # GP-Test my(nstart=0,n=0); floor((n+nstart)/2)==0 && (n+nstart)%2==0 # GP-Test my(nstart=0,n=1); floor((n+nstart)/2)==0 && (n+nstart)%2==1 # # GP-Test my(nstart=3,n=3); floor((n+nstart)/2)==3 && (n+nstart)%2==0 # GP-Test my(nstart=3,n=4); floor((n+nstart)/2)==3 && (n+nstart)%2==1 # GP-Test my(nstart=3,n=5); floor((n+nstart)/2)==4 && (n+nstart)%2==0 # GP-Test my(nstart=3,n=6); floor((n+nstart)/2)==4 && (n+nstart)%2==1 # sub _coordinate_func_ExperimentalPairsXY { my ($self, $n) = @_; my $path = $self->{'planepath_object'}; $n += $path->n_start; my $xy = _divrem_mutate($n,2); my ($x, $y) = $path->n_to_xy($n) or return undef; return ($xy ? $y : $x); } sub _coordinate_func_ExperimentalPairsYX { my ($self, $n) = @_; my $path = $self->{'planepath_object'}; $n += $path->n_start; my $xy = _divrem_mutate($n,2); my ($x, $y) = $path->n_to_xy($n) or return undef; return ($xy ? $x : $y); } sub _coordinate_func_ExperimentalAbsX { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return abs($x); } sub _coordinate_func_ExperimentalAbsY { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return abs($y); } # DiffXYsquared = X^2 - Y^2 sub _coordinate_func_ExperimentalDiffXYsquared { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return $x*$x - $y*$y; } # DiffYXsquared = Y^2 - X^2 sub _coordinate_func_ExperimentalDiffYXsquares { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return $y*$y - $x*$x; } sub _coordinate_func_ExperimentalLeafDistance { my ($self, $n) = @_; if (my $coderef = $self->{'planepath_object'} ->can('_EXPERIMENTAL__tree_n_to_leafdist')) { return $self->{'planepath_object'}->$coderef($n); } return path_tree_n_to_leafdist_by_search($self->{'planepath_object'},$n); } sub path_tree_n_to_leafdist_by_search { my ($path, $n) = @_; ### path_tree_n_to_leafdist(): $n if ($n < $path->n_start || ! $path->tree_any_leaf($path)) { return undef; } if (is_infinite($n)) { return $n; } my @pending = ($n); for (my $distance = 0; ; $distance++) { ### $distance ### @pending @pending = map { my @children = $path->tree_n_children($_) or return $distance; ### @children @children } @pending; } } sub _coordinate_func_ExperimentalNumOverlap { my ($self, $n) = @_; my ($x,$y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return _path_xy_num_overlaps($self->{'planepath_object'}, $x,$y); } # $path->xy_num_overlaps($x,$y) # Return the number of ... sub _path_xy_num_overlaps { my ($path, $x,$y) = @_; my @n_list = $path->xy_to_n_list($x,$y); return scalar(@n_list) - 1; } # math-image --values=PlanePathCoord,coordinate_type=ExperimentalNeighbours4,planepath=DragonCurve --path=DragonCurve --scale=10 my $neighbours3 = [ 2,0, -1,1, -1,-1 ]; sub _coordinate_func_ExperimentalNeighbours3 { my ($self, $n) = @_; return _path_n_neighbours_count ($self->{'planepath_object'}, $n, $neighbours3); } my $neighbours4 = [ 1,0, 0,1, -1,0, 0,-1 ]; sub _coordinate_func_ExperimentalNeighbours4 { my ($self, $n) = @_; return _path_n_neighbours_count ($self->{'planepath_object'}, $n, $neighbours4); } my $neighbours4d = [ 1,1, -1,1, -1,-1, 1,-1 ]; sub _coordinate_func_ExperimentalNeighbours4d { my ($self, $n) = @_; return _path_n_neighbours_count ($self->{'planepath_object'}, $n, $neighbours4d); } my $neighbours8 = [ 1,0, 0,1, -1,0, 0,-1, 1,1, -1,1, 1,-1, -1,-1 ]; sub _coordinate_func_ExperimentalNeighbours8 { my ($self, $n) = @_; return _path_n_neighbours_count ($self->{'planepath_object'}, $n, $neighbours8); } # ExperimentalNeighbours6v triangular vertical my $neighbours6 = [ 2,0, 1,1, -1,1, -2,0, -1,-1, 1,-1 ]; sub _coordinate_func_ExperimentalNeighbours6 { my ($self, $n) = @_; return _path_n_neighbours_count ($self->{'planepath_object'}, $n, $neighbours6); } sub _path_n_neighbours_count { my ($path, $n, $neighbours_aref) = @_; # my $aref = $surround[$num_points] # || croak "_path_n_neighbours_count() unrecognised number of points ",$num_points; my ($x, $y) = $path->n_to_xy($n) or return undef; my $count = 0; for (my $i = 0; $i < @$neighbours_aref; $i+=2) { $count += $path->xy_is_visited($x + $neighbours_aref->[$i], $y + $neighbours_aref->[$i+1]); } return $count; } sub _coordinate_func_ExperimentalGcdDivisions { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; $x = abs(int($x)); $y = abs(int($y)); if ($x == 0) { return $y; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } if ($x < $y) { ($x,$y) = ($y,$x); } my $count = 0; for (;;) { if ($y <= 1) { return $count; } ($x,$y) = ($y, $x % $y); $count++; } } # 1 for first visit, 2, 3, ... for subsequent sub _coordinate_func_ExperimentalVisitNum { my ($self, $n) = @_; my $path = $self->{'planepath_object'}; if (my ($x,$y) = $path->n_to_xy($n)) { my @n_list = grep {$_<=$n} $path->n_to_n_list($n); return scalar(@n_list); } return undef; } # number of visits ever made to location of $n, including $n itself so >=1 sub _coordinate_func_ExperimentalVisitCount { my ($self, $n) = @_; return path_n_num_visits($self->{'planepath_object'}, $n); } # $path->n_to_visit_count($n) # Return the number of visits to the curve at point C<$n>, including C<$n> # itself. If there is no C<$n> in the path then return C. sub path_n_num_visits { my ($path, $n) = @_; my ($x,$y) = $path->n_to_xy($n) or return undef; my @n_list = $path->xy_to_n_list($x,$y); return scalar(@n_list); } # number of revisits ever made to location of $n, so 0 if never sub _coordinate_func_ExperimentalRevisit { my ($self, $n) = @_; return path_n_to_revisit($self->{'planepath_object'}, $n); } # $path->path_n_to_revisit($n) # Return the number of other N which visit point C<$n>. # If point C<$n> is visited only by that C<$n> then the return is 0. sub path_n_to_revisit { my ($path, $n) = @_; if (my ($x, $y) = $path->n_to_xy($n)) { my $ret = 0; foreach my $n_list ($path->n_to_n_list($n)) { if ($n == $n_list) { return $ret; } $ret++; } } return undef; } # A215200 Triangle read by rows, Kronecker symbol (n-k|k) for n>=1, 1<=k<=n. # cf A005825 ExperimentalNumerators in a worst case of a Jacobi symbol algorithm. # A005826 Worst case of a Jacobi symbol algorithm. # A005827 Worst case of a Jacobi symbol algorithm. # A157415 Triangle t(n,m) = Jacobi(prime(n) / prime(m)) + Jacobi( prime(n)/ prime(n-m+2)), 2<=m<=n. sub _coordinate_func_ExperimentalKroneckerSymbol { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return _kronecker_symbol($x,$y); } sub _kronecker_symbol { my ($x, $y) = @_; ### _kronecker_symbol(): "x=$x y=$y" $x = int($x); $y = int($y); if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } if ($x == 0) { # (0/b)=1 if b=+/-1, (0/b)=0 otherwise return ($y == 1 || $y == -1 ? 1 : 0); } if ($y == 0) { # (a/0)=1 if a=+/-1, (a/0)=0 otherwise return ($x == 1 || $x == -1 ? 1 : 0); } my $ret = 0; # (a/-1)=1 if a>=0, (a/-1)=-1 if a<0 if ($y < 0) { $y = abs($y); if ($x < 0) { ### (a/-1) = -1 when a<0 ... $ret = 2; } } if ($y % 2 == 0) { if ($x % 2 == 0) { return 0; # (even/even)=0 } # (a/2) = (2/a) while ($y && $y % 4 == 0) { # (a/2)*(a/2)=1 ### initial y multiple of 4 ... $y /= 4; } # (b/2)=(2/b) for b odd # (2/b) = (-1)^((b^2-1)/8) which is 1 if b==1,7mod8 or -1 if b==3,5mod8 if ($y % 2 == 0) { ### initial y even, xor: (($x+1)/2) & 2 ### assert: $x % 2 != 0 $ret ^= ($x+1)/2; $y /= 2; } } for (;;) { ### at: "x=$x y=$y" ### assert: $y%2 != 0 if ($y <= 1) { ### y=1 stop (a/1)=1 ... last; } ### assert: $y > 1 $x %= $y; ### remainder to: "x=$x" if ($x <= 1) { ### stop, (1/b) = 1 ... last; } # (2/b) with b odd is (-1)^((b^2-1)/8) # is (2/b)=1 if b==1,7mod8 or (2/b)=-1 if b==3,5mod8 while ($x && $x % 4 == 0) { ### x multiple of 4 ... $x /= 4; } if ($x % 2 == 0) { # (2/b) = (-1)^((b^2-1)/8) which is 1 if b==1,7mod8 or -1 if b==3,5mod8 ### x even, xor: (($y+1)/2) & 2 $ret ^= ($y+1)/2; $x /= 2; } ### reciprocity, xor: ($x % 4) & ($y % 4) & 2 $ret ^= ($x % 4) & ($y % 4); ($x,$y) = ($y,$x); } if ($x == 0) { ### (0/b)=0 ... return 0; } ### final ret: ($ret & 2) return ($ret & 2 ? -1 : 1); } sub _coordinate_func_ExperimentalMaxAbsTri { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return max(abs($x+$y),abs($x-$y),abs(2*$y)); } sub _coordinate_func_ExperimentalMinAbsTri { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; return min(abs($x+$y),abs($x-$y),abs(2*$y)); } sub _coordinate_func_ExperimentalRowOffset { my ($self, $n) = @_; return path_n_to_row_offset ($self->{'planepath_object'}, $n); } sub path_n_to_row_offset { my ($path, $n) = @_; my $depth = $path->tree_n_to_depth($n); if (! defined $depth) { return undef; } my ($n_row, $n_end) = $path->tree_depth_to_n_range($depth); if ($n_end - $n_row + 1 == $path->tree_depth_to_width($depth)) { return $n - $n_row; } my $ret = 0; foreach my $i ($n_row .. $n - 1) { if ($path->tree_n_to_depth($i) == $depth) { $ret++; } } return $ret; } use Math::PlanePath::GcdRationals; sub _coordinate_func_ExperimentalMulDist { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; $x = int(abs($x)); $y = int(abs($y)); if (my $g = Math::PlanePath::GcdRationals::_gcd($x,$y)) { $x /= $g; $y /= $g; } unless ($x < (2.0**32) && $y < (2.0**32)) { return undef; } require Math::Factor::XS; return Math::Factor::XS::count_prime_factors($x) + Math::Factor::XS::count_prime_factors($y); } # Count of differing bit positions. # Infinite if twos-comp negative. # 1111 -1 1 # 1101 -2 10 # 1110 -3 11 # use Math::PlanePath::Base::Digits 'bit_split_lowtohigh'; sub _coordinate_func_ExperimentalHammingDist { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } # twos complement if ($x<0) { if ($y >= 0) { return _INFINITY; } $x = -$x; $y = -$y; } else { if ($y < 0) { return _INFINITY; } } # abs values # $x = abs(int($x)); # $y = abs(int($y)); my @xbits = bit_split_lowtohigh($x); my @ybits = bit_split_lowtohigh($y); my $ret = 0; while (@xbits || @ybits) { $ret += (shift @xbits ? 1 : 0) ^ (shift @ybits ? 1 : 0); } return $ret; } sub _coordinate_func_ExperimentalParity { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; $x += $y; $y = _floor($x); $y -= ($y % 2); return $x - $y; } sub _floor { my ($x) = @_; my $int = int($x); return ($x < $int ? $int-1 : $int); } sub _coordinate_func_ExperimentalNumerator { my ($self, $n) = @_; ### _coordinate_func_ExperimentalNumerator(): $n my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; ### $x ### $y if ($y < 0) { $x = -$x; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my $g = Math::PlanePath::GcdRationals::_gcd(abs($x),abs($y)); return ($g == 0 ? 0 : $x/$g); } sub _coordinate_func_ExperimentalDenominator { my ($self, $n) = @_; my ($x, $y) = $self->{'planepath_object'}->n_to_xy($n) or return undef; if ($y < 0) { $x = -$x; $y = -$y; } if ($y == 0) { # +-any/0 reckoned as 1/0 return $y; } if (is_infinite($x)) { return $x; # +-inf/nonzero = +-inf } if ($x == 0 # 0/nonzero reckoned as 0/1 || is_infinite($y)) { # +-finite/+inf reckoned as 0/1 return 1; } my $g = Math::PlanePath::GcdRationals::_gcd(abs($x),$y); if ($g == 0) { # X/0 reckoned as 1/0 return 0; } return abs($y)/$g; } #------------------------------------------------------------------------------ sub characteristic_integer { my ($self) = @_; ### PlanePathCoord characteristic_integer() ... my $planepath_object = $self->{'planepath_object'}; if (my $func = $planepath_object->can("_NumSeq_Coord_$self->{'coordinate_type'}_integer")) { return $planepath_object->$func(); } if (defined (my $values_min = $self->values_min) && defined (my $values_max = $self->values_max)) { if ($values_min == int($values_min) && $values_max == int($values_max) && $values_min == $values_max) { return 1; } } return undef; } sub characteristic_smaller { my ($self) = @_; ### characteristic_smaller() ... my $planepath_object = $self->{'planepath_object'}; my $func; return (($func = ($planepath_object->can("_NumSeq_Coord_$self->{'coordinate_type'}_smaller"))) ? $planepath_object->$func() : 1); # default is smaller } { my %coordinate_to_d_minimum_method = (X => 'dx_minimum', Y => 'dy_minimum', Sum => 'dsumxy_minimum', DiffXY => 'ddiffxy_minimum', DiffYX => \&path_ddiffyx_minimum, ); sub path_ddiffyx_minimum { my ($path) = @_; my $ddiffxy_maximum = $path->ddiffxy_maximum(); return (defined $ddiffxy_maximum ? - $ddiffxy_maximum : undef); } my %coordinate_type_monotonic_use = (RSquared => 'Radius', TRSquared => 'TRadius', ); sub characteristic_increasing { my ($self) = @_; ### PlanePathCoord characteristic_increasing() ... my $planepath_object = $self->{'planepath_object'}; my $coordinate_type = $self->{'coordinate_type'}; # eg. if dx_minimum() > 0 then X is increasing if (my $method = $coordinate_to_d_minimum_method{$coordinate_type}) { my $d_minimum = $planepath_object->$method(); ### delta method: $method ### $d_minimum return (defined $d_minimum && $d_minimum > 0); } $coordinate_type = ($coordinate_type_monotonic_use{$coordinate_type} || $coordinate_type); if (my $coderef = $planepath_object->can("_NumSeq_Coord_${coordinate_type}_increasing")) { ### dispatch to: $coderef return $planepath_object->$coderef(); } ### unknown ... return undef; } sub characteristic_non_decreasing { my ($self) = @_; ### PlanePathCoord characteristic_non_decreasing() ... my $planepath_object = $self->{'planepath_object'}; my $coordinate_type = $self->{'coordinate_type'}; # eg. if dx_minimum() >= 0 then X is non-decreasing if (my $method = $coordinate_to_d_minimum_method{$coordinate_type}) { my $d_minimum = $planepath_object->$method(); ### delta method: $method ### $d_minimum return (defined $d_minimum && $d_minimum >= 0); } if (defined (my $values_min = $self->values_min)) { if (defined (my $values_max = $self->values_max)) { if ($values_min == $values_max) { ### constant seq is non-decreasing ... return 1; } } } $coordinate_type = ($coordinate_type_monotonic_use{$coordinate_type} || $coordinate_type); if (my $coderef = $planepath_object->can("_NumSeq_Coord_${coordinate_type}_non_decreasing")) { ### dispatch to: $coderef return $planepath_object->$coderef(); } ### if increasing then non_decreasing too ... return $self->characteristic_increasing; } } { my %values_min = (X => 'x_minimum', Y => 'y_minimum', Sum => 'sumxy_minimum', SumAbs => 'sumabsxy_minimum', DiffXY => 'diffxy_minimum', AbsDiff => 'absdiffxy_minimum', RSquared => 'rsquared_minimum', GCD => 'gcdxy_minimum', NumChildren => 'tree_num_children_minimum', ); sub values_min { my ($self) = @_; ### PlanePathCoord values_min() ... my $planepath_object = $self->{'planepath_object'}; if (my $method = ($values_min{$self->{'coordinate_type'}} || $planepath_object->can("_NumSeq_Coord_$self->{'coordinate_type'}_min"))) { ### $method return $planepath_object->$method(); } return undef; } } { my %values_max = (X => 'x_maximum', Y => 'y_maximum', Sum => 'sumxy_maximum', SumAbs => 'sumabsxy_maximum', DiffXY => 'diffxy_maximum', AbsDiff => 'absdiffxy_maximum', GCD => 'gcdxy_maximum', NumChildren => 'tree_num_children_maximum', ); sub values_max { my ($self) = @_; my $planepath_object = $self->{'planepath_object'}; if (my $method = ($values_max{$self->{'coordinate_type'}} || $planepath_object->can("_NumSeq_Coord_$self->{'coordinate_type'}_max"))) { return $planepath_object->$method(); } return undef; } } { package Math::PlanePath; use constant _NumSeq_override_parameter_info_list => (); use constant _NumSeq_extra_parameter_info_list => (); use constant _NumSeq_Coord_oeis_anum => {}; #------------- sub _NumSeq_Coord_neighbours_min { my ($self, $neighbours_aref) = @_; my %hash; for (my $i = 0; $i < $#$neighbours_aref; $i+=2) { $hash{"$neighbours_aref->[$i],$neighbours_aref->[$i+1]"} = 1; } my @dxdy_list = $self->_UNDOCUMENTED__dxdy_list; for (my $i = 0; $i < $#dxdy_list; $i += 2) { if ($hash{"$dxdy_list[$i],dxdy_list[$i+1]"}) { return 1; } } return 0; } use constant _NumSeq_Coord_filling_type => 'plane'; { my %_NumSeq_Coord_ExperimentalNeighbours3_min = (plane => 3, triangular => 3, quadrant => 1, half => 2, ); sub _NumSeq_Coord_ExperimentalNeighbours3_min { my ($self) = @_; if (my $filling_type = $self->_NumSeq_Coord_filling_type) { return $_NumSeq_Coord_ExperimentalNeighbours3_min{$filling_type}; } _NumSeq_Coord_neighbours_min($self,$neighbours3); } } { my %_NumSeq_Coord_ExperimentalNeighbours4_min = (plane => 4, triangular => 0, quadrant => 2, half => 3, ); sub _NumSeq_Coord_ExperimentalNeighbours4_min { my ($self) = @_; if (my $filling_type = $self->_NumSeq_Coord_filling_type) { return $_NumSeq_Coord_ExperimentalNeighbours4_min{$filling_type}; } _NumSeq_Coord_neighbours_min($self,$neighbours3); } } { my %_NumSeq_Coord_ExperimentalNeighbours4d_min = (plane => 4, triangular => 4, quadrant => 1, half => 2, ); sub _NumSeq_Coord_ExperimentalNeighbours4d_min { my ($self) = @_; if (my $filling_type = $self->_NumSeq_Coord_filling_type) { return $_NumSeq_Coord_ExperimentalNeighbours4d_min{$filling_type}; } _NumSeq_Coord_neighbours_min($self,$neighbours3); } } { my %_NumSeq_Coord_ExperimentalNeighbours6_min = (plane => 6, triangular => 6, quadrant => 1, half => 4, ); sub _NumSeq_Coord_ExperimentalNeighbours6_min { my ($self) = @_; if (my $filling_type = $self->_NumSeq_Coord_filling_type) { return $_NumSeq_Coord_ExperimentalNeighbours6_min{$filling_type}; } _NumSeq_Coord_neighbours_min($self,$neighbours3); } } { my %_NumSeq_Coord_ExperimentalNeighbours8_min = (plane => 8, triangular => 4, quadrant => 3, half => 5, ); sub _NumSeq_Coord_ExperimentalNeighbours8_min { my ($self) = @_; if (my $filling_type = $self->_NumSeq_Coord_filling_type) { return $_NumSeq_Coord_ExperimentalNeighbours8_min{$filling_type}; } _NumSeq_Coord_neighbours_min($self,$neighbours3); } } use constant _NumSeq_Coord_ExperimentalNeighbours3_max => 3; use constant _NumSeq_Coord_ExperimentalNeighbours4d_max => 4; use constant _NumSeq_Coord_ExperimentalNeighbours6_max => 6; use constant _NumSeq_Coord_ExperimentalNeighbours8_max => 8; sub _NumSeq_Coord_ExperimentalNeighbours4_max { my ($self) = @_; if (my $filling_type = $self->_NumSeq_Coord_filling_type) { if ($filling_type eq 'triangular') { return 0; } } return 4; } use constant _NumSeq_Coord_ExperimentalNeighbours3_integer => 1; # counts use constant _NumSeq_Coord_ExperimentalNeighbours4_integer => 1; use constant _NumSeq_Coord_ExperimentalNeighbours4d_integer => 1; use constant _NumSeq_Coord_ExperimentalNeighbours6_integer => 1; use constant _NumSeq_Coord_ExperimentalNeighbours8_integer => 1; #------ # X use constant _NumSeq_Coord_X_integer => 1; # usually use constant _NumSeq_Coord_X_increasing => undef; use constant _NumSeq_Coord_X_non_decreasing => undef; #------ # Y use constant _NumSeq_Coord_Y_integer => 1; # usually use constant _NumSeq_Coord_Y_increasing => undef; use constant _NumSeq_Coord_Y_non_decreasing => undef; #------ # Sum sub _NumSeq_Coord_Sum_integer { my ($self) = @_; ### _NumSeq_Coord_Sum_integer() ... return ($self->_NumSeq_Coord_X_integer && $self->_NumSeq_Coord_Y_integer); } *_NumSeq_Coord_SumAbs_integer = \&_NumSeq_Coord_Sum_integer; *_NumSeq_Coord_Product_integer = \&_NumSeq_Coord_Sum_integer; *_NumSeq_Coord_DiffXY_integer = \&_NumSeq_Coord_Sum_integer; *_NumSeq_Coord_AbsDiff_integer = \&_NumSeq_Coord_Sum_integer; *_NumSeq_Coord_RSquared_integer = \&_NumSeq_Coord_Sum_integer; *_NumSeq_Coord_TRSquared_integer = \&_NumSeq_Coord_Sum_integer; *_NumSeq_Coord_ExperimentalDiffXYsquared_integer = \&_NumSeq_Coord_Sum_integer; *_NumSeq_Coord_ExperimentalDiffYXsquares_integer = \&_NumSeq_Coord_Sum_integer; sub _NumSeq_Coord_Product_min { my ($self) = @_; my ($x_minimum, $y_minimum); if (defined ($x_minimum = $self->x_minimum) && defined ($y_minimum = $self->y_minimum) && $x_minimum >= 0 && $y_minimum >= 0) { return $x_minimum * $y_minimum; } return undef; } sub _NumSeq_Coord_Product_max { my ($self) = @_; my ($x_max, $y_minimum); ### X_max: $self->x_maximum ### Y_min: $self->y_minimum if (defined ($x_max = $self->x_maximum) && defined ($y_minimum = $self->y_minimum) && $x_max <= 0 && $y_minimum >= 0) { # X all negative, Y all positive return $y_minimum * $x_max; } return undef; } #---------- # DiffYX opposite of DiffXY sub _NumSeq_Coord_DiffYX_min { my ($self) = @_; if (defined (my $m = $self->diffxy_maximum)) { return - $m; } else { return undef; } } sub _NumSeq_Coord_DiffYX_max { my ($self) = @_; if (defined (my $m = $self->diffxy_minimum)) { return - $m; } else { return undef; } } sub _NumSeq_Coord_DiffYX_integer { my ($self) = @_; return $self->_NumSeq_Coord_DiffXY_integer; } #---------- # Radius sub _NumSeq_Coord_Radius_min { my ($path) = @_; return sqrt($path->rsquared_minimum); } sub _NumSeq_Coord_Radius_max { my ($path) = @_; my $rsquared_maximum = $path->rsquared_maximum; return (defined $rsquared_maximum ? sqrt($rsquared_maximum) : undef); } #---------- # TRadius sub _NumSeq_Coord_TRadius_min { my ($path) = @_; return sqrt($path->_NumSeq_Coord_TRSquared_min); } sub _NumSeq_Coord_TRSquared_min { my ($self) = @_; # The X and Y each closest to the origin. This assumes that point is # actually visited, but is likely to be close. my $x_minimum = $self->x_minimum; my $x_maximum = $self->x_maximum; my $y_minimum = $self->y_minimum; my $y_maximum = $self->y_maximum; my $x = (( defined $x_minimum && $x_minimum) > 0 ? $x_minimum : (defined $x_maximum && $x_maximum) < 0 ? $x_maximum : 0); my $y = (( defined $y_minimum && $y_minimum) > 0 ? $y_minimum : (defined $y_maximum && $y_maximum) < 0 ? $y_maximum : 0); return ($x*$x + 3*$y*$y); } sub _NumSeq_Coord_IntXY_min { my ($self) = @_; if (defined(my $x = $self->x_minimum) && defined(my $y = $self->y_minimum)) { if ($y >= 0) { if ($y == 0) { $y = 1; } if ($x >= 0 && $y <= $x) { $y = 2*$x; if (defined (my $y_maximum = $self->y_maximum)) { $y = List::Util::min($y, $y_maximum); } } ### using: "x=$x y=$y" # presume that point x_minimum(),y_minimum() occurs return Math::NumSeq::PlanePathCoord::_xy_to_IntXY($x,$y); } } return undef; } use constant _NumSeq_Coord_IntXY_max => undef; use constant _NumSeq_Coord_IntXY_integer => 1; use constant _NumSeq_Coord_FracXY_max => 1; use constant _NumSeq_FracXY_max_is_supremum => 1; sub _NumSeq_Coord_FracXY_min { my ($self) = @_; if (! $self->x_negative && ! $self->y_negative) { return 0; } else { return -1; } } *_NumSeq_FracXY_min_is_infimum = \&_NumSeq_Coord_FracXY_min; # if non-zero use constant _NumSeq_Coord_FracXY_integer => 0; use constant _NumSeq_Coord_ExperimentalParity_min => 0; sub _NumSeq_Coord_ExperimentalParity_integer { $_[0]->_NumSeq_Coord_Sum_integer } sub _NumSeq_Coord_ExperimentalParity_max { my ($self) = @_; return ($self->_NumSeq_Coord_ExperimentalParity_integer ? 1 : 2); } sub _NumSeq_ExperimentalParity_max_is_supremum { my ($self) = @_; return ($self->_NumSeq_Coord_ExperimentalParity_integer ? 0 : 1); } sub _NumSeq_Coord_ExperimentalNumerator_min { my ($self) = @_; if (defined (my $gcd_maximum = $self->gcdxy_maximum)) { if ($gcd_maximum == 1) { return $self->x_minimum; # X,Y no common factor, so ExperimentalNumerator==X } } if (! $self->y_negative && defined (my $x_minimum = $self->x_minimum)) { ### $x_minimum if ($x_minimum >= 1) { return 1; # somewhere X/Y dividing out to 1/Z } if ($x_minimum >= 0) { return 0; # 0/Y } } return undef; } use constant _NumSeq_Coord_ExperimentalNumerator_integer => 1; sub _NumSeq_Coord_ExperimentalDenominator_min { my ($self) = @_; if (defined (my $y_minimum = $self->y_minimum)) { if ($y_minimum > 0) { return 1; # X/0=1/0 doesn't occur, so den>=1 } if ($y_minimum == 0) { return 0; # X/0=1/0 } } return 0; } use constant _NumSeq_Coord_ExperimentalDenominator_integer => 1; # fractional part treated bitwise *_NumSeq_Coord_BitAnd_integer = \&_NumSeq_Coord_Sum_integer; *_NumSeq_Coord_BitOr_integer = \&_NumSeq_Coord_Sum_integer; *_NumSeq_Coord_BitXor_integer = \&_NumSeq_Coord_Sum_integer; #------------- # GCD use constant _NumSeq_Coord_GCD_integer => 1; use constant _NumSeq_Coord_ExperimentalGcdDivisions_min => 0; use constant _NumSeq_Coord_ExperimentalGcdDivisions_max => undef; use constant _NumSeq_Coord_ExperimentalGcdDivisions_integer => 1; #------------- use constant _NumSeq_Coord_ExperimentalKroneckerSymbol_min => -1; use constant _NumSeq_Coord_ExperimentalKroneckerSymbol_max => 1; use constant _NumSeq_Coord_ExperimentalKroneckerSymbol_integer => 1; sub _NumSeq_Coord_BitAnd_min { my ($self) = @_; # if one of X,Y always >=0 then BitAnd >= 0 my $max_min = $self->_NumSeq_Coord_Max_min; if (defined $max_min && $max_min >= 0) { return 0; } return undef; } sub _NumSeq_Coord_BitOr_min { my ($self) = @_; my $x_minimum = $self->x_minimum; my $y_minimum = $self->y_minimum; if (defined $x_minimum && defined $y_minimum) { return ($x_minimum > 0 ? ($y_minimum > 0 ? List::Util::min($x_minimum, $y_minimum) # +X,+Y : $x_minimum) # +X,-Y : ($y_minimum > 0 ? $y_minimum # -X,+Y : List::Util::min($x_minimum, $y_minimum))); # -X,-Y } else { return undef; } } sub _NumSeq_Coord_BitXor_min { my ($self) = @_; my $x_minimum = $self->x_minimum; my $y_minimum = $self->y_minimum; if ($self->x_negative || $self->y_negative) { return undef; } else { return 0; # no negatives } } #------------- # Min # Return the minimum value taken by min(X,Y) at integer N. # This is simply the smaller of x_minimum() or y_minimum(). # If either X or Y is unbounded below then min(X,Y) is unbounded below too # and minxy_minimum() returns undef. # sub _NumSeq_Coord_Min_min { my ($self) = @_; # min(X,Y) has a minimum iff both X and Y have a minimum. if (defined (my $x_minimum = $self->x_minimum) && defined (my $y_minimum = $self->y_minimum)) { return Math::NumSeq::PlanePathCoord::min($x_minimum, $y_minimum); } return undef; } sub _NumSeq_Coord_Min_max { my ($self) = @_; # If there's a maximum X or Y then that will be the maximum for Min. # If there's both maximum X and Y then the bigger of the two. my $x_maximum = $self->x_maximum; my $y_maximum = $self->y_maximum; if (defined $x_maximum || defined $y_maximum) { return Math::NumSeq::PlanePathCoord::max (defined $x_maximum ? $x_maximum : (), defined $y_maximum ? $y_maximum : ()); } return undef; } #------------- # Max sub _NumSeq_Coord_Max_min { my ($self) = @_; my $x_minimum = $self->x_minimum; my $y_minimum = $self->y_minimum; # or empty max is undef if neither minimum return Math::NumSeq::PlanePathCoord::max (defined $x_minimum ? $x_minimum : (), defined $y_minimum ? $y_minimum : ()); } # Return the maximum value taken by max(X,Y) at integer N. # This is simply the smaller of x_maximum() or y_maximum(). # If either X or Y is unbounded above then max(X,Y) is unbounded above too # and maxxy_maximum() returns undef. # sub _NumSeq_Coord_Max_max { my ($self) = @_; if (defined (my $x_maximum = $self->x_maximum) && defined (my $y_maximum = $self->y_maximum)) { return Math::NumSeq::PlanePathCoord::max($x_maximum, $y_maximum); } return undef; } *_NumSeq_Coord_Min_integer = \&_NumSeq_Coord_Sum_integer; *_NumSeq_Coord_Max_integer = \&_NumSeq_Coord_Sum_integer; sub _NumSeq_Coord_Max_is_always_X { my ($path) = @_; # if X-Y>=0 then X>=Y and max(X,Y)=X my $diffxy_minimum = $path->diffxy_minimum; return (defined $diffxy_minimum && $diffxy_minimum >= 0); } sub _NumSeq_Coord_Max_is_always_Y { my ($path) = @_; # if X-Y<=0 then X<=Y and max(X,Y)=Y my $diffxy_maximum = $path->diffxy_maximum; return (defined $diffxy_maximum && $diffxy_maximum <= 0); } sub _NumSeq_Coord_Max_increasing { my ($path) = @_; if ($path->_NumSeq_Coord_Max_is_always_X) { return $path->_NumSeq_Coord_X_increasing; } if ($path->_NumSeq_Coord_Max_is_always_Y) { return $path->_NumSeq_Coord_Y_increasing; } return undef; } sub _NumSeq_Coord_Max_non_decreasing { my ($path) = @_; if ($path->_NumSeq_Coord_Max_is_always_X) { return $path->_NumSeq_Coord_X_non_decreasing; } if ($path->_NumSeq_Coord_Max_is_always_Y) { return $path->_NumSeq_Coord_Y_non_decreasing; } return undef; } #------------ # ExperimentalHammingDist use constant _NumSeq_Coord_ExperimentalHammingDist_min => 0; use constant _NumSeq_Coord_ExperimentalHammingDist_integer => 1; # sub _NumSeq_Coord_ExperimentalHammingDist_min { # my ($self) = @_; # return ($self->x_negative || $self->y_negative ? undef : 0); # } #------------- # MinAbs sub _NumSeq_Coord_MinAbs_min { my ($self) = @_; return Math::NumSeq::PlanePathCoord::min ($self->_NumSeq_Coord_ExperimentalAbsX_min, $self->_NumSeq_Coord_ExperimentalAbsY_min); } sub _NumSeq_Coord_MinAbs_max { my ($self) = @_; my $absx_maximum = $self->_NumSeq_Coord_ExperimentalAbsX_max; my $absy_maximum = $self->_NumSeq_Coord_ExperimentalAbsY_max; # smaller of the two maxima, or undef if neither bounded return Math::NumSeq::PlanePathCoord::min ((defined $absx_maximum ? $absx_maximum : ()), (defined $absy_maximum ? $absy_maximum : ())); } *_NumSeq_Coord_MinAbs_integer = \&_NumSeq_Coord_Sum_integer; sub _NumSeq_Coord_MaxAbs_non_decreasing { my ($path) = @_; if (! $path->x_negative && ! $path->y_negative) { # X>0 and Y>0 so MaxAbs==Max return $path->_NumSeq_Coord_Max_non_decreasing; } return undef; } sub _NumSeq_Coord_MaxAbs_increasing { my ($path) = @_; if (! $path->x_negative && ! $path->y_negative) { # X>0 and Y>0 so MaxAbs==Max return $path->_NumSeq_Coord_Max_increasing; } return undef; } #------------- # MaxAbs sub _NumSeq_Coord_MaxAbs_min { my ($self) = @_; return Math::NumSeq::PlanePathCoord::max ($self->_NumSeq_Coord_ExperimentalAbsX_min, $self->_NumSeq_Coord_ExperimentalAbsY_min); } sub _NumSeq_Coord_MaxAbs_max { my ($self) = @_; if (defined (my $x_minimum = $self->x_minimum) && defined (my $y_minimum = $self->y_minimum) && defined (my $x_maximum = $self->x_maximum) && defined (my $y_maximum = $self->y_maximum)) { return Math::NumSeq::PlanePathCoord::max (-$x_minimum, -$y_minimum, $x_maximum, $y_maximum); } return undef; } *_NumSeq_Coord_MaxAbs_integer = \&_NumSeq_Coord_Sum_integer; #------------- # ExperimentalAbsX sub _NumSeq_Coord_ExperimentalAbsX_min { my ($self) = @_; # if positive min or negative max then 0 is not crossed and have an ExperimentalAbsX # min which is bigger than 0 { my $x_minimum = $self->x_minimum; if (defined $x_minimum && $x_minimum > 0) { return $x_minimum; } } { my $x_maximum = $self->x_maximum; if (defined $x_maximum && $x_maximum < 0) { return - $x_maximum; } } return 0; } sub _NumSeq_Coord_ExperimentalAbsX_max { my ($self) = @_; # if bounded above and below then have an ExperimentalAbsX if (defined (my $x_minimum = $self->x_minimum)) { if (defined (my $x_maximum = $self->x_maximum)) { return Math::NumSeq::PlanePathCoord::max (abs($x_minimum), abs($x_maximum)); } } return undef; } #------------- # ExperimentalAbsY sub _NumSeq_Coord_ExperimentalAbsY_min { my ($self) = @_; # if positive min or negative max then 0 is not crossed and have an ExperimentalAbsY # min which is bigger than 0 { my $y_minimum = $self->y_minimum; if (defined $y_minimum && $y_minimum > 0) { return $y_minimum; } } { my $y_maximum = $self->y_maximum; if (defined $y_maximum && $y_maximum < 0) { return - $y_maximum; } } return 0; } sub _NumSeq_Coord_ExperimentalAbsY_max { my ($self) = @_; # if bounded above and below then have an ExperimentalAbsY if (defined (my $y_minimum = $self->y_minimum)) { if (defined (my $y_maximum = $self->y_maximum)) { return Math::NumSeq::PlanePathCoord::max (abs($y_minimum), abs($y_maximum)); } } return undef; } #------------- sub _NumSeq_Coord_pred_X { my ($path, $value) = @_; return (($path->figure ne 'square' || $value == int($value)) && ($path->x_negative || $value >= 0)); } sub _NumSeq_Coord_pred_Y { my ($path, $value) = @_; return (($path->figure ne 'square' || $value == int($value)) && ($path->y_negative || $value >= 0)); } sub _NumSeq_Coord_pred_Sum { my ($path, $value) = @_; return (($path->figure ne 'square' || $value == int($value)) && ($path->x_negative || $path->y_negative || $value >= 0)); } sub _NumSeq_Coord_pred_SumAbs { my ($path, $value) = @_; return (($path->figure ne 'square' || $value == int($value)) && $value >= 0); } #-------------------------- sub _NumSeq_Coord_pred_Radius { my ($path, $value) = @_; return $path->_NumSeq_Coord_pred_RSquared($value*$value); } sub _NumSeq_Coord_pred_RSquared { my ($path, $value) = @_; # FIXME: this should be whether x^2+y^2 ever occurs, which is no prime # factor 4k+3 or some such return (($path->figure ne 'square' || $value == int($value)) && $value >= 0); } #-------------------------- sub _NumSeq_Coord_pred_TRadius { my ($path, $value) = @_; return $path->_NumSeq_Coord_pred_RSquared($value*$value); } sub _NumSeq_Coord_pred_TRSquared { my ($path, $value) = @_; # FIXME: this should be whether x^2+3*y^2 occurs ... return (($path->figure ne 'square' || $value == int($value)) && $value >= 0); } #-------------------------- use constant _NumSeq_Coord_Depth_min => 0; sub _NumSeq_Coord_Depth_max { my ($path, $value) = @_; return ($path->tree_n_num_children($path->n_start) ? undef # is a tree, default infinite max depth : 0); # not a tree, depth always 0 } use constant _NumSeq_Coord_Depth_integer => 1; use constant _NumSeq_Coord_Depth_non_decreasing => 1; # usually #-------------------------- use constant _NumSeq_Coord_NumChildren_integer => 1; # compare with "==" to be numeric style, just in case some overloaded # class stringizes to "1.0" or some such nonsense sub _NumSeq_Coord_pred_NumChildren { my ($self, $value) = @_; foreach my $num ($self->tree_num_children_list) { if ($value == $num) { return 1; } } return 0; } #-------------------------- use constant _NumSeq_Coord_NumSiblings_min => 0; # root node no siblings sub _NumSeq_Coord_NumSiblings_max { my ($path) = @_; return List::Util::max(0, # not including self $path->tree_num_children_maximum - 1); } use constant _NumSeq_Coord_NumSiblings_integer => 1; # if NumChildren=const except for NumChildren=0 then have NumSiblings=const-1 # so NumSiblings=0 at the root and thereafter non-decreasing sub _NumSeq_Coord_NumSiblings_non_decreasing { my ($path) = @_; my @num_children = $path->tree_num_children_list; if ($num_children[0] == 0) { shift @num_children; } return (scalar(@num_children) <= 1); } #-------------------------- use constant _NumSeq_Coord_ExperimentalLeafDistance_integer => 1; use constant _NumSeq_Coord_ExperimentalLeafDistance_min => 0; use constant _NumSeq_Coord_ExperimentalLeafDistance_max => 0; #-------------------------- sub _NumSeq_Coord_SubHeight_min { my ($path) = @_; if ($path->tree_any_leaf) { return 0; # height 0 at a leaf } else { return undef; # actually +infinity } } sub _NumSeq_Coord_SubHeight_max { my ($path) = @_; return ($path->tree_n_num_children($path->n_start) ? undef # is a tree, default infinite max height : 0); # not a tree, height always 0 } use constant _NumSeq_Coord_SubHeight_integer => 1; #-------------------------- sub _NumSeq_Coord_RootN_min { my ($path) = @_; return $path->n_start; } sub _NumSeq_Coord_RootN_max { my ($path) = @_; return $path->n_start + List::Util::max(0, $path->tree_num_roots()-1); } use constant _NumSeq_Coord_RootN_integer => 1; #-------------------------- sub _NumSeq_Coord_IsLeaf_min { my ($path) = @_; # if num_children>0 occurs then that's a non-leaf node so IsLeaf=0 occurs return ($path->tree_num_children_maximum() > 0 ? 0 : 1); } sub _NumSeq_Coord_IsLeaf_max { my ($path) = @_; return ($path->tree_any_leaf() ? 1 : 0); } # IsNonLeaf is opposite of IsLeaf sub _NumSeq_Coord_IsNonLeaf_min { my ($path) = @_; return $path->_NumSeq_Coord_IsLeaf_max ? 0 : 1; } sub _NumSeq_Coord_IsNonLeaf_max { my ($path) = @_; return $path->_NumSeq_Coord_IsLeaf_min ? 0 : 1; } use constant _NumSeq_Coord_IsLeaf_integer => 1; use constant _NumSeq_Coord_IsNonLeaf_integer => 1; #-------------------------- use constant _NumSeq_Coord_ExperimentalRowOffset_min => 0; #-------------------------- use constant _NumSeq_Coord_n_list_max => 1; use constant _NumSeq_Coord_ExperimentalVisitCount_min => 1; sub _NumSeq_Coord_ExperimentalVisitCount_max { my ($path) = @_; return $path->_NumSeq_Coord_n_list_max; } use constant _NumSeq_Coord_ExperimentalVisitNum_min => 1; *_NumSeq_Coord_ExperimentalVisitNum_max = \&_NumSeq_Coord_ExperimentalVisitCount_max; use constant _NumSeq_Coord_ExperimentalRevisit_min => 0; sub _NumSeq_Coord_ExperimentalRevisit_max { my ($path) = @_; return $path->_NumSeq_Coord_n_list_max - 1; } } { package Math::PlanePath::SquareSpiral; use constant _NumSeq_Coord_filling_type => 'plane'; sub _NumSeq_Coord_MaxAbs_non_decreasing { my ($self) = @_; return ($self->{'wider'} == 0); } use constant _NumSeq_Coord_oeis_anum => { 'wider=0,n_start=1' => { X => 'A174344', SumAbs => 'A214526', # "Manhattan" distance from n to 1 RSquared => 'A336336', # norm # OEIS-Catalogue: A174344 planepath=SquareSpiral coordinate_type=X # OEIS-Catalogue: A214526 planepath=SquareSpiral coordinate_type=SumAbs # OEIS-Catalogue: A336336 planepath=SquareSpiral coordinate_type=RSquared }, 'wider=0,n_start=0' => { Sum => 'A180714', # X+Y of square spiral AbsDiff => 'A053615', # n..0..n, distance to pronic # OEIS-Catalogue: A180714 planepath=SquareSpiral,n_start=0 coordinate_type=Sum # OEIS-Other: A053615 planepath=SquareSpiral,n_start=0 coordinate_type=AbsDiff }, }; } { package Math::PlanePath::GreekKeySpiral; use constant _NumSeq_Coord_filling_type => 'plane'; sub _NumSeq_Coord_MaxAbs_non_decreasing { my ($self) = @_; return ($self->{'turns'} == 0); # when same as SquareSpiral } } { package Math::PlanePath::PyramidSpiral; use constant _NumSeq_Coord_filling_type => 'plane'; use constant _NumSeq_Coord_oeis_anum => { 'n_start=0' => { X => 'A329116', Y => 'A329972', # OEIS-Catalogue: A329116 planepath=PyramidSpiral,n_start=0 coordinate_type=X # OEIS-Catalogue: A329972 planepath=PyramidSpiral,n_start=0 coordinate_type=Y ExperimentalAbsX => 'A053615', # runs n..0..n, OFFSET=0 # OEIS-Catalogue: A053615 planepath=PyramidSpiral,n_start=0 coordinate_type=ExperimentalAbsX }, }; } { package Math::PlanePath::TriangleSpiral; use constant _NumSeq_Coord_filling_type => 'triangular'; use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always } { package Math::PlanePath::TriangleSpiralSkewed; use constant _NumSeq_Coord_filling_type => 'plane'; } { package Math::PlanePath::DiamondSpiral; use constant _NumSeq_Coord_filling_type => 'plane'; use constant _NumSeq_Coord_SumAbs_non_decreasing => 1; # diagonals pos,neg use constant _NumSeq_Coord_oeis_anum => { 'n_start=0' => { X => 'A010751', # up 1, down 2, up 3, down 4, etc Y => 'A305258', ExperimentalAbsY => 'A053616', # OEIS-Catalogue: A010751 planepath=DiamondSpiral,n_start=0 # OEIS-Catalogue: A305258 planepath=DiamondSpiral,n_start=0 coordinate_type=Y # OEIS-Other: A053616 planepath=DiamondSpiral,n_start=0 coordinate_type=ExperimentalAbsY }, }; } { package Math::PlanePath::AztecDiamondRings; use constant _NumSeq_Coord_filling_type => 'plane'; } { package Math::PlanePath::PentSpiralSkewed; use constant _NumSeq_Coord_filling_type => 'plane'; } { package Math::PlanePath::HexSpiral; use constant _NumSeq_Coord_filling_type => 'triangular'; # origin 0,0 if wider even, otherwise only X=1,Y=0 if wider odd sub _NumSeq_Coord_TRSquared_min { $_[0]->rsquared_minimum } # always odd/even according to wider odd/even sub _NumSeq_Coord_ExperimentalParity_min { my ($self) = @_; return $self->{'wider'} & 1; } *_NumSeq_Coord_ExperimentalParity_max = \&_NumSeq_Coord_ExperimentalParity_min; # X!=Y when wider odd *_NumSeq_Coord_ExperimentalHammingDist_min = \&_NumSeq_Coord_ExperimentalParity_min; *_NumSeq_Coord_MaxAbs_min = \&_NumSeq_Coord_ExperimentalParity_min; use constant _NumSeq_Coord_oeis_anum => { 'wider=0,n_start=0' => {X => 'A328818', Y => 'A307012', # OEIS-Catalogue: A328818 planepath=HexSpiral,n_start=0 coordinate_type=X # OEIS-Catalogue: A307012 planepath=HexSpiral,n_start=0 coordinate_type=Y }, }; } { package Math::PlanePath::HexSpiralSkewed; use constant _NumSeq_Coord_filling_type => 'plane'; } { package Math::PlanePath::HexArms; use constant _NumSeq_Coord_filling_type => 'triangular'; use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always } { package Math::PlanePath::HeptSpiralSkewed; use constant _NumSeq_Coord_filling_type => 'plane'; } { package Math::PlanePath::AnvilSpiral; use constant _NumSeq_Coord_filling_type => 'plane'; } { package Math::PlanePath::OctagramSpiral; use constant _NumSeq_Coord_filling_type => 'plane'; } { package Math::PlanePath::KnightSpiral; use constant _NumSeq_Coord_filling_type => 'plane'; } { package Math::PlanePath::CretanLabyrinth; use constant _NumSeq_Coord_filling_type => 'plane'; } { package Math::PlanePath::SquareArms; use constant _NumSeq_Coord_filling_type => 'plane'; use constant _NumSeq_Coord_MaxAbs_non_decreasing => 1; # successive squares } { package Math::PlanePath::DiamondArms; use constant _NumSeq_Coord_filling_type => 'plane'; } { package Math::PlanePath::SacksSpiral; use constant _NumSeq_Coord_X_integer => 0; use constant _NumSeq_Coord_Y_integer => 0; use constant _NumSeq_Coord_Radius_increasing => 1; # Radius==sqrt($i) use constant _NumSeq_Coord_RSquared_smaller => 0; # RSquared==$i use constant _NumSeq_Coord_RSquared_integer => 1; use constant _NumSeq_Coord_oeis_anum => { '' => { RSquared => 'A001477', # integers 0,1,2,3,etc # OEIS-Other: A001477 planepath=SacksSpiral coordinate_type=RSquared }, }; } { package Math::PlanePath::VogelFloret; use constant _NumSeq_Coord_X_integer => 0; use constant _NumSeq_Coord_Y_integer => 0; use constant _NumSeq_AbsDiff_min_is_infimum => 1; use constant _NumSeq_MinAbs_min_is_infimum => 1; use constant _NumSeq_MaxAbs_min_is_infimum => 1; sub _NumSeq_Coord_Radius_min { my ($self) = @_; # starting N=1 at R=radius_factor*sqrt(1), theta=something return $self->{'radius_factor'}; } sub _NumSeq_Coord_TRSquared_min { my ($self) = @_; # starting N=1 at R=radius_factor*sqrt(1), theta=something my ($x,$y) = $self->n_to_xy($self->n_start); return $x*$x + 3*$y*$y; } sub _NumSeq_Coord_Radius_func { my ($seq, $i) = @_; ### VogelFloret Radius: $i, $seq->{'planepath_object'} # R=radius_factor*sqrt($n) # avoid sin/cos in the main n_to_xy() my $path = $seq->{'planepath_object'}; my $rf = $path->{'radius_factor'}; # promote BigInt $i -> BigFloat so that sqrt() doesn't round, and in # case radius_factor is not an integer if (ref $i && $i->isa('Math::BigInt') && $rf != int($rf)) { require Math::BigFloat; $i = Math::BigFloat->new($i); } return sqrt($i) * $rf; } use constant _NumSeq_Coord_Radius_increasing => 1; # Radius==sqrt($i) use constant _NumSeq_Coord_RSquared_smaller => 0; # RSquared==$i } { package Math::PlanePath::TheodorusSpiral; use constant _NumSeq_Coord_X_integer => 0; use constant _NumSeq_Coord_Y_integer => 0; use constant _NumSeq_Coord_Radius_increasing => 1; # Radius==sqrt($i) use constant _NumSeq_Coord_RSquared_smaller => 0; # RSquared==$i use constant _NumSeq_Coord_RSquared_integer => 1; use constant _NumSeq_Coord_oeis_anum => { '' => { RSquared => 'A001477', # integers 0,1,2,3,etc # OEIS-Other: A001477 planepath=TheodorusSpiral coordinate_type=RSquared }, }; } { package Math::PlanePath::ArchimedeanChords; use constant _NumSeq_Coord_X_integer => 0; use constant _NumSeq_Coord_Y_integer => 0; use constant _NumSeq_Coord_Radius_increasing => 1; # spiralling outwards } { package Math::PlanePath::MultipleRings; #--------- # X sub _NumSeq_Coord_X_increasing { my ($self) = @_; # step==0 trivial on X axis return ($self->{'step'} == 0 ? 1 : 0); } sub _NumSeq_Coord_X_integer { my ($self) = @_; return ($self->{'step'} == 0); # step==0 trivial on X axis } #--------- # Y *_NumSeq_Coord_Y_integer = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_Y_non_decreasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_Sum_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_DiffXY_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_DiffXYdiv2_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_TRSquared_integer = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_Product_non_decreasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_Product_integer = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_BitAnd_non_decreasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_BitOr_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_BitXor_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_Min_non_decreasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_Max_increasing = \&_NumSeq_Coord_X_increasing; #--------- # SumAbs sub _NumSeq_Coord_SumAbs_non_decreasing { my ($self) = @_; # step==0 trivial on X axis # polygon step=4 same x+y in ring, others vary return ($self->{'step'} == 0 || ($self->{'ring_shape'} eq 'polygon' && $self->{'step'} == 4) ? 1 : 0); } *_NumSeq_Coord_SumAbs_increasing = \&_NumSeq_Coord_X_increasing; #--------- # Product sub _NumSeq_Coord_Product_max { my ($self) = @_; # step==0 trivial on X axis # polygon step=4 same x+y in ring, others vary return ($self->{'step'} == 0 ? 0 # step=0 always Y=0 so X*Y=0 : undef); } *_NumSeq_Coord_BitAnd_max = \&_NumSeq_Coord_Product_max; #--------- *_NumSeq_Coord_AbsDiff_increasing = \&_NumSeq_Coord_X_increasing; sub _NumSeq_AbsDiff_min_is_infimum { my ($self) = @_; # step multiple of 4 always falls on X=Y, otherwise approaches 0 only return ($self->{'ring_shape'} eq 'polygon' && $self->{'step'} % 4); } #--------- # RSquared sub _NumSeq_Coord_RSquared_smaller { my ($self) = @_; # step==0 on X axis RSquared is i^2, bigger than i. # step=1 is 0,1,1,4,4,4,9,9,9,9,16,16,16,16,16 etc k+1 repeats of k^2, # bigger than i from i=5 onwards return ($self->{'step'} <= 1 ? 0 : 1); } *_NumSeq_Coord_RSquared_integer = \&_NumSeq_Coord_Radius_integer; *_NumSeq_Coord_RSquared_non_decreasing = \&_NumSeq_Coord_Radius_non_decreasing; #--------- # Radius sub _NumSeq_Coord_Radius_integer { my ($self) = @_; # step==0 on X axis R=N # step==1 start X=0,Y=0, spaced 1 apart on X axis, same radius for others # step==6 start X=1,Y=0, spaced 1 apart return ($self->{'step'} <= 1 || $self->{'step'} == 6); } sub _NumSeq_Coord_Radius_non_decreasing { my ($self) = @_; # circle is non-decreasing, polygon varies return ! ($self->{'ring_shape'} eq 'polygon' && $self->{'step'} >= 3); } *_NumSeq_Coord_Radius_increasing = \&_NumSeq_Coord_X_increasing; #--------- # TRadius sub _NumSeq_Coord_TRadius_min { my ($self) = @_; return $self->_NumSeq_Coord_Radius_min; } sub _NumSeq_Coord_TRSquared_min { my ($self) = @_; return $self->rsquared_minimum; } sub _NumSeq_Coord_TRadius_non_decreasing { my ($self) = @_; # step==0 trivial on X axis return ($self->{'step'} == 0 ? 1 : 0); } *_NumSeq_Coord_TRadius_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_TRadius_integer = \&_NumSeq_Coord_X_increasing; #--------- # GCD *_NumSeq_Coord_GCD_integer = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_GCD_increasing = \&_NumSeq_Coord_X_increasing; #--------- # IntXY # step=0 X/0 so IntXY=X *_NumSeq_Coord_IntXY_increasing = \&_NumSeq_Coord_X_increasing; #--------- # FracXY sub _NumSeq_Coord_FracXY_max { my ($self) = @_; return ($self->{'step'} == 0 ? 0 : 1); } sub _NumSeq_FracXY_max_is_supremum { my ($self) = @_; return ($self->{'step'} == 0 ? 0 : 1); } *_NumSeq_Coord_FracXY_non_decreasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_FracXY_integer = \&_NumSeq_Coord_X_increasing; sub _NumSeq_ExperimentalParity_min_is_infimum { my ($self) = @_; return $self->{'ring_shape'} eq 'polygon'; } use constant _NumSeq_Coord_oeis_anum => { # MultipleRings step=0 is trivial X=N,Y=0 'step=0,ring_shape=circle' => { Y => 'A000004', # all-zeros Product => 'A000004', # all-zeros # OEIS-Other: A000004 planepath=MultipleRings,step=0 coordinate_type=Y # OEIS-Other: A000004 planepath=MultipleRings,step=0 coordinate_type=Product # OFFSET # X => 'A001477', # integers 0 upwards # Sum => 'A001477', # integers 0 upwards # AbsDiff => 'A001477', # integers 0 upwards # Radius => 'A001477', # integers 0 upwards # DiffXY => 'A001477', # integers 0 upwards # DiffYX => 'A001489', # negative integers 0 downwards # RSquared => 'A000290', # squares 0 upwards # # OEIS-Other: A001477 planepath=MultipleRings,step=0 coordinate_type=X # # OEIS-Other: A001477 planepath=MultipleRings,step=0 coordinate_type=Sum # # OEIS-Other: A001477 planepath=MultipleRings,step=0 coordinate_type=AbsDiff # # OEIS-Other: A001477 planepath=MultipleRings,step=0 coordinate_type=Radius # # OEIS-Other: A001477 planepath=MultipleRings,step=0 coordinate_type=DiffXY # # OEIS-Other: A001489 planepath=MultipleRings,step=0 coordinate_type=DiffYX # # OEIS-Other: A000290 planepath=MultipleRings,step=0 coordinate_type=RSquared }, }; } # { package Math::PlanePath::PixelRings; # } { package Math::PlanePath::FilledRings; use constant _NumSeq_Coord_filling_type => 'plane'; } { package Math::PlanePath::Hypot; use constant _NumSeq_Coord_filling_type => 'plane'; sub _NumSeq_Coord_TRSquared_min { $_[0]->rsquared_minimum } # in order of radius so monotonic, but always have 4x duplicates or more use constant _NumSeq_Coord_Radius_non_decreasing => 1; sub _NumSeq_Coord_ExperimentalHammingDist_min { my ($self) = @_; return ($self->{'points'} eq 'odd' ? 1 : 0); } *_NumSeq_Coord_ExperimentalParity_min = \&_NumSeq_Coord_ExperimentalHammingDist_min; *_NumSeq_Coord_MaxAbs_min = \&_NumSeq_Coord_ExperimentalHammingDist_min; sub _NumSeq_Coord_ExperimentalParity_max { my ($self) = @_; return ($self->{'points'} eq 'even' ? 0 : 1); } } { package Math::PlanePath::HypotOctant; use constant _NumSeq_Coord_IntXY_min => 1; # triangular X>=Y so X/Y >= 1 sub _NumSeq_Coord_TRSquared_min { $_[0]->rsquared_minimum } sub _NumSeq_Coord_BitXor_min { my ($self) = @_; # "odd" always has X!=Ymod2 so differ in low bit return ($self->{'points'} eq 'odd' ? 1 : 0); } # in order of radius so monotonic, but can have duplicates use constant _NumSeq_Coord_Radius_non_decreasing => 1; sub _NumSeq_Coord_ExperimentalHammingDist_min { my ($self) = @_; return ($self->{'points'} eq 'odd' ? 1 : 0); } sub _NumSeq_Coord_ExperimentalParity_min { my ($self) = @_; return ($self->{'points'} eq 'odd' ? 1 : 0); } sub _NumSeq_Coord_ExperimentalParity_max { my ($self) = @_; return ($self->{'points'} eq 'even' ? 0 : 1); } } { package Math::PlanePath::TriangularHypot; use constant _NumSeq_Coord_filling_type => 'triangular'; sub _NumSeq_Coord_TRSquared_min { my ($self) = @_; return ($self->{'points'} eq 'odd' ? 1 # odd at X=1,Y=0 : $self->{'points'} eq 'hex_centred' ? 4 # hex_centred at X=2,Y=0 or X=1,Y=1 : 0); # even,all at X=0,Y=0 } # in order of triangular radius so monotonic, but can have duplicates so # non-decreasing use constant _NumSeq_Coord_TRadius_non_decreasing => 1; sub _NumSeq_Coord_ExperimentalHammingDist_min { my ($self) = @_; return ($self->{'points'} eq 'odd' ? 1 : 0); # X!=Y when odd } *_NumSeq_Coord_ExperimentalParity_min = \&_NumSeq_Coord_ExperimentalHammingDist_min; sub _NumSeq_Coord_MaxAbs_min { my ($self) = @_; return ($self->{'points'} eq 'odd' || $self->{'points'} eq 'hex_centred' ? 1 : 0); } sub _NumSeq_Coord_ExperimentalParity_max { my ($self) = @_; return ($self->{'points'} eq 'odd' || $self->{'points'} eq 'all' ? 1 : 0); } } { package Math::PlanePath::PythagoreanTree; use constant _NumSeq_Coord_SubHeight_min => undef; use constant _NumSeq_Coord_ExperimentalLeafDistance_max => undef; { my %_NumSeq_Coord_IntXY_min = (AB => 0, # A>=1,B>=1 so int(A/B)>=0 AC => 0, # A 0, # B 1, # octant X>=Y+1 so X/Y>1 SM => 0, # A>=1,B>=1 so int(A/B)>=0 SC => 0, MC => 0, ); sub _NumSeq_Coord_IntXY_min { my ($self) = @_; return $_NumSeq_Coord_IntXY_min{$self->{'coordinates'}}; } } { my %_NumSeq_Coord_IntXY_max = (AC => 0, # A 0, # B 0, # X 0, # X 0, # X{'coordinates'}}; } } # P=2,Q=1 frac=0 # otherwise A,B,C have no common factor and >1 so frac!=0 sub _NumSeq_FracXY_min_is_infimum { my ($self) = @_; return $self->{'coordinates'} ne 'PQ'; } { my %_NumSeq_Coord_ExperimentalDenominator_min = (AB => 4, # at A=3,B=4 no common factor AC => 5, # at A=3,B=5 BC => 5, # at B=4,B=5 PQ => 1, # at P=2,Q=1 ); sub _NumSeq_Coord_ExperimentalDenominator_min { my ($self) = @_; return $_NumSeq_Coord_ExperimentalDenominator_min{$self->{'coordinates'}}; } } { my %_NumSeq_Coord_BitAnd_min = (AB => 0, # at X=3,Y=4 AC => 1, # at X=3,Y=5 BC => 0, # at X=8,Y=17 PQ => 0, # at X=2,Y=1 SM => 0, # at X=3,Y=4 SC => 0, # at X=3,Y=5 MC => 0, # at X=4,Y=5 ); sub _NumSeq_Coord_BitAnd_min { my ($self) = @_; return $_NumSeq_Coord_BitAnd_min{$self->{'coordinates'}}; } } { my %_NumSeq_Coord_BitOr_min = (AB => 7, # at A=3,B=4 AC => 7, # at A=3,C=5 BC => 5, # at B=4,C=5 PQ => 3, # at P=2,Q=1 SM => 7, # at X=3,Y=4 SC => 7, # at X=3,Y=5 MC => 5, # at X=4,Y=5 ); sub _NumSeq_Coord_BitOr_min { my ($self) = @_; return $_NumSeq_Coord_BitOr_min{$self->{'coordinates'}}; } } { my %_NumSeq_Coord_BitXor_min = (AB => 1, # at X=21,Y=20 AC => 6, # at X=3,Y=5 BC => 1, # at X=4,Y=5 PQ => 1, # at X=3,Y=2 SM => 1, # at X=3,Y=4 SC => 6, # at X=3,Y=5 MC => 1, # at X=4,Y=5 ); sub _NumSeq_Coord_BitXor_min { my ($self) = @_; return $_NumSeq_Coord_BitXor_min{$self->{'coordinates'}}; } } sub _NumSeq_Coord_Radius_integer { my ($self) = @_; return ($self->{'coordinates'} eq 'AB' # hypot || $self->{'coordinates'} eq 'SM'); # hypot } use constant _NumSeq_Coord_ExperimentalHammingDist_min => 1; # X!=Y { my %_NumSeq_Coord_ExperimentalParity_min = (PQ => 1, # one odd one even, so odd always AB => 1, # odd,even so odd always BA => 1, BC => 1, # even,odd so odd always ); sub _NumSeq_Coord_ExperimentalParity_min { my ($self) = @_; return $_NumSeq_Coord_ExperimentalParity_min{$self->{'coordinates'}} || 0; } } sub _NumSeq_Coord_ExperimentalParity_max { my ($self) = @_; return ($self->{'coordinates'} eq 'AC' ? 0 # odd,odd so even always : 1); } # Not quite right. # sub _NumSeq_Coord_pred_Radius { # my ($path, $value) = @_; # return ($value >= 0 # && ($path->{'coordinate_type'} ne 'AB' # || $value == int($value))); # } use constant _NumSeq_Coord_oeis_anum => { 'tree_type=UAD,coordinates=AB,digit_order=HtoL' => { X => 'A321768', Y => 'A321769', Radius => 'A321770', # hypotenuse # OEIS-Catalogue: A321768 planepath=PythagoreanTree coordinate_type=X # OEIS-Catalogue: A321769 planepath=PythagoreanTree coordinate_type=Y # OEIS-Other: A321770 planepath=PythagoreanTree coordinate_type=Radius }, 'tree_type=UAD,coordinates=AC,digit_order=HtoL' => { X => 'A321768', Y => 'A321770', Min => 'A321768', # min = A MinAbs => 'A321768', Max => 'A321770', # min = C MaxAbs => 'A321770', # OEIS-Other: A321768 planepath=PythagoreanTree,coordinates=AC coordinate_type=X # OEIS-Catalogue: A321770 planepath=PythagoreanTree,coordinates=AC coordinate_type=Y # OEIS-Other: A321768 planepath=PythagoreanTree,coordinates=AC coordinate_type=Min # OEIS-Other: A321770 planepath=PythagoreanTree,coordinates=AC coordinate_type=Max # OEIS-Other: A321768 planepath=PythagoreanTree,coordinates=AC coordinate_type=MinAbs # OEIS-Other: A321770 planepath=PythagoreanTree,coordinates=AC coordinate_type=MaxAbs }, 'tree_type=UAD,coordinates=BC,digit_order=HtoL' => { X => 'A321769', Y => 'A321770', Min => 'A321769', # min = B MinAbs => 'A321769', Max => 'A321770', # max = C MaxAbs => 'A321770', # OEIS-Other: A321769 planepath=PythagoreanTree,coordinates=BC coordinate_type=X # OEIS-Other: A321770 planepath=PythagoreanTree,coordinates=BC coordinate_type=Y # OEIS-Other: A321769 planepath=PythagoreanTree,coordinates=BC coordinate_type=Min # OEIS-Other: A321770 planepath=PythagoreanTree,coordinates=BC coordinate_type=Max # OEIS-Other: A321769 planepath=PythagoreanTree,coordinates=BC coordinate_type=MinAbs # OEIS-Other: A321770 planepath=PythagoreanTree,coordinates=BC coordinate_type=MaxAbs }, 'tree_type=UAD,coordinates=SM,digit_order=HtoL' => { Radius => 'A321770', # hypotenuse # OEIS-Other: A321770 planepath=PythagoreanTree,coordinates=SM coordinate_type=Radius }, 'tree_type=UAD,coordinates=SC,digit_order=HtoL' => { Y => 'A321770', Max => 'A321770', # max = C MaxAbs => 'A321770', # OEIS-Other: A321770 planepath=PythagoreanTree,coordinates=SC coordinate_type=Y # OEIS-Other: A321770 planepath=PythagoreanTree,coordinates=SC coordinate_type=Max # OEIS-Other: A321770 planepath=PythagoreanTree,coordinates=SC coordinate_type=MaxAbs }, 'tree_type=UAD,coordinates=MC,digit_order=HtoL' => { Y => 'A321770', Max => 'A321770', # max = C MaxAbs => 'A321770', # OEIS-Other: A321770 planepath=PythagoreanTree,coordinates=MC coordinate_type=Y # OEIS-Other: A321770 planepath=PythagoreanTree,coordinates=MC coordinate_type=Max # OEIS-Other: A321770 planepath=PythagoreanTree,coordinates=MC coordinate_type=MaxAbs }, 'tree_type=UAD,coordinates=PQ,digit_order=HtoL' => { X => 'A321782', Y => 'A321783', Min => 'A321783', # min = Y MinAbs => 'A321783', Max => 'A321782', # min = X MaxAbs => 'A321782', Sum => 'A321784', SumAbs => 'A321784', DiffXY => 'A321785', AbsDiff => 'A321785', RSquared => 'A321770', # p^2 + q^2 = C # OEIS-Catalogue: A321782 planepath=PythagoreanTree,coordinates=PQ coordinate_type=X # OEIS-Catalogue: A321783 planepath=PythagoreanTree,coordinates=PQ coordinate_type=Y # OEIS-Other: A321783 planepath=PythagoreanTree,coordinates=PQ coordinate_type=Min # OEIS-Other: A321783 planepath=PythagoreanTree,coordinates=PQ coordinate_type=MinAbs # OEIS-Other: A321782 planepath=PythagoreanTree,coordinates=PQ coordinate_type=Max # OEIS-Other: A321782 planepath=PythagoreanTree,coordinates=PQ coordinate_type=MaxAbs # OEIS-Catalogue: A321784 planepath=PythagoreanTree,coordinates=PQ coordinate_type=Sum # OEIS-Other: A321784 planepath=PythagoreanTree,coordinates=PQ coordinate_type=SumAbs # OEIS-Catalogue: A321785 planepath=PythagoreanTree,coordinates=PQ coordinate_type=DiffXY # OEIS-Other: A321785 planepath=PythagoreanTree,coordinates=PQ coordinate_type=AbsDiff # OEIS-Other: A321770 planepath=PythagoreanTree,coordinates=PQ coordinate_type=RSquared # No, 2*p*q = B, not just p*q # Product => 'A321769', # 2*p*q = B # # OEIS-Other: A321769 planepath=PythagoreanTree,coordinates=PQ coordinate_type=Product }, 'tree_type=UAD,coordinates=PQ,digit_order=LtoH' => { X => 'A321782', # P is same in HtoL and LtoH Max => 'A321782', # min = X MaxAbs => 'A321782', # OEIS-Other: A321782 planepath=PythagoreanTree,coordinates=PQ,digit_order=LtoH coordinate_type=X # OEIS-Other: A321782 planepath=PythagoreanTree,coordinates=PQ,digit_order=LtoH coordinate_type=Max # OEIS-Other: A321782 planepath=PythagoreanTree,coordinates=PQ,digit_order=LtoH coordinate_type=MaxAbs }, }; } { package Math::PlanePath::RationalsTree; use constant _NumSeq_Coord_BitAnd_min => 0; # X=1,Y=2 use constant _NumSeq_Coord_BitXor_min => 0; # X=1,Y=1 use constant _NumSeq_Coord_SubHeight_min => undef; use constant _NumSeq_Coord_ExperimentalLeafDistance_max => undef; use constant _NumSeq_Coord_oeis_anum => { 'tree_type=SB' => { IntXY => 'A153036', Depth => 'A000523', # floor(log2(n)) starting OFFSET=1 # OEIS-Catalogue: A153036 planepath=RationalsTree coordinate_type=IntXY # OEIS-Catalogue: A000523 planepath=RationalsTree coordinate_type=Depth # Not quite, OFFSET n=0 cf N=1 here # Y => 'A047679', # SB denominator # # OEIS-Catalogue: A047679 planepath=RationalsTree coordinate_type=Y # # X => 'A007305', # SB numerators but starting extra 0,1 # Sum => 'A007306', # Farey/SB denominators, but starting extra 1,1 # Product => 'A119272', # num*den, but starting extra 1,1 # cf A054424 permutation }, 'tree_type=CW' => { # A070871 Stern adjacent S(n)*S(n+1), or Conway's alimentary function, # cf A070872 where S(n)*S(n+1) = n # A070873 where S(n)*S(n+1) > n # A070874 where S(n)*S(n+1) < n Product => 'A070871', Depth => 'A000523', # floor(log2(n)) starting OFFSET=1 # OEIS-Catalogue: A070871 planepath=RationalsTree,tree_type=CW coordinate_type=Product # OEIS-Other: A000523 planepath=RationalsTree,tree_type=CW coordinate_type=Depth # Not quite, A007814 has extra initial 0, OFFSET=0 "0,1,0,2,0" # whereas path CW IntXY starts N=1 "1,0,2,0,1" # IntXY => 'A007814', # countlow1bits(N) # # OEIS-Other: A007814 planepath=RationalsTree,tree_type=CW coordinate_type=IntXY # Not quite, CW X and Y is Stern diatomic A002487, but RationalsTree # starts N=0 X=1,1,2 or Y=1,2 rather than from 0 # Not quite, CW DiffYX is A070990 stern diatomic first diffs, but # path starts N=1 diff="0,1,-1,2,-1,1,-2", whereas A070990 starts n=0 # "1,-1,2,-1,1,-2" one less term and would be n_start=-1 }, 'tree_type=AYT' => { X => 'A020650', # AYT numerator Y => 'A020651', # AYT denominator Sum => 'A086592', # Kepler's tree denominators SumAbs => 'A086592', # Kepler's tree denominators Depth => 'A000523', # floor(log2(n)) starting OFFSET=1 IntXY => 'A135523', # OEIS-Catalogue: A020650 planepath=RationalsTree,tree_type=AYT coordinate_type=X # OEIS-Catalogue: A020651 planepath=RationalsTree,tree_type=AYT coordinate_type=Y # OEIS-Other: A086592 planepath=RationalsTree,tree_type=AYT coordinate_type=Sum # OEIS-Other: A086592 planepath=RationalsTree,tree_type=AYT coordinate_type=SumAbs # OEIS-Other: A000523 planepath=RationalsTree,tree_type=AYT coordinate_type=Depth # OEIS-Catalogue: A135523 planepath=RationalsTree,tree_type=AYT coordinate_type=IntXY # Not quite, DiffYX almost A070990 Stern diatomic first differences, # but we have an extra 0 at the start, and we start i=1 rather than # n=0 too }, 'tree_type=HCS' => { Depth => 'A000523', # floor(log2(n)) starting OFFSET=1 # OEIS-Other: A000523 planepath=RationalsTree,tree_type=HCS coordinate_type=Depth # # Not quite, OFFSET=0 value=1/1 corresponding to N=0 X=0/Y=1 here # Sum => 'A071585', # rats>=1 is HCS num+den # Y => 'A071766', # rats>=1 HCS denominator # # OEIS-Catalogue: A071585 planepath=RationalsTree,tree_type=HCS coordinate_type=X # # OEIS-Catalogue: A071766 planepath=RationalsTree,tree_type=HCS coordinate_type=Y }, 'tree_type=Bird' => { X => 'A162909', # Bird tree numerators Y => 'A162910', # Bird tree denominators Depth => 'A000523', # floor(log2(n)) starting OFFSET=1 # OEIS-Catalogue: A162909 planepath=RationalsTree,tree_type=Bird coordinate_type=X # OEIS-Catalogue: A162910 planepath=RationalsTree,tree_type=Bird coordinate_type=Y # OEIS-Other: A000523 planepath=RationalsTree,tree_type=Bird coordinate_type=Depth }, 'tree_type=Drib' => { X => 'A162911', # Drib tree numerators Y => 'A162912', # Drib tree denominators Depth => 'A000523', # floor(log2(n)) starting OFFSET=1 # OEIS-Catalogue: A162911 planepath=RationalsTree,tree_type=Drib coordinate_type=X # OEIS-Catalogue: A162912 planepath=RationalsTree,tree_type=Drib coordinate_type=Y # OEIS-Other: A000523 planepath=RationalsTree,tree_type=Drib coordinate_type=Depth }, 'tree_type=L' => { X => 'A174981', # numerator # OEIS-Catalogue: A174981 planepath=RationalsTree,tree_type=L coordinate_type=X # # Not quite, A002487 extra initial, so n=2 is denominator at N=0 # Y => 'A002487', # denominator, stern diatomic # # OEIS-Catalogue: A071585 planepath=RationalsTree,tree_type=HCS coordinate_type=Y # Not quite, A000523 is OFFSET=1 path starts N=0 # Depth => 'A000523', # floor(log2(n)) starting OFFSET=1 }, }; } { package Math::PlanePath::FractionsTree; use constant _NumSeq_Coord_IntXY_max => 0; # X/Y<1 always use constant _NumSeq_Coord_IntXY_non_decreasing => 1; use constant _NumSeq_FracXY_min_is_infimum => 1; # no common factor use constant _NumSeq_Coord_BitXor_min => 1; # X=2,Y=3 use constant _NumSeq_Coord_SubHeight_min => undef; use constant _NumSeq_Coord_ExperimentalLeafDistance_max => undef; use constant _NumSeq_Coord_ExperimentalHammingDist_min => 1; # X!=Y use constant _NumSeq_Coord_oeis_anum => { 'tree_type=Kepler' => { X => 'A020651', # numerators, same as AYT denominators Y => 'A086592', # Kepler half-tree denominators DiffYX => 'A020650', # AYT numerators AbsDiff => 'A020650', # AYT numerators Depth => 'A000523', # floor(log2(n)) starting OFFSET=1 # OEIS-Other: A020651 planepath=FractionsTree coordinate_type=X # OEIS-Catalogue: A086592 planepath=FractionsTree coordinate_type=Y # OEIS-Other: A020650 planepath=FractionsTree coordinate_type=DiffYX # OEIS-Other: A020650 planepath=FractionsTree coordinate_type=AbsDiff # OEIS-Other: A000523 planepath=FractionsTree coordinate_type=Depth # Not quite, Sum is from 1/2 value=3 skipping the initial value=2 in # A086593 which would be 1/1. Also is every second denominator, but # again no initial value=2. # Sum => 'A086593', # Y_odd => 'A086593', # at N=1,3,5,etc }, }; } { package Math::PlanePath::CfracDigits; use constant _NumSeq_Coord_IntXY_max => 0; # upper octant 0 < X/Y < 1 use constant _NumSeq_Coord_IntXY_non_decreasing => 1; use constant _NumSeq_FracXY_min_is_infimum => 1; # X,Y no common factor # X=Y doesn't occur so X,Y always differ by at least 1 bit. The smallest # two differing by 1 bit are X=1,Y=2. use constant _NumSeq_Coord_BitOr_min => 3; # X=1,Y=2 use constant _NumSeq_Coord_BitXor_min => 1; # X=2,Y=3 use constant _NumSeq_Coord_ExperimentalHammingDist_min => 1; # X!=Y # use constant _NumSeq_Coord_oeis_anum => # { 'radix=2' => # { # }, # }; } { package Math::PlanePath::ChanTree; sub _NumSeq_Coord_Max_min { my ($self) = @_; # except in k=2 Calkin-Wilf, point X=1,Y=1 doesn't occur, only X=1,Y=2 # or X=2,Y=1 return ($self->{'k'} == 2 ? 1 : 2); } *_NumSeq_Coord_MaxAbs_min = \&_NumSeq_Coord_Max_min; use constant _NumSeq_Coord_SubHeight_min => undef; use constant _NumSeq_Coord_ExperimentalLeafDistance_max => undef; sub _NumSeq_Coord_Product_min { my ($self) = @_; return ($self->{'reduced'} || $self->{'k'} == 2 ? 1 # X=1,Y=1 reduced or k=2 X=1,Y=1 : 2); # X=1,Y=2 } sub _NumSeq_Coord_TRSquared_min { my ($self) = @_; return ($self->{'k'} == 2 || ($self->{'reduced'} && ($self->{'k'} & 1) == 0) ? 4 # X=1,Y=1 reduced k even, or k=2 top 1/1 : 7); # X=2,Y=1 } use constant _NumSeq_Coord_BitAnd_min => 0; # X=1,Y=2 sub _NumSeq_Coord_BitOr_min { my ($self) = @_; return ($self->{'k'} == 2 || $self->{'reduced'} ? 1 # X=1,Y=1 : $self->{'k'} & 1 ? 3 # k odd X=1,Y=2 : 2); # k even X=2,Y=2 } sub _NumSeq_Coord_BitXor_min { my ($self) = @_; return ($self->{'k'} == 2 || $self->{'reduced'} ? 0 # X=1,Y=1 : $self->{'k'} & 1 ? 1 # k odd X=2,Y=3 : 0); # k even X=2,Y=2 } sub _NumSeq_Coord_ExperimentalParity_min { my ($self) = @_; return ($self->{'k'} % 2 ? 1 # k odd has one odd, one even, so odd : 0); } # X!=Y when k odd *_NumSeq_Coord_ExperimentalHammingDist_min = \&_NumSeq_Coord_ExperimentalParity_min; use constant _NumSeq_Coord_oeis_anum => { do { # k=2 same as CW my $cw = { Product => 'A070871', Depth => 'A000523', # floor(log2(n)) starting OFFSET=1 # OEIS-Other: A070871 planepath=ChanTree,k=2,n_start=1 coordinate_type=Product # OEIS-Other: A000523 planepath=ChanTree,k=2,n_start=1 coordinate_type=Depth }; ( 'k=2,n_start=1' => $cw, # 'k=2,reduced=0,points=even,n_start=1' => $cw, # 'k=2,reduced=1,points=even,n_start=1' => $cw, # 'k=2,reduced=0,points=all,n_start=1' => $cw, # 'k=2,reduced=1,points=all,n_start=1' => $cw, ), }, # 'k=3,reduced=0,points=even,n_start=0' => 'k=3,n_start=0' => { X => 'A191379', # OEIS-Catalogue: A191379 planepath=ChanTree }, }; } { package Math::PlanePath::PeanoCurve; use constant _NumSeq_Coord_filling_type => 'quadrant'; use constant _NumSeq_Coord_oeis_anum => { # Same in GrayCode and WunderlichSerpentine 'radix=3' => { X => 'A163528', Y => 'A163529', Sum => 'A163530', SumAbs => 'A163530', RSquared => 'A163531', # OEIS-Catalogue: A163528 planepath=PeanoCurve coordinate_type=X # OEIS-Catalogue: A163529 planepath=PeanoCurve coordinate_type=Y # OEIS-Catalogue: A163530 planepath=PeanoCurve coordinate_type=Sum # OEIS-Other: A163530 planepath=PeanoCurve coordinate_type=SumAbs # OEIS-Catalogue: A163531 planepath=PeanoCurve coordinate_type=RSquared }, }; } { package Math::PlanePath::PeanoDiagonals; use constant _NumSeq_Coord_filling_type => 'quadrant'; } { package Math::PlanePath::WunderlichSerpentine; use constant _NumSeq_Coord_filling_type => 'quadrant'; use constant _NumSeq_Coord_oeis_anum => { do { my $peano = { X => 'A163528', Y => 'A163529', Sum => 'A163530', SumAbs => 'A163530', RSquared => 'A163531', }; # OEIS-Other: A163528 planepath=WunderlichSerpentine,serpentine_type=Peano,radix=3 coordinate_type=X # OEIS-Other: A163529 planepath=WunderlichSerpentine,serpentine_type=Peano,radix=3 coordinate_type=Y # OEIS-Other: A163530 planepath=WunderlichSerpentine,serpentine_type=Peano,radix=3 coordinate_type=Sum # OEIS-Other: A163530 planepath=WunderlichSerpentine,serpentine_type=Peano,radix=3 coordinate_type=SumAbs # OEIS-Other: A163531 planepath=WunderlichSerpentine,serpentine_type=Peano,radix=3 coordinate_type=RSquared # ENHANCE-ME: with serpentine_type by bits too ('serpentine_type=Peano,radix=3' => $peano, ) }, }; } { package Math::PlanePath::HilbertCurve; use constant _NumSeq_Coord_filling_type => 'quadrant'; use constant _NumSeq_Coord_oeis_anum => { '' => { X => 'A059253', Y => 'A059252', Sum => 'A059261', SumAbs => 'A059261', DiffXY => 'A059285', RSquared => 'A163547', BitXor => 'A059905', # alternate bits first (ZOrderCurve X) ExperimentalHammingDist => 'A139351', # count 1-bits at even bit positions # OEIS-Catalogue: A059253 planepath=HilbertCurve coordinate_type=X # OEIS-Catalogue: A059252 planepath=HilbertCurve coordinate_type=Y # OEIS-Catalogue: A059261 planepath=HilbertCurve coordinate_type=Sum # OEIS-Other: A059261 planepath=HilbertCurve coordinate_type=SumAbs # OEIS-Catalogue: A059285 planepath=HilbertCurve coordinate_type=DiffXY # OEIS-Catalogue: A163547 planepath=HilbertCurve coordinate_type=RSquared # OEIS-Other: A059905 planepath=HilbertCurve coordinate_type=BitXor # OEIS-Other: A139351 planepath=HilbertCurve coordinate_type=ExperimentalHammingDist }, }; } { package Math::PlanePath::HilbertSides; use constant _NumSeq_Coord_filling_type => 'quadrant'; use constant _NumSeq_Coord_oeis_anum => { '' => { # 1 -1 # / / # 0 3 0 X-Y = 0,1,0,-1 # / | / # 3---2 -1 2 1 # | / | / # 0---1 0---1 DiffXY => 'A059285', # OEIS-Other: A059285 planepath=HilbertSides coordinate_type=DiffXY }, }; } { package Math::PlanePath::HilbertSpiral; use constant _NumSeq_Coord_filling_type => 'plane'; use constant _NumSeq_Coord_oeis_anum => { '' => { # HilbertSpiral going negative is mirror on X=-Y line, which is # (-Y,-X), so DiffXY = -Y-(-X) = X-Y same diff as plain HilbertCurve. DiffXY => 'A059285', # OEIS-Other: A059285 planepath=HilbertSpiral coordinate_type=DiffXY }, }; } { package Math::PlanePath::ZOrderCurve; use constant _NumSeq_Coord_filling_type => 'quadrant'; use constant _NumSeq_Coord_oeis_anum => { 'radix=2' => { X => 'A059905', # alternate bits first Y => 'A059906', # alternate bits second BitAnd => 'A292373', BitXor => 'A309952', # OEIS-Catalogue: A059905 planepath=ZOrderCurve coordinate_type=X # OEIS-Catalogue: A059906 planepath=ZOrderCurve coordinate_type=Y # OEIS-Catalogue: A292373 planepath=ZOrderCurve coordinate_type=BitAnd # OEIS-Catalogue: A309952 planepath=ZOrderCurve coordinate_type=BitXor }, 'radix=3' => { X => 'A163325', # alternate ternary digits first Y => 'A163326', # alternate ternary digits second # OEIS-Catalogue: A163325 planepath=ZOrderCurve,radix=3 coordinate_type=X # OEIS-Catalogue: A163326 planepath=ZOrderCurve,radix=3 coordinate_type=Y }, 'radix=10' => { Sum => 'A080463', SumAbs => 'A080463', # OEIS-Catalogue: A080463 planepath=ZOrderCurve,radix=10 coordinate_type=Sum # OEIS-Other: A080463 planepath=ZOrderCurve,radix=10 coordinate_type=SumAbs }, 'radix=10,i_start=10' => { # i_start=10 per A080464 OFFSET=10, it skips all but one initial zeros AbsDiff => 'A080465', Product => 'A080464', # OEIS-Catalogue: A080464 planepath=ZOrderCurve,radix=10 coordinate_type=Product i_start=10 # OEIS-Catalogue: A080465 planepath=ZOrderCurve,radix=10 coordinate_type=AbsDiff i_start=10 }, }; } { package Math::PlanePath::GrayCode; use constant _NumSeq_Coord_filling_type => 'quadrant'; use constant _NumSeq_Coord_oeis_anum => { do { my $peano = { X => 'A163528', Y => 'A163529', Sum => 'A163530', SumAbs => 'A163530', RSquared => 'A163531', }; my $z = { BitXor => 'A059905', }; my $fst = { X => 'A309952', # ZOrder too }; ('apply_type=TsF,gray_type=reflected,radix=3' => $peano, 'apply_type=FsT,gray_type=reflected,radix=3' => $peano, # OEIS-Other: A163528 planepath=GrayCode,apply_type=TsF,radix=3 coordinate_type=X # OEIS-Other: A163529 planepath=GrayCode,apply_type=TsF,radix=3 coordinate_type=Y # OEIS-Other: A163530 planepath=GrayCode,apply_type=TsF,radix=3 coordinate_type=Sum # OEIS-Other: A163530 planepath=GrayCode,apply_type=TsF,radix=3 coordinate_type=SumAbs # OEIS-Other: A163531 planepath=GrayCode,apply_type=TsF,radix=3 coordinate_type=RSquared # OEIS-Other: A163528 planepath=GrayCode,apply_type=FsT,radix=3 coordinate_type=X # OEIS-Other: A163529 planepath=GrayCode,apply_type=FsT,radix=3 coordinate_type=Y # OEIS-Other: A163530 planepath=GrayCode,apply_type=FsT,radix=3 coordinate_type=Sum # OEIS-Other: A163530 planepath=GrayCode,apply_type=FsT,radix=3 coordinate_type=SumAbs # OEIS-Other: A163531 planepath=GrayCode,apply_type=FsT,radix=3 coordinate_type=RSquared # TsF = Fs when radix=2 'apply_type=TsF,gray_type=reflected,radix=2' => $z, 'apply_type=TsF,gray_type=modular,radix=2' => $z, 'apply_type=Fs,gray_type=reflected,radix=2' => $z, 'apply_type=Fs,gray_type=modular,radix=2' => $z, # OEIS-Other: A059905 planepath=GrayCode,apply_type=TsF coordinate_type=BitXor # OEIS-Other: A059905 planepath=GrayCode,apply_type=TsF,gray_type=modular coordinate_type=BitXor # OEIS-Other: A059905 planepath=GrayCode,apply_type=Fs coordinate_type=BitXor # OEIS-Other: A059905 planepath=GrayCode,apply_type=Fs,gray_type=modular coordinate_type=BitXor # FsT = Ts when radix=2 'apply_type=FsT,gray_type=reflected,radix=2' => $fst, 'apply_type=FsT,gray_type=modular,radix=2' => $fst, 'apply_type=Ts,gray_type=reflected,radix=2' => $fst, 'apply_type=Ts,gray_type=modular,radix=2' => $fst, # OEIS-Other: A309952 planepath=GrayCode,apply_type=Ts coordinate_type=X # OEIS-Other: A309952 planepath=GrayCode,apply_type=Ts,gray_type=modular coordinate_type=X # OEIS-Other: A309952 planepath=GrayCode,apply_type=Ts coordinate_type=X # OEIS-Other: A309952 planepath=GrayCode,apply_type=Ts,gray_type=modular coordinate_type=X ), }, }; } { package Math::PlanePath::ImaginaryBase; use constant _NumSeq_Coord_filling_type => 'plane'; } { package Math::PlanePath::ImaginaryHalf; use constant _NumSeq_Coord_filling_type => 'half'; } { package Math::PlanePath::CubicBase; use constant _NumSeq_Coord_filling_type => 'triangular'; use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always } { package Math::PlanePath::Flowsnake; use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always use constant _NumSeq_Coord_oeis_anum => { 'arms=1' => {Y => 'A334486', # OEIS-Catalogue: A334486 planepath=Flowsnake coordinate_type=Y }, }; } { package Math::PlanePath::FlowsnakeCentres; use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always } { package Math::PlanePath::GosperIslands; use constant _NumSeq_Coord_TRSquared_min => 4; # minimum X=1,Y=1 use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always } { package Math::PlanePath::GosperReplicate; use constant _NumSeq_Coord_filling_type => 'triangular'; use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always } { package Math::PlanePath::GosperSide; use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always } { package Math::PlanePath::KochCurve; use constant _NumSeq_Coord_IntXY_min => 3; # at X=3,Y=1 among the Y>0 points use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always } { package Math::PlanePath::KochPeaks; use constant _NumSeq_Coord_MaxAbs_min => 1; # odd always use constant _NumSeq_Coord_TRSquared_min => 1; # minimum X=1,Y=0 use constant _NumSeq_Coord_ExperimentalParity_min => 1; # odd always use constant _NumSeq_Coord_ExperimentalHammingDist_min => 1; # X!=Y } { package Math::PlanePath::KochSnowflakes; use constant _NumSeq_Coord_Y_integer => 0; use constant _NumSeq_Coord_BitAnd_integer => 1; # only Y non-integer use constant _NumSeq_Coord_TRSquared_min => 3*4/9; # minimum X=0,Y=2/3 use constant _NumSeq_Coord_MaxAbs_min => 2/3; # at N=3 use constant _NumSeq_Coord_TRadius_min => sqrt(_NumSeq_Coord_TRSquared_min); use constant _NumSeq_Coord_GCD_integer => 0; } { package Math::PlanePath::KochSquareflakes; use constant _NumSeq_Coord_X_integer => 0; use constant _NumSeq_Coord_Y_integer => 0; use constant _NumSeq_Coord_MaxAbs_min => 1/2; # at N=1 use constant _NumSeq_Coord_Sum_integer => 1; use constant _NumSeq_Coord_SumAbs_integer => 1; use constant _NumSeq_Coord_DiffXY_integer => 1; use constant _NumSeq_Coord_DiffYX_integer => 1; use constant _NumSeq_Coord_AbsDiff_integer => 1; use constant _NumSeq_Coord_BitXor_integer => 1; # 0.5 xor 0.5 cancels out use constant _NumSeq_Coord_TRSquared_min => 1; # X=1/2, Y=1/2 use constant _NumSeq_Coord_TRSquared_integer => 1; use constant _NumSeq_Coord_GCD_integer => 0; # GCD(1/2,1/2)=1/2 } { package Math::PlanePath::QuadricCurve; use constant _NumSeq_Coord_IntXY_min => undef; # negatives use constant _NumSeq_Coord_oeis_anum => { '' => {X => 'A332246', Y => 'A332247', # OEIS-Catalogue: A332246 planepath=QuadricCurve coordinate_type=X # OEIS-Catalogue: A332247 planepath=QuadricCurve coordinate_type=Y }, }; } { package Math::PlanePath::QuadricIslands; use constant _NumSeq_Coord_X_integer => 0; use constant _NumSeq_Coord_Y_integer => 0; use constant _NumSeq_Coord_Sum_integer => 1; # 0.5 + 0.5 = integer use constant _NumSeq_Coord_SumAbs_integer => 1; use constant _NumSeq_Coord_DiffXY_integer => 1; use constant _NumSeq_Coord_DiffYX_integer => 1; use constant _NumSeq_Coord_AbsDiff_integer => 1; use constant _NumSeq_Coord_GCD_integer => 0; # GCD(1/2,1/2)=1/2 # BitXor X=1/2=0.1 Y=-1/2=-0.1=...1111.0 BitXor=0 use constant _NumSeq_Coord_BitXor_integer => 1; # TRSquared on X=1/2,Y=1/2 is TR^2 = (1/2)^2+3*(1/2)^2 = 1 use constant _NumSeq_Coord_TRSquared_integer => 1; use constant _NumSeq_Coord_TRSquared_min => 1; # X=1/2,Y=1/2 } { package Math::PlanePath::SierpinskiTriangle; sub _NumSeq_Coord_Y_non_decreasing { my ($self) = @_; return ($self->{'align'} ne 'diagonal'); # rows upwards, except diagonal } # Max==Y for align!=diagonal *_NumSeq_Coord_Max_non_decreasing = \&_NumSeq_Coord_Y_non_decreasing; *_NumSeq_Coord_MaxAbs_non_decreasing = \&_NumSeq_Coord_Y_non_decreasing; sub _NumSeq_Coord_Sum_non_decreasing { my ($self) = @_; return ($self->{'align'} eq 'diagonal'); # anti-diagonals } *_NumSeq_Coord_SumAbs_non_decreasing = \&_NumSeq_Coord_Sum_non_decreasing; use constant _NumSeq_Coord_IntXY_min => -1; # wedge # align=diagonal has X,Y no 1-bits in common, so BitAnd==0 sub _NumSeq_Coord_BitAnd_max { my ($self) = @_; return ($self->{'align'} eq 'diagonal' ? 0 : undef); } sub _NumSeq_Coord_BitAnd_non_decreasing { my ($self) = @_; return ($self->{'align'} eq 'diagonal'); } # align=right,diagonal has X,Y BitOr 1-bits accumulating ... sub _NumSeq_Coord_BitOr_non_decreasing { my ($self) = @_; return ($self->{'align'} eq 'right' || $self->{'align'} eq 'diagonal'); } # align=diagonal has X,Y no bits in common so is same as BitOr 1-bits # accumulating ... sub _NumSeq_Coord_BitXor_non_decreasing { my ($self) = @_; return ($self->{'align'} eq 'diagonal'); } sub _NumSeq_Coord_ExperimentalParity_max { my ($self) = @_; return ($self->{'align'} eq 'triangular' ? 0 # triangular always even points : 1); } use constant _NumSeq_Coord_ExperimentalLeafDistance_max => 4; } { package Math::PlanePath::SierpinskiArrowhead; use constant _NumSeq_Coord_IntXY_min => -1; # wedge *_NumSeq_Coord_ExperimentalParity_max = \&Math::PlanePath::SierpinskiTriangle::_NumSeq_Coord_ExperimentalParity_max; use constant _NumSeq_Coord_oeis_anum => { 'align=diagonal' => { X => 'A334483', Y => 'A334484', # OEIS-Catalogue: A334483 planepath=SierpinskiArrowhead,align=diagonal coordinate_type=X # OEIS-Catalogue: A334484 planepath=SierpinskiArrowhead,align=diagonal coordinate_type=Y }, 'align=left' => { Sum => 'A334483', MinAbs => 'A334484', # OEIS-Other: A334483 planepath=SierpinskiArrowhead,align=left coordinate_type=Sum # OEIS-Other: A334484 planepath=SierpinskiArrowhead,align=left coordinate_type=MinAbs }, 'align=right' => { X => 'A334483', Min => 'A334483', MinAbs => 'A334483', DiffYX => 'A334484', AbsDiff => 'A334484', # OEIS-Other: A334483 planepath=SierpinskiArrowhead,align=right coordinate_type=X # OEIS-Other: A334483 planepath=SierpinskiArrowhead,align=right coordinate_type=Min # OEIS-Other: A334483 planepath=SierpinskiArrowhead,align=right coordinate_type=MinAbs # OEIS-Other: A334484 planepath=SierpinskiArrowhead,align=right coordinate_type=DiffYX # OEIS-Other: A334484 planepath=SierpinskiArrowhead,align=right coordinate_type=AbsDiff }, }; } { package Math::PlanePath::SierpinskiArrowheadCentres; use constant _NumSeq_Coord_IntXY_min => -1; # wedge *_NumSeq_Coord_BitAnd_max = \&Math::PlanePath::SierpinskiTriangle::_NumSeq_Coord_BitAnd_max; *_NumSeq_Coord_BitAnd_non_decreasing = \&Math::PlanePath::SierpinskiTriangle::_NumSeq_Coord_BitAnd_non_decreasing; *_NumSeq_Coord_ExperimentalParity_max = \&Math::PlanePath::SierpinskiTriangle::_NumSeq_Coord_ExperimentalParity_max; } { package Math::PlanePath::SierpinskiCurve; { my @Max_min = (undef, 1, # 1 arm, octant X>Y and X>=1 1, # 2 arms, quadrant X>=1 or Y>=1 1, # 3 arms 0, # 4 arms # more than 3 arm, Max goes negative unbounded ); sub _NumSeq_Coord_Max_min { my ($self) = @_; return $Max_min[$self->arms_count]; } } { my @IntXY_min = (undef, 1, # octant X>Y so X/Y>1 0, # quadrant X>=0 so X/Y>=0 -1, # 3-oct X>=-Y so X/Y>=-1 ); # arms>=4 has X unbounded negative sub _NumSeq_Coord_IntXY_min { my ($self) = @_; return $IntXY_min[$self->arms_count]; } } use constant _NumSeq_Coord_TRSquared_min => 1; # minimum X=1,Y=0 sub _NumSeq_Coord_BitOr_min { my ($self) = @_; return ($self->arms_count <= 2 ? 1 # X=0,Y=0 not visited BitOr(X,Y)>=1 : undef); # going X negative } sub _NumSeq_Coord_BitXor_min { my ($self) = @_; return ($self->arms_count <= 2 ? 1 # X!=Y so BitXor(X,Y)>=1 : undef); # going X negative } } { package Math::PlanePath::SierpinskiCurveStair; use constant _NumSeq_Coord_Max_min => 1; *_NumSeq_Coord_IntXY_min = \&Math::PlanePath::SierpinskiCurve::_NumSeq_Coord_IntXY_min; *_NumSeq_Coord_BitOr_min = \&Math::PlanePath::SierpinskiCurve::_NumSeq_Coord_BitOr_min; *_NumSeq_Coord_BitXor_min = \&Math::PlanePath::SierpinskiCurve::_NumSeq_Coord_BitXor_min; use constant _NumSeq_Coord_TRSquared_min => 1; # minimum X=1,Y=0 } { package Math::PlanePath::HIndexing; use constant _NumSeq_Coord_filling_type => 'quadrant'; # # except 0/0=inf # # use constant _NumSeq_Coord_IntXY_max => 1; # upper octant X<=Y so X/Y<=1 use constant _NumSeq_Coord_oeis_anum => { '' => { X => 'A334235', Y => 'A334236', # OEIS-Catalogue: A334235 planepath=HIndexing coordinate_type=X # OEIS-Catalogue: A334236 planepath=HIndexing coordinate_type=Y }, }; } { package Math::PlanePath::DragonCurve; use constant _NumSeq_Coord_n_list_max => 2; use constant _NumSeq_Coord_oeis_anum => { 'arms=1' => {X => 'A332383', Y => 'A332384', # OEIS-Catalogue: A332383 planepath=DragonCurve coordinate_type=X # OEIS-Catalogue: A332384 planepath=DragonCurve coordinate_type=Y }, }; # 4-arm plane filling if full grid sub _NumSeq_Coord_ExperimentalNeighbours4_min { my ($self) = @_; return $self->arms_count == 4 ? 4 : 2; } # use constant _NumSeq_Coord_ExperimentalNeighbours6_min => 0; # ??? sub _NumSeq_Coord_ExperimentalNeighbours8_min { my ($self) = @_; return $self->arms_count == 4 ? 8 : 3; } } { package Math::PlanePath::DragonRounded; use constant _NumSeq_Coord_TRSquared_min => 1; # minimum X=1,Y=0 use constant _NumSeq_Coord_ExperimentalHammingDist_min => 1; # X!=Y use constant _NumSeq_Coord_MaxAbs_min => 1; # X!=Y } # { package Math::PlanePath::DragonMidpoint; # } { package Math::PlanePath::AlternatePaper; use constant _NumSeq_Coord_n_list_max => 2; { my @_NumSeq_Coord_IntXY_min = (undef, 1, # 1 arm, octant X+Y>=0 0, # 2 arms, X>=0 0, # 3 arms, X>-Y so X/Y>-1 ); # more than 3 arm, X neg axis so undef sub _NumSeq_Coord_IntXY_min { my ($self) = @_; return $_NumSeq_Coord_IntXY_min[$self->arms_count]; } } # 8-arm plane filling if full grid { # arms 1 2 3 4 5 6 7 8 my @_NumSeq_Coord_ExperimentalNeighbours3_min = (undef, 1,1,2,2,3,3,3,3); sub _NumSeq_Coord_ExperimentalNeighbours3_min { my ($self) = @_; return $_NumSeq_Coord_ExperimentalNeighbours3_min[$self->arms_count]; } } { # arms 1 2 3 4 5 6 7 8 my @_NumSeq_Coord_ExperimentalNeighbours4_min = (undef, 1,2,2,3,3,3,3,4); sub _NumSeq_Coord_ExperimentalNeighbours4_min { my ($self) = @_; return $_NumSeq_Coord_ExperimentalNeighbours4_min[$self->arms_count]; } } # use constant _NumSeq_Coord_ExperimentalNeighbours6_min => 0; # ??? { # arms 1 2 3 4 5 6 7 8 my @_NumSeq_Coord_ExperimentalNeighbours8_min = (undef, 2,3,4,5,6,7,7,8); sub _NumSeq_Coord_ExperimentalNeighbours8_min { my ($self) = @_; return $_NumSeq_Coord_ExperimentalNeighbours8_min[$self->arms_count]; } } use constant _NumSeq_Coord_oeis_anum => { 'i_start=1' => { DiffXY => 'A020990', # GRS*(-1)^n cumulative AbsDiff => 'A020990', # Not quite Sum, OFFSET=0 value=1 corresponds to N=1 Sum=1, so # A020986 doesn't have N=0 Sum=0. # Sum => 'A020986', # GRS cumulative # # OEIS-Catalogue: A020986 planepath=AlternatePaper coordinate_type=Sum i_start=1 # X_undoubled => 'A020986', # GRS cumulative # Y_undoubled => 'A020990', # GRS*(-1)^n cumulative }, }; } { package Math::PlanePath::AlternatePaperMidpoint; { my @_NumSeq_Coord_IntXY_min = (undef, 1, # 1 arm, octant X+Y>=0 0, # 2 arms, X>=0 0, # 3 arms, X>-Y so X/Y>-1 ); # more than 3 arm, X neg axis so undef sub _NumSeq_Coord_IntXY_min { my ($self) = @_; return $_NumSeq_Coord_IntXY_min[$self->arms_count]; } } use constant _NumSeq_Coord_oeis_anum => { 'arms=1' => {X => 'A334576', Y => 'A334577', # OEIS-Catalogue: A334576 planepath=AlternatePaperMidpoint coordinate_type=X # OEIS-Catalogue: A334577 planepath=AlternatePaperMidpoint coordinate_type=Y }, }; } { package Math::PlanePath::TerdragonCurve; use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always use constant _NumSeq_Coord_n_list_max => 3; } { package Math::PlanePath::TerdragonRounded; use constant _NumSeq_Coord_TRSquared_min => 4; # either X=2,Y=0 or X=1,Y=1 use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always } { package Math::PlanePath::TerdragonMidpoint; use constant _NumSeq_Coord_TRSquared_min => 4; # either X=2,Y=0 or X=1,Y=1 use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always } { package Math::PlanePath::AlternateTerdragon; use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always use constant _NumSeq_Coord_n_list_max => 3; } { package Math::PlanePath::R5DragonCurve; use constant _NumSeq_Coord_n_list_max => 2; } # { package Math::PlanePath::R5DragonMidpoint; # } { package Math::PlanePath::CCurve; use constant _NumSeq_Coord_n_list_max => 4; use constant _NumSeq_Coord_oeis_anum => { '' => {X => 'A332251', Y => 'A332252', # OEIS-Catalogue: A332251 planepath=CCurve coordinate_type=X # OEIS-Catalogue: A332252 planepath=CCurve coordinate_type=Y }, }; } # { package Math::PlanePath::ComplexPlus; # Sum X+Y < 0 at N=16 # use constant _NumSeq_Coord_oeis_anum => # { 'realpart=1,arms=1' => # { # # not quite, OFFSET=1 but start N=0 here # # NegX => 'A290885', # # Y => 'A290884', # # RSquared => 'A290886', # # OEIS-Catalogue: A290884 planepath=ComplexPlus coordinate_type=Y # # OEIS-Catalogue: A290886 planepath=ComplexPlus coordinate_type=RSquared # }, # }; # } { package Math::PlanePath::ComplexMinus; use constant _NumSeq_Coord_filling_type => 'plane'; use constant _NumSeq_Coord_oeis_anum => { 'realpart=1' => {X => 'A318438', Y => 'A318439', RSquared => 'A318479', # OEIS-Catalogue: A318438 planepath=ComplexMinus coordinate_type=X # OEIS-Catalogue: A318439 planepath=ComplexMinus coordinate_type=Y # OEIS-Catalogue: A318479 planepath=ComplexMinus coordinate_type=RSquared }, }; } { package Math::PlanePath::ComplexRevolving; use constant _NumSeq_Coord_filling_type => 'plane'; } { package Math::PlanePath::Rows; use constant _NumSeq_extra_parameter_info_list => { name => 'width', type => 'integer', }; *_NumSeq_Coord_X_non_decreasing = \&_NumSeq_Coord_Y_increasing; sub _NumSeq_Coord_Y_increasing { my ($self) = @_; return ($self->{'width'} == 1 ? 1 # X=N,Y=0 only : 0); } sub _NumSeq_Coord_Min_max { $_[0]->x_maximum } *_NumSeq_Coord_Max_increasing=\&_NumSeq_Coord_Y_increasing; # height=1 Max=Y sub _NumSeq_Coord_Max_non_decreasing { my ($self) = @_; return ($self->{'width'} <= 2); } sub _NumSeq_Coord_Sum_non_decreasing { my ($self) = @_; return ($self->{'width'} <= 2 ? 1 # width=1 is X=0,Y=N only, or width=2 is X=0,1,Y=N/2 : 0); } *_NumSeq_Coord_Sum_increasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_SumAbs_non_decreasing = \&_NumSeq_Coord_Sum_non_decreasing; *_NumSeq_Coord_SumAbs_increasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_DiffYX_increasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_Product_non_decreasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_AbsDiff_increasing = \&_NumSeq_Coord_Y_increasing; sub _NumSeq_Coord_BitAnd_max { my ($self) = @_; return $self->{'width'}-1; # at X=Y=width-1 } *_NumSeq_Coord_BitAnd_non_decreasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_BitOr_increasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_BitXor_increasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_Radius_non_decreasing = \&_NumSeq_Coord_Sum_non_decreasing; *_NumSeq_Coord_Radius_increasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_Radius_integer = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_GCD_increasing = \&_NumSeq_Coord_Y_increasing; # width <= 2 one or two columns is increasing *_NumSeq_Coord_TRadius_increasing = \&_NumSeq_Coord_Sum_non_decreasing; use constant _NumSeq_Coord_Y_non_decreasing => 1; # rows upwards use constant _NumSeq_Coord_oeis_anum => { 'n_start=1,width=1' => { Product => 'A000004', # all zeros # OEIS-Other: A000004 planepath=Rows,width=1 coordinate_type=Product # OFFSET # Y => 'A001477', # integers 0 upwards # Sum => 'A001477', # integers 0 upwards # # OEIS-Other: A001477 planepath=Rows,width=1 coordinate_type=Y # DiffXY => 'A001489', # negative integers 0 downwards # DiffYX => 'A001477', # integers 0 upwards # AbsDiff => 'A001477', # integers 0 upwards # Radius => 'A001477', # integers 0 upwards # RSquared => 'A000290', # squares 0 upwards # # OEIS-Other: A001477 planepath=Rows,width=1 coordinate_type=Sum # # OEIS-Other: A001489 planepath=Rows,width=1 coordinate_type=DiffXY # # OEIS-Other: A001477 planepath=Rows,width=1 coordinate_type=DiffYX # # OEIS-Other: A001477 planepath=Rows,width=1 coordinate_type=AbsDiff # # OEIS-Other: A001477 planepath=Rows,width=1 coordinate_type=Radius # # OEIS-Other: A000290 planepath=Rows,width=1 coordinate_type=RSquared }, 'n_start=0,width=2' => { X => 'A000035', # 0,1 repeating OFFSET=0 Y => 'A004526', # 0,0,1,1,2,2,etc cf Math::NumSeq::Runs # OEIS-Other: A000035 planepath=Rows,width=2,n_start=0 coordinate_type=X # OEIS-Other: A004526 planepath=Rows,width=2,n_start=0 coordinate_type=Y # # Not quite, A142150 OFFSET=0 starting 0,0,1,0,2 interleave integers # # and 0 but Product here extra 0 start 0,0,0,1,0,2,0 # # Product => 'A142150' # # # Not quite, GCD=>'A057979' but A057979 extra initial 1 }, }; } { package Math::PlanePath::Columns; use constant _NumSeq_extra_parameter_info_list => { name => 'height', type => 'integer', }; sub _NumSeq_Coord_X_increasing { my ($self) = @_; return ($self->{'height'} == 1 ? 1 # X=N,Y=0 only : 0); } use constant _NumSeq_Coord_X_non_decreasing => 1; # columns across *_NumSeq_Coord_Y_non_decreasing = \&_NumSeq_Coord_X_increasing; sub _NumSeq_Coord_Min_max { $_[0]->y_maximum } *_NumSeq_Coord_Max_increasing=\&_NumSeq_Coord_X_increasing; # height=1 Max=X sub _NumSeq_Coord_Max_non_decreasing { my ($self) = @_; return ($self->{'height'} <= 2); } *_NumSeq_Coord_Sum_increasing = \&_NumSeq_Coord_X_increasing; sub _NumSeq_Coord_Sum_non_decreasing { my ($self) = @_; return ($self->{'height'} <= 2 ? 1 # height=1 is X=N,Y=0 only, or height=2 is X=N/2,Y=0,1 : 0); } *_NumSeq_Coord_SumAbs_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_SumAbs_non_decreasing = \&_NumSeq_Coord_Sum_non_decreasing; *_NumSeq_Coord_DiffXY_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_AbsDiff_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_Radius_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_Radius_integer = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_TRadius_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_TRadius_integer = \&_NumSeq_Coord_X_increasing; sub _NumSeq_Coord_BitAnd_max { my ($self) = @_; return $self->{'height'}-1; # at X=Y=height-1 } *_NumSeq_Coord_BitAnd_non_decreasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_BitOr_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_BitXor_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_Product_non_decreasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_GCD_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_Radius_non_decreasing = \&_NumSeq_Coord_Sum_non_decreasing; use constant _NumSeq_Coord_oeis_anum => { 'n_start=1,height=1' => { Product => 'A000004', # all zeros # OEIS-Other: A000004 planepath=Columns,height=1 coordinate_type=Product # OFFSET # X => 'A001477', # integers 0 upwards # Sum => 'A001477', # integers 0 upwards # DiffXY => 'A001477', # integers 0 upwards # DiffYX => 'A001489', # negative integers 0 downwards # AbsDiff => 'A001477', # integers 0 upwards # Radius => 'A001477', # integers 0 upwards # RSquared => 'A000290', # squares 0 upwards # # OEIS-Other: A001477 planepath=Columns,height=1 coordinate_type=X # # OEIS-Other: A001477 planepath=Columns,height=1 coordinate_type=Sum # # OEIS-Other: A001489 planepath=Columns,height=1 coordinate_type=DiffYX # # OEIS-Other: A001477 planepath=Columns,height=1 coordinate_type=DiffXY # # OEIS-Other: A001477 planepath=Columns,height=1 coordinate_type=AbsDiff # # OEIS-Other: A001477 planepath=Columns,height=1 coordinate_type=Radius # # OEIS-Other: A000290 planepath=Columns,height=1 coordinate_type=RSquared }, 'n_start=0,height=2' => { X => 'A004526', # 0,0,1,1,2,2,etc, as per Math::NumSeq::Runs 2rep Y => 'A000035', # 0,1 repeating OFFSET=0 # OEIS-Other: A004526 planepath=Columns,height=2,n_start=0 coordinate_type=X # OEIS-Other: A000035 planepath=Columns,height=2,n_start=0 coordinate_type=Y }, }; } { package Math::PlanePath::Diagonals; use constant _NumSeq_Coord_filling_type => 'quadrant'; use constant _NumSeq_Coord_Sum_non_decreasing => 1; # X+Y diagonals sub _NumSeq_Coord_SumAbs_non_decreasing { my ($self) = @_; return ($self->{'x_start'} >= 0 && $self->{'y_start'} >= 0); } # these irrespective where x_start,y_start make x_minimum(),y_minimum() use constant _NumSeq_Coord_BitAnd_min => 0; # when all diff bits use constant _NumSeq_Coord_BitXor_min => 0; # when X=Y use constant _NumSeq_Coord_oeis_anum => { 'direction=down,n_start=1,x_start=0,y_start=0' => { ExperimentalPairsXY => 'A057554', # starting OFFSET=1 so the default n_start=1 here # OEIS-Catalogue: A057554 planepath=Diagonals coordinate_type=ExperimentalPairsXY }, 'direction=up,n_start=1,x_start=0,y_start=0' => { ExperimentalPairsYX => 'A057554', # starting OFFSET=1 so the default n_start=1 here # OEIS-Other: A057554 planepath=Diagonals,direction=up coordinate_type=ExperimentalPairsYX }, 'direction=down,n_start=1,x_start=1,y_start=0' => { ExperimentalNumerator => 'A164306', # T(n,k) = k/GCD(n,k) n,k>=1 offset=1 ExperimentalDenominator => 'A167192', # T(n,k) = (n-k)/GCD(n,k) n,k>=1 offset=1 # OEIS-Catalogue: A164306 planepath=Diagonals,x_start=1,y_start=0 coordinate_type=ExperimentalNumerator # OEIS-Catalogue: A167192 planepath=Diagonals,x_start=1,y_start=0 coordinate_type=ExperimentalDenominator }, 'direction=down,n_start=1,x_start=1,y_start=1' => { Product => 'A003991', # X*Y starting (1,1) n=1 GCD => 'A003989', # GCD by diagonals starting (1,1) n=1 Min => 'A003983', # X,Y>=1 MinAbs => 'A003983', # MinAbs=Min Max => 'A051125', # X,Y>=1 MaxAbs => 'A051125', # MaxAbs=Max IntXY => 'A004199', # X>=0,Y>=0, X/Y round towards zero ExperimentalPairsXY => 'A057555', # starting OFFSET=1 so n_start=1 here # OEIS-Catalogue: A003991 planepath=Diagonals,x_start=1,y_start=1 coordinate_type=Product # OEIS-Catalogue: A003989 planepath=Diagonals,x_start=1,y_start=1 coordinate_type=GCD # OEIS-Catalogue: A003983 planepath=Diagonals,x_start=1,y_start=1 coordinate_type=Min # OEIS-Other: A003983 planepath=Diagonals,x_start=1,y_start=1 coordinate_type=MinAbs # OEIS-Catalogue: A051125 planepath=Diagonals,x_start=1,y_start=1 coordinate_type=Max # OEIS-Other: A051125 planepath=Diagonals,x_start=1,y_start=1 coordinate_type=MaxAbs # OEIS-Catalogue: A004199 planepath=Diagonals,x_start=1,y_start=1 coordinate_type=IntXY # OEIS-Catalogue: A057555 planepath=Diagonals,x_start=1,y_start=1 coordinate_type=ExperimentalPairsXY # cf A003990 LCM starting (1,1) n=1 # A003992 X^Y power starting (1,1) n=1 }, 'direction=up,n_start=1,x_start=1,y_start=1' => { Product => 'A003991', # X*Y starting (1,1) n=1 GCD => 'A003989', # GCD by diagonals starting (1,1) n=1 IntXY => 'A003988', # Int(X/Y) starting (1,1) n=1 ExperimentalPairsYX => 'A057555', # starting OFFSET=1 so n_start=1 here # OEIS-Other: A003991 planepath=Diagonals,direction=up,x_start=1,y_start=1 coordinate_type=Product # OEIS-Other: A003989 planepath=Diagonals,direction=up,x_start=1,y_start=1 coordinate_type=GCD # OEIS-Catalogue: A003988 planepath=Diagonals,direction=up,x_start=1,y_start=1 coordinate_type=IntXY # OEIS-Other: A057555 planepath=Diagonals,direction=up,x_start=1,y_start=1 coordinate_type=ExperimentalPairsYX # num,den of reduction of A004736/A002260 which is run1toK/runKto1. ExperimentalNumerator => 'A112543', # 1,2,1,3,1,1,4,3,2,1,5,2,1,1,1,6, ExperimentalDenominator => 'A112544', # 1,1,2,1,1,3,1,2,3,4,1,1,1,2,5,1, # OEIS-Catalogue: A112543 planepath=Diagonals,direction=up,x_start=1,y_start=1 coordinate_type=ExperimentalNumerator # OEIS-Catalogue: A112544 planepath=Diagonals,direction=up,x_start=1,y_start=1 coordinate_type=ExperimentalDenominator }, #------------------ # n_start=0 instead 'direction=down,n_start=0,x_start=0,y_start=0' => { X => 'A002262', # runs 0toN 0, 0,1, 0,1,2, etc Y => 'A025581', # runs Nto0 0, 1,0, 2,1,0, 3,2,1,0 descend Sum => 'A003056', # 0, 1,1, 2,2,2, 3,3,3,3 SumAbs => 'A003056', # same Product => 'A004247', # 0, 0,0,0, 1, 0,0, 2,2, 0,0, 3,4,5, 0,0 DiffYX => 'A114327', # Y-X by anti-diagonals AbsDiff => 'A049581', # abs(Y-X) by anti-diagonals RSquared => 'A048147', # x^2+y^2 by diagonals BitAnd => 'A004198', # X bitand Y BitOr => 'A003986', # X bitor Y, cf A006583 diagonal totals BitXor => 'A003987', # cf A006582 X xor Y diagonal totals GCD => 'A109004', # GCD(x,y) by diagonals, (0,0) at n=0 Min => 'A004197', # X,Y>=0, runs 0toNto0,0toNNto0 MinAbs => 'A004197', # MinAbs=Min Max => 'A003984', MaxAbs => 'A003984', # MaxAbs=Max ExperimentalHammingDist => 'A101080', # OEIS-Other: A002262 planepath=Diagonals,n_start=0 coordinate_type=X # OEIS-Other: A025581 planepath=Diagonals,n_start=0 coordinate_type=Y # OEIS-Other: A003056 planepath=Diagonals,n_start=0 coordinate_type=Sum # OEIS-Other: A003056 planepath=Diagonals,n_start=0 coordinate_type=SumAbs # OEIS-Catalogue: A004247 planepath=Diagonals,n_start=0 coordinate_type=Product # OEIS-Catalogue: A114327 planepath=Diagonals,n_start=0 coordinate_type=DiffYX # OEIS-Catalogue: A049581 planepath=Diagonals,n_start=0 coordinate_type=AbsDiff # OEIS-Catalogue: A048147 planepath=Diagonals,n_start=0 coordinate_type=RSquared # OEIS-Catalogue: A004198 planepath=Diagonals,n_start=0 coordinate_type=BitAnd # OEIS-Catalogue: A003986 planepath=Diagonals,n_start=0 coordinate_type=BitOr # OEIS-Catalogue: A003987 planepath=Diagonals,n_start=0 coordinate_type=BitXor # OEIS-Catalogue: A109004 planepath=Diagonals,n_start=0 coordinate_type=GCD # OEIS-Catalogue: A004197 planepath=Diagonals,n_start=0 coordinate_type=Min # OEIS-Other: A004197 planepath=Diagonals,n_start=0 coordinate_type=MinAbs # OEIS-Catalogue: A003984 planepath=Diagonals,n_start=0 coordinate_type=Max # OEIS-Other: A003984 planepath=Diagonals,n_start=0 coordinate_type=MaxAbs # OEIS-Catalogue: A101080 planepath=Diagonals,n_start=0 coordinate_type=ExperimentalHammingDist }, 'direction=up,n_start=0,x_start=0,y_start=0' => { X => 'A025581', # \ opposite of direction="down" Y => 'A002262', # / Sum => 'A003056', # \ SumAbs => 'A003056', # | same as direction="down' Product => 'A004247', # | AbsDiff => 'A049581', # | RSquared => 'A048147', # / DiffXY => 'A114327', # transposed from direction="down" BitAnd => 'A004198', # X bitand Y BitOr => 'A003986', # X bitor Y, cf A006583 diagonal totals BitXor => 'A003987', # cf A006582 X xor Y diagonal totals GCD => 'A109004', # GCD(x,y) by diagonals, (0,0) at n=0 ExperimentalHammingDist => 'A101080', # OEIS-Other: A025581 planepath=Diagonals,direction=up,n_start=0 coordinate_type=X # OEIS-Other: A002262 planepath=Diagonals,direction=up,n_start=0 coordinate_type=Y # OEIS-Other: A003056 planepath=Diagonals,direction=up,n_start=0 coordinate_type=Sum # OEIS-Other: A003056 planepath=Diagonals,direction=up,n_start=0 coordinate_type=SumAbs # OEIS-Other: A004247 planepath=Diagonals,direction=up,n_start=0 coordinate_type=Product # OEIS-Other: A114327 planepath=Diagonals,direction=up,n_start=0 coordinate_type=DiffXY # OEIS-Other: A049581 planepath=Diagonals,direction=up,n_start=0 coordinate_type=AbsDiff # OEIS-Other: A048147 planepath=Diagonals,direction=up,n_start=0 coordinate_type=RSquared # OEIS-Other: A004198 planepath=Diagonals,direction=up,n_start=0 coordinate_type=BitAnd # OEIS-Other: A003986 planepath=Diagonals,direction=up,n_start=0 coordinate_type=BitOr # OEIS-Other: A003987 planepath=Diagonals,direction=up,n_start=0 coordinate_type=BitXor # OEIS-Other: A109004 planepath=Diagonals,direction=up,n_start=0 coordinate_type=GCD # OEIS-Other: A101080 planepath=Diagonals,direction=up,n_start=0 coordinate_type=ExperimentalHammingDist }, }; } { package Math::PlanePath::DiagonalsAlternating; use constant _NumSeq_Coord_filling_type => 'quadrant'; use constant _NumSeq_Coord_Sum_non_decreasing => 1; # X+Y diagonals use constant _NumSeq_Coord_SumAbs_non_decreasing => 1; # X+Y diagonals use constant _NumSeq_Coord_oeis_anum => { 'n_start=0' => { X => 'A319572', Y => 'A319573', Sum => 'A003056', # 0, 1,1, 2,2,2, 3,3,3,3 SumAbs => 'A003056', # same Product => 'A004247', # 0, 0,0,0, 1, 0,0, 2,2, 0,0, 3,4,5, 0,0 AbsDiff => 'A049581', # abs(Y-X) by anti-diagonals RSquared => 'A048147', # x^2+y^2 by diagonals BitAnd => 'A004198', # X bitand Y BitOr => 'A003986', # X bitor Y, cf A006583 diagonal totals BitXor => 'A003987', # cf A006582 X xor Y diagonal totals Min => 'A004197', # runs 0toNto0,0toNNto0 MinAbs => 'A004197', # MinAbs=Min Max => 'A003984', MaxAbs => 'A003984', # MaxAbs=Max ExperimentalHammingDist => 'A101080', # OEIS-Catalogue: A319572 planepath=DiagonalsAlternating,n_start=0 coordinate_type=X # OEIS-Catalogue: A319573 planepath=DiagonalsAlternating,n_start=0 coordinate_type=Y # OEIS-Other: A003056 planepath=DiagonalsAlternating,n_start=0 coordinate_type=Sum # OEIS-Other: A003056 planepath=DiagonalsAlternating,n_start=0 coordinate_type=SumAbs # OEIS-Other: A004247 planepath=DiagonalsAlternating,n_start=0 coordinate_type=Product # OEIS-Other: A049581 planepath=DiagonalsAlternating,n_start=0 coordinate_type=AbsDiff # OEIS-Other: A048147 planepath=DiagonalsAlternating,n_start=0 coordinate_type=RSquared # OEIS-Other: A004198 planepath=DiagonalsAlternating,n_start=0 coordinate_type=BitAnd # OEIS-Other: A003986 planepath=DiagonalsAlternating,n_start=0 coordinate_type=BitOr # OEIS-Other: A003987 planepath=DiagonalsAlternating,n_start=0 coordinate_type=BitXor # OEIS-Other: A004197 planepath=DiagonalsAlternating,n_start=0 coordinate_type=Min # OEIS-Other: A004197 planepath=DiagonalsAlternating,n_start=0 coordinate_type=MinAbs # OEIS-Other: A003984 planepath=DiagonalsAlternating,n_start=0 coordinate_type=Max # OEIS-Other: A003984 planepath=DiagonalsAlternating,n_start=0 coordinate_type=MaxAbs # OEIS-Other: A101080 planepath=DiagonalsAlternating,n_start=0 coordinate_type=ExperimentalHammingDist }, }; } { package Math::PlanePath::DiagonalsOctant; use constant _NumSeq_Coord_Sum_non_decreasing => 1; # X+Y diagonals use constant _NumSeq_Coord_SumAbs_non_decreasing => 1; # X+Y diagonals use constant _NumSeq_Coord_oeis_anum => { 'direction=down,n_start=0' => { X => 'A055087', # 0, 0,1, 0,1, 0,1,2, 0,1,2, etc Min => 'A055087', # X<=Y so Min=X MinAbs => 'A055087', # MinAbs=Min Sum => 'A055086', # reps floor(n/2)+1 SumAbs => 'A055086', # same DiffYX => 'A082375', # step=2 k to 0 # OEIS-Catalogue: A055087 planepath=DiagonalsOctant,n_start=0 coordinate_type=X # OEIS-Other: A055087 planepath=DiagonalsOctant,n_start=0 coordinate_type=Min # OEIS-Other: A055087 planepath=DiagonalsOctant,n_start=0 coordinate_type=MinAbs # OEIS-Catalogue: A055086 planepath=DiagonalsOctant,n_start=0 coordinate_type=Sum # OEIS-Other: A055086 planepath=DiagonalsOctant,n_start=0 coordinate_type=SumAbs # OEIS-Catalogue: A082375 planepath=DiagonalsOctant,n_start=0 coordinate_type=DiffYX }, 'direction=up,n_start=0' => { Sum => 'A055086', # reps floor(n/2)+1 SumAbs => 'A055086', # same # OEIS-Other: A055086 planepath=DiagonalsOctant,direction=up,n_start=0 coordinate_type=Sum # OEIS-Other: A055086 planepath=DiagonalsOctant,direction=up,n_start=0 coordinate_type=SumAbs }, }; } { package Math::PlanePath::MPeaks; use constant _NumSeq_Coord_filling_type => 'half'; } { package Math::PlanePath::Staircase; use constant _NumSeq_Coord_filling_type => 'quadrant'; } # { package Math::PlanePath::StaircaseAlternating; # } { package Math::PlanePath::Corner; use constant _NumSeq_Coord_filling_type => 'quadrant'; sub _NumSeq_Coord_Max_non_decreasing { my ($self) = @_; # non-decreasing when wider=0 or 1 return ($self->{'wider'} <= 1); } use constant _NumSeq_Coord_oeis_anum => { 'wider=0,n_start=1' => { Sum => 'A213088', # Manhattan X+Y SumAbs => 'A213088', # OEIS-Catalogue: A213088 planepath=Corner coordinate_type=Sum # OEIS-Other: A213088 planepath=Corner coordinate_type=SumAbs }, 'wider=0,n_start=0' => { DiffXY => 'A196199', # runs -n to n AbsDiff => 'A053615', # runs n..0..n Max => 'A000196', # n repeated 2n+1 times, floor(sqrt(N)) MaxAbs => 'A000196', # MaxAbs=Max # OEIS-Other: A196199 planepath=Corner,n_start=0 coordinate_type=DiffXY # OEIS-Other: A053615 planepath=Corner,n_start=0 coordinate_type=AbsDiff # OEIS-Other: A000196 planepath=Corner,n_start=0 coordinate_type=Max # OEIS-Other: A000196 planepath=Corner,n_start=0 coordinate_type=MaxAbs # Not quite, A053188 has extra initial 0 # AbsDiff => 'A053188', # distance to nearest square }, 'wider=2,n_start=1' => { Sum => 'A213088', # Manhattan X+Y SumAbs => 'A213088', # OEIS-Other: A213088 planepath=Corner,wider=2 coordinate_type=Sum # OEIS-Other: A213088 planepath=Corner,wider=2 coordinate_type=SumAbs }, }; } { package Math::PlanePath::CornerAlternating; use constant _NumSeq_Coord_filling_type => 'quadrant'; *_NumSeq_Coord_Max_non_decreasing = \&Math::PlanePath::Corner::_NumSeq_Coord_Max_non_decreasing; use constant _NumSeq_Coord_oeis_anum => { 'wider=0,n_start=1' => { Sum => 'A213088', # Manhattan X+Y, OFFSET=0 SumAbs => 'A213088', # OEIS-Other: A213088 planepath=CornerAlternating coordinate_type=Sum # OEIS-Other: A213088 planepath=CornerAlternating coordinate_type=SumAbs }, 'wider=0,n_start=0' => { X => 'A319290', Y => 'A319289', DiffXY => 'A329116', AbsDiff => 'A053615', # runs n..0..n Max => 'A000196', # n repeated 2n+1 times, floor(sqrt(N)) MaxAbs => 'A000196', # MaxAbs=Max # OEIS-Catalogue: A319290 planepath=CornerAlternating,n_start=0 coordinate_type=X # OEIS-Catalogue: A319289 planepath=CornerAlternating,n_start=0 coordinate_type=Y # OEIS-Other: A329116 planepath=CornerAlternating,n_start=0 coordinate_type=DiffXY # OEIS-Other: A053615 planepath=CornerAlternating,n_start=0 coordinate_type=AbsDiff # OEIS-Other: A000196 planepath=CornerAlternating,n_start=0 coordinate_type=Max # OEIS-Other: A000196 planepath=CornerAlternating,n_start=0 coordinate_type=MaxAbs }, 'wider=1,n_start=0' => { DiffXY => 'A180714', # X+Y of square spiral # OEIS-Other: A180714 planepath=CornerAlternating,wider=1,n_start=0 coordinate_type=DiffXY } }; } { package Math::PlanePath::PyramidRows; *_NumSeq_Coord_Min_non_decreasing = \&_NumSeq_Coord_Y_increasing; # Max==Y and Y is non-decreasing when # step=0 align=any # step=1 align=any # step=2 align=left or centre # step>2 align=left sub _NumSeq_Coord_Max_non_decreasing { my ($self) = @_; return ($self->{'step'} <= 1 || ($self->{'step'} == 2 && $self->{'align'} eq 'centre') || $self->{'align'} eq 'left'); } sub _NumSeq_Coord_MaxAbs_non_decreasing { my ($self) = @_; return ($self->{'step'} <= 1 || ($self->{'step'} == 2 && $self->{'align'} eq 'centre')); } *_NumSeq_Coord_Sum_increasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_SumAbs_increasing = \&_NumSeq_Coord_Y_increasing; sub _NumSeq_Coord_IntXY_min { my ($self) = @_; return ($self->{'align'} eq 'left' ? - $self->{'step'} : $self->{'align'} eq 'centre' ? - int($self->{'step'}/2) : 0); # right } sub _NumSeq_Coord_FracXY_max { my ($self) = @_; return ($self->{'step'} == 0 ? 0 : 1); # step=0 X=0 frac=0 always } *_NumSeq_FracXY_max_is_supremum = \&_NumSeq_Coord_FracXY_max; sub _NumSeq_Coord_FracXY_integer { my ($self) = @_; return ($self->{'step'} == 0 ? 1 : 0); # step=0 X=0 frac=0 always } sub _NumSeq_Coord_Radius_integer { my ($self) = @_; return ($self->{'step'} == 0); } sub _NumSeq_Coord_Y_increasing { my ($self) = @_; return ($self->{'step'} == 0 ? 1 # column X=0,Y=N : 0); } *_NumSeq_Coord_DiffYX_increasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_AbsDiff_increasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_Max_increasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_Radius_increasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_TRadius_increasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_BitOr_increasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_BitXor_increasing = \&_NumSeq_Coord_Y_increasing; *_NumSeq_Coord_GCD_increasing = \&_NumSeq_Coord_Y_increasing; use constant _NumSeq_Coord_Y_non_decreasing => 1; # rows upwards *_NumSeq_Coord_X_non_decreasing = \&_NumSeq_Coord_Y_increasing; # X=0 always *_NumSeq_Coord_Product_non_decreasing = \&_NumSeq_Coord_Y_increasing; # N*0=0 # step=0 constant ExperimentalNumerator=0 sub _NumSeq_Coord_ExperimentalNumerator_max { my ($self) = @_; return ($self->{'step'} == 0 ? 0 : undef); } # step=0 has Y=0 so BitAnd=0 always *_NumSeq_Coord_BitAnd_max = \&_NumSeq_Coord_ExperimentalNumerator_max; *_NumSeq_Coord_BitAnd_non_decreasing = \&_NumSeq_Coord_Y_increasing; # cf A050873 GCD(X+1,Y+1) by rows n>=1 k=1..n, x_start=1,y_start=1 # A051173 LCM(X+1,Y+1) by rows n>=1 k=1..n, x_start=1,y_start=1 # # Maybe with x_start,y_start to go by rows starting from 1. # ExperimentalDenominator => 'A164306', # T(n,k) = k/GCD(n,k) n,k>=1 offset=1 # # OEIS-Catalogue: A164306 planepath=PyramidRows,step=1,x_start=1,y_start=1 coordinate_type=ExperimentalDenominator # ExperimentalDenominator => 'A167192', # T(n,k) = (n-k)/GCD(n,k) n,k>=1 offset=1 # # OEIS-Catalogue: A167192 planepath=PyramidRows,step=1,x_start=1,y_start=1,align=left coordinate_type=ExperimentalNumerator # use constant _NumSeq_Coord_oeis_anum => { # PyramidRows step=0 is trivial X=0,Y=N do { my $href = { X => 'A000004', # all-zeros Product => 'A000004', # all-zeros # OEIS-Other: A000004 planepath=PyramidRows,step=0 coordinate_type=X # OEIS-Other: A000004 planepath=PyramidRows,step=0 coordinate_type=Product }; ('step=0,align=centre' => $href, 'step=0,align=right' => $href, 'step=0,align=left' => $href, ); }, do { my $href = { X => 'A000004', # all zeros Min => 'A000004', # Min=X Y => 'A001477', # integers Y=0,1,2,etc Max => 'A001477', # Max=Y MaxAbs => 'A001477', # MaxAbs=Max Sum => 'A001477', # Sum=Y DiffYX => 'A001477', # DiffYX=Y DiffXY => 'A001489', # negatives 0,-1,-2,etc AbsDiff => 'A001477', # AbsDiff=Y Product => 'A000004', # Product=0 Radius => 'A001477', # Radius=Y GCD => 'A001477', # GCD=Y RSquared => 'A000290', # n^2 TRSquared => 'A033428', # 3*n^2 BitAnd => 'A000004', # BitAnd=0 BitOr => 'A001477', # BitOr=Y BitXor => 'A001477', # BitXor=Y # OEIS-Other: A000004 planepath=PyramidRows,step=0,n_start=0 coordinate_type=X # OEIS-Other: A000004 planepath=PyramidRows,step=0,n_start=0 coordinate_type=Min # OEIS-Other: A001477 planepath=PyramidRows,step=0,n_start=0 coordinate_type=Y # OEIS-Other: A001477 planepath=PyramidRows,step=0,n_start=0 coordinate_type=Max # OEIS-Other: A001477 planepath=PyramidRows,step=0,n_start=0 coordinate_type=MaxAbs # OEIS-Other: A001477 planepath=PyramidRows,step=0,n_start=0 coordinate_type=Sum # OEIS-Other: A001477 planepath=PyramidRows,step=0,n_start=0 coordinate_type=DiffYX # OEIS-Other: A001489 planepath=PyramidRows,step=0,n_start=0 coordinate_type=DiffXY # OEIS-Other: A001477 planepath=PyramidRows,step=0,n_start=0 coordinate_type=AbsDiff # OEIS-Other: A000004 planepath=PyramidRows,step=0,n_start=0 coordinate_type=Product # OEIS-Other: A001477 planepath=PyramidRows,step=0,n_start=0 coordinate_type=Radius # OEIS-Other: A001489 planepath=PyramidRows,step=0,n_start=0 coordinate_type=DiffXY # OEIS-Other: A000290 planepath=PyramidRows,step=0,n_start=0 coordinate_type=RSquared # OEIS-Other: A033428 planepath=PyramidRows,step=0,n_start=0 coordinate_type=TRSquared # OEIS-Other: A000004 planepath=PyramidRows,step=0,n_start=0 coordinate_type=BitAnd # OEIS-Other: A001477 planepath=PyramidRows,step=0,n_start=0 coordinate_type=BitOr # OEIS-Other: A001477 planepath=PyramidRows,step=0,n_start=0 coordinate_type=BitXor }; ('step=0,align=centre,n_start=0' => $href, 'step=0,align=right,n_start=0' => $href, 'step=0,align=left,n_start=0' => $href, ); }, # PyramidRows step=1 # cf A050873 GCD triangle starting (1,1) n=1 # A051173 LCM triangle starting (1,1) n=1 # A003991 X*Y product starting (1,1) n=1 # A001316 count of occurrences of n as BitOr do { my $href = { X => 'A002262', # 0, 0,1, 0,1,2, etc (Diagonals) Min => 'A002262', # X<=Y always Y => 'A003056', # 0, 1,1, 2,2,2, 3,3,3,3 (Diagonals) Max => 'A003056', # Max=Y as Y>=X always MaxAbs => 'A003056', # MaxAbs=Max as Y>=0 always DiffYX => 'A025581', # descending N to 0 (Diagonals) AbsDiff => 'A025581', # absdiff same Sum => 'A051162', # triangle X+Y for X=0 to Y inclusive SumAbs => 'A051162', # sumabs same Product => 'A079904', RSquared => 'A069011', # triangle X^2+Y^2 for X=0 to Y inclusive GCD => 'A109004', # same as by diagonals BitAnd => 'A080099', BitOr => 'A080098', BitXor => 'A051933', }; ('step=1,align=centre,n_start=0' => $href, 'step=1,align=right,n_start=0' => $href, ); # OEIS-Other: A002262 planepath=PyramidRows,step=1,n_start=0 coordinate_type=X # OEIS-Other: A002262 planepath=PyramidRows,step=1,n_start=0 coordinate_type=Min # OEIS-Other: A003056 planepath=PyramidRows,step=1,n_start=0 coordinate_type=Y # OEIS-Other: A003056 planepath=PyramidRows,step=1,n_start=0 coordinate_type=Max # OEIS-Other: A003056 planepath=PyramidRows,step=1,n_start=0 coordinate_type=MaxAbs # OEIS-Other: A025581 planepath=PyramidRows,step=1,n_start=0 coordinate_type=DiffYX # OEIS-Other: A025581 planepath=PyramidRows,step=1,n_start=0 coordinate_type=AbsDiff # OEIS-Other: A051162 planepath=PyramidRows,step=1,n_start=0 coordinate_type=Sum # OEIS-Other: A051162 planepath=PyramidRows,step=1,n_start=0 coordinate_type=SumAbs # OEIS-Catalogue: A079904 planepath=PyramidRows,step=1,n_start=0 coordinate_type=Product # OEIS-Catalogue: A069011 planepath=PyramidRows,step=1,n_start=0 coordinate_type=RSquared # OEIS-Other: A109004 planepath=PyramidRows,step=1,n_start=0 coordinate_type=GCD # OEIS-Catalogue: A080099 planepath=PyramidRows,step=1,n_start=0 coordinate_type=BitAnd # OEIS-Catalogue: A080098 planepath=PyramidRows,step=1,n_start=0 coordinate_type=BitOr # OEIS-Catalogue: A051933 planepath=PyramidRows,step=1,n_start=0 coordinate_type=BitXor # OEIS-Other: A002262 planepath=PyramidRows,step=1,align=right,n_start=0 coordinate_type=X # OEIS-Other: A003056 planepath=PyramidRows,step=1,align=right,n_start=0 coordinate_type=Y # OEIS-Other: A025581 planepath=PyramidRows,step=1,align=right,n_start=0 coordinate_type=DiffYX # OEIS-Other: A025581 planepath=PyramidRows,step=1,align=right,n_start=0 coordinate_type=AbsDiff # OEIS-Other: A051162 planepath=PyramidRows,step=1,align=right,n_start=0 coordinate_type=Sum # OEIS-Other: A051162 planepath=PyramidRows,step=1,align=right,n_start=0 coordinate_type=SumAbs # OEIS-Other: A079904 planepath=PyramidRows,step=1,align=right,n_start=0 coordinate_type=Product # OEIS-Other: A069011 planepath=PyramidRows,step=1,align=right,n_start=0 coordinate_type=RSquared # OEIS-Other: A080099 planepath=PyramidRows,step=1,align=right,n_start=0 coordinate_type=BitAnd # OEIS-Other: A080098 planepath=PyramidRows,step=1,align=right,n_start=0 coordinate_type=BitOr # OEIS-Other: A051933 planepath=PyramidRows,step=1,align=right,n_start=0 coordinate_type=BitXor }, # 'step=1,align=left,n_start=0' => # { ExperimentalAbsX => 'A025581', # descending runs n to 0 # }, # PyramidRows step=2 'step=2,align=centre,n_start=0' => { X => 'A196199', # runs -n to n Min => 'A196199', # X since X 'A000196', # n appears 2n+1 times, starting 0 Max => 'A000196', # Y since X 'A053186', # runs 0 to 2n ExperimentalAbsX => 'A053615', # runs n to 0 to n # OEIS-Catalogue: A196199 planepath=PyramidRows,n_start=0 coordinate_type=X # OEIS-Other: A196199 planepath=PyramidRows,n_start=0 coordinate_type=Min # OEIS-Catalogue: A000196 planepath=PyramidRows,n_start=0 coordinate_type=Y # OEIS-Other: A000196 planepath=PyramidRows,n_start=0 coordinate_type=Max # OEIS-Other: A053186 planepath=PyramidRows,n_start=0 coordinate_type=Sum # OEIS-Other: A053615 planepath=PyramidRows,n_start=0 coordinate_type=ExperimentalAbsX # # Not quite, extra initial 0 # DiffYX => 'A068527', # dist to next square # AbsDiff => 'A068527', # same since Y-X>0 }, 'step=2,align=right,n_start=0' => { X => 'A053186', # runs 0 to 2n Y => 'A000196', # n appears 2n+1 times, starting 0 DiffXY => 'A196199', # runs -n to n AbsDiff => 'A053615', # n..0..n, distance to pronic # OEIS-Other: A053186 planepath=PyramidRows,align=right,n_start=0 coordinate_type=X # OEIS-Other: A000196 planepath=PyramidRows,align=right,n_start=0 coordinate_type=Y # OEIS-Other: A196199 planepath=PyramidRows,align=right,n_start=0 coordinate_type=DiffXY # OEIS-Other: A053615 planepath=PyramidRows,align=right,n_start=0 coordinate_type=AbsDiff }, 'step=2,align=left,n_start=0' => { X => '', # runs -2n+1 to 0 Y => 'A000196', # n appears 2n+1 times, starting 0 Sum => 'A196199', # -n to n # OEIS-Other: A000196 planepath=PyramidRows,align=left,n_start=0 coordinate_type=Y # OEIS-Other: A196199 planepath=PyramidRows,align=left,n_start=0 coordinate_type=Sum # Not quite, A068527 doesn't have two initial 0s # ExperimentalAbsX => 'A068527', # dist to next square # # OEIS-Other: A068527 planepath=PyramidRows,align=left,n_start=0 coordinate_type=ExperimentalAbsX }, # PyramidRows step=3 do { my $href = { Y => 'A180447', # n appears 3n+1 times, starting 0 }; ('step=3,align=centre,n_start=0' => $href, 'step=3,align=right,n_start=0' => $href, ); # OEIS-Catalogue: A180447 planepath=PyramidRows,step=3,n_start=0 coordinate_type=Y # OEIS-Other: A180447 planepath=PyramidRows,step=3,align=right,n_start=0 coordinate_type=Y }, 'step=3,align=left,n_start=0' => { Y => 'A180447', # n appears 3n+1 times, starting 0 Max => 'A180447', # Y since X { X => 'A060511', # amount exceeding hexagonal number }, # OEIS-Catalogue: A060511 planepath=PyramidRows,step=4,align=right,n_start=0 coordinate_type=X }; } { package Math::PlanePath::PyramidSides; use constant _NumSeq_Coord_filling_type => 'half'; use constant _NumSeq_Coord_SumAbs_non_decreasing => 1; use constant _NumSeq_Coord_oeis_anum => { 'n_start=0' => { X => 'A196199', # runs -n to n SumAbs => 'A000196', # n appears 2n+1 times, starting 0 # OEIS-Other: A196199 planepath=PyramidSides,n_start=0 coordinate_type=X # OEIS-Other: A000196 planepath=PyramidSides,n_start=0 coordinate_type=SumAbs ExperimentalAbsX => 'A053615', # runs n to 0 to n # OEIS-Other: A053615 planepath=PyramidSides,n_start=0 coordinate_type=ExperimentalAbsX }, }; } { package Math::PlanePath::CellularRule; # single cell # 111 -> any # 110 -> any # 101 -> any # 100 -> 0 initial # 011 -> any # 010 -> 0 initial # 001 -> 0 initial # 000 -> 0 # so (rule & 0x17) == 0 # # right 2 cell line 0x54,74,D4,F4 # 111 -> any # 110 -> 1 # 101 -> any # 100 -> 1 # 011 -> 0 # 010 -> 1 # 001 -> 0 # 000 -> 0 # so (rule & 0x5F) == 0x54 # sub _NumSeq_Coord_X_increasing { my ($self) = @_; ### CellularRule _NumSeq_Coord_X_increasing() rule: $self->{'rule'} return (($self->{'rule'} & 0x17) == 0 # single cell only ? 1 : 0); } sub _NumSeq_Coord_Sum_increasing { my ($self) = @_; return (($self->{'rule'} & 0x17) == 0 # single cell only || ($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? 1 : 0); } *_NumSeq_Coord_Min_increasing = \&_NumSeq_Coord_Sum_increasing; # Min=X *_NumSeq_Coord_SumAbs_increasing = \&_NumSeq_Coord_Sum_increasing; *_NumSeq_Coord_Radius_increasing = \&_NumSeq_Coord_Sum_increasing; *_NumSeq_Coord_TRadius_increasing = \&_NumSeq_Coord_Radius_increasing; *_NumSeq_Coord_Y_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_Max_increasing = \&_NumSeq_Coord_X_increasing; # Max==Y *_NumSeq_Coord_Product_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_DiffXY_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_DiffYX_increasing = \&_NumSeq_Coord_X_increasing; *_NumSeq_Coord_AbsDiff_increasing = \&_NumSeq_Coord_X_increasing; use constant _NumSeq_Coord_MaxAbs_non_decreasing => 1; # -Y<=X<=Y so MaxAbs=Y sub _NumSeq_Coord_X_non_decreasing { my ($self) = @_; return (($self->{'rule'} & 0x17) == 0 # single cell only || ($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? 1 : 0); } *_NumSeq_Coord_Min_non_decreasing = \&_NumSeq_Coord_X_non_decreasing; # Min=X sub _NumSeq_Coord_Product_non_decreasing { my ($self) = @_; return (($self->{'rule'} & 0x17) == 0 # single cell only || ($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? 1 : 0); } use constant _NumSeq_Coord_Y_non_decreasing => 1; # rows upwards use constant _NumSeq_Coord_Max_non_decreasing => 1; # Max==Y sub _NumSeq_Coord_BitAnd_max { my ($self) = @_; return (($self->{'rule'} & 0x17) == 0 # single cell only || ($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? 0 : undef); } sub _NumSeq_Coord_ExperimentalParity_max { my ($self) = @_; return (($self->{'rule'} & 0x17) == 0 # single cell only ? 0 # X=0,Y=0 even : 1); } } { package Math::PlanePath::CellularRule::OneTwo; sub _NumSeq_Coord_Sum_increasing { my ($self) = @_; return ($self->{'sign'} > 0); # when to the right } *_NumSeq_Coord_SumAbs_increasing = \&_NumSeq_Coord_Sum_increasing; *_NumSeq_Coord_Radius_increasing = \&_NumSeq_Coord_Sum_increasing; *_NumSeq_Coord_TRadius_increasing = \&_NumSeq_Coord_Radius_increasing; sub _NumSeq_Coord_X_non_decreasing { my ($self) = @_; return ($self->{'sign'} > 0); # yes when to the right } *_NumSeq_Coord_Min_non_decreasing = \&_NumSeq_Coord_X_non_decreasing; # Min=X *_NumSeq_Coord_Product_non_decreasing = \&_NumSeq_Coord_X_non_decreasing; *_NumSeq_Coord_BitAnd_non_decreasing = \&_NumSeq_Coord_X_non_decreasing; *_NumSeq_Coord_BitOr_non_decreasing = \&_NumSeq_Coord_X_non_decreasing; use constant _NumSeq_Coord_Y_non_decreasing => 1; # rows upwards use constant _NumSeq_Coord_Max_non_decreasing => 1; # Max=Y use constant _NumSeq_Coord_MaxAbs_non_decreasing => 1; # -Y<=X<=Y so MaxAbs=Y sub _NumSeq_Coord_MinAbs_non_decreasing { my ($self) = @_; return ($self->{'sign'} > 0); # yes when to the right } sub _NumSeq_Coord_IntXY_min { my ($self) = @_; return ($self->{'align'} eq 'left' ? -1 : 0); } sub _NumSeq_Coord_BitOr_max { my ($self) = @_; return ($self->{'align'} eq 'left' ? 1 : undef); } use constant _NumSeq_Coord_BitXor_max => 1; use constant _NumSeq_Coord_oeis_anum => { 'align=left,n_start=0' => { Y => 'A004396', # one even two odd Max => 'A004396', # Max=Y SumAbs => 'A131452', # a(3n)=4n, a(3n+1)=4n+2, a(3n+2)=4n+1. DiffYX => 'A131452', # X<0 so Y-X=abs(Y)+abs(X) # OEIS-Catalogue: A004396 planepath=CellularRule,rule=6,n_start=0 coordinate_type=Y # OEIS-Other: A004396 planepath=CellularRule,rule=166,n_start=0 coordinate_type=Max # OEIS-Catalogue: A131452 planepath=CellularRule,rule=6,n_start=0 coordinate_type=SumAbs # OEIS-Other: A131452 planepath=CellularRule,rule=166,n_start=0 coordinate_type=SumAbs # Maybe, but OFFSET in fractions? # Sum => 'A022003', # 1/999 decimal 0,0,1,0,0,1 # # OEIS-Other: A022003 planepath=CellularRule,rule=166,n_start=0 coordinate_type=Sum }, 'align=right,n_start=0' => { X => 'A004523', # 0,0,1,2,2,3 two even, one odd Min => 'A004523', # Min=X BitAnd => 'A004523', # BitAnd=X Y => 'A004396', # one even two odd Max => 'A004396', # Max=Y BitOr => 'A004396', # BitOr=Y SumAbs => 'A004773', # 0,1,2 mod 4 ExperimentalPairsXY => 'A002264', # triples 0,0,0, 1,1,1, etc # OEIS-Catalogue: A004523 planepath=CellularRule,rule=20,n_start=0 coordinate_type=X # OEIS-Other: A004523 planepath=CellularRule,rule=20,n_start=0 coordinate_type=Min # OEIS-Other: A004523 planepath=CellularRule,rule=20,n_start=0 coordinate_type=BitAnd # OEIS-Other: A004396 planepath=CellularRule,rule=20,n_start=0 coordinate_type=Y # OEIS-Other: A004396 planepath=CellularRule,rule=180,n_start=0 coordinate_type=Max # OEIS-Other: A004396 planepath=CellularRule,rule=180,n_start=0 coordinate_type=BitOr # OEIS-Catalogue: A004773 planepath=CellularRule,rule=20,n_start=0 coordinate_type=SumAbs # OEIS-Other: A004773 planepath=CellularRule,rule=180,n_start=0 coordinate_type=SumAbs # OEIS-Other: A002264 planepath=CellularRule,rule=20,n_start=0 coordinate_type=ExperimentalPairsXY }, }; } { package Math::PlanePath::CellularRule::Two; sub _NumSeq_Coord_Sum_increasing { my ($self) = @_; return ($self->{'sign'} > 0); # when to the right } *_NumSeq_Coord_SumAbs_increasing = \&_NumSeq_Coord_Sum_increasing; *_NumSeq_Coord_Radius_increasing = \&_NumSeq_Coord_Sum_increasing; *_NumSeq_Coord_TRadius_increasing = \&_NumSeq_Coord_Radius_increasing; sub _NumSeq_Coord_X_non_decreasing { my ($self) = @_; return ($self->{'sign'} > 0); # yes when to the right } *_NumSeq_Coord_Min_non_decreasing = \&_NumSeq_Coord_X_non_decreasing; # Min=X *_NumSeq_Coord_Product_non_decreasing = \&_NumSeq_Coord_X_non_decreasing; *_NumSeq_Coord_BitAnd_non_decreasing = \&_NumSeq_Coord_X_non_decreasing; *_NumSeq_Coord_BitOr_non_decreasing = \&_NumSeq_Coord_X_non_decreasing; use constant _NumSeq_Coord_Y_non_decreasing => 1; # rows upwards use constant _NumSeq_Coord_Max_non_decreasing => 1; # Max=Y use constant _NumSeq_Coord_MaxAbs_non_decreasing => 1; # -Y<=X<=Y so MaxAbs=Y sub _NumSeq_Coord_MinAbs_non_decreasing { my ($self) = @_; return ($self->{'sign'} > 0); # yes when to the right } sub _NumSeq_Coord_IntXY_min { my ($self) = @_; return ($self->{'align'} eq 'left' ? -1 : 0); } sub _NumSeq_Coord_BitOr_max { my ($self) = @_; return ($self->{'align'} eq 'left' ? 1 : undef); } use constant _NumSeq_Coord_BitXor_max => 1; use constant _NumSeq_Coord_oeis_anum => { 'align=left,n_start=1' => { Y => 'A076938', # 0,1,1,2,2,3,3,... # OEIS-Other: A076938 planepath=CellularRule,rule=14 coordinate_type=Y # OEIS-Other: A076938 planepath=CellularRule,rule=174 coordinate_type=Y }, 'align=right,n_start=1' => { Y => 'A076938', # 0,1,1,2,2,3,3,... # OEIS-Other: A076938 planepath=CellularRule,rule=84 coordinate_type=Y # OEIS-Other: A076938 planepath=CellularRule,rule=116 coordinate_type=Y }, }; } { package Math::PlanePath::CellularRule::Line; sub _NumSeq_Coord_Radius_integer { my ($path) = @_; # centre Radius=Y so integer, otherwise Radius=sqrt(2)*Y not integer return ($path->{'align'} eq 'centre'); } use constant _NumSeq_Coord_Y_increasing => 1; # line upwards use constant _NumSeq_Coord_Max_increasing => 1; # Max=Y use constant _NumSeq_Coord_Radius_increasing => 1; # line upwards use constant _NumSeq_Coord_TRadius_increasing => 1; # line upwards sub _NumSeq_Coord_TRadius_integer { my ($path) = @_; return ($path->{'sign'} != 0); # left or right sloping } sub _NumSeq_Coord_X_increasing { my ($path) = @_; return ($path->{'sign'} >= 1); # X=Y diagonal } sub _NumSeq_Coord_X_non_decreasing { my ($path) = @_; return ($path->{'sign'} >= 0); # X=0 vertical or X=Y diagonal } *_NumSeq_Coord_Min_non_decreasing = \&_NumSeq_Coord_X_non_decreasing; # Min=X *_NumSeq_Coord_Min_increasing = \&_NumSeq_Coord_X_increasing; # Min=X sub _NumSeq_Coord_Sum_increasing { my ($path) = @_; return ($path->{'sign'} == -1 ? 0 # X=-Y so X+Y=0 : 1); # X=0 so X+Y=Y, or X=Y so X+Y=2Y } use constant _NumSeq_Coord_Sum_non_decreasing => 1; # line upwards use constant _NumSeq_Coord_SumAbs_increasing => 1; # line upwards sub _NumSeq_Coord_Product_increasing { my ($path) = @_; return ($path->{'sign'} > 0 ? 1 # X=Y so X*Y=Y^2 : 0); # X=0 so X*Y=0, or X=-Y so X*Y=-(Y^2) } sub _NumSeq_Coord_Product_non_decreasing { my ($path) = @_; return ($path->{'sign'} >= 0 ? 1 # X=Y so X*Y=Y^2 : 0); # X=0 so X*Y=0, or X=-Y so X*Y=-(Y^2) } # sign=1 X=Y so X-Y=0 always, non-decreasing # sign=0 X=0 so Y-X=Y, increasing # sign=-1 X=-Y so Y-X=2*Y, increasing sub _NumSeq_Coord_DiffXY_non_decreasing { my ($path) = @_; return ($path->{'sign'} == 1 ? 1 # X-Y=0 always : 0); } sub _NumSeq_Coord_DiffYX_increasing { my ($path) = @_; return ($path->{'sign'} == 1 ? 0 : 1); } *_NumSeq_Coord_AbsDiff_increasing = \&_NumSeq_Coord_DiffYX_increasing; use constant _NumSeq_Coord_DiffYX_non_decreasing => 1; # Y-X >= 0 always use constant _NumSeq_Coord_AbsDiff_non_decreasing => 1; # Y-X >= 0 always use constant _NumSeq_Coord_GCD_increasing => 1; # GCD==Y use constant _NumSeq_Coord_MaxAbs_non_decreasing => 1; # -Y<=X<=Y so MaxAbs=Y sub _NumSeq_Coord_ExperimentalParity_max { my ($path) = @_; return ($path->{'align'} eq 'centre' ? 1 : 0); } sub _NumSeq_Coord_ExperimentalNumerator_min { my ($path) = @_; return ($path->{'align'} eq 'left' ? -1 : 0); } sub _NumSeq_Coord_ExperimentalNumerator_max { my ($path) = @_; return ($path->{'align'} eq 'right' ? 1 # right X=Y so 1/1 except for 0/0 : 0); } sub _NumSeq_Coord_ExperimentalNumerator_non_decreasing { my ($path) = @_; return ($path->{'align'} ne 'left'); } # ExperimentalDenominator_min => 0; # 0/0 at n_start() use constant _NumSeq_Coord_ExperimentalDenominator_max => 1; use constant _NumSeq_Coord_ExperimentalDenominator_non_decreasing => 1; # left Y bitand -Y twos-complement gives mask of low 1-bits sub _NumSeq_Coord_BitAnd_non_decreasing { my ($path) = @_; return ($path->{'align'} ne 'left'); # centre BitAnd=0, right BitAnd=Y } sub _NumSeq_Coord_BitAnd_increasing { my ($path) = @_; return ($path->{'align'} eq 'right'); # right BitAnd=Y } # left Y bitor -Y twos-complement gives all-1s above low 0-bits sub _NumSeq_Coord_BitOr_increasing { my ($path) = @_; return ($path->{'align'} ne 'left'); # centre,right BitOr = Y } sub _NumSeq_Coord_BitXor_min { my ($path) = @_; return ($path->{'align'} eq 'left' ? undef : 0); # right X=Y so BitXor=0 always, centre X=0 so BitXor=Y } sub _NumSeq_Coord_BitXor_max { my ($path) = @_; return ($path->{'align'} eq 'centre' ? undef # centre X=0 BitXor=Y : 0); # right X=Y so BitXor=0 always, left negative } sub _NumSeq_Coord_BitXor_increasing { my ($path) = @_; return ($path->{'align'} eq 'centre'); # centre BitXor=Y } # and maximum 0/0=infinity sub _NumSeq_Coord_IntXY_min { my ($path) = @_; return ($path->{'align'} eq 'right' ? 1 # right X=Y so X/Y=1 always : $path->{'align'} eq 'left' ? -1 # left X=-Y so X/Y=-1 always : 0); } use constant _NumSeq_Coord_FracXY_min => 0; # X=0,+Y,-Y so frac=0 use constant _NumSeq_Coord_FracXY_max => 0; use constant _NumSeq_Coord_FracXY_integer => 1; use constant _NumSeq_FracXY_min_is_infimum => 0; use constant _NumSeq_FracXY_max_is_supremum => 0; use constant _NumSeq_Coord_oeis_anum => { 'align=left,n_start=0' => { X => 'A001489', # integers negative X=0,-1,-2,etc Min => 'A001489', # Min=X Y => 'A001477', # integers Y=0,1,2,etc Max => 'A001477', # Max=Y Sum => 'A000004', # all zeros DiffYX => 'A005843', # even 0,2,4,etc RSquared => 'A001105', # 2*n^2 TRSquared => 'A016742', # 4*n^2 # OEIS-Other: A001489 planepath=CellularRule,rule=2,n_start=0 coordinate_type=X # OEIS-Other: A001489 planepath=CellularRule,rule=2,n_start=0 coordinate_type=Min # OEIS-Other: A001477 planepath=CellularRule,rule=2,n_start=0 coordinate_type=Y # OEIS-Other: A001477 planepath=CellularRule,rule=2,n_start=0 coordinate_type=Max # OEIS-Other: A000004 planepath=CellularRule,rule=2,n_start=0 coordinate_type=Sum # OEIS-Other: A005843 planepath=CellularRule,rule=2,n_start=0 coordinate_type=DiffYX # OEIS-Other: A001105 planepath=CellularRule,rule=2,n_start=0 coordinate_type=RSquared # OEIS-Other: A016742 planepath=CellularRule,rule=2,n_start=0 coordinate_type=TRSquared }, 'align=right,n_start=0' => { X => 'A001477', # integers Y=0,1,2,etc Min => 'A001477', # Min=X Y => 'A001477', # integers Y=0,1,2,etc Max => 'A001477', # Max=Y Sum => 'A005843', # even 0,2,4,etc DiffYX => 'A000004', # all zeros DiffXY => 'A000004', # all zeros RSquared => 'A001105', # 2*n^2 TRSquared => 'A016742', # 4*n^2 ExperimentalPairsXY => 'A004526', # 0,0,1,1,2,2,etc cf Math::NumSeq::Runs # OEIS-Other: A001477 planepath=CellularRule,rule=16,n_start=0 coordinate_type=X # OEIS-Other: A001477 planepath=CellularRule,rule=16,n_start=0 coordinate_type=Min # OEIS-Other: A001477 planepath=CellularRule,rule=16,n_start=0 coordinate_type=Y # OEIS-Other: A001477 planepath=CellularRule,rule=16,n_start=0 coordinate_type=Max # OEIS-Other: A005843 planepath=CellularRule,rule=16,n_start=0 coordinate_type=Sum # OEIS-Other: A000004 planepath=CellularRule,rule=16,n_start=0 coordinate_type=DiffYX # OEIS-Other: A000004 planepath=CellularRule,rule=16,n_start=0 coordinate_type=DiffXY # OEIS-Other: A001105 planepath=CellularRule,rule=16,n_start=0 coordinate_type=RSquared # OEIS-Other: A016742 planepath=CellularRule,rule=16,n_start=0 coordinate_type=TRSquared # OEIS-Other: A004526 planepath=CellularRule,rule=16,n_start=0 coordinate_type=ExperimentalPairsXY }, # same as PyramidRows step=0 'align=centre,n_start=0' => Math::PlanePath::PyramidRows->_NumSeq_Coord_oeis_anum()->{'step=0,align=centre,n_start=0'}, # OEIS-Other: A000004 planepath=CellularRule,rule=4,n_start=0 coordinate_type=X # OEIS-Other: A000004 planepath=CellularRule,rule=4,n_start=0 coordinate_type=Min # OEIS-Other: A001477 planepath=CellularRule,rule=4,n_start=0 coordinate_type=Y # OEIS-Other: A001477 planepath=CellularRule,rule=4,n_start=0 coordinate_type=Max # OEIS-Other: A001477 planepath=CellularRule,rule=4,n_start=0 coordinate_type=Sum # OEIS-Other: A001477 planepath=CellularRule,rule=4,n_start=0 coordinate_type=DiffYX # OEIS-Other: A001489 planepath=CellularRule,rule=4,n_start=0 coordinate_type=DiffXY # OEIS-Other: A001477 planepath=CellularRule,rule=4,n_start=0 coordinate_type=AbsDiff # OEIS-Other: A001477 planepath=CellularRule,rule=4,n_start=0 coordinate_type=Radius # OEIS-Other: A001489 planepath=CellularRule,rule=4,n_start=0 coordinate_type=DiffXY # OEIS-Other: A000290 planepath=CellularRule,rule=4,n_start=0 coordinate_type=RSquared # OEIS-Other: A033428 planepath=CellularRule,rule=4,n_start=0 coordinate_type=TRSquared }; # CellularRule starts i=1 value=0, but A000027 is OFFSET=1 value=1 # } elsif ($planepath_object->isa('Math::PlanePath::CellularRule::Line')) { # # for all "rule" parameter values # if ($coordinate_type eq 'Y' # || ($planepath_object->{'sign'} == 0 # && ($coordinate_type eq 'Sum' # || $coordinate_type eq 'DiffYX' # || $coordinate_type eq 'AbsDiff' # || $coordinate_type eq 'Radius'))) { # return 'A000027'; # natural numbers 1,2,3 # # OEIS-Other: A000027 planepath=CellularRule,rule=2 coordinate_type=Y # # OEIS-Other: A000027 planepath=CellularRule,rule=4 coordinate_type=Sum # # OEIS-Other: A000027 planepath=CellularRule,rule=4 coordinate_type=DiffYX # # OEIS-Other: A000027 planepath=CellularRule,rule=4 coordinate_type=AbsDiff # # OEIS-Other: A000027 planepath=CellularRule,rule=4 coordinate_type=Radius # } } { package Math::PlanePath::CellularRule::OddSolid; use constant _NumSeq_Coord_Y_non_decreasing => 1; # rows upwards use constant _NumSeq_Coord_Max_non_decreasing => 1; # Max=Y use constant _NumSeq_Coord_MaxAbs_non_decreasing => 1; # -Y<=X<=Y so MaxAbs=Y use constant _NumSeq_Coord_ExperimentalParity_max => 0; # always even points } { package Math::PlanePath::CellularRule54; use constant _NumSeq_Coord_Y_non_decreasing => 1; # rows upwards use constant _NumSeq_Coord_Max_non_decreasing => 1; # Max=Y use constant _NumSeq_Coord_MaxAbs_non_decreasing => 1; # -Y<=X<=Y so MaxAbs=Y use constant _NumSeq_Coord_IntXY_min => -1; } { package Math::PlanePath::CellularRule57; use constant _NumSeq_Coord_Y_non_decreasing => 1; # rows upwards use constant _NumSeq_Coord_Max_non_decreasing => 1; # Max=Y use constant _NumSeq_Coord_MaxAbs_non_decreasing => 1; # -Y<=X<=Y so MaxAbs=Y use constant _NumSeq_Coord_IntXY_min => -1; } { package Math::PlanePath::CellularRule190; use constant _NumSeq_Coord_Y_non_decreasing => 1; # rows upwards use constant _NumSeq_Coord_Max_non_decreasing => 1; # Max=Y use constant _NumSeq_Coord_MaxAbs_non_decreasing => 1; # -Y<=X<=Y so MaxAbs=Y use constant _NumSeq_Coord_IntXY_min => -1; } { package Math::PlanePath::UlamWarburton; use constant _NumSeq_Coord_filling_type => 'plane'; use constant _NumSeq_Coord_ExperimentalLeafDistance_max => 4; } { package Math::PlanePath::UlamWarburtonQuarter; use constant _NumSeq_Coord_filling_type => 'quadrant'; use constant _NumSeq_Coord_ExperimentalLeafDistance_max => 4; use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always } { package Math::PlanePath::DiagonalRationals; use constant _NumSeq_Coord_Sum_non_decreasing => 1; # X+Y diagonals use constant _NumSeq_Coord_SumAbs_non_decreasing => 1; # X+Y diagonals use constant _NumSeq_Coord_BitAnd_min => 0; # at X=1,Y=2 use constant _NumSeq_Coord_oeis_anum => { 'direction=down,n_start=1' => { X => 'A020652', # numerators Y => 'A020653', # denominators ExperimentalNumerator => 'A020652', # ExperimentalNumerator=X ExperimentalDenominator => 'A020653', # ExperimentalDenominator=Y # OEIS-Catalogue: A020652 planepath=DiagonalRationals coordinate_type=X # OEIS-Catalogue: A020653 planepath=DiagonalRationals coordinate_type=Y # OEIS-Other: A020652 planepath=DiagonalRationals coordinate_type=ExperimentalNumerator # OEIS-Other: A020653 planepath=DiagonalRationals coordinate_type=ExperimentalDenominator # Not quite, A038567 has OFFSET=0 to include 0/1 # Sum => 'A038567', # num+den, is den of fractions X/Y <= 1 # SumAbs => 'A038567' }, 'direction=down,n_start=0' => { AbsDiff => 'A157806', # abs(num-den), OFFSET=0 # OEIS-Other: A157806 planepath=DiagonalRationals,n_start=0 coordinate_type=AbsDiff }, 'direction=up,n_start=1' => { X => 'A020653', # transposed is denominators Y => 'A020652', # transposed is numerators ExperimentalNumerator => 'A020653', # ExperimentalNumerator=X ExperimentalDenominator => 'A020652', # ExperimentalDenominator=Y # OEIS-Other: A020652 planepath=DiagonalRationals,direction=up coordinate_type=Y # OEIS-Other: A020653 planepath=DiagonalRationals,direction=up coordinate_type=X # OEIS-Other: A020653 planepath=DiagonalRationals,direction=up coordinate_type=ExperimentalNumerator # OEIS-Other: A020652 planepath=DiagonalRationals,direction=up coordinate_type=ExperimentalDenominator # Not quite, A038567 has OFFSET=0 to include 0/1 # Sum => 'A038567', # num+den, is den of fractions X/Y <= 1 }, 'direction=up,n_start=0' => { AbsDiff => 'A157806', # abs(num-den), OFFSET=0 # OEIS-Other: A157806 planepath=DiagonalRationals,direction=up,n_start=0 coordinate_type=AbsDiff }, }; } { package Math::PlanePath::FactorRationals; use constant _NumSeq_Coord_BitAnd_min => 0; # at X=1,Y=2 use constant _NumSeq_Coord_oeis_anum => { 'factor_coding=even/odd' => { X => 'A071974', # numerators Y => 'A071975', # denominators Product => 'A019554', # replace squares by their root # OEIS-Catalogue: A071974 planepath=FactorRationals coordinate_type=X # OEIS-Catalogue: A071975 planepath=FactorRationals coordinate_type=Y # OEIS-Catalogue: A019554 planepath=FactorRationals coordinate_type=Product }, 'factor_coding=odd/even' => { X => 'A071975', # denominators Y => 'A071974', # numerators Product => 'A019554', # replace squares by their root # OEIS-Other: A071975 planepath=FactorRationals,factor_coding=odd/even coordinate_type=X # OEIS-Other: A071974 planepath=FactorRationals,factor_coding=odd/even coordinate_type=Y # OEIS-Other: A019554 planepath=FactorRationals,factor_coding=odd/even coordinate_type=Product }, }; } { package Math::PlanePath::GcdRationals; use constant _NumSeq_Coord_BitAnd_min => 0; # at X=1,Y=2 use constant _NumSeq_Coord_oeis_anum => { 'pairs_order=rows' => { X => 'A226314', Y => 'A054531', # T(n,k) = n/GCD(n,k), being denominators # OEIS-Catalogue: A226314 planepath=GcdRationals coordinate_type=X # OEIS-Catalogue: A054531 planepath=GcdRationals coordinate_type=Y }, 'pairs_order=rows_reverse' => { Y => 'A054531', # same # OEIS-Other: A054531 planepath=GcdRationals,pairs_order=rows coordinate_type=Y }, }; } { package Math::PlanePath::CoprimeColumns; use constant _NumSeq_Coord_X_non_decreasing => 1; # columns across use constant _NumSeq_Coord_ExperimentalNumerator_non_decreasing => 1; # ExperimentalNumerator==X use constant _NumSeq_Coord_Max_non_decreasing => 1; # Max==X use constant _NumSeq_Coord_IntXY_min => 1; # octant Y<=X so X/Y>=1 use constant _NumSeq_Coord_BitAnd_min => 0; # at X=2,Y=1 use constant _NumSeq_Coord_oeis_anum => { 'direction=up,n_start=0' => { X => 'A038567', # fractions denominator Max => 'A038567', # Max=X since Y <= X MaxAbs => 'A038567', # MaxAbs=Max # OEIS-Catalogue: A038567 planepath=CoprimeColumns coordinate_type=X # OEIS-Other: A038567 planepath=CoprimeColumns coordinate_type=Max # OEIS-Other: A038567 planepath=CoprimeColumns coordinate_type=MaxAbs }, 'direction=up,n_start=0,i_start=1' => { DiffXY => 'A020653', # diagonals denominators, starting N=1 # OEIS-Other: A020653 planepath=CoprimeColumns coordinate_type=DiffXY i_start=1 }, 'direction=up,n_start=1' => { Y => 'A038566', # fractions numerator Min => 'A038566', # Min=Y since Y <= X MinAbs => 'A038566', # MinAbs=Min # OEIS-Catalogue: A038566 planepath=CoprimeColumns,n_start=1 coordinate_type=Y # OEIS-Other: A038566 planepath=CoprimeColumns,n_start=1 coordinate_type=Min # OEIS-Other: A038566 planepath=CoprimeColumns,n_start=1 coordinate_type=MinAbs }, }; } { package Math::PlanePath::DivisibleColumns; use constant _NumSeq_Coord_X_non_decreasing => 1; # columns across sub _NumSeq_Coord_IntXY_min { my ($self) = @_; return ($self->{'proper'} ? 2 : 1); } use constant _NumSeq_Coord_FracXY_max => 0; # frac(X/Y)=0 always use constant _NumSeq_Coord_FracXY_integer => 1; use constant _NumSeq_FracXY_max_is_supremum => 0; sub _NumSeq_Coord_ExperimentalNumerator_min { my ($self) = @_; return ($self->{'proper'} ? 2 : 1); } # X/Y = Z/1 since X divisible by Y, ExperimentalDenominator=1 always use constant _NumSeq_Coord_ExperimentalDenominator_min => 1; use constant _NumSeq_Coord_ExperimentalDenominator_max => 1; use constant _NumSeq_Coord_ExperimentalDenominator_non_decreasing => 1; use constant _NumSeq_Coord_BitAnd_min => 0; # at X=2,Y=1 sub _NumSeq_Coord_BitXor_min { my ($self) = @_; # octant Y<=X so X-Y>=0 return ($self->{'proper'} ? 2 # at X=3,Y=1 : 0); # at X=1,Y=1 } use constant _NumSeq_Coord_Max_non_decreasing => 1; # Max==X sub _NumSeq_Coord_MaxAbs_min { return $_[0]->x_minimum } # Max=X use constant _NumSeq_Coord_ExperimentalHammingDist_min => 1; # X!=Y use constant _NumSeq_Coord_oeis_anum => { 'divisor_type=all,n_start=1' => { X => 'A061017', # n appears divisors(n) times Max => 'A061017', # Max=X since Y <= X MaxAbs => 'A061017', # MaxAbs=Max Y => 'A027750', # triangle divisors of n Min => 'A027750', # Min=Y since Y <= X MinAbs => 'A027750', # MinAbs=Min GCD => 'A027750', # Y since Y is a divisor of X IntXY => 'A056538', # divisors in reverse order, X/Y give high to low ExperimentalNumerator => 'A056538', # same as int(X/Y) # OEIS-Catalogue: A061017 planepath=DivisibleColumns,n_start=1 coordinate_type=X # OEIS-Other: A061017 planepath=DivisibleColumns,n_start=1 coordinate_type=Max # OEIS-Other: A061017 planepath=DivisibleColumns,n_start=1 coordinate_type=MaxAbs # OEIS-Catalogue: A027750 planepath=DivisibleColumns,n_start=1 coordinate_type=Y # OEIS-Other: A027750 planepath=DivisibleColumns,n_start=1 coordinate_type=Min # OEIS-Other: A027750 planepath=DivisibleColumns,n_start=1 coordinate_type=GCD # OEIS-Catalogue: A056538 planepath=DivisibleColumns,n_start=1 coordinate_type=IntXY # OEIS-Other: A056538 planepath=DivisibleColumns,n_start=1 coordinate_type=ExperimentalNumerator }, 'divisor_type=proper,n_start=2' => { DiffXY => 'A208460', # X-Y AbsDiff => 'A208460', # abs(X-Y) same since Y<=X so X-Y>=0 # OEIS-Catalogue: A208460 planepath=DivisibleColumns,divisor_type=proper,n_start=2 coordinate_type=DiffXY # OEIS-Other: A208460 planepath=DivisibleColumns,divisor_type=proper,n_start=2 coordinate_type=AbsDiff # Not quite, A027751 has an extra 1 at the start from reckoning by # convention 1 as a proper divisor of 1 -- though that's # inconsistent with A032741 count of proper divisors being 0. # # 'divisor_type=proper,n_start=0' => # { Y,Min,GCD => 'A027751', # proper divisors by rows # # OEIS-Catalogue: A027751 planepath=DivisibleColumns,divisor_type=proper coordinate_type=Y # }, }, }; } # { package Math::PlanePath::File; # # File points from a disk file # # FIXME: analyze points for min/max maybe # } # { package Math::PlanePath::QuintetCurve; # # inherit from QuintetCentres # } { package Math::PlanePath::QuintetCentres; use constant _NumSeq_Coord_filling_type => 'plane'; } { package Math::PlanePath::QuintetReplicate; use constant _NumSeq_Coord_filling_type => 'plane'; use constant _NumSeq_Coord_oeis_anum => { 'numbering_type=fixed' => {X => 'A316657', Y => 'A316658', RSquared => 'A316707', # OEIS-Catalogue: A316657 planepath=QuintetReplicate coordinate_type=X # OEIS-Catalogue: A316658 planepath=QuintetReplicate coordinate_type=Y # OEIS-Catalogue: A316707 planepath=QuintetReplicate coordinate_type=RSquared }, }; } { package Math::PlanePath::AR2W2Curve; use constant _NumSeq_Coord_filling_type => 'quadrant'; } { package Math::PlanePath::BetaOmega; use constant _NumSeq_Coord_filling_type => 'half'; } { package Math::PlanePath::KochelCurve; use constant _NumSeq_Coord_filling_type => 'quadrant'; } # { package Math::PlanePath::DekkingCurve; # } { package Math::PlanePath::DekkingCentres; use constant _NumSeq_Coord_filling_type => 'quadrant'; } { package Math::PlanePath::CincoCurve; use constant _NumSeq_Coord_filling_type => 'quadrant'; } { package Math::PlanePath::SquareReplicate; use constant _NumSeq_Coord_filling_type => 'plane'; } { package Math::PlanePath::CornerReplicate; use constant _NumSeq_Coord_filling_type => 'quadrant'; use constant _NumSeq_Coord_oeis_anum => { '' => { Y => 'A059906', # alternate bits second (ZOrderCurve Y) BitXor => 'A059905', # alternate bits first (ZOrderCurve X) ExperimentalHammingDist => 'A139351', # count 1-bits at even bit positions # OEIS-Other: A059906 planepath=CornerReplicate coordinate_type=Y # OEIS-Other: A059905 planepath=CornerReplicate coordinate_type=BitXor # OEIS-Catalogue: A139351 planepath=CornerReplicate coordinate_type=ExperimentalHammingDist }, }; } { package Math::PlanePath::DigitGroups; use constant _NumSeq_Coord_filling_type => 'quadrant'; # # Not quite, A073089 is OFFSET=1 not N=0, also A073089 has extra initial 0 # use constant _NumSeq_Coord_oeis_anum => # { 'radix=2' => # { ExperimentalParity => 'A073089', # DragonMidpoint AbsdY Nodd ^ bit-above-low-0 # # OEIS-Other: A073089 planepath=DigitGroups coordinate_type=ExperimentalParity # }, # }; } # { package Math::PlanePath::FibonacciWordFractal; # } { package Math::PlanePath::LTiling; use constant _NumSeq_Coord_filling_type => 'quadrant'; # X=1,Y=1 doesn't occur, only X=1,Y=2 or X=2,Y=1 { my %_NumSeq_Coord_Max_min = (upper => 1, # X=0,Y=0 not visited by these left => 1, ends => 1); sub _NumSeq_Coord_Max_min { my ($self) = @_; return $_NumSeq_Coord_Max_min{$self->{'L_fill'}} || 0; } } sub _NumSeq_Coord_TRSquared_min { my ($self) = @_; return ($self->{'L_fill'} eq 'upper' ? 3 # X=0,Y=1 : ($self->{'L_fill'} eq 'left' || $self->{'L_fill'} eq 'ends') ? 1 # X=1,Y=0 : 0); # 'middle','all' X=0,Y=0 } { my %BitOr_min = (upper => 1, # X=0,Y=0 not visited by these left => 1, ends => 1); sub _NumSeq_Coord_BitOr_min { my ($self) = @_; return $BitOr_min{$self->{'L_fill'}} || 0; } } *_NumSeq_Coord_BitXor_min = \&_NumSeq_Coord_BitOr_min; { my %_NumSeq_Coord_ExperimentalHammingDist_min = (upper => 1, # X!=Y for these left => 1, ends => 1); sub _NumSeq_Coord_ExperimentalHammingDist_min { my ($self) = @_; return $_NumSeq_Coord_ExperimentalHammingDist_min{$self->{'L_fill'}} || 0; } *_NumSeq_Coord_MaxAbs_min = \&_NumSeq_Coord_ExperimentalHammingDist_min; } # Not quite, A112539 OFFSET=1 versus start N=0 here # use constant _NumSeq_Coord_oeis_anum => # { 'L_fill=left' => # { ExperimentalParity => 'A112539', # thue-morse count1bits mod 2 # # OEIS-Catalogue: A112539 planepath=LTiling,L_fill=left coordinate_type=ExperimentalParity # }, # }; } { package Math::PlanePath::WythoffArray; use constant _NumSeq_Coord_filling_type => 'quadrant'; # FIXME: if x_start=1 but y_start=0 then want corresponding mixture of # A-nums. DiffXY is whenever x_start==y_start. use constant _NumSeq_Coord_oeis_anum => { 'x_start=0,y_start=0' => { Y => 'A019586', # row containing N DiffXY => 'A191360', # diagonal containing N # OEIS-Catalogue: A019586 planepath=WythoffArray coordinate_type=Y # OEIS-Catalogue: A191360 planepath=WythoffArray coordinate_type=DiffXY # Not quite, A035614 has OFFSET start n=0 whereas path starts N=1 # X => 'A035614', }, 'x_start=1,y_start=1' => { X => 'A035612', # column number containing N, start column=1 Y => 'A003603', # row number containing N, starting row=1 DiffXY => 'A191360', # diagonal containing N # OEIS-Catalogue: A035612 planepath=WythoffArray,x_start=1,y_start=1 # OEIS-Catalogue: A003603 planepath=WythoffArray,x_start=1,y_start=1 coordinate_type=Y # OEIS-Other: A191360 planepath=WythoffArray,x_start=1,y_start=1 coordinate_type=DiffXY }, }; } { package Math::PlanePath::PowerArray; use constant _NumSeq_Coord_filling_type => 'quadrant'; use constant _NumSeq_Coord_oeis_anum => { 'radix=2' => { X => 'A007814', # base 2 count low 0s, starting n=1 # main generator Math::NumSeq::DigitCountLow # OEIS-Other: A007814 planepath=PowerArray,radix=2 # Not quite, A025480 starts OFFSET=0 for the k in n=(2k+1)*2^j-1 # Y => 'A025480', # # OEIS-Almost: A025480 i_to_n_offset=-1 planepath=PowerArray,radix=2 coordinate_type=Y }, 'radix=3' => { X => 'A007949', # k of greatest 3^k dividing n # OEIS-Other: A007949 planepath=PowerArray,radix=3 # main generator Math::NumSeq::DigitCountLow }, 'radix=5' => { X => 'A112765', # OEIS-Other: A112765 planepath=PowerArray,radix=5 }, 'radix=6' => { X => 'A122841', # OEIS-Other: A122841 planepath=PowerArray,radix=6 }, 'radix=10' => { X => 'A122840', # OEIS-Other: A112765 planepath=PowerArray,radix=5 }, }; } { package Math::PlanePath::ToothpickTree; sub _NumSeq_Coord_Max_min { my ($self) = @_; if ($self->{'parts'} eq '3') { return 0; } return $self->SUPER::_NumSeq_Coord_Max_min; } { my %_NumSeq_Coord_IntXY_min = (1 => 0, octant => 0, # X>=Y-1 so X/Y >= 1-1/Y wedge => -1, # X>=-Y,Y>=0 so X/Y<=-1 ); sub _NumSeq_Coord_IntXY_min { my ($self) = @_; return $_NumSeq_Coord_IntXY_min{$self->{'parts'}}; } } { my %_NumSeq_Coord_IntXY_max = (octant_up => 0, # except wedge 0/0 = infinity # wedge => 1, # Y>=X so X/Y<=1 ); sub _NumSeq_Coord_IntXY_max { my ($self) = @_; return $_NumSeq_Coord_IntXY_max{$self->{'parts'}}; } } sub _NumSeq_Coord_BitAnd_min { my ($self) = @_; return ($self->{'parts'} eq '4' ? undef # X<0,Y<0 : 0); # otherwise X>0 or Y>0 so BitAnd>=0 } { my %_NumSeq_Coord_TRSquared_min = (2 => 3, # X=0,Y=1 1 => 4, # X=1,Y=1 octant => 4, # X=1,Y=1 octant_up => 13, # X=1,Y=2 ); sub _NumSeq_Coord_TRSquared_min { my ($self) = @_; return ($_NumSeq_Coord_TRSquared_min{$self->{'parts'}} || 0); } } { # usually 7, but in these 8 my %_NumSeq_Coord_ExperimentalLeafDistance_max = (octant => 8, octant_up => 8, wedge => 8, ); sub _NumSeq_Coord_ExperimentalLeafDistance_max { my ($self) = @_; return ($_NumSeq_Coord_ExperimentalLeafDistance_max{$self->{'parts'}} || 7); } } } { package Math::PlanePath::ToothpickReplicate; *_NumSeq_Coord_BitAnd_min = \&Math::PlanePath::ToothpickTree::_NumSeq_Coord_BitAnd_min; *_NumSeq_Coord_TRSquared_min = \&Math::PlanePath::ToothpickTree::_NumSeq_Coord_TRSquared_min; } { package Math::PlanePath::ToothpickUpist; use constant _NumSeq_Coord_Y_non_decreasing => 1; # rows upwards use constant _NumSeq_Coord_Max_non_decreasing => 1; # X<=Y so max=Y use constant _NumSeq_Coord_MaxAbs_non_decreasing => 1; # -Y<=X<=Y so MaxAbs=Y use constant _NumSeq_Coord_ExperimentalLeafDistance_max => 9; } { package Math::PlanePath::LCornerTree; sub _NumSeq_Coord_Max_min { my ($self) = @_; return ($self->{'parts'} eq '4' ? undef : 0); } { my %_NumSeq_Coord_IntXY_min = (1 => 0, octant => 1, # X>=Y so X/Y>=1, and 0/0 'octant+1' => 0, # X>=Y-1 so int(X/Y)>=0 octant_up => 0, # X>=0 so X/Y>=1, and 0/0 'octant_up+1' => 0, # X>=0 so X/Y>=1, and 0/0 wedge => -2, # X>=-Y-1 so X/Y>=-2 'wedge+1' => -3, # X>=-Y-2 so X/Y>=-3 ); sub _NumSeq_Coord_IntXY_min { my ($self) = @_; return $_NumSeq_Coord_IntXY_min{$self->{'parts'}}; } } use constant _NumSeq_Coord_ExperimentalLeafDistance_max => 2; } { package Math::PlanePath::LCornerReplicate; use constant _NumSeq_Coord_filling_type => 'quadrant'; } # { package Math::PlanePath::PeninsulaBridge; # } { package Math::PlanePath::OneOfEight; sub _NumSeq_Coord_Max_min { my ($self) = @_; return ($self->{'parts'} eq '4' ? undef : 0); } sub _NumSeq_Coord_IntXY_min { my ($self) = @_; if ($self->{'parts'} eq 'octant') { return 1; } return $self->SUPER::_NumSeq_Coord_IntXY_min; } { # usually 2, but in 3side only 1 my %_NumSeq_Coord_ExperimentalLeafDistance_max = ('3side' => 1, ); sub _NumSeq_Coord_ExperimentalLeafDistance_max { my ($self) = @_; return $_NumSeq_Coord_ExperimentalLeafDistance_max{$self->{'parts'}} || 2; } } } { package Math::PlanePath::HTree; use constant _NumSeq_Coord_Depth_non_decreasing => 0; use constant _NumSeq_Coord_NumSiblings_non_decreasing => 1; } #------------------------------------------------------------------------------ 1; __END__ # sub pred { # my ($self, $value) = @_; # # my $planepath_object = $self->{'planepath_object'}; # my $figure = $planepath_object->figure; # if ($figure eq 'square') { # if ($value != int($value)) { # return 0; # } # } elsif ($figure eq 'circle') { # return 1; # } # # my $coordinate_type = $self->{'coordinate_type'}; # if ($coordinate_type eq 'X') { # if ($planepath_object->x_negative) { # return 1; # } else { # return ($value >= 0); # } # } elsif ($coordinate_type eq 'Y') { # if ($planepath_object->y_negative) { # return 1; # } else { # return ($value >= 0); # } # } elsif ($coordinate_type eq 'Sum') { # if ($planepath_object->x_negative || $planepath_object->y_negative) { # return 1; # } else { # return ($value >= 0); # } # } elsif ($coordinate_type eq 'RSquared') { # # FIXME: only sum of two squares, and for triangular same odd/even. # # Factorize or search ? # return ($value >= 0); # } # # return undef; # } =for stopwords Ryde Math-PlanePath PlanePath DiffXY AbsDiff IntXY FracXY OEIS NumSeq SquareSpiral SumAbs Manhattan ie TRadius TRSquared RSquared DiffYX BitAnd BitOr BitXor bitand bitwise gnomon MinAbs gnomons MaxAbs =head1 NAME Math::NumSeq::PlanePathCoord -- sequence of coordinate values from a PlanePath module =head1 SYNOPSIS use Math::NumSeq::PlanePathCoord; my $seq = Math::NumSeq::PlanePathCoord->new (planepath => 'SquareSpiral', coordinate_type => 'X'); my ($i, $value) = $seq->next; =head1 DESCRIPTION This is a tie-in to make a C sequence giving coordinate values from a C. The NumSeq "i" index is the PlanePath "N" value. The C choices are as follows. Generally they have some sort of geometric interpretation or are related to fractions X/Y. "X" X coordinate "Y" Y coordinate "Min" min(X,Y) "Max" max(X,Y) "MinAbs" min(abs(X),abs(Y)) "MaxAbs" max(abs(X),abs(Y)) "Sum" X+Y sum "SumAbs" abs(X)+abs(Y) sum "Product" X*Y product "DiffXY" X-Y difference "DiffYX" Y-X difference (negative of DiffXY) "AbsDiff" abs(X-Y) difference "Radius" sqrt(X^2+Y^2) radial distance "RSquared" X^2+Y^2 radius squared (norm) "TRadius" sqrt(X^2+3*Y^2) triangular radius "TRSquared" X^2+3*Y^2 triangular radius squared (norm) "IntXY" int(X/Y) division rounded towards zero "FracXY" frac(X/Y) division rounded towards zero "BitAnd" X bitand Y "BitOr" X bitor Y "BitXor" X bitxor Y "GCD" greatest common divisor X,Y "Depth" tree_n_to_depth() "SubHeight" tree_n_to_subheight() "NumChildren" tree_n_num_children() "NumSiblings" not including self "RootN" the N which is the tree root "IsLeaf" 0 or 1 whether a leaf node (no children) "IsNonLeaf" 0 or 1 whether a non-leaf node (has children) also called an "internal" node =head2 Min and Max "Min" and "Max" are the minimum or maximum of X and Y. The geometric interpretation of "Min" is to select X at any point above the X=Y diagonal or Y for any point below. Conversely "Max" is Y above and X below. On the X=Y diagonal itself X=Y=Min=Max. Max=Y / X=Y diagonal Min=X | / |/ ---o---- /| / | Max=X / Min=Y XMin and Max can also be interpreted as counting which gnomon shaped line the X,Y falls on. | | | | Min=gnomon 2 ------------. Max=gnomon | | | | 1 ----------. | | | | | ... 0 --------o | | | | | ------ 1 -1 ------. | | | | | o-------- 0 ... | | | | | ---------- -1 | | | | ------------ -2 | | | | =head2 MinAbs XMinAbs = min(abs(X),abs(Y)) can be interpreted geometrically as counting gnomons successively away from the origin. This is like Min above, but within the quadrant containing X,Y. | | | | | MinAbs=gnomon counted away from the origin | | | | | 2 --- | | | ---- 2 1 ----- | ------ 1 0 -------o-------- 0 1 ----- | ------ 1 2 --- | | | ---- 2 | | | | | | | | | | =head2 MaxAbs MaxAbs = max(abs(X),abs(Y)) can be interpreted geometrically as counting successive squares around the origin. +-----------+ MaxAbs=which square | +-------+ | | | +---+ | | | | | o | | | | | +---+ | | | +-------+ | +-----------+ For example L loops around in squares and so its MaxAbs is unchanged until it steps out to the next bigger square. =head2 Sum and Diff "Sum"=X+Y and "DiffXY"=X-Y can be interpreted geometrically as coordinates on 45-degree diagonals. Sum is a measure up along the leading diagonal and DiffXY down an anti-diagonal, \ / \ s=X+Y / \ ^\ \ / \ \ | / v \|/ * d=X-Y ---o---- /|\ / | \ / | \ / \ / \ / \ Or "Sum" can be thought of as a count of which anti-diagonal stripe contains X,Y, or a projection onto the X=Y leading diagonal. Sum \ = anti-diag 2 numbering / / / / DiffXY \ \ X+Y -1 0 1 2 = diagonal 1 2 / / / / numbering \ \ \ -1 0 1 2 X-Y 0 1 2 / / / \ \ \ 0 1 2 =head2 DiffYX "DiffYX" = Y-X is simply the negative of DiffXY. It's included to give positive values on paths which are above the X=Y leading diagonal. For example DiffXY is positive in C which is below X=Y, whereas DiffYX is positive in C which is above X=Y. =head2 SumAbs X"SumAbs" = abs(X)+abs(Y) is similar to the projection described above for Sum or Diff, but SumAbs projects onto the central diagonal of whichever quadrant contains the X,Y. Or equivalently it's a numbering of anti-diagonals within that quadrant, so numbering which diamond shape the X,Y falls on. | /|\ SumAbs = which diamond X,Y falls on / | \ / | \ -----o----- \ | / \ | / \|/ | As an example, the C path loops around on such diamonds, so its SumAbs is unchanged until completing a loop and stepping out to the next bigger. XXSumAbs is also a "taxi-cab" or "Manhattan" distance, being how far to travel through a square-grid city to get to X,Y. SumAbs = taxi-cab distance, by any square-grid travel +-----o +--o o | | | | +--+ +-----+ | | | * * * If a path is entirely XE=0,YE=0 in the first quadrant then Sum and SumAbs are identical. =head2 AbsDiff "AbsDiff" = abs(X-Y) can be interpreted geometrically as the distance away from the X=Y diagonal, measured at right-angles to that line. d=abs(X-Y) ^ / X=Y line \ / \/ /\ / \ |/ \ --o-- \ /| v / d=abs(X-Y) If a path is entirely below the X=Y line, so XE=Y, then AbsDiff is the same as DiffXY. Or if a path is entirely above the X=Y line, so YE=X, then AbsDiff is the same as DiffYX. =head2 Radius and RSquared Radius and RSquared are per C<$path-En_to_radius()> and C<$path-En_to_rsquared()> respectively (see L). =head2 TRadius and TRSquared "TRadius" and "TRSquared" are designed for use with points on a triangular lattice as per L. For points on the X axis TRSquared is the same as RSquared but off the axis Y is scaled up by factor sqrt(3). Most triangular paths use "even" points X==Y mod 2 and for them TRSquared is always even. Some triangular paths such as C have an offset from the origin and use "odd" points X!=Y mod 2 and for them TRSquared is odd. =head2 IntXY and FracXY "IntXY" = int(X/Y) is the quotient from X divide Y rounded to an integer towards zero. This is like the integer part of a fraction, for example X=9,Y=4 is 9/4 = 2+1/4 so IntXY=2. Negatives are reckoned with the fraction part negated too, so -2 1/4 is -2-1/4 and thus IntXY=-2. Geometrically IntXY gives which wedge of slope 1, 2, 3, etc the point X,Y falls in. For example IntXY is 3 for all points in the wedge 3YE=XE4Y. X=Y X=2Y X=3Y X=4Y * -2 * -1 * 0 | 0 * 1 * 2 * 3 * * * * | * * * * * * * | * * * * * * * | * * * * * * * | * * * * * * * | * * * * ***|**** ---------------------+---------------------------- **|** * * | * * * * | * * * * | * * * * | * * 2 * 1 * 0 | 0 * -1 * -2 "FracXY" is the fraction part which goes with IntXY. In all cases X/Y = IntXY + FracXY IntXY rounds towards zero so the remaining FracXY has the same sign as IntXY. =head2 BitAnd, BitOr, BitXor "BitAnd", "BitOr" and "BitXor" treat negative X or negative Y as infinite twos-complement 1-bits, which means for example X=-1,Y=-2 has X bitand Y = -2. ...11111111 X=-1 ...11111110 Y=-2 ----------- ...11111110 X bitand Y = -2 This twos-complement is per C (which has bitwise operations in Perl 5.6 and up). The code here arranges the same on ordinary scalars. If X or Y are not integers then the fractional parts are treated bitwise too, but currently only to limited precision. =head1 FUNCTIONS See L for behaviour common to all sequence classes. =over 4 =item C<$seq = Math::NumSeq::PlanePathCoord-Enew (planepath =E $name, coordinate_type =E $str)> Create and return a new sequence object. The options are planepath string, name of a PlanePath module planepath_object PlanePath object coordinate_type string, as described above C can be either the module part such as "SquareSpiral" or a full class name "Math::PlanePath::SquareSpiral". =item C<$value = $seq-Eith($i)> Return the coordinate at N=$i in the PlanePath. =item C<$i = $seq-Ei_start()> Return the first index C<$i> in the sequence. This is the position C returns to. This is C<$path-En_start()> from the PlanePath, since the i numbering is the N numbering of the underlying path. For some of the C generated sequences there may be a higher C corresponding to a higher starting point in the OEIS, though this is slightly experimental. =item C<$str = $seq-Eoeis_anum()> Return the A-number (a string) for C<$seq> in Sloane's Online Encyclopedia of Integer Sequences, or return C if not in the OEIS or not known. Known A-numbers are also presented through C. This means PlanePath related OEIS sequences can be created with C by giving their A-number in the usual way for that module. =back =head1 SEE ALSO L, L, L, L, L L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut #------------------------------------------------------------------------------ # Maybe: # # LeafDist # LeafDistDown # # ExperimentalNumerator = X*sgn(Y) / gcd(X,Y) X/Y in least terms, num + or - # ExperimentalDenominator = abs(Y) / gcd(X,Y) den >=0 # X/0 keep as numerator=X ? or reduce to 1/0 ? # 0/Y keep as denominator=Y ? or reduce to 0/1 ? # # ParentDegree -- num siblings and also self # # CfracLength,ExperimentalGcdDivisions,GcdSteps,EuclidSteps # -- terms in cfrac(X/Y), excluding int=0 if X=1 # # ExperimentalKroneckerSymbol(a,b) (a/2)=(2/a), or (a/2)=0 if a even # # Theta angle in radians # AngleFrac # AngleRadians # Theta360 angle matching Radius,RSquared # TTheta360 angle matching TRadius,TRSquared # # IsRational -- Chi(x) = 1 if x rational, 0 if irrational # Dirichlet function D(x) = 1/b if rational x=a/b least terms, 0 if irrational # Multiplicative distance A130836 X,Y>=1 # sum abs(exponent-exponent) of each prime # A130849 total/2 muldist along diagonal Math-PlanePath-129/lib/Math/NumSeq/OEIS/0002755000175000017500000000000014001441522015307 5ustar ggggMath-PlanePath-129/lib/Math/NumSeq/OEIS/Catalogue/0002755000175000017500000000000014001441522017213 5ustar ggggMath-PlanePath-129/lib/Math/NumSeq/OEIS/Catalogue/Plugin/0002755000175000017500000000000014001441522020451 5ustar ggggMath-PlanePath-129/lib/Math/NumSeq/OEIS/Catalogue/Plugin/PlanePath.pm0000644000175000017500000016141714001441521022672 0ustar gggg# Copyright 2011, 2012, 2013, 2014 Kevin Ryde # Generated by Math-NumSeq tools/make-oeis-catalogue.pl -- DO NOT EDIT # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::NumSeq::OEIS::Catalogue::Plugin::PlanePath; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::NumSeq::OEIS::Catalogue::Plugin; @ISA = ('Math::NumSeq::OEIS::Catalogue::Plugin'); ## no critic (CodeLayout::RequireTrailingCommaAtNewline) # total 300 A-numbers in 4 modules use constant info_arrayref => [ { 'anum' => 'A174344', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'SquareSpiral', 'coordinate_type', 'X' ] }, { 'anum' => 'A214526', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'SquareSpiral', 'coordinate_type', 'SumAbs' ] }, { 'anum' => 'A336336', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'SquareSpiral', 'coordinate_type', 'RSquared' ] }, { 'anum' => 'A180714', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'SquareSpiral,n_start=0', 'coordinate_type', 'Sum' ] }, { 'anum' => 'A329116', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PyramidSpiral,n_start=0', 'coordinate_type', 'X' ] }, { 'anum' => 'A329972', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PyramidSpiral,n_start=0', 'coordinate_type', 'Y' ] }, { 'anum' => 'A053615', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PyramidSpiral,n_start=0', 'coordinate_type', 'ExperimentalAbsX' ] }, { 'anum' => 'A010751', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'DiamondSpiral,n_start=0' ] }, { 'anum' => 'A305258', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'DiamondSpiral,n_start=0', 'coordinate_type', 'Y' ] }, { 'anum' => 'A328818', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'HexSpiral,n_start=0', 'coordinate_type', 'X' ] }, { 'anum' => 'A307012', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'HexSpiral,n_start=0', 'coordinate_type', 'Y' ] }, { 'anum' => 'A321768', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PythagoreanTree', 'coordinate_type', 'X' ] }, { 'anum' => 'A321769', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PythagoreanTree', 'coordinate_type', 'Y' ] }, { 'anum' => 'A321770', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PythagoreanTree,coordinates=AC', 'coordinate_type', 'Y' ] }, { 'anum' => 'A321782', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PythagoreanTree,coordinates=PQ', 'coordinate_type', 'X' ] }, { 'anum' => 'A321783', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PythagoreanTree,coordinates=PQ', 'coordinate_type', 'Y' ] }, { 'anum' => 'A321784', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PythagoreanTree,coordinates=PQ', 'coordinate_type', 'Sum' ] }, { 'anum' => 'A321785', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PythagoreanTree,coordinates=PQ', 'coordinate_type', 'DiffXY' ] }, { 'anum' => 'A153036', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'RationalsTree', 'coordinate_type', 'IntXY' ] }, { 'anum' => 'A000523', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'RationalsTree', 'coordinate_type', 'Depth' ] }, { 'anum' => 'A070871', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'RationalsTree,tree_type=CW', 'coordinate_type', 'Product' ] }, { 'anum' => 'A020650', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'RationalsTree,tree_type=AYT', 'coordinate_type', 'X' ] }, { 'anum' => 'A020651', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'RationalsTree,tree_type=AYT', 'coordinate_type', 'Y' ] }, { 'anum' => 'A135523', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'RationalsTree,tree_type=AYT', 'coordinate_type', 'IntXY' ] }, { 'anum' => 'A162909', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'RationalsTree,tree_type=Bird', 'coordinate_type', 'X' ] }, { 'anum' => 'A162910', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'RationalsTree,tree_type=Bird', 'coordinate_type', 'Y' ] }, { 'anum' => 'A162911', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'RationalsTree,tree_type=Drib', 'coordinate_type', 'X' ] }, { 'anum' => 'A162912', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'RationalsTree,tree_type=Drib', 'coordinate_type', 'Y' ] }, { 'anum' => 'A174981', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'RationalsTree,tree_type=L', 'coordinate_type', 'X' ] }, { 'anum' => 'A086592', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'FractionsTree', 'coordinate_type', 'Y' ] }, { 'anum' => 'A191379', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'ChanTree' ] }, { 'anum' => 'A163528', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PeanoCurve', 'coordinate_type', 'X' ] }, { 'anum' => 'A163529', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PeanoCurve', 'coordinate_type', 'Y' ] }, { 'anum' => 'A163530', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PeanoCurve', 'coordinate_type', 'Sum' ] }, { 'anum' => 'A163531', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PeanoCurve', 'coordinate_type', 'RSquared' ] }, { 'anum' => 'A059253', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'HilbertCurve', 'coordinate_type', 'X' ] }, { 'anum' => 'A059252', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'HilbertCurve', 'coordinate_type', 'Y' ] }, { 'anum' => 'A059261', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'HilbertCurve', 'coordinate_type', 'Sum' ] }, { 'anum' => 'A059285', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'HilbertCurve', 'coordinate_type', 'DiffXY' ] }, { 'anum' => 'A163547', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'HilbertCurve', 'coordinate_type', 'RSquared' ] }, { 'anum' => 'A059905', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'ZOrderCurve', 'coordinate_type', 'X' ] }, { 'anum' => 'A059906', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'ZOrderCurve', 'coordinate_type', 'Y' ] }, { 'anum' => 'A292373', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'ZOrderCurve', 'coordinate_type', 'BitAnd' ] }, { 'anum' => 'A309952', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'ZOrderCurve', 'coordinate_type', 'BitXor' ] }, { 'anum' => 'A163325', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'ZOrderCurve,radix=3', 'coordinate_type', 'X' ] }, { 'anum' => 'A163326', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'ZOrderCurve,radix=3', 'coordinate_type', 'Y' ] }, { 'anum' => 'A080463', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'ZOrderCurve,radix=10', 'coordinate_type', 'Sum' ] }, { 'anum' => 'A080464', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'ZOrderCurve,radix=10', 'coordinate_type', 'Product', 'i_start', 10 ] }, { 'anum' => 'A080465', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'ZOrderCurve,radix=10', 'coordinate_type', 'AbsDiff', 'i_start', 10 ] }, { 'anum' => 'A334486', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Flowsnake', 'coordinate_type', 'Y' ] }, { 'anum' => 'A332246', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'QuadricCurve', 'coordinate_type', 'X' ] }, { 'anum' => 'A332247', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'QuadricCurve', 'coordinate_type', 'Y' ] }, { 'anum' => 'A334483', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'SierpinskiArrowhead,align=diagonal', 'coordinate_type', 'X' ] }, { 'anum' => 'A334484', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'SierpinskiArrowhead,align=diagonal', 'coordinate_type', 'Y' ] }, { 'anum' => 'A334235', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'HIndexing', 'coordinate_type', 'X' ] }, { 'anum' => 'A334236', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'HIndexing', 'coordinate_type', 'Y' ] }, { 'anum' => 'A332383', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'DragonCurve', 'coordinate_type', 'X' ] }, { 'anum' => 'A332384', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'DragonCurve', 'coordinate_type', 'Y' ] }, { 'anum' => 'A334576', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'AlternatePaperMidpoint', 'coordinate_type', 'X' ] }, { 'anum' => 'A334577', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'AlternatePaperMidpoint', 'coordinate_type', 'Y' ] }, { 'anum' => 'A332251', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'CCurve', 'coordinate_type', 'X' ] }, { 'anum' => 'A332252', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'CCurve', 'coordinate_type', 'Y' ] }, { 'anum' => 'A318438', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'ComplexMinus', 'coordinate_type', 'X' ] }, { 'anum' => 'A318439', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'ComplexMinus', 'coordinate_type', 'Y' ] }, { 'anum' => 'A318479', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'ComplexMinus', 'coordinate_type', 'RSquared' ] }, { 'anum' => 'A057554', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals', 'coordinate_type', 'ExperimentalPairsXY' ] }, { 'anum' => 'A164306', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,x_start=1,y_start=0', 'coordinate_type', 'ExperimentalNumerator' ] }, { 'anum' => 'A167192', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,x_start=1,y_start=0', 'coordinate_type', 'ExperimentalDenominator' ] }, { 'anum' => 'A003991', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,x_start=1,y_start=1', 'coordinate_type', 'Product' ] }, { 'anum' => 'A003989', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,x_start=1,y_start=1', 'coordinate_type', 'GCD' ] }, { 'anum' => 'A003983', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,x_start=1,y_start=1', 'coordinate_type', 'Min' ] }, { 'anum' => 'A051125', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,x_start=1,y_start=1', 'coordinate_type', 'Max' ] }, { 'anum' => 'A004199', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,x_start=1,y_start=1', 'coordinate_type', 'IntXY' ] }, { 'anum' => 'A057555', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,x_start=1,y_start=1', 'coordinate_type', 'ExperimentalPairsXY' ] }, { 'anum' => 'A003988', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,direction=up,x_start=1,y_start=1', 'coordinate_type', 'IntXY' ] }, { 'anum' => 'A112543', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,direction=up,x_start=1,y_start=1', 'coordinate_type', 'ExperimentalNumerator' ] }, { 'anum' => 'A112544', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,direction=up,x_start=1,y_start=1', 'coordinate_type', 'ExperimentalDenominator' ] }, { 'anum' => 'A004247', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,n_start=0', 'coordinate_type', 'Product' ] }, { 'anum' => 'A114327', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,n_start=0', 'coordinate_type', 'DiffYX' ] }, { 'anum' => 'A049581', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,n_start=0', 'coordinate_type', 'AbsDiff' ] }, { 'anum' => 'A048147', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,n_start=0', 'coordinate_type', 'RSquared' ] }, { 'anum' => 'A004198', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,n_start=0', 'coordinate_type', 'BitAnd' ] }, { 'anum' => 'A003986', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,n_start=0', 'coordinate_type', 'BitOr' ] }, { 'anum' => 'A003987', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,n_start=0', 'coordinate_type', 'BitXor' ] }, { 'anum' => 'A109004', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,n_start=0', 'coordinate_type', 'GCD' ] }, { 'anum' => 'A004197', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,n_start=0', 'coordinate_type', 'Min' ] }, { 'anum' => 'A003984', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,n_start=0', 'coordinate_type', 'Max' ] }, { 'anum' => 'A101080', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Diagonals,n_start=0', 'coordinate_type', 'ExperimentalHammingDist' ] }, { 'anum' => 'A319572', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'DiagonalsAlternating,n_start=0', 'coordinate_type', 'X' ] }, { 'anum' => 'A319573', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'DiagonalsAlternating,n_start=0', 'coordinate_type', 'Y' ] }, { 'anum' => 'A055087', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'DiagonalsOctant,n_start=0', 'coordinate_type', 'X' ] }, { 'anum' => 'A055086', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'DiagonalsOctant,n_start=0', 'coordinate_type', 'Sum' ] }, { 'anum' => 'A082375', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'DiagonalsOctant,n_start=0', 'coordinate_type', 'DiffYX' ] }, { 'anum' => 'A213088', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'Corner', 'coordinate_type', 'Sum' ] }, { 'anum' => 'A319290', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'CornerAlternating,n_start=0', 'coordinate_type', 'X' ] }, { 'anum' => 'A319289', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'CornerAlternating,n_start=0', 'coordinate_type', 'Y' ] }, { 'anum' => 'A079904', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PyramidRows,step=1,n_start=0', 'coordinate_type', 'Product' ] }, { 'anum' => 'A069011', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PyramidRows,step=1,n_start=0', 'coordinate_type', 'RSquared' ] }, { 'anum' => 'A080099', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PyramidRows,step=1,n_start=0', 'coordinate_type', 'BitAnd' ] }, { 'anum' => 'A080098', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PyramidRows,step=1,n_start=0', 'coordinate_type', 'BitOr' ] }, { 'anum' => 'A051933', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PyramidRows,step=1,n_start=0', 'coordinate_type', 'BitXor' ] }, { 'anum' => 'A196199', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PyramidRows,n_start=0', 'coordinate_type', 'X' ] }, { 'anum' => 'A000196', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PyramidRows,n_start=0', 'coordinate_type', 'Y' ] }, { 'anum' => 'A180447', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PyramidRows,step=3,n_start=0', 'coordinate_type', 'Y' ] }, { 'anum' => 'A060511', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'PyramidRows,step=4,align=right,n_start=0', 'coordinate_type', 'X' ] }, { 'anum' => 'A004396', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'CellularRule,rule=6,n_start=0', 'coordinate_type', 'Y' ] }, { 'anum' => 'A131452', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'CellularRule,rule=6,n_start=0', 'coordinate_type', 'SumAbs' ] }, { 'anum' => 'A004523', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'CellularRule,rule=20,n_start=0', 'coordinate_type', 'X' ] }, { 'anum' => 'A004773', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'CellularRule,rule=20,n_start=0', 'coordinate_type', 'SumAbs' ] }, { 'anum' => 'A020652', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'DiagonalRationals', 'coordinate_type', 'X' ] }, { 'anum' => 'A020653', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'DiagonalRationals', 'coordinate_type', 'Y' ] }, { 'anum' => 'A071974', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'FactorRationals', 'coordinate_type', 'X' ] }, { 'anum' => 'A071975', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'FactorRationals', 'coordinate_type', 'Y' ] }, { 'anum' => 'A019554', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'FactorRationals', 'coordinate_type', 'Product' ] }, { 'anum' => 'A226314', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'GcdRationals', 'coordinate_type', 'X' ] }, { 'anum' => 'A054531', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'GcdRationals', 'coordinate_type', 'Y' ] }, { 'anum' => 'A038567', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'CoprimeColumns', 'coordinate_type', 'X' ] }, { 'anum' => 'A038566', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'CoprimeColumns,n_start=1', 'coordinate_type', 'Y' ] }, { 'anum' => 'A061017', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'DivisibleColumns,n_start=1', 'coordinate_type', 'X' ] }, { 'anum' => 'A027750', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'DivisibleColumns,n_start=1', 'coordinate_type', 'Y' ] }, { 'anum' => 'A056538', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'DivisibleColumns,n_start=1', 'coordinate_type', 'IntXY' ] }, { 'anum' => 'A208460', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'DivisibleColumns,divisor_type=proper,n_start=2', 'coordinate_type', 'DiffXY' ] }, { 'anum' => 'A316657', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'QuintetReplicate', 'coordinate_type', 'X' ] }, { 'anum' => 'A316658', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'QuintetReplicate', 'coordinate_type', 'Y' ] }, { 'anum' => 'A316707', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'QuintetReplicate', 'coordinate_type', 'RSquared' ] }, { 'anum' => 'A139351', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'CornerReplicate', 'coordinate_type', 'ExperimentalHammingDist' ] }, { 'anum' => 'A019586', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'WythoffArray', 'coordinate_type', 'Y' ] }, { 'anum' => 'A191360', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'WythoffArray', 'coordinate_type', 'DiffXY' ] }, { 'anum' => 'A035612', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'WythoffArray,x_start=1,y_start=1' ] }, { 'anum' => 'A003603', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'WythoffArray,x_start=1,y_start=1', 'coordinate_type', 'Y' ] }, { 'anum' => 'A079813', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'SquareSpiral', 'delta_type', 'AbsdY' ] }, { 'anum' => 'A339265', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'PyramidSpiral,n_start=0', 'delta_type', 'dX' ] }, { 'anum' => 'A204439', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'TriangleSpiralSkewed,skew=left', 'delta_type', 'AbsdX' ] }, { 'anum' => 'A204437', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'TriangleSpiralSkewed,skew=left', 'delta_type', 'AbsdY' ] }, { 'anum' => 'A204435', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'TriangleSpiralSkewed,skew=right', 'delta_type', 'AbsdX' ] }, { 'anum' => 'A003982', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'DiamondSpiral,n_start=0', 'delta_type', 'dSumAbs' ] }, { 'anum' => 'A023532', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'AztecDiamondRings,n_start=0', 'delta_type', 'AbsdY' ] }, { 'anum' => 'A070990', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'RationalsTree,tree_type=L', 'delta_type', 'dY' ] }, { 'anum' => 'A011655', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'KochCurve', 'delta_type', 'AbsdY' ] }, { 'anum' => 'A246960', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'DragonCurve', 'delta_type', 'Dir4' ] }, { 'anum' => 'A166486', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'DragonRounded', 'delta_type', 'AbsdY' ] }, { 'anum' => 'A152822', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'DragonRounded', 'delta_type', 'AbsdX' ] }, { 'anum' => 'A010059', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'CCurve', 'delta_type', 'AbsdX' ] }, { 'anum' => 'A033999', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'Rows,width=2,n_start=0', 'delta_type', 'dX' ] }, { 'anum' => 'A124625', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'Rows,width=2,n_start=0', 'delta_type', 'dRSquared' ] }, { 'anum' => 'A010673', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'Rows,width=2,n_start=0', 'delta_type', 'TDir6' ] }, { 'anum' => 'A061347', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'Rows,width=3', 'delta_type', 'dX' ] }, { 'anum' => 'A131561', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'Rows,width=3,n_start=0', 'delta_type', 'dSum' ] }, { 'anum' => 'A127949', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'Diagonals', 'delta_type', 'dY' ] }, { 'anum' => 'A051340', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'Diagonals,n_start=0', 'delta_type', 'AbsdY' ] }, { 'anum' => 'A023531', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'PyramidRows,step=1,n_start=0', 'delta_type', 'dY' ] }, { 'anum' => 'A049240', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'PyramidSides', 'delta_type', 'AbsdY' ] }, { 'anum' => 'A177702', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'CellularRule,rule=20,n_start=0', 'delta_type', 'dSumAbs' ] }, { 'anum' => 'A102283', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'CellularRule,rule=6,n_start=0', 'delta_type', 'dSum' ] }, { 'anum' => 'A131756', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'CellularRule,rule=6,n_start=0', 'delta_type', 'dSumAbs' ] }, { 'anum' => 'A062157', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'CellularRule,rule=14,n_start=0', 'delta_type', 'dSum' ] }, { 'anum' => 'A109613', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'CellularRule,rule=84,n_start=0', 'delta_type', 'dRSquared' ] }, { 'anum' => 'A171587', 'class' => 'Math::NumSeq::PlanePathDelta', 'parameters' => [ 'planepath', 'FibonacciWordFractal', 'delta_type', 'AbsdX' ] }, { 'anum' => 'A054552', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'SquareSpiral' ] }, { 'anum' => 'A033951', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'SquareSpiral', 'line_type', 'Y_neg' ] }, { 'anum' => 'A053755', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'SquareSpiral', 'line_type', 'Diagonal_NW' ] }, { 'anum' => 'A016754', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'SquareSpiral', 'line_type', 'Diagonal_SE' ] }, { 'anum' => 'A033991', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'SquareSpiral,n_start=0', 'line_type', 'Y_axis' ] }, { 'anum' => 'A002939', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'SquareSpiral,n_start=0', 'line_type', 'Diagonal' ] }, { 'anum' => 'A002943', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'SquareSpiral,n_start=0', 'line_type', 'Diagonal_SW' ] }, { 'anum' => 'A069894', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'SquareSpiral,wider=1', 'line_type', 'Diagonal_SW' ] }, { 'anum' => 'A185669', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PyramidSpiral,n_start=2', 'line_type', 'Diagonal_SE' ] }, { 'anum' => 'A062741', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'TriangleSpiral,n_start=0', 'line_type', 'Y_axis' ] }, { 'anum' => 'A062708', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'TriangleSpiral,n_start=0', 'line_type', 'Diagonal' ] }, { 'anum' => 'A062725', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'TriangleSpiral,n_start=0', 'line_type', 'Diagonal_SW' ] }, { 'anum' => 'A117625', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'TriangleSpiralSkewed' ] }, { 'anum' => 'A006137', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'TriangleSpiralSkewed', 'line_type', 'X_neg' ] }, { 'anum' => 'A064225', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'TriangleSpiralSkewed', 'line_type', 'Y_neg' ] }, { 'anum' => 'A081589', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'TriangleSpiralSkewed', 'line_type', 'Diagonal' ] }, { 'anum' => 'A038764', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'TriangleSpiralSkewed', 'line_type', 'Diagonal_SW' ] }, { 'anum' => 'A081267', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'TriangleSpiralSkewed', 'line_type', 'Diagonal_SE' ] }, { 'anum' => 'A081274', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'TriangleSpiralSkewed', 'line_type', 'Diagonal_SW' ] }, { 'anum' => 'A081266', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'TriangleSpiralSkewed,n_start=0', 'line_type', 'Diagonal_SW' ] }, { 'anum' => 'A130883', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'DiamondSpiral' ] }, { 'anum' => 'A058331', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'DiamondSpiral', 'line_type', 'Y_axis' ] }, { 'anum' => 'A192136', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PentSpiralSkewed' ] }, { 'anum' => 'A116668', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PentSpiralSkewed', 'line_type', 'X_neg' ] }, { 'anum' => 'A158187', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PentSpiralSkewed', 'line_type', 'Diagonal_NW' ] }, { 'anum' => 'A005891', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PentSpiralSkewed', 'line_type', 'Diagonal_SE' ] }, { 'anum' => 'A005476', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PentSpiralSkewed,n_start=0', 'line_type', 'Y_axis' ] }, { 'anum' => 'A005475', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PentSpiralSkewed,n_start=0', 'line_type', 'X_neg' ] }, { 'anum' => 'A028895', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PentSpiralSkewed,n_start=0', 'line_type', 'Diagonal_SE' ] }, { 'anum' => 'A049450', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HexSpiral,n_start=0', 'line_type', 'Diagonal' ] }, { 'anum' => 'A028896', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HexSpiral,n_start=0', 'line_type', 'Diagonal_SE' ] }, { 'anum' => 'A056105', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HexSpiralSkewed' ] }, { 'anum' => 'A056106', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HexSpiralSkewed', 'line_type', 'Y_axis' ] }, { 'anum' => 'A056108', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HexSpiralSkewed', 'line_type', 'X_neg' ] }, { 'anum' => 'A056109', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HexSpiralSkewed', 'line_type', 'Y_neg' ] }, { 'anum' => 'A056107', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HexSpiralSkewed', 'line_type', 'Diagonal_NW' ] }, { 'anum' => 'A049451', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HexSpiralSkewed,n_start=0', 'line_type', 'X_neg' ] }, { 'anum' => 'A062783', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HexSpiralSkewed,n_start=0', 'line_type', 'Diagonal' ] }, { 'anum' => 'A063436', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HexSpiralSkewed,n_start=0', 'line_type', 'Diagonal_SW' ] }, { 'anum' => 'A022265', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HeptSpiralSkewed,n_start=0', 'line_type', 'X_neg' ] }, { 'anum' => 'A218471', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HeptSpiralSkewed,n_start=0', 'line_type', 'Y_axis' ] }, { 'anum' => 'A195023', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HeptSpiralSkewed,n_start=0', 'line_type', 'Diagonal' ] }, { 'anum' => 'A022264', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HeptSpiralSkewed,n_start=0', 'line_type', 'Diagonal_NW' ] }, { 'anum' => 'A186029', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HeptSpiralSkewed,n_start=0', 'line_type', 'Diagonal_SW' ] }, { 'anum' => 'A024966', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HeptSpiralSkewed,n_start=0', 'line_type', 'Diagonal_SE' ] }, { 'anum' => 'A139273', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'OctagramSpiral,n_start=0', 'line_type', 'Y_axis' ] }, { 'anum' => 'A139275', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'OctagramSpiral,n_start=0', 'line_type', 'X_neg' ] }, { 'anum' => 'A139277', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'OctagramSpiral,n_start=0', 'line_type', 'Y_neg' ] }, { 'anum' => 'A139272', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'OctagramSpiral,n_start=0', 'line_type', 'Diagonal' ] }, { 'anum' => 'A139274', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'OctagramSpiral,n_start=0', 'line_type', 'Diagonal_NW' ] }, { 'anum' => 'A139276', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'OctagramSpiral,n_start=0', 'line_type', 'Diagonal_SW' ] }, { 'anum' => 'A033570', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'AnvilSpiral' ] }, { 'anum' => 'A033568', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'AnvilSpiral', 'line_type', 'Diagonal' ] }, { 'anum' => 'A085473', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'AnvilSpiral', 'line_type', 'Diagonal_SE' ] }, { 'anum' => 'A126587', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'AnvilSpiral', 'line_type', 'Y_axis', 'i_start', 1 ] }, { 'anum' => 'A139267', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'AnvilSpiral,n_start=0', 'line_type', 'Y_axis' ] }, { 'anum' => 'A049452', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'AnvilSpiral,n_start=0', 'line_type', 'X_neg' ] }, { 'anum' => 'A033580', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'AnvilSpiral,n_start=0', 'line_type', 'Y_neg' ] }, { 'anum' => 'A094159', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'AnvilSpiral,n_start=0', 'line_type', 'Diagonal_NW' ] }, { 'anum' => 'A049453', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'AnvilSpiral,n_start=0', 'line_type', 'Diagonal_SW' ] }, { 'anum' => 'A195319', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'AnvilSpiral,n_start=0', 'line_type', 'Diagonal_SE' ] }, { 'anum' => 'A051132', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'Hypot,n_start=0' ] }, { 'anum' => 'A036702', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HypotOctant,points=even', 'line_type', 'Diagonal' ] }, { 'anum' => 'A007051', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PythagoreanTree', 'line_type', 'Depth_start' ] }, { 'anum' => 'A081254', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'RationalsTree,tree_type=Bird' ] }, { 'anum' => 'A086893', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'RationalsTree,tree_type=Drib' ] }, { 'anum' => 'A102631', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'FactorRationals', 'line_type', 'Y_axis' ] }, { 'anum' => 'A163480', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PeanoCurve' ] }, { 'anum' => 'A163481', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PeanoCurve', 'line_type', 'Y_axis' ] }, { 'anum' => 'A163343', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PeanoCurve', 'line_type', 'Diagonal' ] }, { 'anum' => 'A163482', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HilbertCurve' ] }, { 'anum' => 'A163483', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'HilbertCurve', 'line_type', 'Y_axis' ] }, { 'anum' => 'A062880', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'ZOrderCurve', 'line_type', 'Y_axis' ] }, { 'anum' => 'A001196', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'ZOrderCurve', 'line_type', 'Diagonal' ] }, { 'anum' => 'A037314', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'ZOrderCurve,radix=3' ] }, { 'anum' => 'A338086', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'ZOrderCurve,radix=3', 'line_type', 'Diagonal' ] }, { 'anum' => 'A208665', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'ZOrderCurve,radix=3', 'i_start', 1, 'line_type', 'Y_axis' ] }, { 'anum' => 'A051022', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'ZOrderCurve,radix=10' ] }, { 'anum' => 'A338754', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'ZOrderCurve,radix=10', 'line_type', 'Diagonal' ] }, { 'anum' => 'A163344', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'GrayCode,apply_type=sT,radix=3' ] }, { 'anum' => 'A006046', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'SierpinskiTriangle,align=diagonal,n_start=0', 'line_type', 'Y_axis' ] }, { 'anum' => 'A074330', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'SierpinskiTriangle', 'line_type', 'Diagonal', 'i_start', 1 ] }, { 'anum' => 'A066321', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'ComplexMinus' ] }, { 'anum' => 'A256441', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'ComplexMinus', 'line_type', 'X_neg' ] }, { 'anum' => 'A016777', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'Rows,width=3', 'line_type', 'Y_axis' ] }, { 'anum' => 'A016813', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'Rows,width=4', 'line_type', 'Y_axis' ] }, { 'anum' => 'A016861', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'Rows,width=5', 'line_type', 'Y_axis' ] }, { 'anum' => 'A016921', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'Rows,width=6', 'line_type', 'Y_axis' ] }, { 'anum' => 'A016993', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'Rows,width=7', 'line_type', 'Y_axis' ] }, { 'anum' => 'A000124', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'Diagonals', 'line_type', 'Y_axis' ] }, { 'anum' => 'A001844', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'Diagonals', 'line_type', 'Diagonal' ] }, { 'anum' => 'A096376', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'Staircase,n_start=2', 'line_type', 'Diagonal' ] }, { 'anum' => 'A059100', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'Corner,n_start=2', 'line_type', 'Y_axis' ] }, { 'anum' => 'A014206', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'Corner,n_start=2', 'line_type', 'Diagonal' ] }, { 'anum' => 'A028552', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'Corner,wider=2,n_start=0', 'line_type', 'Diagonal' ] }, { 'anum' => 'A028387', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'Corner,wider=2,n_start=1', 'line_type', 'Diagonal' ] }, { 'anum' => 'A081346', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'CornerAlternating' ] }, { 'anum' => 'A081345', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'CornerAlternating', 'line_type', 'Y_axis' ] }, { 'anum' => 'A081347', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'CornerAlternating,wider=1' ] }, { 'anum' => 'A081348', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'CornerAlternating,wider=1', 'line_type', 'Y_axis' ] }, { 'anum' => 'A080335', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'CornerAlternating,wider=1', 'line_type', 'Diagonal' ] }, { 'anum' => 'A081350', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'CornerAlternating,wider=2' ] }, { 'anum' => 'A081351', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'CornerAlternating,wider=2', 'line_type', 'Y_axis' ] }, { 'anum' => 'A081352', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'CornerAlternating,wider=2', 'line_type', 'Diagonal' ] }, { 'anum' => 'A104249', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PyramidRows,step=3', 'line_type', 'Y_axis' ] }, { 'anum' => 'A143689', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PyramidRows,step=3', 'line_type', 'Diagonal_NW' ] }, { 'anum' => 'A084849', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PyramidRows,step=4', 'line_type', 'Y_axis' ] }, { 'anum' => 'A046092', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PyramidRows,step=4,n_start=0', 'line_type', 'Diagonal' ] }, { 'anum' => 'A002522', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PyramidSides', 'line_type', 'X_neg' ] }, { 'anum' => 'A061925', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'CellularRule,rule=5', 'line_type', 'Y_axis' ] }, { 'anum' => 'A006578', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'CellularRule190,n_start=0', 'line_type', 'Diagonal_NW' ] }, { 'anum' => 'A147562', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'UlamWarburton,n_start=0', 'line_type', 'Depth_start' ] }, { 'anum' => 'A183060', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'UlamWarburton,parts=2,n_start=0', 'line_type', 'Depth_start' ] }, { 'anum' => 'A151922', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'UlamWarburton,parts=1,n_start=1', 'line_type', 'Depth_end' ] }, { 'anum' => 'A151920', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'UlamWarburtonQuarter', 'line_type', 'Depth_end' ] }, { 'anum' => 'A084471', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'DigitGroups,radix=2', 'i_start', 1 ] }, { 'anum' => 'A003622', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'WythoffArray,x_start=1,y_start=1', 'line_type', 'Y_axis' ] }, { 'anum' => 'A020941', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'WythoffArray,x_start=1,y_start=1', 'line_type', 'Diagonal' ] }, { 'anum' => 'A014480', 'class' => 'Math::NumSeq::PlanePathN', 'parameters' => [ 'planepath', 'PowerArray', 'line_type', 'Diagonal' ] }, { 'anum' => 'A163536', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'PeanoCurve', 'turn_type', 'SLR' ] }, { 'anum' => 'A163537', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'PeanoCurve', 'turn_type', 'SRL' ] }, { 'anum' => 'A163542', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'HilbertCurve', 'turn_type', 'SLR' ] }, { 'anum' => 'A163543', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'HilbertCurve', 'turn_type', 'SRL' ] }, { 'anum' => 'A035263', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'KochCurve' ] }, { 'anum' => 'A309873', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'KochCurve', 'turn_type', 'LSR' ] }, { 'anum' => 'A056832', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'KochCurve', 'turn_type', 'SLR' ] }, { 'anum' => 'A034947', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'DragonCurve', 'turn_type', 'LSR' ] }, { 'anum' => 'A099545', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'DragonCurve', 'turn_type', 'Turn4' ] }, { 'anum' => 'A209615', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'AlternatePaper', 'turn_type', 'LSR' ] }, { 'anum' => 'A292077', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'AlternatePaper', 'turn_type', 'Right' ] }, { 'anum' => 'A137893', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'GosperSide' ] }, { 'anum' => 'A060236', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'GosperSide', 'turn_type', 'SLR' ] }, { 'anum' => 'A129184', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'Diagonals,n_start=0' ] }, { 'anum' => 'A156319', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'Diagonals,n_start=0', 'turn_type', 'SRL' ] }, { 'anum' => 'A097806', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'Diagonals,n_start=-1', 'turn_type', 'NotStraight' ] }, { 'anum' => 'A000007', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'Corner,wider=1,n_start=-1' ] }, { 'anum' => 'A063524', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'Corner,wider=2,n_start=-1' ] }, { 'anum' => 'A185012', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'Corner,wider=3,n_start=-1' ] }, { 'anum' => 'A131534', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'CellularRule,rule=6,n_start=-1', 'turn_type', 'SRL' ] }, { 'anum' => 'A130196', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'CellularRule,rule=20,n_start=-1', 'turn_type', 'SRL' ] }, { 'anum' => 'A176040', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'CellularRule,rule=84,n_start=-1', 'turn_type', 'Turn4' ] }, { 'anum' => 'A156596', 'class' => 'Math::NumSeq::PlanePathTurn', 'parameters' => [ 'planepath', 'FibonacciWordFractal', 'turn_type', 'SRL' ] } ] ; 1; __END__ Math-PlanePath-129/lib/Math/NumSeq/PlanePathTurn.pm0000644000175000017500000017102113774217323017653 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --values=PlanePathTurn # # maybe: # Turn4 0,1,2,3 and fractional # Turn4n 0,1,2,-1 negatives Turn4mid Turn4n Turn4s # TTurn6n 0,1,2,3, -1,-2, eg. flowsnake TTurn6s # TTurn6 0,1,2,3,4,5 package Math::NumSeq::PlanePathTurn; use 5.004; use strict; use Carp 'croak'; use vars '$VERSION','@ISA'; $VERSION = 129; use Math::NumSeq; @ISA = ('Math::NumSeq'); use Math::NumSeq::PlanePathCoord; use Math::PlanePath; use Math::PlanePath::Base::Generic 'is_infinite'; use Math::NumSeq::PlanePathDelta; # uncomment this to run the ### lines # use Smart::Comments; use constant characteristic_smaller => 1; sub description { my ($self) = @_; if (ref $self) { return "Turn values $self->{'turn_type'} from path $self->{'planepath'}"; } else { # class method return 'Turns from a PlanePath'; } } use constant::defer parameter_info_array => sub { return [ Math::NumSeq::PlanePathCoord::_parameter_info_planepath(), { name => 'turn_type', display => 'Turn Type', type => 'enum', default => 'Left', choices => ['Left','Right','Straight','NotStraight', 'LSR','SLR','SRL', # 'RSL', # 'Turn4', # Turn4 is 0<=value<4. # 'Turn4n', # 'TTurn6', # 'TTurn6n', ], description => 'Left is 1=left, 0=right or straight. Right is 1=right, 0=left or straight. LSR is 1=left,0=straight,-1=right. SLR is 0=straight,1=left,2=right. SRL is 0=straight,1=right,2=left.', }, ]; }; sub characteristic_integer { my ($self) = @_; my $planepath_object = $self->{'planepath_object'}; if (my $func = $planepath_object->can("_NumSeq_Turn_$self->{'turn_type'}_integer")) { return $planepath_object->$func(); } return undef; } #------------------------------------------------------------------------------ sub oeis_anum { my ($self) = @_; ### PlanePathTurn oeis_anum() ... my $planepath = $self->{'planepath_object'}; my $key = Math::NumSeq::PlanePathCoord::_planepath_oeis_anum_key($self->{'planepath_object'}); ### planepath: ref $planepath ### $key ### whole table: $planepath->_NumSeq_Turn_oeis_anum ### key href: $planepath->_NumSeq_Turn_oeis_anum->{$key} return $planepath->_NumSeq_Turn_oeis_anum->{$key}->{$self->{'turn_type'}}; } #------------------------------------------------------------------------------ sub new { ### PlanePathTurn new(): @_ my $self = shift->SUPER::new(@_); ### self from SUPER: $self $self->{'planepath_object'} ||= Math::NumSeq::PlanePathCoord::_planepath_name_to_object($self->{'planepath'}); ### turn_func: "_turn_func_$self->{'turn_type'}", $self->{'turn_func'} $self->{'turn_func'} = $self->can('_turn_func_'.$self->{'turn_type'}) || croak "Unrecognised turn_type: ",$self->{'turn_type'}; $self->rewind; ### $self return $self; } sub i_start { my ($self) = @_; my $planepath_object = $self->{'planepath_object'} || return 0; return $planepath_object->n_start + $planepath_object->arms_count; } sub rewind { my ($self) = @_; my $planepath_object = $self->{'planepath_object'} || return; $self->{'i'} = $self->i_start; $self->{'arms'} = $planepath_object->arms_count; undef $self->{'prev_dx'}; } sub next { my ($self) = @_; ### NumSeq-PlanePathTurn next(): "i=$self->{'i'}" my $planepath_object = $self->{'planepath_object'}; my $i = $self->{'i'}++; my $arms = $self->{'arms'}; my $prev_dx = $self->{'prev_dx'}; my $prev_dy; if (defined $prev_dx) { $prev_dy = $self->{'prev_dy'}; ### use prev dxdy: "$prev_dx,$prev_dy" } else { ($prev_dx, $prev_dy) = $planepath_object->n_to_dxdy($i-$arms) or do { ### nothing in path at n: $i return; }; ### calc prev dxdy: "at i=".($i-$arms)." $prev_dx,$prev_dy" } my ($dx, $dy) = $planepath_object->n_to_dxdy($i) or do { ### nothing in path at previous n: $i-$arms return; }; ### calc dxdy: "at i=$i $dx,$dy" if ($arms == 1) { $self->{'prev_dx'} = $dx; $self->{'prev_dy'} = $dy; } return ($i, $self->{'turn_func'}->($prev_dx,$prev_dy, $dx,$dy)); } sub ith { my ($self, $i) = @_; ### PlanePathTurn ith(): $i if (is_infinite($i)) { return undef; } my $planepath_object = $self->{'planepath_object'}; my $arms = $self->{'arms'}; my ($prev_dx, $prev_dy) = $planepath_object->n_to_dxdy($i - $arms) or return undef; my ($dx, $dy) = $planepath_object->n_to_dxdy($i) or return undef; return $self->{'turn_func'}->($prev_dx,$prev_dy, $dx,$dy); } # dx1,dy1 # dx2,dy2 / # * / # / # / # / # / # O # # cmpy = dx2 * dy1/dx1 # left if dy2 > cmpy # dy2 > dx2 * dy1/dx1 # dy2 * dx1 > dx2 * dy1 # # if dx1=0, dy1 > 0 then left if dx2 < 0 # dy2 * 0 > dx2 * dy1 # 0 > dx2*dy1 good # sub _turn_func_Left { my ($dx,$dy, $next_dx,$next_dy) = @_; ### _turn_func_Left() ... return ($next_dy * $dx > $next_dx * $dy ? 1 : 0); } sub _turn_func_Right { my ($dx,$dy, $next_dx,$next_dy) = @_; ### _turn_func_Right() ... return ($next_dy * $dx < $next_dx * $dy ? 1 : 0); } sub _turn_func_LSR { my ($dx,$dy, $next_dx,$next_dy) = @_; ### _turn_func_LSR() ... return (($next_dy * $dx <=> $next_dx * $dy) || 0); # 1,0,-1 } sub _turn_func_RSL { return - _turn_func_LSR(@_); } { my @LSR_to_SLR = (0, # LSR=0 straight -> SLR=0 1, # LSR=1 left -> SLR=1 2); # LSR=-1 right -> SLR=2 sub _turn_func_SLR { return $LSR_to_SLR[_turn_func_LSR(@_)]; } } { my @LSR_to_SRL = (0, # LSR=0 straight -> SRL=0 2, # LSR=1 left -> SRL=2 1); # LSR=-1 right -> SRL=1 sub _turn_func_SRL { return $LSR_to_SRL[_turn_func_LSR(@_)]; } } sub _turn_func_Straight { my ($dx,$dy, $next_dx,$next_dy) = @_; ### _turn_func_Left() ... return ($next_dy * $dx == $next_dx * $dy ? 1 : 0); } sub _turn_func_NotStraight { my ($dx,$dy, $next_dx,$next_dy) = @_; ### _turn_func_Left() ... return ($next_dy * $dx == $next_dx * $dy ? 0 : 1); } #--------------- # experimental extras # 0,1,2,3 as ddir mod 4, incl fractional sub _turn_func_Turn4 { my ($dx,$dy, $next_dx,$next_dy) = @_; ### _turn_func_Turn4(): "$dx,$dy $next_dx,$next_dy" return (((Math::NumSeq::PlanePathDelta::_delta_func_Dir360($next_dx,$next_dy) - Math::NumSeq::PlanePathDelta::_delta_func_Dir360($dx,$dy)) % 360) / 90); } # 0,1, -2,-1 # 0 <= t < 2 and -2 <= t < 0 for symmetry, so reverse=-2 sub _turn_func_Turn4n { my ($dx,$dy, $next_dx,$next_dy) = @_; require Math::NumSeq::PlanePathDelta; my $ret = (((Math::NumSeq::PlanePathDelta::_delta_func_Dir360($next_dx,$next_dy) - Math::NumSeq::PlanePathDelta::_delta_func_Dir360($dx,$dy)) % 360) / 90); if ($ret >= 2) { $ret -= 4; } return $ret; } # 0,1,2,3,4,5 as dtdir mod 6, incl fractional sub _turn_func_TTurn6 { my ($dx,$dy, $next_dx,$next_dy) = @_; require Math::NumSeq::PlanePathDelta; return (((Math::NumSeq::PlanePathDelta::_delta_func_TDir360($next_dx,$next_dy) - Math::NumSeq::PlanePathDelta::_delta_func_TDir360($dx,$dy)) % 360) / 60); } # 0,1,2, -3,-2,-1 # 0 <= t < 3 and -3 <= t < 0 for symmetry, so reverse=-3 sub _turn_func_TTurn6n { my $t = _turn_func_TTurn6(@_); return ($t < 3 ? $t : $t-6); } # Would suit TerdragonCurve and similar, but SLR is the same as TTurn3 # there, so would be different only on fractional turns. # # sub _turn_func_TTurn3 { # return _turn_func_TTurn6(@_) / 2; # } #--------- sub pred { my ($self, $value) = @_; ### PlanePathTurn pred(): $value my $planepath_object = $self->{'planepath_object'}; if (defined (my $values_min = $self->values_min)) { if ($value < $values_min) { return 0; } } if (defined (my $values_max = $self->values_max)) { if ($value > $values_max) { return 0; } } my $turn_type = $self->{'turn_type'}; if ($turn_type eq 'Left' || $turn_type eq 'Right' || $turn_type eq 'Straight' || $turn_type eq 'NotStraight') { unless ($value == 0 || $value == 1) { return 0; } } elsif ($turn_type eq 'LSR' || $turn_type eq 'RSL') { unless ($value == 1 || $value == 0 || $value == -1) { return 0; } } else { # ($turn_type eq 'SLR' || $turn_type eq 'SRL') { unless ($value == 0 || $value == 1 || $value == 2) { return 0; } } if (my $func = $planepath_object->can('_NumSeq_Turn_'.$self->{'turn_type'}.'_pred_hash')) { my $href = $self->$func(); unless ($href->{$value}) { return 0; } } return 1; } #------------------------------------------------------------------------------ sub values_min { my ($self) = @_; my $method = '_NumSeq_Turn_' . $self->{'turn_type'} . '_min'; return $self->{'planepath_object'}->can($method) ? $self->{'planepath_object'}->$method() : undef; } sub values_max { my ($self) = @_; my $method = '_NumSeq_Turn_' . $self->{'turn_type'} . '_max'; return $self->{'planepath_object'}->can($method) ? $self->{'planepath_object'}->$method() : undef; } sub characteristic_increasing { my ($self) = @_; my $planepath_object = $self->{'planepath_object'}; if (my $func = $planepath_object->can("_NumSeq_Turn_$self->{'turn_type'}_increasing")) { return $planepath_object->$func(); } return undef; # unknown } sub characteristic_non_decreasing { my ($self) = @_; my $planepath_object = $self->{'planepath_object'}; if (my $func = $planepath_object->can("_NumSeq_Turn_$self->{'turn_type'}_non_decreasing")) { return $planepath_object->$func(); } if (defined (my $values_min = $self->values_min)) { if (defined (my $values_max = $self->values_max)) { if ($values_min == $values_max) { # constant seq is non-decreasing return 1; } } } # increasing means non_decreasing too return $self->characteristic_increasing; } # my $all_Left_predhash = { 0=>1, 1=>1 }; # my $all_LSR_predhash = { 0=>1, 1=>1, -1=>1 }; # my $straight_Left_predhash = { 0=>1 }; # my $straight_LSR_predhash = { 0=>1 }; { package Math::PlanePath; use constant 1.02; # for leading underscore sub _NumSeq_Turn_Left_min { my ($self) = @_; return ($self->turn_any_right || $self->turn_any_straight ? 0 : 1); } sub _NumSeq_Turn_Left_max { my ($self) = @_; return ($self->turn_any_left ? 1 : 0); } use constant _NumSeq_Turn_Left_integer => 1; sub _NumSeq_Turn_Right_min { my ($self) = @_; return ($self->turn_any_left || $self->turn_any_straight ? 0 : 1); } sub _NumSeq_Turn_Right_max { my ($self) = @_; return ($self->turn_any_right ? 1 : 0); } use constant _NumSeq_Turn_Right_integer => 1; sub _NumSeq_Turn_Straight_min { my ($self) = @_; return ($self->turn_any_left || $self->turn_any_right ? 0 : 1); } sub _NumSeq_Turn_Straight_max { my ($self) = @_; return ($self->turn_any_straight ? 1 : 0); } use constant _NumSeq_Turn_Straight_integer => 1; sub _NumSeq_Turn_NotStraight_min { my ($self) = @_; return 1 - $self->_NumSeq_Turn_Straight_max; # NotStraight opposite } sub _NumSeq_Turn_NotStraight_max { my ($self) = @_; return 1 - $self->_NumSeq_Turn_Straight_min; # NotStraight opposite } use constant _NumSeq_Turn_NotStraight_integer => 1; sub _NumSeq_Turn_LSR_min { my ($self) = @_; return ($self->turn_any_right ? -1 : $self->turn_any_straight ? 0 : 1); # only ever Left } sub _NumSeq_Turn_LSR_max { my ($self) = @_; return ($self->turn_any_left ? 1 : $self->turn_any_straight ? 0 : -1); # only ever Right } use constant _NumSeq_Turn_LSR_integer => 1; sub _NumSeq_Turn_SLR_min { my ($self) = @_; return ($self->turn_any_straight ? 0 : $self->turn_any_left ? 1 : 2); # only ever Right } sub _NumSeq_Turn_SLR_max { my ($self) = @_; return ($self->turn_any_right ? 2 : $self->_NumSeq_Turn_Left_max); # 1 if any Left } use constant _NumSeq_Turn_SLR_integer => 1; sub _NumSeq_Turn_SRL_min { my ($self) = @_; return ($self->turn_any_straight ? 0 : $self->turn_any_right ? 1 : 2); # only ever Left } sub _NumSeq_Turn_SRL_max { my ($self) = @_; return ($self->turn_any_left ? 2 : $self->_NumSeq_Turn_Right_max); # 1 if any Right } use constant _NumSeq_Turn_SRL_integer => 1; use constant _NumSeq_Turn_Turn4_min => 0; sub _NumSeq_Turn_Turn4_integer { my ($self) = @_; return $self->_NumSeq_Delta_Dir4_integer; } sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return ($self->_NumSeq_Turn_Turn4_integer ? 3 : 4); } use constant _NumSeq_Turn_oeis_anum => {}; } # { package Math::PlanePath::SquareSpiral; # # SquareSpiral # # abs(A167752)==Left=LSR=Turn4 if that really is the quarter-squares # # abs(A167753)==Left=LSR=Turn4 of wider=1 if that really is the ceil(n+1)^2 # # Left = A240025 characteristic of quarter-squares OFFSET=0 # # except it has extra initial 1 # } { package Math::PlanePath::GreekKeySpiral; sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return ($self->{'turns'} == 0 ? 1 # SquareSpiral, left or straight only : 3); # otherwise turn right too } } { package Math::PlanePath::PyramidSpiral; use constant _NumSeq_Turn_Turn4_max => 1.5; } { package Math::PlanePath::TriangleSpiral; use constant _NumSeq_Turn_Turn4_max => 1.5; use constant _NumSeq_Turn_oeis_anum => { 'n_start=-1' => { Left => 'A023531', # 1 at k*(k+3)/2 LSR => 'A023531', Straight => 'A023532', # 0 at k*(k+3)/2, 1 otherwise # OEIS-Other: A023531 planepath=TriangleSpiral,n_start=-1 # OEIS-Other: A023531 planepath=TriangleSpiral,n_start=-1 turn_type=LSR # OEIS-Other: A023532 planepath=TriangleSpiral,n_start=-1 turn_type=Straight }, # PlanePathTurn planepath=TriangleSpiral,n_start=1, turn_type=TTurn6 # A089799 Expansion of Jacobi theta function theta_2(q^(1/2))/q^(1/8) # is this 2s with runs of 0s ? }; } { package Math::PlanePath::TriangleSpiralSkewed; use constant _NumSeq_Turn_Turn4_max => 1.5; use constant _NumSeq_Turn_oeis_anum => { do { my $href = { Left => 'A023531', # 1 at k*(k+3)/2 LSR => 'A023531', Straight => 'A023532', # 0 at k*(k+3)/2, 1 otherwise }; ('skew=left,n_start=-1' => $href, 'skew=right,n_start=-1' => $href, 'skew=up,n_start=-1' => $href, 'skew=down,n_start=-1' => $href) # OEIS-Other: A023531 planepath=TriangleSpiralSkewed,n_start=-1 # OEIS-Other: A023531 planepath=TriangleSpiralSkewed,n_start=-1 turn_type=LSR # OEIS-Other: A023532 planepath=TriangleSpiralSkewed,n_start=-1 turn_type=Straight # OEIS-Other: A023531 planepath=TriangleSpiralSkewed,n_start=-1,skew=right # OEIS-Other: A023531 planepath=TriangleSpiralSkewed,n_start=-1,skew=up # OEIS-Other: A023531 planepath=TriangleSpiralSkewed,n_start=-1,skew=down }, }; } { package Math::PlanePath::DiamondSpiral; use constant _NumSeq_Turn_Turn4_max => 1.5; } { package Math::PlanePath::AztecDiamondRings; use constant _NumSeq_Turn_Turn4_max => 1; # left or straight } { package Math::PlanePath::PentSpiral; use constant _NumSeq_Turn_Turn4_max => Math::NumSeq::PlanePathTurn::_turn_func_Turn4(2,0, -2,1); } { package Math::PlanePath::PentSpiralSkewed; use constant _NumSeq_Turn_Turn4_max => 1.5; } { package Math::PlanePath::HexSpiral; use constant _NumSeq_Turn_Turn4_max => 1.5; } { package Math::PlanePath::HexSpiralSkewed; use constant _NumSeq_Turn_Turn4_max => 1.5; } { package Math::PlanePath::HeptSpiralSkewed; use constant _NumSeq_Turn_Turn4_max => 1.5; # at N=2 turn +135 } { package Math::PlanePath::AnvilSpiral; use constant _NumSeq_Turn_Turn4_max => 3; } { package Math::PlanePath::OctagramSpiral; use constant _NumSeq_Turn_Turn4_max => 3; # +90 right } { package Math::PlanePath::KnightSpiral; # use constant _NumSeq_Turn_Turn4_min => ...; # 2,1 } # { package Math::PlanePath::CretanLabyrinth; # } { package Math::PlanePath::SquareArms; use constant _NumSeq_Turn_Turn4_max => 1; # left or straight } { package Math::PlanePath::DiamondArms; use constant _NumSeq_Turn_Turn4_max => 1; # left or straight use constant _NumSeq_Turn_Turn4_integer => 1; } { package Math::PlanePath::HexArms; use constant _NumSeq_Turn_Turn4_max => 1; # at N=8 } { package Math::PlanePath::SacksSpiral; sub _NumSeq_Turn_Turn4_max { my ($self) = @_; # at N=1 is maximum turn return Math::NumSeq::PlanePathTurn::_turn_func_Turn4(1,0, $self->n_to_dxdy(1)); } use constant _NumSeq_Turn4_min_is_infimum => 1; use constant _NumSeq_Turn_oeis_anum => { '' => { 'Left' => 'A000012', # left always, all ones 'LSR' => 'A000012', # OEIS-Other: A000012 planepath=SacksSpiral # OEIS-Other: A000012 planepath=SacksSpiral turn_type=LSR }, }; } # { package Math::PlanePath::VogelFloret; # } { package Math::PlanePath::TheodorusSpiral; use constant _NumSeq_Turn4_min_is_infimum => 1; # approaches straight use constant _NumSeq_Turn_Turn4_max => 1; # initial 90deg use constant _NumSeq_Turn_oeis_anum => { '' => { 'Left' => 'A000012', # left always, all ones 'LSR' => 'A000012', # OEIS-Other: A000012 planepath=TheodorusSpiral # OEIS-Other: A000012 planepath=TheodorusSpiral turn_type=LSR }, }; } { package Math::PlanePath::ArchimedeanChords; sub _NumSeq_Turn_Turn4_max { my ($self) = @_; # at N=1 is maximum turn return Math::NumSeq::PlanePathTurn::_turn_func_Turn4(1,0, $self->n_to_dxdy(1)); } use constant _NumSeq_Turn4_min_is_infimum => 1; # approaches straight ahead use constant _NumSeq_Turn_oeis_anum => { '' => { 'Left' => 'A000012', # left always, all ones 'LSR' => 'A000012', # OEIS-Other: A000012 planepath=ArchimedeanChords # OEIS-Other: A000012 planepath=ArchimedeanChords turn_type=LSR }, }; } { package Math::PlanePath::MultipleRings; # step=1 and step=2 are mostly 1 for left, but after a while each ring # endpoint is to the right sub _NumSeq_Turn_Left_non_decreasing { my ($self) = @_; # step=0 always straight # step=1 straight,straight, then always left return ($self->{'step'} <= 1); } *_NumSeq_Turn_Right_non_decreasing = \&_NumSeq_Turn_Left_non_decreasing; *_NumSeq_Turn_LSR_non_decreasing = \&_NumSeq_Turn_Left_non_decreasing; *_NumSeq_Turn_SLR_non_decreasing = \&_NumSeq_Turn_Left_non_decreasing; *_NumSeq_Turn_SRL_non_decreasing = \&_NumSeq_Turn_Left_non_decreasing; sub _NumSeq_Turn_Turn4_max { my ($self) = @_; my $step = $self->{'step'}; return ($step == 0 ? 0 # step == 0 is always straight ahead : 4/$step); } use constant _NumSeq_Turn_oeis_anum => { # MultipleRings step=0 is trivial X=N,Y=0 'step=0,ring_shape=circle' => { Left => 'A000004', # all-zeros LSR => 'A000004', # all zeros, straight # OEIS-Other: A000004 planepath=MultipleRings,step=0 # OEIS-Other: A000004 planepath=MultipleRings,step=0 turn_type=LSR }, 'step=0,ring_shape=polygon' => { Left => 'A000004', # all-zeros LSR => 'A000004', # all zeros, straight # OEIS-Other: A000004 planepath=MultipleRings,step=0,ring_shape=polygon # OEIS-Other: A000004 planepath=MultipleRings,step=0,ring_shape=polygon turn_type=LSR }, }; } { package Math::PlanePath::PixelRings; # has right turns between rings use constant _NumSeq_Turn_Turn4_max => 3.5; } { package Math::PlanePath::FilledRings; use constant _NumSeq_Turn_Turn4_max => 3.5; } { package Math::PlanePath::Hypot; sub _NumSeq_Turn4_min_is_infimum { my ($self) = @_; return ($self->{'points'} eq 'all'); } { my %_NumSeq_Turn_Turn4_max = (all => 1.5, # at N=2, apparent maximum even => 1.5, # at N=2, apparent maximum odd => Math::NumSeq::PlanePathTurn::_turn_func_Turn4(3,-3, 3,5), ); sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return ($_NumSeq_Turn_Turn4_max{$self->{'points'}} || 0); } } } { package Math::PlanePath::HypotOctant; # apparently approaches +360 degrees use constant _NumSeq_Turn4_max_is_supremum => 1; } { package Math::PlanePath::TriangularHypot; # points=even Turn4=0 at N=31 # # points=all apparently approaches 0 # min i=473890[1303230202] 0.00000 px=-11,py=1 dx=-13,dy=1 -13.000 # # points=odd apparently approaches 0 # min i=95618[113112002] 0.01111 px=-14,py=4 dx=-16,dy=4 -4.000 # # points=hex apparently approaches 0 # min i=44243[22303103] 0.01111 px=-15,py=3 dx=-12,dy=2 -6.000 # # points=hex_rotated Turn4=0 at N=58 # points=hex_centred Turn4=0 at N=24 { my %_NumSeq_Turn4_min_is_infimum = (all => 1, odd => 1, hex => 1, ); sub _NumSeq_Turn4_min_is_infimum { my ($self) = @_; return ($_NumSeq_Turn4_min_is_infimum{$self->{'points'}} || 0); } } { my %_NumSeq_Turn_Turn4_max = (even => 1.5, # at N=2 odd => Math::NumSeq::PlanePathTurn::_turn_func_Turn4(5,3, 0,-6), all => Math::NumSeq::PlanePathTurn::_turn_func_Turn4(5,3, 0,-6), hex => Math::NumSeq::PlanePathTurn::_turn_func_Turn4(2,0, -3,1), hex_rotated => Math::NumSeq::PlanePathTurn::_turn_func_Turn4(1,1, -3,-1), hex_centred => Math::NumSeq::PlanePathTurn::_turn_func_Turn4(3,1, -2,2), ); sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return ($_NumSeq_Turn_Turn4_max{$self->{'points'}} || 0); } } } # { package Math::PlanePath::PythagoreanTree; # # A000004 all-zeros and A000012 all-ones are OFFSET=0 which doesn't match # # start N=1 here for always turn left or right in UAD. # } # { package Math::PlanePath::RationalsTree; # # SB turn cf A021913 0,0,1,1 # # A133872 1,1,0,0 # # A057077 1,1,-1,-1 # # A087960 1,-1,-1,1 # # HCS turn left close to A010059 thue-morse or A092436 # # right A010060 # # LSR => 'A106400', # thue-morse +/-1 # # CfracDigits radix=1 likewise # } # { package Math::PlanePath::FractionsTree; # } # { package Math::PlanePath::ChanTree; # # FIXME: k=4,5,6 are Right-only, maybe # # sub _NumSeq_Turn_Left_max { # # my ($self) = @_; # # return ($self->{'k'} >= 4 # # ? 0 # never Left # # : 1); # # } # # sub _NumSeq_Turn_Right_min { # # my ($self) = @_; # # return ($self->{'k'} >= 4 # # ? 1 # always Right # # : 0); # # } # # sub _NumSeq_Turn_LSR_max { # # my ($self) = @_; # # return ($self->{'k'} >= 4 # # ? -1 # always Right # # : 1); # # } # } { package Math::PlanePath::DiagonalRationals; sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return ($self->{'direction'} eq 'down' ? 2.5 # N=2 : Math::NumSeq::PlanePathTurn::_turn_func_Turn4(-1,1, 2,-1)); # N=3 } } # { package Math::PlanePath::FactorRationals; # # revbinary # # max i=296[10220] 3.98889 px=-258,py=1 dx=-26,dy=1[-122,1] -26.000 # N=295=5*59 X=5*59 Y=1 N=297 # N=296=2^3*37 X=37 Y=2 -26,+1 \ N=296 <-------- N=295 # N=297=3^3*11 X=11 Y=3 -258,+1 # } { package Math::PlanePath::GcdRationals; # Turn4 minimum # pairs_order=rows # min=0 at N=12 # max i=216[3120] 3.98889 px=11,py=-14 dx=3,dy=-4[3,-10] -0.750 # # pairs_order=rows_reverse # min i=13[31] 0.00000 px=-1,py=0 dx=-1,dy=0 0.000 # max i=611[21203] 3.98889 px=-1,py=2 dx=-13,dy=28[-31,130] -0.464 # # pairs_order=diagonals_down # min i=2[2] 0.00000 px=0,py=1 dx=0,dy=1 0.000 # max i=561[20301] 3.98889 px=-7,py=8 dx=-5,dy=6[-11,12] -0.833 # # pairs_order=diagonals_up # min i=11[23] 0.00000 px=-1,py=1 dx=-1,dy=1 -1.000 # max i=4886[1030112] 3.98889 px=6,py=-6 dx=15,dy=-16[33,-100] -0.938 # # Are these exact maximums or more when bigger N? } # { package Math::PlanePath::CfracDigits; # } { package Math::PlanePath::AR2W2Curve; # NSEW+diag use constant _NumSeq_Turn_Turn4_max => 3.5; } { package Math::PlanePath::PeanoCurve; use constant _NumSeq_Turn_oeis_anum => { 'radix=3' => { # 2---0---0---0---0---2 # | | # 2---0---1 1---0---2 # | | # .---0---1 1---0---0-... SLR => 'A163536', # turn 0=ahead,1=left,2=right, OFFSET=1 SRL => 'A163537', # OEIS-Catalogue: A163536 planepath=PeanoCurve turn_type=SLR # OEIS-Catalogue: A163537 planepath=PeanoCurve turn_type=SRL # Not quite, A039963 is OFFSET=0 vs first turn N=1 here # Straight => 'A039963', }, }; } # { package Math::PlanePath::PeanoDiagonals; # } # { package Math::PlanePath::WunderlichSerpentine; # } { package Math::PlanePath::HilbertCurve; use constant _NumSeq_Turn_oeis_anum => { '' => { SLR => 'A163542', # relative direction ahead=0,left=1,right=2 OFFSET=1 SRL => 'A163543', # relative direction transpose # OEIS-Catalogue: A163542 planepath=HilbertCurve turn_type=SLR # OEIS-Catalogue: A163543 planepath=HilbertCurve turn_type=SRL }, }; } { package Math::PlanePath::HilbertSides; use constant _NumSeq_Turn_oeis_anum => { '' => { NotStraight => 'A035263', # morphism # main A035263 is KochCurve turn # OEIS-Other: A035263 planepath=HilbertSides turn_type=NotStraight # Not quite, OFFSET=0 but first turn here N=1. # # OEIS-Other: A096268 planepath=HilbertSides turn_type=Straight # Straight => 'A096268', # odd/even trailing 0 bits }, }; } { package Math::PlanePath::ZOrderCurve; sub _NumSeq_Turn_Turn4_min { my ($self) = @_; return ($self->{'radix'} == 2 ? 0.5 : 0); # includes straight } # radix max at # ----- ------ # 2 3 *---* Y=radix-1 # 3 8 \ # 4 15 \ # 5 24 * Y=0 sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return (Math::NumSeq::PlanePathTurn::_turn_func_Turn4 (1,0, 1,1-$self->{'radix'})); } } { package Math::PlanePath::GrayCode; # ENHANCE-ME: check this is true # PlanePathTurn planepath=GrayCode,apply_type=TsF,gray_type=reflected,radix=2, turn_type=SLR # PlanePathTurn planepath=GrayCode,apply_type=Fs,gray_type=reflected,radix=2, turn_type=SLR # match 1,1,0,0,1,1,1,1,1,1,0,0,1,1,0,0,1,1,0,0,1,1,1,1,1,1 # A039963 The period-doubling sequence A035263 repeated. # A039963 ,1,1,0,0,1,1,1,1,1,1,0,0,1,1,0,0,1,1,0,0,1,1,1,1,1,1 # Not quite, A039963 is OFFSET=0 vs first turn at N=1 here # 'Math::PlanePath::GrayCode' => # { # Left => 'A039963', # duplicated KochCurve # LSR => 'A039963', # }, # Koch characteristic of A003159 ending even zeros # 'Math::PlanePath::GrayCode' => use constant _NumSeq_Turn_oeis_anum => { do { my $peano = Math::PlanePath::PeanoCurve -> _NumSeq_Turn_oeis_anum -> {'radix=3'}; ('apply_type=TsF,gray_type=reflected,radix=3' => $peano, 'apply_type=FsT,gray_type=reflected,radix=3' => $peano, ), # OEIS-Other: A163536 planepath=GrayCode,apply_type=TsF,radix=3 turn_type=SLR # OEIS-Other: A163536 planepath=GrayCode,apply_type=FsT,radix=3 turn_type=SLR # OEIS-Other: A163537 planepath=GrayCode,apply_type=TsF,radix=3 turn_type=SRL # OEIS-Other: A163537 planepath=GrayCode,apply_type=FsT,radix=3 turn_type=SRL }, }; } # { package Math::PlanePath::ImaginaryBase; # } # { package Math::PlanePath::ImaginaryHalf; # } # { package Math::PlanePath::CubicBase; # } # { package Math::PlanePath::Flowsnake; # # inherit from FlowsnakeCentres # } { package Math::PlanePath::FlowsnakeCentres; use constant _NumSeq_Turn_Turn4_max => 3.5; } # { package Math::PlanePath::GosperIslands; # } { package Math::PlanePath::KochCurve; use constant _NumSeq_Turn_oeis_anum => { '' => { Left => 'A035263', # OFFSET=1 matches N=1 # OEIS-Catalogue: A035263 planepath=KochCurve LSR => 'A309873', # (mine) # OEIS-Catalogue: A309873 planepath=KochCurve turn_type=LSR SLR => 'A056832', # OEIS-Catalogue: A056832 planepath=KochCurve turn_type=SLR # A056832 All a(n) = 1 or 2; a(1) = 1; get next 2^k terms by repeating first 2^k terms and changing last element so sum of first 2^(k+1) terms is odd. # A056832 ,1,2,1,1,1,2,1,2,1,2,1,1,1,2,1 # Not quite, A096268 OFFSET=0 values 0,1,0,0,0,1 # whereas here N=1 first turn values 0,1,0,0,0,1 # Right => 'A096268', # morphism }, }; } # { package Math::PlanePath::KochPeaks; # } # { package Math::PlanePath::KochSnowflakes; # } # { package Math::PlanePath::KochSquareflakes; # } # { package Math::PlanePath::QuadricCurve; # } # { package Math::PlanePath::QuadricIslands; # } { package Math::PlanePath::SierpinskiTriangle; { my %_NumSeq_Turn_Turn4_max = (triangular => 2.5, left => 2.5, right => 3, diagonal => 2.5, ); sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return $_NumSeq_Turn_Turn4_max{$self->{'align'}}; } } } { package Math::PlanePath::SierpinskiArrowhead; use constant _NumSeq_Turn_Turn4_min => 0.5; # North-East diagonal use constant _NumSeq_Turn_Turn4_max => 3.5; # South-East diagonal } { package Math::PlanePath::SierpinskiArrowheadCentres; use constant _NumSeq_Turn_Turn4_max => 3.5; # South-East diagonal } # { package Math::PlanePath::SierpinskiCurve; # # use constant _NumSeq_Turn_oeis_anum => # # { 'arms=1' => # # { # # # Not quite, A039963 numbered OFFSET=0 whereas first turn at N=1 here # # Right => 'A039963', # duplicated KochCurve turns # # }, # # }, # # } # } { package Math::PlanePath::SierpinskiCurveStair; use constant _NumSeq_Turn_Turn4_min => 1; # never straight } { package Math::PlanePath::DragonCurve; use constant _NumSeq_Turn_Turn4_min => 1; # left or right only use constant _NumSeq_Turn_oeis_anum => { 'arms=1' => { 'LSR' => 'A034947', # Jacobi symbol (-1/n) # OEIS-Catalogue: A034947 planepath=DragonCurve turn_type=LSR Turn4 => 'A099545', # (odd part of n) mod 4 # OEIS-Catalogue: A099545 planepath=DragonCurve turn_type=Turn4 # 'L1R0' => 'A014577', # left=1,right=0 OFFSET=0 # 'L0R1' => 'A014707', # left=0,right=1 OFFSET=0 # 'L1R2' => 'A014709', # left=1,right=2 OFFSET=0 # 'L1R3' => 'A099545', # left=1,right=3 OFFSET=1 # # Not quite, A014707 has OFFSET=0 cf first elem for N=1 # Left => 'A014707', # turn, 1=left,0=right # # OEIS-Catalogue: A014707 planepath=DragonCurve # # Not quite, A014577 has OFFSET=0 cf first elem for N=1 # Right => 'A014577', # turn, 0=left,1=right # # OEIS-Catalogue: A014577 planepath=DragonCurve turn_type=Right # Not quite A014709 OFFSET=0 vs first turn at N=1 here # SLR => 'A014709' # SRL => 'A014710', }, }; } { package Math::PlanePath::DragonRounded; use constant _NumSeq_Turn_Turn4_min => 0.5; use constant _NumSeq_Turn_Turn4_max => 3.5; } # { package Math::PlanePath::DragonMidpoint; # } { package Math::PlanePath::AlternatePaper; use constant _NumSeq_Turn_Turn4_min => 1; # left or right only # A209615 is (-1)^e for each p^e prime=4k+3 or prime=2 # 3*3 mod 4 = 1 mod 4 # so picks out bit above lowest 1-bit, and factor -1 if an odd power-of-2 # which is the AlternatePaper turn formula # use constant _NumSeq_Turn_oeis_anum => { 'arms=1' => { LSR => 'A209615', # OEIS-Catalogue: A209615 planepath=AlternatePaper turn_type=LSR Right => 'A292077', # OEIS-Catalogue: A292077 planepath=AlternatePaper turn_type=Right # # Not quite, A106665 has OFFSET=0 cf first here i=1 # 'Left' => 'A106665', # turn, 1=left,0=right # # OEIS-Catalogue: A106665 planepath=AlternatePaper i_offset=1 }, }; } { package Math::PlanePath::GosperSide; # Suspect not in OEIS: # Left or Right according to lowest non-zero ternary digit 1 or 2 # use constant _NumSeq_Turn_oeis_anum => { '' => { Left => 'A137893', # turn, 1=left,0=right, OFFSET=1 SLR => 'A060236', # base-3 lowest non-zero digit 1=left,2=right # OEIS-Catalogue: A137893 planepath=GosperSide # OEIS-Other: A137893 planepath=TerdragonCurve # OEIS-Catalogue: A060236 planepath=GosperSide turn_type=SLR # OEIS-Other: A060236 planepath=TerdragonCurve turn_type=SLR # A060236 would also be a "TTurn3" # cf A136442 - a(3n)=1, a(3n-1)=0, a(3n+1)=a(n) # ternary lowest non-1 0->1 2->0 # Not quite, A080846 OFFSET=0 values 0,1,0,0,1 which are N=1 here # Right => 'A080846', # # OEIS-Catalogue: A080846 planepath=GosperSide turn_type=Right # # OEIS-Other: A080846 planepath=TerdragonCurve turn_type=Right # Or A189640 has extra initial 0. }, }; } { package Math::PlanePath::TerdragonCurve; # GosperSide and TerdragonCurve same turn sequence, by diff angles use constant _NumSeq_Turn_Turn4_min => 1; use constant _NumSeq_Turn_Turn4_max => 3; use constant _NumSeq_Turn_oeis_anum => { 'arms=1' => Math::PlanePath::GosperSide->_NumSeq_Turn_oeis_anum->{''} }; } { package Math::PlanePath::TerdragonRounded; use constant _NumSeq_Turn_Turn4_min => 0.5; use constant _NumSeq_Turn_Turn4_max => 3.5; } { package Math::PlanePath::TerdragonMidpoint; use constant _NumSeq_Turn_Turn4_max => 3; } { package Math::PlanePath::AlternateTerdragon; use constant _NumSeq_Turn_Turn4_min => 1; use constant _NumSeq_Turn_Turn4_max => 3; # use constant _NumSeq_Turn_oeis_anum => # { 'arms=1' => # { # # Not quite, A156595 OFFSET=0, whereas here N=1 first turn # # Right => 'A156595', # }, # }; } { package Math::PlanePath::R5DragonCurve; use constant _NumSeq_Turn_Turn4_min => 1; # right or left turn always # # Not quite, OFFSET=0 values 0,0,1,1,0 # # cf first turn here N=1 values 0,0,1,1,0 # # 'Math::PlanePath::R5DragonCurve' => # # { Right => 'A175337', # # # OEIS-Catalogue: A175337 planepath=R5DragonCurve turn_type=Right # # }, } # { package Math::PlanePath::R5DragonMidpoint; # } { package Math::PlanePath::CCurve; use constant _NumSeq_Turn_oeis_anum => { '' => { NotStraight => 'A035263', # main A035263 is KochCurve turn # OEIS-Other: A035263 planepath=CCurve turn_type=NotStraight }, # Not quite, A096268 OFFSET=1 vs first turn N=1 here # Straight => 'A096268' }; } # { package Math::PlanePath::ComplexPlus; # } { package Math::PlanePath::ComplexMinus; # use constant _NumSeq_Turn_oeis_anum => # { 'realpart=1' => # { # # Not quite, A011658 OFFSET=0 vs first turn N=1 here # # NotStraight => 'A011658', # repeat 0,0,0,1,1 # # # OEIS-Other: A011658 planepath=ComplexMinus turn_type=NotStraight # }, # }; } # { package Math::PlanePath::ComplexRevolving; # } { package Math::PlanePath::Rows; # 3---4 width=2 # \ N=2 turn4=1.5 # 1---2 N=3 turn4=2.5 sub _NumSeq_Turn_Turn4_min { my ($self) = @_; return ($self->{'width'} == 2 ? 1.5 # at N=2 : 0); # otherwise has straight points } # *-------- # \---\ # *---------* sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return ($self->{'width'} <= 1 ? 0 # width=1 always straight : (Math::NumSeq::PlanePathTurn::_turn_func_Turn4 (1-$self->{'width'},1, 1,0))); # at row start } use constant _NumSeq_Turn_oeis_anum => { 'n_start=1,width=0' => # Rows width=0 is trivial X=N,Y=0 { Left => 'A000004', # all-zeros LSR => 'A000004', # all zeros, straight # OEIS-Other: A000004 planepath=Rows,width=0 # OEIS-Other: A000004 planepath=Rows,width=0 turn_type=LSR }, # 4 N=1 turn=2 # \ N=2 turn=4 # 2---3 # \ # 0---1 'n_start=-1,width=2' => { TTurn6 => 'A010694', # repeat 2,4 with OFFSET=0 # OEIS-Other: A010694 planepath=Rows,width=2,n_start=-1 turn_type=TTurn6 }, }; } { package Math::PlanePath::Columns; # 2 4 height=2 # | \ | N=2 turn4=2.5 # 1 3 N=3 turn4=1.5 sub _NumSeq_Turn_Turn4_min { my ($self) = @_; return ($self->{'height'} == 2 ? 1.5 # at N=3 : 0); # otherwise has straight points } # * # | \ # | | # * * sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return ($self->{'height'} <= 1 ? 0 # height=1 always straight : (Math::NumSeq::PlanePathTurn::_turn_func_Turn4 (0,1, 1,1-$self->{'height'}))); # at column top } use constant _NumSeq_Turn_oeis_anum => { 'n_start=1,height=0' => # Columns height=0 is trivial X=N,Y=0 { Left => 'A000004', # all-zeros LSR => 'A000004', # all zeros, straight # OEIS-Other: A000004 planepath=Columns,height=0 # OEIS-Other: A000004 planepath=Columns,height=0 turn_type=LSR }, # 'n_start=-1,height=4' => # { Straight => 'A133872', # repeat 1,1,0,0 OFFSET=0 # NotStraight => 'A021913', # repeat 0,0,1,1 OFFSET=0 # # OEIS-Other: A133872 planepath=Columns,n_start=-1,height=4 turn_type=Straight # # OEIS-Other: A021913 planepath=Columns,n_start=-1,height=4 turn_type=NotStraight # }, }; } { package Math::PlanePath::Diagonals; { my %_NumSeq_Turn_Turn4_max = (down => 2.5, # at N=3 dx=-1,dy=+1 then dx=2,dy=-1 up => Math::NumSeq::PlanePathTurn::_turn_func_Turn4(-1,1, 2,-1)); sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return $_NumSeq_Turn_Turn4_max{$self->{'direction'}}; } } use constant _NumSeq_Turn_oeis_anum => { 'direction=down,n_start=0,x_start=0,y_start=0' => { Left => 'A129184', # shift of triangle SRL => 'A156319', # triangle 1, 2, 0, 0, 0, ... in each row OFFSET=1 # OEIS-Catalogue: A129184 planepath=Diagonals,n_start=0 # OEIS-Catalogue: A156319 planepath=Diagonals,n_start=0 turn_type=SRL }, 'direction=down,n_start=-1,x_start=0,y_start=0' => { Right => 'A023531', # 1 at m(m+3)/2 NotStraight => 'A097806', # OEIS-Other: A023531 planepath=Diagonals,n_start=-1 turn_type=Right # OEIS-Catalogue: A097806 planepath=Diagonals,n_start=-1 turn_type=NotStraight }, 'direction=up,n_start=0,x_start=0,y_start=0' => { Right => 'A129184', # shift of triangle SLR => 'A156319', # triangle 1, 2, 0, 0, 0, ... in each row OFFSET=1 # OEIS-Other: A129184 planepath=Diagonals,direction=up,n_start=0 turn_type=Right # OEIS-Other: A156319 planepath=Diagonals,direction=up,n_start=0 turn_type=SLR }, 'direction=up,n_start=-1,x_start=0,y_start=0' => { Left => 'A023531', # 1 at m(m+3)/2 # OEIS-Other: A023531 planepath=Diagonals,direction=up,n_start=-1 NotStraight => 'A097806', # OFFSET=0 # OEIS-Other: A097806 planepath=Diagonals,direction=up,n_start=-1 turn_type=NotStraight }, }; } { package Math::PlanePath::DiagonalsAlternating; use constant _NumSeq_Turn_Turn4_max => 3.5; } { package Math::PlanePath::DiagonalsOctant; sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return ($self->{'direction'} eq 'down' ? 2.5 # N=3 : 3); # N=2 } # # down is left or straight, but also right at N=2,3,4 # # up is straight or right, but also left at N=2,3,4 # 'Math::PlanePath::DiagonalsOctant,direction=down' => # { Left => square or pronic starting from 1 # }, # 'Math::PlanePath::DiagonalsOctant,direction=up' => # { Left => square or pronic starting from 1 # }, } { package Math::PlanePath::Staircase; use constant _NumSeq_Turn_Turn4_min => 1; use constant _NumSeq_Turn_Turn4_max => 3; } # { package Math::PlanePath::StaircaseAlternating; # } { package Math::PlanePath::MPeaks; use constant _NumSeq_Turn_Turn4_max => 3; } { package Math::PlanePath::Corner; use constant _NumSeq_Turn_Turn4_max => 3; use constant _NumSeq_Turn_oeis_anum => { 'wider=1,n_start=-1' => { Left => 'A000007', # turn Left=1 at N=0 only # catalogued only unless/until a better implementation # OEIS-Catalogue: A000007 planepath=Corner,wider=1,n_start=-1 }, 'wider=2,n_start=-1' => { Left => 'A063524', # turn Left=1 at N=1 only # catalogued only unless/until a better implementation # OEIS-Catalogue: A063524 planepath=Corner,wider=2,n_start=-1 }, 'wider=3,n_start=-1' => { Left => 'A185012', # turn Left=1 at N=2 only # catalogued only unless/until a better implementation # OEIS-Catalogue: A185012 planepath=Corner,wider=3,n_start=-1 }, # A185013 Characteristic function of three. # A185014 Characteristic function of four. # A185015 Characteristic function of 5. # A185016 Characteristic function of 6. # A185017 Characteristic function of 7. }; } { package Math::PlanePath::PyramidRows; # if step==0 then always straight ahead # *--* *---* # | step=1 \ step=3 # * * sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return ($self->{'step'} == 0 ? 0 # straight vertical only # at N=2 : (Math::NumSeq::PlanePathTurn::_turn_func_Turn4 (- $self->{'left_slope'},1, 1,0))); } use constant _NumSeq_Turn_oeis_anum => { # PyramidRows step=0 is trivial X=N,Y=0 do { my $href= { Left => 'A000004', # all-zeros, OFFSET=0 LSR => 'A000004', # all zeros straight }; ('step=0,align=centre,n_start=1' => $href, 'step=0,align=right,n_start=1' => $href, 'step=0,align=left,n_start=1' => $href, ); # OEIS-Other: A000004 planepath=PyramidRows,step=0 # OEIS-Other: A000004 planepath=PyramidRows,step=0 turn_type=LSR # OEIS-Other: A000004 planepath=PyramidRows,step=0,align=right # OEIS-Other: A000004 planepath=PyramidRows,step=0,align=left turn_type=LSR }, # PyramidRows step=1 do { my $href= { Left => 'A129184', # triangle 1s shift right }; ('step=1,align=centre,n_start=0' => $href, 'step=1,align=right,n_start=0' => $href, 'step=1,align=left,n_start=0' => $href, ); # OEIS-Other: A129184 planepath=PyramidRows,step=1,n_start=0 # OEIS-Other: A129184 planepath=PyramidRows,step=1,align=right,n_start=0 # OEIS-Other: A129184 planepath=PyramidRows,step=1,align=left,n_start=0 }, do { my $href= { Right => 'A023531', # 1 at n==m*(m+3)/2 }; ('step=1,align=centre,n_start=-1' => $href, 'step=1,align=right,n_start=-1' => $href, ); # OEIS-Other: A023531 planepath=PyramidRows,step=1,n_start=-1 turn_type=Right # OEIS-Other: A023531 planepath=PyramidRows,step=1,align=right,n_start=-1 turn_type=Right }, }; } { package Math::PlanePath::PyramidSides; use constant _NumSeq_Turn_Turn4_max => 3; # at N=3 } { package Math::PlanePath::CellularRule; sub _NumSeq_Turn_Left_increasing { my ($self) = @_; return (defined $self->{'rule'} && ($self->{'rule'} & 0x17) == 0 # single cell only ? 1 : 0); } *_NumSeq_Turn_Right_increasing = \&_NumSeq_Turn_Left_increasing; sub _NumSeq_Turn_LSR_increasing { my ($self) = @_; return (defined $self->{'rule'} && ($self->{'rule'} & 0x17) == 0 # single cell only ? 1 : 0); } } { package Math::PlanePath::CellularRule::Line; use constant _NumSeq_Turn_Turn4_max => 0; use constant _NumSeq_Turn_Turn4_integer => 1; } { package Math::PlanePath::CellularRule::OneTwo; # 5-6 5--6 # \ | # 4 left rule=6 4 right rule=20 # ^----. N=2 turn4=2.5 / N=2 turn4=0.5 # 2--3 N=3 turn4=3.5 2--3 N=3 turn4=3 # \ N=4 turn4= | # 1 1 sub _NumSeq_Turn_Turn4_min { my ($self) = @_; return ($self->{'align'} eq 'left' ? Math::NumSeq::PlanePathTurn::_turn_func_Turn4(1,0, -2,1) # N=3 : 0.5); } sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return ($self->{'align'} eq 'left' ? Math::NumSeq::PlanePathTurn::_turn_func_Turn4(-2,1, -1,1) # N=4 : 3); } use constant _NumSeq_Turn_oeis_anum => { 'align=left,n_start=-1' => { SRL => 'A131534', # repeat 1,2,1, 1,2,1, ... OFFSET=0 # OEIS-Catalogue: A131534 planepath=CellularRule,rule=6,n_start=-1 turn_type=SRL # OEIS-Other: A131534 planepath=CellularRule,rule=38,n_start=-1 turn_type=SRL # OEIS-Other: A131534 planepath=CellularRule,rule=134,n_start=-1 turn_type=SRL # OEIS-Other: A131534 planepath=CellularRule,rule=166,n_start=-1 turn_type=SRL }, 'align=right,n_start=-1' => { SRL => 'A130196', # repeat 1,2,2, 1,2,2, ... OFFSET=0 # OEIS-Catalogue: A130196 planepath=CellularRule,rule=20,n_start=-1 turn_type=SRL # OEIS-Other: A130196 planepath=CellularRule,rule=52,n_start=-1 turn_type=SRL # OEIS-Other: A130196 planepath=CellularRule,rule=148,n_start=-1 turn_type=SRL # OEIS-Other: A130196 planepath=CellularRule,rule=180,n_start=-1 turn_type=SRL }, }; } { package Math::PlanePath::CellularRule::Two; # 5--6 6--7 # ^---. | # 4--5 left rule=6 4--5 right rule=84 # ^----. N=2 turn4=2.5 | N=2 turn4=1 # 2--3 N=3 turn4= 2--3 N=3 turn4=3 # \ | # 1 1 sub _NumSeq_Turn_Turn4_min { my ($self) = @_; return ($self->{'align'} eq 'left' ? Math::NumSeq::PlanePathTurn::_turn_func_Turn4(1,0, -2,1) # N=3 : 1); } sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return ($self->{'align'} eq 'left' ? 2.5 : 3); } use constant _NumSeq_Turn_oeis_anum => { 'align=right,n_start=-1' => { # right line 2, stair step # | # 3--1 # | # 3--1 Turn4 amounts # | # * Turn4 => 'A176040', # 3,1 repeating OFFSET=0 }, # OEIS-Catalogue: A176040 planepath=CellularRule,rule=84,n_start=-1 turn_type=Turn4 # OEIS-Other: A176040 planepath=CellularRule,rule=116,n_start=-1 turn_type=Turn4 # OEIS-Other: A176040 planepath=CellularRule,rule=212,n_start=-1 turn_type=Turn4 # OEIS-Other: A176040 planepath=CellularRule,rule=244,n_start=-1 turn_type=Turn4 }; } { package Math::PlanePath::CellularRule::OddSolid; use constant _NumSeq_Turn_Turn4_max => 2.5; # at N=2 # R 0 0 L 1 0 0 2 # R 0 L 1 0 2 # R L 1 2 # . . use constant _NumSeq_Turn_oeis_anum => { 'n_start=0' => { SRL => 'A156319', # triangle rows 1,2,0,0,0,0,... # OEIS-Other: A156319 planepath=CellularRule,rule=50,n_start=0 turn_type=SRL # OEIS-Other: A156319 planepath=CellularRule,rule=58,n_start=0 turn_type=SRL # OEIS-Other: A156319 planepath=CellularRule,rule=250,n_start=0 turn_type=SRL # OEIS-Other: A156319 planepath=CellularRule,rule=179,n_start=0 turn_type=SRL }, }; } { package Math::PlanePath::CellularRule54; use constant _NumSeq_Turn_Turn4_max => 2.5; } { package Math::PlanePath::CellularRule57; sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return ($self->{'mirror'} ? 3.5 : Math::NumSeq::PlanePathTurn::_turn_func_Turn4(-2,1, 2,0)); # N=2 } } { package Math::PlanePath::CellularRule190; use constant _NumSeq_Turn_Turn4_max => 2.5; } # { package Math::PlanePath::CoprimeColumns; # } # { package Math::PlanePath::DivisibleColumns; # } # { package Math::PlanePath::File; # # File points from a disk file # # FIXME: analyze points for min/max etc # } { package Math::PlanePath::QuintetCurve; use constant _NumSeq_Turn_Turn4_max => 3; } { package Math::PlanePath::QuintetCentres; use constant _NumSeq_Turn_Turn4_max => 3.5; } # { package Math::PlanePath::QuintetSide; # PlanePathTurn planepath=QuintetSide, turn_type=SLR # match 1,2,1,1,2,2,1,2,1,1,2,1,1,2,2,1,2,2,1,2,1,1,2,2,1,2 # A060236 If n mod 3 = 0 then a(n)=a(n/3), otherwise a(n)=n mod 3. # A060236 ,1,2,1,1,2,2,1,2,1,1,2,1,1,2,2,1,2,2,1,2,1,1,2,2,1,2,1,1,2,1,1,2,2,1,2,1,1,2,1,1,2,2,1,2,2,1,2,1,1,2,2,1,2,2,1,2,1,1,2,2,1,2,1,1,2,1,1,2,2,1,2,2,1,2,1,1,2,2,1,2,1,1,2,1,1,2,2,1,2,1,1,2,1,1,2,2,1,2,2,1,2,1,1,2,2, # } # { package Math::PlanePath::DekkingCurve; # } # { package Math::PlanePath::DekkingCentres; # } # { package Math::PlanePath::CincoCurve; # } { package Math::PlanePath::CornerReplicate; use constant _NumSeq_Turn_Turn4_min => # apparent minimum Math::NumSeq::PlanePathTurn::_turn_func_Turn4(-1,0, -2,-1); # at N=11 use constant _NumSeq_Turn_Turn4_max => 3; # apparent maximum } { package Math::PlanePath::SquareReplicate; use constant _NumSeq_Turn_Turn4_max => Math::NumSeq::PlanePathTurn::_turn_func_Turn4(2,1, 0,1); # at N=9 } { package Math::PlanePath::DigitGroups; # radix=3 "11110222222" len many 1s, 2*len-2 many 2s gives ever-increasing # radix=4 "1303333...3333" ever-increasing } { package Math::PlanePath::FibonacciWordFractal; use constant _NumSeq_Turn_oeis_anum => { '' => { SRL => 'A156596', # turns 0=straight,1=right,2=left # OEIS-Catalogue: A156596 planepath=FibonacciWordFractal turn_type=SRL # Not quite, A003849 OFFSET=0 vs first turn N=1 here # Straight => 'A003849' }, }; } # { package Math::PlanePath::LTiling; # } { package Math::PlanePath::WythoffArray; use constant _NumSeq_Turn_Turn4_max => # apparent maximum Math::NumSeq::PlanePathTurn::_turn_func_Turn4(-2,3, 5,-4); # at N=12 } { package Math::PlanePath::WythoffPreliminaryTriangle; # apparent maximum, searched through to N=10_000_000 # turn4 = 3.17777777777778 use constant _NumSeq_Turn_Turn4_max => Math::NumSeq::PlanePathTurn::_turn_func_Turn4(-54,-16, -8,13); # at N=1344 } { package Math::PlanePath::PowerArray; # Turn4 ... # # radix=2 # min i=2[10] 1.50000 px=1,py=0 dx=-1,dy=1 -1.000 # max i=3[11] 2.20000 px=-1,py=1 dx=2,dy=-1[10,-1] -2.000 # # radix=3 # min i=130[11211] 0.00000 px=-1,py=58 dx=0,dy=1 0.000 # max i=67[2111] 3.98889 px=-1,py=30 dx=0,dy=1[0,1] 0.000 # # radix=4 # min i=2[2] 0.00000 px=0,py=1 dx=0,dy=1 0.000 # max i=53[311] 3.98889 px=-1,py=30 dx=0,dy=1[0,1] 0.000 # # radix=5 # min i=2[2] 0.00000 px=0,py=1 dx=0,dy=1 0.000 # max i=46[141] 3.98889 px=-1,py=29 dx=0,dy=1[0,1] 0.000 # # radix=6 # min i=2[2] 0.00000 px=0,py=1 dx=0,dy=1 0.000 # max i=43[111] 3.98889 px=-1,py=30 dx=0,dy=1[0,1] 0.000 # # radix=7 # min i=2[2] 0.00000 px=0,py=1 dx=0,dy=1 0.000 # max i=43[61] 3.98889 px=-1,py=31 dx=0,dy=1[0,1] 0.000 # # radix=8 # min i=2[2] 0.00000 px=0,py=1 dx=0,dy=1 0.000 # max i=41[51] 3.98889 px=-1,py=31 dx=0,dy=1[0,1] 0.000 # # radix=9 # min i=2[2] 0.00000 px=0,py=1 dx=0,dy=1 0.000 # max i=37[41] 3.98889 px=-1,py=29 dx=0,dy=1[0,1] 0.000 # # radix=10 # min i=2[2] 0.00000 px=0,py=1 dx=0,dy=1 0.000 # max i=41[41] 3.98889 px=-1,py=33 dx=0,dy=1[0,1] 0.000 # # radix=16 # min i=2[2] 0.00000 px=0,py=1 dx=0,dy=1 0.000 # max i=33[21] 3.98889 px=-1,py=29 dx=0,dy=1[0,1] 0.000 # # radix=29 # min i=2[2] 0.00000 px=0,py=1 dx=0,dy=1 0.000 # max i=59[21] 3.98889 px=-1,py=55 dx=0,dy=1[0,1] 0.000 # use constant _NumSeq_oeis_anum => # # Not quite, A011765 0,0,0,1 repeating has OFFSET=1 # # cf n_start=1 is first turn at N=2 # # Left => 'A011765', # # Right => 'A011765', # # # Not quite, A131534 has OFFSET=1 vs first turn at N=2 here # # 'radix=3' => # # { SRL => 'A131534', # repeat 1,2,1, OFFSET=0 # # } # # # Not quite, A007877 has OFFSET=1 vs first turn at N=2 here # # 'radix=4' => # # { SRL => 'A007877', # repeat 0,1,2,1 # # } # # # Not quite, 0,0,2,1,2 here vs A053796 0,2,1,2,0 # # 'radix=5' => # # { SRL => 'A053796', # repeat 0,2,1,2,0 # # } # }; } { package Math::PlanePath::ToothpickTree; { my %_NumSeq_Turn_Turn4_max = (wedge => 3, # at N=1 turn right ); sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return $_NumSeq_Turn_Turn4_max{$self->{'parts'}} || 4; } } { my %_NumSeq_Turn4_max_is_supremum = (wedge => 0, ); sub _NumSeq_Turn4_max_is_supremum { my ($self) = @_; my $ret = $_NumSeq_Turn4_max_is_supremum{$self->{'parts'}}; return (defined $ret ? $ret : 1); } } } { package Math::PlanePath::ToothpickReplicate; { my %_NumSeq_Turn_Turn4_max = ( # at N=16 '3' => Math::NumSeq::PlanePathTurn::_turn_func_Turn4(-1,3, 0,1), ); sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return $_NumSeq_Turn_Turn4_max{$self->{'parts'}} || 3.5; } } } { package Math::PlanePath::ToothpickUpist; use constant _NumSeq_Turn_Turn4_max => Math::NumSeq::PlanePathTurn::_turn_func_Turn4(-2,1, 2,0); # at N=4 } { package Math::PlanePath::ToothpickSpiral; use constant _NumSeq_Turn_Turn4_min => 1; # left or right always } { package Math::PlanePath::LCornerReplicate; # min i=63[333] 0.25556 px=1,py=0 dx=7,dy=3 2.333 use constant _NumSeq_Turn_Turn4_min => Math::NumSeq::PlanePathTurn::_turn_func_Turn4(1,0, 7,3); # at N=63 # higher N=1333..3333[base4] loop 3.8, 2.8, 1.7888, 0.7888 # max i=8191[1333333] 3.80000 px=-1,py=0 dx=-38,dy=13[-212,31] -2.923 use constant _NumSeq_Turn_Turn4_max => Math::NumSeq::PlanePathTurn::_turn_func_Turn4(-1,0, -38,13); # at N=8191 } { package Math::PlanePath::LCornerTree; # parts=3 maybe maximum # max i=3107[300203] 3.98889 px=1,py=0 dx=66,dy=-1[1002,-1] -66.000 # LCornerTree,parts=diagonal-1 Turn4 values_max=4 vs saw_values_max=3 at i=27 (to i_end=801) { my %_NumSeq_Turn_Turn4_max = (wedge => 3, # at N=14 'wedge+1' => 3, # at N=19 diagonal => 3, # at N=21 'diagonal-1' => 3, # at N=27 ); sub _NumSeq_Turn_Turn4_max { my ($self) = @_; return $_NumSeq_Turn_Turn4_max{$self->{'parts'}} || 4; } } { my %_NumSeq_Turn4_max_is_supremum = (4 => 0, # apparently ); sub _NumSeq_Turn4_max_is_supremum { my ($self) = @_; return $_NumSeq_Turn4_max_is_supremum{$self->{'parts'}}; } } } 1; __END__ =for stopwords Ryde Math-PlanePath NumSeq PlanePath SquareSpiral ie LSR dX,dY dx1,dy1 dx2,dy2 dx1 dx2 SRL dX =head1 NAME Math::NumSeq::PlanePathTurn -- turn sequence from PlanePath module =head1 SYNOPSIS use Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'DragonCurve', turn_type => 'Left'); my ($i, $value) = $seq->next; =head1 DESCRIPTION This is a tie-in to present turns from a C module in the form of a NumSeq sequence. The C choices are "Left" 1=left, 0=right or straight "Right" 1=right, 0=left or straight "Straight" 1=straight, 0=left or right "NotStraight" 0=straight, 1=left or right "LSR" 1=left, 0=straight, -1=right "SLR" 0=straight, 1=left, 2=right "SRL" 0=straight, 1=right, 2=left In each case the value at sequence index i is the turn at N=i, i+1 ^ | | i-1 ---> i turn at i first turn at i = n_start + 1 For multiple "arms" in the path, the turn follows that particular arm so locations of N = i-arms to i to i+arms. i values start C so that i-arms is C which is the first N on the path. A single arm path beginning N=0 has its first turn at i=1. For "Straight", "LSR", "SLR", and "SRL", straight means either straight ahead or 180-degree reversal, ie. the direction N to N+1 is along the same line as N-1 to N was. "Left" means to the left side of the N-1 to N line, so not straight or right. Similarly "Right" means to the right side of the N-1 to N line, so not straight or left. =head1 FUNCTIONS See L for behaviour common to all sequence classes. =over 4 =item C<$seq = Math::NumSeq::PlanePathTurn-Enew (key=Evalue,...)> Create and return a new sequence object. The options are planepath string, name of a PlanePath module planepath_object PlanePath object turn_type string, as described above C can be either the module part such as "SquareSpiral" or a full class name "Math::PlanePath::SquareSpiral". =item C<$value = $seq-Eith($i)> Return the turn at N=$i in the PlanePath. =item C<$bool = $seq-Epred($value)> Return true if C<$value> occurs as a turn. Often this is merely the possible turn values 1,0,-1, etc, but some spiral paths for example only go left or straight in which case only 1 and 0 occur and C reflects that. =item C<$i = $seq-Ei_start()> Return the first index C<$i> in the sequence. This is the position C returns to. This is C<$path-En_start() - $path-Earms_count()> from the PlanePath object. =back =head1 FORMULAS =head2 Turn Left or Right A turn left or right is identified by considering the dX,dY at N-1 and at N. N+1 * | | | dx2,dy2 | N * / / / dx1,dy1 N-1 * With the two vectors dx1,dy1 and dx2,dy2 at a common origin, if the dx2,dy2 is "above" the dx1,dy1 line then it's a turn to the left, or below is a turn to the right dx2,dy2 * ^ * dx1,dy1 | ^ | / |/ o At dx2, the Y value of the dx1,dy1 vector is cmpY = dx2 * dy1/dx1 if dx1 != 0 left if dy2 > cmpY dy2 > dx2 * dy1/dx1 so dy2 * dx1 > dx2 * dy1 This cross-product comparison dy2*dx1 E dx2*dy1 works when dx1=0 too, ie. when dx1,dy1 is vertical left if dy2 * 0 > dx2 * dy1 0 > dx2*dy1 good, left if dx2 and dy1 opposite signs So dy2*dx1 > dx2*dy1 left dy2*dx1 < dx2*dy1 right dy2*dx1 = dx2*dy1 straight, including 180 degree reverse =head1 SEE ALSO L, L, L, L L has a C turn calculator =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/0002755000175000017500000000000014001441522015214 5ustar ggggMath-PlanePath-129/lib/Math/PlanePath/TerdragonMidpoint.pm0000644000175000017500000006371413734026654021235 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=TerdragonMidpoint --lines --scale=40 # # math-image --path=TerdragonMidpoint --all --output=numbers_dash --size=78x60 # math-image --path=TerdragonMidpoint,arms=6 --all --output=numbers_dash --size=78x60 package Math::PlanePath::TerdragonMidpoint; use 5.004; use strict; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'digit_join_lowtohigh', 'round_up_pow'; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; use constant parameter_info_array => [ { name => 'arms', share_key => 'arms_6', display => 'Arms', type => 'integer', minimum => 1, maximum => 6, default => 1, width => 1, description => 'Arms', } ]; { my @x_negative_at_n = (undef, 12, 5, 2, 2, 2, 2); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } { my @y_negative_at_n = (undef, 158, 73, 17, 7, 4, 4); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } use constant sumabsxy_minimum => 2; # X=2,Y=0 or X=1,Y=1 sub rsquared_minimum { my ($self) = @_; return ($self->arms_count < 2 ? 4 # 1 arm, minimum X=2,Y=0 : 2); # 2 or more arms, minimum X=1,Y=1 } use constant dx_minimum => -2; sub dx_maximum { my ($self) = @_; return ($self->{'arms'} == 1 ? 1 : 2); } use constant dy_minimum => -1; use constant dy_maximum => 1; sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; return ($self->{'arms'} == 1 ? (1,1, # NE -2,0, # W 1,-1) # SE : Math::PlanePath::_UNDOCUMENTED__dxdy_list_six()); } { my @_UNDOCUMENTED__dxdy_list_at_n = (undef, 12, 25, 37, 15, 18, 5); sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $_UNDOCUMENTED__dxdy_list_at_n[$self->{'arms'}]; } } use constant absdx_minimum => 1; use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; # arms=1 curve goes at 60,180,300 degrees # arms=2 second +60 to 120,240,0 degrees # so when arms==1 dir minimum is 60 degrees North-East # sub dir_minimum_dxdy { my ($self) = @_; return ($self->{'arms'} == 1 ? (1,1) # North-East : (1,0)); # East } use constant dir_maximum_dxdy => (1,-1); # South-East sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; # N=5 first right, and on multi-arms 10,15,20,25,30 return 5*$self->arms_count; } #------------------------------------------------------------------------------ # Not quite. # # all even points when arms==3 # use Math::PlanePath::TerdragonCurve; # *xy_is_visited = \&Math::PlanePath::TerdragonCurve::xy_is_visited; sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(6, $self->{'arms'} || 1)); return $self; } sub n_to_xy { my ($self, $n) = @_; ### TerdragonMidpoint n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } { my $int = int($n); if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'}); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } # ENHANCE-ME: own code ... # require Math::PlanePath::TerdragonCurve; my ($x1,$y1) = $self->Math::PlanePath::TerdragonCurve::n_to_xy($n); my ($x2,$y2) = $self->Math::PlanePath::TerdragonCurve::n_to_xy($n+$self->{'arms'}); # dx = x2-x1 # X = 2 * (x1 + dx/2) # = 2 * (x1 + x2/2 - x1/2) # = 2 * (x1/2 + x2/2) # = x1+x2 return ($x1+$x2, $y1+$y2); } # sub n_to_xy { # my ($self, $n) = @_; # ### TerdragonMidpoint n_to_xy(): $n # # if ($n < 0) { return; } # if (is_infinite($n)) { return ($n, $n); } # # my $frac; # { # my $int = int($n); # $frac = $n - $int; # inherit possible BigFloat # $n = $int; # BigFloat int() gives BigInt, use that # } # # my $zero = ($n * 0); # inherit bignum 0 # # ($n, my $rot) = _divrem ($n, $self->{'arms'}); # # # ENHANCE-ME: sx,sy just from len,len # my @digits; # my @sx; # my @sy; # { # my $sx = $zero + 1; # my $sy = -$sx; # while ($n) { # push @digits, ($n % 2); # push @sx, $sx; # push @sy, $sy; # $n = int($n/2); # # # (sx,sy) + rot+90(sx,sy) # ($sx,$sy) = ($sx - $sy, # $sy + $sx); # } # } # # ### @digits # my $rev = 0; # my $x = $zero; # my $y = $zero; # my $above_low_zero = 0; # # for (my $i = $#digits; $i >= 0; $i--) { # high to low # my $digit = $digits[$i]; # my $sx = $sx[$i]; # my $sy = $sy[$i]; # ### at: "$x,$y $digit side $sx,$sy" # ### $rot # # if ($rot & 2) { # $sx = -$sx; # $sy = -$sy; # } # if ($rot & 1) { # ($sx,$sy) = (-$sy,$sx); # } # ### rotated side: "$sx,$sy" # # if ($rev) { # if ($digit) { # $x += -$sy; # $y += $sx; # ### rev add to: "$x,$y next is still rev" # } else { # $above_low_zero = $digits[$i+1]; # $rot ++; # $rev = 0; # ### rev rot, next is no rev ... # } # } else { # if ($digit) { # $rot ++; # $x += $sx; # $y += $sy; # $rev = 1; # ### plain add to: "$x,$y next is rev" # } else { # $above_low_zero = $digits[$i+1]; # } # } # } # # # Digit above the low zero is the direction of the next turn, 0 for left, # # 1 for right. # # # ### final: "$x,$y rot=$rot above_low_zero=".($above_low_zero||0) # # if ($rot & 2) { # $frac = -$frac; # rotate 180 # $x -= 1; # } # if (($rot+1) & 2) { # # rot 1 or 2 # $y += 1; # } # if (!($rot & 1) && $above_low_zero) { # $frac = -$frac; # } # $above_low_zero ^= ($rot & 1); # if ($above_low_zero) { # $y = $frac + $y; # } else { # $x = $frac + $x; # } # # ### rotated offset: "$x_offset,$y_offset return $x,$y" # return ($x,$y); # } # w^2 = -1+w # c = (X-Y)/2 x=2c+d # d = Y y=d # (c+dw)/(w+1) # = (c+dw)*(2-w)/3 # = (2c-cw + 2dw-dw^2) / 3 # = (2c-cw + 2dw-d(w-1)) / 3 # = (2c-cw + 2dw-dw+d)) / 3 # = (2c+d + w(-c + 2d-d)) / 3 # = (2c+d + w(d-c)) / 3 # # = (x-y+y + w(y - (x-y)/2)) / 3 # = (x + w((2y-x+y)/2)) / 3 # = (x + w((3y-x)/2)) / 3 # then # xq = 2c+d # = (2x + (3y-x)/2 ) / 3 # = (4x + 3y-x)/6 # = (3x+3y)/6 # = (x+y)/2 # yq = d = (3y-x)/6 # # (-1+5w)(2-w) x=2*-1+5=3,y=5 # = -2+w+10w-5w^2 # = -2+11w-5(w-1) # = -2+11w-5w+5 # = 3+6w -> 1+2w # c=2*-1+5=3 d=-1+5=4 # x=2*1+2=4 y=3 # # (w+1)*(2-w) # = 2w-w^2+2-w # = 2w-(w-1)+2-w # = 2w-w+1+2-w # = 3 -> 1 x=2 # # 3w*(2-w) x=3,y=3 div x=3,y(3+3)/2=3 # = 6w-3w^2 # = 6w-3(w-1) # = 6w-3w+3 # = 3w+3 -> w+1 x=3,y=1 # # (w+1)(w+1) # = w^2+2w+1 # = w-1+2w+1 # = 3w # # # x=3,y=3 (x+y)/2=3 # X=-3 -2 -1 0 1 2 3 my @yx_to_arm = ([9, 9, 9, 4, 9, 9, 9], # Y=-2 [3, 9, 9, 9, 9, 9, 5], # Y=-1 [9, 9, 9, 9, 9, 9, 9], # Y=0 [2, 9, 9, 9, 9, 9, 0], # Y=1 [9, 9, 9, 1, 9, 9, 9], # Y= 2 ); # my @yx_to_dxdy = (undef,undef, -1,1, undef,undef, 0,0, undef,undef, 1,-1, # 1,1, 0,0, -1,-1, -2,0, 0,0, 2,0, # undef,undef, 1,-1, undef,undef, -1,1, undef,undef, 0,0, # 0,0, 2,0, 1,1, 0,0, -1,-1, -2,0, # undef,undef, 0,0, undef,undef, 1,-1, undef,undef, -1,1, # -1,-1, -2,0, 0,0, 2,0, 1,1, 0,0, # ); my @yx_to_dxdy # 12 each row = (undef,undef, undef,undef, 1,1, undef,undef, undef,undef, undef,undef, 0,0, undef,undef, undef,undef, undef,undef, -1,-1, undef,undef, undef,undef, -1,1, undef,undef, 0,0, undef,undef, 1,-1, undef,undef, 2,0, undef,undef, 0,0, undef,undef, -2,0, 0,0, undef,undef, undef,undef, undef,undef, -1,-1, undef,undef, undef,undef, undef,undef, 1,1, undef,undef, undef,undef, undef,undef, undef,undef, 2,0, undef,undef, 0,0, undef,undef, -2,0, undef,undef, -1,1, undef,undef, 0,0, undef,undef, 1,-1, undef,undef, undef,undef, 1,1, undef,undef, undef,undef, undef,undef, 0,0, undef,undef, undef,undef, undef,undef, -1,-1, undef,undef, undef,undef, -1,1, undef,undef, 0,0, undef,undef, 1,-1, undef,undef, 2,0, undef,undef, 0,0, undef,undef, -2,0, 0,0, undef,undef, undef,undef, undef,undef, -1,-1, undef,undef, undef,undef, undef,undef, 1,1, undef,undef, undef,undef, undef,undef, undef,undef, 2,0, undef,undef, 0,0, undef,undef, -2,0, undef,undef, -1,1, undef,undef, 0,0, undef,undef, 1,-1, undef,undef, undef,undef, 1,1, undef,undef, undef,undef, undef,undef, 0,0, undef,undef, undef,undef, undef,undef, -1,-1, undef,undef, undef,undef, -1,1, undef,undef, 0,0, undef,undef, 1,-1, undef,undef, 2,0, undef,undef, 0,0, undef,undef, -2,0, 0,0, undef,undef, undef,undef, undef,undef, -1,-1, undef,undef, undef,undef, undef,undef, 1,1, undef,undef, undef,undef, undef,undef, undef,undef, 2,0, undef,undef, 0,0, undef,undef, -2,0, undef,undef, -1,1, undef,undef, 0,0, undef,undef, 1,-1, ); my @x_to_digit = (1, 2, 0); # digit = X+1 mod 3 sub xy_to_n { my ($self, $x, $y) = @_; ### TerdragonMidpoint xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); if (is_infinite($x)) { return $x; # infinity } if (is_infinite($y)) { return $y; # infinity } my $zero = ($x * 0 * $y); # inherit bignum 0 my @ndigits; # low to high; for (;;) { my $digit = $x_to_digit[$x%3]; my $k = 2*(12*($y%12) + ($x%12)); my $dx = $yx_to_dxdy[$k++]; if (! defined $dx) { ### not a visited point: "k=$k" ### x mod 12: $x%12 ### y mod 12: $y%12 return undef; } ### at: "$x,$y (k=$k) digit=$digit k=$k offset=$yx_to_dxdy[$k-1],$yx_to_dxdy[$k] to ".($x+$yx_to_dxdy[$k-1]).",".($y+$yx_to_dxdy[$k]) push @ndigits, $digit; $x += $dx; $y += $yx_to_dxdy[$k]; last if ($x <= 3 && $x >= -3 && $y <= 2 && $y >= -2); ### assert: ($x+$y) % 2 == 0 ### assert: $x % 3 == 0 ### assert: (3 * $y - $x) % 6 == 0 ($x,$y) = (($x+$y)/2, # divide w+1 ($y-$x/3)/2); ### divide down to: "$x,$y" } ### final: "xy=$x,$y" my $arm = $yx_to_arm[$y+2][$x+3] || 0; # 0 to 5 ### $arm my $arms_count = $self->arms_count; if ($arm >= $arms_count) { return undef; } if ($arm & 1) { ### flip ... @ndigits = map {2-$_} @ndigits; } return digit_join_lowtohigh(\@ndigits, 3, $zero) * $arms_count + $arm; } # quarter size of TerdragonCurve # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### TerdragonCurve rect_to_n_range(): "$x1,$y1 $x2,$y2" my $xmax = int(max(abs($x1),abs($x2))); my $ymax = int(max(abs($y1),abs($y2))); return (0, int (($xmax*$xmax + 3*$ymax*$ymax + 1) / 2) * $self->{'arms'}); } #----------------------------------------------------------------------------- # level_to_n_range() # 3^level segments, one midpoint each # arms*3^level when multi-arm # numbered starting 0 # sub level_to_n_range { my ($self, $level) = @_; return (0, 3**$level * $self->{'arms'} - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, $self->{'arms'}); my ($pow, $exp) = round_up_pow ($n+1, 3); return $exp; } #----------------------------------------------------------------------------- 1; __END__ # 72----66----60----54 # \ / # 55 78 48 # / \ \ / # 61 49 96----90----84 42 # / \ / # 67 43 19 36 # / \ / \ / # 73----79----85 37 25 13 30----24----18 # / \ / \ / # 91 31 7 12 # / \ / # 97 20----14-----8-----2 1 6 35----41----47--... # \ / \ # 26 3 0 29 # \ / \ # ...-44----38----32 9 4 5----11----17----23 100 # / \ / # 15 10 34 94 # / \ / \ / # 21----27----33 16 28 40 88----82----76 # / \ / \ / # 39 22 46 70 # / \ / # 45 87----93----99 52 64 # / \ \ / # 51 81 58 # / \ # 57----63----69----75 =for stopwords eg Ryde Terdragon Math-PlanePath Nlevel Davis Knuth et al terdragon ie Xadj Yadj =head1 NAME Math::PlanePath::TerdragonMidpoint -- dragon curve midpoints =head1 SYNOPSIS use Math::PlanePath::TerdragonMidpoint; my $path = Math::PlanePath::TerdragonMidpoint->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XXThis is midpoints of an integer version of the terdragon curve by Davis and Knuth. 30----29----28----27 13 \ / 31 26 12 \ / 36----35----34----33----32 25 11 \ / 37 41 24 10 \ / \ / 38 40 42 23----22----21 9 \ / \ / 39 43 20 8 \ / 48----47----46----45----44 19 12----11----10-----9 7 \ / \ / 49 18 13 8 6 \ / \ / ...---50 17----16----15----14 7 5 / 6 4 / 5-----4-----3 3 / 2 2 / 1 1 / 0 <- Y=0 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -12-11-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 ... The points are the middle of each edge of a double-size C. ... \ 6 -----8----- double size \ TerdragonCurve \ giving midpoints 5 7 \ \ 4 -----6---- _ \ / \ \ / \ 3 5 4 3 \ / \ \_/ \ 2 _----2----- \ \ 1 1 \ \ Y=0 -> +-----0-----. ^ X=0 1 2 3 4 5 6 For example in the C N=3 to N=4 is X=3,Y=1 to X=2,Y=2 and that's doubled out here to X=6,Y=2 and X=4,Y=4 then the midpoint of those positions is X=5,Y=3 for N=3 in the C. The result is integer X,Y coordinates on every second point per L, but visiting only 3 of every 4 such triangular points, which in turn is 3 of 8 all integer X,Y points. The points used are a pattern of alternate rows with 1 of 2 points and 1 of 4 points. For example the Y=7 row is 1 of 2 and the Y=8 row is 1 of 4. Notice the pattern is the same when turned by 60 degrees. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * =head2 Arms Multiple copies of the curve can be selected, each advancing successively. Like the main C the midpoint curve covers 1/6 of the plane and 6 arms rotated by 60, 120, 180, 240 and 300 degrees mesh together perfectly. With 6 arms all the alternating "1of2" and "1of4" points described above are visited. C 6> begins as follows. N=0,6,12,18,etc is the first arm (like the single curve above), then N=1,7,13,19 the second copy rotated 60 degrees, N=2,8,14,20 the third rotated 120, etc. arms=>6 ... / ... 42 \ / 43 19 36 \ / \ / 37 25 13 30----24----18 \ / \ / 31 7 12 \ / 20----14-----8-----2 1 6 35----41----47-.. \ / \ 26 3 . 0 29 \ / \ ..-44----38----32 9 4 5----11----17----23 / \ 15 10 34 / \ / \ 21----27----33 16 28 40 / \ / \ 39 22 46 / \ 45 ... / ... =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::TerdragonMidpoint-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =item C<$n = $path-En_start()> Return 0, the first N in the path. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 3**$level - 1)>, or for multiple arms return C<(0, $arms * 3**$level - 1)>. There are 3^level segments comprising the terdragon, or arms*3^level when multiple arms, numbered starting from 0. =back =head1 FORMULAS =head2 X,Y to N An X,Y point can be turned into N by dividing out digits of a complex base b=w+1 where w = 1/2 + i * sqrt(3)/2 w^2 w = 6th root of unity \ / \ / w^3=-1 -----o------ w^0=1 / \ / \ w^4 w^5 At each step the low ternary digit is formed from X,Y and an adjustment applied to move X,Y onto a multiple of w+1 ready to divide out w+1. In the N points above it can be seen that each group of three N values make a straight line, such as N=0,1,2, or N=3,4,5 etc. The adjustment moves the two ends N=0mod3 or N=2mod3 to the centre N=1mod3. The centre N=1mod3 position is always a multiple of w+1. The angles and positions for the N triples follow a 12-point pattern as follows, where each / \ or - is a point on the path (any arm). \ / / \ / / \ / / \ / / \ - \ / \ - - - \ / \ - - - \ / \ - - - \ / \ - - - / \ / / \ / / \ / / \ / \ - - - \ / \ - - - \ / \ - - - \ / \ - - - \ / \ \ / / \ / / \ / / \ / / \ - \ / \ - - - \ / \ - - - \ / \ - - - \ / \ - - - / \ / / \ / / \ / / \ / \ - - - \ / \ - - - \ / \ - - - \ / \ - - - \ / \ \ / / \ / / \ / / \ / / \ - \ / \ - - - \ / \ - - - \ / \ - - - \ / \ - - - / \ / / \ / / \ / / \ / \ - - - \ / \ - - - \ / \ - - - \ / \ - - - \ / \ \ / / \ / / \ / / \ / / \ - \ / \ - - - \ / \ - - - \ / \ - - - \ / \ - - - / \ / / \ / / \ / / \ / \ - - - \ / \ - - - \ / \ - - - \ / \ - - - \ / \ \ / / \ / / \ / / \ / / \ - \ / \ - - - \ / \ - - - \ / \ - - - \ / \ - - - / \ / / \ / / \ / / \ / \ - - - \ / \ - - - \ / \ - - - \ / \ - - - \ / \ \ / / \ / / \ / / \ / / \ - \ / \ - - - \ / \ - - - \ / \ - - - \ / \ - - - / \ / / \ / / \ / / \ / \ - - - \ / \ - - - \ / \ - - - \ / \ - - - \ / \ In the current code a 12x12 table is used, indexed by X mod 12 and Y mod 12. With Xadj and Yadj from there Ndigit = (X + 1) mod 3 # N digits low to high Xm = X + Xadj[X mod 12, Y mod 12] Ym = Y + Yadj[X mod 12, Y mod 12] new X,Y = (Xm,Ym) / (w+1) = (Xm,Ym) * (2-w) / 3 = ((Xm+Ym)/2, (Ym-(Xm/3))/2) Is there a good aX+bY mod 12 or mod 24 for a smaller table? Maybe X+3Y like the digit? Taking C=(X-Y)/2 in triangular coordinate style can reduce the table to 6x6. Points not reached by the curve (ie. not the 3 of 4 triangular or 3 of 8 rectangular described above) can be detected with C or suitably tagged entries in the adjustment table. The X,Y reduction stops at the midpoint of the first triple of the originating arm. So X=3,Y=1 which is N=1 for the first arm, and that point rotated by 60,120,180,240,300 degrees for the others. If only some of the arms are of interest then reaching one of the others means the original X,Y was outside the desired region. Arm X,Y Endpoint --- ------------ 0 3,1 1 0,2 2 -3,1 3 -3,-1 4 0,-2 5 3,-1 For the odd arms 1,3,5 each digit of N must be flipped 2-digit so 0,1,2 becomes 2,1,0, if arm odd then N = 3**numdigits - 1 - N =head1 SEE ALSO L, L, L L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/FactorRationals.pm0000644000175000017500000006474313734026672020702 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Multiples of prime make grid. # [13] L. S. Johnston, Denumerability of the rational number system, Amer. Math. Monthly, 55 (Feb. # 1948), no. 2, 65-70. # www.jstor.org/stable/2305738 # prime factors q1,..qk of n # f(m/n) = m^2*n^2/ (q1q2...qk) # Kevin McCrimmon, 1960 # # integer prod p[i]^a[i] -> rational prod p[i]^b[i] # b[i] = a[2i-1] if a[2i-1]!=0 # b[1]=a[1], b[2]=a[3], b[3]=a[5] # b[i] = -a[2k] if a[2i-1]=0 and is kth such # # b[i] = f(a[i]) where f(n) = (-1)^(n+1) * floor((n+1)/2) # f(0) = 0 # f(1) = 1 # f(2) = -1 # f(3) = 2 # f(4) = -2 # Gerald Freilich, 1965 # # f(n) = n/2 if n even n>=0 # = -(n+1)/2 if n odd n>0 # f(0)=0/2 = 0 # f(1)=-(1+1)/2 = -1 # f(2)=2/2 = 1 # f(3)=-(3+1)/2 = -2 # f(4)=4/2 = 2 # Yoram Sagher, "Counting the rationals", American Math Monthly, Nov 1989, # page 823. http://www.jstor.org/stable/2324846 # # m = p1^e1.p2^e2... # n = q1^f1.q2^f2... # f(m/n) = p1^2e1.p2^2e2... . q1^(2f1-1).q2^(2f2-1)... # so 0 -> 0 0 -> 0 # num 1 -> 2 1 -> -1 # 2 -> 4 2 -> 1 # den -1 1 -> 2*1-1 = 1 3 -> -2 # -2 2 -> 2*2-1 = 3 4 -> 2 # Umberto Cerruti, "Ordinare i razionali Gli alberi di Keplero e di # Calkin-Wilf", following T.J. Heard # B(2k)=-k even=negative and zero # B(2k-1)=k odd=positive # which is Y/X invert # B(0 =2*0) = 0 # B(1 =2*1-1) = 1 # B(2 =2*1) = -1 # B(3 =2*2-1) = 2 # B(4 =2*2) = -2 package Math::PlanePath::FactorRationals; use 5.004; use strict; use Carp 'croak'; use List::Util 'min'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'digit_join_lowtohigh'; use Math::PlanePath::CoprimeColumns; *_coprime = \&Math::PlanePath::CoprimeColumns::_coprime; # uncomment this to run the ### lines # use Smart::Comments; # Not yet. use constant parameter_info_array => [ { name => 'factor_coding', display => 'Sign Encoding', type => 'enum', default => 'even/odd', choices => ['even/odd','odd/even', 'negabinary','revbinary', ], choices_display => ['Even/Odd','Odd/Even', 'Negabinary','Revbinary', ], }, ]; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant x_minimum => 1; use constant y_minimum => 1; use constant gcdxy_maximum => 1; # no common factor use constant absdy_minimum => 1; # factor_coding=even/odd # factor_coding=odd/even # dir_minimum_dxdy() suspect dir approaches 0. # Eg. N=5324 = 2^2.11^3 dx=3,dy=92 0.97925 # N=642735 = 3^5.23^2 dX=45 dY=4 Dir4=0.05644 # 642736 = 2^4.17^2.139 # dir_maximum_dxdy() suspect approaches 360 degrees # use constant dir_maximum_dxdy => (0,0); # the default # # factor_coding=negabinary # dir_minimum_dxdy() = East 1,0 at N=1 # dir_maximum_dxdy() believe approaches 360 degrees # Eg. N=40=2^3.5 X=5, Y=2 # N=41=41 X=41, Y=1 # N=multiple 8 and solitary primes, followed by N+1=prime is dX=big, dY=-1 # # factor_coding=revbinary # dir_maximum_dxdy() approaches 360 degrees dY=-1, dX=big # Eg. N=7208=2^3*17*53 X=17*53 Y=2 # N=7209=3^4*89 X=3^4*89 Y=1 # dX=6308 dY=-1 #------------------------------------------------------------------------------ # even/odd # $n>=0, return a positive if even or negative if odd # $n==0 return 0 # $n==1 return -1 # $n==2 return +1 # $n==3 return -2 # $n==4 return +2 sub _pos_to_pn__even_odd { my ($n) = @_; return ($n % 2 ? -1-$n : $n) / 2; } # # $n is positive or negative, return even for positive or odd for negative. # # $n==0 return 0 # # $n==-1 return 1 # # $n==+1 return 2 # # $n==-2 return 3 # # $n==+2 return 4 # sub _pn_to_pos__even_odd { # my ($n) = @_; # return ($n >= 0 ? 2*$n : -1-2*$n); # } #------------------------------------------------------------------------------ # odd/even # $n>=0, return a positive if even or negative if odd # $n==0 return 0 # $n==1 return +1 # $n==2 return -1 # $n==3 return +2 # $n==4 return -2 sub _pos_to_pn__odd_even { my ($n) = @_; return ($n % 2 ? $n+1 : -$n) / 2; } # # $n is positive or negative, return odd for positive or even for negative. # # $n==0 return 0 # # $n==+1 return 1 # # $n==-1 return 2 # # $n==+2 return 3 # # $n==-2 return 4 # sub _pn_to_pos__odd_even { # my ($n) = @_; # return ($n <= 0 ? -2*$n : 2*$n-1); # } #------------------------------------------------------------------------------ # negabinary sub _pn_to_pos__negabinary { my ($n) = @_; my @bits; while ($n) { my $bit = ($n % 2); push @bits, $bit; $n -= $bit; $n /= 2; $n = -$n; } return digit_join_lowtohigh(\@bits, 2, $n); # zero } sub _pos_to_pn__negabinary { my ($n) = @_; return (($n & 0x55555555) - ($n & 0xAAAAAAAA)); } #------------------------------------------------------------------------------ # revbinary # A065620 pos -> pn # A065621 pn(+ve) -> pos # A048724 pn(-ve) -> pos n XOR 2n # A048725 A048724 twice # cf # A073122 minimizing by taking +/- powers cf A072219 A072339 # rev = 2^e0 - 2^e1 + 2^e2 - 2^e3 + ... + (-1)^t*2^et # 0 <= e0 < e1 < e2 ... sub _pos_to_pn__revbinary { my ($n) = @_; my $sign = 1; my $ret = 0; for (my $bit = 1; $bit <= $n; $bit *= 2) { if ($n & $bit) { $ret += $bit*$sign; $sign = -$sign; } } return $ret; } sub _pn_to_pos__revbinary { my ($n) = @_; my @bits; while ($n) { my $bit = ($n % 2); push @bits, $bit; $n -= $bit; $n /= 2; if ($bit) { $n = -$n; } } return digit_join_lowtohigh(\@bits, 2, $n); # zero } #------------------------------------------------------------------------------ my %factor_coding__pos_to_pn = ('even/odd' => \&_pos_to_pn__even_odd, 'odd/even' => \&_pos_to_pn__odd_even, negabinary => \&_pos_to_pn__negabinary, revbinary => \&_pos_to_pn__revbinary, ); my %factor_coding__pn_to_pos = (# 'even/odd' => \&_pn_to_pos__even_odd, # 'odd/even' => \&_pn_to_pos__odd_even, negabinary => \&_pn_to_pos__negabinary, revbinary => \&_pn_to_pos__revbinary, ); sub new { my $self = shift->SUPER::new(@_); my $factor_coding = ($self->{'factor_coding'} ||= 'even/odd'); $factor_coding__pos_to_pn{$factor_coding} or croak "Unrecognised factor_coding: ",$factor_coding; return $self; } sub n_to_xy { my ($self, $n) = @_; ### FactorRationals n_to_xy(): "$n" if ($n < 1) { return; } if (is_infinite($n)) { return ($n,$n); } # what to do for fractional $n? { my $int = int($n); if ($n != $int) { ### frac ... my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } my $zero = $n * 0; my $pos_to_pn = $factor_coding__pos_to_pn{$self->{'factor_coding'}}; my $x = my $y = ($n * 0) + 1; # inherit bignum 1 my ($limit,$overflow) = _limit($n); ### $limit my $divisor = 2; my $dstep = 1; while ($divisor <= $limit) { if (($n % $divisor) == 0) { my $count = 0; for (;;) { $count++; $n /= $divisor; if ($n % $divisor) { my $pn = &$pos_to_pn($count); ### $count ### $pn my $pow = ($divisor+$zero) ** abs($pn); if ($pn >= 0) { $x *= $pow; } else { $y *= $pow; } last; } } ($limit,$overflow) = _limit($n); ### $limit } $divisor += $dstep; $dstep = 2; } if ($overflow) { ### n too big ... return; } ### remaining $n is prime, count=1: "n=$n" my $pn = &$pos_to_pn(1); ### $pn my $pow = $n ** abs($pn); if ($pn >= 0) { $x *= $pow; } else { $y *= $pow; } ### result: "$x, $y" return ($x, $y); } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### FactorRationals xy_to_n(): "x=$x y=$y" if ($x < 1 || $y < 1) { return undef; # negatives and -infinity } if (is_infinite($x)) { return $x; } # +infinity or nan if (is_infinite($y)) { return $y; } # +infinity or nan if ($self->{'factor_coding'} eq 'negabinary' || $self->{'factor_coding'} eq 'revbinary') { ### negabinary or revbinary ... my $pn_to_pos = $factor_coding__pn_to_pos{$self->{'factor_coding'}}; my $n = 1; my $zero = $x * 0 * $y; # Factorize both $x and $y and apply their pn_to_pos encoded powers to # make $n. A common factor between $x and $y is noticed if $divisor # divides both. my ($limit,$overflow) = _limit(max($x,$y)); my $dstep = 1; for (my $divisor = 2; $divisor <= $limit; $divisor += $dstep, $dstep=2) { my $count = 0; if ($x % $divisor == 0) { if ($y % $divisor == 0) { return undef; # common factor } while ($x % $divisor == 0) { $count++; $x /= $divisor; # mutate loop variable } } elsif ($y % $divisor == 0) { while ($y % $divisor == 0) { $count--; $y /= $divisor; # mutate loop variable } } else { next; } # Here $count > 0 if from $x or $count < 0 if from $y. ### $count ### pn: &$pn_to_pos($count) $count = &$pn_to_pos($count); $n *= ($divisor+$zero) ** $count; # new search limit, perhaps smaller than before ($limit,$overflow) = _limit(max($x,$y)); } if ($overflow) { ### x,y too big to find all primes ... return undef; } # Here $x and $y are primes. if ($x > 1 && $x == $y) { ### common factor final remaining prime x,y ... return undef; } # $x is power p^1 which is negabinary=1 or revbinary=1 so multiply into # $n. $y is power p^-1 and -1 is negabinary=3 so cube and multiply into # $n. $n *= $x; $n *= $y*$y*$y; return $n; } else { ### assert: $self->{'factor_coding'} eq 'even/odd' || $self->{'factor_coding'} eq 'odd/even' if ($self->{'factor_coding'} eq 'odd/even') { ($x,$y) = ($y,$x); } # Factorize $y so as to make an odd power of its primes. Only need to # divide out one copy of each prime, but by dividing out them all the # $limit to search up to is reduced, usually by a lot. # # $ymult is $y with one copy of each prime factor divided out. # $ychop is $y with all primes divided out as they're found. # $y itself is unchanged. # my $ychop = my $ymult = $y; my ($limit,$overflow) = _limit($ychop); my $dstep = 1; for (my $divisor = 2; $divisor <= $limit; $divisor += $dstep, $dstep=2) { next if $ychop % $divisor; if ($x % $divisor == 0) { ### common factor with X ... return undef; } $ymult /= $divisor; # one of $divisor divided out do { $ychop /= $divisor; # all of $divisor divided out } until ($ychop % $divisor); ($limit,$overflow) = _limit($ychop); # new lower $limit, perhaps } if ($overflow) { return undef; # Y too big to find all primes } # remaining $ychop is a prime, or $ychop==1 if ($ychop > 1) { if ($x % $ychop == 0) { ### common factor with X ... return undef; } $ymult /= $ychop; } return $x*$x * $y*$ymult; } } #------------------------------------------------------------------------------ # all rationals X,Y >= 1 with no common factor use Math::PlanePath::DiagonalRationals; *xy_is_visited = Math::PlanePath::DiagonalRationals->can('xy_is_visited'); #------------------------------------------------------------------------------ # even/odd # X=2^10 -> N=2^20 is X^2 # Y=3 -> N=3 # Y=3^2 -> N=3^3 # Y=3^3 -> N=3^5 # Y=3^4 -> N=3^7 # Y*Y / distinct prime factors # # negabinary # X=prime^2 -> N=prime^6 is X^3 # X=prime^6 -> N=prime^26 is X^4.33 # maximum 101010...10110 -> 1101010...10 approaches factor 5 # same for negatives # # revbinary # X=prime^k -> N=prime^(3k) ix X^3 # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### rect_to_n_range() $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); my $n = max($x1,$x2) * max($y1,$y2); my $n_squared = $n * $n; return (1, ($self->{'factor_coding'} eq 'negabinary' ? ($n_squared*$n_squared) * $n # X^5*Y^5 : $self->{'factor_coding'} eq 'revbinary' ? $n_squared * $n # X^3*Y^3 # even/odd, odd/even : $n_squared)); # X^2*Y^2 } #------------------------------------------------------------------------------ # _limit() returns ($limit,$overflow). # # $limit is the biggest divisor to attempt trial division of $n. If $n < # 2^32 then $limit=sqrt($n) and that will find all primes. If $n >= 2^32 # then $limit is smaller than sqrt($n), being calculated from the length of # $n so as to make a roughly constant amount of time doing divisions. But # $limit is always at least 50 so as to divide by primes up to 50. # # $overflow is a boolean, true if $n is too big to search for all primes and # $limit is something smaller than sqrt($n). $overflow is false if $limit # has not been capped and is enough to find all primes. # sub _limit { my ($n) = @_; my $limit = int(sqrt($n)); my $cap = max (int(65536 * 10 / length($n)), 50); if ($limit > $cap) { return ($cap, 1); } else { return ($limit, 0); } } 1; __END__ =for stopwords eg Ryde OEIS ie Math-PlanePath Calkin-Wilf McCrimmon Freilich Yoram Sagher negabinary Denumerability revbinary Niven factorize factorizing factorization =head1 NAME Math::PlanePath::FactorRationals -- rationals by prime powers =head1 SYNOPSIS use Math::PlanePath::FactorRationals; my $path = Math::PlanePath::FactorRationals->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XXXThis path enumerates rationals X/Y with no common factor, based on the prime powers in numerator and denominator, as per =over Kevin McCrimmon, "Enumeration of the Positive Rationals", American Math. Monthly, Nov 1960, page 868. L Gerald Freilich, "A Denumerability Formula for the Rationals", American Math. Monthly, Nov 1965, pages 1013-1014. L Yoram Sagher, "Counting the rationals", American Math. Monthly, Nov 1989, page 823. L =back The result is =cut # math-image --path=FactorRationals,factor_coding=even/odd --all --output=numbers --size=58x16 =pod 15 | 15 60 240 735 960 1815 14 | 14 126 350 1134 1694 13 | 13 52 117 208 325 468 637 832 1053 1300 1573 12 | 24 600 1176 2904 11 | 11 44 99 176 275 396 539 704 891 1100 10 | 10 90 490 810 1210 9 | 27 108 432 675 1323 1728 2700 3267 8 | 32 288 800 1568 2592 3872 7 | 7 28 63 112 175 252 448 567 700 847 6 | 6 150 294 726 5 | 5 20 45 80 180 245 320 405 605 4 | 8 72 200 392 648 968 3 | 3 12 48 75 147 192 300 363 2 | 2 18 50 98 162 242 1 | 1 4 9 16 25 36 49 64 81 100 121 Y=0 | ---------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 A given fraction X/Y with no common factor has a prime factorization X/Y = p1^e1 * p2^e2 * ... The exponents e[i] are positive, negative or zero, being positive when the prime is in the numerator or negative when in the denominator. Those exponents are represented in an integer N by mapping the exponents to non-negative, N = p1^f(e1) * p2^f(e2) * ... f(e) = 2*e if e >= 0 = 1-2*e if e < 0 f(e) e --- --- 0 0 1 -1 2 1 3 -2 4 2 For example X/Y = 125/7 = 5^3 * 7^(-1) encoded as N = 5^(2*3) * 7^(1-2*(-1)) = 5^6 * 7^1 = 5359375 N=3 -> 3^-1 = 1/3 N=9 -> 3^1 = 3/1 N=27 -> 3^-2 = 1/9 N=81 -> 3^2 = 9/1 The effect is to distinguish prime factors of the numerator or denominator by odd or even exponents of those primes in N. Since X and Y have no common factor a given prime appears in one and not the other. The oddness or evenness of the p^f() exponent in N can then encode which of the two X or Y it came from. The exponent f(e) in N has term 2*e in both cases, but the exponents from Y are reduced by 1. This can be expressed in the following form. Going from X,Y to N doesn't need to factorize X, only Y. X^2 * Y^2 N = -------------------- distinct primes in Y N=1,2,3,8,5,6,etc in the column X=1 is integers with odd powers of prime factors. These are the fractions 1/Y so the exponents of the primes are all negative and thus all exponents in N are odd. XN=1,4,9,16,etc in row Y=1 are the perfect squares. That row is the integers X/1 so the exponents are all positive and thus in N become 2*e, giving simply N=X^2. =head2 Odd/Even Option C "odd/even"> changes the f() mapping to numerator exponents as odd numbers and denominator exponents as even. f(e) = 2*e-1 if e > 0 = -2*e if e <= 0 The effect is simply to transpose XE-EY. "odd/even" is the form given by Kevin McCrimmon and Gerald Freilich. The default "even/odd" is the form given by Yoram Sagher. =head2 Negabinary XOption C "negabinary"> changes the f() mapping to negabinary as suggested in =over David M. Bradley, "Counting the Positive Rationals: A Brief Survey", L =back =cut # math-image --path=FactorRationals,factor_coding=negabinary --all --output=numbers_xy --size=70x14 =pod This coding is not as compact as odd/even and tends to make bigger N values, 13 | 2197 4394 6591 140608 10985 13182 15379 281216 12 | 108 540 756 11 | 1331 2662 3993 85184 6655 7986 9317 170368 10 | 1000 3000 7000 9 | 9 18 576 45 63 1152 8 | 8192 24576 40960 57344 7 | 343 686 1029 21952 1715 2058 43904 6 | 216 1080 1512 5 | 125 250 375 8000 750 875 16000 4 | 4 12 20 28 3 | 27 54 1728 135 189 3456 2 | 8 24 40 56 1 | 1 2 3 64 5 6 7 128 Y=0 | ---------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 =head2 Reversing Binary Option C "revbinary"> changes the f() mapping to "reversing binary" where a given integer is represented as a sum of powers 2^k with alternating signs e = 2^k1 - 2^k2 + 2^k3 - ... 0 <= k1 < k2 < k3 f(e) e --- --- 0 0 1 1 2 2 3 -1 4 4 5 -3 6 -2 7 3 This representation is per Knuth volume 2 section 4.1 exercise 27. The exercise there is to show all integers can be represented this way. =cut # math-image --path=FactorRationals,factor_coding=revbinary --all --output=numbers --size=15x10 =pod 9 | 729 1458 2916 3645 5103 93312 7290 8 | 32 96 160 224 288 7 | 343 686 1029 1372 1715 2058 43904 3087 3430 6 | 216 1080 1512 5 | 125 250 375 500 750 875 16000 1125 4 | 64 192 320 448 576 3 | 27 54 108 135 189 3456 270 2 | 8 24 40 56 72 1 | 1 2 3 4 5 6 7 128 9 10 Y=0 | --------------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 The X axis begins with the integers 1 to 7 because f(1)=1 and f(2)=2 so N=X until X has a prime p^3 or higher power. The first such is X=8=2^3 which is f(7)=3 so N=2^7=128. =head1 FUNCTIONS See L for behaviour common to all path classes. =over =item C<$path = Math::PlanePath::FactorRationals-Enew ()> =item C<$path = Math::PlanePath::FactorRationals-Enew (factor_coding =E $str)> Create and return a new path object. C can be "even/odd" (the default) "odd/even" "negabinary" "revbinary" =item C<($x,$y) = $path-En_to_xy ($n)> Return X,Y coordinates of point C<$n> on the path. If there's no point C<$n> then the return is an empty list. This depends on factorizing C<$n> and in the current code there's a hard limit on the amount of factorizing attempted. If C<$n> is too big then the return is an empty list. =item C<$n = $path-Exy_to_n ($x,$y)> Return the N point number for coordinates C<$x,$y>. If there's nothing at C<$x,$y>, such as when they have a common factor, then return C. This depends on factorizing C<$y>, or factorizing both C<$x> and C<$y> for negabinary or revbinary. In the current code there's a hard limit on the amount of factorizing attempted. If the coordinates are too big then the return is C. =back The current factorizing limits handle anything up to 2^32, and above that numbers comprised of small factors. But big numbers with big factors are not handled. Is this a good idea? For large inputs there's no merit in disappearing into a nearly-infinite loop. Perhaps the limits could be configurable and/or some advanced factoring modules attempted for a while if/when available. =head1 OEIS This enumeration of the rationals is in Sloane's Online Encyclopedia of Integer Sequences in the following forms =over L (etc) =back A071974 X coordinate, numerators A071975 Y coordinate, denominators A019554 X*Y product A102631 N in column X=1, n^2/squarefreekernel(n) A072345 X and Y at N=2^k, being alternately 1 and 2^k A011262 permutation N at transpose Y/X (exponents mangle odd<->even) A060837 permutation DiagonalRationals -> FactorRationals A071970 permutation RationalsTree CW -> FactorRationals The last A071970 is rationals taken in order of the Stern diatomic sequence stern[i]/stern[i+1] which is the Calkin-Wilf tree rows (L). The negabinary representation is A053985 index -> signed A005351 signed positives -> index A039724 signed positives -> index, in binary A005352 signed negatives -> index The reversing binary representation is A065620 index -> signed A065621 signed positives -> index A048724 signed negatives -> index =head1 SEE ALSO L, L, L, L =head2 Other Ways to Do It Niven gives another prime factor based construction but encoding N by runs of 1-bits, =over Ivan Niven, "Note on a paper by L. S. Johnston", American Math. Monthly, volume 55, number 6, June-July 1948, page 358. L =back N is written in binary each 0-bit is considered a separator. The number of 1-bits between each N = 11 0 0 111 0 11 binary | | | 2 0 3 2 f(e) = run lengths of 1-bits -1 0 +2 -1 e exponent by "odd/even" style X/Y = 2^(-1) * 3^(+2) * 5^0 * 7^(-1) Kevin McCrimmon's note begins with a further possible encoding for N where the prime powers from numerator are spread out to primes p[2i+1] and with 0 powers sending a p[2i] power to the denominator. In this form the primes from X and Y spread out to different primes rather than different exponents. =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/TriangleSpiral.pm0000644000175000017500000002657713734026654020532 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::TriangleSpiral; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; *_sqrtint = \&Math::PlanePath::_sqrtint; # uncomment this to run the ### lines #use Smart::Comments; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_even; use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1() ]; sub x_negative_at_n { my ($self) = @_; return $self->n_start + 4; } sub y_negative_at_n { my ($self) = @_; return $self->n_start + 6; } sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + 3; } use constant dx_minimum => -1; use constant dx_maximum => 2; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant 1.02 _UNDOCUMENTED__dxdy_list => (2,0, # E -1,1, # NW -1,-1); # SW use constant absdx_minimum => 1; use constant dsumxy_minimum => -2; # SW diagonal use constant dsumxy_maximum => 2; # dX=+2 horiz use constant ddiffxy_minimum => -2; # NW diagonal use constant ddiffxy_maximum => 2; # dX=+2 horiz use constant dir_maximum_dxdy => (-1,-1); # at most South-West diagonal use constant turn_any_right => 0; # only left or straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # base at bottom right corner # d = [ 1, 2, 3 ] # n = [ 2, 11, 29 ] # $d = 1/2 + sqrt(2/9 * $n + -7/36) # = 1/2 + sqrt(8/36 * $n + -7/36) # = 0.5 + sqrt(8*$n + -7)/6 # = (1 + 2*sqrt(8*$n + -7)/6) / 2 # = (1 + sqrt(8*$n + -7)/3) / 2 # = (3 + sqrt(8*$n - 7)) / 6 # # $n = (9/2*$d**2 + -9/2*$d + 2) # = (4.5*$d - 4.5)*$d + 2 # # top of pyramid # d = [ 1, 2, 3 ] # n = [ 4, 16, 37 ] # $n = (9/2*$d**2 + -3/2*$d + 1) # so remainder from there # rem = $n - (9/2*$d**2 + -3/2*$d + 1) # = $n - (4.5*$d*$d - 1.5*$d + 1) # = $n - ((4.5*$d - 1.5)*$d + 1) # # sub n_to_xy { my ($self, $n) = @_; #### TriangleSpiral n_to_xy: $n $n = $n - $self->{'n_start'}; # starting $n==0, warn if $n==undef if ($n < 0) { return; } my $d = int ((3 + _sqrtint(8*$n+1)) / 6); #### $d $n -= (9*$d - 3)*$d/2; #### remainder: $n if ($n <= 3*$d) { ### sides, remainder pos/neg from top return (-$n, 2*$d - abs($n)); } else { ### rightwards from bottom left ### remainder: $n - 3*$d # corner is x=-3*$d # so -3*$d + 2*($n - 3*$d) # = -3*$d + 2*$n - 6*$d # = -9*$d + 2*$n # = 2*$n - 9*$d return (2*$n - 9*$d, -$d); } } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### xy_to_n(): "$x,$y" if (($x ^ $y) & 1) { return undef; # nothing on odd points } if ($y < 0 && 3*$y <= $x && $x <= -3*$y) { ### bottom horizontal # negative y, at vertical x=0 # [ -1, -2, -3, -4, -5, -6 ] # [ 8.5, 25, 50.5, 85, 128.5, 181 ] # $n = (9/2*$y**2 + -3*$y + 1) # = (4.5*$y*$y + -3*$y + 1) # = ((4.5*$y -3)*$y + 1) # from which $x/2 # return ((9*$y - 6)*$y/2) + $x/2 + $self->{'n_start'}; } else { ### sides diagonal # # positive y, x=0 centres # [ 2, 4, 6, 8 ] # [ 4, 16, 37, 67 ] # n = (9/8*$d**2 + -3/4*$d + 1) # = (9/8*$d + -3/4)*$d + 1 # = (9*$d + - 6)*$d/8 + 1 # from which -$x offset # my $d = abs($x) + $y; return ((9*$d - 6)*$d/8) - $x + $self->{'n_start'}; } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); my $d = 0; foreach my $x ($x1, $x2) { foreach my $y ($y1, $y2) { $d = max ($d, 1 + ($y < 0 && 3*$y <= $x && $x <= -3*$y ? -$y # bottom horizontal : int ((abs($x) + $y) / 2))); # sides } } return ($self->{'n_start'}, (9*$d - 9)*$d/2 + $self->{'n_start'}); } 1; __END__ =for stopwords Ryde Math-PlanePath hendecagonal 11-gonal (s+2)-gonal OEIS hendecagonals =head1 NAME Math::PlanePath::TriangleSpiral -- integer points drawn around an equilateral triangle =head1 SYNOPSIS use Math::PlanePath::TriangleSpiral; my $path = Math::PlanePath::TriangleSpiral->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes a spiral shaped as an equilateral triangle (each side the same length). 16 4 / \ 17 15 3 / \ 18 4 14 ... 2 / / \ \ \ 19 5 3 13 32 1 / / \ \ \ 20 6 1-----2 12 31 <- Y=0 / / \ \ 21 7-----8-----9----10----11 30 -1 / \ 22----23----24----25----26----27----28----29 -2 ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 Cells are spread horizontally to fit on a square grid as per L. The horizontal gaps are 2, so for instance n=1 is at x=0,y=0 then n=2 is at x=2,y=0. The diagonals are 1 across and 1 up or down, so n=3 is at x=1,y=1. Each alternate row is offset from the one above or below. This grid is the same as the C and the path is like that spiral except instead of a flat top and SE,SW sides it extends to triangular peaks. The result is a longer loop and each successive loop is step=9 longer than the previous (whereas the C is step=6 more). XThe triangular numbers 1, 3, 6, 10, 15, 21, 28, 36 etc, k*(k+1)/2, fall one before the successive corners of the triangle, so when plotted make three lines going vertically and angled down left and right. The 11-gonal "hendecagonal" numbers 11, 30, 58, etc, k*(9k-7)/2 fall on a straight line horizontally to the right. (As per the general rule that a step "s" lines up the (s+2)-gonal numbers.) =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start with the same shape etc. For example to start at 0, =cut # math-image --path=TriangleSpiral,n_start=0 --expression='i<=31?i:0' --output=numbers_dash =pod n_start => 0 15 / \ 16 14 / \ 17 3 13 / / \ \ 18 4 2 12 ... / / \ \ \ 19 5 0-----1 11 30 / / \ \ 20 6-----7-----8-----9----10 29 / \ 21----22----23----24----25----26----27----28 With this adjustment the X axis N=0,1,11,30,etc is the hendecagonal numbers (9k-7)*k/2. And N=0,8,25,etc diagonally South-East is the hendecagonals of the second kind which is (9k-7)*k/2 for k negative. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::TriangleSpiral-Enew ()> =item C<$path = Math::PlanePath::TriangleSpiral-Enew (n_start =E $n)> Create and return a new triangle spiral object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n < 1> the return is an empty list, it being considered the path starts at 1. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each C<$n> in the path as a square of side 1. Only every second square in the plane has an N. If C<$x,$y> is a position without an N then the return is C. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back n_start=1 (default) A010054 turn 1=left,0=straight, extra initial 1 A117625 N on X axis A081272 N on Y axis A006137 N on X negative axis A064226 N on X=Y leading diagonal, but without initial value=1 A064225 N on X=Y negative South-West diagonal A081267 N on X=-Y negative South-East diagonal A081589 N on ENE slope dX=3,dY=1 A038764 N on WSW slope dX=-3,dY=-1 A060544 N on ESE slope dX=3,dY=-1 diagonal A063177 total sum previous row or diagonal n_start=0 A051682 N on X axis (11-gonal numbers) A062741 N on Y axis A062708 N on X=Y leading diagonal A081268 N on X=Y+2 diagonal (right of leading diagonal) A062728 N on South-East diagonal (11-gonal second kind) A062725 N on South-West diagonal A081275 N on ENE slope from X=2,Y=0 then dX=+3,dY=+1 A081266 N on WSW slope dX=-3,dY=-1 A081271 N on X=2 vertical n_start=-1 A023531 turn 1=left,0=straight, being 1 at N=k*(k+3)/2 A023532 turn 1=straight,0=left A023531 is C to match its "offset=0" for the first turn, being the second point of the path. A010054 which is 1 at triangular numbers k*(k+1)/2 is the same except for an extra initial 1. =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/HilbertSides.pm0000644000175000017500000003253313734026670020156 0ustar gggg# Copyright 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::HilbertSides; use 5.004; use strict; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; use Math::PlanePath::HilbertCurve; my $hilbert_path = Math::PlanePath::HilbertCurve->new; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; #------------------------------------------------------------------------------ # ---3 # | # state=0 3--2 plain 2 # | | # 0--1 0--1 # # state=4 1--2 transpose 1--2--3 # | | | | # 0 3 0 | # # state=8 1--0 rot180 1--0 # | | # 2--3 2 # | # 3--- # # state=12 3 0 rot180 + transpose | 0 # | | | | # 2--1 3--2--1 # # generated by tools/hilbert-curve-table.pl my @next_state = (4,0,0,12, 0,4,4,8, 12,8,8,4, 8,12,12,0); my @digit_to_x = (0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0); my @digit_to_y = (0,0,1,1, 0,1,1,0, 1,1,0,0, 1,0,0,1); my @state_to_frac = (0,1, undef,undef, 1,0, undef,undef, 0,-1, undef,undef, -1,0, undef,undef); sub n_to_xy { my ($self, $n) = @_; ### HilbertSides n_to_xy(): $n ### hex: sprintf "%#X", $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; # fraction part my @ndigits = digit_split_lowtohigh($int,4); my $state = ($#ndigits & 1 ? 4 : 0); my (@xbits, @ybits); foreach my $i (reverse 0 .. $#ndigits) { # digits high to low $state += $ndigits[$i]; $xbits[$i] = $digit_to_x[$state]; $ybits[$i] = $digit_to_y[$state]; $state = $next_state[$state]; } my $zero = ($int * 0); # inherit bigint 0 # print "state $state state $state\n"; my $add = ($state >= 8 ? 1 : 0); return ($n * $state_to_frac[$state] # frac + digit_join_lowtohigh (\@xbits, 2, $zero) + $add, $n * $state_to_frac[$state+1] # frac + digit_join_lowtohigh (\@ybits, 2, $zero) + $add); } sub xy_to_n { return scalar((shift->xy_to_n_list(@_))[0]); } sub xy_to_n_list { my ($self, $x, $y) = @_; ### HilbertSides xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); my @n_list; foreach my $d (0,1) { ### try: ($x-$d).','.($y-$d) my $n = $hilbert_path->xy_to_n($x-$d,$y-$d); ### $n if (defined $n) { if (my ($gx,$gy) = $self->n_to_xy($n)) { ### is at: "$gx,$gy" if ($x == $gx && $y == $gy) { ### push: $n push @n_list, $n; } } } } # if (@n_list == 2 && $n_list[0] > $n_list[1]) { # @n_list = reverse @n_list; # } @n_list = sort {$a <=> $b} @n_list; ### @n_list return @n_list; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### HilbertSides rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); my ($pow, $exp) = round_down_pow (max(abs($x1),abs($y1), abs($x2),abs($y2)), 2); return (0, 4*$pow*$pow); } 1; __END__ =for stopwords eg Ryde ie HilbertSides Math-PlanePath Dekking unrotated Ns HilbertCurve dX OEIS =head1 NAME Math::PlanePath::HilbertSides -- sides of Hilbert curve squares =head1 SYNOPSIS use Math::PlanePath::HilbertSides; my $path = Math::PlanePath::HilbertSides->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path is segments along the sides of the Hilbert curve squares as per =over F. M. Dekking, "Recurrent Sets", Advances in Mathematics, volume 44, 1982, pages 79-104, section 4.8 "Hilbert Curve" =back The base pattern is N=0 to N=4. That pattern repeats transposed as points N=0,4,8,12,16, etc. 9 | ... | | 8 | 64----63 49----48 44----43 | | | | | | 7 | 62 50 47----46----45 42 | | | | 6 | 60----61 56 51----52 40---39,41 | | | | | 5 | 59----58---57,55--54---53,33--34----35 38 | | | | 4 | 32 36,28--37,27 | | | | 3 | 5-----6----7,9---10---11,31--30----29 26 | | | | | 2 | 4-----3 8 13----12 24---23,25 | | | | 1 | 2 14 17----18----19 22 | | | | | | Y=0 | 0-----1 15----16 20----21 +------------------------------------------------- X=0 1 2 3 4 5 6 7 If each point of the C path is taken to be a unit square the effect here is to go along the sides of those squares. The square for a segment is on the left or right, -------3. . v | |> | 2 . | |> ^ | 0-------1 . Some points are visited twice. The first is at X=2,Y=3 which is N=7 and N=9 where the two segments N=7to8 and N=8to9 overlap. These are consecutive segments, and non-consecutive segments can overlap too, as for example N=27to28 and N=36to37. Double-visited points occur also as corners touching, for example at X=4,Y=3 the two N=11 N=31 touch without overlapping segments. The HilbertCurve squares fall within 2^k x 2^k blocks and so likewise the segments here. The right side 1 to 2 and 2 to 3 don't touch the 2^k side. This is so of the base figure N=0 to N=4 which doesn't touch X=2 and higher levels are unrotated replications so for example in the N=0 to N=64 shown above X=8 is not touched. This creates rectangular columns up from the X axis. Likewise rectangular rows across from the Y axis, and both columns and rows inside. The sides which are N=0 to N=1 and N=3 to N=4 of the base pattern variously touch in higher levels giving interesting patterns of squares, shapes, notches, etc. =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::HilbertSides-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. If there's nothing at C<$x,$y> then return C. The curve visits an C<$x,$y> twice for various points. The smaller of the two N values is returned. =item C<@n_list = $path-Exy_to_n_list ($x,$y)> Return a list of N point numbers for coordinates C<$x,$y>. Points may have up to two Ns for a given C<$x,$y>. =back =head1 FORMULAS =head2 Coordinates Difference X-Y is the same here as in the C. The base pattern here has N=3 at 1,2 whereas the HilbertCurve is 0,1 so X-Y is the same. The two then have the same pattern of rotate 180 and/or transpose in subsequent replications. 3 | HilbertSides 2 3----2 HilbertCurve | | 0----1 0----1 =head2 Abs dX,dY abs(dY) is 1 for a vertical segment and 0 for a horizontal segment. For the curve here it is abs(dY) = count 1-bits of N, mod 2 = Thue-Morse binary parity abs(dX) = 1 - abs(dY) = opposite This is so for the base pattern N=0,1,2, and also at N=3 turning towards N=4. Replication parts 1 and 2 are transposes where there is a single extra 1-bit in N and dX,dY are swapped. Replication part 3 is a 180 degree rotation where there are two extra 1-bits in N and dX,dY are negated so abs(dX),abs(dY) unchanged. =head2 Turn The path can turn left or right or go straight forward or 180 degree reverse. Straight,reverse vs left,right is given by N num trailing 0 bits turn --------------------- ----------------------- odd straight or 180 reverse (A096268) even left or right (A035263) The path goes straight ahead at 2 and reverses 180 at 8 and all subsequent 2*4^k. =head2 Segments on Axes The number of line segments on the X and Y axes 0 to 2^k, which is N=0 to 4^k, is Xsegs[k] = 1/3*2^k + 1/2 + 1/6*(-1)^k = 1, 1, 2, 3, 6, 11, 22, 43, 86 (A005578) = Ysegs[k] + 1 Ysegs[k] = 1/3*2^k - 1/2 + 1/6*(-1)^k = 0, 0, 1, 2, 5, 10, 21, 42, 85,... (A000975) = binary 101010... k-1 many bits alternating =cut # GP-DEFINE Xsegs(k) = 1/3*2^k + 1/2 + 1/6*(-1)^k; # GP-DEFINE Ysegs(k) = 1/3*2^k - 1/2 + 1/6*(-1)^k; # GP-Test vector(9,k,k--; Xsegs(k)) == [1,1,2,3,6,11,22,43,86] # GP-Test vector(9,k,k--; Ysegs(k)) == [0,0,1,2,5,10,21,42,85] # GP-DEFINE from_binary_vector(v) = subst(Polrev(v),'x,2); # GP-Test from_binary_vector([1,1,0,1]) == 11 # GP-DEFINE Ysegs_by_binary(k) = \ # GP-DEFINE from_binary_vector(vector(max(0,k-1),i, (k-i)%2)); # GP-Test vector(100,k,k--; Ysegs_by_binary(k)) == \ # GP-Test vector(100,k,k--; Ysegs(k)) =pod These counts can be calculated from the curve sub-parts k odd k even +---+ . . . . R |>T T T . . . +---+---+ |>T |> R<| o---+ . o . + The block at the origin is X and Y segments of the k-1 level. For k odd, the X axis then has a transposed block which means the Y segments of k-1. The Y axis has a 180 degree rotated block R. The curve is symmetric in mirror image across its start to end so the count of segments it puts on the Y axis is the same as Y of level k-1. Xsegs[k] = Xsegs[k-1] + Ysegs[k-1] for k odd Ysegs[k] = 2*Ysegs[k-1] =for GP-Test vector(100,k,k=2*k-1; Xsegs(k)) == vector(100,k,k=2*k-1; Xsegs(k-1) + Ysegs(k-1)) /* k>=1 odd */ =for GP-Test vector(100,k,k=2*k-1; Ysegs(k)) == vector(100,k,k=2*k-1; 2*Ysegs(k-1)) /* k>=1 odd */ Then similarly for k even, but the other way around the 2*Y. Xsegs[k] = 2*Xsegs[k-1] for k even Ysegs[k] = Xsegs[k-1] + Ysegs[k-1] =for GP-Test vector(100,k,k=2*k; Xsegs(k)) == vector(100,k,k=2*k; 2*Xsegs(k-1)) /* k>=2 even */ =for GP-Test vector(100,k,k=2*k; Ysegs(k)) == vector(100,k,k=2*k; Xsegs(k-1) + Ysegs(k-1)) /* k>=2 even */ =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A059285 X-Y A010059 abs(dX), 1 - Thue-Morse binary parity A010060 abs(dY), Thue-Morse binary parity A096268 turn 1=straight or reverse, 0=left or right A035263 turn 0=straight or reverse, 1=left or right A062880 N values on diagonal X=Y (digits 0,2 in base 4) A005578 count segments on X axis, level k A000975 count segments on Y axis, level k =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/DiagonalsAlternating.pm0000644000175000017500000002214613774317032021665 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::DiagonalsAlternating; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; *_sqrtint = \&Math::PlanePath::_sqrtint; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1() ]; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant 1.02 _UNDOCUMENTED__dxdy_list => (1,0, # E at N=3 in default n_start=1 0,1, # N at N=1 -1,1, # NW at N=4 1,-1, # SE at N=2 ); sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + 3; } use constant dsumxy_minimum => 0; # advancing diagonals use constant dsumxy_maximum => 1; use constant ddiffxy_minimum => -2; # NW diagonal use constant ddiffxy_maximum => 2; # SE diagonal use constant dir_maximum_dxdy => (1,-1); # South-East #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } $self->{'x_start'} ||= 0; $self->{'y_start'} ||= 0; return $self; } # \ # 15 26 # |\ \ # 14 16 25 # \ \ # . . 17 24 # \ \ # . . . 18 23 # \ \ # . . . . 19 22 # \ \ # . . . . . 20-21 # Basis N=0 # d = [ 0, 1, 2, 3 ] # n = [ 0, 5, 14, 27 ] # N = (2 d^2 + 3 d) # = (2*$d**2 + 3*$d) # = ((2*$d + 3)*$d) # d = -3/4 + sqrt(1/2 * $n + 9/16) # = (sqrt(8*$n + 9) - 3) / 4 # # Midpoint # d = [ 0, 1, 2, 3 ] # n = [ 2, 9, 20, 35 ] # N = (2 d^2 + 5 d + 2) # = (2*$d**2 + 5*$d + 2) # = ((2*$d + 5)*$d + 2) sub n_to_xy { my ($self, $n) = @_; ### DiagonalsAlternating n_to_xy(): "$n ".(ref $n || '') # adjust to N=0 at origin X=0,Y=0 $n = $n - $self->{'n_start'}; if ($n < 0) { return; } my $d = int( (_sqrtint(8*int($n)+9) - 3)/4 ); $n -= (2*$d + 5)*$d + 3; ### remainder: $n my ($x,$y); if ($n >= -1) { if ($n < 0) { ### horizontal X axis ... $x = $n + 2*$d+2; $y = 0; } else { ### diagonal upwards ... $x = -$n + 2*$d+2; $y = $n; } } else { $n += 2*$d+2; # -1 <= $n < ... ### added n: $n ### assert: ! ($n < -1) if ($n < 0) { ### vertical Y axis ... $x = 0; $y = $n + 2*$d+1; } else { ### diagonal downwards ... $x = $n; $y = -$n + 2*$d+1; } } return ($x + $self->{'x_start'}, $y + $self->{'y_start'}); } sub xy_to_n { my ($self, $x, $y) = @_; ### xy_to_n(): $x, $y $x = $x - $self->{'x_start'}; # "-" operator to provoke warning if x==undef $y = $y - $self->{'y_start'}; $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; # outside first quadrant } my $d = $x + $y; # odd, downwards ... # d= [ 1,3,5 ] # N= [ 2,7,16 ] # N = ((1/2*$d + 1/2)*$d + 1) # # even, upwards # d= [ 0,2,4 ] # N= [ 1,4,11 ] # N = ((1/2*$d + 1/2)*$d + 1) # = ($d + 1)*$d/2 + 1 my $n = ($d + 1)*$d/2 + $self->{'n_start'}; if ($d % 2) { return $n + $x; } else { return $n + $y; } } use Math::PlanePath::Diagonals; *rect_to_n_range = \&Math::PlanePath::Diagonals::rect_to_n_range; 1; __END__ =for stopwords PlanePath Ryde Math-PlanePath OEIS =head1 NAME Math::PlanePath::DiagonalsAlternating -- points in diagonal stripes of alternating directions =head1 SYNOPSIS use Math::PlanePath::DiagonalsAlternating; my $path = Math::PlanePath::DiagonalsAlternating->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path follows successive diagonals going from the Y axis down to the X axis and then back up again, =cut # math-image --path=DiagonalsAlternating --output=numbers_dash --size=35x14 =pod 5 | 16 | |\ 4 | 15 17 | \ \ 3 | 7 14 18 | |\ \ \ 2 | 6 8 13 19 ... | \ \ \ \ \ 1 | 2 5 9 12 20 23 | |\ \ \ \ \ \ Y=0 | 1 3-- 4 10--11 21--22 +---------------------------- X=0 1 2 3 4 5 6 XThe triangular numbers 1,3,6,10,etc k*(k+1)/2 are the start of each run up or down alternately on the X axis and Y axis. XN=1,6,15,28,etc on the Y axis (Y even) are the hexagonal numbers j*(2j-1). N=3,10,21,36,etc on the X axis (X odd) are the hexagonal numbers of the second kind j*(2j+1). =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same pattern. For example to start at 0, =cut # math-image --path=DiagonalsAlternating,n_start=0 --expression='i<=14?i:0' --output=numbers --size=35x5 =pod n_start => 0 4 | 14 3 | 6 13 2 | 5 7 12 1 | 1 4 8 11 Y=0 | 0 2 3 9 10 +----------------- X=0 1 2 3 4 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::DiagonalsAlternating-Enew ()> =item C<$path = Math::PlanePath::DiagonalsAlternating-Enew (n_start =E $n)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n E 1> the return is an empty list, it being considered the path begins at 1. =back =head1 FORMULAS =head2 Rectangle to N Range Within each row increasing X is increasing N, and in each column increasing Y is increasing N. So in a rectangle the lower left corner is the minimum N and the upper right is the maximum N. | N max | ----------+ | | ^ | | | | | | | ----> | | +---------- | N min +------------------- =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back n_start=1 A131179 N on X axis (extra initial 0) A128918 N on Y axis (extra initial 1) A001844 N on X=Y diagonal A038722 permutation N at transpose Y,X n_start=0 A319572 X coordinate A319573 Y coordinate A319571 X,Y coordinates together A003056 X+Y A004247 X*Y A049581 abs(X-Y) A048147 X^2+Y^2 A004198 X bit-and Y A003986 X bit-or Y A003987 X bit-xor Y A004197 min(X,Y) A003984 max(X,Y) A101080 HammingDist(X,Y) A023531 dSum = dX+dY, being 1 at N=triangular+1 (and 0) A046092 N on X=Y diagonal A061579 permutation N at transpose Y,X A056011 permutation N at points by Diagonals,direction=up order A056023 permutation N at points by Diagonals,direction=down runs alternately up and down, both are self-inverse The coordinates such as A003056 X+Y are the same here as in the Diagonals path. C transposes X,Y -E Y,X in every second diagonal but forms such as X+Y are unchanged by swapping to Y+X. =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/Flowsnake.pm0000644000175000017500000007564113734026671017536 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=Flowsnake --lines --scale=10 # math-image --path=Flowsnake --all --output=numbers_dash # math-image --path=Flowsnake,arms=3 --all --output=numbers_dash # # Martin Gardner, "In which `Monster' Curves Force Redefinition of the Word # `Curve'", Scientific American 235, December 1976, pages 124-133. # # http://80386.nl/pub/gosper-level21.png # # http://www.mathcurve.com/fractals/gosper/gosper.shtml # # plain hexagonal tiling http://tilingsearch.org/HTML/data136/F666.html # http://complex-systems.com/pdf/24-4-1.pdf # http://complex-systems.com/issues/24-4.html # Jeffrey Ventrella # root-7 family # "inner-flip" which is initial state reversal package Math::PlanePath::Flowsnake; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; # inherit: new(), rect_to_n_range(), arms_count(), n_start(), # parameter_info_array(), xy_is_visited() use Math::PlanePath::FlowsnakeCentres 55; # v.55 inheritance switch-around @ISA = ('Math::PlanePath::FlowsnakeCentres'); use Math::PlanePath; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'round_up_pow'; # uncomment this to run the ### lines # use Smart::Comments; # (i,j)*(2+w) = (2i-j,2j+i+j) = (2i-j,3j+i) # (x,y)*(2+w) = 2x + (x-3y)/2, 2y + (x+y)/2 # = (4x + x-3y)/2, (4y + x+y)/2 # = (5x-3y)/2, (x+5y)/2 { my @x_negative_at_n = (undef, 23, 1, 1); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } { my @y_negative_at_n = (undef, 8598, 7, 2); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } { my @_UNDOCUMENTED__dxdy_list_at_n = (undef, 11, 6, 9); sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $_UNDOCUMENTED__dxdy_list_at_n[$self->{'arms'}]; } } # Table generated by tools/flowsnake-table.pl. # next_state length 84 my @next_state = (0, 21,49,28, 0, 0,77, 70, 7, 7,35,42,14, 7, # 0,7 14,35,63,42,14,14, 7, 0,21,21,49,56,28,21, # 14,21 28,49,77,56,28,28,21, 14,35,35,63,70,42,35, # 28,35 42,63, 7,70,42,42,35, 28,49,49,77, 0,56,49, # 42,49 56,77,21, 0,56,56,49, 42,63,63, 7,14,70,63, # 56,63 70, 7,35,14,70,70,63, 56,77,77,21,28, 0,77); # 70,77 my @digit_to_i = (0, 1, 1, 0,-1, 0, 1, 0, 1, 2, 3, 2, 1, 1, # 0,7 0, 0,-1,-1,-2,-2,-2, 0, 1, 1, 1, 0, 0,-1, # 14,21 0, -1,-2,-1,-1,-2,-3, 0, 0,-1,-2,-2,-1,-2, # 28,35 0, -1,-1, 0, 1, 0,-1, 0,-1,-2,-3,-2,-1,-1, # 42,49 0, 0, 1, 1, 2, 2, 2, 0,-1,-1,-1, 0, 0, 1, # 56,63 0, 1, 2, 1, 1, 2, 3, 0, 0, 1, 2, 2, 1,2); # 70,77 my @digit_to_j = (0, 0, 1, 1, 2, 2, 2, 0,-1,-1,-1, 0, 0, 1, # 0,7 0, 1, 2, 1, 1, 2, 3, 0, 0, 1, 2, 2, 1, 2, # 14,21 0, 1, 1, 0,-1, 0, 1, 0, 1, 2, 3, 2, 1, 1, # 28,35 0, 0,-1,-1,-2,-2,-2, 0, 1, 1, 1, 0, 0,-1, # 42,49 0, -1,-2,-1,-1,-2,-3, 0, 0,-1,-2,-2,-1,-2, # 56,63 0, -1,-1, 0, 1, 0,-1, 0,-1,-2,-3,-2,-1,-1); # 70,77 # state 0 to 11 my @dir6_to_di = (1, 0,-1, -1, 0, 1); my @dir6_to_dj = (0, 1, 1, 0,-1,-1); sub n_to_xy { my ($self, $n) = @_; ### Flowsnake n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; # fraction part ### $int ### frac: $n my $state; { my $arm = _divrem_mutate ($int, $self->{'arms'}); $state = 28 * $arm; # initial rotation # adjust so that for arms=2 point N=1 has $int==1 # or for arms=3 then points N=1 and N=2 have $int==1 if ($arm) { $int += 1; } } ### initial state: $state my $i = my $j = $int*0; # bignum zero foreach my $digit (reverse digit_split_lowtohigh($int,7)) { # high to low ### at: "state=$state digit=$digit i=$i,j=$j di=".$digit_to_i[$state+$digit]." dj=".$digit_to_j[$state+$digit] # (i,j) *= (2+w), being (i,j) = 2*(i,j)+rot60(i,j) # then add low digit pos # $state += $digit; ($i, $j) = (2*$i - $j + $digit_to_i[$state], 3*$j + $i + $digit_to_j[$state]); $state = $next_state[$state]; } ### integer: "i=$i, j=$j" # fraction in final $state direction if ($n) { ### apply: "frac=$n state=$state" $state = int($state/14); # divide to direction 0 to 5 $i = $n * $dir6_to_di[$state] + $i; $j = $n * $dir6_to_dj[$state] + $j; } ### ret: "$i, $j x=".(2*$i+$j)." y=$j" return (2*$i+$j, $j); } # Table generated by tools/flowsnake-table.pl. my @digit_to_next_di = (0, -1,-1, 1, 1, 1,undef, 1, 1,-1,-1, 0, 1,undef, # 0,7 -1, 0,-1, 0, 0, 1,undef, 0, 0,-1, 0,-1, 0,undef, # 14,21 -1, 1, 0,-1,-1, 0,undef, -1,-1, 0, 1,-1,-1,undef, # 28,35 0, 1, 1,-1,-1,-1,undef, -1,-1, 1, 1, 0,-1,undef, # 42,49 1, 0, 1, 0, 0,-1,undef, 0, 0, 1, 0, 1, 0,undef, # 56,63 1, -1, 0, 1, 1, 0,undef, 1, 1, 0,-1, 1, 1,undef, # 70,77 1, -1,-1, 1, 1, 0,undef, 1, 1, 0,-1, 0, 1,undef, # 84,91 0, -1,-1, 0, 0, 1,undef, 1, 1,-1, 0,-1, 1,undef, # 98,105 -1, 0, 0,-1,-1, 1,undef, 0, 0,-1, 1,-1, 0,undef, # 112,119 -1, 1, 1,-1,-1, 0,undef, -1,-1, 0, 1, 0,-1,undef, # 126,133 0, 1, 1, 0, 0,-1,undef, -1,-1, 1, 0, 1,-1,undef, # 140,147 1, 0, 0, 1, 1,-1,undef, 0, 0, 1,-1, 1,0); my @digit_to_next_dj = (1, 0, 1, 0, 0,-1,undef, 0, 0, 1, 0, 1, 0,undef, # 0,7 1, -1, 0, 1, 1, 0,undef, 1, 1, 0,-1, 1, 1,undef, # 14,21 0, -1,-1, 1, 1, 1,undef, 1, 1,-1,-1, 0, 1,undef, # 28,35 -1, 0,-1, 0, 0, 1,undef, 0, 0,-1, 0,-1, 0,undef, # 42,49 -1, 1, 0,-1,-1, 0,undef, -1,-1, 0, 1,-1,-1,undef, # 56,63 0, 1, 1,-1,-1,-1,undef, -1,-1, 1, 1, 0,-1,undef, # 70,77 0, 1, 1, 0, 0,-1,undef, -1,-1, 1, 0, 1,-1,undef, # 84,91 1, 0, 0, 1, 1,-1,undef, 0, 0, 1,-1, 1, 0,undef, # 98,105 1, -1,-1, 1, 1, 0,undef, 1, 1, 0,-1, 0, 1,undef, # 112,119 0, -1,-1, 0, 0, 1,undef, 1, 1,-1, 0,-1, 1,undef, # 126,133 -1, 0, 0,-1,-1, 1,undef, 0, 0,-1, 1,-1, 0,undef, # 140,147 -1, 1, 1,-1,-1, 0,undef, -1,-1, 0, 1, 0,-1); sub n_to_dxdy { my ($self, $n) = @_; ### Flowsnake n_to_dxdy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; # fraction part ### $int ### frac: $n my $state; { my $arm = _divrem_mutate ($int, $self->{'arms'}); $state = 28 * $arm; # initial rotation # adjust so that for arms=2 point N=1 has $int==1 # or for arms=3 then points N=1 and N=2 have $int==1 if ($arm) { $int += 1; } } ### initial state: $state my $turn_state = $state; my $turn_notlow = 0; foreach my $digit (reverse digit_split_lowtohigh($int,7)) { # high to low ### $digit $state += $digit; if ($digit == 6) { $turn_notlow = 84; # is not the least significant digit } else { $turn_state = $state; # lowest non-6 $turn_notlow = 0; # and is the least significant digit } $state = $next_state[$state]; } ### int digits state: $state ### $turn_state ### $turn_notlow $state = int($state/14); my $di = $dir6_to_di[$state]; my $dj = $dir6_to_dj[$state]; ### int direction: "di=$di, dj=$dj" # fraction in final $state direction if ($n) { $turn_state += $turn_notlow; my $next_di = $digit_to_next_di[$turn_state]; my $next_dj = $digit_to_next_dj[$turn_state]; ### $next_di ### $next_dj $di += $n*($next_di - $di); $dj += $n*($next_dj - $dj); ### with frac: "di=$di, dj=$dj" } ### ret: "dx=".(2*$di+$dj)." dy=$dj" return (2*$di+$dj, $dj); } my @attempt_dx = (0, -2, -1); my @attempt_dy = (0, 0, -1); sub xy_to_n { my ($self, $x, $y) = @_; ### Flowsnake xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); if (($x + $y) % 2) { return undef; } ### round to: "$x,$y" my ($n, $cx, $cy); foreach my $i (0, 1, 2) { if (defined ($n = $self->SUPER::xy_to_n($x + $attempt_dx[$i], $y + $attempt_dy[$i])) && (($cx,$cy) = $self->n_to_xy($n)) && $x == $cx && $y == $cy) { return $n; } } return undef; } # 0 straight # 1 +60 rev # 2 180 rev # 3 +240 # 4 straight # 5 straight # 6 -60 rev # 4---- 5---- 6 # \ \ # 3---- 2 7 # / # 0---- 1 # # turn(N) = tdir6(N)-tdir6(N-1) # N-1 changes low 0s to low 6s # N = aaad000 # N-1 = aaac666 # low 0s no change to direction # low 6s state 7 # N=14=20[7] dir[2]=3,dirrev[0]=5 total 3+5=2mod6 # N-1=13=16[7] dir[1]=1,dirrev[6]=0 total 1+0=1 diff 2-1=1 # dir[2]-dir[1]=2 # dirrev[0] since digit=2 goes to rev # N=23=32[7] # 0 1 2 3 4 5 my @turn6 = (1, 2,-1,-2, 0,-1, # forward 1, 0, 2, 1,-2,-1, # reverse # 1, 1,-1,-1, 1,-1, # 0,0,-1,0,+1,+1,0 1,-1, 1, 1,-1,-1, # 0,0,-1,-1,0,+1,0 ); my @digit_to_reverse = (-1,5,5,undef,-1,-1,5); # -1=forward,5=reverse sub _WORKING_BUT_SECRET__n_to_turn6 { my ($self, $n) = @_; unless ($n >= 1) { return undef; } if (is_infinite($n)) { return $n; } my $lowdigit = _divrem_mutate($n,7); ### $lowdigit # skip low 0 digits unless ($lowdigit) { while ($n) { last if ($lowdigit = _divrem_mutate($n,7)); # stop at non-zero } # flag that some zeros were skipped $lowdigit += 12; ### $lowdigit } # Forward/reverse reverse from lowest non-3. # Digit parts 0,4,5 always forward, 1,2,6 always reverse, # 3 is unchanged so following the digit above it. for (;;) { my $digit = _divrem_mutate($n,7); if ($digit != 3) { $lowdigit += $digit_to_reverse[$digit]; last; } } ### lookup: $lowdigit return $turn6[$lowdigit]; } #------------------------------------------------------------------------------ # levels # arms=1 arms=2 arms=3 # level 0 0..1 = 2 0..2 = 2+1=3 0..3 = 2+1+1=4 # level 1 0..7 = 8 0..14 = 8+7=15 0..21 = 8+7+7=22 # level 2 0..49 = 50 0..98 = 50+49=99 0..147 = 50+49+49=148 # 7^k 2*7^k 3*7^k # sub level_to_n_range { my ($self, $level) = @_; return (0, 7**$level * $self->{'arms'}); } sub n_to_level { my ($self, $n) = @_; ### n_to_level(): $n if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); $n += $self->{'arms'}-1; # division rounding up _divrem_mutate ($n, $self->{'arms'}); my ($pow, $exp) = round_up_pow ($n, 7); return $exp; } #------------------------------------------------------------------------------ 1; __END__ # 4-->5-->6 # ^ ^ # \ \ # 3-->2 # / # v # 0-->1 # # longest to 6 is x=4,y=2 is 4*4+3*2*2 = 28 # # 6<--- # ^ # / # 0 5<--4 # \ \ # v v # 1<--2<--3 # # longest to 3 is x=5,y=1 is 5*5+3*1*1 = 28 # # side len 1 len sqrt(7) # total sqrt(7)^k + ... + 1 # = (b^(k+1) - 1) / (b - 1) # < b^(k+1) / (b - 1) # squared 7^(k+1) / (7 - 2*sqrt(7) + 1) # = 7^k * 7/(7-2*sqrt(7)+1) # = 7^k * 2.584 # # minimum = b^k - upper(k-1) # = b^k - b^k / (b - 1) # = b^k * (1 - 1/(b-1)) # = b^k * (b-1 - 1)/(b-1) # = b^k * (b-2)/(b-1) # = b^k * 0.392 # # sqrt((x/2)^2 + (y*sqrt(3)/2)^2) # = sqrt(x^2/4 + y^2*3/4) # = sqrt(x^2 + 3*y^2)/2 # sqrt(x^2 + 3*y^2)/2 > b^k * (b-2)/(b-1) # sqrt(x^2 + 3*y^2) > b^k * 2*(b-2)/(b-1) # x^2 + 3*y^2 > 7^k * (2*(b-2)/(b-1))^2 # x^2 + 3*y^2 > 7^k * (2*(b-2)/(b-1))^2 # (x^2 + 3*y^2) / (2*(b-2)/(b-1))^2 > 7^k # 7^k < (x^2 + 3*y^2) / (2*(b-2)/(b-1))^2 # k < log7 ((x^2 + 3*y^2) / (2*(b-2)/(b-1))^2) # k < log7 ((x^2 + 3*y^2) * 1.62 # k < log((x^2 + 3*y^2) * 1.62/log(7) # k < log((x^2 + 3*y^2) * 0.8345 # *---E # / \ # *---* *---* # / \ / \ # * *---* * # \ / \ / # *---* *---* # / \ / \ # * *---* * # \ / \ / # *---* *---* # \ / # *---* # # # * * # / \ / \ # / \ / \ # * * * # | | | # | | | # * * * # / \ / \ / \ # / \ / \ / \ # * * * * # | | | | # | | | | # * * * * # \ / \ / \ / # \ / \ / \ / # * * * # | | | # | | | # * * * # \ / \ / # \ / \ / # * * # # # # # B # / \ / \ # / \ / \ # . ^ . . # | | | | # | || | # . O--> A # \ / \ / # \ / | \ / # . | . # | v | # | | # C . # \ / # \ / =for stopwords eg Ryde flowsnake Gosper ie Fukuda Shimizu Nakamura Math-PlanePath Ns zdigit tdigit OEIS =head1 NAME Math::PlanePath::Flowsnake -- self-similar path through hexagons =head1 SYNOPSIS use Math::PlanePath::Flowsnake; my $path = Math::PlanePath::Flowsnake->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis path is an integer version of the flowsnake curve by William Gosper. A single arm of the curve fills 1/3 of the plane, spiralling around anti-clockwise ever fatter and with jagged self-similar edges. =cut # math-image --path=Flowsnake --all --output=numbers_dash =pod 39----40----41 8 \ \ 32----33----34 38----37 42 7 \ \ / / 31----30 35----36 43 47----48 6 / \ \ \ 28----29 17----16----15 44 46 49--.. 5 / \ \ \ / 27 23----22 18----19 14 45 4 \ \ \ / / 26 24 21----20 13 11----10 3 \ / \ / / 25 4---- 5---- 6 12 9 2 \ \ / 3---- 2 7---- 8 1 / 0---- 1 Y=0 X=-4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10 11 The points are spread out on every second X coordinate to make little triangles with integer coordinates, per L. The base pattern is the seven points 0 to 6, 4---- 5---- 6 \ \ 3---- 2 / 0---- 1 This repeats at 7-fold increasing scale, with sub-sections rotated according to the edge direction and the 1, 2 and 6 sections in reverse. The next level can be seen at the multiple-of-7 points N=0,7,14,21,28,35,42,49. 42 ----------- --- 35 --- ----------- --- 28 49 --- --- ---- 14 --- ----------- | 21 | | | | ---- 7 ----- 0 --- Notice this is the same shape as N=0 to N=6, but rotated by atan(1/sqrt(7)) = 20.68 degrees anti-clockwise. Each level rotates further and for example after about 18 levels it goes all the way around and back to the first quadrant. The rotation doesn't fill the plane though, only 1/3 of it. The shape fattens as it curls around, but leaves a spiral gap beneath (see L below). =head2 Tiling The base pattern corresponds to a tiling by hexagons as follows, with the "***" lines being the base figure. . . / \ / \ / \ / \ . . . | | | | | | 4*****5*****6 /*\ / \ /*\ / * \ / \ / * \ . * . . * . | * | | *| | *| | *| . 3*****2 7... \ / \ /*\ / \ / \ / * \ / . . * . | | * | | |* | 0*****1 . \ / \ / \ / \ / . . In the next level the parts corresponding to 1, 2 and 6 are reversed because they have their hexagon tile to the right of the line segment, rather than to the left. =head2 Arms The optional C parameter can give up to three copies of the flowsnake, each advancing successively. For example C3> is as follows. arms => 3 51----48----45 5 \ \ ... 69----66 54----57 42 4 \ \ \ / / 28----25----22 78 72 63----60 39 33----30 3 \ \ \ / \ / / 31----34 19 75 12----15----18 36 27 2 / / \ \ / 40----37 16 4---- 1 9---- 6 21----24 1 / \ \ / 43 55----58 13 7 0---- 3 74----77---... <- Y=0 \ \ \ \ / \ 46 52 61 10 2 8----11 71----68 -1 \ / / \ / / / 49 64 70----73 5 14 62----65 -2 \ / / / / 67 76 20----17 59 53----50 -3 / / \ / / ... 23 35----38 56 47 -4 \ \ \ / 26 32 41----44 -5 \ / 29 -6 ^ -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 The N=3*k points are the plain curve, N=3*k+1 is a copy rotated by 120 degrees (1/3 around), and N=3*k+2 is a copy rotated by 240 degrees (2/3 around). The initial N=1 of the second arm and N=2 of the third correspond to N=3 of the first arm, rotated around. Essentially the flowsnake fills an ever expanding hexagon with one corner at the origin, and wiggly sides. *---* / \ *---* A * Plain curve in A. / \ / Room for two more arms in B and C, * B O---* rotated 120 and 240 degrees. \ / \ *---* C * \ / *---* The sides of these "hexagons" are not straight lines but instead wild wiggly spiralling S shapes, and the endpoints rotate around by the angle described above at each level. Opposing sides are symmetric, so they mesh perfectly and with three arms fill the plane. =head2 Fractal The flowsnake can also be thought of as successively subdividing line segments with suitably scaled copies of the 0 to 7 figure (or its reversal). The code here could be used for that by taking points N=0 to N=7^level. The Y coordinates should be multiplied by sqrt(3) to make proper equilateral triangles, then a rotation and scaling to make the endpoint come out at some desired point, such as X=1,Y=0. With such a scaling the path is confined to a finite fractal boundary. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::Flowsnake-Enew ()> =item C<$path = Math::PlanePath::Flowsnake-Enew (arms =E $a)> Create and return a new flowsnake path object. The optional C parameter gives between 1 and 3 copies of the curve successively advancing. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> In the current code the returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle, but don't rely on that yet since finding the exact range is a touch on the slow side. (The advantage of which though is that it helps avoid very big ranges from a simple over-estimate.) =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 7**$level)>, or for multiple arms return C<(0, $arms * 7**$level)>. There are 7^level + 1 points in a level, numbered starting from 0. On the second and third arms the origin is omitted (so as not to repeat that point) and so just 7^level for them, giving 7^level+1 + (arms-1)*7^level = arms*7^level + 1 many points starting from 0. =back =head1 FORMULAS =head2 N to X,Y The position of a given N can be calculated from the base-7 digits of N from high to low. At a given digit position the state maintained is direction 0 to 5, multiple of 60-degrees plain or reverse pattern It's convenient to work in the "i,j" coordinates per L. This represents a point in the triangular grid as i+j*w where w=1/2+I*sqrt(3)/2 the a complex sixth root of unity at +60 degrees. foreach base-7 digit high to low (i,j) = (2i-j, i+3j) # multiply by 2+w (i,j) += position of digit in plain or reverse, and rotated by "direction" The multiply by 2+w scales up i,j by that vector, so for instance i=1,j=0 becomes i=2,j=1. This spreads the points as per the multiple-of-7 diagram shown above, so what was at N scales up to 7*N. The digit is then added as either the plain or reversed base figure, plain reverse 4-->5-->6 ^ ^ \ \ 3-->2 * 6<---* / ^ v / 0-->1 0 5<--4 \ \ v v 1<--2<--3 The arrow shown in each part is whether the state becomes plain or reverse. For example in plain state at digit=1 the arrow in segment 1 to 2 points backwards so if digit=1 then the state changes to reverse for the next digit. The direction likewise follows the direction of each segment in the pattern. Notice the endpoint "*" is at at 2+w in both patterns. When considering the rotation it's convenient to reckon the direction by this endpoint. The combination of direction and plain/reverse is a total of 14 different states, and for each there's 7 digit values (0 to 6) for a total 84 i,j. =head2 X,Y to N The current approach uses the C code. The tiling in C and C is the same so the X,Y of a given N are no more than 1 away in the grid of the two forms. The way the two lowest shapes are arranged in fact means that if the Flowsnake N is at X,Y then the same N in C is at one of three locations X, Y same X-2, Y left (+180 degrees) X-1, Y-1 left down (+240 degrees) This is true even when the rotated "arms" multiple paths (the same number of arms in both paths). Is there an easy way to know which of the three offsets is right? The current approach is to put each through C to make an N, and put that N back through Flowsnake C to see if it's the target C<$n>. =head2 Rectangle to N Range The current code calculates an exact C by searching for the highest and lowest Ns which are in the rectangle. The curve at a given level is bounded by the Gosper island shape but the wiggly sides make it difficult to calculate, so a bounding radius sqrt(7)^level, plus a bit, is used. The portion of the curve comprising a given set of high digits of N can be excluded if the N point is more than that radius away from the rectangle. When a part of the curve is excluded it prunes a whole branch of the digits tree. When the lowest digit is reached then a check for that point being actually within the rectangle is made. The radius calculation is a bit rough, and it doesn't take into account the direction of the curve, so it's a rather large over-estimate, but it works. The same sort of search can be applied for highest and lowest N in a non-rectangular shapes, calculating a radial distance away from the shape. The distance calculation doesn't have to be exact either, it can go from something bounding the shape until the lowest digit is reached and an individual X,Y is considered as a candidate for high or low N. =head2 N to Turn The turn made by the curve at a point NE=1 can be calculated from the lowest non-0 digit and the plain/reverse state per the lowest non-3 above there. N digits in base 7 strip low 0-digits, zcount many of them zdigit = take next digit (lowest non-0) strip 3-digits tdigit = take next digit (0 if no digits left) plain if tdigit=0,4,5, reverse if tdigit=1,2,6 zcount ---+--------+--------+--------+--------+ high | tdigit | 3s | zdigit | 0s | low ---+--------+--------+--------+--------+ if zcount=0 if zcount>=1 ie. no low 0s ie. some low 0s zdigit plain reverse plain reverse ------ ----- ------- ----- ------- 1 1 1 1 1 2 2 0 1 -1 turn left 3 -1 2 -1 1 multiple of 4 -2 1 -1 1 60 degrees 5 0 -2 1 -1 6 -1 -1 -1 -1 For example N=9079 is base-7 "35320" so a single low 0 for zcount=1 and strip it to "3532". Take zdigit=2 leaving "353". Skip low 3s leaving "35". Take tdigit=5 which is "plain". So table "plain" with zcount>=1 is the third column and there zdigit=2 is turn=+1. The turns in the zcount=0 "no low 0s" columns follow the turns of the base pattern shown above. For example zdigit=1 is as per N=1 turning 120 degrees left, so +2. For the reverse pattern the turns are negated and the zdigit value reversed, so the "reverse" column read 6 to 1 is the same as the plain column negated and read 1 to 6. Low 0s are stripped because the turn at a point such as N=7 ("10" in base-7) is determined by the pattern above it, the self-similar multiple-of-7s shape. But when there's low 0s in the way there's an adjustment to apply because the last segment of the base pattern is not in the same direction as the first, but instead at -60 degrees. Likewise the first segment of the reverse pattern. At some zdigit positions those two cancel out, such as at zdigit=1 where a plain and reverse meet, but others it's not so and hence separate table columns for with or without low 0s. The plain or reverse pattern is determined by the lowest non-3 digit. This works because the digit=0, digit=4, and digit=5 segments of the base pattern have their sub-parts "plain" in all cases, both the plain and reverse forms. Conversely digit=1, digit=2 and digit=6 segments are "reverse" in all cases, both plain and reverse forms. But the digit=3 part is plain in plain and reverse in reverse, so it inherits the orientation of the digit above and it's therefore necessary to skip across any 3s. When taking digits, N is treated as having infinite 0-digits at the high end. This only affects the tdigit plain/reverse step. If N has a single non-zero such as "5000" then it's taken as zdigit=5 and above that for the plain/reverse a tdigit=0 is then assumed. The first turn is at N=1 so there's always at least one non-0 for the zdigit. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A334485 (X-Y)/2 diagonal coordinate A334486 Y coordinate A261180 direction 0 to 5 A261185 direction mod 2 A229214 direction 1,2,3,-1,-2,-3 spiralling clockwise A261120 count of triple-visited points in the fractal limit A262147 \ fractions making a spiral in the fractal A262148 / =head1 SEE ALSO L, L, L L, L, L, L Fukuda, Shimizu and Nakamura, "New Gosper Space Filling Curves", on flowsnake variations in bigger hexagons (with wiggly sides too). =over L or L =back =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/DiamondSpiral.pm0000644000175000017500000003356213734026672020330 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::DiamondSpiral; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_sqrtint = \&Math::PlanePath::_sqrtint; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use constant xy_is_visited => 1; use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1() ]; sub x_negative_at_n { my ($self) = @_; return $self->n_start + 3; } sub y_negative_at_n { my ($self) = @_; return $self->n_start + 4; } use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant 1.02 _UNDOCUMENTED__dxdy_list => (1,0, # E N=1 and other bottom 1,1, # NE N=6 -1,1, # NW N=2 -1,-1, # SW N=3 1,-1); # SE N=4 sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + 5; } use constant absdx_minimum => 1; use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_right => 0; # only left or straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # start cycle at the vertical downwards from x=1,y=0 # s = [ 0, 1, 2, 3 ] # n = [ 2, 6, 14,26 ] # n = 2*$s*$s - 2*$s + 2 # s = .5 + sqrt(.5*$n-.75) # # then top of the diamond at 2*$s - 1 # so n - (2*$s*$s - 2*$s + 2 + 2*$s - 1) # n - (2*$s*$s + 1) # # gives y=$s - n # then x=$s-abs($y) on the right or x=-$s+abs($y) on the left # sub n_to_xy { my ($self, $n) = @_; #### n_to_xy: $n $n = $n - $self->{'n_start'}; # starting $n==0, and warn if $n==undef if ($n < 1) { if ($n < 0) { return; } return ($n, 0); } my $d = int ( (1 + _sqrtint(2*$n-1)) / 2 ); #### $d #### d frac: ( (1 + _sqrtint(2*$n-1)) / 2 ) #### base: 2*$d*$d - 2*$d + 2 #### extra: 2*$d - 1 #### sub: 2*$d*$d +1 $n -= 2*$d*$d; ### rem from top: $n my $y = -abs($n) + $d; # y=+$d at the top, down to y=-$d my $x = abs($y) - $d; # 0 to $d on the right #### uncapped y: $y #### abs x: $x # cap for horiz at 5 to 6, 13 to 14 etc $d = -$d; if ($y < $d) { $y = $d; } return (($n >= 0 ? $x : -$x), # negate if on the right $y); } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); my $d = abs($x) + abs($y); # vertical along the y>=0 axis # s=0 n=1 # s=1 n=3 # s=2 n=9 # s=3 n=19 # s=4 n=33 # n = 2*$d*$d + 1 # my $n = 2*$d*$d; # then +/- $d to go to left or right x axis, and -/+ $y from there if ($x > 0) { ### right quad 1 and 4 return $n - $d + $y + $self->{'n_start'}; } else { # left quads 2 and 3 return $n + $d - $y + $self->{'n_start'}; } } # | | x2>=-x1 | # M---+ | M-------M | +---M # | | | | | | | | | # +---m | +----m--+ | m---+ # | | | # -----+------ -------+------- -----+-------- # | | | # # | | | # M---+ | M-------M y2>=-y1 | +---M # | | | | | | | | | # | m | | | | | m | # -------+------ -------m------- -----+-------- # | | | | | | | | | # M---+ | M-------M | +---M # | | | # # | | | # -----+------ -------+------- -----+-------- # | | | # +---m | +--m----+ | m---+ # | | | | | | | | | # M---+ | M-------M | +---M # | | | # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### DiamondSpiral rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; my $min_x = ($x2 < 0 ? $x2 : $x1 > 0 ? $x1 : 0); my $min_y = ($y2 < 0 ? $y2 : $y1 > 0 ? $y1 : 0); my $max_x = ($x2 > -$x1 ? $x2 : $x1); my $max_y = ($y2 >= -$y1+($max_x<=0) ? $y2 : $y1); return ($self->xy_to_n($min_x,$min_y), $self->xy_to_n($max_x,$max_y)); } 1; __END__ # 73 6 # 74 51 72 5 # 75 52 33 50 71 4 # 76 53 34 19 32 49 70 3 # 77 54 35 20 9 18 31 48 69 2 # 78 55 36 21 10 3 8 17 30 47 68 1 # 79 56 37 22 11 4 1 2 7 16 29 46 67 <- Y=0 # 80 57 38 23 12 5 6 15 28 45 66 -1 # 81 58 39 24 13 14 27 44 65 ... -2 # 82 59 40 25 26 43 64 89 -3 # 83 60 41 42 63 88 -4 # 84 61 62 87 -5 # 85 86 -6 # # ^ # -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 =for stopwords ie eg PlanePath Ryde Math-PlanePath OEIS =head1 NAME Math::PlanePath::DiamondSpiral -- integer points around a diamond shaped spiral =head1 SYNOPSIS use Math::PlanePath::DiamondSpiral; my $path = Math::PlanePath::DiamondSpiral->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes a diamond shaped spiral. 19 3 / \ 20 9 18 2 / / \ \ 21 10 3 8 17 1 / / / \ \ \ 22 11 4 1---2 7 16 <- Y=0 \ \ \ / / 23 12 5---6 15 ... -1 \ \ / / 24 13--14 27 -2 \ / 25--26 -3 ^ -3 -2 -1 X=0 1 2 3 This is not simply the C rotated, it spirals around faster, with side lengths following a pattern 1,1,1,1, 2,2,2,2, 3,3,3,3, etc, if the flat kink at the bottom (like N=13 to N=14) is treated as part of the lower right diagonal. Going diagonally on the sides as done here is like cutting the corners of the C, which is how it gets around in fewer steps than the C. See C, C and C for similar cutting just 3, 2 or 1 of the corners. XN=1,5,13,25,etc on the Y negative axis is the "centred square numbers" 2*k*(k+1)+1. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, with the same shape etc. For example to start at 0, =cut # math-image --path=DiamondSpiral,n_start=0 --all --output=numbers_dash --size=35x16 =pod n_start => 0 18 / \ 19 8 17 / / \ \ 20 9 2 7 16 / / / \ \ \ 21 10 3 0-- 1 6 15 \ \ \ / / 22 11 4-- 5 14 ... \ \ / / 23 12--13 26 \ / 24--25 XN=0,1,6,15,28,etc on the X axis is the hexagonal numbers k*(2k-1). N=0,3,10,21,36,etc on the negative X axis is the hexagonal numbers of the "second kind" k*(2k-1) for kE0. Combining those two is the triangular numbers 0,1,3,6,10,15,21,etc, k*(k+1)/2, on the X axis alternately positive and negative. XN=0,2,8,18,etc on the Y axis is 2*squares, 2*Y^2. N=0,4,12,24,etc on the negative Y axis is X2*pronic, 2*Y*(Y+1). =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::DiamondSpiral-Enew ()> =item C<$path = Math::PlanePath::DiamondSpiral-Enew (n_start =E $n)> Create and return a new diamond spiral object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n < 1> the return is an empty list, it being considered the path starts at 1. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each point in the path as a square of side 1, so the entire plane is covered. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head1 FORMULAS =head2 Rectangle to N Range Within each row N increases as X moves away from the Y axis, and within each column similarly N increases as Y moves away from the X axis. So in a rectangle the maximum N is at one of the four corners. | x1,y2 M---|----M x2,y2 | | | -------O--------- | | | | | | x1,y1 M---|----M x1,y1 | For any two columns x1 and x2 with x1Ex2, the values in column x2 are all bigger if x2E-x1. This is so even when x1 and x2 are on the same side of the origin, ie. both positive or both negative. For any two rows y1 and y2, the values in the part of the row with XE0 are bigger if y2E=-y1, and in the part of the row with XE=0 it's y2E-y1, or equivalently y2E=-y1+1. So the biggest corner is at max_x = (x2 > -x1 ? x2 : x1) max_y = (y2 >= -y1+(max_x<=0) ? y2 : y1) The minimum is similar but a little simpler. In any column the minimum is at Y=0, and in any row the minimum is at X=0. So at 0,0 if that's in the rectangle, or the edge on the side nearest the origin when not. min_x = / if x2 < 0 then x2 | if x1 > 0 then x1 \ else 0 min_y = / if y2 < 0 then y2 | if y1 > 0 then y1 \ else 0 =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back n_start=1 A130883 N on X axis, 2*n^2-n+1 A058331 N on Y axis, 2*n^2 + 1 A001105 N on column X=1, 2*n^2 A084849 N on X negative axis, 2*n^2+n+1 A001844 N on Y negative axis, centred squares 2*n^2+2n+1 A215471 N with >=5 primes among its 8 neighbours A215468 sum 8 neighbours N A217015 N permutation points order SquareSpiral rotate -90, value DiamondSpiral N at each A217296 inverse permutation n_start=0 A010751 X coordinate, runs 1 inc, 2 dec, 3 inc, etc A305258 Y coordinate A053616 abs(Y), runs k to 0 to k A000384 N on X axis, hexagonal numbers A001105 N on Y axis, 2*n^2 (and cf similar A184636) A014105 N on X negative axis, second hexagonals A046092 N on Y negative axis, 2*pronic A003982 delta(abs(X)+abs(Y)), 1 when N on Y negative axis which is where move "outward" to next ring n_start=-1 A188551 N positions of turns, from N=1 up A188552 and which are primes =head1 SEE ALSO L, L, L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/Columns.pm0000644000175000017500000002103013734026673017206 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::Columns; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest', 'floor'; # uncomment this to run the ### lines #use Devel::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1() ]; sub y_maximum { my ($self) = @_; return $self->{'height'} - 1; } sub diffxy_minimum { my ($self) = @_; if ($self->{'height'} == 0) { return 0; # at X=0,Y=0 } else { return 1 - $self->{'height'}; # at X=0,Y=height-1 } } sub dx_minimum { my ($self) = @_; return ($self->{'height'} <= 1 ? 1 # single row only : 0); } use constant dx_maximum => 1; sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; return (($self->{'height'} >= 2 ? (0,1) # N too : ()), 1, 1-$self->{'height'}); } sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + $self->{'height'} - 1; } sub dy_minimum { my ($self) = @_; return - ($self->{'height'}-1); } sub dy_maximum { my ($self) = @_; return ($self->{'height'} <= 1 ? 0 # single row only : 1); } sub absdx_minimum { my ($self) = @_; return ($self->{'height'} <= 1 ? 1 # single row only : 0); } sub absdy_minimum { my ($self) = @_; return ($self->{'height'} <= 1 ? 0 : 1); } sub dsumxy_minimum { my ($self) = @_; return 2 - $self->{'height'}; # dX=+1 dY=-(height-1) } use constant dsumxy_maximum => 1; sub ddiffxy_minimum { my ($self) = @_; return ($self->{'height'} == 1 ? 1 # constant dX=1,dY=0 : -1); # straight N } sub ddiffxy_maximum { my ($self) = @_; return $self->{'height'}; # dX=+1 dY=-(height-1) } sub dir_minimum_dxdy { my ($self) = @_; return ($self->{'height'} == 1 ? (1,0) # height=1 East only : (0,1)); # height>1 North } sub dir_maximum_dxdy { my ($self) = @_; return (1, $self->dy_minimum); } sub turn_any_left { my ($self) = @_; return ($self->{'height'} != 1); # height=1 only straight ahead } sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; return ($self->{'height'} == 1 ? undef : $self->n_start + $self->{'height'}); } *turn_any_right = \&turn_any_left; sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; return ($self->{'height'} == 1 ? undef : $self->n_start + $self->{'height'} - 1); } sub turn_any_straight { my ($self) = @_; return ($self->{'height'} != 2); # height=2 never straight } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); if (! exists $self->{'height'}) { $self->{'height'} = 1; } if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } sub n_to_xy { my ($self, $n) = @_; # no division by zero, and negatives not meaningful for now my $height; if (($height = $self->{'height'}) <= 0) { ### no points for height<=0 return; } $n = $n - $self->{'n_start'}; # zero based my $int = int($n); # BigFloat int() gives BigInt, use that $n -= $int; # fraction part, preserve any BigFloat if (2*$n >= 1) { # if $n >= 0.5, but BigInt friendly $n -= 1; $int += 1; } ### $n ### $int ### assert: $n >= -0.5 ### assert: $n < 0.5 my $x = int ($int / $height); $int -= $x*$height; if ($int < 0) { # ensure round down when $int negative $int += $height; $x -= 1; } ### floor x: $x ### remainder: $int return ($x, $n + $int); } sub xy_to_n { my ($self, $x, $y) = @_; $y = round_nearest ($y); if ($y < 0 || $y >= $self->{'height'}) { return undef; # outside the oblong } $x = round_nearest ($x); return $x * $self->{'height'} + $y + $self->{'n_start'}; } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; my $height = $self->{'height'}; $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); if ($y2 < $y1) { ($y1,$y2) = ($y2,$y1) } # swap to y1= $height || $y2 < 0) { ### completely outside 0 to height-1, or height<=0 ... return (1,0); } $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); if ($x2 < $x1) { ($x1,$x2) = ($x2,$x1) } # swap to x1= $height) { $y2 = ($y2*0) + $height-1; } # preserve bignum # exact range bottom left to top right return ($x1*$height + $y1 + $self->{'n_start'}, $x2*$height + $y2 + $self->{'n_start'}); } 1; __END__ =for stopwords PlanePath Math-PlanePath Ryde =head1 NAME Math::PlanePath::Columns -- points in fixed-height columns =head1 SYNOPSIS use Math::PlanePath::Columns; my $path = Math::PlanePath::Columns->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path is columns of a given fixed height. For example height 5 would be | 4 | 5 10 15 20 <--- height==5 3 | 4 9 14 19 2 | 3 8 13 18 1 | 2 7 12 17 ... Y=0 | 1 6 11 16 21 ---------------------- X=0 1 2 3 4 ... =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, with the same shape. For example to start at 0, =cut # math-image --path=Columns,n_start=0,height=5 --all --output=numbers =pod n_start => 0, height => 5 4 | 4 9 14 19 3 | 3 8 13 18 2 | 2 7 12 17 1 | 1 6 11 16 ... Y=0 | 0 5 10 15 20 ---------------------- X=0 1 2 3 4 ... The only effect is to push the N values around by a constant amount. It might help match coordinates with something else zero-based. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::Columns-Enew (height =E $h)> =item C<$path = Math::PlanePath::Columns-Enew (height =E $h, n_start =E $n)> Create and return a new path object. A C parameter must be supplied. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> in the path. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are rounded to the nearest integers, which has the effect of treating each point in the path as a square of side 1, so a rectangle $x >= -0.5 and -0.5 <= y < height+0.5 is covered. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/DekkingCentres.pm0000644000175000017500000003036213734026673020476 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::DekkingCentres; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'round_up_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; *_UNDOCUMENTED__dxdy_list = \&Math::PlanePath::_UNDOCUMENTED__dxdy_list_eight; use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East #------------------------------------------------------------------------------ # tables generated by tools/dekking-curve-table.pl # state length 200 in each of 4 tables use vars '@_next_state','@_digit_to_x','@_digit_to_y','@_yx_to_digit'; @_next_state = ( 0, 0,175,100, 25, # 0 0,175,100, 50,175, 0, 0,150, 25,150, 75, 75,100, 75,125, 150, 25, 0,125,125, 25, 25,100,125, 50, # 25 25,100,125, 75,100, 25, 25,175, 50,175, 0, 0,125, 0,150, 175, 50, 25,150,150, 50, 50,125,150, 75, # 50 50,125,150, 0,125, 50, 50,100, 75,100, 25, 25,150, 25,175, 100, 75, 50,175,175, 75, 75,150,175, 0, # 75 75,150,175, 25,150, 75, 75,125, 0,125, 50, 50,175, 50,100, 125, 0, 75,100,100, 25, 25,100,125, 50, # 100 25,175, 0,175,175, 50,125, 50,100,100, 75,150, 0, 75,100, 125, 0, 75,100,100, 50, 50,125,150, 75, # 125 50,100, 25,100,100, 75,150, 75,125,125, 0,175, 25, 0,125, 150, 25, 0,125,125, 75, 75,150,175, 0, # 150 75,125, 50,125,125, 0,175, 0,150,150, 25,100, 50, 25,150, 175, 50, 25,150,150, 0, 0,175,100, 25, # 175 0,150, 75,150,150, 25,100, 25,175,175, 50,125, 75, 50,175, 100, 75, 50,175,175); @_digit_to_x = (0,1,2,1,0, 1,2,1,0,0, 0,1,2,2,3, 4,4,3,3,2, 3,3,4,4,4, 4,4,4,3,3, 2,2,1,2,1, 0,0,1,0,0, 0,1,1,2,3, 4,3,2,3,4, 4,3,2,3,4, 3,2,3,4,4, 4,3,2,2,1, 0,0,1,1,2, 1,1,0,0,0, 0,0,0,1,1, 2,2,3,2,3, 4,4,3,4,4, 4,3,3,2,1, 0,1,2,1,0, 4,4,4,3,3, 2,3,3,4,4, 3,2,2,1,0, 0,0,1,2,1, 0,1,2,1,0, 4,3,2,3,4, 3,2,1,1,0, 0,0,1,0,0, 1,2,1,2,2, 3,3,4,4,4, 0,0,0,1,1, 2,1,1,0,0, 1,2,2,3,4, 4,4,3,2,3, 4,3,2,3,4, 0,1,2,1,0, 1,2,3,3,4, 4,4,3,4,4, 3,2,3,2,2, 1,1,0,0,0); @_digit_to_y = (0,0,0,1,1, 2,2,3,2,3, 4,4,3,4,4, 4,3,3,2,1, 0,1,2,1,0, 0,1,2,1,0, 1,2,1,0,0, 0,1,2,2,3, 4,4,3,3,2, 3,3,4,4,4, 4,4,4,3,3, 2,2,1,2,1, 0,0,1,0,0, 0,1,1,2,3, 4,3,2,3,4, 4,3,2,3,4, 3,2,3,4,4, 4,3,2,2,1, 0,0,1,1,2, 1,1,0,0,0, 0,1,2,1,0, 1,2,3,3,4, 4,4,3,4,4, 3,2,3,2,2, 1,1,0,0,0, 4,4,4,3,3, 2,3,3,4,4, 3,2,2,1,0, 0,0,1,2,1, 0,1,2,1,0, 4,3,2,3,4, 3,2,1,1,0, 0,0,1,0,0, 1,2,1,2,2, 3,3,4,4,4, 0,0,0,1,1, 2,1,1,0,0, 1,2,2,3,4, 4,4,3,2,3, 4,3,2,3,4); @_yx_to_digit = (0, 1, 2,20,24, # 0 4, 3,19,21,23, 8, 5, 6,18,22, 9, 7,12,17,16, 10,11,13,14,15, 10, 9, 8, 4, 0, # 25 11, 7, 5, 3, 1, 13,12, 6,19, 2, 14,17,18,21,20, 15,16,22,23,24, 15,14,13,11,10, # 50 16,17,12, 7, 9, 22,18, 6, 5, 8, 23,21,19, 3, 4, 24,20, 2, 1, 0, 24,23,22,16,15, # 75 20,21,18,17,14, 2,19, 6,12,13, 1, 3, 5, 7,11, 0, 4, 8, 9,10, 24,23,22, 4, 0, # 100 20,21, 5, 3, 1, 16,19,18, 6, 2, 15,17,12, 7, 8, 14,13,11,10, 9, 14,15,16,20,24, # 125 13,17,19,21,23, 11,12,18, 5,22, 10, 7, 6, 3, 4, 9, 8, 2, 1, 0, 9,10,11,13,14, # 150 8, 7,12,17,15, 2, 6,18,19,16, 1, 3, 5,21,20, 0, 4,22,23,24, 0, 1, 2, 8, 9, # 175 4, 3, 6, 7,10, 22, 5,18,12,11, 23,21,19,17,13, 24,20,16,15,14); sub n_to_xy { my ($self, $n) = @_; ### DekkingCurve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; my @digits = digit_split_lowtohigh($int,25); my $state = my $dirstate = 0; my @x; my @y; foreach my $i (reverse 0 .. $#digits) { $state += $digits[$i]; ### $state ### digit_to_x: $digit_to_x[$state] ### digit_to_y: $digit_to_y[$state] ### next_state: $next_state[$state] if ($digits[$i] != 24) { # lowest non-24 digit $dirstate = $state; } $x[$i] = $_digit_to_x[$state]; $y[$i] = $_digit_to_y[$state]; $state = $_next_state[$state]; } my $zero = $int * 0; return ($n * ($_digit_to_x[$dirstate+1] - $_digit_to_x[$dirstate]) + digit_join_lowtohigh(\@x, 5, $zero), $n * ($_digit_to_y[$dirstate+1] - $_digit_to_y[$dirstate]) + digit_join_lowtohigh(\@y, 5, $zero)); } sub xy_to_n { my ($self, $x, $y) = @_; ### DekkingCurve xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my @x = digit_split_lowtohigh($x,5); my @y = digit_split_lowtohigh($y,5); ### @x ### @y my $state = 0; my @n; foreach my $i (reverse 0 .. max($#x,$#y)) { my $digit = $n[$i] = $_yx_to_digit[$state + 5*($y[$i]||0) + ($x[$i]||0)]; $state = $_next_state[$state+$digit]; } return digit_join_lowtohigh(\@n, 25, $x*0*$y); # preserve bignum } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### DekkingCurve rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); $x2 = max($x1,$x2); $y2 = max($y1,$y2); if ($x2 < 0 || $y2 < 0) { ### rectangle all negative, no N values ... return (1, 0); } my ($pow) = round_down_pow (max($x2,$y2), 5); ### $pow return (0, 25*$pow*$pow-1); } #------------------------------------------------------------------------------ # shared by Math::PlanePath::CincoCurve sub level_to_n_range { my ($self, $level) = @_; return (0, 25**$level - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($pow, $exp) = round_up_pow ($n+1, 25); return $exp; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde ie Math-PlanePath Dekking Gosper McKenna =head1 NAME Math::PlanePath::DekkingCentres -- 5x5 self-similar =head1 SYNOPSIS use Math::PlanePath::DekkingCentres; my $path = Math::PlanePath::DekkingCentres->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is a variation on a 5x5 self-similar curve from =over F. M. Dekking, "Recurrent Sets", Advances in Mathematics, volume 44, 1982, pages 79-104, section 4.9 "Gosper-Type Curves" =back and which is a horizontal mirror image of the E-curve of McKenna 1978. The form visits the "centres" of the 5x5 self-similar unit squares of the pattern. The result is some diagonal steps, but replications wholly within 5x5 areas. =cut # math-image --path=DekkingCentres --all --output=numbers_dash --size=75x26 =pod ... | / 9 | 115-116 122-123-124 89--88 86--85--84 | | | \ | \ | | 8 | 114 117-118 121-120 90 92 87 82--83 | | \ / |/ \ | 7 | 113-112 106 119 102 91 94--93 81 77 | / / | / | / / / | 6 | 111 107 105 103 101 95--96 80 78 76 | | \ \ | | \ \ | | 5 | 110-109-108 104 100--99--98--97 79 75 | \ 4 | 10--11 13--14--15 35--36 38--39--40 74 70 66--65--64 | | \ | | | \ | | | |\ \ | 3 | 9 7 12 17--16 34 32 37 42--41 73 71 69 67 63 | |/ \ | |/ \ | |/ |/ / 2 | 8 5-- 6 18 22 33 30--31 43 47 72 55 68 62--61 | / / / | / / / | / \ | 1 | 4-- 3 19 21 23 29--28 44 46 48 54--53 56--57 60 | \ \ | | \ \ | | \ | | Y=0 | 0-- 1-- 2 20 24--25--26--27 45 49--50--51--52 58--59 +--------------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 The base pattern is the N=0 to N=24 section. It repeats with rotations or reversals which make the ends join. For example N=75 to N=99 is the base pattern in reverse. Or N=50 to N=74 is reverse and also rotate by -90. =head1 FUNCTIONS See L the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::DekkingCentres-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 25**$level - 1)>. =back =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/StaircaseAlternating.pm0000644000175000017500000003260513734026654021706 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=StaircaseAlternating --all --output=numbers_dash --size=70x30 # math-image --path=StaircaseAlternating,end_type=square --all --output=numbers_dash --size=70x30 package Math::PlanePath::StaircaseAlternating; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; use Math::PlanePath::Base::NSEW; *_sqrtint = \&Math::PlanePath::_sqrtint; # uncomment this to run the ### lines #use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; my %n_frac_discontinuity = (jump => .5); sub n_frac_discontinuity { my ($self) = @_; return $n_frac_discontinuity{$self->{'end_type'}}; } use constant parameter_info_array => [ { name => 'end_type', share_key => 'end_type_jumpsquare', display => 'Type', type => 'enum', default => 'jump', choices => ['jump','square'], choices_display => ['Jump','Wquare'], }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; #------------------------------------------------------------------------------ use constant dx_minimum => -1; { my %dx_maximum = (jump => 2, square => 1); sub dx_maximum { my ($self) = @_; return $dx_maximum{$self->{'end_type'}}; } } use constant dy_minimum => -1; *dy_maximum = \&dx_maximum; sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; return ($self->{'end_type'} eq 'jump' ? (1,0, # E 2,0, # E by 2 0,1, # N 0,2, # N by 2 -1,0, # W 0,-1) # S : Math::PlanePath::Base::NSEW->_UNDOCUMENTED__dxdy_list); } use constant dsumxy_minimum => -1; # straight S *dsumxy_maximum = \&dx_maximum; { my %dDiffXY_max = (jump => -2, square => -1); sub ddiffxy_minimum { my ($self) = @_; return $dDiffXY_max{$self->{'end_type'}}; } } *ddiffxy_maximum = \&dx_maximum; use constant dir_maximum_dxdy => (0,-1); # South #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'end_type'} ||= 'jump'; if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # --16 # | # 17--18 # | # --15 19--20 # | | # 14--13 21--22 # | | # --2 12--11 23--24 34--33 # | | | | # 3-- 4 10-- 9 25--26 32--31 # | | | | # --1 5-- 6 8-- 7 27--28 30--29 # 17 # |\ # 16 18 # | | # 15 19-20 # | | # 14-13 21-22 # | | # 3 12-11 23-24 34-33 # |\ | | | # 2 4 10--9 25-26 32-31 # | | \ | \ # 1 5--6--7--8 27-28-29-30 # . # # 42-43 # | | # 41 44-45 # | | # 40-39 46-47 # | | # . 38-37 48- # | # 14-15 35-36 # | | | # 13 16-17 34-33 # | | | # 12-11 18-19 32-31 # | | | # . 10--9 20-21 30-29 # | | | # 2--3 8--7 22-23 28-27 # | | | | | # 1 4--5--6 . 24-25-26 . # # start from integer vertical # d = [ 2, 3, 4, 5 ] # N = [ 5, 13, 25, 41 ] # N = (2 d^2 - 2 d + 1) # = ((2*$d - 2)*$d + 1) # d = 1/2 + sqrt(1/2 * $n + -1/4) # = (1 + sqrt(2*$n - 1)) / 2 # sub n_to_xy { my ($self, $n) = @_; #### StaircaseAlternating n_to_xy: $n # adjust to N=1 at origin X=0,Y=0 $n = $n - $self->{'n_start'} + 1; my $d; if ($self->{'end_type'} eq 'square') { if ($n < 1) { return; } $d = int( (1 + _sqrtint(2*$n-1)) / 2 ); $n -= (2*$d - 2)*$d; ### $d ### remainder n: $n if ($n < 2) { if ($d % 2) { return (0, $n+2*$d-3); } else { return ($n+2*$d-3, 0); } } } else { if (2*$n < 1) { return; } $d = int ((1 + _sqrtint(8*$n-3)) / 4); $n -= (2*$d - 1)*$d; ### rem: $n } my $int = int($n); my $frac = $n - $int; my $r = int($int/2); my ($x,$y); if ($int % 2) { ### down ... $x = $r; $y = -$frac + 2*$d - $r; } else { ### across ... $x = $frac + $r-1; $y = 2*$d - $r; } if ($d % 2) { return ($x,$y); } else { return ($y,$x); } } sub xy_to_n { my ($self, $x, $y) = @_; ### StaircaseAlternating xy_to_n(): "$x,$y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } my $jump = ($self->{'end_type'} ne 'square'); unless ($jump) { # square omitted endpoints if ($x == 0) { if (($y % 4) == 2) { return undef; } } elsif ($y == 0 && ($x % 4) == 0) { return undef; } } my $d = int(($x + $y + 1) / 2); return ((2*$d + $jump) * $d + ($d % 2 ? $x - $y : $y - $x) + $self->{'n_start'}); } # 12--11 18--19 14--13 21--22 # | | | | # . 10-- 9 20 2 12--11 23 # | | | # 2-- 3 8-- 7 3-- 4 10-- 9 # | | | | | # 1 4-- 5-- 6 1 5-- 6 8 # my @yx_to_min_dx = (0, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0); my @yx_to_min_dy = (0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0); my @yx_to_max_dx = (1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0); my @yx_to_max_dy = (0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1); # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### StaircaseAlternating rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } # x2 > x1 if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # y2 > y1 if ($x2 < 0 || $y2 < 0) { ### entirely outside first quadrant ... return (1, 0); } # not less than 0,0 if ($x1 < 0) { $x1 *= 0; } if ($y1 < 0) { $y1 *= 0; } my $corner_x1 = $x1; my $corner_y1 = $y1; my $corner_x2 = $x2; my $corner_y2 = $y2; { my $key = 4*($y2 % 4) + ($x2 % 4); if ($x2 > $x1 && $yx_to_max_dx[$key]) { $corner_x2 -= 1; } elsif ($y2 > 0 && $y2 > $y1) { $corner_y2 -= $yx_to_max_dy[$key]; } } my $square = ($self->{'end_type'} eq 'square'); if ($square && $x1 == 0 && ($y1 % 4) == 2) { ### x1,y1 is an omitted Y axis point ... if ($corner_x1 < $x2) { $corner_x1 += 1; } elsif ($corner_y1 < $y2) { $corner_y1 += 1; } else { ### only this point ... return (1, 0); } } elsif ($square && $y1 == 0 && $x1 > 0 && ($x1 % 4) == 0) { if ($corner_y1 < $y2) { $corner_y1 += 1; } elsif ($corner_x1 < $x2) { $corner_x1 += 1; } else { ### only an omitted X axis point ... return (1, 0); } } { my $key = 4*($corner_y1 % 4) + ($corner_x1 % 4); ### min key: $key if ($corner_x1 < $x2 && (my $dx = $yx_to_min_dx[$key])) { ### x1 incr ... unless ($square && $dx < 0 && $corner_y1 == 0) { $corner_x1 += 1; } } elsif ($corner_y1 < $y2 && (my $dy = $yx_to_min_dy[$key])) { ### y1 incr ... unless ($square && $dy < 0 && $corner_x1 == 0) { $corner_y1 += 1; } } } ### corners: "$x1,$y1 $x2,$y2" return ($self->xy_to_n($corner_x1,$corner_y1), $self->xy_to_n($corner_x2,$corner_y2)); } # inexact but easier ... # # if ($self->{'end_type'} eq 'square') { # $x2 += $y2 + 1; # $x2 = int($x2/2); # return (1, # (2*$x2+2)*$x2 + 1); # } else { # $x2 += $y2 + 2; # return (1, # $x2*($x2+1)/2); # } 1; __END__ =for stopwords eg Ryde Math-PlanePath OEIS =head1 NAME Math::PlanePath::StaircaseAlternating -- stair-step diagonals up and down =head1 SYNOPSIS use Math::PlanePath::StaircaseAlternating; my $path = Math::PlanePath::StaircaseAlternating->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes a staircase pattern up from Y axis down to the X and then back up again. 10 46 | 9 47--48 | 8 45 49--50 | | 7 44--43 51--52 | | 6 16 42--41 53--54 | | | 5 17--18 40--39 55--... | | 4 15 19--20 38--37 | | | 3 14--13 21--22 36--35 | | | 2 2 12--11 23--24 34--33 | | | | 1 3-- 4 10-- 9 25--26 32--31 | | | | Y=0 -> 1 5-- 6 8-- 7 27--28 30--29 ^ X=0 1 2 3 4 5 6 7 8 =head2 Square Ends Option C "square"> changes the path as follows, omitting one point at each end so as to square up the joins. 9 42--43 | | 8 41 44--45 | | 7 40--39 46--47 | | 6 . 38--37 48--49 | | 5 14--15 36--35 50--... | | | 4 13 16--17 34--33 | | | 3 12--11 18--19 32--31 | | | 2 . 10-- 9 20--21 30--29 | | | 1 2-- 3 8-- 7 22--23 28--27 | | | | | Y=0 -> 1 4-- 5-- 6 . 24--25--26 ^ X=0 1 2 3 4 5 6 7 8 The effect is to shorten each diagonal by a constant 1 each time. The lengths of each diagonal still grow by +4 each time (or by +16 up and back). =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same pattern. For example to start at 0, =cut # math-image --path=StaircaseAlternating,n_start=0 --expression='i<=53?i:0' --output=numbers --size=80x10 # math-image --path=StaircaseAlternating,n_start=0,end_type=square --expression='i<=48?i:0' --output=numbers --size=80x10 =pod n_start => 0 n_start => 0, end_type=>"square" 46 47 41 42 44 48 49 40 43 44 43 42 50 51 39 38 45 46 15 41 40 52 53 37 36 47 48 16 17 39 38 ... 13 14 35 34 ... 14 18 19 37 36 12 15 16 33 32 13 12 20 21 35 34 11 10 17 18 31 30 1 11 10 22 23 33 32 9 8 19 20 29 28 2 3 9 8 24 25 31 30 1 2 7 6 21 22 27 26 0 4 5 7 6 26 27 29 28 0 3 4 5 23 24 25 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::StaircaseAlternating-Enew ()> =item C<$path = Math::PlanePath::StaircaseAlternating-Enew (end_type =E $str, n_start =E $n)> Create and return a new path object. The C choices are "jump" (the default) "square" =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back end_type=jump, n_start=1 (the defaults) A084849 N on diagonal X=Y end_type=jump, n_start=0 A014105 N on diagonal X=Y, second hexagonal numbers end_type=jump, n_start=2 A096376 N on diagonal X=Y end_type=square, n_start=1 A058331 N on diagonal X=Y, 2*squares+1 end_type=square, n_start=0 A001105 N on diagonal X=Y, 2*squares =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/HeptSpiralSkewed.pm0000644000175000017500000002304713734026670021013 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::HeptSpiralSkewed; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_sqrtint = \&Math::PlanePath::_sqrtint; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; use constant xy_is_visited => 1; sub x_negative_at_n { my ($self) = @_; return $self->n_start + 3; } sub y_negative_at_n { my ($self) = @_; return $self->n_start + 5; } sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + 8; } use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant 1.02 _UNDOCUMENTED__dxdy_list => (1,0, # E four plus NW 0,1, # N -1,1, # NW -1,0, # W 0,-1); # S use constant dsumxy_minimum => -1; # W,S straight use constant dsumxy_maximum => 1; # N,E straight use constant ddiffxy_minimum => -2; # NW diagonal use constant ddiffxy_maximum => 1; use constant dir_maximum_dxdy => (0,-1); # South use constant turn_any_right => 0; # only left or straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # base South-West diagonal # d = [ 1, 2, 3, 4 ] # n = [ 0, 5, 17, 36 ] # N = (7/2 d^2 - 11/2 d + 2) # = (7/2*$d**2 - 11/2*$d + 2) # = ((7/2*$d - 11/2)*$d + 2) # d = 11/14 + sqrt(2/7 * $n + 9/196) # = (11 + 14*sqrt(2/7 * $n + 9/196))/14 # = (sqrt(56*$n + 9) + 11)/14 # # split North Y axis # d = [ 1, 2, 3 ] # n = [ 2, 11, 27 ] # N = (7*$d-3)*$d/2 sub n_to_xy { my ($self, $n) = @_; #### HeptSpiralSkewed n_to_xy: $n $n = $n - $self->{'n_start'}; # adjust to N=0 at origin X=0,Y=0 if ($n < 0) { return; } my $d = int((_sqrtint(56*$n+9) + 11) / 14); ### $d ### d frac: (_sqrtint(56*$n+9) + 11) / 14 $n -= (7*$d-3)*$d/2; ### remainder: $n if ($n < 0) { # split at Y axis if ($n >= -$d) { #### right diagonal ... return (-$n, $n + $d); } $n += $d; if ($n < 1-$d) { ### bottom horizontal ... return ($n + 2*$d-1, 1-$d); } ### right vertical ... return ($d, $n); } if ($n <= $d) { ### top horizontal ... return (-$n, $d); } #### left vertical ... return (-$d, -$n + 2*$d); } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); if ($x >= 0 && $y >= 0) { ### slope # relative to the y=0 base same as above # d = [ 1, 2, 3, 4 ] # n = [ 2, 10, 25, 47 ] # n = (7/2*$d**2 + -5/2*$d + 1) # = (3.5*$d - 2.5)*$d + 1 # my $d = $x + $y; return (7*$d - 5)*$d/2 + $y + $self->{'n_start'}; } my $d = max(abs($x),abs($y)); my $n = (7*$d - 5)*$d/2; if ($y == $d) { ### top horizontal return $n+$d - $x + $self->{'n_start'}; } if ($y == -$d) { ### bottom horizontal return $n + 5*$d + $x + $self->{'n_start'}; } if ($x == $d) { ### right vertical return $n + $y + $self->{'n_start'}; } # ($x == - $d) ### left vertical return $n + 3*$d - $y + $self->{'n_start'}; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); my $d = 0; foreach my $x ($x1, $x2) { foreach my $y ($y1, $y2) { $d = max ($d, 1 + ($x > 0 && $y > 0 ? $x+$y # slope : max(abs($x),abs($y)))); # square corners } } # ENHANCE-ME: find actual minimum if rect doesn't cover 0,0 return ($self->{'n_start'}, $self->{'n_start'} + (7*$d - 5)*$d/2); } 1; __END__ =for stopwords PlanePath Ryde Math-PlanePath OEIS =head1 NAME Math::PlanePath::HeptSpiralSkewed -- integer points around a skewed seven sided spiral =head1 SYNOPSIS use Math::PlanePath::HeptSpiralSkewed; my $path = Math::PlanePath::HeptSpiralSkewed->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes a seven-sided spiral by cutting one corner of a square =cut # math-image --path=HeptSpiralSkewed --expression='i<=44?i:0' --output=numbers_dash --size=60x18 =pod 31-30-29-28 3 | \ 32 14-13-12 27 2 | | \ \ 33 15 4--3 11 26 1 | | | \ \ \ 34 16 5 1--2 10 25 <- Y=0 | | | | | 35 17 6--7--8--9 24 -1 | | | 36 18-19-20-21-22-23 -2 | 37-38-39-40-41-... -3 ^ -3 -2 -1 X=0 1 2 3 The path is as if around a heptagon, with the left and bottom here as two sides of the heptagon straightened out, and the flat top here skewed across to fit a square grid. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same pattern. For example to start at 0, =cut # math-image --path=HeptSpiralSkewed,n_start=0 --expression='i<=40?i:0' --output=numbers --size=60x11 =pod 30 29 28 27 n_start => 0 31 13 12 11 26 32 14 3 2 10 25 33 15 4 0 1 9 24 34 16 5 6 7 8 23 35 17 18 19 20 21 22 36 37 38 39 40 ... =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::HeptSpiralSkewed-Enew ()> =item C<$path = Math::PlanePath::HeptSpiralSkewed-Enew (n_start =E $n)> Create and return a new path object. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each N in the path as centred in a square of side 1, so the entire plane is covered. =back =head1 FORMULAS =head2 N to X,Y It's convenient to work in terms of Nstart=0 and to take each loop as beginning on the South-West diagonal, =cut # math-image --path=HeptSpiralSkewed,n_start=0 --expression='i<=37?i:0' --output=numbers_dash --size=25x16 =pod top length = d 30-29-28-27 | \ 31 26 diagonal length = d left | \ length 32 25 = 2*d | \ 33 0 24 | | right 34 . 23 length = d-1 | | 35 17-18-19-20-21-22 | . bottom length = 2*d-1 The SW diagonal is N=0,5,17,36,etc which is N = (7d-11)*d/2 + 2 # starting d=1 first loop This can be inverted to get d from N d = floor( (sqrt(56*N+9)+11)/14 ) The side lengths are as shown above. The first loop is d=1 and for it the "right" vertical length is zero, so no such side on that first loop 0 E= N < 5. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back n_start=1 A140065 N on Y axis n_start=0 A001106 N on X axis, 9-gonal numbers A218471 N on Y axis A022265 N on X negative axis A179986 N on Y negative axis, second 9-gonals A195023 N on X=Y diagonal A022264 N on North-West diagonal A186029 N on South-West diagonal A024966 N on South-East diagonal =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/PythagoreanTree.pm0000644000175000017500000023016013734026657020677 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=PythagoreanTree --all --scale=3 # http://sunilchebolu.wordpress.com/pythagorean-triples-and-the-integer-points-on-a-hyperboloid/ # http://www.math.uconn.edu/~kconrad/blurbs/ugradnumthy/pythagtriple.pdf # # http://www.math.ou.edu/~dmccullough/teaching/pythagoras1.pdf # http://www.math.ou.edu/~dmccullough/teaching/pythagoras2.pdf # # http://www.microscitech.com/pythag_eigenvectors_invariants.pdf # package Math::PlanePath::PythagoreanTree; use 5.004; use strict; use Carp 'croak'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_divrem = \&Math::PlanePath::_divrem; *_sqrtint = \&Math::PlanePath::_sqrtint; @ISA = ('Math::PlanePath'); #use List::Util 'min','max'; *min = \&Math::PlanePath::_min; *max = \&Math::PlanePath::_max; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use Math::PlanePath::GrayCode; # uncomment this to run the ### lines # use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant tree_num_children_list => (3); # complete ternary tree use constant tree_n_to_subheight => undef; # complete tree, all infinity use constant parameter_info_array => [ { name => 'tree_type', share_key => 'tree_type_uadfb', display => 'Tree Type', type => 'enum', default => 'UAD', choices => ['UAD','UArD','FB','UMT'], }, { name => 'coordinates', share_key => 'coordinates_abcpqsm', display => 'Coordinates', type => 'enum', default => 'AB', choices => ['AB','AC','BC','PQ', 'SM','SC','MC', # 'BA' # 'UV', # q from x=y diagonal down at 45-deg # 'RS','ST', # experimental ], }, { name => 'digit_order', display => 'Digit Order', type => 'enum', default => 'HtoL', choices => ['HtoL','LtoH'], }, ]; { my %UAD_coordinates_always_right = (PQ => 1, AB => 1, AC => 1); sub turn_any_left { my ($self) = @_; return ! ($self->{'tree_type'} eq 'UAD' && $UAD_coordinates_always_right{$self->{'coordinates'}}); } } { my %UAD_coordinates_always_left = (BC => 1); sub turn_any_right { my ($self) = @_; return ! ($self->{'tree_type'} eq 'UAD' && $UAD_coordinates_always_left{$self->{'coordinates'}}); } } { my %UMT_coordinates_any_straight = (BC => 1, # UMT at N=5 PQ => 1); # UMT at N=5 sub turn_any_straight { my ($self) = @_; return ($self->{'tree_type'} eq 'UMT' && $UMT_coordinates_any_straight{$self->{'coordinates'}}); } } #------------------------------------------------------------------------------ { my %coordinate_minimum = (A => 3, B => 4, C => 5, P => 2, Q => 1, S => 3, M => 4, ); sub x_minimum { my ($self) = @_; return $coordinate_minimum{substr($self->{'coordinates'},0,1)}; } sub y_minimum { my ($self) = @_; return $coordinate_minimum{substr($self->{'coordinates'},1)}; } } { my %diffxy_minimum = (PQ => 1, # octant X>=Y+1 so X-Y>=1 ); sub diffxy_minimum { my ($self) = @_; return $diffxy_minimum{$self->{'coordinates'}}; } } { my %diffxy_maximum = (AC => -2, # C>=A+2 so X-Y<=-2 BC => -1, # C>=B+1 so X-Y<=-1 SM => -1, # S -2, # S -1, # M{'coordinates'}}; } } { my %absdiffxy_minimum = (PQ => 1, AB => 1, # X=Y never occurs BA => 1, # X=Y never occurs AC => 2, # C>=A+2 so abs(X-Y)>=2 BC => 1, SM => 1, # X=Y never occurs SC => 2, # X<=Y-2 MC => 1, # X=Y never occurs ); sub absdiffxy_minimum { my ($self) = @_; return $absdiffxy_minimum{$self->{'coordinates'}}; } } use constant gcdxy_maximum => 1; # no common factor { my %absdx_minimum = ('AB,UAD' => 2, 'AB,FB' => 2, 'AB,UMT' => 2, 'AC,UAD' => 2, 'AC,FB' => 2, 'AC,UMT' => 2, 'BC,UAD' => 4, # at N=37 'BC,FB' => 4, # at N=2 X=12,Y=13 'BC,UMT' => 4, # at N=2 X=12,Y=13 'PQ,UAD' => 0, 'PQ,FB' => 0, 'PQ,UMT' => 0, 'SM,UAD' => 1, 'SM,FB' => 1, 'SM,UMT' => 2, 'SC,UAD' => 1, 'SC,FB' => 1, 'SC,UMT' => 1, 'MC,UAD' => 3, 'MC,FB' => 3, 'MC,UMT' => 1, ); sub absdx_minimum { my ($self) = @_; return $absdx_minimum{"$self->{'coordinates'},$self->{'tree_type'}"} || 0; } } { my %absdy_minimum = ('AB,UAD' => 4, 'AB,FB' => 4, 'AB,UMT' => 4, 'AC,UAD' => 4, 'AC,FB' => 4, 'BC,UAD' => 4, 'BC,FB' => 4, 'PQ,UAD' => 0, 'PQ,FB' => 1, 'SM,UAD' => 3, 'SM,FB' => 3, 'SM,UMT' => 1, 'SC,UAD' => 4, 'SC,FB' => 4, 'MC,UAD' => 4, 'MC,FB' => 4, ); sub absdy_minimum { my ($self) = @_; return $absdy_minimum{"$self->{'coordinates'},$self->{'tree_type'}"} || 0; } } { my %dir_minimum_dxdy = (# AB apparent minimum dX=16,dY=8 'AB,UAD' => [16,8], 'AC,UAD' => [1,1], # it seems # 'BC,UAD' => [1,0], # infimum # 'SM,UAD' => [1,0], # infimum # 'SC,UAD' => [1,0], # N=255 dX=7,dY=0 # 'MC,UAD' => [1,0], # infimum # 'SM,FB' => [1,0], # infimum # 'SC,FB' => [1,0], # infimum # 'SM,FB' => [1,0], # infimum 'AB,UMT' => [6,12], # it seems # N=ternary 1111111122 dx=118,dy=40 # in general dx=3*4k-2 dy=4k 'AC,UMT' => [3,1], # infimum # # 'BC,UMT' => [1,0], # N=31 dX=72,dY=0 'PQ,UMT' => [1,1], # N=1 'SM,UMT' => [1,0], # infiumum dX=big,dY=3 'SC,UMT' => [3,1], # like AC # 'MC,UMT' => [1,0], # at N=31 ); sub dir_minimum_dxdy { my ($self) = @_; return @{$dir_minimum_dxdy{"$self->{'coordinates'},$self->{'tree_type'}"} || [1,0] }; } } { # AB apparent maximum dX=-6,dY=-12 at N=3 # AC apparent maximum dX=-6,dY=-12 at N=3 same # PQ apparent maximum dX=-1,dY=-1 my %dir_maximum_dxdy = ('AB,UAD' => [-6,-12], 'AC,UAD' => [-6,-12], # 'BC,UAD' => [0,0], 'PQ,UAD' => [-1,-1], # 'SM,UAD' => [0,0], # supremum # 'SC,UAD' => [0,0], # supremum # 'MC,UAD' => [0,0], # supremum # 'AB,FB' => [0,0], # 'AC,FB' => [0,0], 'BC,FB' => [1,-1], # 'PQ,FB' => [0,0], # 'SM,FB' => [0,0], # supremum # 'SC,FB' => [0,0], # supremum # 'MC,FB' => [0,0], # supremum # N=ternary 1111111122 dx=118,dy=-40 # in general dx=3*4k-2 dy=-4k 'AB,UMT' => [3,-1], # supremum # 'AC,UMT' => [-10,-20], # at N=9 apparent maximum # 'BC,UMT' => [0,0], # apparent approach 'PQ,UMT' => [1,-1], # N=2 # 'SM,UMT' => [0,0], # supremum dX=big,dY=-1 'SC,UMT' => [-3,-5], # apparent approach # 'MC,UMT' => [0,0], # supremum dX=big,dY=-small ); sub dir_maximum_dxdy { my ($self) = @_; return @{$dir_maximum_dxdy{"$self->{'coordinates'},$self->{'tree_type'}"} || [0,0]}; } } #------------------------------------------------------------------------------ sub _noop { return @_; } my %xy_to_pq = (AB => \&_ab_to_pq, AC => \&_ac_to_pq, BC => \&_bc_to_pqa, # ignoring extra $a return PQ => \&_noop, SM => \&_sm_to_pq, SC => \&_sc_to_pq, MC => \&_mc_to_pq, UV => \&_uv_to_pq, RS => \&_rs_to_pq, ST => \&_st_to_pq, ); my %pq_to_xy = (AB => \&_pq_to_ab, AC => \&_pq_to_ac, BC => \&_pq_to_bc, PQ => \&_noop, SM => \&_pq_to_sm, SC => \&_pq_to_sc, MC => \&_pq_to_mc, UV => \&_pq_to_uv, RS => \&_pq_to_rs, ST => \&_pq_to_st, ); my %tree_types = (UAD => 1, UArD => 1, FB => 1, UMT => 1); my %digit_orders = (HtoL => 1, LtoH => 1); sub new { my $self = shift->SUPER::new (@_); { my $digit_order = ($self->{'digit_order'} ||= 'HtoL'); $digit_orders{$digit_order} || croak "Unrecognised digit_order option: ",$digit_order; } { my $tree_type = ($self->{'tree_type'} ||= 'UAD'); $tree_types{$tree_type} || croak "Unrecognised tree_type option: ",$tree_type; } { my $coordinates = ($self->{'coordinates'} ||= 'AB'); $self->{'xy_to_pq'} = $xy_to_pq{$coordinates} || croak "Unrecognised coordinates option: ",$coordinates; $self->{'pq_to_xy'} = $pq_to_xy{$coordinates}; } return $self; } sub n_to_xy { my ($self, $n) = @_; ### PythagoreanTree n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } } return &{$self->{'pq_to_xy'}}(_n_to_pq($self,$n)); } # maybe similar n_to_rsquared() as C^2=(P^2+Q^2)^2 sub n_to_radius { my ($self, $n) = @_; if (($self->{'coordinates'} eq 'AB' || $self->{'coordinates'} eq 'BA' || $self->{'coordinates'} eq 'SM') && $n == int($n)) { if ($n < 1) { return undef; } if (is_infinite($n)) { return $n; } my ($p,$q) = _n_to_pq($self,$n); return $p*$p + $q*$q; # C=P^2+Q^2 } return $self->SUPER::n_to_radius($n); } sub _n_to_pq { my ($self, $n) = @_; my $ndigits = _n_to_digits_lowtohigh($n); ### $ndigits if ($self->{'tree_type'} eq 'UArD') { Math::PlanePath::GrayCode::_digits_to_gray_reflected($ndigits,3); ### gray: $ndigits } if ($self->{'digit_order'} eq 'HtoL') { @$ndigits = reverse @$ndigits; ### reverse: $ndigits } my $zero = $n * 0; my $p = 2 + $zero; my $q = 1 + $zero; if ($self->{'tree_type'} eq 'FB') { ### FB ... foreach my $digit (@$ndigits) { # high to low, possibly $digit=undef ### $p ### $q ### $digit if ($digit) { if ($digit == 1) { $q = $p-$q; # (2p, p-q) M2 $p *= 2; } else { # ($p,$q) = (2*$p, $p+$q); $q += $p; # (p+q, 2q) M3 $p *= 2; } } else { # $digit == 0 # ($p,$q) = ($p+$q, 2*$q); $p += $q; # (p+q, 2q) M1 $q *= 2; } } } elsif ($self->{'tree_type'} eq 'UMT') { ### UMT ... foreach my $digit (@$ndigits) { # high to low, possibly $digit=undef ### $p ### $q ### $digit if ($digit) { if ($digit == 1) { $q = $p-$q; # (2p, p-q) M2 $p *= 2; } else { # $digit == 2 $p += 3*$q; # T $q *= 2; } } else { # $digit == 0 # ($p,$q) = ($p+$q, 2*$q); ($p,$q) = (2*$p-$q, $p); # "U" = (2p-q, p) } } } else { ### UAD or UArD ... ### assert: $self->{'tree_type'} eq 'UAD' || $self->{'tree_type'} eq 'UArD' # # Could optimize high zeros as repeated U # # high zeros as repeated U: $depth-scalar(@$ndigits) # # U^0 = p, q # # U^1 = 2p-q, p eg. P=2,Q=1 is 2*2-1,2 = 3,2 # # U^2 = 3p-2q, 2p-q eg. P=2,Q=1 is 3*2-2*1,2*2-1 = 4,3 # # U^3 = 4p-3q, 3p-2q # # U^k = (k+1)p-kq, kp-(k-1)q for k>=2 # # = p + k*(p-q), k*(p-q)+q # # and with initial p=2,q=1 # # U^k = 2+k, 1+k # # # $q = $depth - $#ndigits + $zero; # count high zeros + 1 # $p = $q + 1 + $zero; foreach my $digit (@$ndigits) { # high to low, possibly $digit=undef ### $p ### $q ### $digit if ($digit) { if ($digit == 1) { ($p,$q) = (2*$p+$q, $p); # "A" = (2p+q, p) } else { $p += 2*$q; # "D" = (p+2q, q) } } else { # $digit==0 ($p,$q) = (2*$p-$q, $p); # "U" = (2p-q, p) } } } ### final pq: "$p, $q" return ($p, $q); } # _n_to_digits_lowtohigh() returns an arrayref $ndigits which is a list of # ternary digits 0,1,2 from low to high which are the position of $n within # its row of the tree. # The length of the array is the depth. # # depth N N%3 2*N-1 (N-2)/3*2+1 # 0 1 1 1 1/3 # 1 2 2 3 1 # 2 5 2 9 3 # 3 14 2 27 9 # 4 41 2 81 27 28 + (28/2-1) = 41 # # (N-2)/3*2+1 rounded down to pow=3^k gives depth=k+1 and base=pow+(pow+1)/2 # is the start of the row base=1,2,5,14,41 etc. # # An easier calculation is 2*N-1 rounded down to pow=3^d gives depth=d and # base=2*pow-1, but 2*N-1 and 2*pow-1 might overflow an integer. Though # just yet round_down_pow() goes into floats and so doesn't preserve 64-bit # integer. So the technique here helps 53-bit float integers, but not right # up to 64-bits. # sub _n_to_digits_lowtohigh { my ($n) = @_; ### _n_to_digits_lowtohigh(): $n my @ndigits; if ($n >= 2) { my ($pow) = _divrem($n-2, 3); ($pow, my $depth) = round_down_pow (2*$pow+1, 3); ### $depth ### base: $pow + ($pow+1)/2 ### offset: $n - $pow - ($pow+1)/2 @ndigits = digit_split_lowtohigh ($n - $pow - ($pow+1)/2, 3); push @ndigits, (0) x ($depth - $#ndigits); # pad to $depth with 0s } ### @ndigits return \@ndigits; # { # my ($pow, $depth) = round_down_pow (2*$n-1, 3); # # ### h: 2*$n-1 # ### $depth # ### $pow # ### base: ($pow + 1)/2 # ### rem n: $n - ($pow + 1)/2 # # my @ndigits = digit_split_lowtohigh ($n - ($pow+1)/2, 3); # $#ndigits = $depth-1; # pad to $depth with undefs # ### @ndigits # # return \@ndigits; # } } #------------------------------------------------------------------------------ # xy_to_n() # Nrow(depth+1) - Nrow(depth) # = (3*pow+1)/2 - (pow+1)/2 # = (3*pow + 1 - pow - 1)/2 # = (2*pow)/2 # = pow # sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### PythagoreanTree xy_to_n(): "$x, $y" my ($p,$q) = &{$self->{'xy_to_pq'}}($x,$y) or return undef; # not a primitive A,B,C unless ($p >= 2 && $q >= 1) { # must be P > Q >= 1 return undef; } if (is_infinite($p)) { return $p; # infinity } if (is_infinite($q)) { return $q; # infinity } if ($p%2 == $q%2) { # must be opposite parity, not same parity return undef; } my @ndigits; # low to high if ($self->{'tree_type'} eq 'FB') { for (;;) { unless ($p > $q && $q >= 1) { return undef; } last if $q <= 1 && $p <= 2; if ($q % 2) { ### q odd, p even, digit 1 or 2 ... $p /= 2; if ($q > $p) { ### digit 2, M3 ... push @ndigits, 2; $q -= $p; # opp parity of p, and < new p } else { ### digit 1, M2 ... push @ndigits, 1; $q = $p - $q; # opp parity of p, and < p } } else { ### q even, p odd, digit 0, M1 ... push @ndigits, 0; $q /= 2; $p -= $q; # opp parity of q } ### descend: "$q / $p" } } elsif ($self->{'tree_type'} eq 'UMT') { for (;;) { ### at: "p=$p q=$q" my $qmod2 = $q % 2; unless ($p > $q && $q >= 1) { return undef; } last if $q <= 1 && $p <= 2; if ($p < 2*$q) { ($p,$q) = ($q, 2*$q-$p); # U push @ndigits, 0; } elsif ($qmod2) { $p /= 2; # M2 $q = $p - $q; push @ndigits, 1; } else { $q /= 2; # T $p -= 3*$q; push @ndigits, 2; } } } else { ### UAD or UArD ... ### assert: $self->{'tree_type'} eq 'UAD' || $self->{'tree_type'} eq 'UArD' for (;;) { ### $p ### $q if ($q <= 0 || $p <= 0 || $p <= $q) { return undef; } last if $q <= 1 && $p <= 2; if ($p > 2*$q) { if ($p > 3*$q) { ### digit 2 ... push @ndigits, 2; $p -= 2*$q; } else { ### digit 1 push @ndigits, 1; ($p,$q) = ($q, $p - 2*$q); } } else { ### digit 0 ... push @ndigits, 0; ($p,$q) = ($q, 2*$q-$p); } ### descend: "$q / $p" } } ### @ndigits if ($self->{'digit_order'} eq 'LtoH') { @ndigits = reverse @ndigits; ### unreverse: @ndigits } if ($self->{'tree_type'} eq 'UArD') { Math::PlanePath::GrayCode::_digits_from_gray_reflected(\@ndigits,3); ### ungray: @ndigits } my $zero = $x*0*$y; ### offset: digit_join_lowtohigh(\@ndigits,3,$zero) ### depth: scalar(@ndigits) ### Nrow: $self->tree_depth_to_n($zero + scalar(@ndigits)) return ($self->tree_depth_to_n($zero + scalar(@ndigits)) + digit_join_lowtohigh(\@ndigits,3,$zero)); # offset into row } # numprims(H) = how many with hypot < H # limit H->inf numprims(H) / H -> 1/2pi # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### PythagoreanTree rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); my $zero = ($x1 * 0 * $y1 * $x2 * $y2); # inherit bignum ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### x2: "$x2" ### y2: "$y2" if ($self->{'coordinates'} eq 'BA') { ($x2,$y2) = ($y2,$x2); } if ($self->{'coordinates'} eq 'SM') { if ($x2 > $y2) { # both max $y2 = $x2; } else { $x2 = $y2; } } if ($self->{'coordinates'} eq 'PQ') { if ($x2 < 2 || $y2 < 1) { return (1,0); } # P > Q so reduce y2 to at most x2-1 if ($y2 >= $x2) { $y2 = $x2-1; # $y2 = min ($y2, $x2-1); } if ($y2 < $y1) { ### PQ y range all above X=Y diagonal ... return (1,0); } } else { # AB,AC,BC, SM,SC,MC if ($x2 < 3 || $y2 < 0) { return (1,0); } } my $depth; if ($self->{'tree_type'} eq 'FB') { ### FB ... if ($self->{'coordinates'} eq 'PQ') { $x2 *= 3; } my ($pow, $exp) = round_down_pow ($x2, 2); $depth = 2*$exp; } else { ### UAD or UArD, and UMT ... if ($self->{'coordinates'} eq 'PQ') { ### PQ ... # P=k+1,Q=k diagonal N=100..000 first of row is depth=P-2 # anything else in that X=P column is smaller depth $depth = $x2 - 2; } else { my $xdepth = int (($x2+1) / 2); my $ydepth = int (($y2+31) / 4); $depth = min($xdepth,$ydepth); } } ### depth: "$depth" return (1, $self->tree_depth_to_n_end($zero+$depth)); } #------------------------------------------------------------------------------ use constant tree_num_roots => 1; sub tree_n_children { my ($self, $n) = @_; unless ($n >= 1) { return; } $n *= 3; return ($n-1, $n, $n+1); } sub tree_n_num_children { my ($self, $n) = @_; return ($n >= 1 ? 3 : undef); } sub tree_n_parent { my ($self, $n) = @_; unless ($n >= 2) { return undef; } return int(($n+1)/3); } sub tree_n_to_depth { my ($self, $n) = @_; ### PythagoreanTree tree_n_to_depth(): $n unless ($n >= 1) { return undef; } my ($pow, $depth) = round_down_pow (2*$n-1, 3); return $depth; } sub tree_depth_to_n { my ($self, $depth) = @_; return ($depth >= 0 ? (3**$depth + 1)/2 : undef); } # (3^(d+1)+1)/2-1 = (3^(d+1)-1)/2 sub tree_depth_to_n_end { my ($self, $depth) = @_; return ($depth >= 0 ? (3**($depth+1) - 1)/2 : undef); } sub tree_depth_to_n_range { my ($self, $depth) = @_; if ($depth >= 0) { my $n_lo = (3**$depth + 1) / 2; # same as tree_depth_to_n() return ($n_lo, 3*$n_lo-2); } else { return; } } sub tree_depth_to_width { my ($self, $depth) = @_; return ($depth >= 0 ? 3**$depth : undef); } #------------------------------------------------------------------------------ # Maybe, or abc_to_pq() perhaps with two of three values. # # @EXPORT_OK = ('ab_to_pq','pq_to_ab'); # # =item C<($p,$q) = Math::PlanePath::PythagoreanTree::ab_to_pq($a,$b)> # # Return the P,Q coordinates for C<$a,$b>. As described above this is # # P = sqrt((C+A)/2) where C=sqrt(A^2+B^2) # Q = sqrt((C-A)/2) # # The returned P,Q are integers PE=0,QE=0, but the further # conditions for the path (namely PEQE=1 and no common factor) are # not enforced. # # If P,Q are not integers or if BE0 then return an empty list. This # ensures A,B is a Pythagorean triple, ie. that C=sqrt(A^2+B^2) is an # integer, but it might not be a primitive triple and might not have A odd B # even. # # =item C<($a,$b) = Math::PlanePath::PythagoreanTree::pq_to_ab($p,$q)> # # Return the A,B coordinates for C<$p,$q>. This is simply # # $a = $p*$p - $q*$q # $b = 2*$p*$q # # This is intended for use with C<$p,$q> satisfying PEQE=1 and no # common factor, but that's not enforced. # a=p^2-q^2, b=2pq, c=p^2+q^2 # Done as a=(p-q)*(p+q) for one multiply instead of two squares, and to work # close to a=UINT_MAX. # sub _pq_to_ab { my ($p, $q) = @_; return (($p-$q)*($p+$q), 2*$p*$q); } # C=(p-q)^2+B for one squaring instead of two. # Also possible is C=(p+q)^2-B, but prefer "+B" so as not to round-off in # floating point if (p+q)^2 overflows an integer. sub _pq_to_bc { my ($p, $q) = @_; my $b = 2*$p*$q; $p -= $q; return ($b, $p*$p+$b); } # a=p^2-q^2, b=2pq, c=p^2+q^2 # Could a=(p-q)*(p+q) to avoid overflow if p^2 exceeds an integer as per # _pq_to_ab(), but c overflows in that case anyway. sub _pq_to_ac { my ($p, $q) = @_; $p *= $p; $q *= $q; return ($p-$q, $p+$q); } # a=p^2-q^2, b=2pq, c=p^2+q^2 # aa # # a = (a+c)/2 - q^2 # q^2 = (a+c)/2 - a # = (c-a)/2 # q = sqrt((c-a)/2) # # if c^2 = a^2+b^2 is a perfect square then a,b,c is a pythagorean triple # p^2 = (a+c)/2 # = (a + sqrt(a^2+b^2))/2 # 2p^2 = a + sqrt(a^2+b^2) # # p>q so a>0 # a+c even is a odd, c odd or a even, c even # if a odd then c=a^2+b^2 is opp of b parity, must have b even to make c+a even # if a even then c=a^2+b^2 is same as b parity, must have b even to c+a even # # a=6,b=8 is c=sqrt(6^2+8^2)=10 # a=0,b=4 is c=sqrt(0+4^4)=4 p^2=(a+c)/2 = 2 not a square # a+c even, then (a+c)/2 == 0,1 mod 4 so a+c==0,2 mod 4 # sub _ab_to_pq { my ($a, $b) = @_; ### _ab_to_pq(): "A=$a, B=$b" unless ($b >= 4 && ($a%2) && !($b%2)) { # A odd, B even return; } # This used to be $c=hypot($a,$b) and check $c==int($c), but libm hypot() # on Darwin 8.11.0 is somehow a couple of bits off being an integer, for # example hypot(57,176)==185 but a couple of bits out so $c!=int($c). # Would have thought hypot() ought to be exact on integer inputs and a # perfect square sum :-(. Check for a perfect square by multiplying back # instead. # # The condition is "$csquared != $c*$c" with operands that way around # since the other way is bad for Math::BigInt::Lite 0.14. # my $c; { my $csquared = $a*$a + $b*$b; $c = _sqrtint($csquared); ### $csquared ### $c # since A odd and B even should have C odd, but floating point rounding # might prevent that unless ($csquared == $c*$c) { ### A^2+B^2 not a perfect square ... return; } } return _ac_to_pq($a,$c); } sub _bc_to_pqa { my ($b, $c) = @_; ### _bc_to_pqa(): "B=$b C=$c" unless ($c > $b && $b >= 4 && !($b%2) && ($c%2)) { # B even, C odd return; } my $a; { my $asquared = $c*$c - $b*$b; unless ($asquared > 0) { return; } $a = _sqrtint($asquared); ### $asquared ### $a unless ($asquared == $a*$a) { return; } } # If $c is near DBL_MAX can have $a overflow to infinity, leaving A>C. # _ac_to_pq() will detect that. my ($p,$q) = _ac_to_pq($a,$c) or return; return ($p,$q,$a); } sub _ac_to_pq { my ($a, $c) = @_; ### _ac_to_pq(): "A=$a C=$c" unless ($c > $a && $a >= 3 && ($a%2) && ($c%2)) { # A odd, C odd return; } $a = ($a-1)/2; $c = ($c-1)/2; ### halved to: "a=$a c=$c" my $p; { # If a,b,c is a triple but not primitive then can have psquared not an # integer. Eg. a=9,b=12 has c=15 giving psquared=(9+15)/2=12 is not a # perfect square. So notice that here. # my $psquared = $c+$a+1; $p = _sqrtint($psquared); ### $psquared ### $p unless ($psquared == $p*$p) { ### P^2=A+C not a perfect square ... return; } } my $q; { # If a,b,c is a triple but not primitive then can have qsquared not an # integer. Eg. a=15,b=36 has c=39 giving qsquared=(39-15)/2=12 is not a # perfect square. So notice that here. # my $qsquared = $c-$a; $q = _sqrtint($qsquared); ### $qsquared ### $q unless ($qsquared == $q*$q) { return; } } # Might have a common factor between P,Q here. Eg. # A=27 = 3*3*3, B=36 = 4*3*3 # A=45 = 3*3*5, B=108 = 4*3*3*3 # A=63, B=216 # A=75 =3*5*5 B=100 = 4*5*5 # A=81, B=360 # return ($p, $q); } sub _sm_to_pq { my ($s, $m) = @_; unless ($s < $m) { return; } return _ab_to_pq($s % 2 ? ($s,$m) # s odd is A : ($m,$s)); # s even is B } # s^2+m^2=c^2 # if s odd then a=s # ac_to_pq # b = 2pq check isn't smaller than s # # p^2=(c+a)/2 # q^2=(c-a)/2 sub _sc_to_pq { my ($s, $c) = @_; my ($p,$q); if ($s % 2) { ($p,$q) = _ac_to_pq($s,$c) # s odd is A or return; if ($s > 2*$p*$q) { return; } # if s>B then s is not the smaller one } else { ($p,$q,$a) = _bc_to_pqa($s,$c) # s even is B or return; if ($s > $a) { return; } # if s>A then s is not the smaller one } return ($p,$q); } sub _mc_to_pq { my ($m, $c) = @_; ### _mc_to_pq() ... my ($p,$q); if ($m % 2) { ### m odd is A ... ($p,$q) = _ac_to_pq($m,$c) or return; if ($m < 2*$p*$q) { return; } # if m= 1; my $q = _sqrtint($s); return unless $q*$q == $s; return unless $r >= 1; my $p_plus_q = _sqrtint($r); return unless $p_plus_q*$p_plus_q == $r; return ($p_plus_q - $q, $q); } # s = 2*q^2 # t = a+b-c = p^2-q^2 + 2pq - (p^2+q^2) = 2pq-2q^2 = 2(p-q)q # # p=2,q=1 s=2 t=2.1.1=2 # sub _st_to_pq { my ($s, $t) = @_; ### _st_to_pq(): "$s, $t" return if $s % 2; $s /= 2; return unless $s >= 1; my $q = _sqrtint($s); ### $q return unless $q*$q == $s; return if $t % 2; $t /= 2; ### rem: $t % $q return if $t % $q; $t /= $q; # p-q ### pq: ($t+$q).", $q" return ($t+$q, $q); } 1; __END__ # my $a = 1; # my $b = 1; # my $c = 2; # my $d = 3; # ### at: "$a,$b,$c,$d digit $digit" # if ($digit == 0) { # ($a,$b,$c) = ($a,2*$b,$d); # } elsif ($digit == 1) { # ($a,$b,$c) = ($d,$a,2*$c); # } else { # ($a,$b,$c) = ($a,$d,2*$c); # } # $d = $b+$c; # ### final: "$a,$b,$c,$d" # # print "$a,$b,$c,$d\n"; # my $x = $c*$c-$b*$b; # my $y = 2*$b*$c; # return (max($x,$y), min($x,$y)); # return $x,$y; =for stopwords eg Ryde UAD FB Berggren Barning ie PQ parameterized parameterization Math-PlanePath someP someQ Q's coprime mixed-radix Nrow N-Nrow Liber Quadratorum gnomon gnomons Diophantus Nrem OEIS UArD mirrorings Firstov Semigroup Matematicheskie Zametki semigroup UMT LtoH =head1 NAME Math::PlanePath::PythagoreanTree -- primitive Pythagorean triples by tree =head1 SYNOPSIS use Math::PlanePath::PythagoreanTree; my $path = Math::PlanePath::PythagoreanTree->new (tree_type => 'UAD', coordinates => 'AB'); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path enumerates primitive Pythagorean triples by a breadth-first traversal of one of three ternary trees, "UAD" Berggren, Barning, Hall, and others "FB" Firstov and Price "UMT" Firstov Each X,Y point is a pair of integer A,B sides of a right triangle. The points are "primitive" in the sense that the sense that A and B have no common factor. A^2 + B^2 = C^2 gcd(A,B) = 1, no common factor X=A, Y=B ^ * ^ / /| | right triangle C / | B A side odd / / | | B side even v *---* v C hypotenuse (all integers) <-A-> A primitive triple always has one of A,B odd and the other even. The trees here have A odd and B even. The trees are traversed breadth-first and tend to go out to rather large A,B values while yet to complete smaller ones. The UAD tree goes out further than the FB. See the author's mathematical write-up for more properties. =over L =back =head2 UAD Tree The UAD tree by Berggren (1934) and later independently by Barning (1963), Hall (1970), and other authors, uses three matrices U, A and D which can be multiplied onto an existing primitive triple to form three further new primitive triples. tree_type => "UAD" (the default) Y=40 | 14 | | | | 7 Y=24 | 5 | Y=20 | 3 | Y=12 | 2 13 | | 4 Y=4 | 1 | +-------------------------------------------------- X=3 X=15 X=20 X=35 X=45 The UAD matrices are / 1 -2 2 \ / 1 2 2 \ / -1 2 2 \ U = | 2 -1 2 | A = | 2 1 2 | D = | -2 1 2 | \ 2 -2 3 / \ 2 2 3 / \ -2 2 3 / They're multiplied on the right of an (A,B,C) column vector, for example / 3 \ / 5 \ U * | 4 | = | 12 | \ 5 / \ 13 / The starting point is N=1 at X=3,Y=4 which is the well-known triple 3^2 + 4^2 = 5^2 From it three further points N=2, N=3 and N=4 are derived, then three more from each of those, etc, =cut # generated by tools/pythagorean-tree.pl =pod tree_type => "UAD" coordinates A,B ______________ 3,4 _____________ / | \ 5,12 21,20 15,8 / | \ / | \ / | \ 7,24 55,48 45,28 39,80 119,120 77,36 33,56 65,72 35,12 rows depth = 0 N=1 depth = 1 N=2..4 depth = 2 N=5..13 depth = 3 N=14.. Counting N=1 as depth=0, each level has 3^depth many points and the first N of a level (C) is at Nrow = 1 + (1 + 3 + 3^2 + ... + 3^(depth-1)) = (3^depth + 1) / 2 = 1, 2, 5, 14, 41, 122, 365, ... (A007051) The level numbering is like a mixed-radix representation of N where the high digit is binary (so always 1) and the digits below are ternary. +--------+---------+---------+-- --+---------+ N = | binary | ternary | ternary | ... | ternary | +--------+---------+---------+-- --+---------+ 1 0,1,2 0,1,2 0,1,2 The number of ternary digits is the "depth" and their value without the high binary 1 is the position in the row. =cut # GP-DEFINE depth_to_n(d) = (3^d+1)/2; # GP-Test vector(7,d,d--; depth_to_n(d)) == [1, 2, 5, 14, 41, 122, 365] # GP-DEFINE n_to_depth(n) = logint(2*n-1,3); # vector(42,n, n_to_depth(n)) # not in OEIS: 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4 # not A133879 # GP-DEFINE digits_padded(n,base,width) = { # GP-DEFINE my(v=digits(n,base)); # GP-DEFINE #v <= width || error(); # GP-DEFINE v=concat(vector(width-#v),v); # GP-DEFINE #v == width || error(); # GP-DEFINE v; # GP-DEFINE } # GP-DEFINE n_to_row_digits(n) = { # GP-DEFINE n>=1 || error(); # GP-DEFINE my(d=n_to_depth(n)); # GP-DEFINE digits_padded(n-depth_to_n(d),3,d); # GP-DEFINE } # GP-DEFINE digits_mixed(n) = concat([1],n_to_row_digits(n)); # GP-DEFINE to_mixed(n) = fromdigits(digits_mixed(n)); # vector(20,n, to_mixed(n)) # not in OEIS: 1, 10, 11, 12, 100, 101, 102, 110, 111, 112, 120, 121, 122, 1000, 1001, 1002, 1010, 1011 # GP-DEFINE n_from_row_digits(v) = depth_to_n(#v) + fromdigits(v,3); # GP-Test vector(3^6,n, n_from_row_digits(n_to_row_digits(n))) == \ # GP-Test vector(3^6,n, n) # GP-DEFINE n_reverse_digits(n) = \ # GP-DEFINE n_from_row_digits(Vecrev(n_to_row_digits(n))); # vector(20,n, n_reverse_digits(n)) # not in OEIS: 1,2,3,4,5,8,11,6,9,12,7,10,13,14,23,32,17,26,35,20 =pod =head2 U Repeatedly Taking the upper "U" matrix repeatedly gives 3.4 -> 5,12 -> 7,24 -> 9,40 -> etc with C=B+1 and A the odd numbers. These are the first of each level so at Nrow described above. The resulting triples are a sequence known to Pythagoras (Dickson's I, start of chapter IV). A = any odd integer, so A^2 any odd square B = (A^2-1)/2 C = (A^2+1)/2 / A^2-1 \ / A^2+1 \ A^2 + | ------ |^2 = | ----- |^2 \ 2 / \ 2 / =cut # listput(l,(A^2+1)/2); # my(l=List([])); forstep(A=3,11,2, \ # listput(l,A^2); \ # listput(l,(A^2-1)/2); \ # ); \ # Vec(l) # not in OEIS: 9, 4, 5, 25, 12, 13, 49, 24, 25 # not in OEIS: 9, 4, 25, 12, 49, 24, 81, 40, 121, 60 =pod This is also described by XFibonacci (XXI) in terms of sums of odd numbers s = any odd square = A^2 B^2 = 1 + 3 + 5 + ... + s-2 = ((s-1)/2)^2 C^2 = 1 + 3 + 5 + ... + s-2 + s = ((s+1)/2)^2 so C^2 = A^2 + B^2 eg. s=25=A^2 B^2=((25-1)/2)^2=144 so A=5,B=12 XThe geometric interpretation is that an existing square of side B is extended by a X"gnomon" around two sides making a new larger square of side C=B+1. The length of the gnomon is odd and when it's an odd square then the new total area is the sum of two squares. ****gnomon******* gnomon length an odd square = A^2 +-------------+ * | | * so new bigger square area | square | * C^2 = A^2 + B^2 | with side B | * | | * +-------------+ * See L for a path following such gnomons. =head2 A Repeatedly Taking the middle "A" matrix repeatedly gives 3,4 -> 21,20 -> 119,120 -> 697,696 -> etc A,B legs which are the triples with legs A,B differing by 1 and so just above and below the X=Y leading diagonal. The N values are 1,3,9,27,etc = 3^depth. =cut # FIXME: were these known to Fermat? # PQ coordinates A000129 Pell numbers =pod =head2 D Repeatedly Taking the lower "D" matrix repeatedly gives 3,4 -> 15,8 -> 35,12 -> 63,16 -> etc A,B legs which is the primitives among a sequence of triples known to the ancients (Dickson's I, start of chapter IV), A = k^2 - 1 k even >= 2 for primitives B = 2*k C = k^2 + 1 so C=A+2 When k is even these are primitive. If k is odd then A and B are both even, ie. a common factor of 2, so not primitive. These points are the last of each level, so at N=(3^(depth+1)-1)/2 which is C. =cut # listput(l,k^2+1); # my(l=List([])); forstep(k=2,8,2, \ # listput(l,k^2-1); \ # listput(l,2*k); \ # ); \ # Vec(l) # not in OEIS: 3, 4, 5, 15, 8, 17, 35, 12, 37, 63, 16, 65 # not in OEIS: 3, 4, 15, 8, 35, 12, 63, 16 =pod =head2 UArD Tree XOption C "UArD"> varies the UAD tree by applying a left-right reflection under each "A" matrix. The result is ternary reflected Gray code order. The 3 children under each node are unchanged, just their order. =cut # generated by tools/pythagorean-tree.pl =pod tree_type => "UArD" coordinates A,B ______________ 3,4 _____________ / | \ 5,12 21,20 15,8 / | \ / | \ / | \ 7,24 55,48 45,28 77,36 119,120 39,80 33,56 65,72 35,12 Notice the middle points 77,36 and 39,80 are swapped relative to the UAD shown above. In general, the whole tree underneath an "A" is mirrored left E-Eright. If there's an even number of "A"s above then those mirrorings cancel out to be plain again. This tree form is primarily of interest for L described below since it gives each row of points in order clockwise down from the Y axis. In L below, with the default digits high to low, UArD also makes successive steps across the row either horizontal or 45-degrees NE-SW. In all cases, the Gray coding is applied to N first, then the resulting digits are interpreted either high to low (the default) or low to high (C option). =head2 FB Tree XXOption C "FB"> selects a tree independently by =over V. E. Firstov, "A Special Matrix Transformation Semigroup of Primitive Pairs and the Genealogy of Pythagorean Triples", Matematicheskie Zametki, 2008, volume 84, number 2, pages 281-299 (in Russian), and Mathematical Notes, 2008, volume 84, number 2, pages 263-279 (in English) H. Lee Price, "The Pythagorean Tree: A New Species", 2008, L (version 2) =back Firstov finds this tree by semigroup transformations. Price finds it by expressing triples in certain "Fibonacci boxes" with a box of four values q',q,p,p' having p=q+q' and p'=p+q so each is the sum of the preceding two in a fashion similar to the Fibonacci sequence. A box where p and q have no common factor corresponds to a primitive triple. See L and L below. tree_type => "FB" Y=40 | 5 | | | | 17 Y=24 | 4 | | 8 | Y=12 | 2 6 | | 3 Y=4 | 1 | +---------------------------------------------- X=3 X=15 x=21 X=35 For a given box, three transformations can be applied to go to new boxes corresponding to new primitive triples. This visits all and only primitive triples, but in a different order to UAD above. The first point N=1 is again at X=3,Y=4, from which three further points N=2,3,4 are derived, then three more from each of those, etc. =cut # generated by tools/pythagorean-tree.pl =pod tree_type => "FB" coordinates A,B ______________ 3,4 _____________ / | \ 5,12 15,8 7,24 / | \ / | \ / | \ 9,40 35,12 11,60 21,20 55,48 39,80 13,84 63,16 15,112 =head2 UMT Tree XOption C "UMT"> is a third tree type by Firstov (reference above). It is matrices U, M2, and a new third T = M1*D. =cut # generated by tools/pythagorean-tree.pl =pod tree_type => "UMT" coordinates A,B children U,M2,T ______________ 3,4 _____________ / | \ 5,12 15,8 21,20 / | \ / | \ / | \ 7,24 35,12 65,72 33,56 55,48 45,28 39,80 91,60 105,88 The first "T" child 21,20 is the same as the "A" matrix, but it differs at further levels down. For example "T" twice is 105,88 (bottom most in the diagram) which is not the same as "A" twice 119,120. =head2 Digit Order Low to High Option C 'LtoH'> applies matrices using the ternary digits of N taken from low to high. The points in each row are unchanged, as is the parent-child N numbering, but the X,Y values are rearranged within the row. The UAD matrices send points to disjoint regions and the effect of LtoH is to keep the tree growing into those separate wedge regions. The arms grow roughly as follows =cut # math-image --path=PythagoreanTree,digit_order=LtoH --all --output=numbers_xy --size=75x14 =pod tree_type => "UAD", digit_order => "LtoH" Y=80 | 6 UAD LtoH | / | / Y=56 | / 7 10 9 | / / / / | / / | / 8 | / _/ / / / | / / / / / Y=24 | 5 / / | / _/ __--11 | / / _/ |/_/ __-- Y=20 | / / / __3 __-- _____----12 | |/_/ __-- __--- ____----- Y=12 | 2 __-- _/___---- ____13 | / __-- __-- _____----- | /_--_____---4----- Y=4 | 1--- | +-------------------------------------------------- X=3 X=15 X=20 X=35 X=76 Notice the points of the second row N=5 to N=13 are almost clockwise down from the Y axis, except N=8,9,10 go upwards. Those N=8,9,10 go upwards because the A matrix has a reflection (its determinant is -1). Option C "UArD"> reverses the tree underneath each A, and that plus LtoH gives A,B points going clockwise in each row. P,Q coordinates likewise go clockwise. =head2 AC Coordinates Option C 'AC'> gives the A and C legs of each triple as X=A,Y=C. coordinates => "AC" 85 | 122 10 | | 73 | 6 | 65 | 11 40 61 | 41 | | 7 | | 41 | 14 | 13 35 | | 3 25 | 5 | 17 | 4 13 | 2 | Y=5 | 1 | +------------------------------------------- X=3 7 9 21 35 45 55 63 77 Since AEC, the coordinates are XEY all above the X=Y diagonal. The L triples described above have C=A+2 so they are the points Y=X+2 just above the diagonal. For the FB tree the set of points visited is the same (of course), but a different N numbering. tree_type => "FB", coordinates => "AC" 85 | 11 35 | | 73 | 9 | 65 | 23 12 61 | 7 | | 17 | | 41 | 5 | 6 35 | | 8 25 | 4 | 17 | 3 13 | 2 | Y=5 | 1 | +------------------------------------------- X=3 7 9 21 35 45 55 63 77 =head2 BC Coordinates Option C 'BC'> gives the B and C legs of each triple as X=B,Y=C. This is the B=even and C=long legs of all primitive triples. This combination has points on 45-degree straight lines. coordinates => "BC" 101 | 121 97 | 12 | 89 | 8 85 | 10 122 | | 73 | 6 | 65 | 40 11 61 | 41 | | 7 | | 41 | 14 | 13 35 | | 3 25 | 5 | 17 | 4 13 | 2 | Y=5 | 1 | +-------------------------------------------------- X=4 12 24 40 60 84 Since BEC, the coordinates are XEY above the X=Y leading diagonal. N=1,2,5,14,41,etc along the X=Y-1 diagonal are the L triples described above which have C=B+1 and are at the start of each tree row. For the FB tree, the set of points visited is the same of course, but a different N numbering. tree_type => "FB", coordinates => "BC" 101 | 15 97 | 50 | 89 | 10 85 | 35 11 | | 73 | 9 | 65 | 12 23 61 | 7 | | 17 | | 41 | 5 | 6 35 | | 8 25 | 4 | 17 | 3 13 | 2 | Y=5 | 1 | +---------------------------------------------- X=4 12 24 40 60 84 As seen in the diagrams, B,C points fall on 45-degree straight lines going up from X=Y-1. This occurs because a primitive triple A,B,C with A odd and B even can be written A^2 = C^2 - B^2 = (C+B)*(C-B) Then gcd(A,B)=1 means also gcd(C+B,C-B)=1 and so since C+B and C-B have no common factor they must each be squares to give A^2. Call them s^2 and t^2, C+B = s^2 and conversely C = (s^2 + t^2)/2 C-B = t^2 B = (s^2 - t^2)/2 s = odd integer s >= 3 t = odd integer s > t >= 1 with gcd(s,t)=1 so that gcd(C+B,C-B)=1 When t=1, this is C=(s^2+1)/2 and B=(s^2-1)/2 which is the "U"-repeated points at Y=X+1 for each s. As t increases, the B,C coordinate combination makes a line upwards at 45-degrees from those t=1 positions, C + B = s^2 anti-diagonal 45-degrees, position along diagonal determined by t All primitive triples start from a C=B+1 with C=(s^2+1)/2 half an odd square, and go up from there. To ensure the triple is primitive, must have gcd(s,t)=1. Values of t where gcd(s,t)!=1 are gaps in the anti-diagonal lines. =head2 PQ Coordinates Primitive Pythagorean triples can be parameterized as follows for A odd and B even. This is per Euclid, Diophantus, and anonymous Arabic manuscript for constraining it to primitive triples (Dickson's I, start of chapter IV). A = P^2 - Q^2 B = 2*P*Q C = P^2 + Q^2 with P > Q >= 1, one odd, one even, and no common factor P = sqrt((C+A)/2) Q = sqrt((C-A)/2) The first P=2,Q=1 is the triple A=3,B=4,C=5. Option C 'PQ'> gives these as X=P,Y=Q, for either C. Because PEQE=1 the values fall in the eighth of the plane below the X=Y diagonal, =cut # math-image --path=PythagoreanTree,coordinates=PQ --all --output=numbers_xy --size=75x14 =pod tree_type => "UAD", coordinates => "PQ" 10 | 9842 9 | 3281 8 | 1094 23 7 | 365 32 6 | 122 38 5 | 41 8 4 | 14 11 12 15 3 | 5 6 16 2 | 2 3 7 10 22 1 | 1 4 13 40 121 Y=0 | +-------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 The diagonal N=1,2,5,14,41,etc is P=Q+1 as per L above. The one-to-one correspondence between P,Q and A,B means all tree types visit all P,Q pairs, so all X,Y with no common factor and one odd one even. There's other ways to iterate through such coprime pairs and any such method would generate Pythagorean triples too, in a different order from the trees here. The letters P and Q here are a little bit arbitrary. This parameterization is often written m,n or u,v but don't want "n" to be confused that with N point numbering or "u" to be confused with the U matrix (leg "A" is already too close to matrix "A"!). =head2 SM Coordinates Option C 'SM'> gives the small and medium legs from each triple as X=small,Y=medium. This is like "AB" except that if AEB then they're swapped to X=B,Y=A so XEY always. The effect is to mirror the AB points below the X=Y diagonal up to the upper half quadrant, coordinates => "SM" 91 | 16 84 | 122 | 8 | 10 72 | 12 | | 60 | 41 40 | 11 55 | 6 | | 7 40 | 14 | 35 | 13 | 24 | 5 21 | 3 | 12 | 2 4 | Y=4 | 1 | +---------------------------------------- X=3 8 20 33 48 60 65 =head2 SC Coordinates Option C 'SC'> gives the small leg and hypotenuse from each triple, coordinates => "SC" 85 | 122 10 | | 73 | 6 | | 40 11 61 | 41 | 53 | 7 | | 41 | 14 37 | 13 | | 3 25 | 5 | | 4 13 | 2 | Y=5 | 1 | +----------------------------- X=3 8 20 33 48 The points are all X E sqrt(2)*Y since with X as the smaller leg must have S Y^2/2> so S Y*1/sqrt(2)>. =head2 MC Coordinates Option C 'MC'> gives the medium leg and hypotenuse from each triple, coordinates => "MC" 65 | 11 40 61 | 41 | 53 | 7 | | 41 | 14 37 | 13 | 29 | 3 25 | 5 | 17 | 4 13 | 2 | Y=5 | 1 | +----------------------------------- X=4 15 24 35 40 56 63 The points are in a wedge sqrt(2)*Y E X E Y. X is the bigger leg and S Y^2/2> so S Y*1/sqrt(2)>. =cut # if A=B=C/sqrt(2) # A^2+B^2 = C^2/2+C^2/2 = C^2 # so X=Y/sqrt(2) = Y*0.7071 =pod =head2 UAD Coordinates AB, AC, PQ -- Turn Right In the UAD tree with coordinates AB, AC or PQ the path always turns to the right. For example in AB coordinates at N=2 the path turns to the right to go towards N=3. coordinates => "AB" 20 | 3 N X,Y | -- ------ 12 | 2 1 3,4 | 2 5,12 | 3 21,20 4 | 1 | turn towards the +------------------------- right at N=2 3 5 21 This can be proved from the transformations applied to seven cases, a triplet U,A,D, then four crossing a gap within a level, then two wrapping around at the end of a level. The initial N=1,2,3 can be treated as a wrap-around from the end of depth=0 (the last case D to U,A). U triplet U,A,D A D U.D^k.A crossing A,D to U U.D^k.D across U->A gap A.U^k.U k>=0 A.D^k.A crossing A,D to U A.D^k.D across A->D gap D.U^k.U k>=0 U.D^k.D crossing D to U,A U.U^k.U across U->A gap A.U^k.A k>=0 A.D^k.D crossing D to U,A A.U^k.U across A->D gap D.U^k.A k>=0 D^k .A wraparound A,D to U D^k .D k>=0 U^(k+1).U D^k wraparound D to U,A U^k.U k>=0 U^k.A (k=0 is initial N=1,N=2,N=3 for none,U,A) The powers U^k and D^k are an arbitrary number of descents U or D. In P,Q coordinates, these powers are U^k P,Q -> (k+1)*P-k*Q, k*P-(k-1)*Q D^k P,Q -> P+2k*Q, Q For AC coordinates, squaring to stretch to P^2,Q^2 doesn't change the turns. Then a rotate by -45 degrees to A=P^2-Q^2, C=P^2+Q^2 also doesn't change the turns. =head2 UAD Coordinates BC -- Turn Left In the UAD tree with coordinates BC the path always turns to the left. For example in BC coordinates at N=2 the path turns to the right to go towards N=3. coordinates => "BC" 29 | 3 N X,Y | -- ------ | 1 4,5 | 2 12,13 13 | 2 3 20,29 | 5 | 1 turn towards the | left at N=2 +--------------- 4 12 20 As per above, A,C turns to the right, which squared is A^2,C^2 to the right too, which equals C^2-B^2,C^2. Negating the X coordinate to B^2-C^2,C^2 mirrors to be a left turn always, and addition shearing to X+Y,Y doesn't change that, giving B^2,C^2 always left and so B,C always left. =cut # U P -> 2P-Q # Q -> P # # A P -> 2P+Q # Q -> P # # D P -> P+2Q # Q -> Q unchanged # # ------------------------------------ # none (P,Q) # U (2P-Q,P) dx1=P-Q dy1=P-Q # A (2P+Q,P) dx2=P+Q dy2=P-Q # dx2*dy1 - dx1*dy2 # = (P+Q)*(P-Q) - (P-Q)*(P-Q) # = (P-Q) * (P+Q - (P-Q)) # = (P-Q) * 2Q > 0 so Right # # ------------------------------------ # U (2P-Q,P) # A (2P+Q,P) dx1=2Q dy1=0 # D (P+2Q,Q) dx2=-P+3Q dy2=Q-P # dx2*dy1 - dx1*dy2 # = (-P+3Q)*0 - 2Q * (Q-P) # = 2Q*(P-Q) > 0 so Right # # ------------------------------------ # crossing A,D to U from gap U,A # U.D^k.A = (2*P-Q,P) . D^k . A # = (2*P-Q + 2*k*P, P) . A # = ((2*k+2)*P-Q, P) . A # = 2*((2*k+2)*P-Q) + P, (2*k+2)*P-Q # = (4*k+4)*P - 2*Q + P, (2*k+2)*P-Q # = (4*k+5)*P - 2*Q, (2*k+2)*P-Q # U.D^k.D = ((2*k+2)*P-Q, P) . D # = (2*k+2)*P-Q + 2*P, P # = (2*k+4)*P-Q, P # A.U^k.U = (2*P+Q, P) . U^(k+1) # = (k+2)*(2*P+Q) - (k+1)*P, (k+1)*(2*P+Q) - k*P # = (k+3)*P + (k+2)*Q, (k+2)*P + (k+1)*Q # dx1 = (2*k+4)*P-Q - ((4*k+5)*P - 2*Q) # dy1 = P - ((2*k+2)*P-Q) # dx2 = (k+3)*P + (k+2)*Q - ((4*k+5)*P - 2*Q) # dy2 = (k+2)*P + (k+1)*Q - ((2*k+2)*P-Q) # dx2*dy1 - dx1*dy2 # = 4*P^2*k^2 + (6*P^2 - 6*Q*P)*k + (2*P^2 - 4*Q*P + 2*Q^2) # = 4*P^2*k^2 + 6*P*(P-Q)*k + 2*(P-Q)^2 # > 0 turn right # # ------------------------------------ # wraparound A,D to U # D^k .A = (P+2kQ, Q) . A # = 2*(P+2*k*Q)+Q, P+2*k*Q # = 2*P+(4*k+1)*Q, P+2*k*Q # D^k .D = D^(k+1) = P+(2*k+2)*Q, Q # U^(k+1).U = U^(k+1) = (k+3)*P-(k+2)*Q, (k+2)*P-(k+1)*Q # dx1 = P+(2*k+2)*Q - (2*P+(4*k+1)*Q) # = -P + (-2*k+1)*Q # dy1 = Q - (P+2*k*Q) # = -P + (-2k+1)Q # dx2 = (k+3)*P-(k+2)*Q - (2*P+(4*k+1)*Q) # = (k+1)*P + (-5*k-3)*Q # dy2 = (k+2)*P-(k+1)*Q - (P+2*k*Q) # = (k+1)P + (-k-1 -2k)Q # = (k+1)*P + (-3k-1)*Q # dx2*dy1 - dx1*dy2 # = ((k+1)P + (-5k-3)Q) * (-P + (-2k+1)Q) - (-P + (-2k+1)) * ((k+1)P + (-3k-1)Q) # = (2*Q*k + 2*Q)*P + (4*Q^2*k^2 + 2*Q^2*k - 2*Q^2) # = (2*k + 2)*P*Q + (4*k^2 + 2*k - 2)*Q^2 # > 0 turn Right # # eg. P=2,Q=1 k=0 # D^k .A = 5,2 # D^k .D = 4,1 # U^k+1.U = 4,3 # dx1 = -1 # dy1 = -1 # dx2 = -1 # dy2 = 1 # dx2*dy1 - dx1*dy2 = 2 # # ------------------------------------ # wraparound D to U,A # D^k = P+2*k*Q, Q # U^k.U = U^(k+1) # = (k+2)*P-(k+1)*Q, (k+1)*P-k*Q # U^k.A = (k+1)*P-k*Q, k*P-(k-1)*Q . A # = 2*((k+1)*P-k*Q) + k*P-(k-1)*Q, (k+1)*P-k*Q # = (3*k+2)*P + (-3*k+1)*Q, (k+1)*P-k*Q # dx1 = (k+2)*P-(k+1)*Q - (P+2*k*Q) # = (k+1)*P + (-3*k-1)*Q # dy1 = (k+1)*P-k*Q - Q # = (k+1)*P-(k+1)*Q # dx2 = (3*k+2)*P + (-3*k+1)*Q - (P+2*k*Q) # = (3*k+1)*P + (-5*k+1)*Q # dy2 = (k+1)*P-k*Q - Q # = (k+1)*P-(k+1)*Q # dx2*dy1 - dx1*dy2 # = (2*P^2 - 4*Q*P + 2*Q^2)*k^2 + (2*P^2 - 2*Q*P)*k + (2*Q*P - 2*Q^2) # = 2*(P-Q)^2*k^2 + 2*P*(P-Q)*k + 2*Q*(P-Q) # > 0 turn Right # # eg. P=2;Q=1;k=1 # 4,1 # 4,3 # 8,3 # 2P-Q,P to 2P+Q,P to P+2Q,Q P>Q>=1 # # right at first "U" # 3P-2Q,2P-Q ----- 5P-2Q,2P-Q # | # | # 2P-Q,P ---- 2P+Q,P right at "A" # | / # | / # P,Q P+2Q,Q # # 3P+2Q,2P+Q # # # "U" 3P-2Q,2P-Q ----- 5P-2Q,2P-Q "A" # / # / # 4P-Q,P "D" # # # P,Q # # / U 4P-2Q-P,2P-Q = 3P-2Q,2P-Q # U 2P-Q,P -- A 4P-2Q+P,2P-Q = 5P-2Q,2P-Q # / \ D 2P-Q+2P,P = 4P-Q, P # / / U 4P+2Q-P,2P+Q = 3P+2Q,2P+Q # P,Q -- A 2P+Q,P -- A # \ \ D # \ / U # D P+2Q,Q -- A # \ D =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::PythagoreanTree-Enew ()> =item C<$path = Math::PlanePath::PythagoreanTree-Enew (tree_type =E $str, coordinates =E $str)> Create and return a new path object. The C option can be "UAD" (the default) "UArD" UAD with Gray code reflections "FB" "UMT" The C option can be "AB" odd, even legs (the default) "AC" odd leg, hypotenuse "BC" even leg, hypotenuse "PQ" "SM" small, medium legs "SC" small leg, hypotenuse "MC" medium leg, hypotenuse The C option can be "HtoL" high to low (the default) "LtoH" low to high (the default) =item C<$n = $path-En_start()> Return 1, the first N in the path. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 1 and if C<$nE1> then the return is an empty list. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. If there's nothing at C<$x,$y> then return C. The return is C if C<$x,$y> is not a primitive Pythagorean triple, per the C option. =item C<$rsquared = $path-En_to_radius ($n)> Return the radial distance R=sqrt(X^2+Y^2) of point C<$n>. If there's no point C<$n> then return C. For coordinates=AB or SM, this is the hypotenuse C and therefore an integer, for integer C<$n>. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> Return a range of N values which occur in a rectangle with corners at C<$x1>,C<$y1> and C<$x2>,C<$y2>. The range is inclusive. Both trees go off into large X,Y coordinates while yet to finish values close to the origin which means the N range for a rectangle can be quite large. For UAD, C<$n_hi> is roughly C<3^max(x/2)>, or for FB smaller at roughly C<3^log2(x)>. =back =head2 Descriptive Methods =over =item C<$x = $path-Ex_minimum()> =item C<$y = $path-Ey_minimum()> Return the minimum X or Y occurring in the path. The value goes according to the C option, coordinate minimum ---------- ------- A,S 3 B,M 4 C 5 P 2 Q 1 =back =head2 Tree Methods XEach point has 3 children, so the path is a complete ternary tree. =over =item C<@n_children = $path-Etree_n_children($n)> Return the three children of C<$n>, or an empty list if C<$n E 1> (ie. before the start of the path). This is simply C<3*$n-1, 3*$n, 3*$n+1>. This is appending an extra ternary digit 0, 1 or 2 to the mixed-radix form for N described above. Or staying all in ternary then appending to N+1 rather than N and adjusting back. =item C<$num = $path-Etree_n_num_children($n)> Return 3, since every node has three children, or return C if C<$nE1> (ie. before the start of the path). =item C<$n_parent = $path-Etree_n_parent($n)> Return the parent node of C<$n>, or C if C<$n E= 1> (the top of the tree). This is simply C, reversing the C calculation above. =item C<$depth = $path-Etree_n_to_depth($n)> Return the depth of node C<$n>, or C if there's no point C<$n>. The top of the tree at N=1 is depth=0, then its children depth=1, etc. The structure of the tree with 3 nodes per point means the depth is floor(log3(2N-1)), so for example N=5 through N=13 all have depth=2. =item C<$n = $path-Etree_depth_to_n($depth)> =item C<$n = $path-Etree_depth_to_n_end($depth)> Return the first or last N at tree level C<$depth> in the path, or C if nothing at that depth or not a tree. The top of the tree is depth=0. =back =head2 Tree Descriptive Methods =over =item C<$num = $path-Etree_num_children_minimum()> =item C<$num = $path-Etree_num_children_maximum()> Return 3 since every node has 3 children, making that both the minimum and maximum. =item C<$bool = $path-Etree_any_leaf()> Return false, since there are no leaf nodes in the tree. =back =head1 FORMULAS =head2 UAD Matrices Internally the code uses P,Q and calculates A,B at the end as necessary. The UAD transformations in P,Q coordinates are U P -> 2P-Q ( 2 -1 ) Q -> P ( 1 0 ) A P -> 2P+Q ( 2 1 ) Q -> P ( 1 0 ) D P -> P+2Q ( 1 2 ) Q -> Q unchanged ( 0 1 ) The advantage of P,Q for the calculation is that it's 2 values instead of 3. The transformations can be written with the 2x2 matrices shown, but explicit steps are enough for the code. Repeatedly applying "U" gives U 2P-Q, P U^2 3P-2Q, 2P-Q U^3 4P-3Q, 3P-2Q ... U^k (k+1)P-kQ, kP-(k-1)Q = P+k(P-Q), Q+k*(P-Q) If there's a run of k many high zeros in the Nrem = N-Nrow position in the level then they can be applied to the initial P=2,Q=1 as U^k P=k+2, Q=k+1 start for k high zeros =head2 FB Transformations The FB tree is calculated in P,Q and converted to A,B at the end as necessary. Its three transformations are M1 P -> P+Q ( 1 1 ) Q -> 2Q ( 0 2 ) M2 P -> 2P ( 2 0 ) Q -> P-Q ( 1 -1 ) M3 P -> 2P ( 2 0 ) Q -> P+Q ( 1 1 ) Price's paper shows rearrangements of a set of four values q',q,p,p'. Just the p and q are enough for the calculation. The set of four has some attractive geometric interpretations though. =head2 X,Y to N -- UAD C works in P,Q coordinates. An A,B or other input is converted to P,Q per the formulas in L above. P,Q can be reversed up the UAD tree to its parent point if P > 3Q reverse "D" P -> P-2Q digit=2 Q -> unchanged if P > 2Q reverse "A" P -> Q digit=1 Q -> P-2Q otherwise reverse "U" P -> Q digit=0 Q -> 2Q-P This gives a ternary digit 2, 1, 0 respectively from low to high. Those plus a high "1" bit make N. The number of steps is the "depth" level. If at any stage P,Q doesn't satisfy PEQE=1, one odd, the other even, then it means the original point, however it was converted, was not a primitive triple. For a primitive triple the endpoint is always P=2,Q=1. The conditions PE3Q or PE2Q work because each matrix sends its parent P,Q to one of three disjoint regions, Q P=Q P=2Q P=3Q | * U ---- A ++++++ | * ---- ++++++ | * ---- ++++++ | * ---- ++++++ | * ---- ++++++ | * ---- ++++++ | * ---- ++++++ D | * ----++++++ | * ----++++ | ----++ | +------------------------------------------------- P So U is the upper wedge, A the middle, and D the lower. The parent P,Q can be anywhere in PEQE=1, the matrices always map to these regions. =head2 X,Y to N -- FB After converting to P,Q as necessary, a P,Q point can be reversed up the FB tree to its parent if P odd reverse M1 P -> P-Q (Q even) Q -> Q/2 if P > 2Q reverse M2 P -> P/2 (P even) Q -> P/2 - Q otherwise reverse M3 P -> P/2 (P even) Q -> Q - P/2 This is a little like the binary greatest common divisor algorithm, but designed for one value odd and the other even. Like the UAD ascent above, if at any stage P,Q doesn't satisfy PEQE=1, one odd, the other even, then the initial point wasn't a primitive triple. The M1 reversal works because M1 sends any parent P,Q to a child which has P odd. All odd P,Q come from M1. The M2 and M3 always make children with P even. Those children are divided between two disjoint regions above and below the line P=2Q. Q P=Q P=2Q | * M3 P=even ---- | * ---- | * ---- | * ---- | * ---- M2 P=even | * ---- | * ---- | * ---- | * ---- M1 P=odd anywhere | ---- | +------------------------------------------------- P =head2 X,Y to N -- UMT After converting to P,Q as necessary, a P,Q point can be reversed up the UMT tree to its parent if P > 2Q reverse "U" P -> Q digit=0 Q -> 2Q-P if P even reverse "M2" P -> P/2 (Q odd) Q -> P/2 - Q otherwise reverse "T" P -> P - 3 * Q/2 (Q even) Q -> Q/2 These reversals work because U sends any parent P,Q to a child PE2Q whereas the M2 and T go below that line. M2 and T are distinguished by M2 giving P even whereas T gives P odd. Q P=Q P=2Q | * U ---- | * ---- | * ---- | * ---- | * ---- M2 for P=even | * ---- T for P=odd | * ---- | * ---- | * ---- | ---- | +------------------------------------------------- P =head2 Rectangle to N Range -- UAD For the UAD tree, the smallest A,B within each level is found at the topmost "U" steps for the smallest A or the bottom-most "D" steps for the smallest B. For example in the table above of level=2 N=5..13 the smallest A is the top A=7,B=24, and the smallest B is in the bottom A=35,B=12. In general Amin = 2*level + 1 Bmin = 4*level In P,Q coordinates the same topmost line is the smallest P and bottom-most the smallest Q. The values are Pmin = level+1 Qmin = 1 The fixed Q=1 arises from the way the "D" transformation sends Q-EQ unchanged, so every level includes a Q=1. This means if you ask what range of N is needed to cover all Q E someQ then there isn't one, only a P E someP has an N to go up to. =head2 Rectangle to N Range -- FB For the FB tree, the smallest A,B within each level is found in the topmost two final positions. For example in the table above of level=2 N=5..13 the smallest A is in the top A=9,B=40, and the smallest B is in the next row A=35,B=12. In general, Amin = 2^level + 1 Bmin = 2^level + 4 In P,Q coordinates a Q=1 is found in that second row which is the minimum B, and the smallest P is found by taking M1 steps half-way then a M2 step, then M1 steps for the balance. This is a slightly complicated Pmin = / 3*2^(k-1) + 1 if even level = 2*k \ 2^(k+1) + 1 if odd level = 2*k+1 Q = 1 The fixed Q=1 arises from the M1 steps giving P = 2 + 1+2+4+8+...+2^(level-2) = 2 + 2^(level-1) - 1 = 2^(level-1) + 1 Q = 2^(level-1) followed by M2 step Q -> P-Q = 1 As for the UAD above this means small Q's always remain no matter how big N gets, only a P range determines an N range. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include, =over L (etc) =back A007051 N start of depth=n, (3^n+1)/2, ie. tree_depth_to_n() A003462 N end of depth=n-1, (3^n-1)/2, ie. tree_depth_to_n_end() A000244 N of row middle line, 3^n A058529 possible values taken by abs(A-B), being integers with all prime factors == +/-1 mod 8 UAD Tree HtoL A321768 leg A A321769 leg B A321770 hypot C A321782 P (and the same for LtoH) A321783 Q A321784 P+Q A321785 P-Q UAD Tree A001542 row total p (even Pells) A001653 row total q (odd Pells) A001541 row total p + total q A002315 row total p - total q "U" repeatedly A046092 leg B, 2n(n+1) = 4*triangular numbers A099776 \ hypot C, being 2n(n+1)+1 A001844 / which is the "centred squares" "A" repeatedly A046727 \ leg A A084159 / "Pell oblongs" A046729 leg B A001653 hypot C, numbers n where 2*n^2-1 is square A000129 P and Q, the Pell numbers A001652 leg S, the smaller A046090 leg M, the bigger "D" repeatedly A000466 leg A, being 4*n^2-1 for n>=1 "M1" repeatedly A028403 leg B, binary 10..010..000 A007582 leg B/4, binary 10..010..0 A085601 hypot C, binary 10..010..001 "M2" repeatedly A015249 \ leg A, binary 111000111000... A084152 | A084175 / A054881 leg B, binary 1010..1010000..00 "M3" repeatedly A106624 P,Q pairs, 2^k-1,2^k "T" repeatedly A134057 leg A, binomial(2^n-1,2) binary 111..11101000..0001 A093357 leg B, binary 10111..111000..000 A052940 \ A055010 | P, 3*2^n-1 A083329 | binary 10111..111 A153893 / =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/CoprimeColumns.pm0000644000175000017500000003214713734026673020540 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=CoprimeColumns --all --scale=10 # math-image --path=CoprimeColumns --output=numbers --all package Math::PlanePath::CoprimeColumns; use 5.004; use strict; use vars '$VERSION', '@ISA', '@_x_to_n'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # uncomment this to run the ### lines # use Smart::Comments; use constant default_n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; use constant x_minimum => 1; use constant y_minimum => 1; use constant diffxy_minimum => 0; # octant Y<=X so X-Y>=0 use constant gcdxy_maximum => 1; # no common factor use constant dx_minimum => 0; use constant dx_maximum => 1; use constant dir_maximum_dxdy => (1,-1); # South-East use constant parameter_info_array => [ { name => 'direction', share_key => 'direction_updown', display => 'Direction', type => 'enum', default => 'up', choices => ['up','down'], choices_display => ['Down','Up'], description => 'Number points upwards or downwards in the columns.', }, Math::PlanePath::Base::Generic::parameter_info_nstart0(), ]; #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); $self->{'direction'} ||= 'up'; if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # shared with DiagonalRationals @_x_to_n = (0,0,1); sub _extend { ### _extend(): $#_x_to_n my $x = $#_x_to_n; push @_x_to_n, $_x_to_n[$x] + _totient($x); # if ($x > 2) { # if (($x & 3) == 2) { # $x >>= 1; # $next_n += $_x_to_n[$x] - $_x_to_n[$x-1]; # } else { # $next_n += # } # } ### last x: $#_x_to_n ### second last: $_x_to_n[$#_x_to_n-2] ### last: $_x_to_n[$#_x_to_n-1] ### diff: $_x_to_n[$#_x_to_n-1] - $_x_to_n[$#_x_to_n-2] ### totient of: $#_x_to_n - 2 ### totient: _totient($#_x_to_n-2) ### assert: $_x_to_n[$#_x_to_n-1] - $_x_to_n[$#_x_to_n-2] == _totient($#_x_to_n-2) } sub n_to_xy { my ($self, $n) = @_; ### CoprimeColumns n_to_xy(): $n $n = $n - $self->{'n_start'}; # to N=0 basis, and warn on undef # $n<-0.5 is ok for Math::BigInt circa Perl 5.12, it seems if (2*$n < -1) { return; } if (is_infinite($n)) { return ($n,$n); } my $frac; { my $int = int($n); $frac = $n - $int; # -.5 <= $frac < 1 $n = $int; # BigFloat int() gives BigInt, use that if (2*$frac >= 1) { $frac--; $n += 1; # now -.5 <= $frac < .5 } ### $n ### $frac ### assert: 2*$frac >= -1 ### assert: 2*$frac < 1 } my $x = 1; for (;;) { while ($x > $#_x_to_n) { _extend(); } if ($_x_to_n[$x] > $n) { $x--; last; } $x++; } $n -= $_x_to_n[$x]; ### $x ### n base: $_x_to_n[$x] ### n next: $_x_to_n[$x+1] ### remainder: $n my $y = 1; for (;;) { if (_coprime($x,$y)) { if (--$n < 0) { last; } } if (++$y >= $x) { ### oops, not enough in this column ... return; } } $y += $frac; if ($x >= 2 && $self->{'direction'} eq 'down') { $y = $x - $y; } return ($x, $y); } sub xy_is_visited { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 1 || $y < 1 || $y >= $x+($x==1) # Y= $x+($x==1) # Y= 2 && $self->{'direction'} eq 'down') { $y = $x - $y; } while ($#_x_to_n < $x) { _extend(); } my $n = $_x_to_n[$x]; ### base n: $n if ($y != 1) { foreach my $i (1 .. $y-1) { if (_coprime($x,$i)) { $n += 1; } } } return $n + $self->{'n_start'}; } # Asymptotically # phisum(x) ~ 1/(2*zeta(2)) * x^2 + O(x ln x) # = 3/pi^2 * x^2 + O(x ln x) # or by Walfisz # phisum(x) ~ 3/pi^2 * x^2 + O(x * (ln x)^(2/3) * (ln ln x)^4/3) # # but want an upper bound, so that for a given X at least enough N is # covered ... # # Note: DiagonalRationals depends on this working only to column resolution. # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### CoprimeColumns rect_to_n_range(): "$x1,$y1 $x2,$y2" ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; $x2 = round_nearest($x2); $y2 = round_nearest($y2); ### rounded ... ### $x2 ### $y2 if ($x2 < 1 || $y2 < 1 # bottom right corner above X=Y diagonal, except X=1,Y=1 included || ($y1 >= $x2 + ($x2 == 1))) { ### outside ... return (1, 0); } if (is_infinite($x2)) { return ($self->{'n_start'}, $x2); } while ($#_x_to_n <= $x2) { _extend(); } ### rect use xy_to_n at: "x=".($x2+1)." y=1" if ($x1 < 0) { $x1 = 0; } return ($_x_to_n[$x1] + $self->{'n_start'}, $_x_to_n[$x2+1] - 1 + $self->{'n_start'}); # asympototically ? # return ($self->{'n_start'}, $self->{'n_start'} + .304*$x2*$x2 + 20); } # A000010 sub _totient { my ($x) = @_; my $count = (1 # y=1 always + ($x > 2 && ($x&1)) # y=2 if $x odd + ($x > 3 && ($x % 3) != 0) # y=3 + ($x > 4 && ($x&1)) # y=4 if $x odd ); for (my $y = 5; $y < $x; $y++) { $count += _coprime($x,$y); } return $count; } # code here only uses X>=Y but allow for any X,Y>=0 for elsewhere sub _coprime { my ($x, $y) = @_; #### _coprime(): "$x,$y" if ($y > $x) { if ($x <= 1) { ### result yes ... return 1; } $y %= $x; } for (;;) { if ($y <= 1) { ### result: ($y == 1) return ($y == 1); } ($x,$y) = ($y, $x % $y); } } 1; __END__ =for stopwords Ryde coprime coprimes coprimeness totient totients Math-PlanePath Euler's onwards OEIS ie GCD =head1 NAME Math::PlanePath::CoprimeColumns -- coprime X,Y by columns =head1 SYNOPSIS use Math::PlanePath::CoprimeColumns; my $path = Math::PlanePath::CoprimeColumns->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path visits points X,Y which are coprime, ie. no common factor so gcd(X,Y)=1, in columns from Y=0 to YE=X. 13 | 63 12 | 57 11 | 45 56 62 10 | 41 55 9 | 31 40 54 61 8 | 27 39 53 7 | 21 26 30 38 44 52 6 | 17 37 51 5 | 11 16 20 25 36 43 50 60 4 | 9 15 24 35 49 3 | 5 8 14 19 29 34 48 59 2 | 3 7 13 23 33 47 1 | 0 1 2 4 6 10 12 18 22 28 32 42 46 58 Y=0| +--------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Since gcd(X,0)=0 the X axis itself is never visited, and since gcd(K,K)=K the leading diagonal X=Y is not visited except X=1,Y=1. The number of coprime pairs in each column is Euler's totient function phi(X). Starting N=0 at X=1,Y=1 means N=0,1,2,4,6,10,etc horizontally along row Y=1 are the cumulative totients i=K cumulative totient = sum phi(i) i=1 Anything making a straight line etc in the path will probably be related to totient sums in some way. The pattern of coprimes or not within a column is the same going up as going down, since X,X-Y has the same coprimeness as X,Y. This means coprimes occur in pairs from X=3 onwards. When X is even the middle point Y=X/2 is not coprime since it has common factor 2 from X=4 onwards. So there's an even number of points in each column from X=2 onwards and those cumulative totient totals horizontally along X=1 are therefore always even likewise. =head2 Direction Down Option C 'down'> reverses the order within each column to go downwards to the X axis. =cut # math-image --path=CoprimeColumns,direction=down --all --output=numbers --size=50x10 =pod direction => "down" 8 | 22 7 | 18 23 numbering 6 | 12 downwards 5 | 10 13 19 24 | 4 | 6 14 25 | 3 | 4 7 15 20 v 2 | 2 8 16 26 1 | 0 1 3 5 9 11 17 21 27 Y=0| +----------------------------- X=0 1 2 3 4 5 6 7 8 9 =head2 N Start The default is to number points starting N=0 as shown above. An optional C can give a different start with the same shape, For example to start at 1, =cut # math-image --path=CoprimeColumns,n_start=1 --all --output=numbers --size=50x16 =pod n_start => 1 8 | 28 7 | 22 27 6 | 18 5 | 12 17 21 26 4 | 10 16 25 3 | 6 9 15 20 2 | 4 8 14 24 1 | 1 2 3 5 7 11 13 19 23 Y=0| +------------------------------ X=0 1 2 3 4 5 6 7 8 9 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::CoprimeColumns-Enew ()> =item C<$path = Math::PlanePath::CoprimeColumns-Enew (direction =E $str, n_start =E $n)> Create and return a new path object. C (a string) can be "up" (the default) "down" =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =item C<$bool = $path-Exy_is_visited ($x,$y)> Return true if C<$x,$y> is visited. This means C<$x> and C<$y> have no common factor. This is tested with a GCD and is much faster than the full C. =back =head1 BUGS The current implementation is fairly slack and is slow on medium to large N. A table of cumulative totients is built and retained up to the highest X column number used. =head1 OEIS This pattern is in Sloane's Online Encyclopedia of Integer Sequences in a couple of forms, =over L (etc) =back n_start=0 (the default) A038567 X coordinate, reduced fractions denominator A020653 X-Y diff, fractions denominator by diagonals skipping N=0 initial 1/1 A002088 N on X axis, cumulative totient A127368 by columns Y coordinate if coprime, 0 if not A054521 by columns 1 if coprime, 0 if not A054427 permutation columns N -> RationalsTree SB N X/Y<1 A054428 inverse, SB X/Y<1 -> columns A121998 Y of skipped X,Y among 2<=Y<=X, those not coprime A179594 X column position of KxK square unvisited n_start=1 A038566 Y coordinate, reduced fractions numerator A002088 N on X=Y+1 diagonal, cumulative totient =head1 SEE ALSO L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/KochCurve.pm0000644000175000017500000006743113737207703017474 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=KochCurve --lines --scale=10 # math-image --path=KochCurve --all --scale=10 # continuous but nowhere differentiable # # Sur une Courbe Continue sans Tangente, Obtenue par une Construction # Géométrique Élémentaire # # http://www.nku.edu/~curtin/grenouille.html # http://www.nku.edu/~curtin/koch_171.jpg # # Cesàro, "Remarques sur la Courbe de von Koch." Atti della # R. Accad. della Scienze Fisiche e Matem. Napoli 12, No. 15, 1-12, # 1905. Reprinted as §228 in Opere scelte, a cura dell'Unione matematica # italiana e col contributo del Consiglio nazionale delle ricerche, Vol. 2: # Geometria, Analisi, Fisica Matematica. Rome: Edizioni Cremonese, # pp. 464-479, 1964. # # Thue-Morse count 1s mod 2 is net direction # Toeplitz first diffs is turn sequence +1 or -1 # # J. Ma and J.A. Holdener. When Thue-Morse Meets Koch. In Fractals: # Complex Geometry, Patterns, and Scaling in Nature and Society, volume 13, # pages 191-206, 2005. # http://personal.kenyon.edu/holdenerj/StudentResearch/WhenThueMorsemeetsKochJan222005.pdf # # F.M. Dekking. On the Distribution of Digits In Arithmetic Sequences. # In Seminaire de Theorie des Nombres de Bordeaux, volume 12, 1983, pages # 3201-3212, # package Math::PlanePath::KochCurve; use 5.004; use strict; use List::Util 'sum','first'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'round_up_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant diffxy_minimum => 0; # X>=Y octant so X-Y>=0 use constant dx_minimum => -2; use constant dx_maximum => 2; use constant dy_minimum => -1; use constant dy_maximum => 1; *_UNDOCUMENTED__dxdy_list = \&Math::PlanePath::_UNDOCUMENTED__dxdy_list_six; use constant absdx_minimum => 1; # never vertical use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ sub n_to_xy { my ($self, $n) = @_; ### KochCurve n_to_xy(): $n # secret negatives to -.5 if (2*$n < -1) { return; } if (is_infinite($n)) { return ($n,$n); } my $x; my $y; { my $int = int($n); $x = 2 * ($n - $int); # usually positive, but n=-0.5 gives x=-0.5 $y = $x * 0; # inherit possible bigrat 0 $n = $int; # BigFloat int() gives BigInt, use that } my $len = $y+1; # inherit bignum 1 foreach my $digit (digit_split_lowtohigh($n,4)) { ### at: "$x,$y digit=$digit" if ($digit == 0) { } elsif ($digit == 1) { ($x,$y) = (($x-3*$y)/2 + 2*$len, # rotate +60 ($x+$y)/2); } elsif ($digit == 2) { ($x,$y) = (($x+3*$y)/2 + 3*$len, # rotate -60 ($y-$x)/2 + $len); } else { ### assert: $digit==3 $x += 4*$len; } $len *= 3; } ### final: "$x,$y" return ($x,$y); } sub xy_to_n { my ($self, $x, $y) = @_; ### KochPeaks xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($y < 0 || $x < 0 || (($x ^ $y) & 1)) { ### neg y or parity different ... return undef; } my ($len,$level) = round_down_pow(($x/2)||1, 3); ### $level ### $len if (is_infinite($level)) { return $level; } my $n = 0; foreach (0 .. $level) { $n *= 4; ### at: "level=$level len=$len x=$x,y=$y n=$n" if ($x < 3*$len) { if ($x < 2*$len) { ### digit 0 ... } else { ### digit 1 ... $x -= 2*$len; ($x,$y) = (($x+3*$y)/2, # rotate -60 ($y-$x)/2); $n += 1; } } else { $x -= 4*$len; ### digit 2 or 3 to: "x=$x" if ($x < $y) { # before diagonal ### digit 2... $x += $len; $y -= $len; ($x,$y) = (($x-3*$y)/2, # rotate +60 ($x+$y)/2); $n += 2; } else { #### digit 3... $n += 3; } } $len /= 3; } ### end at: "x=$x,y=$y n=$n" if ($x != 0 || $y != 0) { return undef; } return $n; } # level extends to x= 2*3^level # level = log3(x/2) # # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### KochCurve rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } if ($x2 < 0 || $y2 < 0 || 3*$y1 > $x2 ) { # above line Y=X/3 return (1,0); } # \ # \ # * \ # / \ \ # o-+-* *-+-e \ # 0 3 6 # # 3*Y+X/2 - (Y!=0) # # / # *-+-* # \ # * * # / \ / # o-+-* *-+-* # 0 3 6 X/2 # my ($len, $level) = round_down_pow ($x2/2, 3); return _rect_to_n_range_rot ($len, $level, 0, $x1,$y1, $x2,$y2); # (undef, my $level) = round_down_pow ($x2/2, 3); # ### $level # return (0, 4**($level+1)-1); } my @dir6_to_dx = (2, 1,-1,-2, -1, 1); my @dir6_to_dy = (0, 1, 1, 0, -1,-1); my @max_digit_to_rot = (1, -2, 1, 0); my @min_digit_to_rot = (0, 1, -2, 1); my @max_digit_to_offset = (-1, -1, -1, 2); sub _rect_to_n_range_rot { my ($initial_len, $level_max, $initial_rot, $x1,$y1, $x2,$y2) = @_; ### KochCurve _rect_to_n_range_rot(): "$x1,$y1 $x2,$y2 len=$initial_len level=$level_max rot=$initial_rot" my ($rot, $len, $x, $y); my $overlap = sub { ### overlap: "$x,$y len=$len rot=$rot" if ($len == 1) { return ($x >= $x1 && $x <= $x2 && $y >= $y1 && $y <= $y2); } my $len = $len / 3; if ($rot < 3) { if ($rot == 0) { # * # / \ # o-+-* *-+-. return ($y <= $y2 # bottom before end && $y+$len >= $y1 && $x <= $x2 && $x+6*$len > $x1); # right before end, exclusive } elsif ($rot == 1) { # . # / # *-+-* # \ # * +----- # / |x1,y2 # o return ($x <= $x2 # left before end && $y+3*$len > $y1 # top after start, exclusive && $y-$x <= $y2-$x1); # diag before corner } else { # . |x1,y1 # \ +----- # * # / # *-+-* # \ # o return ($y <= $y2 # bottom before end && $x-3*$len <=$x2 # left before end && $y+$x >= $y1+$x1); # diag after corner } } else { if ($rot == 3) { # .-+-* *-+-o # \ / # * return ($y >= $y1 # top after start && $y-$len <= $y2 # bottom before end && $x >= $x1 # right after start && $x-6*$len < $x2); # left before end, exclusive } elsif ($rot == 4) { # x2,y1| o # -----+ / # * # \ # *-+-* # / # . return ($x >= $x1 # right after start && $y-3*$len < $y2 # bottom before end, exclusive && $y-$x >= $y1-$x2); # diag after corner } else { # o # \ # *-+-* # / # * # -----+ \ # x2,y2| . return ($y >= $y1 # top after start && $x+3*$len >= $x1 # right after start && $y+$x <= $y2+$x2); # diag before corner } } }; my $zero = 0*$x1*$x2*$y1*$y2; my @lens = ($initial_len); my $n_hi; $rot = $initial_rot; $len = $initial_len; $x = $zero; $y = $zero; my @digits = (4); for (;;) { my $digit = --$digits[-1]; ### max at: "digits=".join(',',@digits)." xy=$x,$y len=$len" if ($digit < 0) { pop @digits; if (! @digits) { ### nothing found to level_max ... return (1, 0); } ### end of digits, backtrack ... $len = $lens[$#digits]; next; } my $offset = $max_digit_to_offset[$digit]; $rot = ($rot - $max_digit_to_rot[$digit]) % 6; $x += $dir6_to_dx[$rot] * $offset * $len; $y += $dir6_to_dy[$rot] * $offset * $len; ### $offset ### $rot if (&$overlap()) { if ($#digits >= $level_max) { ### yes overlap, found n_hi ... ### digits: join(',',@digits) ### n_hi: _digit_join_hightolow (\@digits, 4, $zero) $n_hi = _digit_join_hightolow (\@digits, 4, $zero); last; } ### yes overlap, descend ... push @digits, 4; $len = ($lens[$#digits] ||= $len/3); } else { ### no overlap, next digit ... } } $rot = $initial_rot; $x = $zero; $y = $zero; $len = $initial_len; @digits = (-1); for (;;) { my $digit = ++$digits[-1]; ### min at: "digits=".join(',',@digits)." xy=$x,$y len=$len" if ($digit > 3) { pop @digits; if (! @digits) { ### oops, n_lo not found to level_max ... return (1, 0); } ### end of digits, backtrack ... $len = $lens[$#digits]; next; } ### $digit ### rot increment: $min_digit_to_rot[$digit] $rot = ($rot + $min_digit_to_rot[$digit]) % 6; if (&$overlap()) { if ($#digits >= $level_max) { ### yes overlap, found n_lo ... ### digits: join(',',@digits) ### n_lo: _digit_join_hightolow (\@digits, 4, $zero) return (_digit_join_hightolow (\@digits, 4, $zero), $n_hi); } ### yes overlap, descend ... push @digits, -1; $len = ($lens[$#digits] ||= $len/3); } else { ### no overlap, next digit ... $x += $dir6_to_dx[$rot] * $len; $y += $dir6_to_dy[$rot] * $len; } } } # $aref->[0] high digit sub _digit_join_hightolow { my ($aref, $radix, $zero) = @_; my @lowtohigh = reverse @$aref; return digit_join_lowtohigh(\@lowtohigh, $radix, $zero); } my @digit_to_dir = (0, 1, -1, 0); my @digit_to_nextturn = (1, # digit=1 (with +1 for "next" N) -2, # digit=2 1); # digit=3 sub n_to_dxdy { my ($self, $n) = @_; ### n_to_dxdy(): $n if ($n < 0) { return; # first direction at N=0 } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; my @ndigits = digit_split_lowtohigh($int,4); my $dir6 = sum(0, map {$digit_to_dir[$_]} @ndigits) % 6; my $dx = $dir6_to_dx[$dir6]; my $dy = $dir6_to_dy[$dir6]; if ($n) { # fraction part # lowest non-3 digit, or zero if all 3s (0 above high digit) $dir6 += $digit_to_nextturn[ first {$_!=3} @ndigits, 0 ]; $dir6 %= 6; $dx += $n*($dir6_to_dx[$dir6] - $dx); $dy += $n*($dir6_to_dy[$dir6] - $dy); } return ($dx, $dy); } sub _UNTESTED__n_to_dir6 { my ($self, $n) = @_; if ($n < 0) { return undef; # first direction at N=0 } if (is_infinite($n)) { return ($n,$n); } return (sum (map {$digit_to_dir[$_]} digit_split_lowtohigh($n,4)) || 0) # if empty % 6; } my @n_to_turn6 = (undef, 1, # +60 degrees -2, # -120 degrees 1); # +60 degrees sub _UNTESTED__n_to_turn6 { my ($self, $n) = @_; if (is_infinite($n)) { return undef; } while ($n) { my $digit = _divrem_mutate($n,4); if ($digit) { # lowest non-zero digit return $n_to_turn6[$digit]; } } return 0; } sub _UNTESTED__n_to_turn_LSR { my ($self, $n) = @_; my $turn6 = $self->_UNTESTED__n_to_turn6($n) || return undef; return ($turn6 > 0 ? 1 : -1); } sub _UNTESTED__n_to_turn_left { my ($self, $n) = @_; my $turn6 = $self->_UNTESTED__n_to_turn6($n) || return undef; return ($turn6 > 0 ? 1 : 0); } sub _UNTESTED__n_to_turn_right { my ($self, $n) = @_; my $turn6 = $self->_UNTESTED__n_to_turn6($n) || return undef; return ($turn6 < 0 ? 1 : 0); } #------------------------------------------------------------------------------ # levels sub level_to_n_range { my ($self, $level) = @_; return (0, 4**$level); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($pow, $exp) = round_up_pow ($n, 4); return $exp; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Helge von Koch Math-PlanePath Nlevel differentiable ie OEIS Xlevel floorlevel Nhi Nlo Ndigit Une thode trique mentaire tude de Certaines orie des Courbes Acta Arithmetica =head1 NAME Math::PlanePath::KochCurve -- horizontal Koch curve =head1 SYNOPSIS use Math::PlanePath::KochCurve; my $path = Math::PlanePath::KochCurve->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis is an integer version of the self-similar Koch curve, =over 4 Helge von Koch, "Une ME<233>thode GE<233>omE<233>trique E<201>lE<233>mentaire pour l'E<201>tude de Certaines Questions de la ThE<233>orie des Courbes Planes", Acta Arithmetica, volume 30, 1906, pages 145-174. L =back It goes along the X axis and makes triangular excursions upwards. 8 3 / \ 6---- 7 9----10 18-... 2 \ / \ 2 5 11 14 17 1 / \ / \ / \ / 0----1 3---- 4 12----13 15----16 <- Y=0 ^ X=0 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 The replicating shape is the initial N=0 to N=4, * / \ *---* *---* which is rotated and repeated 3 times in the same pattern to give sections N=4 to N=8, N=8 to N=12, and N=12 to N=16. Then that N=0 to N=16 is itself replicated three times at the angles of the base pattern, and so on infinitely. The X,Y coordinates are arranged on a square grid using every second point, per L. The result is flattened triangular segments with diagonals at a 45 degree angle. =head2 Level Ranges Each replication adds 3 copies of the existing points and is thus 4 times bigger, so if N=0 to N=4 is reckoned as level 1 then a given replication level goes from Nstart = 0 Nlevel = 4^level (inclusive) Each replication is 3 times the width. The initial N=0 to N=4 figure is 6 wide and in general a level runs from Xstart = 0 Xlevel = 2*3^level at N=Nlevel The highest Y is 3 times greater at each level similarly. The peak is at the midpoint of each level, Npeak = (4^level)/2 Ypeak = 3^level Xpeak = 3^level It can be seen that the N=6 point backtracks horizontally to the same X as the start of its section N=4 to N=8. This happens in the further replications too and is the maximum extent of the backtracking. The Nlevel is multiplied by 4 to get the end of the next higher level. The same 4*N can be applied to all points N=0 to N=Nlevel to get the same shape but a factor of 3 bigger X,Y coordinates. The in-between points 4*N+1, 4*N+2 and 4*N+3 are then new finer structure in the higher level. =head2 Fractal Koch conceived the curve as having a fixed length and infinitely fine structure, making it continuous everywhere but differentiable nowhere. The code here can be pressed into use for that sort of construction for a given level of granularity by scaling X/3^level Y/3^level which makes it a fixed 2 wide by 1 high. Or for unit-side equilateral triangles then apply further factors 1/2 and sqrt(3)/2, as noted in L. (X/2) / 3^level (Y*sqrt(3)/2) / 3^level =head2 Area The area under the curve to a given level can be calculated from its self-similar nature. The curve at level+1 is 3 times wider and higher and adds a triangle of unit area onto each line segment. So reckoning the line segment N=0 to N=1 as level=0 (which is area[0]=0), area[level] = 9*area[level-1] + 4^(level-1) = 4^(level-1) + 9*4^(level-2) + ... + 9^(level-1)*4^0 9^level - 4^level = ----------------- 5 = 0, 1, 13, 133, 1261, 11605, 105469, ... (A016153) The sides are 6 different angles. The triangles added on the sides are always the same shape either pointing up or down. Base width=2 and height=1 gives area=1. * *-----* ^ / \ \ / | height=1 / \ \ / | *-----* * v triangle area = 2*1/2 = 1 <-----> width=2 If the Y coordinates are stretched to make equilateral triangles then the number of triangles is not changed and so the area increases by a factor of the area of the equilateral triangle, sqrt(3)/4. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::KochCurve-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =item C<$n = $path-En_start()> Return 0, the first N in the path. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 4**$level)>. =back =head1 FORMULAS =head2 N to Turn The curve always turns either +60 degrees or -120 degrees, it never goes straight ahead. In the base 4 representation of N, the lowest non-zero digit gives the turn. The first turn is at N=1 so there's always a non-zero digit in N. low digit base 4 turn --------- ------------ 1 +60 degrees (left) 2 -120 degrees (right) 3 +60 degrees (left) For example N=8 is 20 base 4, so lowest nonzero "2" means turn -120 degrees for the next segment. If the least significant digit is non-zero then it determines the turn, making the base N=0 to N=4 shape. If the least significant is zero then the next level up is in control, eg. N=0,4,8,12,16, making a turn according to the base shape again at that higher level. The first and last segments of the base shape are "straight" so there's no extra adjustment to apply in those higher digits. This base 4 digit rule is equivalent to counting low 0-bits. A low base-4 digit 1 or 3 is an even number of low 0-bits and a low digit 2 is an odd number of low 0-bits. count low 0-bits turn ---------------- ------------ even +60 degrees (left) odd -120 degrees (right) For example N=8 in binary "1000" has 3 low 0-bits and 3 is odd so turn -120 degrees (right). See L for a similar turn sequence arising from binary Gray code. =head2 N to Next Turn The turn at N+1, ie the next turn, can be found from the base-4 digits by considering how the digits of N change when 1 is added, and the low-digit turn calculation is applied on those changed digits. Adding 1 means low digit 0, 1 or 2 will become non-zero. Any low 3s wrap around to become low 0s. So the turn at N+1 can be found from the digits of N by seeking the lowest non-3 lowest non-3 turn digit of N at N+1 ------------ ------------ 0 +60 degrees (left) 1 -120 degrees (right) 2 +60 degrees (left) =head2 N to Direction The total turn at a given N can be found by counting digits 1 and 2 in base 4. direction = ((count of 1-digits in base 4) - (count of 2-digits in base 4)) * 60 degrees For example N=11 is "23" in base 4, so 60*(0-1) = -60 degrees. In this formula the count of 1s and 2s can go past 360 degrees, representing a spiralling around which occurs at progressively higher replication levels. The direction can be taken mod 360 degrees, or the count mod 6, for a direction 0 to 5 if desired. =head2 N to abs(dX),abs(dY) The direction expressed as abs(dX) and abs(dY) can be calculated simply from N modulo 3. abs(dX) is a repeating pattern 2,1,1 and abs(dY) repeating 0,1,1. N mod 3 abs(dX),abs(dY) ------- --------------- 0 2,0 horizontal, East or West 1 1,1 slope North-East or South-West 2 1,1 slope North-West or South-East This works because the direction calculation above corresponds to N mod 3. Each N digit in base 4 becomes N digit base 4 direction add ------- ------------- 0 0 1 1 2 -1 3 0 Notice that direction == Ndigit mod 3. Then because 4==1 mod 3 the power-of-4 for each digit reduces down to 1, N = 4^k * digit_k + ... 4^0 * digit_0 N mod 3 = 1 * digit_k + ... 1 * digit_0 = digit_k + ... digit_0 same as direction = digit_k + ... + digit_0 taken mod 3 =head2 Rectangle to N Range -- Level An easy over-estimate of the N values in a rectangle can be had from the Xlevel formula above. If XlevelErectangleX then Nlevel is past the rectangle extent. X = 2*3^level so floorlevel = floor log_base_3(X/2) Nhi = 4^(floorlevel+1) - 1 For example a rectangle extending to X=13 has floorlevel = floor(log3(13/2))=1 and so Nhi=4^(1+1)-1=15. The rounding-down of the log3 ensures a point such as X=18 which is the first in the next Nlevel will give that next level. So floorlevel=log3(18/2)=2 (exactly) and Nhi=4^(2+1)-1=63. The worst case for this over-estimate is when rectangleX==Xlevel, ie. the rectangle is just into the next level. In that case Nhi is nearly a factor 4 bigger than it needs to be. =head2 Rectangle to N Range -- Exact The exact Nlo and Nhi in a rectangle can be found by searching along the curve. For Nlo search forward from the origin N=0. For Nhi search backward from the Nlevel over-estimate described above. At a given digit position in the prospective N the sub-part of the curve comprising the lower digits has a certain triangular extent. If it's outside the target rectangle then step to the next digit value, and to the next of the digit above when past digit=3 (or below digit=0 when searching backwards). There's six possible orientations for the curve sub-part. In the following pictures "o" is the start and the surrounding lines show the triangular extent. There's just four curve parts shown in each, but these triangles bound a sub-curve of any level. rot=0 -+- +-----------------+ -- -- - .-+-* *-+-o - -- * -- -- \ / -- -- / \ -- -- * -- - o-+-* *-+-. - -- -- +-----------------+ rot=3 -+- rot=1 +---------+ rot=4 /+ | . / / | | / / / o| |*-+-* / / / | | \ / / * | | * / / \ | | / / / *-+-*| |o / / / | | / / . | +/ +---------+ +\ rot=2 +---------+ | \ \ o | |. \ \ \ | | \ \ \ *-+-*| | * \ \ / | | / \ \ * | |*-+-* \ \ \ | | \ \ \ .| | o \ rot=5 \ | +---------+ \+ The "." is the start of the next sub-curve. It belongs to the next digit value and so can be excluded. For rot=0 and rot=3 this means simply shortening the X range permitted. For rot=1 and rot=4 similarly the Y range. For rot=2 and rot=5 it would require a separate test. Tight sub-part extent checking reduces the sub-parts which are examined, but it works perfectly well with a looser check, such as a square box for the sub-curve extents. Doing that might be easier if the target region is not a rectangle but instead some trickier shape. =head1 OEIS The Koch curve is in Sloane's Online Encyclopedia of Integer Sequences in various forms, =over L (etc) =back A335358 (X-Y)/2 diagonal coordinate A335359 Y coordinate A035263 turn 1=left,0=right, by morphism A096268 turn 0=left,1=right, period doubling sequence A056832 turn 1=left,2=right, by replicate and flip last A309873 turn 1=left,-1=right A029883 turn +/-1=left,0=right, Thue-Morse first differences A089045 turn +/-1=left,0=right, by +/- something A177702 abs(dX) from N=1 onwards, being 1,1,2 repeating A011655 abs(dY), being 0,1,1 repeating A003159 N positions of left turns, ending even number 0 bits A036554 N positions of right turns, ending odd number 0 bits A332206 N on X axis A001196 N segments on X axis (N and N+1 on X axis) A065359 segment direction, *60 degrees A229216 segment direction, 1,2,3,-1,-2,-3 A050292 num left turns 1 to N A123087 num right turns 1 to N A020988 num left turns 1 to 4^k-1, being 2*(4^k-1)/3 A002450 num right turns 1 to 4^k-1, being (4^k-1)/3 A016153 area under the curve, (9^k-4^k)/5 For reference, A217586 is not quite the same as A096268 right turn. A217586 differs by a 0E-E1 flip at N=2^k due to different initial a(1)=1. =head1 SEE ALSO L, L, L, L, L, L L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/FractionsTree.pm0000644000175000017500000003370213734026671020345 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # PowerPart has mostly square-free for X/Y > 1/2, then wedge of mostly # multiple of 4, then mostly multiple of 16, then wedge of higher powers # of 2. Similar in AYT. package Math::PlanePath::FractionsTree; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'bit_split_lowtohigh', 'digit_join_lowtohigh'; use Math::PlanePath::RationalsTree; use Math::PlanePath::CoprimeColumns; *_coprime = \&Math::PlanePath::CoprimeColumns::_coprime; # uncomment this to run the ### lines #use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant x_minimum => 1; use constant y_minimum => 2; use constant diffxy_maximum => -1; # upper octant X<=Y-1 so X-Y<=-1 use constant gcdxy_maximum => 1; # no common factor use constant tree_num_children_list => (2); # complete binary tree use constant tree_n_to_subheight => undef; # complete tree, all infinity use constant parameter_info_array => [ { name => 'tree_type', share_key => 'tree_type_fractionstree', display => 'Tree Type', type => 'enum', default => 'Kepler', choices => ['Kepler'], }, ]; use constant dir_maximum_dxdy => (-2, -(sqrt(5)+1)); # phi #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'tree_type'} ||= 'Kepler'; $self->{'n_start'} = 1; # for RationalsTree sharing return $self; } sub n_to_xy { my ($self, $n) = @_; ### FractionsTree n_to_xy(): "$n" if ($n < 1) { return; } if (is_infinite($n)) { return ($n,$n); } # what to do for fractional $n? { my $int = int($n); if ($n != $int) { ### frac ... my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; ### x1,y1: "$x1, $y1" ### x2,y2: "$x2, $y2" ### dx,dy: "$dx, $dy" ### result: ($frac*$dx + $x1).', '.($frac*$dy + $y1) return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } my $zero = ($n * 0); # inherit bignum 0 my $one = $zero + 1; # inherit bignum 1 # my $tree_type = $self->{'tree_type'}; # if ($tree_type eq 'Kepler') { ### Kepler tree ... # X/Y # / \ # X/(X+Y) Y/(X+Y) # # (1 0) (x) = ( x ) (a b) (1 0) = (a+b b) digit 0 # (1 1) (y) (x+y) (c d) (1 1) (c+d d) # # (0 1) (x) = ( y ) (a b) (0 1) = (b a+b) digit 1 # (1 1) (y) (x+y) (c d) (1 1) (d c+d) my @bits = bit_split_lowtohigh($n); pop @bits; # drop high 1 bit my $a = $one; # initial (1 0) my $b = $zero; # (0 1) my $c = $zero; my $d = $one; while (@bits) { ### at: "($a $b)" ### at: "($c $d)" ### $digit if (shift @bits) { # low to high ($a,$b) = ($b, $a+$b); ($c,$d) = ($d, $c+$d); } else { $a += $b; $c += $d; } } ### final: "($a $b)" ### final: "($c $d)" # (a b) (1) = (a+b) # (c d) (2) (c+d) return ($a+2*$b, $c+2*$d); } } sub xy_is_visited { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 1 || $y < 2 || $x >= $y || ! _coprime($x,$y)) { return 0; } return 1; } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### FractionsTree xy_to_n(): "$x,$y $self->{'tree_type'}" if ($x < 1 || $y < 2 || $x >= $y) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my $zero = $x * 0 * $y; # inherit bignum 0 # X/Y # / \ # X/(X+Y) Y/(X+Y) # # (x,y) <- (x, y-x) nbit 0 # (x,y) <- (y-x, x) nbit 1 # my @nbits; # low to high for (;;) { ### at: "$x,$y n=$n" if ($y <= 2) { if ($x == 1 && $y == 2) { push @nbits, 1; # high bit return digit_join_lowtohigh(\@nbits, 2, $zero); } else { return undef; } } ($y -= $x) # (X,Y) <- (X, Y-X) || return undef; # common factor if had X==Y if ($x > $y) { ($x,$y) = ($y,$x); push @nbits, 1; } else { push @nbits, 0; } } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### rect_to_n_range() $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### $x2 ### $y2 # | / # | / x1 # | / +-----y2 # | / | # |/ +----- # if ($x2 < 1 || $y2 < 2 || $x1 >= $y2) { ### no values, rect outside upper octant ... return (1,0); } my $zero = ($x1 * 0 * $y1 * $x2 * $y2); # inherit bignum ### $zero if ($x2 >= $y2) { $x2 = $y2-1; } if ($x1 < 1) { $x1 = 1; } if ($y1 < 2) { $y1 = 2; } # big x2, small y1 # big y2, small x1 # my $level = _bingcd_max ($y2,$x1); ### $level my $level = $y2; return (1, ($zero+2) ** $level); } sub _bingcd_max { my ($x,$y) = @_; ### _bingcd_max(): "$x,$y" if ($x < $y) { ($x,$y) = ($y,$x) } ### div: int($x/$y) ### bingcd: int($x/$y) + $y return int($x/$y) + $y + 1; } #------------------------------------------------------------------------------ use constant tree_num_roots => 1; # Same structure as RationalsTree *tree_n_children = \&Math::PlanePath::RationalsTree::tree_n_children; *tree_n_num_children = \&Math::PlanePath::RationalsTree::tree_n_num_children; *tree_n_parent = \&Math::PlanePath::RationalsTree::tree_n_parent; *tree_n_to_depth = \&Math::PlanePath::RationalsTree::tree_n_to_depth; *tree_depth_to_n = \&Math::PlanePath::RationalsTree::tree_depth_to_n; *tree_depth_to_n_end = \&Math::PlanePath::RationalsTree::tree_depth_to_n_end; *tree_depth_to_n_range=\&Math::PlanePath::RationalsTree::tree_depth_to_n_range; *tree_depth_to_width = \&Math::PlanePath::RationalsTree::tree_depth_to_width; 1; __END__ =for stopwords eg Ryde OEIS ie Math-PlanePath coprime Harmonices Mundi octant onwards Aiton =head1 NAME Math::PlanePath::FractionsTree -- fractions by tree =head1 SYNOPSIS use Math::PlanePath::FractionsTree; my $path = Math::PlanePath::FractionsTree->new (tree_type => 'Kepler'); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path enumerates fractions X/Y in the range 0 E X/Y E 1 and in reduced form, ie. X and Y having no common factor, using a method by Johannes Kepler. Fractions are traversed by rows of a binary tree which effectively represents a coprime pair X,Y by subtraction steps of a subtraction-only form of Euclid's greatest common divisor algorithm which would prove X,Y coprime. The steps left or right are encoded/decoded as an N value. =head2 Kepler Tree XThe default and only tree currently is by Kepler. =over Johannes Kepler, "Harmonices Mundi", Book III. Excerpt of translation by Aiton, Duncan and Field at L =back In principle similar bit reversal etc variations as in C would be possible. N=1 1/2 ------ ------ N=2 to N=3 1/3 2/3 / \ / \ N=4 to N=7 1/4 3/4 2/5 3/5 | | | | | | | | N=8 to N=15 1/5 4/5 3/7 4/7 2/7 5/7 3/8 5/8 A node descends as X/Y / \ X/(X+Y) Y/(X+Y) Kepler described the tree as starting at 1, ie. 1/1, which descends to two identical 1/2 and 1/2. For the code here a single copy starting from 1/2 is used. Plotting the N values by X,Y is as follows. Since it's only fractions X/YE1, ie. XEY, all points are above the X=Y diagonal. The unused X,Y positions are where X and Y have a common factor. For example X=2,Y=6 have common factor 2 so is never reached. 12 | 1024 26 27 1025 11 | 512 48 28 22 34 35 23 29 49 513 10 | 256 20 21 257 9 | 128 24 18 19 25 129 8 | 64 14 15 65 7 | 32 12 10 11 13 33 6 | 16 17 5 | 8 6 7 9 4 | 4 5 3 | 2 3 2 | 1 1 | Y=0 | ---------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 The X=1 vertical is the fractions 1/Y at the left end of each tree row, which is Nstart=2^level The diagonal X=Y-1, fraction K/(K+1), is the second in each row, at N=Nstart+1. That's the maximum X/Y in each level. The N values in the upper octant, ie. above the line Y=2*X, are even and those below that line are odd. This arises since XEY so the left leg X/(X+Y) E 1/2 and the right leg Y/(X+Y) E 1/2. The left is an even N and the right an odd N. =head1 FUNCTIONS See L for behaviour common to all path classes. =over =item C<$path = Math::PlanePath::FractionsTree-Enew ()> Create and return a new path object. =item C<$n = $path-En_start()> Return 1, the first N in the path. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> Return a range of N values which occur in a rectangle with corners at C<$x1>,C<$y1> and C<$x2>,C<$y2>. The range is inclusive. For reference, C<$n_hi> can be quite large because within each row there's only one new 1/Y fraction. So if X=1 is included then roughly C<$n_hi = 2**max(x,y)>. =back =head2 Tree Methods XEach point has 2 children, so the path is a complete binary tree. =over =item C<@n_children = $path-Etree_n_children($n)> Return the two children of C<$n>, or an empty list if C<$n E 1> (before the start of the path). This is simply C<2*$n, 2*$n+1>. The children are C<$n> with an extra bit appended, either a 0-bit or a 1-bit. =item C<$num = $path-Etree_n_num_children($n)> Return 2, since every node has two children, or return C if C<$nE1> (before the start of the path). =item C<$n_parent = $path-Etree_n_parent($n)> Return the parent node of C<$n>, or C if C<$n E= 1> (the top of the tree). This is simply C, stripping the least significant bit from C<$n> (undoing what C appends). =item C<$depth = $path-Etree_n_to_depth($n)> Return the depth of node C<$n>, or C if there's no point C<$n>. The top of the tree at N=1 is depth=0, then its children depth=1, etc. The structure of the tree with 2 nodes per point means the depth is simply floor(log2(N)), so for example N=4 through N=7 are all depth=2. =back =head2 Tree Descriptive Methods =over =item C<$num = $path-Etree_num_children_minimum()> =item C<$num = $path-Etree_num_children_maximum()> Return 2 since every node has 2 children, making that both the minimum and maximum. =item C<$bool = $path-Etree_any_leaf()> Return false, since there are no leaf nodes in the tree. =back =head1 OEIS The trees are in Sloane's Online Encyclopedia of Integer Sequences in the following forms =over L (etc) =back tree_type=Kepler A020651 X numerator (RationalsTree AYT denominators) A086592 Y denominators A086593 X+Y sum, and every second denominator A020650 Y-X difference (RationalsTree AYT numerators) The tree descends as X/(X+Y) and Y/(X+Y) so the denominators are two copies of X+Y time after the initial 1/2. A086593 is every second, starting at 2, eliminating the duplication. This is also the sum X+Y, from value 3 onwards, as can be seen by thinking of writing a node as the X+Y which would be the denominators it descends to. =head1 SEE ALSO L, L, L, L L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/ImaginaryHalf.pm0000644000175000017500000004545513734026670020317 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::ImaginaryHalf; use 5.004; use strict; use Carp 'croak'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use Math::PlanePath::ImaginaryBase; *_negaradix_range_digits_lowtohigh = \&Math::PlanePath::ImaginaryBase::_negaradix_range_digits_lowtohigh; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_y_negative => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad12; use constant parameter_info_array => [ Math::PlanePath::Base::Digits::parameter_info_radix2(), { name => 'digit_order', share_key => 'digit_order_XYX', display => 'Digit Order', type => 'enum', default => 'XYX', choices => ['XYX', 'XXY', 'YXX', 'XnYX', 'XnXY', 'YXnX', ], }, ]; { my %x_negative_at_n = (XYX => 2, XXY => 1, YXX => 2, XnYX => 0, XnXY => 0, YXnX => 1, ); sub x_negative_at_n { my ($self) = @_; return $self->{'radix'} ** $x_negative_at_n{$self->{'digit_order'}}; } } # ENHANCE-ME: prove dY range use constant dy_maximum => 1; { my %absdx_minimum = (XYX => 1, XXY => 1, YXX => 0, # dX=0 at N=0 XnYX => 2, # dX=-2 at N=0 XnXY => 1, YXnX => 0, # dX=0 at N=0 ); sub absdx_minimum { my ($self) = @_; return $absdx_minimum{$self->{'digit_order'}}; } } { my %absdy_minimum = (XYX => 0, # dY=0 at N=0 XXY => 0, # dY=0 at N=0 YXX => 1, XnYX => 0, # dY=0 at N=0 XnXY => 0, # dY=0 at N=0 YXnX => 1, ); sub absdy_minimum { my ($self) = @_; return $absdy_minimum{$self->{'digit_order'}}; } } # was this anything? # # sub dir4_minimum { # my ($self) = @_; # if ($self->{'digit_order'} eq 'zzXYX') { # return Math::NumSeq::PlanePathDelta::_delta_func_Dir4 # ($self->{'radix'}-1,-2); # } else { # return 0; # } # } { # radix>2 has a straight somewhere # radix=2 only has straight in XXY, XnXY my %turn_any_straight = (# XYX => 0, XXY => 1, # YXX => 0, XnXY => 1, # XnYX => 0, # YXnX => 0, ); sub turn_any_straight { my ($self) = @_; return ($self->{'radix'} > 2 || $turn_any_straight{$self->{'digit_order'}}); } } sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; my $digit_order = $self->{'digit_order'}; my $radix = $self->{'radix'}; if ($digit_order eq 'XXY') { return $radix*$radix - 1; } if ($digit_order eq 'YXX' || $digit_order eq 'XnYX') { return $radix; } if ($digit_order eq 'XnXY') { return $radix*$radix ; } return $radix - 1; } sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; my $digit_order = $self->{'digit_order'}; my $radix = $self->{'radix'}; if ($digit_order eq 'XXY') { return $radix*$radix; } if ($digit_order eq 'XnXY') { return $radix*$radix - 1; } if ($digit_order eq 'YXX' || $digit_order eq 'XnYX') { return $radix - 1; } return $radix; } #------------------------------------------------------------------------------ my %digit_permutation = (XYX => [0,2,1], YXX => [2,0,1], XXY => [0,1,2], XnYX => [1,2,0], YXnX => [2,1,0], XnXY => [1,0,2], ); sub new { my $self = shift->SUPER::new(@_); my $radix = $self->{'radix'}; if (! defined $radix || $radix <= 2) { $radix = 2; } $self->{'radix'} = $radix; my $digit_order = ($self->{'digit_order'} ||= 'XYX'); $self->{'digit_permutation'} = $digit_permutation{$digit_order} || croak "Unrecognised digit_order: ",$digit_order; return $self; } sub n_to_xy { my ($self, $n) = @_; ### ImaginaryHalf n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $radix = $self->{'radix'}; my $zero = ($n*0); # inherit bignum 0 my @xydigits = ([],[0],[]); my $digit_permutation = $digit_permutation{$self->{'digit_order'}}; my @ndigits = digit_split_lowtohigh($n, $radix); foreach my $i (0 .. $#ndigits) { my $p = $digit_permutation->[$i%3]; push @{$xydigits[$p]}, $ndigits[$i], ($p < 2 ? (0) : ()); } return (digit_join_lowtohigh ($xydigits[0], $radix, $zero) - digit_join_lowtohigh ($xydigits[1], $radix, $zero), digit_join_lowtohigh ($xydigits[2], $radix, $zero)); } sub xy_to_n { my ($self, $x, $y) = @_; ### ImaginaryHalf xy_to_n(): "$x, $y" $y = round_nearest ($y); if (is_infinite($y)) { return $y; } if ($y < 0) { return undef; } $x = round_nearest ($x); if (is_infinite($x)) { return $x; } my $zero = ($x * 0 * $y); # inherit bignum 0 my $radix = $self->{'radix'}; my @ydigits = digit_split_lowtohigh($y, $radix); my $digit_permutation = $digit_permutation{$self->{'digit_order'}}; my @ndigits; # digits low to high my @nd; while ($x || @ydigits) { $nd[0] = _divrem_mutate ($x, $radix); $x = -$x; $nd[1] = _divrem_mutate ($x, $radix); $x = -$x; $nd[2] = shift @ydigits || 0; push @ndigits, $nd[$digit_permutation->[0]], $nd[$digit_permutation->[1]], $nd[$digit_permutation->[2]]; } return digit_join_lowtohigh (\@ndigits, $radix, $zero); } # Nlevel=2^level-1 # 66666666 55 55 5555 7.[16].7 # 66666666 55 55 5555 7.[16].7 # 66666666 33 22 4444 7.[16].7 # 9 66666666 33 01 4444 7.[16].7 # ^ ^ ^ ^ ^ ^ ^ # -11 -3 -1 1 2 6 22 # # X=1 when level=1 # X=1+1=2 when level=4 # X=2+4=6 when level=7 # X=6+16=22 when level=10 # # X=0-2=-2 when level=3 # X=-2-8=-10 when level=6 # X=-10-32=-42 when level=9 # # Y=1 k=0 want level=2 # Y=2 k=1 want level=5 # Y=4 k=2 want level=8 # # X = 1 + 1 + 4 + 16 + 4^k # = 1 + (4^(k+1) - 1) / (4-1) # X*(R2-1) = (R2-1) + R2^(k+1) - 1 # X*(R2-1) + 1 - (R2-1) = R2^(k+1) # R2^(k+1) = (X-1)*(R2-1) + 1 # k+1 = round down pow (X-1)*(R2-1) + 1 # (1-1)*3+1=1 k+1=0 want level=1 # (2-1)*3+1=4 k+1=1 want level=4 # (6-1)*3+1=16 k+1=2 want level=7 # (22-1)*3+1=64 k+1=3 want level=10 # # X = 1 + 2 + 8 + 32 + ... 2*4^k # = 1 + 2*(4^(k+1) - 1) / (4-1) # X = 1 + R*(R2^(k+1) - 1) / (R2-1) # R*(R2^(k+1) - 1) / (R2-1) = X-1 # R2^(k+1) - 1 = (X-1)*(R2-1)/R # R2^(k+2) - R2 = (X-1)*(R2-1)*R # R2^(k+2) = (X-1)*(R2-1)*R + R2 # (1-1)*3*2+4=4 k+2=1 want level=3 # (3-1)*3*2+4=16 k+2=2 want level=6 # (11-1)*3*2+4=64 k+2=3 want level=9 # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### ImaginaryBase rect_to_n_range(): "$x1,$y1 $x2,$y2" my $zero = $x1 * 0 * $x2 * $y1 * $y2; $y1 = round_nearest($y1); $y2 = round_nearest($y2); ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($y2 < 0) { ### rectangle all Y negative, no points ... return (1, 0); } if (is_infinite($y2)) { return (0, $y2); } if ($y1 < 0) { $y1 *= 0; } # "*=" to preserve bigint y1 $x1 = round_nearest($x1); $x2 = round_nearest($x2); my $radix = $self->{'radix'}; my ($min_xdigits, $max_xdigits) = _negaradix_range_digits_lowtohigh($x1,$x2, $radix); unless (defined $min_xdigits) { return (0, $max_xdigits); # infinity } my @min_ydigits = digit_split_lowtohigh ($y1, $radix); my @max_ydigits = digit_split_lowtohigh ($y2, $radix); my $digit_permutation = $digit_permutation{$self->{'digit_order'}}; my @min_ndigits = _digit_permutation_interleave ($digit_permutation, $min_xdigits, \@min_ydigits); my @max_ndigits = _digit_permutation_interleave ($digit_permutation, $max_xdigits, \@max_ydigits); return (digit_join_lowtohigh (\@min_ndigits, $radix, $zero), digit_join_lowtohigh (\@max_ndigits, $radix, $zero)); } sub _digit_permutation_interleave { my ($digit_permutation, $xaref, $yaref) = @_; my @ret; my @d; foreach (0 .. max($#$xaref,2*$#$yaref)) { $d[0] = shift @$xaref || 0; $d[1] = shift @$xaref || 0; $d[2] = shift @$yaref || 0; push @ret, $d[$digit_permutation->[0]], $d[$digit_permutation->[1]], $d[$digit_permutation->[2]]; } return @ret; } #------------------------------------------------------------------------------ # levels *level_to_n_range = \&Math::PlanePath::ImaginaryBase::level_to_n_range; *n_to_level = \&Math::PlanePath::ImaginaryBase::n_to_level; #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Math-PlanePath radix Radix ie radix-1 Proth XYX XXY Xn =head1 NAME Math::PlanePath::ImaginaryHalf -- half-plane replications in three directions =head1 SYNOPSIS use Math::PlanePath::ImaginaryBase; my $path = Math::PlanePath::ImaginaryBase->new (radix => 4); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is a half-plane variation on the C path. =cut # math-image --path=ImaginaryHalf --all --output=numbers_dash --size=85x10 =pod 54-55 50-51 62-63 58-59 22-23 18-19 30-31 26-27 3 \ \ \ \ \ \ \ \ 52-53 48-49 60-61 56-57 20-21 16-17 28-29 24-25 2 38-39 34-35 46-47 42-43 6--7 2--3 14-15 10-11 1 \ \ \ \ \ \ \ \ 36-37 32-33 44-45 40-41 4--5 0--1 12-13 8--9 <- Y=0 ------------------------------------------------- -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 The pattern can be seen by dividing into blocks, +---------------------------------+ | 22 23 18 19 30 31 26 27 | | | | 20 21 16 17 28 29 24 25 | +--------+-------+----------------+ | 6 7 | 2 3 | 14 15 10 11 | | +---+---+ | | 4 5 | 0 | 1 | 12 13 8 9 | <- Y=0 +--------+---+---+----------------+ ^ X=0 N=0 is at the origin, then N=1 replicates it to the right. Those two repeat above as N=2 and N=3. Then that 2x2 repeats to the left as N=4 to N=7, then 4x2 repeats to the right as N=8 to N=15, and 8x2 above as N=16 to N=31, etc. The replications are successively to the right, above, left. The relative layout within a replication is unchanged. This is similar to the C, but where it repeats in 4 directions there's just 3 directions here. The C is a 2 direction replication. =head2 Radix The C parameter controls the radix used to break N into X,Y. For example C 4> gives 4x4 blocks, with radix-1 replications of the preceding level at each stage. radix => 4 60 61 62 63 44 45 46 47 28 29 30 31 12 13 14 15 3 56 57 58 59 40 41 42 43 24 25 26 27 8 9 10 11 2 52 53 54 55 36 37 38 39 20 21 22 23 4 5 6 7 1 48 49 50 51 32 33 34 35 16 17 18 19 0 1 2 3 <- Y=0 --------------------------------------^----------- -12-11-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 Notice for X negative the parts replicate successively towards -infinity, so the block N=16 to N=31 is first at X=-4, then N=32 at X=-8, N=48 at X=-12, and N=64 at X=-16 (not shown). =head2 Digit Order The C parameter controls the order digits from N are applied to X and Y. The default above is "XYX" so the replications go X then Y then negative X. "XXY" goes to negative X before Y, so N=2,N=3 goes to negative X before repeating N=4 to N=7 in the Y direction. =cut # math-image --path=ImaginaryHalf,digit_order=XXY --all --output=numbers --size=55x4 =pod digit_order => "XXY" 38 39 36 37 46 47 44 45 34 35 32 33 42 43 40 41 6 7 4 5 14 15 12 13 2 3 0 1 10 11 8 9 ---------^-------------------- -2 -1 X=0 1 2 3 4 5 The further options are as follows, for six permutations of each 3 digits from N. =cut # math-image --path=ImaginaryHalf,digit_order=YXX --all --output=numbers --size=55x4 =pod digit_order => "YXX" digit_order => "XnYX" 38 39 36 37 46 47 44 45 19 23 18 22 51 55 50 54 34 35 32 33 42 43 40 41 17 21 16 20 49 53 48 52 6 7 4 5 14 15 12 13 3 7 2 6 35 39 34 38 2 3 0 1 10 11 8 9 1 5 0 4 33 37 32 36 digit_order => "XnXY" digit_order => "YXnX" 37 39 36 38 53 55 52 54 11 15 9 13 43 47 41 45 33 35 32 34 49 51 48 50 10 14 8 12 42 46 40 44 5 7 4 6 21 23 20 22 3 7 1 5 35 39 33 37 1 3 0 2 17 19 16 18 2 6 0 4 34 38 32 36 "Xn" means the X negative direction. It's still spaced 2 apart (or whatever radix), so the result is not simply a -X,Y. =head2 Axis Values N=0,1,4,5,8,9,etc on the X axis (positive and negative) are those integers with a 0 at every third bit starting from the second least significant bit. This is simply demanding that the bits going to the Y coordinate must be 0. X axis Ns = binary ...__0__0__0_ with _ either 0 or 1 in octal, digits 0,1,4,5 only N=0,1,8,9,etc on the X positive axis have the highest 1-bit in the first slot of a 3-bit group. Or N=0,4,5,etc on the X negative axis have the high 1 bit in the third slot, X pos Ns = binary 1_0__0__0...0__0__0_ X neg Ns = binary 10__0__0__0...0__0__0_ ^^^ three bit group X pos Ns in octal have high octal digit 1 X neg Ns in octal high octal digit 4 or 5 N=0,2,16,18,etc on the Y axis are conversely those integers with a 0 in two of each three bits, demanding the bits going to the X coordinate must be 0. Y axis Ns = binary ..._00_00_00_0 with _ either 0 or 1 in octal has digits 0,2 only For a radix other than binary the pattern is the same. Each "_" is any digit of the given radix, and each 0 must be 0. The high 1 bit for X positive and negative become a high non-zero digit. =head2 Level Ranges Because the X direction replicates twice for each once in the Y direction the width grows at twice the rate, so after each 3 replications width = height*height For this reason N values for a given Y grow quite rapidly. =head2 Proth Numbers The Proth numbers, k*2^n+1 for S2^n>, fall in columns on the path. =cut # math-image --path=ImaginaryHalf --values=ProthNumbers --text --size=70x25 =pod * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ----------------------------------------------------------------- -31 -23 -15 -7 -3-1 0 3 5 9 17 25 33 The height of the column is from the zeros in X ending binary ...1000..0001 since this limits the "k" part of the Proth numbers which can have N ending suitably. Or for X negative ending ...10111...11. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::ImaginaryBase-Enew ()> =item C<$path = Math::PlanePath::ImaginaryBase-Enew (radix =E $r, digit_order =E $str)> Create and return a new path object. The choices for C are "XYX" "XXY" "YXX" "XnYX" "XnXY" "YXnX" =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, $radix**$level - 1)>. =back =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/CellularRule190.pm0000644000175000017500000003754313734026674020434 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # http://mathworld.wolfram.com/ElementaryCellularAutomaton.html # # Loeschian numbers strips on the right ... package Math::PlanePath::CellularRule190; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_sqrtint = \&Math::PlanePath::_sqrtint; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; use Math::PlanePath::CellularRule54; *_rect_for_V = \&Math::PlanePath::CellularRule54::_rect_for_V; # uncomment this to run the ### lines # use Smart::Comments; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; use constant parameter_info_array => [ { name => 'mirror', display => 'Mirror', type => 'boolean', default => 0, description => 'Mirror to "rule 246" instead.', }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; sub x_negative_at_n { my ($self) = @_; return $self->n_start + 1; } use constant sumxy_minimum => 0; # triangular X>=-Y so X+Y>=0 use constant diffxy_maximum => 0; # triangular X<=Y so X-Y<=0 use constant dx_maximum => 2; # across gap use constant dy_minimum => 0; use constant dy_maximum => 1; use constant absdx_minimum => 1; use constant dsumxy_maximum => 2; # straight East dX=+2 use constant ddiffxy_maximum => 2; # straight East dX=+2 use constant dir_maximum_dxdy => (-1,0); # supremum, West except dY=+1 #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # 31 32 33 34 35 36 37 38 39 40 # 22 23 24 25 26 27 28 29 30 # 15 6 17 18 19 20 21 # 9 10 11 12 13 14 # 5 6 7 8 # 2 3 4 # 1 # # even y = [ 0, 2, 4, 6 ] # N = [ 1, 5, 15, 31 ] # Neven = (3/4 y^2 + 1/2 y + 1) # = (3y + 2)*y/4 + 1 # = ((3y + 2)*y + 4) /4 # = (3 (y/2)^2 + (y/2) + 1) # = (3*(y/2) + 1)*(y/2) + 1 # # odd y = [ 1, 3, 5,7 ] # N = [ 2,9,22,41 ] # Nodd = (3/4 y^2 + 1/2 y + 3/4) # = ((3y+2)*y+ 3) / 4 # # pair even d = [0,1,2,3] # N = [ 1, 5, 15, 31 ] # Npair = (3 d^2 + d + 1) # d = -1/6 + sqrt(1/3 * $n + -11/36) # = [ -1 + sqrt(1/3 * $n + -11/36)*6 ] / 6 # = [ -1 + sqrt(1/3 * $n*36 + -11/36*36) ] / 6 # = [ -1 + sqrt(12n-11) ] / 6 # sub n_to_xy { my ($self, $n) = @_; ### CellularRule190 n_to_xy(): $n $n = $n - $self->{'n_start'} + 1; # to N=1 basis, and warn if $n undef my $frac; { my $int = int($n); $frac = $n - $int; $n = $int; # BigFloat int() gives BigInt, use that if (2*$frac >= 1) { $frac -= 1; $n += 1; } # now -0.5 <= $frac < 0.5 ### assert: 2*$frac >= -1 || $n!=$n || $n+1==$n ### assert: 2*$frac < 1 || $n!=$n || $n+1==$n } if ($n < 1) { return; } # d is the two-row number, ie. d=2*y, where n belongs # start of the two-row group is nbase = 3 d^2 + d + 1 # my $d = int ((_sqrtint(12*$n-11) - 1) / 6); $n -= ((3*$d + 1)*$d + 1); # remainder within two-row ### $d ### remainder: $n if ($n <= 3*$d) { # 3d+1 many points in the Y=0,2,4,6 etc even row $d *= 2; # y=2*d return ($frac + $n + int(($n + ($self->{'mirror'} ? 2 : 0))/3) - $d, $d); } else { # 3*d many points in the Y=1,3,5,7 etc odd row, using 3 in 4 cells $n -= 3*$d+1; # remainder 0 upwards into odd row $d = 2*$d+1; # y=2*d+1 return ($frac + $n + int($n/3) - $d, $d); } } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### CellularRule190 xy_to_n(): "$x,$y" if ($y < 0 || $x > $y) { return undef; } $x += $y; # move to have x=0 the start of the row if ($x < 0) { return undef; } ### x centred: $x if ($y % 2) { ### odd row, 3s from the start ... if (($x % 4) == 3) { return undef; } # 3y^2+2y-1 = (3y-1)*(y+1) return ($x - int($x/4) + ((3*$y+2)*$y-1)/4 + $self->{'n_start'}); } else { ### even row, 3s then 1 sep, or mirror 1 sep start ... my $mirror = $self->{'mirror'}; if (($x % 4) == ($mirror ? 1 : 3)) { return undef; } return ($x - int(($x+($mirror ? 2 : 1))/4) + (3*$y+2)*$y/4 + $self->{'n_start'}); } } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### CellularRule190 rect_to_n_range(): "$x1,$y1, $x2,$y2" ($x1,$y1, $x2,$y2) = _rect_for_V ($x1,$y1, $x2,$y2) or return (1,0); # rect outside pyramid # # inherit bignum (before collapsing some y1 to x1 etc) # my $zero = ($x1 * 0 * $y1 * $x2 * $y2); my $mirror = $self->{'mirror'}; my $unincremented_x1 = $x1; # \+------+ # | | / # |\ | / # | \ | / # | \ | / # y1 +------+ / # x1 \ / # \/ # if ($x1 < (my $neg_y1 = -$y1)) { ### bottom-left outside, move across to: "$neg_y1,$y1" $x1 = $neg_y1; # For the following blank checks a blank doesn't occur at the ends of a # row, so when on a blank it's always possible to increment or decrement # X to go to a non-blank -- as long as that adjacent space is within the # rectangle. # } elsif ((($mirror ? $y1-$x1 : $x1+$y1) % 4) == 3) { ### x1,y1 bottom left is on a blank: "x1+y1=".($x1+$y1) if ($x1 < $x2) { ### rect width >= 2, so increase x1 ... $x1 += 1; } else { ### rect is a single column width==1, increase y1 ... if (($y1 += 1) > $y2) { ### rect was a single blank square, contains no N ... return (1,0); } } } if ((($mirror ? $y2-$x2 : $x2+$y2) % 4) == 3) { ### x2,y2 top right is on a blank, decrement ... if ($x2 > $unincremented_x1) { ### rect width >= 2, so decrease x2 ... $x2 -= 1; } else { ### rect is a single column width==1, decrease y2 ... $y2 -= 1; # Can decrement without checking whether the rect is a single square. # If the rect was a single blank square then the x1+y1 bottom left # above detects and returns. And the bottom left blank check # incremented y1 to leave a single square then that's a non-blank # because there's no vertical blank pairs (they go on the diagonal). ### assert $y2 >= $y1 } } # At this point $x1,$y1 is a non-blank bottom left corner, and $x2,$y2 # is a non-blank top right corner, being the N lo to hi range. ### range: "bottom-right $x1,$y1 top-left $x2,$y2" return ($self->xy_to_n ($x1,$y1), $self->xy_to_n ($x2,$y2)); } # old rect_to_n_range() of row endpoints # # # even right y = [ 0, 2, 4, 6 ] # # N = [ 1,8,21,40 ] # # Nright = (3/4 y^2 + 2 y + 1) # # = (3 y^2 + 8 y + 4) / 4 # # = ((3y + 8)y + 4) / 4 # # # # odd right y = [ 1, 3, 5, 7 ] # # N = [ 4,14,30, 52 ] # # Nright = (3/4 y^2 + 2 y + 5/4) # # = (3 y^2 + 8 y + 5) / 4 # # = ((3y + 8)y + 5) / 4 # # # # Nleft y even ((3y+2)*y + 4)/4 # # Nleft y odd ((3y+2)*y + 3)/4 # # Nright even ((3(y+1)+2)*(y+1) + 3)/4 - 1 # # = ((3y+3+2)*(y+1) + 3 - 4)/4 # # = ((3y+5)*(y+1) - 1)/4 # # = ((3y^2 + 8y + 5 - 1)/4 # # = ((3y^2 + 8y + 4)/4 # # = ((3y+8)y + 4)/4 # # = ((3y+2)(y+2)/4 # # # ### $y1 # ### $y2 # $y2 += $zero; # $y1 += $zero; # return (((3*$y1 + 2)*$y1 + 4 - ($y1%2)) / 4, # even/odd Nleft # ((3*$y2 + 8)*$y2 + 4 + ($y2%2)) / 4); # even/odd Nright 1; __END__ =for stopwords straight-ish Ryde Math-PlanePath ie hexagonals 18-gonal Xmax-Xmin Nleft Nright Klaner-Rado unplotted OEIS =head1 NAME Math::PlanePath::CellularRule190 -- cellular automaton 190 and 246 points =head1 SYNOPSIS use Math::PlanePath::CellularRule190; my $path = Math::PlanePath::CellularRule190->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis is the pattern of Stephen Wolfram's "rule 190" cellular automaton =over L =back arranged as rows, 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 9 53 54 55 56 57 58 59 60 61 62 63 64 65 8 41 42 43 44 45 46 47 48 49 50 51 52 7 31 32 33 34 35 36 37 38 39 40 6 22 23 24 25 26 27 28 29 30 5 15 16 17 18 19 20 21 4 9 10 11 12 13 14 3 5 6 7 8 2 2 3 4 1 1 <- Y=0 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 Each row is 3 out of 4 cells. Even numbered rows have one point on its own at the end. Each two-row group has a step of 6 more points than the previous two-row. The of rightmost N=1,4,8,14,21,etc are triangular plus quarter square, ie. Nright = triangular(Y+1) + quartersquare(Y+1) triangular(t) = t*(t+1)/2 quartersquare(t) = floor(t^2/4) The rightmost N=1,8,21,40,65,etc on even rows Y=0,2,4,6,etc are the octagonal numbers k*(3k-2). The octagonal numbers of the "second kind" N=5,16,33,56,85, etc, k*(3k+2) are a straight-ish line upwards to the left. =head2 Mirror The C 1> option gives the mirror image pattern which is "rule 246". It differs only in the placement of the gaps on the even rows. The point on its own is at the left instead of the right. The numbering is still left to right. 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 9 53 54 55 56 57 58 59 60 61 62 63 64 65 8 41 42 43 44 45 46 47 48 49 50 51 52 7 31 32 33 34 35 36 37 38 39 40 6 22 23 24 25 26 27 28 29 30 5 15 16 17 18 19 20 21 4 9 10 11 12 13 14 3 5 6 7 8 2 2 3 4 1 1 <- Y=0 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 Sometimes this small change to the pattern helps things line up better. For example plotting the Klaner-Rado sequence gives some unplotted lines up towards the right in the mirror 246 which are not visible in the plain 190. =head2 Row Ranges The left end of each row, both ordinary and mirrored, is Nleft = ((3Y+2)*Y + 4)/4 if Y even ((3Y+2)*Y + 3)/4 if Y odd The right end is Nright = ((3Y+8)*Y + 4)/4 if Y even ((3Y+8)*Y + 5)/4 if Y odd = Nleft(Y+1) - 1 ie. 1 before next Nleft The row width Xmax-Xmin = 2*Y but with the gaps the number of visited points in a row is less than that, rowpoints = 3*Y/2 + 1 if Y even 3*(Y+1)/2 if Y odd For any Y of course the Nleft to Nright difference is the number of points in the row too rowpoints = Nright - Nleft + 1 =cut # even Nright - Nleft + 1 # = ((3Y+8)Y + 4)/4 - ((3Y+2)*Y + 4)/4 + 1 # = [ (3Y+8)Y + 4 - (3Y+2)*Y - 4 ]/4 + 1 # = [ (3Y+8)Y - (3Y+2)*Y ] / 4 + 1 # = (3Y+8-3Y-2)Y/4 + 1 # = 6Y/4 + 1 # = 3Y/2 + 1 # odd Nright - Nleft + 1 # = ((3Y+8)Y + 5)/4 - ((3Y+2)*Y + 3)/4 + 1 # = [ (3Y+8)Y + 5 - (3Y+2)*Y - 3 ]/4 + 1 # = [ (3Y+8)Y - (3Y+2)*Y + 2 ]/4 + 1 # = [ 6Y + 2 ]/4 + 1 # = [ 6Y + 2 + 4]/4 # = [ 6Y + 6]/4 # = 3(Y+1)/2 =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same pattern. For example to start at 0, =cut # math-image --path=CellularRule190,n_start=0 --all --output=numbers --size=75x6 =pod n_start => 0 21 22 23 24 25 26 27 28 29 5 14 15 16 17 18 19 20 4 8 9 10 11 12 13 3 4 5 6 7 2 1 2 3 1 0 <- Y=0 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 The effect is to push each N rightwards by 1, and wrapping around. So the N=0,1,4,8,14,etc on the left were on the right of the default n_start=1. This also has the effect of removing the +1 in the Nright formula given above, so Nleft = triangular(Y) + quartersquare(Y) =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::CellularRule190-Enew ()> =item C<$path = Math::PlanePath::CellularRule190-Enew (mirror =E 1, n_start =E $n)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each cell as a square of side 1. If C<$x,$y> is outside the pyramid or on a skipped cell the return is C. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head1 OEIS This pattern is in Sloane's Online Encyclopedia of Integer Sequences in a couple of forms, =over L (etc) =back A037576 whole-row used cells as bits of a bignum A265688 and in binary A071039 1/0 used and unused cells across rows A118111 same A071041 1/0 used and unused of mirrored rule 246 n_start=0 A006578 N at left of each row (X=-Y), and at right of each row when mirrored, being triangular+quartersquare =head1 SEE ALSO L, L, L, L, L L L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut # Local variables: # compile-command: "math-image --path=CellularRule190 --all" # End: # # math-image --path=CellularRule190 --all --output=numbers --size=132x50 # Math-PlanePath-129/lib/Math/PlanePath/HexSpiral.pm0000644000175000017500000003466013734026670017477 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Kanga "Number Mosaics" rotated to # # ...-16---15 # \ # 6----5 14 # / \ \ # 7 1 4 13 # / / / / # 8 2----3 12 # \ / # 9---10---11 # # # Could go pointy end with same loop/step, or point to the right # # 13--12--11 # / | # 14 4---3 10 # / / | | # 15 5 1---2 9 # \ \ | # 16 6---7---8 # \ | # 17--18--19--20 # package Math::PlanePath::HexSpiral; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_sqrtint = \&Math::PlanePath::_sqrtint; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest', 'xy_is_even'; # uncomment this to run the ### lines #use Devel::Comments '###'; use Math::PlanePath::SquareSpiral; *parameter_info_array = \&Math::PlanePath::SquareSpiral::parameter_info_array; # 2w+3 --- 3w/2+3 -- w+4 # / \ # 2w+4 0 -------- w+3 * # \ / # 2w+5 ----------------- 3w+7 w=2; 1+3*w+7=14 # ^ # X=0 sub x_negative_at_n { my ($self) = @_; return $self->n_start + ($self->{'wider'} ? 0 : 3); } sub y_negative_at_n { my ($self) = @_; return $self->n_start + 2*$self->{'wider'} + 5; } sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + 3*$self->{'wider'} + 7; } sub rsquared_minimum { my ($self) = @_; return ($self->{'wider'} % 2 ? 1 # odd "wider" minimum X=1,Y=0 : 0); # even "wider" includes X=0,Y=0 } *sumabsxy_minimum = \&rsquared_minimum; use constant dx_minimum => -2; use constant dx_maximum => 2; use constant dy_minimum => -1; use constant dy_maximum => 1; *_UNDOCUMENTED__dxdy_list = \&Math::PlanePath::_UNDOCUMENTED__dxdy_list_six; use constant absdx_minimum => 1; *absdiffxy_minimum = \&rsquared_minimum; use constant dsumxy_minimum => -2; # SW diagonal use constant dsumxy_maximum => 2; # dX=+2 and diagonal use constant ddiffxy_minimum => -2; # NW diagonal use constant ddiffxy_maximum => 2; # SE diagonal use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_right => 0; # only left or straight sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; return $self->n_start + $self->{'wider'} + 1; } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); # parameters $self->{'wider'} ||= 0; # default if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # wider==0 # diagonal down and to the left # d = [ 0, 1, 2, 3 ] # N = [ 1, 6, 17, 34 ] # N = (3*$d**2 + 2*$d + 1) # d = -1/3 + sqrt(1/3 * $n + -2/9) # = (-1 + sqrt(3*$n - 2)) / 3 # # wider==1 # diagonal down and to the left # d = [ 0, 1, 2, 3 ] # N = [ 1, 8, 21, 40 ] # N = (3*$d**2 + 4*$d + 1) # d = -2/3 + sqrt(1/3 * $n + 1/9) # = (-2 + sqrt(3*$n + 1)) / 3 # # wider==2 # diagonal down and to the left # d = [ 0, 1, 2, 3, 4 ] # N = [ 1, 10, 25, 46, 73 ] # N = (3*$d**2 + 6*$d + 1) # d = -1 + sqrt(1/3 * $n + 2/3) # = (-3 + sqrt(3*$n + 6)) / 3 # # N = 3*$d*$d + (2+2*$w)*$d + 1 # = (3*$d + 2 + 2*$w)*$d + 1 # d = (-1-w + sqrt(3*$n + ($w+2)*$w - 2)) / 3 # = (sqrt(3*$n + ($w+2)*$w - 2) -1-w) / 3 sub n_to_xy { my ($self, $n) = @_; #### n_to_xy: "$n wider=$self->{'wider'}" $n = $n - $self->{'n_start'}; # N=0 basis if ($n < 0) { return; } my $w = $self->{'wider'}; my $d = int((_sqrtint(3*$n + ($w+2)*$w + 1) - 1 - $w) / 3); #### d frac: ((_sqrtint(3*$n + ($w+2)*$w + 1) - 1 - $w) / 3) #### $d $n += 1; # N=1 basis $n -= (3*$d + 2 + 2*$w)*$d + 1; #### remainder: $n $d = $d + 1; # no warnings if $d==inf if ($n <= $d+$w) { #### bottom horizontal $d = -$d + 1; return (2*$n + $d - $w, $d); } $n -= $d+$w; if ($n <= $d-1) { #### right lower diagonal, being 1 shorter: $n return ($n + $d + 1 + $w, $n - $d + 1); } $n -= $d-1; if ($n <= $d) { #### right upper diagonal: $n return (-$n + 2*$d + $w, $n); } $n -= $d; if ($n <= $d+$w) { #### top horizontal return (-2*$n + $d + $w, $d); } $n -= $d+$w; if ($n <= $d) { #### left upper diagonal return (-$n - $d - $w, -$n + $d ); } #### left lower diagonal $n -= $d; return ($n - 2*$d - $w, -$n); } sub xy_is_visited { my ($self, $x, $y) = @_; return xy_is_even($self,$x+$self->{'wider'},$y); } sub xy_to_n { my ($self, $x, $y) = @_; ### xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); my $w = $self->{'wider'}; if (($x ^ $y ^ $w) & 1) { return undef; # nothing on odd squares } my $ay = abs($y); my $ax = abs($x) - $w; if ($ax > $ay) { my $d = ($ax + $ay)/2; # x+y is even if ($x > 0) { ### right ends ### $d return ((3*$d - 2 + 2*$w)*$d - $w # horizontal to the right + $y # offset up or down + $self->{'n_start'}); } else { ### left ends return ((3*$d + 1 + 2*$w)*$d # horizontal to the left - $y # offset up or down + $self->{'n_start'}); } } else { my $d = $ay; if ($y > 0) { ### top horizontal ### $d return ((3*$d + 2*$w)*$d # diagonal up to the left + (-$d - $x-$w) / 2 # negative offset rightwards + $self->{'n_start'}); } else { ### bottom horizontal, and centre horizontal ### $d ### offset: $d return ((3*$d + 2 + 2*$w)*$d # diagonal down to the left + ($x + $w + $d)/2 # offset rightwards + $self->{'n_start'}); } } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### HexSpiral rect_to_n_range(): $x1,$y1, $x2,$y2 my $w = $self->{'wider'}; # symmetric in +/-y, and biggest y is biggest n my $y = max (abs($y1), abs($y2)); # symmetric in +/-x, and biggest x my $x = max (abs($x1), abs($x2)); if ($x >= $w) { $x -= $w; } # in the middle horizontal path parts y determines the loop number # in the end parts diagonal distance, 2 apart my $d = ($y >= $x ? $y # middle : ($x + $y + 1)/2); # ends $d = int($d) + 1; # diagonal downwards bottom left being the end of a revolution # s=0 # s=1 n=7 # s=2 n=19 # s=3 n=37 # s=4 n=61 # n = 3*$d*$d + 3*$d + 1 # # ### gives: "sum $d is " . (3*$d*$d + 3*$d + 1) # ENHANCE-ME: find actual minimum if rect doesn't cover 0,0 return ($self->{'n_start'}, (3*$d + 3 + 2*$w)*$d + $self->{'n_start'}); } 1; __END__ =for stopwords PlanePath Ryde Math-PlanePath ie OEIS =head1 NAME Math::PlanePath::HexSpiral -- integer points around a hexagonal spiral =head1 SYNOPSIS use Math::PlanePath::HexSpiral; my $path = Math::PlanePath::HexSpiral->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes a hexagonal spiral, with points spread out horizontally to fit on a square grid. 28 -- 27 -- 26 -- 25 3 / \ 29 13 -- 12 -- 11 24 2 / / \ \ 30 14 4 --- 3 10 23 1 / / / \ \ \ 31 15 5 1 --- 2 9 22 <- Y=0 \ \ \ / / 32 16 6 --- 7 --- 8 21 -1 \ \ / 33 17 -- 18 -- 19 -- 20 -2 \ 34 -- 35 ... -3 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 Each horizontal gap is 2, so for instance n=1 is at X=0,Y=0 then n=2 is at X=2,Y=0. The diagonals are just 1 across, so n=3 is at X=1,Y=1. Each alternate row is offset from the one above or below. The result is a triangular lattice per L. The octagonal numbers 8,21,40,65, etc 3*k^2-2*k fall on a horizontal straight line at Y=-1. In general straight lines are 3*k^2 + b*k + c. A plain 3*k^2 goes diagonally up to the left, then b is a 1/6 turn anti-clockwise, or clockwise if negative. So b=1 goes horizontally to the left, b=2 diagonally down to the left, b=3 diagonally down to the right, etc. =head2 Wider An optional C parameter makes the path wider, stretched along the top and bottom horizontals. For example $path = Math::PlanePath::HexSpiral->new (wider => 2); gives ... 36----35 3 \ 21----20----19----18----17 34 2 / \ \ 22 8---- 7---- 6---- 5 16 33 1 / / \ \ \ 23 9 1---- 2---- 3---- 4 15 32 <- Y=0 \ \ / / 24 10----11----12----13----14 31 -1 \ / 25----26----27----28---29----30 -2 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 The centre horizontal from N=1 is extended by C many extra places, then the path loops around that shape. The starting point N=1 is shifted to the left by wider many places to keep the spiral centred on the origin X=0,Y=0. Each horizontal gap is still 2. Each loop is still 6 longer than the previous, since the widening is basically a constant amount added into each loop. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start with the same shape etc. For example to start at 0, =cut # math-image --path=HexSpiral,n_start=0 --all --output=numbers --size=70x9 =pod n_start => 0 27 26 25 24 3 28 12 11 10 23 2 29 13 3 2 9 22 1 30 14 4 0 1 8 21 <- Y=0 31 15 5 6 7 20 ... -1 32 16 17 18 19 38 -2 33 34 35 36 37 -3 ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 In this numbering the X axis N=0,1,8,21,etc is the octagonal numbers 3*X*(X+1). =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::HexSpiral-Enew ()> =item C<$path = Math::PlanePath::HexSpiral-Enew (wider =E $w)> Create and return a new hex spiral object. An optional C parameter widens the path, it defaults to 0 which is no widening. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n < 1> the return is an empty list, it being considered the path starts at 1. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each C<$n> in the path as a square of side 1. Only every second square in the plane has an N, being those where X,Y both odd or both even. If C<$x,$y> is a position without an N, ie. one of X,Y odd the other even, then the return is C. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A328818 X coordinate A307012 Y coordinate A307011 (X-Y)/2 A307013 (X+Y)/2 A056105 N on X axis A056106 N on X=Y diagonal A056107 N on North-West diagonal A056108 N on negative X axis A056109 N on South-West diagonal A003215 N on South-East diagonal A063178 total sum N previous row or diagonal A135711 boundary length of N hexagons A135708 grid sticks of N hexagons n_start=0 A001399 N positions of turns (extra initial 1) A000567 N on X axis, octagonal numbers A049451 N on X negative axis A049450 N on X=Y diagonal north-east A033428 N on north-west diagonal, 3*k^2 A045944 N on south-west diagonal, octagonal numbers second kind A063436 N on WSW slope dX=-3,dY=-1 A028896 N on south-east diagonal =head1 SEE ALSO L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/ChanTree.pm0000644000175000017500000011021313734026673017261 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # digit_direction LtoH # digit_order HtoL # reduced = bool # points = even, all_mul, all_div # points=all wrong # # Chan corollary 3 taking frac(2n) = b(2n) / b(2n+1) # frac(2n+1) = b(2n+1) / 2*b(2n+2) # at N odd multiply 2 into denominator, # which is divide out 2 from numerator since b(2n+1) odd terms are even # package Math::PlanePath::ChanTree; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; *_divrem = \&Math::PlanePath::_divrem; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::CoprimeColumns; *_coprime = \&Math::PlanePath::CoprimeColumns::_coprime; use Math::PlanePath::GcdRationals; *_gcd = \&Math::PlanePath::GcdRationals::_gcd; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ { name => 'k', display => 'k', type => 'integer', default => 3, minimum => 2, }, # Not sure about these yet. # { name => 'reduced', # display => 'Reduced', # type => 'boolean', # default => 0, # }, # { name => 'points', # share_key => 'points_ea', # display => 'Points', # type => 'enum', # default => 'even', # choices => ['even','all_mul','all_div'], # choices_display => ['Even','All Mul','All Div'], # when_name => 'k', # when_condition => 'odd', # }, # { name => 'digit_order', # display => 'Digit Direction', # type => 'enum', # default => 'HtoL', # choices => ['HtoL','LtoH'], # choices_display => ['High to Low','Low to High'], # }, Math::PlanePath::Base::Generic::parameter_info_nstart0(), ]; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant x_minimum => 1; use constant y_minimum => 1; sub sumxy_minimum { my ($self) = @_; return ($self->{'reduced'} || $self->{'k'} == 2 ? 2 # X=1,Y=1 if reduced or k=2 : 3); # X=1,Y=2 } sub absdiffxy_minimum { my ($self) = @_; return ($self->{'k'} & 1 ? 1 # k odd, X!=Y since one odd one even : 0); # k even, has X=Y in top row } sub rsquared_minimum { my ($self) = @_; return ($self->{'k'} == 2 || ($self->{'reduced'} && ($self->{'k'} & 1) == 0) ? 2 # X=1,Y=1 reduced k even, including k=2 top 1/1 : 5); # X=1,Y=2 } sub gcdxy_maximum { my ($self) = @_; return ($self->{'k'} == 2 # k=2, RationalsTree CW above || $self->{'reduced'} ? 1 : undef); # other, unlimited } sub absdx_minimum { my ($self) = @_; return ($self->{'k'} & 1 ? 1 # k odd : 0); # k even, dX=0,dY=-1 at N=k/2 middle of roots } sub absdy_minimum { my ($self) = @_; return ($self->{'k'} == 2 || ($self->{'k'} & 1) ? 1 # k=2 or k odd : 0); # k even, dX=1,dY=0 at N=k/2-1 middle of roots } sub dir_minimum_dxdy { my ($self) = @_; return ($self->{'k'} == 2 ? (0,1) # k=2, per RationalsTree CW # otherwise East # k even exact dX=1,dY=0 middle of roots # k odd infimum dX=big,dY=-1 eg k=5 N="2222220" : (1,0)); } sub tree_num_children_list { my ($self) = @_; return ($self->{'k'}); # complete tree, always k children } use constant tree_n_to_subheight => undef; # complete trees, all infinite sub turn_any_left { my ($self) = @_; return ($self->{'k'} <= 3 || $self->{'reduced'}); } sub turn_any_straight { my ($self) = @_; return ($self->{'k'} >= 7); } # left reduced=1,k=5,7,9,11 at N=51,149,327,609,... sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; if ($self->{'k'} == 5 && $self->{'reduced'}) { return 51; } if ($self->{'k'} == 7 && $self->{'reduced'}) { return 149; } return undef; } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'digit_order'} ||= 'HtoL'; # default my $k = ($self->{'k'} ||= 3); # default $self->{'half_k'} = int($k / 2); if (! defined $self->{'n_start'}) { $self->{'n_start'} = 0; # default } $self->{'points'} ||= 'even'; return $self; } # rows # level=0 k-1 # level=1 k * (k-1) # level=2 k^2 * (k-1) # total (k-1)*(1+k+k^2+...+k^level) # = (k-1)*(k^(level+1) - 1)/(k-1) # = k^(level+1) - 1 # # middle odd # k(r+s)/2-r-2s / k(r+s)/2-s # (k-1)(r+s)/2+r / (k-1)(r+s)/2+s # k(r+s)/2-r-2s / k(r+s)/2-s # # k=5 # 5(r+2)/2 -r-2s / 5(r+s)/2-s # # (1 + 2*x + 3*x^2 + 2*x^3 + x^4 + 2*x^5 + 3*x^6 + 2*x^7 + x^8) # * (1 + 2*x^5 + 3*x^10 + 2*x^15 + x^20 + 2*x^25 + 3*x^30 + 2*x^35 + x^40) # * (1 + 2*x^(25*1) + 3*x^(25*2) + 2*x^(25*3) + x^(25*4) + 2*x^(25*5) + 3*x^(25*6) + 2*x^(25*7) + x^(25*8)) # # 1 2 3 2 # 1 4 7 8 5 2 7 12 13 8 3 8 # x^48 + 2*x^47 + 3*x^46 + 2*x^45 + x^44 + 4*x^43 + 7*x^42 + 8*x^41 + 5*x^40 + 2*x^39 + 7*x^38 + 12*x^37 + 13*x^36 + 8*x^35 + 3*x^34 + 8*x^33 + 13*x^32 + 12*x^31 + 7*x^30 + 2*x^29 + 5*x^28 + 8*x^27 + 7*x^26 + 4*x^25 + x^24 + 4*x^23 + 7*x^22 + 8*x^21 + 5*x^20 + 2*x^19 + 7*x^18 + 12*x^17 + 13*x^16 + 8*x^15 + 3*x^14 + 8*x^13 + 13*x^12 + 12*x^11 + 7*x^10 + 2*x^9 + 5*x^8 + 8*x^7 + 7*x^6 + 4*x^5 + x^4 + 2*x^3 + 3*x^2 + 2*x + 1 sub n_to_xy { my ($self, $n) = @_; ### ChanTree n_to_xy(): "$n k=$self->{'k'} reduced=".($self->{'reduced'}||0) if ($n < $self->{'n_start'}) { return; } $n -= $self->{'n_start'}-1; ### 1-based N: $n if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat $int += $self->{'n_start'}-1; # back to n_start() based my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } } my $k = $self->{'k'}; my $half_k = int($self->{'k'} / 2); my $half_ceil = int(($self->{'k'}+1) / 2); my @digits = digit_split_lowtohigh ($n, $k); ### @digits # top 1/2, 2/3, ..., (k/2-1)/(k/2), (k/2)/(k/2) ... 3/2, 2/1 my $x = (pop @digits) + ($n*0); # inherit bignum zero my $y = $x+1; if ($x > $half_k) { $x = $k+1 - $x; } if ($y > $half_k) { $y = $k+1 - $y; } ### top: "x=$x y=$y" # 1/2 2/3 3/4 ... # 1/4 4/7 7/10 10/13 ... # descend # # middle even # (k/2-1)(r+s)-s / (k/2)(r+s)-s # (k/2)(r+s)-s / (k/2)(r+s) # (k/2)(r+s) / (k/2)(r+s)-r # (k/2)(r+s)-r / (k/2-1)(r+s)-r # # k=4 r/s=1/2 # r/2r+s 1/4 # 2r+s/2r+2s 4/6 # 2r+2s/r+2s 6/5 # r+2s/s 5/1 # # even eg k=4 half_k==2 half_ceil==2 # x + 0*(x+y) / x + 1*(x+y) 0 1x+0y / 2x+1y <1/2 # x + 1*(x+y) / 2*(x+y) 1 2x+1y / 2x+2y <2/3 # 2*(x+y) / 1*(x+y) + y 2 2x+2y / 1x+2y >3/2 # 1*(x+y) + y / 0*(x+y) + y 3 1x+2y / 0x+1y >2/1 # # even eg k=6 half_k==3 half_ceil==3 # x + 0*(x+y) / x + 1*(x+y) 0 1x+0y / 2x+1y # x + 1*(x+y) / x + 2*(x+y) 1 2x+1y / 3x+2y # x + 2*(x+y) / 3(x+y) 2 3x+2y / 3x+3y # 3*(x+y) / 2*(x+y) + y 3 3x+3y / 2x+3y # 2*(x+y) + y / 1*(x+y) + y 4 2x+3y / 1x+2y # 1*(x+y) + y / 0*(x+y) + y 5 1x+2y / 0x+1y # # odd eg k=3 half_k==1 half_ceil==2 # x + 0*(x+y) / x + 1*(x+y) 0 1x+0y / 2x+1y <1/2 # x + 1*(x+y) / 1*(x+y) + y 1 2x+1y / 1x+2y # 1*(x+y) + y / 0*(x+y) + y 2 1x+2y / 0x+1y >2/1 # # odd eg k=5 half_k==2 half_ceil==3 # x + 0*(x+y) / x + 1*(x+y) 0 1x+0y / 2x+1y <1/2 # x + 1*(x+y) / x + 2*(x+y) 1 2x+1y / 3x+2y <2/3 # x + 2*(x+y) / 2*(x+y) + y 2 3x+2y / 2x+3y # 2*(x+y) + y / 1*(x+y) + y 3 2x+3y / 1x+2y >3/2 # 1*(x+y) + y / 0*(x+y) + y 4 1x+2y / 0x+1y >2/1 if ($self->{'digit_order'} eq 'HtoL') { @digits = reverse @digits; # high to low is the default } foreach my $digit (@digits) { # c1 = 1,2,3,3,2,1 or 1,2,3,2,1 my $c0 = ($digit <= $half_ceil ? $digit : $k-$digit+1); my $c1 = ($digit < $half_ceil ? $digit+1 : $k-$digit); my $c2 = ($digit < $half_ceil-1 ? $digit+2 : $k-$digit-1); ### at: "x=$x y=$y next digit=$digit $c1,$c0 $c2,$c1" ($x,$y) = ($x*$c1 + $y*$c0, $x*$c2 + $y*$c1); } ### loop: "x=$x y=$y" if (($k & 1) && ($n % 2) == 0) { # odd N=2n+1 when 1 based if ($self->{'points'} eq 'all_div') { $x /= 2; ### all_div divide X to: "x=$x y=$y" } elsif ($self->{'points'} eq 'all_mul') { if ($self->{'reduced'} && ($x % 2) == 0) { $x /= 2; ### all_mul reduced divide X to: "x=$x y=$y" } else { $y *= 2; ### all_mul multiply Y to: "x=$x y=$y" } } } if ($self->{'reduced'}) { ### unreduced: "x=$x y=$y" if ($k & 1) { # k odd, gcd(x,y)=k^m for some m, divide out factors of k as possible foreach (0 .. scalar(@digits)) { last if ($x % $k) || ($y % $k); $x /= $k; $y /= $k; } } else { # k even, gcd(x,y) divides (k/2)^m for some m, but gcd isn't # necessarily equal to such a power, only a divisor of it, so must do # full gcd calculation my $g = _gcd($x,$y); $x /= $g; $y /= $g; } } ### n_to_xy() return: "x=$x y=$y" return ($x,$y); } # (3*pow+1)/2 - (pow+1)/2 # = (3*pow + 1 - pow - 1)/2 # = (2*pow)/2 # = pow # sub xy_to_n { my ($self, $x, $y) = @_; ### Chan xy_to_n(): "x=$x y=$y k=$self->{'k'}" $x = round_nearest ($x); $y = round_nearest ($y); if (is_infinite($x)) { return $x; # infinity } if (is_infinite($y)) { return $y; # infinity } my $orig_x = $x; my $orig_y = $y; my $k = $self->{'k'}; my $zero = ($x * 0 * $y); # inherit bignum my $half_k = $self->{'half_k'}; my $half_ceil = int(($self->{'k'}+1) / 2); if ($k & 1) { if ($self->{'points'} eq 'all_div' || ($self->{'points'} eq 'all_mul' && ($self->{'reduced'}))) { my $n = do { local $self->{'points'} = 'even'; $self->xy_to_n(2*$x,$y) }; if (defined $n) { my ($nx,$ny) = $self->n_to_xy($n); if ($nx == $x && $ny == $y) { return $n; } } } if ($self->{'points'} eq 'all_mul' && ($y % 2) == 0) { my $n = do { local $self->{'points'} = 'even'; $self->xy_to_n($x,$y/2) }; if (defined $n) { my ($nx,$ny) = $self->n_to_xy($n); if ($nx == $x && $ny == $y) { return $n; } } } # k odd cannot have X,Y both odd if (($x % 2) && ($y % 2)) { return undef; } } if (ref $x && ref $y && $x < 0xFF_FFFF && $y < 0xFF_FFFF) { # numize BigInt for speed $x = "$x"; $y = "$y"; } if ($self->{'reduced'}) { ### unreduced: "x=$x y=$y" unless (_coprime($x,$y)) { return undef; } } # left t'th child (t-1)/t < x/y < t/(t+1) x/y<1 t=1,2,3,... # x/y < (t-1)/t # xt < (t-1)y # xt < ty-y # y < (y-x)t # t > y/(y-x) # # lx = x + (t-1)*(x+y) = t*x + (t-1)y # t=1 upwards # ly = x + t*(x+y) = (t+1)x + ty # t*lx - (t-1)*ly # = t*t*x - (t-1)(t+1)x # = (t^2 - (t^2 - 1))x # = x # x = t*lx - (t-1)*ly # # lx = x + (t-1)*(x+y) # ly = x + t*(x+y) # ly-lx = x+y # y = ly-lx - x # = ly-lx - (t*lx - (t-1)*ly) # = ly-lx - t*lx + (t-1)*ly # = (-1-t)*lx + (1 + t-1)*ly # = t*ly - (t+1)*lx # # right t'th child is (t+1)/t < x/y < t/(t-1) x/y > 1 # (t+1)*y < t*x # ty+y < tx # t(x-y) > y # t > y/(x-y) # # lx = y + t*(x+y) = t*x + (t+1)y # ly = y + (t-1)*(x+y) = (t-1)x + ty # t*lx - (t+1)*ly # = t*t*x - (t+1)(t-1)x # = (t^2 - (t^2 - 1))x # = x # x = t*lx - (t+1)*ly # # lx-ly = x+y # y = lx-ly - x # = lx - ly - t*lx + (t+1)*ly # = (1-t)*lx + t*ly # = t*ly - (t-1)*lx # # middle odd # lx = x + t*(x+y) = (t+1)x + ty # ly = y + t*(x+y) = tx + (t+1)y # (t+1)*lx - t*ly # = (t+1)*(t+1)*x - t*t*x # = (2t+1)*x # x = ((t+1)*lx - t*ly) / k with 2t+1=k # lx-ly = x-y # y = ly - lx + x # = x-diff # ky = kx-k*diff # # (t+1)*ly - t*lx # = (t+1)*(t+1)*y - t*t*y # = (2t+1)*y # # eg. k=11 x=6 y=5 t=5 -> child_x=6+5*(6+5)=61 child_y=5+5*(6+5)=60 # N=71 digits=5,6 top=6,5 -> 61,60 # low diff=11-10=1 k*ly-k*lx + x # # middle even first, t=k/2 # lx = tx + (t-1)y # eg. x + 2*(x+y) / 3(x+y) = 3x+2y / 3x+3y # ly = tx + ty # y = ly-lx # t*x = ly - t*y # x = ly/t - y # eg k=4 lx=6,ly=10 t=2 y=10-6=4 x=10/2-4=1 # middle even second, t=k/2 # lx = tx + ty # eg. 3*(x+y) / 2*(x+y) + y = 3x+3y / 2x+3y # ly = (t-1)x + ty # x = lx-ly # t*y = lx - t*x # y = lx/t - x my @digits; for (;;) { ### at: "x=$x, y=$y" ### assert: $x==int($x) ### assert: $y==int($y) if ($x < 1 || $y < 1) { ### X,Y negative, no such point ... return undef; } if ($x == $y) { if ($x == $half_k) { ### X=Y=half_k, done: $half_k push @digits, $x; last; } elsif ($x == 1 && $self->{'reduced'}) { ### X=Y=1 reduced, is top middle ... push @digits, $half_k; last; } else { ### X=Y, no such point ... return undef; } } my $diff = $x - $y; if ($diff < 0) { ### X{'reduced'}) { ### no divide k, no such point ... return undef; } $diff *= $k; ### no divide k, diff increased to: $diff } else { ### divide k ... $next_x /= $k; # X = ((t+1)X - tY) / k } $x = $next_x; $y = $next_x - $diff; } else { ### left middle even, t=half_k ... my $next_y = $y - $x; ### $next_y if ($y % $half_k) { ### y not a multiple of half_k ... unless ($self->{'reduced'}) { return undef; } my $g = _gcd($y,$half_k); $y /= $g; $next_y *= $half_k / $g; ($x,$y) = ($y - $next_y, # x = ly/t - y $next_y); # y = ly - lx } else { ### divide half_k ... ($x,$y) = ($y/$half_k - $next_y, # x = ly/t - y $next_y); # y = ly - lx } } push @digits, $half_ceil-1; } } else { ### X>Y, right of row ... if ($diff == 1 && $y < $half_ceil) { ### end at diff=1 ... push @digits, $k+1-$x; last; } my ($t) = _divrem ($x, $diff); ### $t if ($t < $half_ceil) { ($x,$y) = ($t*$x - ($t+1)*$y, $t*$y - ($t-1)*$x); push @digits, $k-$t; } else { if ($k & 1) { ### right middle odd ... # x = ((t+1)*lx - t*ly) / k with 2t+1=k t=(k-1)/2 my $next_x = $half_ceil * $x - $half_k * $y; ### $next_x if ($next_x % $k) { unless ($self->{'reduced'}) { ### no divide k, no such point ... return undef; } $diff *= $k; ### no divide k, diff increased to: $diff } else { ### divide k ... $next_x /= $k; # X = ((t+1)X - tY) / k } $x = $next_x; $y = $next_x - $diff; push @digits, $half_k; } else { ### right middle even ... my $next_x = $x - $y; if ($x % $half_k) { ### x not a multiple of half_k ... unless ($self->{'reduced'}) { return undef; } # multiply lx,ly by half_k/gcd so lx is a multiple of half_k my $g = _gcd($x,$half_k); $x /= $g; $next_x *= $half_k / $g; ($x,$y) = ($next_x, # x = lx-ly $x - $next_x); # y = lx/t - x } else { ### divide half_k ... ($x,$y) = ($next_x, # x = lx-ly $x/$half_k - $next_x); # y = lx/t - x } push @digits, $half_k; } } } } ### @digits if ($self->{'digit_order'} ne 'HtoL') { my $high = pop @digits; @digits = (reverse(@digits), $high); ### reverse digits to: @digits } my $n = digit_join_lowtohigh (\@digits, $k, $zero) + $self->{'n_start'}-1; ### $n # if (! $self->{'reduced'}) { my ($nx,$ny) = $self->n_to_xy($n); ### reversed to: "$nx, $ny cf orig $orig_x, $orig_y" if ($nx != $orig_x || $ny != $orig_y) { return undef; } } ### xy_to_n result: "n=$n" return $n; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### ChanTree rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 1 || $y2 < 1) { return (1,0); } my $zero = ($x1 * 0 * $y1 * $x2 * $y2); # inherit bignum if ($self->{'points'} eq 'all_div') { $x2 *= 2; } my $max = max($x2,$y2); my $level = ($self->{'reduced'} || $self->{'k'} == 2 # k=2 is reduced ? $max + 1 : int($max/2)); return ($self->{'n_start'}, $self->{'n_start'}-2 + ($self->{'k'}+$zero)**$level); } #------------------------------------------------------------------------------ # (N - (Nstart-1))*k + Nstart run -1 to k-2 # = N*k - (Nstart-1)*k + Nstart run -1 to k-2 # = N*k - k*Nstart + k + Nstart run -1 to k-2 # = (N+1)*k + (1-k)*Nstart run -1 to k-2 # k*Nstart - k - Nstart + 1 = (k-1)*(Nstart-1) # = N*k - (k-1)*(Nstart-1) +1 run -1 to k-2 # = N*k - (k-1)*(Nstart-1) run 0 to k-1 # sub tree_n_children { my ($self, $n) = @_; my $n_start = $self->{'n_start'}; unless ($n >= $n_start) { return; } my $k = $self->{'k'}; $n = $n*$k - ($k-1)*($n_start-1); return map {$n+$_} 0 .. $k-1; } sub tree_n_num_children { my ($self, $n) = @_; return ($n >= $self->{'n_start'} ? $self->{'k'} : undef); } # parent = floor((N-Nstart+1) / k) + Nstart-1 # = floor((N-Nstart+1 + k*Nstart-k) / k) # = floor((N + (k-1)*(Nstart-1)) / k) # N-(Nstart-1) >= k # N-Nstart+1 >= k # N-Nstart >= k-1 # N >= k-1+Nstart # N >= k+Nstart-1 sub tree_n_parent { my ($self, $n) = @_; ### ChanTree tree_n_parent(): $n my $n_start = $self->{'n_start'}; $n = $n - ($n_start-1); # to N=1 basis, and warn if $n undef my $k = $self->{'k'}; unless ($n >= $k) { ### root node, no parent ... return undef; } _divrem_mutate ($n, $k); # delete low digit ... return $n + ($n_start-1); } sub tree_n_to_depth { my ($self, $n) = @_; ### ChanTree tree_n_to_depth(): $n $n = $n - $self->{'n_start'} + 1; # N=1 basis, and warn if $n==undef unless ($n >= 1) { return undef; } my ($pow, $exp) = round_down_pow ($n, $self->{'k'}); return $exp; # floor(log base k (N-Nstart+1)) } sub tree_depth_to_n { my ($self, $depth) = @_; return ($depth >= 0 ? $self->{'k'}**$depth + ($self->{'n_start'}-1) : undef); } sub tree_num_roots { my ($self) = @_; return $self->{'k'} - 1; } sub tree_root_n_list { my ($self) = @_; my $n_start = $self->{'n_start'}; return $n_start .. $n_start + $self->{'k'} - 2; } sub tree_n_root { my ($self, $n) = @_; my $n_start_offset = $self->{'n_start'} - 1; $n = $n - $n_start_offset; # N=1 basis, and warn if $n==undef return ($n >= 1 ? _high_digit($n,$self->{'k'}) + $n_start_offset : undef); } # Return the most significant digit of $n written in base $radix. sub _high_digit { my ($n, $radix) = @_; ### assert: ! ($n < 1) my ($pow) = round_down_pow ($n, $radix); _divrem_mutate($n,$pow); # $n=quotient return $n; } 1; __END__ =for stopwords Ryde Math-PlanePath Heng coeffs GCD Calkin-Wilf ie Nstart OEIS k-ary generalization =head1 NAME Math::PlanePath::ChanTree -- tree of rationals =head1 SYNOPSIS use Math::PlanePath::ChanTree; my $path = Math::PlanePath::ChanTree->new (k => 3, reduced => 0); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis path enumerates rationals X/Y in a tree as per =over Song Heng Chan, "Analogs of the Stern Sequence", Integers 2011, L =back The default k=3 visits X,Y with one odd, one even, and perhaps a common factor 3^m. =cut # math-image --path=ChanTree --all --output=numbers_xy --size=62x15 =pod 14 | 728 20 12 13 | 53 11 77 27 12 | 242 14 18 11 | 10 | 80 9 | 17 23 9 15 8 | 26 78 7 | 6 | 8 24 28 5 | 5 3 19 4 | 2 6 10 22 3 | 2 | 0 4 16 52 1 | 1 7 25 79 241 727 Y=0 | +-------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 There are 2 tree roots (so technically it's a "forest") and each node has 3 children. The points are numbered by rows starting from N=0. This numbering corresponds to powers in a polynomial product generating function. N=0 to 1 1/2 2/1 / | \ / | \ N=2 to 7 1/4 4/5 5/2 2/5 5/4 4/1 / | \ ... ... ... ... / | \ N=8 to 25 1/6 6/9 9/4 ... ... 5/9 9/6 6/1 N=26 ... The children of each node are X/Y ------------/ | \----------- | | | X/(2X+Y) (2X+Y)/(X+2Y) (X+2Y)/Y Which as X,Y coordinates means vertical, 45-degree diagonal, and horizontal. X,Y+2X X+(X+Y),Y+(X+Y) | / | / | / | / X,Y------- X+2Y,Y The slowest growth is on the far left of the tree 1/2, 1/4, 1/6, 1/8, etc advancing by just 2 at each level. Similarly on the far right 2/1, 4/1, 6/1, etc. This means that to cover such an X or Y requires a power-of-3, N=3^(max(X,Y)/2). =head2 GCD Chan shows that these top nodes and children visit all rationals X/Y with X,Y one odd, one even. But the X,Y are not in least terms, they may have a power-of-3 common factor GCD(X,Y)=3^m for some m. The GCD is unchanged in the first and third children. The middle child GCD might gain an extra factor 3. This means the power is at most the number of middle legs taken, which is the count of ternary 1-digits of its position across the row. GCD(X,Y) = 3^m m <= count ternary 1-digits of N+1, excluding high digit As per L below, N+1 in ternary has high digit 1 or 2 which indicates the tree root. Ignoring that high digit gives an offset into the row of that tree and the digits are 0,1,2 for left,middle,right. For example the first GCD is at N=9 with X=6,Y=9 common factor GCD=3. N+1=10="101" ternary, which without the high digit is "01" which has a single "1" so GCD <= 3^1. The mirror image of this point is X=9,Y=6 at N=24 and there N+1=24+1=25="221" ternary which without the high digit is "21" with a single 1-digit likewise. For various points the power m is equal to the count of 1-digits. =head2 k Parameter Parameter C $integer> controls the number of children and top nodes. There are k-1 top nodes and each node has k children. The top nodes are k odd, k-1 many tops, with h=ceil(k/2) 1/2 2/3 3/4 ... (h-1)/h h/(h-1) ... 4/3 3/2 2/1 k even, k-1 many tops, with h=k/2 1/2 2/3 3/4 ... (h-1)/h h/h h/(h-1) ... 4/3 3/2 2/1 Notice the list for k odd or k even is the same except that for k even there's an extra middle term h/h. The first few tops are as follows. The list in each row is spread to show how successive bigger h adds terms in the middle. k X/Y top nodes --- --------------------------------- k=2 1/1 k=3 1/2 2/1 k=4 1/2 2/2 2/1 k=5 1/2 2/3 3/2 2/1 k=6 1/2 2/3 3/3 3/2 2/1 k=7 1/2 2/3 3/4 4/3 3/2 2/1 k=8 1/2 2/3 3/4 4/4 4/3 3/2 2/1 As X,Y coordinates these tops are a run up along X=Y-1 and back down along X=Y+1, with a middle X=Y point if k even. For example, =cut # math-image --path=ChanTree,k=13 --output=numbers --expression='i<12?i:0' # math-image --path=ChanTree,k=14 --output=numbers --expression='i<13?i:0' =pod 7 | 5 k=13 top nodes N=0 to N=11 6 | 4 6 total 12 top nodes 5 | 3 7 4 | 2 8 3 | 1 9 2 | 0 10 1 | 11 Y=0 | +------------------------------ X=0 1 2 3 4 5 6 7 k=14 top nodes N=0 to N=12 7 | 5 6 total 13 top nodes 6 | 4 7 5 | 3 8 N=6 is the 7/7 middle term 4 | 2 9 3 | 1 10 2 | 0 11 1 | 12 Y=0 | +------------------------------ X=0 1 2 3 4 5 6 7 Each node has k children. The formulas for the children can be seen from sample cases k=5 and k=6. A node X/Y descends to k=5 k=6 1X+0Y / 2X+1Y 1X+0Y / 2X+1Y 2X+1Y / 3X+2Y 2X+1Y / 3X+2Y 3X+2Y / 2X+3Y 3X+2Y / 3X+3Y 2X+3Y / 1X+2Y 3X+3Y / 2X+3Y 1X+2Y / 0X+1Y 2X+3Y / 1X+2Y 1X+2Y / 0X+1Y The coefficients of X and Y run up to h=ceil(k/2) starting from either 0, 1 or 2 and ending 2, 1 or 0. When k is even there's two h coeffs in the middle. When k is odd there's just one. The resulting tree for example with k=4 is k=4 1/2 2/2 2/1 / \ / \ / \ 1/4 4/6 6/5 5/2 2/6 6/8 8/6 6/2 2/5 5/6 6/4 4/1 Chan shows that this combination of top nodes and children visits if k odd: rationals X/Y with X,Y one odd, one even possible GCD(X,Y)=k^m for some integer m if k even: all rationals X/Y possible GCD(X,Y) a divisor of (k/2)^m When k odd, GCD(X,Y) is a power of k, so for example as described above k=3 gives GCD=3^m. When k even GCD(X,Y) is a divisor of (k/2)^m but not necessarily a full such power. For example with k=12 the first such non-power GCD is at N=17 where X=16,Y=18 has GCD(16,18)=2 which is only a divisor of k/2=6, not a power of 6. =head2 N Start The C $n> option can select a different initial N. The tree structure is unchanged, just the numbering shifted. As noted above the default Nstart=0 corresponds to powers in a generating function. C1> makes the numbering correspond to digits of N written in base-k. For example k=10 corresponds to N written in decimal, N=1 to 9 1/2 ... ... 2/1 N=10 to 99 1/4 4/7 ... ... 7/4 4/1 N=100 to 999 1/6 6/11 ... ... 11/6 6/1 In general C1> makes the tree N written in base-k digits depth = numdigits(N)-1 NdepthStart = k^depth = 100..000 base-k, high 1 in high digit position of N N-NdepthStart = position across whole row of all top trees And the high digit of N selects which top-level tree the given N is under, so N written in base-k digits top tree = high digit of N (1 to k, selecting the k-1 many top nodes) Nrem = digits of N after the highest = position across row within the high-digit tree depth = numdigits(Nrem) # top node depth=0 = numdigits(N)-1 =head2 Diatomic Sequence Chan shows that each denominator Y becomes the numerator X in the next point. The last Y of a row becomes the first X of the next row. This is a generalization of Stern's diatomic sequence and of the Calkin-Wilf tree of rationals. (See L and L.) The case k=2 is precisely the Calkin-Wilf tree. There's just one top node 1/1, being the even k "middle" form h/h with h=k/2=1 as described above. Then there's two children of each node (the "middle" pair of the even k case), k=2, Calkin-Wilf tree X/Y / \ (1X+0Y)/(1X+1Y) (1X+1Y)/(0X+1Y) = X/(X+Y) = (X+Y)/Y =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::ChanTree-Enew ()> =item C<$path = Math::PlanePath::ChanTree-Enew (k =E $k, n_start =E $n)> Create and return a new path object. The defaults are k=3 and n_start=0. =item C<$n = $path-En_start()> Return the first N in the path. This is 0 by default, otherwise the C parameter. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. If there's nothing at C<$x,$y> then return C. =back =head2 Tree Methods XEach point has k children, so the path is a complete k-ary tree. =over =item C<@n_children = $path-Etree_n_children($n)> Return the children of C<$n>, or an empty list if C<$n E n_start()>, ie. before the start of the path. =item C<$num = $path-Etree_n_num_children($n)> Return k, since every node has k children. Or return C if C<$n E n_start()>, ie. before the start of the path. =item C<$n_parent = $path-Etree_n_parent($n)> Return the parent node of C<$n>, or C if C<$n> has no parent either because it's a top node or before C. =item C<$n_root = $path-Etree_n_root ($n)> Return the N which is root node of C<$n>. =item C<$depth = $path-Etree_n_to_depth($n)> Return the depth of node C<$n>, or C if there's no point C<$n>. The tree tops are depth=0, then their children depth=1, etc. =item C<$n = $path-Etree_depth_to_n($depth)> =item C<$n = $path-Etree_depth_to_n_end($depth)> Return the first or last N at tree level C<$depth> in the path. The top of the tree is depth=0. =back =head2 Tree Descriptive Methods =over =item C<$num = $path-Etree_num_roots ()> Return the number of root nodes in C<$path>, which is k-1. For example the default k=3 return 2 as there are two root nodes. =item C<@n_list = $path-Etree_root_n_list ()> Return a list of the N values which are the root nodes of C<$path>. This is C through C inclusive, being the first k-1 many points. For example in the default k=2 and Nstart=0 the return is two values C<(0,1)>. =item C<$num = $path-Etree_num_children_minimum()> =item C<$num = $path-Etree_num_children_maximum()> Return k since every node has k many children, making that both the minimum and maximum. =item C<$bool = $path-Etree_any_leaf()> Return false, since there are no leaf nodes in the tree. =back =head1 FORMULAS =head2 N Children For the default k=3 the children are 3N+2, 3N+3, 3N+4 n_start=0 If C1> then instead 3N, 3N+1, 3N+2 n_start=1 For this C the children are found by appending an extra ternary digit, or base-k digit for arbitrary k. k*N, k*N+1, ... , k*N+(k-1) n_start=1 In general for k and Nstart the children are kN - (k-1)*(Nstart-1) + 0 ... kN - (k-1)*(Nstart-1) + k-1 =head2 N Parent The parent node reverses the children calculation above. The simplest case is C where it's a division to remove the lowest base-k digit parent = floor(N/k) when n_start=1 For other C adjust before and after to an C basis, parent = floor((N-(Nstart-1)) / k) + Nstart-1 For example in the default k=0 Nstart=1 the parent of N=3 is floor((3-(1-1))/3)=1. The post-adjustment can be worked into the formula with (k-1)*(Nstart-1) similar to the children above, parent = floor((N + (k-1)*(Nstart-1)) / k) But the first style is more convenient to compare to see that N is past the top nodes and therefore has a parent. N-(Nstart-1) >= k to check N is past top-nodes =head2 N Root As described under L
above, if Nstart=1 then the tree root is simply the most significant base-k digit of N. For other Nstart an adjustment is made to N=1 style and back again. adjust = Nstart-1 Nroot(N) = high_base_k_digit(N-adjust) + adjust =head2 N to Depth The structure of the tree means depth = floor(logk(N+1)) for n_start=0 For example if k=3 then all of N=8 through N=25 inclusive have depth=floor(log3(N+1))=2. With an C it becomes depth = floor(logk(N-(Nstart-1))) C is the simplest case, being the length of N written in base-k digits. depth = floor(logk(N)) for n_start=1 =head1 OEIS This tree is in Sloane's Online Encyclopedia of Integer Sequences as =over L (etc) =back k=3, n_start=0 (the defaults) A191379 X coordinate, and Y=X(N+n) As noted above k=2 is the Calkin-Wilf tree. See L for "CW" related sequences. =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/CornerReplicate.pm0000644000175000017500000004072413734026673020662 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::CornerReplicate; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'bit_split_lowtohigh', 'digit_split_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; use constant dy_maximum => 1; # dY=1,-1,-3,-7,-15,etc only use constant dsumxy_maximum => 1; use constant ddiffxy_minimum => -1; use constant dir_maximum_dxdy => (2,-1); # ESE use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ my @digit_to_x = (0,1,1,0); my @digit_to_y = (0,0,1,1); sub n_to_xy { my ($self, $n) = @_; ### CornerReplicate n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $x = my $y = ($n * 0); # inherit bignum 0 my $len = $x + 1; # inherit bignum 1 foreach my $digit (digit_split_lowtohigh($n,4)) { ### at: "$x,$y digit=$digit" $x += $digit_to_x[$digit] * $len; $y += $digit_to_y[$digit] * $len; $len *= 2; } ### final: "$x,$y" return ($x,$y); } my @digit_to_next_dx = (1, 0, -1, -1); my @digit_to_next_dy = (0, 1, 0, 0); # use Smart::Comments; sub n_to_dxdy { my ($self, $n) = @_; ### CornerReplicate n_to_dxdy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $zero = $n * 0; my $int = int($n); $n -= $int; # fractional part my $digit = _divrem_mutate($int,4); ### low digit: $digit if ($digit == 0) { # N = "...0" eg. N=0 # ^ # | this dX=1,dY=0 # N---* next dX=0,dY=1 # dX = dXthis*(1-frac) + dXnext*frac # = 1*(1-frac) + 0*frac # = 1-frac # dY = dYthis*(1-frac) + dYnext*frac # = 0*(1-frac) + 1*frac # = frac return (1-$n,$n); } if ($digit == 1) { # N = "...1" eg. N=1 # <---* # | this dX=0,dY=1 # N next dX=-1,dY=0 # dX = dXthis*(1-frac) + dXnext*frac # = 0*(1-frac) + -1*frac # = -frac # dY = dYthis*(1-frac) + dYnext*frac # = 1*(1-frac) + 0*frac # = 1-frac return (-$n,1-$n); } my ($dx,$dy); if ($digit == 2) { # N="...2" # *---N this dX=-1, dY=0 # \ next dX=power, dY=power # \ # power part for next only needed if $n fractional $dx = -1; $dy = 0; if ($n) { # N = "[digit]333..3332" (my $exp, $digit) = _count_low_base4_3s($int); if ($digit == 1) { # N = "1333..3332" so N=6, N=30, N=126, ... # ^ # | this dX=-1, dY=0 # *---N next dX=0, dY=+1 # dX = dXthis*(1-frac) + dXnext*frac # = -1*(1-frac) + 0*frac # = frac-1 # dY = dYthis*(1-frac) + dYnext*frac # = 0*(1-frac) + 1*frac # = frac return ($n-1, $n); } my $next_dx = (2+$zero) ** ($exp+1); my $next_dy; ### power: $dx if ($digit) { # $digit == 2 # N = "2333..3332" so N=10, N=14, N=62, ... # *---N this dX=-1, dY=0 # / next dX=-2^k, dY=-(2^k-1)=1-2^k # / $next_dx = -$next_dx; $next_dy = $next_dx+1; } else { # $digit == 0 # N = "0333..3332" so N=2, N=14, N=62, ... # *---N this dX=-1, dY=0 # \ next dX=+2^k, dY=-(2^k-1)=1-2^k # \ $next_dy = 1-$next_dx; } my $f1 = 1-$n; $dx = $f1*$dx + $n*$next_dx; $dy = $f1*$dy + $n*$next_dy; } } else { # $digit == 3 my ($exp, $digit) = _count_low_base4_3s($int); ### $exp ### $digit if ($digit == 1) { # N = "1333..333" eg. N=31 # N+1 = "2000..000" eg. N=32 # *---> # | this dX=0, dY=+1 # N next dX=+1, dY=0 # dX = dXthis*(1-frac) + dXnext*frac # = 0*(1-frac) + 1*frac # = frac # dY = dYthis*(1-frac) + dYnext*frac # = 1*(1-frac) + 0*frac # = 1-frac return ($n, 1-$n); } $dx = (2+$zero) ** ($exp+1); ### power: $dx if ($digit) { # $digit == 2 # N = "2333..333" so N=11, N=47, N=191 # N # / this dX=-2^k, dY=-(2^k-1)=1-2^k # / next dX=1, dY=0 # *-> $dx = -$dx; $dy = $dx+1; } else { # $digit == 0 # N = "0333..333" so N=3, N=15, N=63, ... # N # \ this dX=2^k, dY=-(2^k-1)=1-2^k # \ next dX=1, dY=0 # *-> $dy = 1-$dx; } if ($n) { # dX*(1-frac) + nextdX*frac # dY*(1-frac) + nextdY*frac # nextdX=1, nextdY=0 my $f1 = 1-$n; $dx = $f1*$dx + $n; $dy = $f1*$dy; } } ### final: "$dx,$dy" return ($dx,$dy); } # Return ($count,$digit) where $count is how many trailing 3s on $n # (possibly 0), and $digit is the next digit above those 3s. sub _count_low_base4_3s { my ($n) = @_; my $count =0; for (;;) { my $digit = _divrem_mutate($n,4); if ($digit != 3) { return ($count,$digit); } $count++; } } # my @yx_to_digit = ([0,1], # [3,2]); sub xy_to_n { my ($self, $x, $y) = @_; ### CornerReplicate xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my @xbits = bit_split_lowtohigh($x); my @ybits = bit_split_lowtohigh($y); my $n = ($x * 0 * $y); # inherit bignum 0 foreach my $i (reverse 0 .. max($#xbits,$#ybits)) { # high to low $n *= 4; my $ydigit = $ybits[$i] || 0; $n += 2*$ydigit + (($xbits[$i]||0) ^ $ydigit); } return $n; } # these tables generated by tools/corner-replicate-table.pl my @min_digit = (0,0,1, 0,0,1, 3,2,2); my @max_digit = (0,1,1, 3,3,2, 3,3,2); # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### CornerReplicate rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### rect: "X = $x1 to $x2, Y = $y1 to $y2" if ($x2 < 0 || $y2 < 0) { ### rectangle outside first quadrant ... return (1, 0); } my ($len, $level) = round_down_pow (max($x2,$y2), 2); ### $len ### $level if (is_infinite($level)) { return (0,$level); } my $n_min = my $n_max = my $x_min = my $y_min = my $x_max = my $y_max = ($x1 * 0 * $x2 * $y1 * $y2); # inherit bignum 0 while ($level-- >= 0) { ### $level { my $x_cmp = $x_max + $len; my $y_cmp = $y_max + $len; my $digit = $max_digit[($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0)]; $n_max = 4*$n_max + $digit; if ($digit_to_x[$digit]) { $x_max += $len; } if ($digit_to_y[$digit]) { $y_max += $len; } # my $key = ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) # + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0); ### max ... ### len: sprintf "%#X", $len ### $x_cmp ### $y_cmp # ### $key ### $digit ### n_max: sprintf "%#X", $n_max ### $x_max ### $y_max } { my $x_cmp = $x_min + $len; my $y_cmp = $y_min + $len; my $digit = $min_digit[($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0)]; $n_min = 4*$n_min + $digit; if ($digit_to_x[$digit]) { $x_min += $len; } if ($digit_to_y[$digit]) { $y_min += $len; } # my $key = ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) # + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0); ### min ... ### len: sprintf "%#X", $len ### $x_cmp ### $y_cmp # ### $key ### $digit ### n_min: sprintf "%#X", $n_min ### $x_min ### $y_min } $len /= 2; } return ($n_min, $n_max); } #------------------------------------------------------------------------------ # levels use Math::PlanePath::HilbertCurve; *level_to_n_range = \&Math::PlanePath::HilbertCurve::level_to_n_range; *n_to_level = \&Math::PlanePath::HilbertCurve::n_to_level; #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Math-PlanePath OEIS bitwise dSum=dX+dY dX dSum =head1 NAME Math::PlanePath::CornerReplicate -- replicating U parts =head1 SYNOPSIS use Math::PlanePath::CornerReplicate; my $path = Math::PlanePath::CornerReplicate->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path is a self-similar replicating corner fill with 2x2 blocks. XIt's sometimes called a "U order" since the base N=0 to N=3 is like a "U" (sideways). 7 | 63--62 59--58 47--46 43--42 | | | | | 6 | 60--61 56--57 44--45 40--41 | | | 5 | 51--50 55--54 35--34 39--38 | | | | | 4 | 48--49 52--53 32--33 36--37 | | 3 | 15--14 11--10 31--30 27--26 | | | | | 2 | 12--13 8-- 9 28--29 24--25 | | | 1 | 3-- 2 7-- 6 19--18 23--22 | | | | | Y=0 | 0-- 1 4-- 5 16--17 20--21 +-------------------------------- X=0 1 2 3 4 5 6 7 The pattern is the initial N=0 to N=3 section, +-------+-------+ | | | | 3 | 2 | | | | +-------+-------+ | | | | 0 | 1 | | | | +-------+-------+ It repeats as 2x2 blocks arranged in the same pattern, then 4x4 blocks, etc. There's no rotations or reflections within sub-parts. X axis N=0,1,4,5,16,17,etc is all the integers which use only digits 0 and 1 in base 4. For example N=17 is 101 in base 4. Y axis N=0,3,12,15,48,etc is all the integers which use only digits 0 and 3 in base 4. For example N=51 is 303 in base 4. The X=Y diagonal N=0,2,8,10,32,34,etc is all the integers which use only digits 0 and 2 in base 4. The X axis is the same as the C. The Y axis here is the X=Y diagonal of the C, and conversely the X=Y diagonal here is the Y axis of the C. The N value at a given X,Y is converted to or from the C by transforming base-4 digit values 2E-E3. This can be done by a bitwise "X xor Y". When Y has a 1-bit the xor swaps 2E-E3 in N. ZOrder X = CRep X xor CRep Y ZOrder Y = CRep Y CRep X = ZOrder X xor ZOrder Y CRep Y = ZOrder Y See L for a rotating corner form. =head2 Level Ranges A given replication extends to Nlevel = 4^level - 1 0 <= X < 2^level 0 <= Y < 2^level =head2 Hamming Distance The Hamming distance between two integers X and Y is the number of bit positions where the two values differ when written in binary. In this corner replicate each bit-pair of N becomes a bit of X and a bit of Y, N X Y ------ --- --- 0 = 00 0 0 1 = 01 1 0 <- difference 1 bit 2 = 10 1 1 3 = 11 0 1 <- difference 1 bit So the Hamming distance is the number of base4 bit-pairs of N which are 01 or 11. Counting bit positions from 0 for least significant bit, this is the 1-bits in even positions, HammingDist(X,Y) = count 1-bits at even bit positions in N = 0,1,0,1, 1,2,1,2, 0,1,0,1, 1,2,1,2, ... (A139351) =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::CornerReplicate-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 4**$level - 1)>. =back =head1 FORMULAS =head2 N to dX,dY The change dX,dY is given by N in base 4 count trailing 3s and the digit above those trailing 3s. N = ...[d]333...333 base 4 \--exp--/ When N to N+1 crosses between 4^k blocks it goes as follows. Within a block the pattern is the same, since there's no rotations or transposes etc. N, N+1 X Y dX dY dSum dDiffXY -------- ----- ------- ----- -------- ------ ------- 033..33 0 2^k-1 2^k -(2^k-1) +1 2*2^k-1 100..00 2^k 0 133..33 2^k 2^k-1 0 +1 +1 -1 200..00 2^k 2^k 133..33 2^k 2*2^k-1 -2^k 1-2^k -(2^k-1) -1 200..00 0 2^k 133..33 0 2*2^k-1 2*2^k -(2*2^k-1) +1 4*2^k-1 200..00 2*2^k 0 It can be noted dSum=dX+dY the change in X+Y is at most +1, taking values 1, -1, -3, -7, -15, etc. The crossing from block 2 to 3 drops back, such as at N=47="233" to N=48="300". Everywhere else it advances by +1 anti-diagonal. The difference dDiffXY=dX-dY the change in X-Y decreases at most -1, taking similar values -1, 1, 3, 7, 15, etc but in a different order to dSum. =head1 OEIS This path is in Sloane's Online Encyclopedia of Integer Sequences as =over L (etc) =back A059906 Y coordinate A059905 X xor Y, being ZOrderCurve X A139351 HammingDist(X,Y), count 1-bits at even positions in N A000695 N on X axis, base 4 digits 0,1 only A001196 N on Y axis, base 4 digits 0,3 only A062880 N on diagonal, base 4 digits 0,2 only A163241 permutation base-4 flip 2<->3, converts N to ZOrderCurve N, and back A048647 permutation N at transpose Y,X base4 digits 1<->3 =head1 SEE ALSO L, L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/CellularRule54.pm0000644000175000017500000003130113734026673020334 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=CellularRule54 --all --scale=10 # math-image --path=CellularRule54 --all --output=numbers --size=132x50 package Math::PlanePath::CellularRule54; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem = \&Math::PlanePath::_divrem; *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; sub x_negative_at_n { my ($self) = @_; return $self->n_start + 1; } use constant sumxy_minimum => 0; # triangular X>=-Y so X+Y>=0 use constant diffxy_maximum => 0; # triangular X<=Y so X-Y<=0 use constant dx_maximum => 4; use constant dy_minimum => 0; use constant dy_maximum => 1; use constant absdx_minimum => 1; use constant dsumxy_maximum => 4; # straight East dX=+4 use constant ddiffxy_maximum => 4; # straight East dX=+4 use constant dir_maximum_dxdy => (-1,0); # supremum, West and dY=+1 up #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # left add # even y=0 0 1 # 2 1 2 # 4 3 3 # 6 6 4 # left = y/2*(y/2+1)/2 # = y*(y+2)/8 of 4-cell figures # inverse y = -1 + sqrt(2 * $n + -1) # # left add # odd y=1 0 3 # 3 3 6 # 5 9 9 # 7 18 12 # left = 3*(y-1)/2*((y-1)/2+1)/2 # = 3*(y-1)*(y+1)/8 of 4-cell figures # # nbase y even = y*(y+2)/8 + 3*((y+1)-1)*((y+1)+1)/8 # = [ y*(y+2) + 3*y*(y+2) ] / 8 # = y*(y+2)/2 # y=0 nbase=0 # y=2 nbase=4 # y=4 nbase=12 # y=6 nbase=24 # # nbase y odd = 3*(y-1)*(y+1)/8 + (y+1)*(y+3)/8 # = (y+1) * (3y-3 + y+3)/8 # = (y+1)*4y/8 # = y*(y+1)/2 # y=1 nbase=1 # y=3 nbase=6 # y=5 nbase=15 # y=7 nbase=28 # inverse y = -1/2 + sqrt(2 * $n + -7/4) # = sqrt(2n-7/4) - 1/2 # = (2*sqrt(2n-7/4) - 1)/2 # = (sqrt(4n-7)-1)/2 # # dual # d = [ 0, 1, 2, 3 ] # N = [ 1, 5, 13, 25 ] # N = (2 d^2 + 2 d + 1) # = ((2*$d + 2)*$d + 1) # d = -1/2 + sqrt(1/2 * $n + -1/4) # = sqrt(1/2 * $n + -1/4) - 1/2 # = [ 2*sqrt(1/2 * $n + -1/4) - 1 ] / 2 # = [ sqrt(4/2 * $n + -4/4) - 1 ] / 2 # = [ sqrt(2*$n - 1) - 1 ] / 2 # sub n_to_xy { my ($self, $n) = @_; ### CellularRule54 n_to_xy(): $n $n = $n - $self->{'n_start'}; # to N=0 basis my $frac; { my $int = int($n); $frac = $n - $int; $n = $int; # BigFloat int() gives BigInt, use that if (2*$frac >= 1) { # $frac>=0.5 and BigInt friendly $frac -= 1; $n += 1; } # -0.5 <= $frac < 0.5 ### assert: $frac >= -0.5 ### assert: $frac < 0.5 } if ($n < 0) { return; } # d is the two-row group number, d=2*y, where n belongs # start of the two-row group is nbase = 2 d^2 + 2 d starting from N=0 # my $d = int((_sqrtint(2*$n+1) - 1) / 2); $n -= (2*$d + 2)*$d; # remainder within two-row ### $d ### remainder: $n if ($n <= $d) { # d+1 many points in the Y=0,2,4,6 etc even row, spaced 4*n apart $d *= 2; # y=2*d return ($frac + 4*$n - $d, $d); } else { # 3*d many points in the Y=1,3,5,7 etc odd row, using 3 in 4 cells $n -= $d+1; # remainder 0 upwards into odd row $d = 2*$d+1; # y=2*d+1 my ($q) = _divrem($n,3); return ($frac + $n + $q - $d, $d); } } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### CellularRule54 xy_to_n(): "$x,$y" if ($y < 0 || $x < -$y || $x > $y) { return undef; } $x += $y; ### x centred: $x if ($y % 2) { ### odd row, 3 in 4 ... if (($x % 4) == 3) { return undef; } return $x - int($x/4) + $y*($y+1)/2 + $self->{'n_start'}; } else { ## even row, sparse ... if ($x % 4) { return undef; } return $x/4 + $y*($y+2)/2 + $self->{'n_start'}; } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### CellularRule54 rect_to_n_range(): "$x1,$y1, $x2,$y2" ($x1,$y1, $x2,$y2) = _rect_for_V ($x1,$y1, $x2,$y2) or return (1,0); # rect outside pyramid my $zero = ($x1 * 0 * $y1 * $x2 * $y2); # inherit bignum # nbase y even y*(y+2)/2 # nbase y odd y*(y+1)/2 # y even end (y+1)*(y+2)/2 # y odd end (y+1)*(y+3)/2 $y2 += 1; return (# even/odd left end $zero + $y1*($y1 + 2-($y1%2))/2 + $self->{'n_start'}, # even/odd right end $zero + $y2*($y2 + 2-($y2%2))/2 + $self->{'n_start'} - 1); } # Return ($x1,$y1, $x2,$y2) which is the rectangle part chopped to the top # row entirely within the pyramid V and the bottom row partly within. # sub _rect_for_V { my ($x1,$y1, $x2,$y2) = @_; ### _rect_for_V(): "$x1,$y1, $x2,$y2" $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # swap to y1<=y2 unless ($y2 >= 0) { ### rect all negative, no N ... return; } unless ($y1 >= 0) { # increase y1 to zero, including negative infinity discarded $y1 = 0; } $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } # swap to x1<=x2 my $neg_y2 = -$y2; # \ / # y2 \ / +----- # \ / | # \ / # \/ x1 # # \ / # ----+ \ / y2 # | \ / # \ / # x2 \/ # if ($x1 > $y2 # off to the right || $x2 < $neg_y2) { # off to the left ### rect all off to the left or right, no N return; } # \ / x2 # \ +------+ y2 # \ | / | # \ +------+ # \/ # if ($x2 > $y2) { ### top-right beyond pyramid, reduce ... $x2 = $y2; } # # x1 \ / # y2 +--------+ / y2 # | \ | / # +--------+/ # \/ # if ($x1 < $neg_y2) { ### top-left beyond pyramid, increase ... $x1 = $neg_y2; } # \ | / # \ |/ # \ /| | # y1 \ / +-------+ # \/ x1 # # \| / # \ / # |\ / # -------+ \ / y1 # x2 \/ # # in both of the following y1=x2 or y1=-x2 leaves y1<=y2 because have # already established some part of the rectangle is in the V shape # if ($x1 > $y1) { ### x1 off to the right, so y1 row is outside, increase y1 ... $y1 = $x1; } elsif ((my $neg_x2 = -$x2) > $y1) { ### x2 off to the left, so y1 row is outside, increase y1 ... $y1 = $neg_x2; } # values ordered ### assert: $x1 <= $x2 ### assert: $y1 <= $y2 # top row x1..x2 entirely within pyramid ### assert: $x1 >= -$y2 ### assert: $x2 <= $y2 # bottom row x1..x2 some part within pyramid ### assert: $x1 <= $y1 ### assert: $x2 >= -$y1 return ($x1,$y1, $x2,$y2); } 1; __END__ =for stopwords straight-ish Ryde Math-PlanePath ie hexagonals 18-gonal Xmax-Xmin Nleft Nright OEIS =head1 NAME Math::PlanePath::CellularRule54 -- cellular automaton points =head1 SYNOPSIS use Math::PlanePath::CellularRule54; my $path = Math::PlanePath::CellularRule54->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis is the pattern of Stephen Wolfram's "rule 54" cellular automaton =over L =back arranged as rows, 29 30 31 . 32 33 34 . 35 36 37 . 38 39 40 7 25 . . . 26 . . . 27 . . . 28 6 16 17 18 . 19 20 21 . 22 23 24 5 13 . . . 14 . . . 15 4 7 8 9 . 10 11 12 3 5 . . . 6 2 2 3 4 1 1 <- Y=0 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 The initial figure N=1,2,3,4 repeats in two-row groups with 1 cell gap between figures. Each two-row group has one extra figure, for a step of 4 more points than the previous two-row. XThe rightmost N on the even rows Y=0,2,4,6 etc is the hexagonal numbers N=1,6,15,28, etc k*(2k-1). The hexagonal numbers of the "second kind" 1, 3, 10, 21, 36, etc j*(2j+1) are a steep sloping line upwards in the middle too. Those two taken together are the Xtriangular numbers 1,3,6,10,15 etc, k*(k+1)/2. The 18-gonal numbers 18,51,100,etc are the vertical line at X=-3 on every fourth row Y=5,9,13,etc. =head2 Row Ranges The left end of each row is Nleft = Y*(Y+2)/2 + 1 if Y even Y*(Y+1)/2 + 1 if Y odd The right end is Nright = (Y+1)*(Y+2)/2 if Y even (Y+1)*(Y+3)/2 if Y odd = Nleft(Y+1) - 1 ie. 1 before next Nleft The row width Xmax-Xmin is 2*Y but with the gaps the number of visited points in a row is less than that, being either about 1/4 or 3/4 of the width on even or odd rows. rowpoints = Y/2 + 1 if Y even 3*(Y+1)/2 if Y odd For any Y of course the Nleft to Nright difference is the number of points in the row too rowpoints = Nright - Nleft + 1 =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same pattern. For example to start at 0, =cut # math-image --path=CellularRule54,n_start=0 --all --output=numbers --size=75x6 =pod n_start => 0 15 16 17 18 19 20 21 22 23 5 12 13 14 4 6 7 8 9 10 11 3 4 5 2 1 2 3 1 0 <- Y=0 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::CellularRule54-Enew ()> =item C<$path = Math::PlanePath::CellularRule54-Enew (n_start =E $n)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each cell as a square of side 1. If C<$x,$y> is outside the pyramid or on a skipped cell the return is C. =back =head1 OEIS This pattern is in Sloane's Online Encyclopedia of Integer Sequences in a couple of forms, =over L (etc) =back A118108 whole-row used cells as bits of a bignum A118109 1/0 used and unused cells across rows =head1 SEE ALSO L, L, L, L, L L L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/TerdragonCurve.pm0000644000175000017500000010542413734026654020531 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # points singles A052548 2^n + 2 # points doubles A000918 2^n - 2 # points triples A028243 3^(n-1) - 2*2^(n-1) + 1 cf A[k] = 2*3^(k-1) - 2*2^(k-1) # T(3*N) = (w+1)*T(N) dir(N)=w^(2*count1digits) # T(3*N+1) = (w+1)*T(N) + 1*dir(N) # T(3*N+2) = (w+1)*T(N) + w*dir(N) # T(0*3^k + N) = T(N) # T(1*3^k + N) = 2^k + w^2*T(N) # rotate and offset # T(2*3^k + N) = w*2^k + T(N) # offset only package Math::PlanePath::TerdragonCurve; use 5.004; use strict; use List::Util 'first'; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use Math::PlanePath; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest', 'xy_is_even'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh', 'round_up_pow'; use vars '$VERSION', '@ISA'; $VERSION = 129; @ISA = ('Math::PlanePath'); use Math::PlanePath::TerdragonMidpoint; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; use constant parameter_info_array => [ { name => 'arms', share_key => 'arms_6', display => 'Arms', type => 'integer', minimum => 1, maximum => 6, default => 1, width => 1, description => 'Arms', } ]; { my @x_negative_at_n = (undef, 13, 5, 5, 6, 7, 8); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } { my @y_negative_at_n = (undef, 159, 75, 20, 11, 9, 10); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } sub dx_minimum { my ($self) = @_; return ($self->{'arms'} == 1 ? -1 : -2); } use constant dx_maximum => 2; use constant dy_minimum => -1; use constant dy_maximum => 1; sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; return ($self->{'arms'} == 1 ? Math::PlanePath::_UNDOCUMENTED__dxdy_list_three() : Math::PlanePath::_UNDOCUMENTED__dxdy_list_six()); } { my @_UNDOCUMENTED__dxdy_list_at_n = (undef, 4, 9, 13, 7, 8, 5); sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $_UNDOCUMENTED__dxdy_list_at_n[$self->{'arms'}]; } } use constant absdx_minimum => 1; use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; # arms=1 curve goes at 0,120,240 degrees # arms=2 second +60 to 60,180,300 degrees # so when arms==1 dir maximum is 240 degrees sub dir_maximum_dxdy { my ($self) = @_; return ($self->{'arms'} == 1 ? (-1,-1) # 0,2,4 only South-West : ( 1,-1)); # rotated to 1,3,5 too South-East } use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(6, $self->{'arms'} || 1)); return $self; } my @dir6_to_si = (1,0,0, -1,0,0); my @dir6_to_sj = (0,1,0, 0,-1,0); my @dir6_to_sk = (0,0,1, 0,0,-1); sub n_to_xy { my ($self, $n) = @_; ### TerdragonCurve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } my $zero = ($n * 0); # inherit bignum 0 my $i = 0; my $j = 0; my $k = 0; my $si = $zero; my $sj = $zero; my $sk = $zero; # initial rotation from arm number { my $int = int($n); my $frac = $n - $int; # inherit possible BigFloat $n = $int; # BigFloat int() gives BigInt, use that my $rot = _divrem_mutate ($n, $self->{'arms'}); my $s = $zero + 1; # inherit bignum 1 if ($rot >= 3) { $s = -$s; # rotate 180 $frac = -$frac; $rot -= 3; } if ($rot == 0) { $i = $frac; $si = $s; } # rotate 0 elsif ($rot == 1) { $j = $frac; $sj = $s; } # rotate +60 else { $k = $frac; $sk = $s; } # rotate +120 } foreach my $digit (digit_split_lowtohigh($n,3)) { ### at: "$i,$j,$k side $si,$sj,$sk" ### $digit if ($digit == 1) { ($i,$j,$k) = ($si-$j, $sj-$k, $sk+$i); # rotate +120 and add } elsif ($digit == 2) { $i -= $sk; # add rotated +60 $j += $si; $k += $sj; } # add rotated +60 ($si,$sj,$sk) = ($si - $sk, $sj + $si, $sk + $sj); } ### final: "$i,$j,$k side $si,$sj,$sk" ### is: (2*$i + $j - $k).",".($j+$k) return (2*$i + $j - $k, $j+$k); } # all even points when arms==6 sub xy_is_visited { my ($self, $x, $y) = @_; if ($self->{'arms'} == 6) { return xy_is_even($self,$x,$y); } else { return defined($self->xy_to_n($x,$y)); } } sub xy_to_n { return scalar((shift->xy_to_n_list(@_))[0]); } sub xy_to_n_list { my ($self, $x,$y) = @_; ### TerdragonCurve xy_to_n_list(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); { # nothing at an odd point, and trap overflows in $x+$y dividing out b my $sum = abs($x) + abs($y); if (is_infinite($sum)) { return $sum; } # infinity if ($sum % 2) { return; } } if ($x==0 && $y==0) { return 0 .. $self->{'arms'}-1; } my $arms_count = $self->arms_count; my $zero = ($x * 0 * $y); # inherit bignum 0 my @n_list; foreach my $d (0,1,2) { my ($ndigits,$arm) = _xy_d_to_ndigits_and_arm($x,$y,$d); next if $arm >= $arms_count; my $odd = ($arm & 1); if ($odd) { @$ndigits = (map {2-$_} @$ndigits); ### flip to: $ndigits } push @n_list, (digit_join_lowtohigh($ndigits, 3, $zero) + $odd) * $arms_count + $arm; } ### @n_list return sort {$a<=>$b} @n_list; } my @x_to_digit = (0, 2, 1); # digit = -X mod 3 my @digit_to_x = ([0,2,1], [0,-1,-2], [0,-1, 1]); my @digit_to_y = ([0,0,1], [0, 1, 0], [0,-1,-1]); # $d = 0,1,2 for segment leaving $x,$y at direction $d*120 degrees. # For odd arms the digits are 0<->2 reversals. sub _xy_d_to_ndigits_and_arm { my ($x,$y, $d) = @_; my @ndigits; my $arm; for (;;) { ### at: "$x,$y d=$d" if ($x==0 && $y==0) { $arm = 2*$d; last; } if ($d==0 && $x==-2 && $y==0) { $arm = 3; last; } if ($d==2 && $x==1 && $y==1) { $arm = 1; last; } if ($d==1 && $x==1 && $y==-1) { $arm = 5; last; } my $digit = $x_to_digit[$x%3]; push @ndigits, $digit; if ($digit == 1) { $d = ($d-1) % 3; } $x -= $digit_to_x[$d]->[$digit]; $y -= $digit_to_y[$d]->[$digit]; ### $digit ### new d: $d ### subtract: "$digit_to_x[$d]->[$digit],$digit_to_y[$d]->[$digit] to $x,$y" # ### assert: ($x+$y) % 2 == 0 # ### assert: $x % 3 == 0 # ### assert: ($y-$x/3) % 2 == 0 ($x,$y) = (($x+$y)/2, # divide b = w6+1 ($y-$x/3)/2); } ### $arm ### @ndigits return (\@ndigits, $arm); } # x+y*w3 # (x-y)+y*w3 # x/2 + y*sqrt3i/2 # sqrt3i/2 = w3+1/2 # x/2 + y*(w3+1/2) == 1/2*(x+y) + y*w3 # a = x+y = (x+3*y)/2 # GP-Test my(x=0,y=0); (-x)%3 == 0 # GP-Test my(x=2,y=0); (-x)%3 == 1 # GP-Test my(x=1,y=1); (-x)%3 == 2 # minimum -- no, not quite right # # *----------* # \ # \ * # * \ # \ # *----------* # # width = side/2 # minimum = side*sqrt(3)/2 - width # = side*(sqrt(3)/2 - 1) # # minimum 4/9 * 2.9^level roughly # h = 4/9 * 2.9^level # 2.9^level = h*9/4 # level = log(h*9/4)/log(2.9) # 3^level = 3^(log(h*9/4)/log(2.9)) # = h*9/4, but big bigger for log # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### TerdragonCurve rect_to_n_range(): "$x1,$y1 $x2,$y2" my $xmax = int(max(abs($x1),abs($x2))); my $ymax = int(max(abs($y1),abs($y2))); return (0, ($xmax*$xmax + 3*$ymax*$ymax + 1) * 2 * $self->{'arms'}); } # direction # my @dir6_to_dx = (2, 1,-1,-2, -1, 1); my @dir6_to_dy = (0, 1, 1, 0, -1,-1); my @digit_to_nextturn = (2,-2); sub n_to_dxdy { my ($self, $n) = @_; ### n_to_dxdy(): $n if ($n < 0) { return; # first direction at N=0 } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); # integer part $n -= $int; # fraction part # initial direction from arm my $dir6 = _divrem_mutate ($int, $self->{'arms'}); my @ndigits = digit_split_lowtohigh($int,3); $dir6 += 2 * scalar(grep {$_==1} @ndigits); # count 1s for total turn $dir6 %= 6; my $dx = $dir6_to_dx[$dir6]; my $dy = $dir6_to_dy[$dir6]; if ($n) { # fraction part # find lowest non-2 digit, or zero if all 2s or no digits at all $dir6 += $digit_to_nextturn[ first {$_!=2} @ndigits, 0]; $dir6 %= 6; $dx += $n*($dir6_to_dx[$dir6] - $dx); $dy += $n*($dir6_to_dy[$dir6] - $dy); } return ($dx, $dy); } #----------------------------------------------------------------------------- # eg. arms=5 0 .. 5*3^k step by 5s # 1 .. 5*3^k+1 step by 5s # 4 .. 5*3^k+4 step by 5s # sub level_to_n_range { my ($self, $level) = @_; return (0, (3**$level + 1) * $self->{'arms'} - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, $self->{'arms'}); my ($pow, $exp) = round_up_pow ($n, 3); return $exp; } #----------------------------------------------------------------------------- # right boundary N # mixed radix binary, ternary # no 11, 12, 20 # 11 -> 21, including low digit # run of 11111 becomes 22221 # low to high 1 or 0 <- 0 cannot 20 can 10 00 # 2 or 0 <- 1 cannot 11 can 21 01 # 2 or 0 <- 2 cannot 12 can 02 22 sub _UNDOCUMENTED__right_boundary_i_to_n { my ($self, $i) = @_; my @digits = _digit_split_mix23_lowtohigh($i); for ($i = $#digits; $i >= 1; $i--) { # high to low if ($digits[$i] == 1 && $digits[$i-1] != 0) { $digits[$i] = 2; } } return digit_join_lowtohigh(\@digits, 3, $i*0); # { # for (my $i = 0; $i < $#digits; $i++) { # low to high # if ($digits[$i+1] == 1 && ($digits[$i] == 1 || $digits[$i] == 2)) { # $digits[$i+1] = 2; # } # } # return digit_join_lowtohigh(\@digits,3); # } } # Return a list of digits, low to high, which is a mixed radix # representation low digit ternary and the rest binary. sub _digit_split_mix23_lowtohigh { my ($n) = @_; if ($n == 0) { return (); } my $low = _divrem_mutate($n,3); return ($low, digit_split_lowtohigh($n,2)); } { # disallowed digit pairs $disallowed[high][low] my @disallowed; $disallowed[1][1] = 1; $disallowed[1][2] = 1; $disallowed[2][0] = 1; sub _UNDOCUMENTED__n_segment_is_right_boundary { my ($self, $n) = @_; if (is_infinite($n)) { return 0; } unless ($n >= 0) { return 0; } $n = int($n); # no boundary when arms=6, right boundary is only in arm 0 { my $arms = $self->{'arms'}; if ($arms == 6) { return 0; } if (_divrem_mutate($n,$arms)) { return 0; } } my $prev = _divrem_mutate($n,3); while ($n) { my $digit = _divrem_mutate($n,3); if ($disallowed[$digit][$prev]) { return 0; } $prev = $digit; } return 1; } } #----------------------------------------------------------------------------- # left boundary N # mixed 0,1, 2, 10, 11, 12, 100, 101, 102, 110, 111, 112, 1000, 1001, 1002, 1010, 1011, 1012, 1100, 1101, 1102, # vals 0,1,12,120,121,122,1200,1201,1212,1220,1221,1222,12000,12001,12012,12120,12121,12122,12200,12201,12212, { my @_UNDOCUMENTED__left_boundary_i_to_n = ([0,2], # 0 [0,2], # 1 [1,2]); # 2 sub _UNDOCUMENTED__left_boundary_i_to_n { my ($self, $i, $level) = @_; ### _UNDOCUMENTED__left_boundary_i_to_n(): $i ### $level if (defined $level && $level < 0) { if ($i <= 2) { return $i; } $i += 2; } my @digits = _digit_split_mix23_lowtohigh($i); ### @digits if (defined $level) { if ($level >= 0) { if (@digits > $level) { ### beyond given level ... return undef; } # pad for $level, total $level many digits push @digits, (0) x ($level - scalar(@digits)); } else { ### union all levels ... pop @digits; if ($digits[-1]) { push @digits, 0; # high 0,1 or 0,2 when i=3 } else { $digits[-1] = 1; # high 1 } } } else { ### infinite curve, an extra high 0 ... push @digits, 0; } ### @digits my $prev = $digits[0]; foreach my $i (1 .. $#digits) { $prev = $digits[$i] = $_UNDOCUMENTED__left_boundary_i_to_n[$prev][$digits[$i]]; } ### ternary: @digits return digit_join_lowtohigh(\@digits, 3, $i*0); } } { # disallowed digit pairs $disallowed[high][low] my @disallowed; $disallowed[0][2] = 1; $disallowed[1][0] = 1; $disallowed[1][1] = 1; sub _UNDOCUMENTED__n_segment_is_left_boundary { my ($self, $n, $level) = @_; ### _UNDOCUMENTED__n_segment_is_left_boundary(): $n ### $level if (is_infinite($n)) { return 0; } unless ($n >= 0) { return 0; } $n = int($n); if (defined $level && $level == 0) { ### level 0 curve, N=0 is only segment: ($n == 0) return ($n == 0); } { my $arms = $self->{'arms'}; if ($arms == 6) { return 0; } my $arm = _divrem_mutate($n,$arms); if ($arm != $arms-1) { return 0; } } my $prev = _divrem_mutate($n,3); if (defined $level) { $level -= 1; } for (;;) { if (defined $level && $level == 0) { ### end of level many digits, must be N < 3**$level return ($n == 0); } last unless $n; my $digit = _divrem_mutate($n,3); if ($disallowed[$digit][$prev]) { return 0; } if (defined $level) { $level -= 1; } $prev = $digit; } return ((defined $level && $level < 0) # union all levels || ($prev != 2)); # not high 2 otherwise } sub _UNDOCUMENTED__n_segment_is_any_left_boundary { my ($self, $n) = @_; my $prev = _divrem_mutate($n,3); while ($n) { my $digit = _divrem_mutate($n,3); if ($disallowed[$digit][$prev]) { return 0; } $prev = $digit; } return 1; } # sub left_boundary_n_pred { # my ($n) = @_; # my $n3 = '0' . Math::BaseCnv::cnv($n,10,3); # return ($n3 =~ /02|10|11/ ? 0 : 1); # } } sub _UNDOCUMENTED__n_segment_is_boundary { my ($self, $n, $level) = @_; return $self->_UNDOCUMENTED__n_segment_is_right_boundary($n) || $self->_UNDOCUMENTED__n_segment_is_left_boundary($n,$level); } 1; __END__ # old n_to_xy() # # # initial rotation from arm number # my $arms = $self->{'arms'}; # my $rot = $n % $arms; # $n = int($n/$arms); # my @digits; # my (@si, @sj, @sk); # vectors # { # my $si = $zero + 1; # inherit bignum 1 # my $sj = $zero; # inherit bignum 0 # my $sk = $zero; # inherit bignum 0 # # for (;;) { # push @digits, ($n % 3); # push @si, $si; # push @sj, $sj; # push @sk, $sk; # ### push: "digit $digits[-1] $si,$sj,$sk" # # $n = int($n/3) || last; # # # straight + rot120 + straight # ($si,$sj,$sk) = (2*$si - $sj, # 2*$sj - $sk, # 2*$sk + $si); # } # } # ### @digits # # my $i = $zero; # my $j = $zero; # my $k = $zero; # while (defined (my $digit = pop @digits)) { # digits high to low # my $si = pop @si; # my $sj = pop @sj; # my $sk = pop @sk; # ### at: "$i,$j,$k $digit side $si,$sj,$sk" # ### $rot # # $rot %= 6; # if ($rot == 1) { ($si,$sj,$sk) = (-$sk,$si,$sj); } # elsif ($rot == 2) { ($si,$sj,$sk) = (-$sj,-$sk,$si); } # elsif ($rot == 3) { ($si,$sj,$sk) = (-$si,-$sj,-$sk); } # elsif ($rot == 4) { ($si,$sj,$sk) = ($sk,-$si,-$sj); } # elsif ($rot == 5) { ($si,$sj,$sk) = ($sj,$sk,-$si); } # # if ($digit) { # $i += $si; # digit=1 or digit=2 # $j += $sj; # $k += $sk; # if ($digit == 2) { # $i -= $sj; # digit=2, straight+rot120 # $j -= $sk; # $k += $si; # } else { # $rot += 2; # digit=1 # } # } # } # # $rot %= 6; # $i = $frac * $dir6_to_si[$rot] + $i; # $j = $frac * $dir6_to_sj[$rot] + $j; # $k = $frac * $dir6_to_sk[$rot] + $k; # # ### final: "$i,$j,$k" # return (2*$i + $j - $k, $j+$k); =for stopwords eg Ryde Dragon Math-PlanePath Nlevel Knuth et al vertices doublings OEIS terdragon ie morphism si,sj,sk dX,dY Pari rhombi dX si Ns unexpand unpoint =head1 NAME Math::PlanePath::TerdragonCurve -- triangular dragon curve =head1 SYNOPSIS use Math::PlanePath::TerdragonCurve; my $path = Math::PlanePath::TerdragonCurve->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XXThis is the terdragon curve by Davis and Knuth, =over Chandler Davis and Donald Knuth, "Number Representations and Dragon Curves -- I", Journal Recreational Mathematics, volume 3, number 2 (April 1970), pages 66-81 and "Number Representations and Dragon Curves -- II", volume 3, number 3 (July 1970), pages 133-149. Reprinted with addendum in Knuth "Selected Papers on Fun and Games", 2010, pages 571--614. L =back Points are a triangular grid using every second integer X,Y as per L, beginning \ / \ --- 26,29,32 ---------- 27 6 / \ \ / \ -- 24,33,42 ---------- 22,25 5 / \ / \ \ / \ --- 20,23,44 -------- 12,21 10 4 / \ / \ / \ \ / \ / \ / \ 18,45 --------- 13,16,19 ------ 8,11,14 -------- 9 3 \ / \ / \ \ / \ / \ 17 6,15 --------- 4,7 2 \ / \ \ / \ 2,5 ---------- 3 1 \ \ 0 ----------- 1 <-Y=0 ^ ^ ^ ^ ^ ^ ^ -3 -2 -1 X=0 1 2 3 The base figure is an "S" shape 2-----3 \ \ 0-----1 which then repeats in self-similar style, so N=3 to N=6 is a copy rotated +120 degrees, which is the angle of the N=1 to N=2 edge, 6 4 base figure repeats \ / \ as N=3 to N=6, \/ \ rotated +120 degrees 5 2----3 \ \ 0-----1 Then N=6 to N=9 is a plain horizontal, which is the angle of N=2 to N=3, 8-----9 base figure repeats \ as N=6 to N=9, \ no rotation 6----7,4 \ / \ \ / \ 5,2----3 \ \ 0-----1 Notice X=1,Y=1 is visited twice as N=2 and N=5. Similarly X=2,Y=2 as N=4 and N=7. Each point can repeat up to 3 times. "Inner" points are 3 times and on the edges up to 2 times. The first tripled point is X=1,Y=3 which as shown above is N=8, N=11 and N=14. The curve never crosses itself. The vertices touch as triangular corners and no edges repeat. The curve turns are the same as the C, but here the turns are by 120 degrees each whereas C is 60 degrees each. The extra angle here tightens up the shape. =head2 Spiralling The first step N=1 is to the right along the X axis and the path then slowly spirals anti-clockwise and progressively fatter. The end of each replication is Nlevel = 3^level That point is at level*30 degrees around (as reckoned with Y*sqrt(3) for a triangular grid). Nlevel X, Y Angle (degrees) ------ ------- ----- 1 1, 0 0 3 3, 1 30 9 3, 3 60 27 0, 6 90 81 -9, 9 120 243 -27, 9 150 729 -54, 0 180 The following is points N=0 to N=3^6=729 going half-circle around to 180 degrees. The N=0 origin is marked "0" and the N=729 end is marked "E". =cut # the following generated by # math-image --path=TerdragonCurve --expression='i<=729?i:0' --text --size=132x40 =pod * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * E * * * * * * * * * * * * * * * * 0 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * =head2 Tiling The little "S" shapes of the base figure N=0 to N=3 can be thought of as a rhombus 2-----3 . . . . 0-----1 The "S" shapes of each 3 points make a tiling of the plane with those rhombi \ \ / / \ \ / / *-----*-----* *-----*-----* / / \ \ / / \ \ \ / / \ \ / / \ \ / --*-----* *-----*-----* *-----*-- / \ \ / / \ \ / / \ \ \ / / \ \ / / *-----*-----* *-----*-----* / / \ \ / / \ \ \ / / \ \ / / \ \ / --*-----* *-----o-----* *-----*-- / \ \ / / \ \ / / \ \ \ / / \ \ / / *-----*-----* *-----*-----* / / \ \ / / \ \ Which is an ancient pattern, =over L =back =head2 Arms The curve fills a sixth of the plane and six copies rotated by 60, 120, 180, 240 and 300 degrees mesh together perfectly. The C parameter can choose 1 to 6 such curve arms successively advancing. For example C 6> begins as follows. N=0,6,12,18,etc is the first arm (the same shape as the plain curve above), then N=1,7,13,19 the second, N=2,8,14,20 the third, etc. =cut # generated by code in devel/terdragon.pl =pod \ / \ / \ / \ / --- 8,13,31 ---------------- 7,12,30 --- / \ / \ \ / \ / \ / \ / \ / \ / --- 9,14,32 ------------- 0,1,2,3,4,5 -------------- 6,17,35 --- / \ / \ / \ / \ / \ / \ \ / \ / --- 10,15,33 ---------------- 11,16,34 --- / \ / \ / \ / \ With six arms every X,Y point is visited three times, except the origin 0,0 where all six begin. Every edge between points is traversed once. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::TerdragonCurve-Enew ()> =item C<$path = Math::PlanePath::TerdragonCurve-Enew (arms =E 6)> Create and return a new path object. The optional C parameter can make 1 to 6 copies of the curve, each arm successively advancing. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. If there's nothing at C<$x,$y> then return C. The curve can visit an C<$x,$y> up to three times. C returns the smallest of the these N values. =item C<@n_list = $path-Exy_to_n_list ($x,$y)> Return a list of N point numbers for coordinates C<$x,$y>. The origin 0,0 has C many N since it's the starting point for each arm. Other points have up to 3 Ns for a given C<$x,$y>. If arms=6 then every even C<$x,$y> except the origin has exactly 3 Ns. =back =head2 Descriptive Methods =over =item C<$n = $path-En_start()> Return 0, the first N in the path. =item C<$dx = $path-Edx_minimum()> =item C<$dx = $path-Edx_maximum()> =item C<$dy = $path-Edy_minimum()> =item C<$dy = $path-Edy_maximum()> The dX,dY values on the first arm take three possible combinations, being 120 degree angles. dX,dY for arms=1 ----- 2, 0 dX minimum = -1, maximum = +2 -1, 1 dY minimum = -1, maximum = +1 1,-1 For 2 or more arms the second arm is rotated by 60 degrees so giving the following additional combinations, for a total six. This changes the dX minimum. dX,dY for arms=2 or more ----- -2, 0 dX minimum = -2, maximum = +2 1, 1 dY minimum = -1, maximum = +1 -1,-1 =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 3**$level)>, or for multiple arms return C<(0, $arms * 3**$level + ($arms-1))>. There are 3^level segments in a curve level, so 3^level+1 points numbered from 0. For multiple arms there are arms*(3^level+1) points, numbered from 0 so n_hi = arms*(3^level+1)-1. =back =head1 FORMULAS Various formulas for boundary length, area and more can be found in the author's mathematical write-up =over L =back =head2 N to X,Y There's no reversals or reflections in the curve so C can take the digits of N either low to high or high to low and apply what is effectively powers of the N=3 position. The current code goes low to high using i,j,k coordinates as described in L. si = 1 # position of endpoint N=3^level sj = 0 # where level=number of digits processed sk = 0 i = 0 # position of N for digits so far processed j = 0 k = 0 loop base 3 digits of N low to high if digit == 0 i,j,k no change if digit == 1 (i,j,k) = (si-j, sj-k, sk+i) # rotate +120, add si,sj,sk if digit == 2 i -= sk # add (si,sj,sk) rotated +60 j += si k += sj (si,sj,sk) = (si - sk, # add rotated +60 sj + si, sk + sj) The digit handling is a combination of rotate and offset, digit==1 digit 2 rotate and offset offset at si,sj,sk rotated ^ 2------> \ \ \ *--- --1 *-- --* The calculation can also be thought of in term of w=1/2+I*sqrt(3)/2, a complex number sixth root of unity. i is the real part, j in the w direction (60 degrees), and k in the w^2 direction (120 degrees). si,sj,sk increase as if multiplied by w+1. =head2 Turn At each point N the curve always turns 120 degrees either to the left or right, it never goes straight ahead. If N is written in ternary then the lowest non-zero digit gives the turn ternary lowest non-zero digit turn -------------- ----- 1 left 2 right At N=3^level or N=2*3^level the turn follows the shape at that 1 or 2 point. The first and last unit step in each level are in the same direction, so the next level shape gives the turn. 2*3^k-------3*3^k \ \ 0-------1*3^k =head2 Next Turn The next turn, ie. the turn at position N+1, can be calculated from the ternary digits of N similarly. The lowest non-2 digit gives the turn. ternary lowest non-2 digit turn -------------- ----- 0 left 1 right If N is all 2s then the lowest non-2 is taken to be a 0 above the high end. For example N=8 is 22 ternary so considered 022 for lowest non-2 digit=0 and turn left after the segment at N=8, ie. at point N=9 turn left. This rule works for the same reason as the plain turn above. The next turn of N is the plain turn of N+1 and adding +1 turns trailing 2s into trailing 0s and increments the 0 or 1 digit above them to be 1 or 2. =head2 Total Turn The direction at N, ie. the total cumulative turn, is given by the number of 1 digits when N is written in ternary, direction = (count 1s in ternary N) * 120 degrees For example N=12 is ternary 110 which has two 1s so the cumulative turn at that point is 2*120=240 degrees, ie. the segment N=16 to N=17 is at angle 240. The segments for digit 0 or 2 are in the "current" direction unchanged. The segment for digit 1 is rotated +120 degrees. =head2 X,Y to N The current code find digits of N low to high by a remainder on X,Y to get the lowest then subtract and divide to unexpand. See "unpoint" in the author's mathematical write-up for details. =head2 X,Y Visited When arms=6 all "even" points of the plane are visited. As per the triangular representation of X,Y this means X+Y mod 2 == 0 "even" points =head1 OEIS The terdragon is in Sloane's Online Encyclopedia of Integer Sequences as, =over L (etc) =back A060236 turn 1=left,2=right, by 120 degrees (lowest non-zero ternary digit) A137893 turn 1=left,0=right (morphism) A189640 turn 0=left,1=right (morphism, extra initial 0) A080846 next turn 0=left,1=right, by 120 degrees (n=0 is turn at N=1) A189673 prev turn 1=left,0=right (morphism, extra initial 0) A038502 strip trailing ternary 0s, taken mod 3 is turn 1=left,2=right A133162 1=segment, 2=right turn between A189673 and A026179 start with extra initial values arising from their morphism definition. That can be skipped to consider the turns starting with a left turn at N=1. A026225 N positions of left turns, being (3*i+1)*3^j so lowest non-zero digit is 1 A026179 N positions of right turns (except initial 1) being (3*i+2)*3^j so lowest non-zero digit is 2 A060032 bignum turns 1=left,2=right to 3^level A189674 num left turns 1 to N A189641 num right turns 1 to N A189672 same A026141 \ dTurnLeft increment between left turns N A026171 / A026181 \ dTurnRight increment between right turns N A131989 / A062756 direction (net total turn), count ternary 1s A005823 N positions where direction = 0, ternary no 1s A023692 through A023698 N positions where direction = 1 to 7, ternary num 1s A111286 boundary length, N=0 to N=3^k, skip initial 1 A003945 boundary/2 A002023 boundary odd levels N=0 to N=3^(2k+1), or even levels one side N=0 to N=3^(2k), being 6*4^k A164346 boundary even levels N=0 to N=3^(2k), or one side, odd levels, N=0 to N=3^(2k+1), being 3*4^k A042950 V[k] boundary length A056182 area enclosed N=0 to N=3^k, being 2*(3^k-2^k) A081956 same A118004 1/2 area N=0 to N=3^(2k+1), odd levels, 9^n-4^n A155559 join area, being 0 then 2^k A099754 1/2 count distinct visited points N=0 to N=3^k A092236 count East segments N=0 to N=3^k-1 A135254 count North-West segments N=0 to N=3^k-1, extra 0 A133474 count South-West segments N=0 to N=3^k-1 A057083 count segments diff from 3^(k-1) A101990 count segments same dir as middle N=0 to N=3^k-1 A097038 num runs of 12 consecutive segments within N=0 to 3^k-1 each segment enclosing a new unit triangle A057682 level X, at N=3^level also arms=2 level Y, at N=2*3^level A057083 level Y, at N=3^level also arms=6 level X at N=6*3^level A057681 arms=2 level X, at N=2*3^level also arms=3 level Y at 3*3^level A103312 same =head1 HOUSE OF GRAPHS House of Graphs entries for the terdragon as a graph include =over L etc =back 19655 level=0 (1-segment path) 594 level=1 (3-segment path) 21138 level=2 21140 level=3 33761 level=4 33763 level=5 =head1 SEE ALSO L, L, L, L L, L Larry Riddle's Terdragon page, for boundary and area calculations of the terdragon as an infinite fractal L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/VogelFloret.pm0000644000175000017500000006552113734026653020031 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # n_start=>0 to include N=0 at the origin, but that not a documented feature # yet. # http://algorithmicbotany.org/papers/#abop # # http://www.sciencedirect.com/science/article/pii/0025556479900804 # http://dx.doi.org/10.1016/0025-5564(79)90080-4 Helmut Vogel, "A Better Way # to Construct the Sunflower Head", Volume 44, Issues 3-4, June 1979, Pages # 179-189 # http://artemis.wszib.edu.pl/~sloot/2_1.html # # http://www.csse.monash.edu.au/publications/2003/tr-2003-149-full.pdf # on 3D surfaces of revolution or some such maybe # 14 Mbytes (or preview with google) # Count of Zeckendorf bits plotted on Vogel floret. # Zeckendorf/Fibbinary with N bits makes radial spokes. cf FibbinaryBitCount # http://www.ms.unimelb.edu.au/~segerman/papers/sunflower_spiral_fibonacci_metric.pdf # private copy ? # closest two for phi are 1 and 4 # n=1 r=sqrt(1) = 1 # t=1/phi^2 = 0.381 around # x=-.72 y=.68 # n=4 r=sqrt(4) = 2 # t=4/phi^2 = 1.527 = .527 around # x=-1.97 y=-.337 # diff angle=4/phi^2 - 1/phi^2 = 3/phi^2 = 3*(2-phi) = 1.14 = .14 # diff dx=1.25 dy=1.017 hypot=1.61 # dang = 2*PI()*(5-3*phi) # y = sin() # x = sin(2*PI()*(5-3*phi)) # Continued fraction # 1 # x = k + ------ # k + 1 # ------ # k + 1 # --- # k + ... # # x = k + 1/x # (x-k/2)^2 = 1 + (k^2)/4 # # k + sqrt(4+k^2) # x = --------------- # 2 # # k x # 1 (1+sqrt(5)) / 2 # 2 1 + sqrt(2) # 3 (3+sqrt(13)) / 2 # 4 2 + sqrt(5) # 5 (5 + sqrt(29)) / 2 # 6 3 + sqrt(10) # 2e e + sqrt(1+e^2) even package Math::PlanePath::VogelFloret; use 5.004; use strict; use Carp 'croak'; use Math::Libm 'hypot'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite'; use Math::PlanePath::SacksSpiral; # uncomment this to run the ### lines #use Smart::Comments '###'; use constant figure => 'circle'; use constant 1.02; # for leading underscore use constant _PHI => (1 + sqrt(5)) / 2; use constant _TWO_PI => 4*atan2(1,0); # not documented yet ... use constant rotation_types => { phi => { rotation_factor => 2 - _PHI(), radius_factor => 0.624239116809924, # closest_Ns => [ 1,4 ], # continued_frac => [ 1,1,1,1,1,... ], }, sqrt2 => { rotation_factor => sqrt(2)-1, radius_factor => 0.679984167849259, # closest_Ns => [ 3,8 ], # continued_frac => [ 2,2,2,2,2,... ], }, sqrt3 => { rotation_factor => sqrt(3)-1, radius_factor => 0.755560810248419, # closest_Ns => [ 3,7 ], # continued_frac => [ 1,2,1,2,1,2,1,2,... ], }, sqrt5 => { rotation_factor => sqrt(5)-2, radius_factor => 0.853488207169303, # closest_Ns => [ 4,8 ], # continued_frac => [ 4,4,4,4,4,4,... ], }, }; use constant parameter_info_array => [ { name => 'rotation_type', type => 'enum', display => 'Rotation Type', share_key => 'vogel_rotation_type', choices => ['phi', 'sqrt2', 'sqrt3', 'sqrt5', 'custom'], default => 'phi', }, { name => 'rotation_factor', type => 'float', type_hint => 'expression', display => 'Rotation Factor', description => 'Rotation factor. If you have Math::Symbolic then this can be an expression like pi+2*e-phi (constants phi,e,gam,pi), otherwise it should be a plain number.', default => - (1 + sqrt(5)) / 2, default_expression => '-phi', width => 10, when_name => 'rotation_type', when_value => 'custom', }, { name => 'radius_factor', display => 'Radius Factor', description => 'Radius factor, spreading points out to make them non-overlapping. 0 means the default factor.', type => 'float', minimum => 0, maximum => 999, page_increment => 1, step_increment => .1, decimals => 2, default => 1, when_name => 'rotation_type', when_value => 'custom', }, ]; sub x_negative_at_n { my ($self) = @_; return int(.25 / $self->{'rotation_factor'}) + 1; } sub y_negative_at_n { my ($self) = @_; return int(.5 / $self->{'rotation_factor'}) + 1; } sub sumabsxy_minimum { my ($self) = @_; my ($x,$y) = $self->n_to_xy($self->n_start); return abs($x)+abs($y); } sub rsquared_minimum { my ($self) = @_; # starting N=1 at R=radius_factor*sqrt(1), theta=something return $self->{'radius_factor'} ** 2; } use constant gcdxy_maximum => 0; sub turn_any_left { # always left if rot<=0.5 my ($self) = @_; return ($self->{'rotation_factor'} <= 0.5); } sub turn_any_right { # always left if rot<=0.5 my ($self) = @_; return ($self->{'rotation_factor'} > 0.5); } use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); ### $self my $rotation_type = ($self->{'rotation_type'} ||= 'phi'); my $defaults = rotation_types()->{$rotation_type} || croak 'Unrecognised rotation_type: "',$rotation_type,'"'; $self->{'radius_factor'} ||= ($self->{'rotation_factor'} ? 1.0 : $defaults->{'radius_factor'}); $self->{'rotation_factor'} ||= $defaults->{'rotation_factor'}; return $self; } # R=radius_factor*sqrt($n) # R^2 = radius_factor^2 * $n # avoids sqrt and sin/cos in the main n_to_xy() # sub n_to_rsquared { my ($self, $n) = @_; ### VogelFloret RSquared: $i, $seq->{'planepath_object'} if ($n < 0) { return undef; } my $rf = $self->{'radius_factor'}; $rf *= $rf; # squared # don't round BigInt*flonum if radius_factor is not an integer, promote to # BigFloat instead if (ref $n && $n->isa('Math::BigInt') && $rf != int($rf)) { require Math::BigFloat; $n = Math::BigFloat->new($n); } return $n * $rf; } sub n_to_xy { my ($self, $n) = @_; if ($n < 0) { return; } my $two_pi = _TWO_PI(); if (ref $n) { if ($n->isa('Math::BigInt')) { $n = Math::PlanePath::SacksSpiral::_bigfloat()->new($n); } if ($n->isa('Math::BigRat')) { $n = $n->as_float; } if ($n->isa('Math::BigFloat')) { $two_pi = 2 * Math::BigFloat->bpi; } } my $r = sqrt($n) * $self->{'radius_factor'}; # take the frac part of 1==circle and then convert to radians, so as not # to lose precision in an fmod(...,2*pi) # my $theta = $n * $self->{'rotation_factor'}; # 1==full circle $theta = $two_pi * ($theta - int($theta)); # radians 0 to 2*pi return ($r * cos($theta), $r * sin($theta)); # cylindrical_to_cartesian() is only perl code, so may as well sin/cos # here directly # return (Math::Trig::cylindrical_to_cartesian($r, $theta, 0))[0,1]; } sub xy_to_n { my ($self, $x, $y) = @_; # Slack approach just trying all the N values between r-.5 and r+.5. # # r = sqrt(n)*FACTOR # n = (r/FACTOR)^2 # # The target N satisfies N = K * phi + epsilon for integer K. What's an # easy way to find the first integer N >= (r-.5)**2 satisfying -small <= N # mod .318 <= +small ? # my $r = sqrt($x*$x + $y*$y); # hypot my $factor = $self->{'radius_factor'}; my $n_lo = int( (($r-.6)/$factor)**2 ); if ($n_lo < 0) { $n_lo = 0; } my $n_hi = int( (($r+.6)/$factor)**2 + 1 ); #### $r #### xy: "$x,$y" #### $n_lo #### $n_hi if (is_infinite($n_lo) || is_infinite($n_hi)) { ### infinite range, r inf or too big return undef; } # for(;;) loop since "reverse $n_lo..$n_hi" limited to IV range for (my $n = $n_hi; $n >= $n_lo; $n--) { my ($nx, $ny) = $self->n_to_xy($n); ### hypot: "$n ".hypot($nx-$x,$ny-$y) if (hypot($nx-$x,$ny-$y) <= 0.5) { #### found: $n return $n; } } return undef; # my $theta_frac = Math::PlanePath::MultipleRings::_xy_to_angle_frac($x,$y); # ### assert: 0 <= $frac && $frac < 1 # # # seeking integer k where (k+theta)*PHIPHI == $r*$r == $n or nearby # my $k = $r*$r / (PHI*PHI) - $theta; # # ### $x # ### $y # ### $r # ### $theta # ### $k # # foreach my $ki (POSIX::floor($k), POSIX::ceil($k)) { # my $n = int (($ki+$theta)*PHI*PHI + 0.5); # # # look for within 0.5 radius # my ($nx, $ny) = $self->n_to_xy($n); # ### $ki # ### n frac: ($ki+$theta)*PHI*PHI # ### $n # ### hypot: hypot($nx-$x,$ny-$y) # if (hypot($nx-$x,$ny-$y) <= 0.5) { # return $n; # } # } # return; } # max corner at R # R+0.5 = sqrt(N) * radius_factor # sqrt(N) = (R+0.5)/rfactor # N = (R+0.5)^2 / rfactor^2 # = (R^2 + R + 1/4) / rfactor^2 # <= (X^2+Y^2 + X+Y + 1/4) / rfactor^2 # <= (X(X+1) + Y(Y+1) + 1) / rfactor^2 # # min corner at R # R-0.5 = sqrt(N) * radius_factor # sqrt(N) = (R-0.5)/rfactor # N = (R-0.5)^2 / rfactor^2 # = (R^2 - R + 1/4) / rfactor^2 # >= (X^2+Y^2 - (X+Y)) / rfactor^2 because x+y >= r # = (X(X-1) + Y(Y-1)) / rfactor^2 # not exact sub rect_to_n_range { my $self = shift; ### VogelFloret rect_to_n_range(): @_ my ($n_lo, $n_hi) = Math::PlanePath::SacksSpiral->rect_to_n_range(@_); my $rf = $self->{'radius_factor'}; $rf *= $rf; # squared # avoid BigInt/flonum if radius_factor is not an integer, promote to # BigFloat instead if ($rf == int($rf)) { $n_hi += $rf-1; # division round upwards } else { if (ref $n_lo && $n_lo->isa('Math::BigInt')) { require Math::BigFloat; $n_lo = Math::BigFloat->new($n_lo); } if (ref $n_hi && $n_lo->isa('Math::BigInt')) { require Math::BigFloat; $n_hi = Math::BigFloat->new($n_hi); } } $n_lo = int($n_lo / $rf); if ($n_lo < 1) { $n_lo = 1; } $n_hi = _ceil($n_hi / $rf); return ($n_lo, $n_hi); } sub _ceil { my ($x) = @_; my $int = int($x); return ($x > $int ? $int+1 : $int); } 1; __END__ =for stopwords Helmut Vogel fibonacci sqrt sqrt2 Ryde Math-PlanePath frac repdigits straightish Vogel's builtin repunit eg phi-ness radix Zeckendorf OEIS =head1 NAME Math::PlanePath::VogelFloret -- circular pattern like a sunflower =head1 SYNOPSIS use Math::PlanePath::VogelFloret; my $path = Math::PlanePath::VogelFloret->new; my ($x, $y) = $path->n_to_xy (123); # other rotations $path = Math::PlanePath::VogelFloret->new (rotation_type => 'sqrt2'); =head1 DESCRIPTION XXThe is an implementation of Helmut Vogel's model for the arrangement of seeds in the head of a sunflower. Integer points are on a spiral at multiples of the golden ratio phi = (1+sqrt(5))/2, 27 19 24 14 11 22 16 6 29 30 9 3 8 1 21 17 . 4 13 25 2 5 12 7 26 10 18 20 15 23 31 28 The polar coordinates for a point N are R = sqrt(N) * radius_factor angle = N / (phi**2) in revolutions, 1==full circle = N * -phi modulo 1, with since 1/phi^2 = 2-phi theta = 2*pi * angle in radians Going from point N to N+1 adds an angle 0.382 revolutions around (anti-clockwise, the usual spiralling direction), which means just over 1/3 of a circle. Or equivalently it's -0.618 back (clockwise) which is phi=1.618 ignoring the integer part since that's a full circle -- only the fractional part determines the position. C is a scaling 0.6242 designed to put the closest points 1 apart. The closest are N=1 and N=4. See L below. =head2 Other Rotation Types An optional C parameter selects other possible floret forms. $path = Math::PlanePath::VogelFloret->new (rotation_type => 'sqrt2'); The current types are as follows. The C for each keeps points at least 1 apart so unit circles don't overlap. rotation_type rotation_factor radius_factor "phi" 2-phi = 0.3820 0.624 "sqrt2" sqrt(2) = 0.4142 0.680 "sqrt3" sqrt(3) = 0.7321 0.756 "sqrt5" sqrt(5) = 0.2361 0.853 The "sqrt2" floret is quite similar to phi, but doesn't pack as tightly. Custom rotations can be made with C and C parameters, # R = sqrt(N) * radius_factor # angle = N * rotation_factor in revolutions # theta = 2*pi * angle in radians # $path = Math::PlanePath::VogelFloret->new (rotation_factor => sqrt(37), radius_factor => 2.0); Usually C should be an irrational number. A rational like P/Q merely results in Q many straight lines and doesn't spread the points enough to suit R=sqrt(N). Irrationals which are very close to simple rationals behave that way too. (Of course all floating point values are implicitly rationals, but are fine within the limits of floating point accuracy.) The "noble numbers" (A+B*phi)/(C+D*phi) with A*D-B*C=1, AEB, CED behave similar to the basic phi. Their continued fraction expansion begins with some arbitrary values and then becomes a repeating "1" the same as phi. The effect is some spiral arms near the origin then the phi-ness dominating for large N. =head2 Packing Each point is at an increasing distance sqrt(N) from the origin. This sqrt based on how many unit figures will fit within that distance. The area within radius R is T = pi * R^2 area of circle R so if N figures each of area A are packed into that space then the radius R is proportional to sqrt(N), N*A = T = pi * R^2 R = sqrt(N) * sqrt(A/pi) The tightest possible packing for unit circles is a hexagonal honeycomb grid, each of area A = sqrt(3)/2 = 0.866. That would be factor sqrt(A/pi) = 0.525. The phi floret packing is not as tight as that, needing radius factor 0.624 as described above. Generally the tightness of the packing depends on the fractions which closely approximate the rotation factor. If the terms of the continued fraction expansion are large then there's large regions of spiral arcs with gaps between. The density in such regions is low and a big radius factor is needed to keep the points apart. If the continued fraction terms are ever increasing then there may be no radius factor big enough to always keep the points a minimum distance apart ... or something like that. The terms of the continued fraction for phi are all 1 and is therefore, in that sense, among all irrationals, the value least well approximated by rationals. 1 phi = 1 + ------ 1 + 1 ------ ^ 1 + 1 | --- | ^ 1 + 1 | | ---- | | ^ ... terms -+---+---+ sqrt(3) is 1,2 repeating. sqrt(13) is 3s repeating. =head2 Fibonacci and Lucas Numbers XXThe Fibonacci numbers F(k) = 1,1,2,3,5,8,13,21, etc and Lucas number L(k) = 2,1,3,4,7,11,18, etc form almost straight lines on the X axis of the phi floret. This occurs because N*-phi is close to an integer for those N. For example N=13 has angle 13*-phi = -21.0344, the fractional part -0.0344 puts it just below the X axis. Both F(k) and L(k) grow exponentially (as phi^k) which soon outstrips the sqrt in the R radial distance so they become widely spaced apart along the X axis. For interest, or for reference, the angle F(k)*phi is in fact roughly the next Fibonacci number F(k+1), per the well-known limit F(k+1)/F(k) -> phi as k->infinity, angle = F(k)*-phi = -F(k+1) + epsilon The Lucas numbers similarly with L(k)*phi close to L(k+1). The "epsilon" approaches zero quickly enough in both cases that the resulting Y coordinate approaches zero. This can be calculated as follows, writing beta = -1/phi =-0.618 Since abs(beta)<1 the powers beta^k go to zero. F(k) = (phi^k - beta^k) / (phi - beta) # an integer angle = F(k) * -phi = - (phi*phi^k - phi*beta^k) / (phi - beta) = - (phi^(k+1) - beta^(k+1) + beta^(k+1) - phi*beta^k) / (phi - beta) = - F(k+1) - (phi-beta)*beta^k / (phi - beta) = - F(k+1) - beta^k frac(angle) = - beta^k = 1/(-phi)^k The arc distance away from the X axis at radius R=sqrt(F(k)) is then as follows, simplifying using phi*(-beta)=1 and S = sqrt(5). The Y coordinate vertical distance is a little less than the arc distance. arcdist = 2*pi * R * frac(angle) = 2*pi * sqrt((phi^k - beta^k)/sqrt(5)) * 1/(-phi)^k = - (-1)^k * 2*pi * sqrt((1/phi^2k*phi^k - beta^3k)/sqrt(5)) = - (-1)^k * 2*pi * sqrt((1/phi^k - 1/(-phi)^3k)/sqrt(5)) -> 0 as k -> infinity Essentially the radius increases as phi^(k/2) but the angle frac decreases as (1/phi)^k so their product goes to zero. The (-1)^k in the formula puts the points alternately just above and just below the X axis. The calculation for the Lucas numbers is very similar, with term +(beta^k) instead of -(beta^k) and an extra factor sqrt(5). L(k) = phi^k + beta^k angle = L(k) * -phi = -phi*phi^k - phi*beta^k = -phi^(k+1) - beta^(k+1) + beta^(k+1) - phi*beta^k = -L(k) + beta^k * (beta - phi) = -L(k) - sqrt(5) * beta^k frac(angle) = -sqrt(5) * beta^k = -sqrt(5) / (-phi)^k arcdist = 2*pi * R * frac(angle) = 2*pi * sqrt(L(k)) * sqrt(5)*beta^k = 2*pi * sqrt(phi^k + 1/(-phi)^k) * sqrt(5)*beta^k = (-1)*k * 2*pi * sqrt(5) * sqrt((-beta)^2k * phi^k + beta^3k) = (-1)*k * 2*pi * sqrt(5) * sqrt((-beta)^k + beta^3k) =head2 Spectrum The spectrum of a real number is its multiples, each rounded down to an integer. For example the spectrum of phi is floor(phi), floor(2*phi), floor(3*phi), floor(4*phi), ... 1, 3, 4, 6, ... When plotted on the Vogel floret these integers are all in the first 1/phi = 0.618 of the circle. =cut # math-image --oeis=A000201 --output=numbers # but better scaled in vogel.pl # A001950 floor(N*phi^2) spectrum of 1+1/phi # A000201 floor(N*phi) spectrum of phi =pod 61 53 69 40 45 58 48 32 71 56 27 37 35 19 24 50 43 14 11 63 64 22 6 16 29 30 3 42 51 9 1 8 21 72 17 4 . 55 38 59 25 12 46 33 67 This occurs because angle = N * 1/phi^2 = N * (1-1/phi) = N * -1/phi # modulo 1 = floor(int*phi) * -1/phi # N=spectrum = (int*phi - frac) * -1/phi # 0 with 0EtE1 the spectrum of 1/t falls within the first 0 to t angle. =head2 Fibonacci Word The Fibonacci word 0,1,0,0,1,0,1,0,0,1,etc is the least significant bit of the Zeckendorf base representation of i, starting from i=0. Plotted at N=i on the C gives 1 0 1 1 1 0 0 Fibonacci word 1 1 1 0 0 1 1 0 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 0 0 0 1 1 1 1 . 0 0 0 1 1 1 0 0 0 0 1 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 This pattern occurs because the Fibonacci word, among its various possible definitions, is 0 or 1 according to whether i+1 occurs in the spectrum of phi (1,3,4,6,8,etc) or not. So for example at i=5 the value is 0 because i+1=6 is in the spectrum of phi, then at i=6 the value is 1 because i+1=7 is not. The "+1" for i to spectrum has the effect of rotating the spectrum pattern described above by -0.381 (one rotation factor back). So the Fibonacci word "0"s are from angle -0.381 to -0.381+0.618=0.236 and the rest "1"s. 0.236 is close to 1/4, hence the "0"s to "1"s line just before the Y axis. =cut # math-image--path=VogelFloret --values=FibonacciWord --output=numbers # 1*phi = 1.61 1 1 0 # 2 0 1 # 2*phi = 3.23 3 1 0 # 3*phi = 4.85 4 1 0 # 0 1 # 4*phi = 6.47 6 1 0 # 0 1 # 5*phi = 8.09 1 0 # 6*phi = 9.70 1 0 =pod =cut # angle = N * t # N = s*i - frac spectrum of s # angle = t*(s*i-frac) # = t*s*i - t*frac # s=1/t is spectrum s>1 =pod =head2 Repdigits in Decimal Some of the decimal repdigits 11, 22, ..., 99, 111, ..., 999, etc make nearly straight radial lines on the phi floret. For example 11, 66, 333, 888 make a line upwards to the right. 11 and 66 are at the same polar angle because the difference is 55 and 55*phi = 88.9919 is nearly an integer meaning the angle is nearly unchanged when added. Similarly 66 to 333 difference 267 has 267*phi = 432.015, or 333 to 888 difference 555 has 555*phi = 898.009. The 55 is a Fibonacci number, the 123 between 99 and 222 is a Lucas number, and 267 = 144+123 = F(12)+L(10). The differences 55 and 555 apply to pairs 22 and 77, 33 and 88, 666 and 1111, etc, making four straightish arms. 55 and 555 themselves are near the X axis. A separate spiral arm arises from 11111 falling moderately close to the X axis since 11111*-phi = -17977.9756, or about 0.024 of a circle upwards. The subsequent 22222, 33333, 44444, etc make a little arc of nine values going upwards that much each time for a total about a quarter turn 9*0.024 = 0.219. =head2 Repdigits in Other Bases By choosing a radix so that "11" (or similar repunit) in that radix is close to the X axis, spirals like the decimal 11111 above can be created. This includes when "11" in the base is a Fibonacci number or Lucas number, such as base 12 so "11" base 12 is 13. If "11" is near the negative X axis then there's two spiral arms, one going out on the X negative side and one X positive, eg. base 16 has 0x11=17 which is near the negative X axis. A four-arm shape can be formed similarly if "11" is near the Y axis, eg. base 107. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::VogelFloret-Enew ()> =item C<$path = Math::PlanePath::VogelFloret-Enew (key =E value, ...)> Create and return a new path object. The default is Vogel's phi floret. Optional parameters can vary the pattern, rotation_type => string, choices above rotation_factor => number radius_factor => number The available C values are listed above (see L). C can be given together with C to have its rotation, but scale the radius differently. If a C is given then the default C is not specified yet. Currently it's 1.0, but perhaps something suiting at least the first few N positions would be better. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. C<$n> can be any value C<$n E= 0> and fractions give positions on the spiral in between the integer points, though the principle interest for the floret is where the integers fall. For C<$n < 0> the return is an empty list, it being considered there are no negative points in the spiral. =item C<$rsquared = $path-En_to_rsquared ($n)> Return the radial distance R^2 of point C<$n>, or C if there's no point C<$n>. As per the formulas above this is simply $n * $radius_factor**2 =item C<$n = $path-Exy_to_n ($x,$y)> Return an integer point number for coordinates C<$x,$y>. Each integer N is considered the centre of a circle of diameter 1 and an C<$x,$y> within that circle returns N. The builtin C choices are scaled so no two points are closer than 1 apart so the circles don't overlap, but they also don't cover the plane and if C<$x,$y> is not within one of those circles then the return is C. With C and C parameters it's possible for unit circles to overlap. In the current code the return is the largest N covering C<$x,$y>, but perhaps that will change. =item C<$str = $path-Efigure ()> Return "circle". =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A000201 spectrum of phi, N in first 0.618 of circle A003849 Fibonacci word, values 0,1 =head1 SEE ALSO L, L, L L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/SierpinskiArrowheadCentres.pm0000644000175000017500000005532713734026656023110 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=SierpinskiArrowheadCentres --lines --scale=10 # # math-image --path=SierpinskiArrowheadCentres --all --output=numbers_dash # math-image --path=SierpinskiArrowheadCentres --all --text --size=80 package Math::PlanePath::SierpinskiArrowheadCentres; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'round_up_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use Math::PlanePath::SierpinskiArrowhead; *parameter_info_array # align parameter = \&Math::PlanePath::SierpinskiArrowhead::parameter_info_array; *new = \&Math::PlanePath::SierpinskiArrowhead::new; use constant n_start => 0; use constant class_y_negative => 0; *x_negative = \&Math::PlanePath::SierpinskiArrowhead::x_negative; { my %x_negative_at_n = (triangular => 2, # right => undef, left => 2, # diagonal => undef, ); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n{$self->{'align'}}; } } *x_maximum = \&Math::PlanePath::SierpinskiArrowhead::x_maximum; use constant sumxy_minimum => 0; # triangular X>=-Y use Math::PlanePath::SierpinskiTriangle; *diffxy_maximum = \&Math::PlanePath::SierpinskiTriangle::diffxy_maximum; use constant dy_minimum => -1; use constant dy_maximum => 1; *dx_minimum = \&Math::PlanePath::SierpinskiArrowhead::dx_minimum; *dx_maximum = \&Math::PlanePath::SierpinskiArrowhead::dx_maximum; *_UNDOCUMENTED__dxdy_list = \&Math::PlanePath::SierpinskiArrowhead::_UNDOCUMENTED__dxdy_list; # same use constant 1.02 _UNDOCUMENTED__dxdy_list_at_n => 15; *absdx_minimum = \&Math::PlanePath::SierpinskiArrowhead::absdx_minimum; *absdx_maximum = \&Math::PlanePath::SierpinskiArrowhead::absdx_maximum; *dsumxy_minimum = \&Math::PlanePath::SierpinskiArrowhead::dsumxy_minimum; *dsumxy_maximum = \&Math::PlanePath::SierpinskiArrowhead::dsumxy_maximum; sub ddiffxy_minimum { my ($self) = @_; return ($self->{'align'} eq 'right' ? -1 : -2); } sub ddiffxy_maximum { my ($self) = @_; return ($self->{'align'} eq 'right' ? 1 : 2); } *dir_maximum_dxdy = \&Math::PlanePath::SierpinskiArrowhead::dir_maximum_dxdy; #------------------------------------------------------------------------------ # States as multiples of 3 so that state+digit is the lookup for next state # and x,y bit. # # 0 3 6 9 12 15 # # 8 0 4 4 0 8 # | | |\ |\ \ \ # 7-6 1-2 3 5 5 3 2-1 6-7 # \ \ | \ | \ | | # 1 5 7 3 2 6 6 2 3 7 5 1 # |\ \ |\ \ \ | \ | | |\ | |\ # 0 2-3-4 8 6-5-4 0-1 7-8 8-7 1-0 4-5-6 8 4-3-2 0 # 15 6 3 # 6 0 0 12 12 6 # 0,1 0,2 1,2 my @next_state = (6,0,15, 12,3,9, # 3,6 0,6,12, 15,9,3, # 6,9 3,12,6, 9,15,0); # 12,15 my @state_to_xbit = (0,1,0, 0,1,0, 0,0,1, 1,0,0, 0,0,1, 1,0,0); # 12,15 my @state_to_ybit = (0,0,1, 1,0,0, 0,1,0, 0,1,0, 1,0,0, 0,0,1); # 12,15 # dx,dy for digit==0 and digit==1 in each stage my @state_to_dx; my @state_to_dy; foreach my $state (0,1, 3,4, 6,7, 9,10, 12,13, 15,16) { $state_to_dx[$state] = $state_to_xbit[$state+1] - $state_to_xbit[$state]; $state_to_dy[$state] = $state_to_ybit[$state+1] - $state_to_ybit[$state]; } sub n_to_xy { my ($self, $n) = @_; ### SierpinskiArrowheadCentres n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; # fraction part my @digits = digit_split_lowtohigh($int,3); my $state = ($#digits & 1 ? 6 : 0); ### @digits ### $state my (@x,@y); # bits low to high my $dirstate = $state ^ 6; # if all digits==2 foreach my $i (reverse 0 .. $#digits) { ### at: "x=".join(',',@x[($i+1)..$#digits])." y=".join(',',@y[($i+1)..$#digits])." apply i=$i state=$state digit=$digits[$i]" my $digit = $digits[$i]; # high to low $state += $digit; $x[$i] = $state_to_xbit[$state]; $y[$i] = $state_to_ybit[$state]; if ($digit != 2) { $dirstate = $state; # lowest non-2 digit } $state = $next_state[$state]; } my $zero = $int * 0; my $x = $n*$state_to_dx[$dirstate] + digit_join_lowtohigh(\@x,2,$zero); my $y = $n*$state_to_dy[$dirstate] + digit_join_lowtohigh(\@y,2,$zero); if ($self->{'align'} eq 'right') { $y += $x; } elsif ($self->{'align'} eq 'left') { ($x,$y) = (-$y,$x+$y); } elsif ($self->{'align'} eq 'triangular') { ($x,$y) = ($x-$y,$x+$y); } return ($x,$y); } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### SierpinskiArrowheadCentres xy_to_n(): "$x, $y" if ($y < 0) { return undef; } if ($self->{'align'} eq 'left') { if ($x > 0) { return undef; } $x = 2*$x + $y; # adjust to triangular style } elsif ($self->{'align'} eq 'triangular') { if (($x%2) != ($y%2)) { return undef; } } else { # right or diagonal if ($x < 0) { return undef; } if ($self->{'align'} eq 'right') { $x = 2*$x - $y; } else { # diagonal ($x,$y) = ($x-$y, $x+$y); } } ### adjusted xy: "$x,$y" my ($len, $level) = round_down_pow ($y, 2); ### pow2 round up: ($y + ($y==$x || $y==-$x)) ### $len ### $level $level += 1; if (is_infinite($level)) { return $level; } my $n = 0; while ($level) { $n *= 3; ### at: "$x,$y level=$level len=$len" if ($y < 0 || $x < -$y || $x > $y) { ### out of range ... return undef; } if ($y < $len) { ### digit 0, first triangle, no change ... } else { if ($level & 1) { ### odd level ... if ($x > 0) { ### digit 1, right triangle ... $n += 1; $y -= $len; $x = - ($x-$len); ### shift right and mirror to: "$x,$y" } else { ### digit 2, left triangle ... $n += 2; $x += 1; $y -= 2*$len-1; ### shift down to: "$x,$y" ($x,$y) = ((3*$y-$x)/2, # rotate -120 ($x+$y)/-2); ### rotate to: "$x,$y" } } else { ### even level ... if ($x < 0) { ### digit 1, left triangle ... $n += 1; $y -= $len; $x = - ($x+$len); ### shift right and mirror to: "$x,$y" } else { ### digit 2, right triangle ... $n += 2; $x -= 1; $y -= 2*$len-1; ### shift down to: "$x,$y" ($x,$y) = (($x+3*$y)/-2, # rotate +120 ($x-$y)/2); ### now: "$x,$y" } } } $level--; $len /= 2; } ### final: "$x,$y with n=$n" if ($x == 0 && $y == 0) { return $n; } else { return undef; } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### SierpinskiArrowheadCentres rect_to_n_range(): "$x1,$y1, $x2,$y2" $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # swap to y1<=y2 if ($self->{'align'} eq 'diagonal') { $y2 += max (round_nearest ($x1), round_nearest ($x2)); } unless ($y2 >= 0) { ### rect all negative, no N ... return (1, 0); } my ($len,$level) = round_down_pow ($y2, 2); ### $y2 ### $level return (0, 3**($level+1) - 1); } #----------------------------------------------------------------------------- # level_to_n_range() # shared by SierpinskiTriangle sub level_to_n_range { my ($self, $level) = @_; my $n_start = $self->n_start; return ($n_start, $n_start + 3**$level - 1); } sub n_to_level { my ($self, $n) = @_; $n = $n - $self->n_start; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($pow, $exp) = round_up_pow ($n+1, 3); return $exp; } #----------------------------------------------------------------------------- 1; __END__ #------------------------------------------------------------------------------ # Old n_to_xy() triangular with explicit add/sub. # my $x = my $y = ($int * 0); # inherit bigint 0 # my $len = $x + 1; # inherit bigint 1 # # my @digits = digit_split_lowtohigh($int,3); # for (;;) { # unless (@digits) { # $x = $n + $x; # $y = $n + $y; # last; # } # my $digit = shift @digits; # low to high # # ### odd right: "$x, $y len=$len frac=$n" # ### $digit # if ($digit == 0) { # $x = $n + $x; # $y = $n + $y; # $n = 0; # # } elsif ($digit == 1) { # $x = -2*$n -$x + $len; # mirror and offset # $y += $len; # $n = 0; # # } else { # ($x,$y) = (($x+3*$y)/-2 - 1, # rotate +120 # ($x-$y)/2 + 2*$len-1); # } # # unless (@digits) { # $x = -$n + $x; # $y = $n + $y; # last; # } # $digit = shift @digits; # low to high # $len *= 2; # # ### odd left: "$x, $y len=$len frac=$n" # ### $digit # if ($digit == 0) { # $x = -$n + $x; # $y = $n + $y; # $n = 0; # # } elsif ($digit == 1) { # $x = 2*$n + -$x - $len; # mirror and offset # $y += $len; # $n = 0; # # } else { # ($x,$y) = ((3*$y-$x)/2 + 1, # rotate -120 # ($x+$y)/-2 + 2*$len-1); # } # $len *= 2; # } # # ### final: "$x,$y" # if ($self->{'align'} eq 'right') { # return (($x+$y)/2, $y); # } elsif ($self->{'align'} eq 'left') { # return (($x-$y)/2, $y); # } elsif ($self->{'align'} eq 'diagonal') { # return (($x+$y)/2, ($y-$x)/2); # } else { # triangular # return ($x,$y); # } #------------------------------------------------------------------------------ =for stopwords eg Ryde Sierpinski Nlevel ie Math-PlanePath bitand dX dY =head1 NAME Math::PlanePath::SierpinskiArrowheadCentres -- self-similar triangular path traversal =head1 SYNOPSIS use Math::PlanePath::SierpinskiArrowheadCentres; my $path = Math::PlanePath::SierpinskiArrowheadCentres->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis path is variation on Sierpinski's curve from =over Waclaw Sierpinski, "Sur une Courbe Dont Tout Point est un Point de Ramification", Comptes Rendus Hebdomadaires des SE<233>ances de l'AcadE<233>mie des Sciences, volume 160, January-June 1915, pages 302-305. L =back =cut # PDF download pages 304 to 307 inclusive =pod The path here takes the centres of each triangle represented by the arrowhead segments. The points visited are the same as the C path, but traversing in a connected sequence (rather than across rows). ... ... / / . 30 . . . . . 65 . ... / \ / 28----29 . . . . . . 66 68 9 \ \ / 27 . . . . . . . 67 8 \ 26----25 19----18----17 15----14----13 7 / \ \ / / 24 . 20 . 16 . 12 6 \ / / 23 21 . . 10----11 5 \ / \ 22 . . . 9 4 / 4---- 5---- 6 8 3 \ \ / 3 . 7 2 \ 2---- 1 1 / 0 <- Y=0 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 The base figure is the N=0 to N=2 shape. It's repeated up in mirror image as N=3 to N=6 then rotated across as N=6 to N=9. At the next level the same is done with N=0 to N=8 as the base, then N=9 to N=17 up mirrored, and N=18 to N=26 across, etc. The X,Y coordinates are on a triangular lattice using every second integer X, per L. The base pattern is a triangle like .-------.-------. \ / \ / \ 2 / \ 1 / \ / \ / .- - - -. \ / \ 0 / \ / . Higher levels replicate this within the triangles 0,1,2 but the middle is not traversed. The result is the familiar Sierpinski triangle by connected steps of either 2 across or 1 diagonal. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * See the C path to traverse by rows instead. =head2 Level Ranges Counting the N=0,1,2 part as level 1, each replication level goes from Nstart = 0 Nlevel = 3^level - 1 inclusive For example level 2 from N=0 to N=3^2-1=9. Each level doubles in size, 0 <= Y <= 2^level - 1 - (2^level - 1) <= X <= 2^level - 1 The Nlevel position is alternately on the right or left, Xlevel = / 2^level - 1 if level even \ - 2^level + 1 if level odd The Y axis ie. X=0, is crossed just after N=1,5,17,etc which is is 2/3 through the level, which is after two replications of the previous level, Ncross = 2/3 * 3^level - 1 = 2 * 3^(level-1) - 1 =head2 Align Parameter An optional C parameter controls how the points are arranged relative to the Y axis. The default shown above is "triangular". The choices are the same as for the C path. "right" means points to the right of the axis, packed next to each other and so using an eighth of the plane. =cut # math-image --path=SierpinskiArrowheadCentres,align=right --all --output=numbers_dash =pod align => "right" | | 7 | 26-25 19-18-17 15-14-13 | / | |/ / 6 | 24 20 16 12 | | / / 5 | 23 21 10-11 | |/ | 4 | 22 9 | / 3 | 4--5--6 8 | | |/ 2 | 3 7 | | 1 | 2--1 | / Y=0 | 0 +-------------------------- X=0 1 2 3 4 5 6 7 "left" is similar but skewed to the left of the Y axis, ie. into negative X. =cut # math-image --path=SierpinskiArrowheadCentres,align=left --all --output=numbers_dash =pod align => "left" \ | 26-25 19-18-17 15-14-13 | 7 | \ \ | | | 24 20 16 12 | 6 \ | | | 23 21 10-11 | 5 \ | \ | 22 9 | 4 | | 4--5--6 8 | 3 \ \ | | 3 7 | 2 \ | 2--1 | 1 | | 0 | Y=0 --------------------------+ -7 -6 -5 -4 -3 -2 -1 X=0 "diagonal" puts rows on diagonals down from the Y axis to the X axis. This uses the whole of the first quadrant, with gaps. =cut # math-image --expression='i<=26?i:0' --path=SierpinskiArrowheadCentres,align=diagonal --output=numbers_dash =pod align => "diagonal" | | 7 | 26 | \ 6 | 24-25 | | 5 | 23 19 | | |\ 4 | 22-21-20 18 | \ 3 | 4 17 | |\ | 2 | 3 5 16-15 | | \ \ 1 | 2 6 10 14 | \ | |\ \ Y=0 | 0--1 7--8--9 11-12-13 +-------------------------- X=0 1 2 3 4 5 6 7 These diagonals visit all points X,Y where X and Y written in binary have no 1-bits in the same places, ie. where S = 0. This is the same as the C with align=diagonal. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::SierpinskiArrowheadCentres-Enew ()> =item C<$path = Math::PlanePath::SierpinskiArrowheadCentres-Enew (align =E $str)> Create and return a new arrowhead path object. C is a string, one of the following as described above. "triangular" the default "right" "left" "diagonal" =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. If C<$n> is not an integer then the return is on a straight line between the integer points. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 3**$level - 1)>. =back =head1 FORMULAS =head2 N to X,Y The align="diagonal" style is the most convenient to calculate. Each ternary digit of N becomes a bit of X and Y. ternary digits of N, high to low xbit = state_to_xbit[state+digit] ybit = state_to_ybit[state+digit] state = next_state[state+digit] There's a total of 6 states which are the permutations of 0,1,2 in the three triangular parts. The states are in pairs as forward and reverse, but that has no particular significance. Numbering the states by "3"s allows the digit 0,1,2 to be added to make an index into tables for X,Y bit and next state. state=0 state=3 +---------+ +---------+ |^ 2 | | |\ 0 | | | \ | | | \ | | | \ | | | v | | |----+----| |----+----| | |^ | | || | | 0 || 1 | | 0 || 1 | |--->|| | |<---|v | +---------+ +---------+ state=6 state=9 +---------+ +---------+ | | | | | | | 1 | | | 1 | | |--->| | |<---| | |----+----| |----+----| |^ |\ 2 | || |^ | ||0 | \ | || 2 | \0 | || | v | |v | \ | +---------+ +---------+ state=12 state=15 +---------+ +---------+ || 0 | | |^ | | || | | || 2 | | |v | | || | | |----+----| |----+----| |\ 1 | | |^ 1 | | | \ | 2 | | \ | 0 | | v |--->| | \ |<---| +---------+ +---------+ The initial state is 0 if an even number of ternary digits, or 6 if odd. In the samples above it can be seen for example that N=0 to N=8 goes upwards as per state 0, whereas N=0 to N=2 goes across as per state 6. Having calculated an X,Y in align="diagonal" style it can be mapped to the other alignments by align coordinates from diagonal X,Y ----- ----------------------------- triangular X-Y, X+Y right X, X+Y left -Y, X+Y =head2 N to dX,dY For fractional N the direction of the curve towards the N+1 point can be found from the least significant digit 0 or 1 (ie. a non-2 digit) and the state at that point. This works because if the least significant ternary digit of N is a 2 then the direction of the curve is determined by the next level up, and so on for all trailing 2s until reaching a non-2 digit. If N is all 2s then the direction should be reckoned from an initial 0 digit above them, which means the opposite 6 or 0 of the initial state. =head2 Rectangle to N Range An easy over-estimate of the range can be had from inverting the Nlevel formulas in L
above. level = floor(log2(Ymax)) + 1 Nmax = 3^level - 1 For example Y=5, level=floor(log2(11))+1=3, so Nmax=3^3-1=26, which is the left end of the Y=7 row, ie. rounded up to the end of the Y=4 to Y=7 replication. =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/ComplexPlus.pm0000644000175000017500000003446213734026673020056 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=ComplexPlus --all --scale=5 # # math-image --path=ComplexPlus --expression='i<128?i:0' --output=numbers --size=132x40 # # Realpart: # math-image --path=ComplexPlus,realpart=2 --expression='i<50?i:0' --output=numbers --size=180 # # Arms: # math-image --path=ComplexPlus,arms=2 --expression='i<64?i:0' --output=numbers --size=79 package Math::PlanePath::ComplexPlus; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_up_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant parameter_info_array => [ { name => 'realpart', display => 'Real Part', type => 'integer', default => 1, minimum => 1, width => 2, description => 'Real part r in the i+r complex base.', }, { name => 'arms', share_key => 'arms_2', display => 'Arms', type => 'integer', minimum => 1, maximum => 2, default => 1, width => 1, description => 'Arms', when_name => 'realpart', when_value => '1', }, ]; # b=i+r # theta = atan(1/r) sub x_negative_at_n { my ($self) = @_; if ($self->{'realpart'} == 1) { return 8; } return $self->{'norm'} ** _ceil((2*atan2(1,1)) / atan2(1,$self->{'realpart'})); } sub y_negative_at_n { my ($self) = @_; if ($self->{'realpart'} == 1) { return 32; } return $self->{'norm'} ** _ceil((4*atan2(1,1)) / atan2(1,$self->{'realpart'})); } sub _ceil { my ($x) = @_; my $int = int($x); return ($x > $int ? $int+1 : $int); } sub absdx_minimum { my ($self) = @_; return ($self->{'realpart'} == 1 ? 0 # i+1 N=1 dX=0,dY=1 : 1); # i+r otherwise always diff } # use constant dir_maximum_dxdy => (0,0); # supremum, almost full way sub turn_any_straight { my ($self) = @_; return ($self->{'realpart'} != 1); # realpart=1 never straight } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); my $realpart = $self->{'realpart'}; if (! defined $realpart || $realpart < 1) { $self->{'realpart'} = $realpart = 1; } $self->{'norm'} = $realpart*$realpart + 1; my $arms = $self->{'arms'}; if (! defined $arms || $arms <= 0 || $realpart != 1) { $arms = 1; } elsif ($arms > 2) { $arms = 2; } $self->{'arms'} = $arms; return $self; } sub n_to_xy { my ($self, $n) = @_; ### ComplexPlus n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'}); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $realpart = $self->{'realpart'}; my $norm = $self->{'norm'}; ### $norm ### $realpart # for i+1, arm=0 start X=0,Y=0, arm=1 start X=0,Y=1 my $x = 0; my $y = _divrem_mutate ($n, $self->{'arms'}); # for i+1, arm=0 start dX=1,dY=0, arm=1 start dX=-1,dY=0 my $dy = ($n * 0); # 0, inheriting bignum from $n my $dx = ($y ? -1 : 1) + $dy; # inheriting bignum from $n foreach my $digit (digit_split_lowtohigh($n,$norm)) { ### at: "$x,$y digit=$digit dxdy=$dx,$dy" $x += $dx * $digit; $y += $dy * $digit; # multiply i+r, ie. (dx,dy) = (dx + i*dy)*(i+$realpart) ($dx,$dy) = ($realpart*$dx - $dy, $dx + $realpart*$dy); } ### final: "$x,$y" return ($x,$y); } sub xy_to_n { my ($self, $x, $y) = @_; ### ComplexPlus xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); my $realpart = $self->{'realpart'}; { my $rx = $realpart*$x; my $ry = $realpart*$y; foreach my $overflow ($rx+$ry, $rx-$ry) { if (is_infinite($overflow)) { return $overflow; } } } my $orig_x = $x; my $orig_y = $y; my $norm = $self->{'norm'}; my $zero = ($x * 0 * $y); # inherit bignum 0 my @n; # digits low to high my $prev_x = 0; my $prev_y = 0; while ($x || $y) { my $neg_y = $x - $y*$realpart; my $digit = $neg_y % $norm; ### at: "$x,$y n=$n digit $digit" push @n, $digit; $x -= $digit; $neg_y -= $digit; ### assert: ($neg_y % $norm) == 0 ### assert: (($x * $realpart + $y) % $norm) == 0 # divide i+r = mul (i-r)/(i^2 - r^2) # = mul (i-r)/-norm # is (i*y + x) * (i-realpart)/-norm # x = [ x*-realpart - y ] / -norm # = [ x*realpart + y ] / norm # y = [ y*-realpart + x ] / -norm # = [ y*realpart - x ] / norm # ($x,$y) = (($x*$realpart+$y)/$norm, -$neg_y/$norm); if ($x == $prev_x && $y == $prev_y) { last; } $prev_x = $x; $prev_y = $y; } ### final: "$x,$y n=$n cf arms $self->{'arms'}" if ($y) { if ($self->{'arms'} > 1) { ### not on first arm, re-run as: -$orig_x, 1-$orig_y local $self->{'arms'} = 1; my $n = $self->xy_to_n(-$orig_x,1-$orig_y); if (defined $n) { return 1 + 2*$n; # 180 degrees } } ### X,Y not visited return undef; } my $n = digit_join_lowtohigh (\@n, $norm, $zero); if ($self->{'arms'} > 1) { $n *= 2; } return $n; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### ComplexPlus rect_to_n_range(): "$x1,$y1 $x2,$y2" my $xm = max(abs($x1),abs($x2)); my $ym = max(abs($y1),abs($y2)); my $n_hi = ($xm*$xm + $ym*$ym) * $self->{'arms'}; if ($self->{'realpart'} == 1) { $n_hi *= 16; # 2**4 } return (0, int($n_hi)); } #------------------------------------------------------------------------------ # levels sub level_to_n_range { my ($self, $level) = @_; return (0, $self->{'norm'}**$level * $self->{'arms'} - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, $self->{'arms'}); my ($pow, $exp) = round_up_pow ($n+1, $self->{'norm'}); return $exp; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Math-PlanePath ie Nstart Nlevel twindragon DragonCurve OEIS =head1 NAME Math::PlanePath::ComplexPlus -- points in complex base i+r =head1 SYNOPSIS use Math::PlanePath::ComplexPlus; my $path = Math::PlanePath::ComplexPlus->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path traverses points by a complex number base i+r for integer rE=1. The default is base i+1 30 31 14 15 5 28 29 12 13 4 26 27 22 23 10 11 6 7 3 24 25 20 21 8 9 4 5 2 62 63 46 47 18 19 2 3 1 60 61 44 45 16 17 0 1 <- Y=0 58 59 54 55 42 43 38 39 -1 56 57 52 53 40 41 36 37 -2 50 51 94 95 34 35 78 79 -3 48 49 92 93 32 33 76 77 -4 90 91 86 87 74 75 70 71 -5 88 89 84 85 72 73 68 69 -6 126 127 110 111 82 83 66 67 -7 124 125 108 109 80 81 64 65 -8 122 123 118 119 106 107 102 103 -9 120 121 116 117 104 105 100 101 -10 114 115 98 99 -11 112 113 96 97 -12 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 The shape of these points N=0 to N=2^k-1 inclusive is equivalent to the twindragon turned 135 degrees. Each complex base point corresponds to a unit square inside the twindragon curve (two DragonCurve back-to-back). =head2 Real Part Option C $r> selects another r for complex base b=i+r. For example realpart => 2 45 46 47 48 49 8 40 41 42 43 44 7 35 36 37 38 39 6 30 31 32 33 34 5 25 26 27 28 29 20 21 22 23 24 4 15 16 17 18 19 3 10 11 12 13 14 2 5 6 7 8 9 1 0 1 2 3 4 <- Y=0 ^ X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 N is broken into digits of a base norm=r*r+1, ie. digits 0 to r*r inclusive. norm = r*r + 1 Nstart = 0 Nlevel = norm^level - 1 The low digit of N makes horizontal runs of r*r+1 many points, such as N=0 to N=4, then N=5 to N=9, etc shown above. In the default r=1 these runs are 2 long. For r=2 shown above they're 2*2+1=5 long, or r=3 would be 3*3+1=10, etc. The offset for each successive run is i+r, ie. Y=1,X=r such as at N=5 shown above. Then the offset for the next level is (i+r)^2 = (2r*i + r^2-1) so N=25 begins at Y=2*r=4, X=r*r-1=3. In general each level adds an angle angle = atan(1/r) Nlevel_angle = level * angle So the points spiral around anti-clockwise. For r=1 the angle is atan(1/1)=45 degrees, so that for example level=4 is angle 4*45=180 degrees, putting N=2^4=16 on the negative X axis as shown in the first sample above. As r becomes bigger the angle becomes smaller, making it spiral more slowly. The points never fill the plane, but the set of points N=0 to Nlevel are all touching. =head2 Arms For C 1>, an optional C 2> adds a second copy of the curve rotated 180 degrees and starting from X=0,Y=1. It meshes perfectly to fill the plane. Each arm advances successively so N=0,2,4,etc is the plain path and N=1,3,5,7,etc is the copy arms=>2 60 62 28 30 5 56 58 24 26 4 52 54 44 46 20 22 12 14 3 48 50 40 42 16 18 8 10 2 36 38 3 1 4 6 35 33 1 32 34 7 5 0 2 39 37 <- Y=0 11 9 19 17 43 41 51 49 -1 15 13 23 21 47 45 55 53 -2 27 25 59 57 -3 31 29 63 61 -4 ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 There's no C parameter for other C values as yet, only for i+1. Is there a good rotated arrangement for others? Do "norm" many copies fill the plane in general? =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::ComplexPlus-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 2**$level - 1)>, or for 2 arms return C<(0, 2 * 2**$level - 1)>. With the C option return C<(0, $norm**$level - 1)> where norm=realpart^2+1. =back =head1 FORMULAS Various formulas and pictures etc for the i+1 case can be found in the author's long mathematical write-up (section "Complex Base i+1") =over L =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back realpart=1 (i+1, the default) A290885 -X A290884 Y A290886 norm X^2 + Y^2 A146559 dX at N=2^k-1 (step to next replication level) A077950,A077870 location of ComplexMinus origin in ComplexPlus (mirror horizontal even level, vertical odd level) =head1 SEE ALSO L, L, L, L =over L =back =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/SacksSpiral.pm0000644000175000017500000003110713734026656020014 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # could loop by more or less, eg. 4*n^2 each time like a square spiral # (Kevin Vicklund at the_surprises_never_eend_the_u.php) package Math::PlanePath::SacksSpiral; use 5.004; use strict; use Math::Libm 'hypot'; use POSIX 'floor'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use Math::PlanePath; use Math::PlanePath::MultipleRings; use vars '$VERSION', '@ISA'; $VERSION = 129; @ISA = ('Math::PlanePath'); # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant figure => 'circle'; use constant x_negative_at_n => 2; use constant y_negative_at_n => 3; use constant 1.02; # for leading underscore use constant _TWO_PI => 4*atan2(1,0); # at N=k^2 polygon of 2k+1 sides R=k # dX -> sin(2pi/(2k+1))*k # -> 2pi/(2k+1) * k # -> pi use constant dx_minimum => - 2*atan2(1,0); # -pi use constant dx_maximum => 2*atan2(1,0); # +pi use constant dy_minimum => - 2*atan2(1,0); use constant dy_maximum => 2*atan2(1,0); use constant turn_any_right => 0; # left always use constant turn_any_straight => 0; # left always #------------------------------------------------------------------------------ # sub _as_float { # my ($x) = @_; # if (ref $x) { # if ($x->isa('Math::BigInt')) { # return Math::BigFloat->new($x); # } # if ($x->isa('Math::BigRat')) { # return $x->as_float; # } # } # return $x; # } # Note: this is "use Math::BigFloat" not "require Math::BigFloat" because # BigFloat 1.997 does some setups in its import() needed to tie-in to the # BigInt back-end, or something. use constant::defer _bigfloat => sub { eval "use Math::BigFloat; 1" or die $@; return "Math::BigFloat"; }; sub n_to_xy { my ($self, $n) = @_; if ($n < 0) { return; } my $two_pi = _TWO_PI(); if (ref $n) { if ($n->isa('Math::BigInt')) { $n = _bigfloat()->new($n); } if ($n->isa('Math::BigRat')) { $n = $n->as_float; } if ($n->isa('Math::BigFloat')) { $two_pi = 2 * Math::BigFloat->bpi ($n->accuracy || $n->precision || $n->div_scale); } } my $r = sqrt($n); my $theta = $two_pi * ($r - int($r)); # 0 <= $theta < 2*pi return ($r * cos($theta), $r * sin($theta)); } sub n_to_rsquared { my ($self, $n) = @_; if ($n < 0) { return undef; } return $n; # exactly RSquared=$n } sub xy_to_n { my ($self, $x, $y) = @_; ### SacksSpiral xy_to_n(): "$x, $y" my $theta_frac = Math::PlanePath::MultipleRings::_xy_to_angle_frac($x,$y); ### assert: 0 <= $theta_frac && $theta_frac < 1 # the nearest arc, integer my $s = floor (hypot($x,$y) - $theta_frac + 0.5); # the nearest N on the arc my $n = floor ($s*$s + $theta_frac * (2*$s + 1) + 0.5); # check within 0.5 radius my ($nx, $ny) = $self->n_to_xy($n); ### $theta_frac ### raw hypot: hypot($x,$y) ### $s ### $n ### hypot: hypot($nx-$x, $ny-$y) if (hypot($nx-$x,$ny-$y) <= 0.5) { return $n; } else { return undef; } } # r^2 = x^2 + y^2 # (r+1)^2 = r^2 + 2r + 1 # r < x+y # (r+1)^2 < x^2+y^2 + x + y + 1 # < (x+.5)^2 + (y+.5)^2 + 1 # (x+1)^2 + (y+1)^2 = x^2+y^2 + 2x+2y+2 # # (x+1)^2 + (y+1)^2 - (r+1)^2 # = x^2+y^2 + 2x+2y+2 - (r^2 + 2r + 1) # = x^2+y^2 + 2x+2y+2 - x^2-y^2 - 2*sqrt(x^2+y^2) - 1 # = 2x+2y+1 - 2*sqrt(x^2+y^2) # >= 2x+2y+1 - 2*(x+y) # = 1 # # (x+e)^2 + (y+e)^2 - (r+e)^2 # = x^2+y^2 + 2xe+2ye + 2e^2 - (r^2 + 2re + e^2) # = x^2+y^2 + 2xe+2ye + 2e^2 - x^2-y^2 - 2*e*sqrt(x^2+y^2) - e^2 # = 2xe+2ye + e^2 - 2*e*sqrt(x^2+y^2) # >= 2xe+2ye + e^2 - 2*e*(x+y) # = e^2 # # x+1,y+1 increases the radius by at least 1 thus pushing it to the outside # of a ring. Actually it's more, as much as sqrt(2)=1.4142 on the leading # diagonal X=Y. But the over-estimate is close enough for now. # # r = hypot(xmin,ymin) # Nlo = (r-1/2)^2 # = r^2 - r + 1/4 # >= x^2+y^2 - (x+y) because x+y >= r # = x(x-1) + y(y-1) # >= floorx(floorx-1) + floory(floory-1) # in integers if round down to x=0 then x*(x-1)=0 too, so not negative # # r = hypot(xmax,ymax) # Nhi = (r+1/2)^2 # = r^2 + r + 1/4 # <= x^2+y^2 + (x+y) + 1 # = x(x+1) + y(y+1) + 1 # <= ceilx(ceilx+1) + ceily(ceily+1) + 1 # Note: this code shared by TheodorusSpiral. If start using the polar angle # for more accuracy here then unshare it first. # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ($x1,$y1, $x2,$y2) = _rect_to_radius_corners ($x1,$y1, $x2,$y2); ### $x_min ### $y_min ### N min: $x_min*($x_min-1) + $y_min*($y_min-1) ### $x_max ### $y_max ### N max: $x_max*($x_max+1) + $y_max*($y_max+1) + 1 return ($x1*($x1-1) + $y1*($y1-1), $x2*($x2+1) + $y2*($y2+1) + 1); } #------------------------------------------------------------------------------ # generic # $x1,$y1, $x2,$y2 is a rectangle. # Return ($xmin,$ymin, $xmax,$ymax). # # The two points are respectively minimum and maximum radius from the # origin, rounded down or up to integers. # # If the rectangle is entirely one quadrant then the points are two opposing # corners. But if an axis is crossed then the minimum is on that axis and # if the origin is covered then the minimum is 0,0. # # Currently the return is abs() absolute values of the places. Could change # that if there was any significance to the quadrant containing the min/max # corners. # sub _rect_to_radius_corners { my ($x1,$y1, $x2,$y2) = @_; ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; return (int($x2 < 0 ? -$x2 : $x1 > 0 ? $x1 : 0), int($y2 < 0 ? -$y2 : $y1 > 0 ? $y1 : 0), max(_ceil(abs($x1)), _ceil(abs($x2))), max(_ceil(abs($y1)), _ceil(abs($y2)))); } sub _ceil { my ($x) = @_; my $int = int($x); return ($x > $int ? $int+1 : $int); } # FIXME: prefer to stay in integers if possible # return ($rlo,$rhi) which is the radial distance range found in the rectangle sub _rect_to_radius_range { my ($x1,$y1, $x2,$y2) = @_; ($x1,$y1, $x2,$y2) = _rect_to_radius_corners ($x1,$y1, $x2,$y2); return (hypot($x1,$y1), hypot($x2,$y2)); } 1; __END__ =for stopwords Archimedean ie pronic PlanePath Ryde Math-PlanePath XPM Euler's arctan Theodorus dX dY =head1 NAME Math::PlanePath::SacksSpiral -- circular spiral squaring each revolution =head1 SYNOPSIS use Math::PlanePath::SacksSpiral; my $path = Math::PlanePath::SacksSpiral->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThe Sacks spiral by Robert Sacks is an Archimedean spiral with points N placed on the spiral so the perfect squares fall on a line going to the right. Read more at =over L =back An Archimedean spiral means radial distance a constant factor of the angle, and so each loop a constant distance out from the preceding loop, in this case 1 unit out. The polar coordinates are R = sqrt(N) theta = sqrt(N) * 2pi which comes out roughly as 18 19 11 10 17 5 20 12 6 2 0 1 4 9 16 25 3 21 13 7 8 15 24 14 22 23 The X,Y positions returned are fractional, except for the perfect squares on the positive X axis at X=0,1,2,3,etc. The perfect squares are the closest points, at 1 unit apart. Other points are a little further apart. The arms going to the right like N=5,10,17,etc or N=8,15,24,etc are constant offsets from the perfect squares, ie. S for positive or negative integer c. To the left the central arm N=2,6,12,20,etc is the Xpronic numbers S = S, half way between the successive perfect squares. Other arms going to the left are offsets from that, ie. S for integer c. Euler's quadratic d^2+d+41 is one such arm going left. Low values loop around a few times before straightening out at about y=-127. This quadratic has relatively many primes and in a plot of primes on the spiral it can be seen standing out from its surrounds. Plotting various quadratic sequences of points can form attractive patterns. For example the Xtriangular numbers k*(k+1)/2 come out as spiral arcs going clockwise and anti-clockwise. See F for a complete program plotting the spiral points to an XPM image. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::SacksSpiral-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. C<$n> can be any value C<$n E= 0> and fractions give positions on the spiral in between the integer points. For C<$n < 0> the return is an empty list, it being considered there are no negative points in the spiral. =item C<$rsquared = $path-En_to_rsquared ($n)> Return the radial distance R^2 of point C<$n>, or C if there's no point C<$n>. This is simply C<$n> itself, since R=sqrt(N). =item C<$n = $path-Exy_to_n ($x,$y)> Return an integer point number for coordinates C<$x,$y>. Each integer N is considered the centre of a circle of diameter 1 and an C<$x,$y> within that circle returns N. The unit spacing of the spiral means those circles don't overlap, but they also don't cover the plane and if C<$x,$y> is not within one then the return is C. =back =head2 Descriptive Methods =over =item C<$dx = $path-Edx_minimum()> =item C<$dx = $path-Edx_maximum()> =item C<$dy = $path-Edy_minimum()> =item C<$dy = $path-Edy_maximum()> dX and dY have minimum -pi=-3.14159 and maximum pi=3.14159. The loop beginning at N=2^k is approximately a polygon of 2k+1 many sides and radius R=k. Each side is therefore side = sin(2pi/(2k+1)) * k -> 2pi/(2k+1) * k -> pi =item C<$str = $path-Efigure ()> Return "circle". =back =head1 FORMULAS =head2 Rectangle to N Range R=sqrt(N) here is the same as in the C and the code is shared here. See L. The accuracy could be improved here by taking into account the polar angle of the corners which are candidates for the maximum radius. On the X axis the stripes of N are from X-0.5 to X+0.5, but up on the Y axis it's 0.25 further out at Y-0.25 to Y+0.75. The stripe the corner falls in can thus be biased by theta expressed as a fraction 0 to 1 around the plane. An exact theta 0 to 1 would require an arctan, but approximations 0, 0.25, 0.5, 0.75 from the quadrants, or eighths of the plane by XEY etc diagonals. As noted for the Theodorus spiral the over-estimate from ignoring the angle is at worst R many points, which corresponds to a full loop here. Using the angle would reduce that to 1/4 or 1/8 etc of a loop. =head1 SEE ALSO L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/HexArms.pm0000644000175000017500000002570413734026670017146 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=HexArms --lines --scale=10 # math-image --path=HexArms --all --output=numbers_dash # math-image --path=HexArms --values=Polygonal,polygonal=8 # Abundant: A005101 # octagonal numbers ... # 26-gonal near vertical x2 # 152 near horizontal # # 2 # 164 +162 # 542 +378 +216 # 1136 +594 +216 # package Math::PlanePath::HexArms; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines #use Devel::Comments '###'; use constant arms_count => 6; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_even; use constant x_negative_at_n => 4; use constant y_negative_at_n => 6; use constant dx_minimum => -2; use constant dx_maximum => 2; use constant dy_minimum => -1; use constant dy_maximum => 1; *_UNDOCUMENTED__dxdy_list = \&Math::PlanePath::_UNDOCUMENTED__dxdy_list_six; use constant absdx_minimum => 1; use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_right => 0; # only left or straight #------------------------------------------------------------------------------ # [ 0, 1, 2, 3,], # [ 0, 1, 3, 6 ], # N = (1/2 d^2 + 1/2 d) # d = -1/2 + sqrt(2 * $n + 1/4) # = (-1 + 2*sqrt(2 * $n + 1/4)) / 2 # = (-1 + sqrt(8 * $n + 1)) / 2 sub n_to_xy { my ($self, $n) = @_; #### HexArms n_to_xy: $n if ($n < 2) { if ($n < 1) { return; } ### centre $n--; return ($n, -$n); # from n=1 towards n=7 at x=1,y=-1 } $n -= 2; my $frac; { my $int = int($n); $frac = $n - $int; $n = $int; # BigFloat int() gives BigInt, use that } # arm as initial rotation my $rot = _divrem_mutate($n,6); ### $n my $d = int ((-1 + _sqrtint(8 * $n + 1)) / 2); ### d frac: ((-1 + _sqrtint(8 * $n + 1)) / 2) ### $d ### base: $d*($d+1)/2 $n -= $d*($d+1)/2; ### remainder: $n ### assert: $n <= $d $rot += ($d % 6); my $x = $frac + 2 + $d + $n; my $y = $frac - $d + $n; $rot %= 6; if ($rot >= 3) { $rot -= 3; $x = -$x; # rotate 180 $y = -$y; } if ($rot == 0) { return ($x,$y); } elsif ($rot == 1) { return (($x-3*$y)/2, # rotate +60 ($x+$y)/2); } else { return (($x+3*$y)/-2, # rotate +120 ($x-$y)/2); } } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### HexArms xy_to_n: "x=$x, y=$y" if (($x ^ $y) & 1) { return undef; # nothing on odd points } if ($x == 0 && $y == 0) { return 1; } my $rot = 0; # eg. y=2 have (0<=>$y)-$y == -1-2 == -3 if ($x < (0 <=> $y) - $y) { ### left diagonal half ... $rot = 3; $x = -$x; # rotate 180 $y = -$y; } if ($x < $y) { ### upper mid sixth, rot 2 ... $rot += 2; ($x,$y) = ((3*$y-$x)/2, # rotate -120 ($x+$y)/-2); } elsif ($y > 0) { ### first sixth, rot 1 ... $rot++; ($x,$y) = (($x+3*$y)/2, # rotate -60 ($y-$x)/2); } else { ### last sixth, rot 0 ... } ### assert: ($x+$y) % 2 == 0 # diagonal down from N=2 # d=0 n=2 # d=6 n=128 # d=12 n=470 # N = (3 d^2 + 3 d + 2) # = ((3*$d + 3)*$d + 2) # xoffset = 3*($x+$y-2) # N + xoffset = ((3*$d + 3)*$d + 2) + 3*($x+$y-2) # = (3*$d + 3)*$d + 2 + 3*($x+$y) - 6 # = (3*$d + 3)*$d + 3*($x+$y) - 4 # my $d = ($x-$y-2)/2; ### xy: "$x,$y" ### $rot ### x offset: $x+$y-2 ### x offset sixes: 3*($x+$y-2) ### quadratic: "d=$d q=".((3*$d + 3)*$d + 2) ### d mod: $d % 6 ### rot d mod: (($rot-$d) % 6) return ((3*$d + 3)*$d) + 3*($x+$y) - 4 + (($rot-$d) % 6); } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; # d = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ], # Nmax = [ 7, 19, 37, 61, 91, 127, 169, 217, 271 ] # being the N=7 arm one spot before the corner of each run # N = (3 d^2 + 3 d + 1) # = ((3*$d + 3)*$d + 1) # my $d = _rect_to_hex_radius ($x1,$y1, $x2,$y2); return (1, ((3*$d + 3)*$d + 1)); } # hexagonal distance sub _rect_to_hex_radius { my ($x1,$y1, $x2,$y2) = @_; $x1 = abs (round_nearest ($x1)); $y1 = abs (round_nearest ($y1)); $x2 = abs (round_nearest ($x2)); $y2 = abs (round_nearest ($y2)); # radial symmetric in +/-y my $y = max (abs($y1), abs($y2)); # radial symmetric in +/-x my $x = max (abs($x1), abs($x2)); return ($y >= $x ? $y # middle : int(($x + $y + 1)/2)); # end, round up } 1; __END__ =for stopwords Math-PlanePath Ryde =head1 NAME Math::PlanePath::HexArms -- six spiral arms =head1 SYNOPSIS use Math::PlanePath::HexArms; my $path = Math::PlanePath::HexArms->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path follows six spiral arms, each advancing successively, ...--66 5 \ 67----61----55----49----43 60 4 / \ \ ... 38----32----26----20 37 54 3 / \ \ \ 44 21----15---- 9 14 31 48 ... 2 / / \ \ \ \ \ 50 27 10---- 4 3 8 25 42 65 1 / / / / / / / 56 33 16 5 1 2 19 36 59 <-Y=0 / / / / \ / / / 62 39 22 11 6 7----13 30 53 -1 \ \ \ \ \ / / ... 45 28 17 12----18----24 47 -2 \ \ \ / 51 34 23----29----35----41 ... -3 \ \ / 57 40----46----52----58----64 -4 \ 63--... -5 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 The X,Y points are integers using every second position to give a triangular lattice, per L. Each arm is N=6*k+rem for a remainder rem=0,1,2,3,4,5, so sequences related to multiples of 6 or with a modulo 6 pattern may fall on particular arms. =head2 Abundant Numbers The "abundant" numbers are those N with sum of proper divisors E N. For example 12 is abundant because it's divisible by 1,2,3,4,6 and their sum is 16. All multiples of 6 starting from 12 are abundant. Plotting the abundant numbers on the path gives the 6*k arm and some other points in between, * * * * * * * * * * * * * * ... * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * There's blank arms either side of the 6*k because 6*k+1 and 6*k-1 are not abundant until some fairly big values. The first abundant 6*k+1 might be 5,391,411,025, and the first 6*k-1 might be 26,957,055,125. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::HexArms-Enew ()> Create and return a new square spiral object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n E 1> the return is an empty list, as the path starts at 1. Fractional C<$n> gives a point on the line between C<$n> and C<$n+6>, that C<$n+6> being the next on the same spiralling arm. This is probably of limited use, but arises fairly naturally from the calculation. =back =head2 Descriptive Methods =over =item C<$arms = $path-Earms_count()> Return 6. =back =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/TerdragonRounded.pm0000644000175000017500000003110113734026654021033 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::TerdragonRounded; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_up_pow'; use Math::PlanePath::TerdragonCurve; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; *parameter_info_array # arms = \&Math::PlanePath::TerdragonCurve::parameter_info_array; *new = \&Math::PlanePath::TerdragonCurve::new; { my @x_negative_at_n = (undef, 24, 7, 2, 2, 2, 2); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } { my @y_negative_at_n = (undef, 316, 145, 32, 11, 4, 4); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } use constant sumabsxy_minimum => 2; # X=2,Y=0 sub rsquared_minimum { my ($self) = @_; return ($self->arms_count < 2 ? 4 # 1 arm, minimum X=2,Y=0 : 2); # 2 or more arms, minimum X=1,Y=1 } use constant dx_minimum => -2; use constant dx_maximum => 2; use constant dy_minimum => -1; use constant dy_maximum => 1; *_UNDOCUMENTED__dxdy_list = \&Math::PlanePath::_UNDOCUMENTED__dxdy_list_six; use constant absdx_minimum => 1; use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ sub n_to_xy { my ($self, $n) = @_; ### TerdragonRounded n_to_xy(): $n if ($n < 0) { # negative return; } if (is_infinite($n)) { return ($n,$n); } { # ENHANCE-ME: the ends join and the direction can be had without a full # N+1 calculation my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'}); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $arms_count = $self->{'arms'}; my $arm = _divrem_mutate ($n, $arms_count); my $pair = _divrem_mutate ($n, 2); my ($x, $y) = $self->Math::PlanePath::TerdragonCurve::n_to_xy ((9*$n + ($pair ? 4 : 2)) * $arms_count + $arm); ### is: (($x+3*$y)/2).", ".(($y-$x)/2) return (($x+3*$y)/2, ($y-$x)/2); # rotate -60 } sub xy_to_n { my ($self, $x, $y) = @_; ### TerdragonRounded xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); { my $sum = 3*$y + $x; if (is_infinite($sum)) { return $sum; } $sum %= 6; unless ($sum == 2 || $sum == 4) { return undef; } } ($x,$y) = (($x-3*$y)/2, # rotate +60 ($x+$y)/2); ### rotated: "$x,$y" my @n_list = $self->Math::PlanePath::TerdragonCurve::xy_to_n_list ($x, $y); ### @n_list my $arms_count = $self->{'arms'}; foreach my $n (@n_list) { my $arm = _divrem_mutate ($n, $arms_count); ### $arm ### remainder: $n my $mod = $n % 9; if ($mod == 2) { return (2*int(($n-2)/9))*$arms_count + $arm; } if ($mod == 4) { return (2*int(($n-4)/9) + 1)*$arms_count + $arm; } } return undef; } # arms==6 is all "hex_centred" points X+3Y mod 6 == 2 or 4 sub xy_is_visited { my ($self, $x, $y) = @_; if ($self->{'arms'} == 6) { my $mod = (3*$y + $x) % 6; return ($mod == 2 || $mod == 4); } return defined($self->xy_to_n($x,$y)); } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; # my $xmax = int(max(abs($x1),abs($x2))) + 1; # my $ymax = int(max(abs($y1),abs($y2))) + 1; # return (0, # ($xmax*$xmax + 3*$ymax*$ymax) # * 1 # * $self->{'arms'}); $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; # FIXME: How much wider ? # Might matter when TerdragonCurve becomes exact. $x1 = int($x1/3) - 2; $y1 = int($y1/3) - 2; $x2 = int($x2/3) + 2; $y2 = int($y2/3) + 2; my ($n_lo, $n_hi) = $self->Math::PlanePath::TerdragonCurve::rect_to_n_range ($x1,$y1, $x2,$y2); if ($n_hi >= $n_hi) { $n_lo *= 2; $n_hi = 2*$n_hi + 1; } return ($n_lo, $n_hi); } #----------------------------------------------------------------------------- # level_to_n_range() # like TerdragonMidpoint but 2* points on each arm, numbered starting 0 # sub level_to_n_range { my ($self, $level) = @_; return (0, (2*$self->{'arms'}) * 3**$level - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, 2 * $self->{'arms'}); my ($pow, $exp) = round_up_pow ($n+1, 3); return $exp; } #----------------------------------------------------------------------------- 1; __END__ =for stopwords Terdragon terdragon eg Sur une courbe qui remplit toute aire Mathematische Annalen Ryde OEIS ie Math-PlanePath versa Online Radix radix Jorg Arndt Hexdragon hexdragon =head1 NAME Math::PlanePath::TerdragonRounded -- triangular dragon curve, with rounded corners =head1 SYNOPSIS use Math::PlanePath::TerdragonRounded; my $path = Math::PlanePath::TerdragonRounded->new; my ($x, $y) = $path->n_to_xy (123); # or another radix digits ... my $path5 = Math::PlanePath::TerdragonRounded->new (radix => 5); =head1 DESCRIPTION This is a version of the terdragon curve with rounded-off corners, =cut # math-image --path=TerdragonRounded --all --output=numbers_dash --size=132x70 =pod ... 44----43 14 \ / \ 46----45 . 42 13 / . 40----41 12 / 39 . 24----23 20----19 11 \ / \ / \ . 38 25 . 22----21 . 18 10 / \ / 36----37 . 26----27 . 16----17 9 / \ / 35 . 32----31 . 28 15 . 8 \ / \ / \ 34----33 30----29 . 14 7 / . 12----13 . 6 / 11 . 8-----7 5 \ / \ 10-----9 . 6 4 / . 4-----5 3 / 3 2 \ . 2 1 / . 0-----1 . <- Y=0 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 The plain C is tripled in size and two points on each 3-long edge are visited by the C here. =head2 Arms Multiple copies of the curve can be selected, each advancing successively. The curve is 1/6 of the plane (like the plain terdragon) and 6 arms rotated by 60, 120, 180, 240 and 300 degrees mesh together perfectly. C 6> begins as follows. N=0,6,12,18,etc is the first arm (the curve shown above), then N=1,7,13,19 the second copy rotated 60 degrees, N=2,8,14,20 the third rotated 120, etc. =cut # math-image --path=TerdragonRounded,arms=6 --all --output=numbers_dash --size=80x30 =pod arms=>6 43----37 72--... / \ / ... 49 31 66 48----42 / \ / \ / \ 73 55 25 60----54 36 \ / \ / 67----61 19----13 24----30 \ / 38----32 14-----8 7 18 71---... / \ / \ / \ / 44 26----20 2 1 12 65 \ / \ 50----56 9-----3 . 0-----6 59----53 \ / \ ... 62 15 4 5 23----29 47 \ / \ / \ / \ / 74----68 21 10 11----17 35----41 / \ 33----27 16----22 64----70 / \ / \ 39 57----63 28 58 76 \ / \ / \ / 45----51 69 34 52 ... / \ / ...--75 40----46 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -11-10-9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 10 11 =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::TerdragonRounded-Enew ()> =item C<$path = Math::PlanePath::TerdragonRounded-Enew (arms =E $count)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 2 * 3**$level - 1)>, or for multiple arms return C<(0, 2 * $arms * 3**$level - 1)>. These level ranges are like C but with 2 points on each line segment terdragon line segment instead of 1. =back =head1 FORMULAS =head2 X,Y Visited When arms=6 all "hex centred" points of the plane are visited, being those points with X+3Y mod 6 == 2 or 4 "hex_centred" =head1 SEE ALSO L, L, L, L XXJorg Arndt C section 1.31.4 "Terdragon and Hexdragon", where this rounded terdragon is called hexdragon. =head1 HOME PAGE L =head1 LICENSE Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/AlternateTerdragon.pm0000644000175000017500000005611213734026674021365 0ustar gggg# Copyright 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::AlternateTerdragon; use 5.004; use strict; use List::Util 'first'; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use Math::PlanePath; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest', 'xy_is_even'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh', 'round_up_pow'; use vars '$VERSION', '@ISA'; $VERSION = 129; @ISA = ('Math::PlanePath'); use Math::PlanePath::TerdragonMidpoint; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; use constant parameter_info_array => [ { name => 'arms', share_key => 'arms_6', display => 'Arms', type => 'integer', minimum => 1, maximum => 6, default => 1, width => 1, description => 'Arms', } ]; sub x_negative { my ($self) = @_; return ($self->{'arms'} >= 2); } { my @x_negative_at_n = (undef, undef, 5, 5, 6, 7, 8); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } { my @y_negative_at_n = (undef, 6, 12, 18, 11, 9, 10); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } sub dx_minimum { my ($self) = @_; return ($self->{'arms'} == 1 ? -1 : -2); } use constant dx_maximum => 2; use constant dy_minimum => -1; use constant dy_maximum => 1; sub sumxy_minimum { my ($self) = @_; # arm 0 and arm 1 are always above X+Y=0 opposite diagonal, which is +120 deg return ($self->{'arms'} <= 2 ? 0 : undef); } sub diffxy_minimum { my ($self) = @_; # arm 0 remains below the X-Y leading diagonal, being +60 deg return ($self->{'arms'} <= 1 ? 0 : undef); } sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; return ($self->{'arms'} == 1 ? Math::PlanePath::_UNDOCUMENTED__dxdy_list_three() : Math::PlanePath::_UNDOCUMENTED__dxdy_list_six()); } { my @_UNDOCUMENTED__dxdy_list_at_n = (undef, 3, 7, 10, 7, 8, 5); sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $_UNDOCUMENTED__dxdy_list_at_n[$self->{'arms'}]; } } use constant absdx_minimum => 1; use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; # arms=1 curve goes at 0,120,240 degrees # arms=2 second +60 to 60,180,300 degrees # so when arms==1 dir maximum is 240 degrees sub dir_maximum_dxdy { my ($self) = @_; return ($self->{'arms'} == 1 ? (-1,-1) # 0,2,4 only South-West : ( 1,-1)); # rotated to 1,3,5 too South-East } use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(6, $self->{'arms'} || 1)); return $self; } my @dir6_to_dx = (2, 1,-1,-2, -1, 1); my @dir6_to_dy = (0, 1, 1, 0, -1,-1); sub n_to_xy { my ($self, $n) = @_; ### AlternateTerdragon n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } my $zero = ($n * 0); # inherit bignum 0 my $i; # X my $j = $zero; # +60 my $k = $zero; # +120 my $pow = $zero + 1; # inherit bignum 1 # initial rotation from arm number my $rot; { my $int = int($n); $i = $n - $int; # frac, inherit possible BigFloat $n = $int; # BigFloat int() gives BigInt, use that $rot = _divrem_mutate ($n, $self->{'arms'}); } # even si = pow, sj = 0, sk = 0 # odd si = pow, sj = 0, sk = -pow my $even = 1; my @n = digit_split_lowtohigh($n,3); while (@n) { my $digit = shift @n; ### at: "$i, $j, $k even digit $digit" if ($digit == 1) { ($i,$j,$k) = ($pow-$j, -$k, $i); # rotate +120 and add } elsif ($digit == 2) { $j += $pow; # add rotated +60 } last unless @n; $digit = shift @n; if ($digit == 1) { ($i,$j,$k) = ($pow+$k, $pow-$i, -$j); # rotate -120 and add } elsif ($digit == 2) { $i += $pow; # add * b $k -= $pow; } $pow *= 3; } ### final: "$i, $j, $k" ### is: (2*$i + $j - $k).", ".($j+$k) ### $rot if ($rot >= 3) { ($i,$j,$k) = (-$i,-$j,-$k); $rot -= 3; } if ($rot == 1) { ($i,$j,$k) = (-$k,$i,$j); } # rotate +60 elsif ($rot == 2) { ($i,$j,$k) = (-$j,-$k, $i); } # rotate +128 return (2*$i + $j - $k, $j+$k); } # all even points when arms==6 sub xy_is_visited { my ($self, $x, $y) = @_; if ($self->{'arms'} == 6) { return xy_is_even($self,$x,$y); } else { return defined($self->xy_to_n($x,$y)); } } sub xy_to_n { return scalar((shift->xy_to_n_list(@_))[0]); } sub xy_to_n_list { my ($self, $x,$y) = @_; ### AlternateTerdragon xy_to_n_list(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); { # nothing at an odd point, and trap overflows in $x+$y dividing out b my $sum = abs($x) + abs($y); if (is_infinite($sum)) { return $sum; } # infinity if ($sum % 2) { return; } } if ($x==0 && $y==0) { return 0 .. $self->{'arms'}-1; } my $arms_count = $self->arms_count; my $zero = ($x * 0 * $y); # inherit bignum 0 my @n_list; foreach my $d (0,1,2) { my ($ndigits,$arm) = _xy_d_to_ndigits_and_arm($x,$y,$d); next if $arm >= $arms_count; if ($arm & 1) { ### flip ... @$ndigits = map {2-$_} @$ndigits; } push @n_list, digit_join_lowtohigh($ndigits, 3, $zero) * $arms_count + $arm; } ### unsorted n_list: @n_list return sort {$a<=>$b} @n_list; } my @digit_to_x = ([0,2,1], [0,-1,-2], [0,-1, 1]); my @digit_to_y = ([0,0,1], [0, 1, 0], [0,-1,-1]); # $d = 0,1,2 for segment leaving $x,$y at direction $d*120 degrees. # For odd arms the digits are 0<->2 reversals. sub _xy_d_to_ndigits_and_arm { my ($x,$y, $d) = @_; ### _xy_d_to_ndigits_and_arm(): "$x,$y d=$d" my @ndigits; my $arm; for (;;) { ### at: "$x,$y d=$d" if ($x==0 && $y==0) { $arm = 2*$d; last; } if ($d==2 && $x==1 && $y==1) { $arm = 1; last; } if ($d==0 && $x==-2 && $y==0) { $arm = 3; last; } if ($d==1 && $x==1 && $y==-1) { $arm = 5; last; } my $a = $x % 3; # z mod b = -x mod 3 if ($a) { $a = 3-$a; } push @ndigits, $a; if ($a==1) { $d = ($d-1) % 3; } ### a: $a ### new d: $d $x -= $digit_to_x[$d]->[$a]; $y -= $digit_to_y[$d]->[$a]; ### subtract: "$digit_to_x[$d]->[$a],$digit_to_y[$d]->[$a] to $x,$y" ### assert: ($x+$y) % 2 == 0 ### assert: $x % 3 == 0 ### assert: ($y-$x/3) % 2 == 0 ### assert: (3*$y-$x) % 6 == 0 ($x,$y) = (($x+$y)/2, # divide b = w6+1 ($y-$x/3)/2); $y = -$y; $d = (-$d) % 3; } if (scalar(@ndigits) & 1) { $arm = (6-$arm) % 6; } ### $arm ### @ndigits return (\@ndigits, $arm); } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### AlternateTerdragon rect_to_n_range(): "$x1,$y1 $x2,$y2" my $xmax = int(max(abs($x1),abs($x2))); my $ymax = int(max(abs($y1),abs($y2))); return (0, ($xmax*$xmax + 3*$ymax*$ymax + 1) * 2 * $self->{'arms'}); } my @digit_to_nextturn = (2,-2); sub n_to_dxdy { my ($self, $n) = @_; ### AlternateTerdragon n_to_dxdy(): $n if ($n < 0) { return; # first direction at N=0 } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); # integer part $n -= $int; # fraction part # initial direction from arm my $dir6 = _divrem_mutate ($int, $self->{'arms'}); my @ndigits = digit_split_lowtohigh($int,3); foreach my $i (0 .. $#ndigits) { if ($ndigits[$i] == 1) { $dir6 += 2*($i&1 ? -1 : 1); # count 1s for total turn } } $dir6 %= 6; my $dx = $dir6_to_dx[$dir6]; my $dy = $dir6_to_dy[$dir6]; if ($n) { ### fraction part: $n # find lowest non-2 digit, or zero if all 2s or no digits at all my $above = scalar(@ndigits); foreach my $i (0 .. $#ndigits) { if ($ndigits[$i] != 2) { ### lowest non-2: "at i=$i digit=$ndigits[$i]" $above = $ndigits[$i] ^ $i; last; } } $dir6 = ($dir6 + $digit_to_nextturn[$above & 1]) % 6; ### $above ### $dir6 $dx += $n*($dir6_to_dx[$dir6] - $dx); $dy += $n*($dir6_to_dy[$dir6] - $dy); } return ($dx, $dy); } #----------------------------------------------------------------------------- # eg. arms=5 0 .. 5*3^k step by 5s # 1 .. 5*3^k+1 step by 5s # 4 .. 5*3^k+4 step by 5s # sub level_to_n_range { my ($self, $level) = @_; return (0, (3**$level + 1) * $self->{'arms'} - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, $self->{'arms'}); my ($pow, $exp) = round_up_pow ($n, 3); return $exp; } 1; __END__ =for stopwords eg Ryde Math-PlanePath terdragon Ns dX ie OEIS =head1 NAME Math::PlanePath::AlternateTerdragon -- alternate terdragon curve =head1 SYNOPSIS use Math::PlanePath::AlternateTerdragon; my $path = Math::PlanePath::AlternateTerdragon->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XXThis is the alternate terdragon curve by Davis and Knuth, =over Chandler Davis and Donald Knuth, "Number Representations and Dragon Curves -- I", Journal Recreational Mathematics, volume 3, number 2 (April 1970), pages 66-81 and "Number Representations and Dragon Curves -- II", volume 3, number 3 (July 1970), pages 133-149. Reprinted with addendum in Knuth "Selected Papers on Fun and Games", 2010, pages 571--614. L =back Points are a triangular grid using every second integer X,Y as per L, beginning =cut # generated by code in devel/alternate-terdragon.pl =pod \ / \ / Y=2 14,17 --- 15,24,33 -- \ / \ \ / \ / Y=1 2 ------- 3,12 ---- 10,13,34 -- 32,35,38 \ / \ / \ / \ \ / \ / \ / Y=0 0 -------- 1,4 ----- 5,8,11 ----- 9,36 ---- / \ / \ Y=-1 6 --------- 7 ^ ^ ^ ^ ^ ^ ^ ^ X=0 1 2 3 4 5 6 7 A segment 0 to 1 is unfolded to 2-----3 \ \ 0-----1 Then 0 to 3 is unfolded likewise, but the folds are the opposite way. Where 1-2 went on the left, for 3-6 goes to the right. 2-----3 2-----3 \ / \ / \ / \ / 0----1,4----5 0----1,4---5,8----9 / / \ / / \ 6 6-----7 Successive unfolds go alternate ways. Taking two unfold at a time is segment replacement by the 0 to 9 figure (rotated as necessary). The curve never crosses itself. Vertices touch at triangular corners. Points can be visited 1, 2 or 3 times. The two triangles have segment 4-5 between. In general points to a level N=3^k have a single segment between two blobs, for example N=0 to N=3^6=729 below. But as the curve continues it comes back to put further segments there (and a single segment between bigger blobs). =cut # the following generated by # math-image --path=AlternateTerdragon --expression='i<=729?i:0' --text --size=132x40 =pod * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * O * * * * * * * * * * * * * * * * * * * * * * * * * * E * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * The top boundary extent is at an angle +60 degrees and the bottom at -30 degrees, / 60 deg / / O------------------- --__ --__ 30 deg An even expansion level is within a rectangle with endpoint at X=2*3^(k/2),Y=0. =head2 Arms The curve fills a sixth of the plane and six copies rotated by 60, 120, 180, 240 and 300 degrees mesh together perfectly. The C parameter can choose 1 to 6 such curve arms successively advancing. For example C 6> begins as follows. N=0,6,12,18,etc is the first arm (the same shape as the plain curve above), then N=1,7,13,19 the second, N=2,8,14,20 the third, etc. =cut # generated by code in devel/alternate-terdragon.pl =pod \ / \ / \ / \ / --- 7,8,26 ----------------- 1,12,19 --- / \ / \ \ / \ / \ / \ / \ / \ / --- 3,14,21 ------------- 0,1,2,3,4,5 -------------- 6,11,24 --- / \ / \ / \ / \ / \ / \ \ / \ / ---- 9,10,28 ---------------- 5,16,23 --- / \ / \ / \ / \ With six arms every X,Y point is visited three times, except the origin 0,0 where all six begin. Every edge between points is traversed once. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::AlternateTerdragon-Enew ()> =item C<$path = Math::PlanePath::AlternateTerdragon-Enew (arms =E 6)> Create and return a new path object. The optional C parameter can make 1 to 6 copies of the curve, each arm successively advancing. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. If there's nothing at C<$x,$y> then return C. The curve can visit an C<$x,$y> up to three times. C returns the smallest of the these N values. =item C<@n_list = $path-Exy_to_n_list ($x,$y)> Return a list of N point numbers for coordinates C<$x,$y>. The origin 0,0 has C many N since it's the starting point for each arm. Other points have up to 3 Ns for a given C<$x,$y>. If arms=6 then every even C<$x,$y> except the origin has exactly 3 Ns. =back =head2 Descriptive Methods =over =item C<$n = $path-En_start()> Return 0, the first N in the path. =item C<$dx = $path-Edx_minimum()> =item C<$dx = $path-Edx_maximum()> =item C<$dy = $path-Edy_minimum()> =item C<$dy = $path-Edy_maximum()> The dX,dY values on the first arm take three possible combinations, being 120 degree angles. dX,dY for arms=1 ----- 2, 0 dX minimum = -1, maximum = +2 -1, 1 dY minimum = -1, maximum = +1 1,-1 For 2 or more arms the second arm is rotated by 60 degrees so giving the following additional combinations, for a total six. This changes the dX minimum. dX,dY for arms=2 or more ----- -2, 0 dX minimum = -2, maximum = +2 1, 1 dY minimum = -1, maximum = +1 -1,-1 =item C<$sum = $path-Esumxy_minimum()> =item C<$sum = $path-Esumxy_maximum()> Return the minimum or maximum values taken by coordinate sum X+Y reached by integer N values in the path. If there's no minimum or maximum then return C. S=X+Y is an anti-diagonal. The first arm is entirely above a line 135deg -- -45deg, per the +60deg to -30deg extents shown above. Likewise the second arm which is to 60+60=120deg. They have C. More arms and all C are unbounded so C. =item C<$diffxy = $path-Ediffxy_minimum()> =item C<$diffxy = $path-Ediffxy_maximum()> Return the minimum or maximum values taken by coordinate difference X-Y reached by integer N values in the path. If there's no minimum or maximum then return C. D=X-Y is a leading diagonal. The first arm is entirely right of a line 45deg -- -135deg, per the +60deg to -30deg extents shown above, so it has C. More arms and all C are unbounded so C. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 3**$level)>, or for multiple arms return C<(0, $arms * 3**$level + ($arms-1))>. There are 3^level segments in a curve level, so 3^level+1 points numbered from 0. For multiple arms there are arms*(3^level+1) points, numbered from 0 so n_hi = arms*(3^level+1)-1. =back =head1 FORMULAS =cut # Various formulas for coordinates, boundary, area and more can be found in # the author's mathematical write-up # # =over # # L # # =back # # =head2 N to X,Y # # There's no reversals or reflections in the curve so C can take # the digits of N either low to high or high to low and apply what is # effectively powers of the N=3 position. The current code goes low to high # using i,j,k coordinates as described in L. # # si = 1 # position of endpoint N=3^level # sj = 0 # where level=number of digits processed # sk = 0 # # i = 0 # position of N for digits so far processed # j = 0 # k = 0 # # loop base 3 digits of N low to high # if digit == 0 # i,j,k no change # if digit == 1 # (i,j,k) = (si-j, sj-k, sk+i) # rotate +120, add si,sj,sk # if digit == 2 # i -= sk # add (si,sj,sk) rotated +60 # j += si # k += sj # # (si,sj,sk) = (si - sk, # add rotated +60 # sj + si, # sk + sj) # # The digit handling is a combination of rotate and offset, # # digit==1 digit 2 # rotate and offset offset at si,sj,sk rotated # # ^ 2------> # \ # \ \ # *--- --1 *-- --* # # The calculation can also be thought of in term of w=1/2+I*sqrt(3)/2, a # complex number sixth root of unity. i is the real part, j in the w # direction (60 degrees), and k in the w^2 direction (120 degrees). si,sj,sk # increase as if multiplied by w+1. =pod =head2 Turn At each point N the curve always turns 120 degrees either to the left or right, it never goes straight ahead. If N is written in ternary then the lowest non-zero digit at its position gives the turn. Positions are counted from 0 for the least significant digit and up from there. turn ternary lowest non-zero digit ----- --------------------------------------- left 1 at even position or 2 at odd position right 2 at even position or 1 at odd position The flip of turn at odd positions is the "alternating" in the curve. next turn ternary lowest non-2 digit --------- --------------------------------------- left 0 at even position or 1 at odd position right 1 at even position or 0 at odd position =head2 Total Turn The direction at N, ie. the total cumulative turn, is given by the 1 digits of N written in ternary. direction = 120deg * sum / +1 if digit=1 at even position \ -1 if digit=1 at odd position This is used mod 3 for C. =head2 X,Y to N The current code is roughly the same as C C, but with a conjugate (negate Y, reverse direction d) after each digit low to high. =head2 X,Y Visited When arms=6 all "even" points of the plane are visited. As per the triangular representation of X,Y this means X+Y mod 2 == 0 "even" points =head1 OEIS Sequences in Sloane's Online Encyclopedia of Integer Sequences related to the alternate terdragon include, =over L (etc) =back A156595 next turn 0=left, 1=right (morphism) A189715 N positions of left turns A189716 N positions of right turns A189717 count right turns so far =head1 HOUSE OF GRAPHS House of Graphs entries for the alternate terdragon curve as a graph include =over L etc =back 19655 level=0 (1-segment path) 594 level=1 (3-segment path) 30397 level=2 30399 level=3 33575 level=4 33577 level=5 =head1 SEE ALSO L, L L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/SquareReplicate.pm0000644000175000017500000003737513734026656020703 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=SquareReplicate --lines --scale=10 # math-image --path=SquareReplicate --all --output=numbers_dash --size=80x50 # math-image --path=SquareReplicate,numbering_type=rotate-4 --all --output=numbers --size=48x9 package Math::PlanePath::SquareReplicate; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow','round_up_pow', 'digit_split_lowtohigh','digit_join_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ { name => 'numbering_type', display => 'Numbering', type => 'enum', default => 'fixed', choices => ['fixed','rotate-4','rotate-8'], choices_display => ['Fixed','Rotate 4','Rotate 8'], description => 'Fixed or rotating sub-part numbering.', }, ]; use constant n_start => 0; use constant xy_is_visited => 1; use constant ddiffxy_maximum => 1; use constant dir_maximum_dxdy => (0,-1); # South # these don't vary with numbering_type since initial N=0to9 same use constant x_negative_at_n => 4; use constant y_negative_at_n => 6; #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); $self->{'numbering_type'} ||= 'fixed'; # default return $self; } sub _digits_rotate_lowtohigh { my ($self, $aref) = @_; my $rot = 0; my $mask = ($self->{'numbering_type'} eq 'rotate-4' ? 1 : 0); foreach my $digit (reverse @$aref) { if ($digit) { $digit--; my $delta_rot = $digit - ($digit & $mask); $digit = (($digit + $rot) % 8) + 1; # mutate $aref $rot += $delta_rot; } } } sub _digits_unrotate_lowtohigh { my ($self, $aref) = @_; ### _digits_unrotate_lowtohigh(): @$aref my $rot = 0; my $mask = ($self->{'numbering_type'} eq 'rotate-4' ? 1 : 0); foreach my $digit (reverse @$aref) { ### at: "digit=$digit rot=$rot" if ($digit) { $digit = ($digit-1 - $rot) % 8; # mutate $aref ### new digit 0-based: $digit $rot += $digit - ($digit & $mask); ### $rot $digit++; ### new digit 1-based: $digit } } } # 4 3 2 # 5 0 1 # 6 7 8 # my @digit_to_x = (0,1, 1,0,-1, -1, -1, 0, 1); my @digit_to_y = (0,0, 1,1, 1, 0, -1,-1,-1); sub n_to_xy { my ($self, $n) = @_; ### SquareReplicate n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $x = my $y = ($n * 0); # inherit bignum 0 my $len = ($x + 1); # inherit bignum 1 my @digits = digit_split_lowtohigh($n,9); if ($self->{'numbering_type'} ne 'fixed') { _digits_rotate_lowtohigh($self, \@digits, 1); } foreach my $digit (@digits) { ### at: "$x,$y digit=$digit" $x += $digit_to_x[$digit] * $len; $y += $digit_to_y[$digit] * $len; $len *= 3; } ### final: "$x,$y" return ($x,$y); } # mod digit # 5 3 4 4 3 2 (x mod 3) + 3*(y mod 3) # 2 0 1 5 0 1 # 8 6 7 6 7 8 # my @mod_to_digit = (0,1,5, 3,2,4, 7,8,6); sub xy_to_n { my ($self, $x, $y) = @_; ### SquareReplicate xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); my ($len,$level_limit); { my $xa = abs($x); my $ya = abs($y); ($len,$level_limit) = round_down_pow (2*($xa > $ya ? $xa : $ya) || 1, 3); ### $level_limit ### $len } $level_limit += 2; if (is_infinite($level_limit)) { return $level_limit; } my $zero = ($x * 0 * $y); # inherit bignum 0 my @n; # digits low to high while ($x || $y) { if ($level_limit-- < 0) { ### oops, level limit reached ... return undef; } my $m = ($x % 3) + 3*($y % 3); my $digit = $mod_to_digit[$m]; push @n, $digit; ### at: "$x,$y m=$m digit=$digit" $x -= $digit_to_x[$digit]; $y -= $digit_to_y[$digit]; ### subtract: "$digit_to_x[$digit],$digit_to_y[$digit] to $x,$y" ### assert: $x!=$x || $x % 3 == 0 ### assert: $y!=$y || $y % 3 == 0 $x /= 3; $y /= 3; } ### n from xy: @n if ($self->{'numbering_type'} ne 'fixed') { _digits_rotate_lowtohigh($self, \@n, -1); ### @n } return digit_join_lowtohigh (\@n, 9, $zero); } # level N Xmax # 1 9^1-1 1 # 2 9^2-1 1+3 # 3 9^3-1 1+3+9 # X <= 3^0+3^1+...+3^(level-1) # X <= 1 + 3^0+3^1+...+3^(level-1) # X <= (3^level - 1)/2 # 2*X+1 <= 3^level # level >= log3(2*X+1) # # X < 1 + 3^0+3^1+...+3^(level-1) # X < 1 + (3^level - 1)/2 # (3^level - 1)/2 > X-1 # 3^level - 1 > 2*X-2 # 3^level > 2*X-1 # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### SquareReplicate rect_to_n_range(): "$x1,$y1 $x2,$y2" my $max = abs(round_nearest($x1)); foreach ($y1, $x2, $y2) { my $m = abs(round_nearest($_)); if ($m > $max) { $max = $m } } my ($pow) = round_down_pow (2*($max||1)-1, 3); return (0, 9*$pow*$pow - 1); # 9^level-1 } #----------------------------------------------------------------------------- # level_to_n_range() # shared by Math::PlanePath::WunderlichMeander and more sub level_to_n_range { my ($self, $level) = @_; return (0, 9**$level - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($pow, $exp) = round_up_pow ($n+1, 9); return $exp; } #----------------------------------------------------------------------------- 1; __END__ =for stopwords eg Ryde Math-PlanePath aabbccdd characterize =head1 NAME Math::PlanePath::SquareReplicate -- replicating squares =head1 SYNOPSIS use Math::PlanePath::SquareReplicate; my $path = Math::PlanePath::SquareReplicate->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path is a self-similar replicating square, 40--39--38 31--30--29 22--21--20 4 | | | | | | 41 36--37 32 27--28 23 18--19 3 | | | 42--43--44 33--34--35 24--25--26 2 49--48--47 4-- 3-- 2 13--12--11 1 | | | | | | 50 45--46 5 0-- 1 14 9--10 <- Y=0 | | | 51--52--53 6-- 7-- 8 15--16--17 -1 58--57--56 67--66--65 76--75--74 -2 | | | | | | 59 54--55 68 63--64 77 72--73 -3 | | | 60--61--62 69--70--71 78--79--80 -4 ^ -4 -3 -2 -1 X=0 1 2 3 4 The base shape is the initial N=0 to N=8 section, 4 3 2 5 0 1 6 7 8 It then repeats with 3x3 blocks arranged in the same pattern, then 9x9 blocks, etc. 36 --- 27 --- 18 | | | | 45 0 --- 9 | | 54 --- 63 --- 72 The replication means that the values on the X axis are those using only digits 0,1,5 in base 9. Those to the right have a high 1 digit and those to the left a high 5 digit. These digits are the values in the initial N=0 to N=8 figure which fall on the X axis. Similarly on the Y axis digits 0,3,7 in base 9, or the leading diagonal X=Y 0,2,6 and opposite diagonal 0,4,8. The opposite diagonal digits 0,4,8 are 00,11,22 in base 3, so is all the values in base 3 with doubled digits aabbccdd, etc. =head2 Level Ranges A given replication extends to Nlevel = 9^level - 1 - (3^level - 1) <= X <= (3^level - 1) - (3^level - 1) <= Y <= (3^level - 1) =head2 Complex Base This pattern corresponds to expressing a complex integer X+i*Y with axis powers of base b=3, X+Yi = a[n]*b^n + ... + a[2]*b^2 + a[1]*b + a[0] using complex digits a[i] encoded in N in integer base 9, a[i] digit N digit ---------- ------- 0 0 1 1 i+1 2 i 3 i-1 4 -1 5 -i-1 6 -i 7 -i+1 8 =head2 Numbering Rotate-4 Parameter C 'rotate-4'> applies a rotation to 4 directions E,N,W,S for each sub-part according to its position around the preceding level. ^ ^ | | +---+---+---+ | 4 3 | 2 |--> +---+---+ + <--| 5 | 0>| 1 |--> + +---+---+ <--| 6 | 7 8 | +---+---+---+ | | v v The effect can be illustrated by writing N in base-9. =cut # math-image --path=SquareReplicate,numbering_type=rotate-4 --all --output=numbers_dash --size=58x27 =pod 42--41 48 32--31 38 24--23--22 | | | | | | | | 43 40 47 33 30 37 25 20--21 numbering_type => 'rotate-4' | | | | | N shown in base-9 44--45--46 34--35--36 26--27--28 58--57--56 4---3---2 14--13--12 | | | | | 51--50 55 5 0---1 15 10--11 | | | | 52--53--54 6---7---8 16--17--18 68--67--66 76--75--74 86--85--84 | | | | | 61--60 65 77 70 73 87 80 83 | | | | | | | | 62--63--64 78 71--72 88 81--82 Parts 10-18 and 20-28 are the same as the middle 0-8. Parts 30-38 and 40-48 have a rotation by +90 degrees. Parts 50-58 and 60-68 rotation by +180 degrees, and so on. Notice this means in each part the base-9 points 11, 21, 31, points are directed away from the middle in the same way, relative to the sub-part locations. This gives a reasonably simple way to characterize points on the boundary of a given expansion level. Working through the directions and boundary sides gives a state machine for which unit squares are on the boundary. For level E= 1 a given unit square has one of both of two sides on the boundary. B +-----+ | | unit square with expansion direction, | |-> A one or both of sides A,B on the boundary | | +-----+ A further low base-9 digit expands the square to a block of 9, with squares then boundary or not. The result is 4 states, which can be expressed by pairs of digits write N in base-9 using level many digits, delete all 2s in 2nd or later digit non-boundary = 0 anywhere 5 or 6 or 7 in 2nd or later digit pair 13,33,53,73, 14,34,54,74 anywhere pair 43,44, 81,88 at 2nd or later digit Pairs 53,73,54,74 can be checked just at the start of the digits, since 5 or 7 anywhere later are non-boundary alone irrespective of what (if any) pair they might make. =cut # boundary squares # GP-DEFINE B(k) = if(k==0,1, 4*(3^k-1)); # GP-Test vector(6,k,k--; B(k)) == [1, 8, 32, 104, 320, 968] # k>=1 half = A100774 2*(3^n - 1) # GP-DEFINE BpredRot4(n,k) = { # GP-DEFINE my(v=digits(n,9)); # GP-DEFINE while(#v=2, # GP-DEFINE v=concat([v[1]],select(d->d!=2, v[2..#v]))); # GP-DEFINE for(i=1,#v, if(v[i]==0,return(0))); # GP-DEFINE for(i=2,#v, if(v[i]==5||v[i]==6||v[i]==7,return(0))); # GP-DEFINE for(i=1,#v-1, # GP-DEFINE if((v[i]==1||v[i]==3||v[i]==5||v[i]==7) # GP-DEFINE && (v[i+1]==3||v[i+1]==4), return(0))); # GP-DEFINE for(i=2,#v-1, # GP-DEFINE if(v[i]==4 # GP-DEFINE && (v[i+1]==3||v[i+1]==4), return(0)); # GP-DEFINE if(v[i]==8 # GP-DEFINE && (v[i+1]==1||v[i+1]==8), return(0))); # GP-DEFINE 1; # GP-DEFINE } # GP-Test vector(6,k,k--; B(k)) == \ # GP-Test vector(6,k,k--; sum(n=0,9^k-1,BpredRot4(n,k))) # GP-DEFINE to_base9(n) = fromdigits(digits(n,9)); # my(k=2); for(n=0,9^k-1,if(BpredRot4(n,k),print1(to_base9(n)","))); print(); # my(k=2); for(n=0,9^k-1,if(BpredRot4(n,k),print1(n","))); print(); # not in OEIS: 10,11,17,19,20,21,22,26,28,29,35,37,38,39,40,44,46,47,53,55,56,57,58,62,64,65,71,73,74,75,76,80 # not in OEIS: 11,12,18,21,22,23,24,28,31,32,38,41,42,43,44,48,51,52,58,61,62,63,64,68,71,72,78,81,82,83,84,88 =pod =head2 Numbering Rotate 8 Parameter C 'rotate-8'> applies a rotation to 8 directions for each sub-part according to its position around the preceding level. ^ ^ ^ \ | / +---+---+---+ | 4 | 3 | 2 | +---+---+---+ <--| 5 | 0>| 1 |--> +---+---+---+ | 6 | 7 | 8 | +---+---+---+ / | \ v v v The effect can be illustrated again by N in base-9. =cut # math-image --path=SquareReplicate,numbering_type=rotate-8 --all --output=numbers_dash --size=80x50 =pod 41 48-47 32-31 38 23-22-21 |\ | | | | | / 42 40 46 33 30 37 24 20 28 numbering_type => 'rotate' | | | | | | N shown in base-9 43-44-45 34-35-36 25-26-27 58-57-56 4--3--2 14-13-12 | | | | | 51-50 55 5 0--1 15 10-11 | | | | 52-53-54 6--7--8 16-17-18 67-66-65 76-75-74 85-84-83 | | | | | | 68 60 64 77 70 73 86 80 82 / | | | | | \ | 61-62-63 78 71-72 87-88 81 Notice this means in each part the 11, 21, 31, etc, points are directed away from the middle in the same way, relative to the sub-part locations. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::SquareReplicate-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 9**$level - 1)>. =back =head1 SEE ALSO L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/WythoffPreliminaryTriangle.pm0000644000175000017500000002252113734026653023122 0ustar gggg# Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # x=45,y=10 x=59,y=19 dx=14,dy=9 14/9=1.55 # # x=42,y=8 x=113,y=52 dx=71,dy=44 71/44=1.613 # # below # 32,12 to 36,4 sqrt((32-36)^2+(12-4)^2) = 9 # 84,34 to 99,14 sqrt((84-99)^2+(34-14)^2) = 25 # 180,64 to 216,11 sqrt((180-216)^2+(64-11)^2) = 64 # # above # 14,20 to 5,32 sqrt((14-5)^2+(20-32)^2) = 15 = 9*1.618 3 # 34,50 to 14,85 sqrt((34-14)^2+(50-85)^2) = 40 = 25*1.618 5 # 132,158 to 77,247 sqrt((132-77)^2+(158-247)^2) = 104 = 64*1.618 8 # 8,525 to 133,280 sqrt((8-133)^2+(525-280)^2) = 275 = 169*1.618 13 package Math::PlanePath::WythoffPreliminaryTriangle; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'bit_split_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant y_minimum => 1; use constant diffxy_maximum => -1; # Y>=X+1 so X-Y <= -1 # Apparent minimum dx=F(i),dy=F(i+5) # eg. N=57313 dx=377,dy=34 F(14),F(9) # N=392835 dx=987,dy=89 F(16),F(11) # N=2692537 dx=2584,dy=233 F(18),F(13) # dy/dx -> 1/phi^5 use constant dir_minimum_dxdy => (((1+sqrt(5))/2)**5, 1); use constant dir_maximum_dxdy => (1,-1); # SE at N=5 use Math::PlanePath::WythoffArray; my $wythoff = Math::PlanePath::WythoffArray->new; sub new { my $self = shift->SUPER::new(@_); $self->{'shift'} ||= 0; return $self; } sub n_to_xy { my ($self, $n) = @_; ### WythoffPreliminaryTriangle n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n) || $n == 0) { return ($n,$n); } { # fractions on straight line ? my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } # prev+y=x # prev = x-y $n -= 1; my $y = $wythoff->xy_to_n(0,$n); my $x = $wythoff->xy_to_n(1,$n); while ($y <= $x) { ### at: "y=$y x=$x" ($y,$x) = ($x-$y,$y); } ### reduction to: "y=$y x=$x" ### return: "y=$y x=$x" return ($x, $y); } sub xy_is_visited { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); return $x >= 0 && $x < $y; } sub xy_to_n { my ($self, $x, $y) = @_; ### WythoffPreliminaryTriangle xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); my $orig_x = $x; my $orig_y = $y; if (is_infinite($y)) { return $y; } unless ($x >= 0 && $x < $y) { return undef; } ($y,$x) = ($x,$x+$y); foreach (0 .. 500) { ($y,$x) = ($x,$x+$y); ### at: "seek y=$y x=$x" my ($c,$r) = $wythoff->n_to_xy($y) or next; my $wx = $wythoff->xy_to_n($c+1,$r); if (defined $wx && $wx == $x) { ### found: "pair $y $x at c=$c r=$r" my $n = $r+1; my ($nx,$ny) = $self->n_to_xy($n); ### nxy: "nx=$nx, ny=$ny" if ($nx == $orig_x && $ny == $orig_y) { return $n; } else { ### no match: "cf x=$x y=$y" return undef; } } } ### not found ... return undef; } sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### WythoffPreliminaryTriangle rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 1) { ### all outside first quadrant ... return (1, 0); } return (1, $self->xy_to_n(0,2*abs($y2))); } 1; __END__ =for stopwords eg Ryde Math-PlanePath Moore Wythoff Zeckendorf concecutive fibbinary OEIS Kimberling precurses =head1 NAME Math::PlanePath::WythoffPreliminaryTriangle -- Wythoff row containing X,Y recurrence =head1 SYNOPSIS use Math::PlanePath::WythoffPreliminaryTriangle; my $path = Math::PlanePath::WythoffPreliminaryTriangle->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis path is the Wythoff preliminary triangle by Clark Kimberling, =cut # math-image --path=WythoffPreliminaryTriangle --output=numbers --all --size=60x14 =pod 13 | 105 118 131 144 60 65 70 75 80 85 90 95 100 12 | 97 110 47 52 57 62 67 72 77 82 87 92 11 | 34 39 44 49 54 59 64 69 74 79 84 10 | 31 36 41 46 51 56 61 66 71 76 9 | 28 33 38 43 48 53 58 63 26 8 | 25 30 35 40 45 50 55 23 7 | 22 27 32 37 42 18 20 6 | 19 24 29 13 15 17 5 | 16 21 10 12 14 4 | 5 7 9 11 3 | 4 6 8 2 | 3 2 1 | 1 Y=0 | +----------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 A given N is at an X,Y position in the triangle according to where row number N of the Wythoff array "precurses" back to. Each Wythoff row is a Fibonacci recurrence. Starting from the pair of values in the first and second columns of row N it can be run in reverse by F[i-1] = F[i+i] - F[i] It can be shown that such a reverse always reaches a pair Y and X with YE=1 and 0E=XEY, hence making the triangular X,Y arrangement above. N=7 WythoffArray row 7 is 17,28,45,73,... go backwards from 17,28 by subtraction 11 = 28 - 17 6 = 17 - 11 5 = 11 - 6 1 = 6 - 5 4 = 5 - 1 stop on reaching 4,1 which is Y=4,X=1 with Y>=1 and 0<=X) after some initial iterations. The N value at X,Y is the row number of the Wythoff array which is reached. Rows are numbered starting from 1. For example, Y=4,X=1 sequence: 4, 1, 5, 6, 11, 17, 28, 45, ... row 7 of WythoffArray: 17, 28, 45, ... so N=7 at Y=4,X=1 =cut # =head2 Phi Slope Blocks # # The effect of each step backwards is to move to successive blocks of values # with slope golden ratio phi=(sqrt(5)+1)/2. # # Suppose no backwards steps were applied, so Y,X were the first two values of # Wythoff row N. In the example above that would be N=7 at Y=17,X=28. The # first two values of the Wythoff array are # # Y = W[0,r] = r-1 + floor(r*phi) # r = row numbered from 1 # X = W[1,r] = r-1 + 2*floor(r*phi) # # So this would put N values on a line of slope Y/X = 1/phi = 0.618. The # portion of that line which falls within 0E=XEY =pod =cut # (r-1 + floor(r*phi)) / (r-1 + 2*floor(r*phi)) # ~= (r-1+r*phi)/(r-1+2*r*phi) # = (r*(phi+1) - 1) / (r*(2phi+1) - 1) # -> r*(phi+1) / r*(2*phi+1) # = (phi+1) / (2*phi+1) # = 1/phi = 0.618 =pod =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::WythoffPreliminaryTriangle-Enew ()> Create and return a new path object. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A165360 X A165359 Y A166309 N by rows A173027 N on Y axis =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/PyramidSides.pm0000644000175000017500000002217413734026657020177 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::PyramidSides; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; *_sqrtint = \&Math::PlanePath::_sqrtint; # uncomment this to run the ### lines #use Smart::Comments; use constant class_y_negative => 0; use constant n_frac_discontinuity => 0.5; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad12; use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1() ]; sub x_negative_at_n { my ($self) = @_; return $self->n_start + 1; } use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant absdx_minimum => 1; use constant dsumxy_maximum => 2; # NE diagonal use constant ddiffxy_maximum => 2; # SE diagonal use constant dir_minimum_dxdy => (1,1); # North-East use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_left => 0; # only right or straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # starting each left side at 0.5 before # # d = [ 0, 1, 2, 3, 4 ] # n = [ 0-0.5, 1-0.5, 4-0.5, 9-0.5, 16-0.5 ] # N = (d^2 - 1/2) # = ($d**2 - 1/2) # d = 0 + sqrt(1 * $n + 1/2) # = sqrt(4*$n+2)/2 # sub n_to_xy { my ($self, $n) = @_; ### PyramidSides n_to_xy: $n # adjust to N=0 at origin X=0,Y=0 $n = $n - $self->{'n_start'}; my $d; { my $r = 4*$n + 2; if ($r < 0) { return; # N < -0.5 } $d = int( _sqrtint($r) / 2 ); } $n -= $d*($d+1); # to $n=0 on Y axis, so X=$n ### remainder: $n return ($n, - abs($n) + $d); } sub xy_to_n { my ($self, $x, $y) = @_; ### PyramidSides xy_to_n(): $x, $y $y = round_nearest ($y); if ($y < 0) { return undef; } $x = round_nearest ($x); my $d = abs($x) + $y; return $d*$d + $x+$d + $self->{'n_start'}; } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # swap to y1<=y2 if ($y2 < 0) { return (1, 0); # rect all negative, no N } if ($y1 < 0) { $y1 *= 0; } # "*=" to preserve bigint y1 my ($xlo, $xhi) = (abs($x1) < abs($x2) # lo,hi by absolute value ? ($x1, $x2) : ($x2, $x1)); if ($x2 == -$x1) { # when say x1=-5 x2=+5 then x=+5 is the bigger N $xhi = abs($xhi); } if (($x1 >= 0) ^ ($x2 >= 0)) { # if x1>=0 and x2<0 or other way around then x=0 is covered and is the # smallest N $xlo *= 0; # "*=" to preserve bigint } return ($self->xy_to_n ($xlo, $y1), $self->xy_to_n ($xhi, $y2)); } 1; __END__ =for stopwords pronic versa PlanePath Ryde Math-PlanePath ie Euler's OEIS =head1 NAME Math::PlanePath::PyramidSides -- points along the sides of pyramid =head1 SYNOPSIS use Math::PlanePath::PyramidSides; my $path = Math::PlanePath::PyramidSides->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path puts points in layers along the sides of a pyramid growing upwards. 21 4 20 13 22 3 19 12 7 14 23 2 18 11 6 3 8 15 24 1 17 10 5 2 1 4 9 16 25 <- Y=0 ------------------------------------ ^ ... -4 -3 -2 -1 X=0 1 2 3 4 ... XN=1,4,9,16,etc along the positive X axis is the perfect squares. N=2,6,12,20,etc in the X=-1 vertical is the Xpronic numbers k*(k+1) half way between those successive squares. The pattern is the same as the C path but turned and spread so the single quadrant in the C becomes a half-plane here. The pattern is similar to C (with its default step=2), just with the columns dropped down vertically to start at the X axis. Any pattern occurring within a column is unchanged, but what was a row becomes a diagonal and vice versa. =head2 Lucky Numbers of Euler An interesting sequence for this path is Euler's k^2+k+41. The low values are spread around a bit, but from N=1763 (k=41) they're the vertical at X=40. There's quite a few primes in this quadratic and when plotting primes that vertical stands out a little denser than its surrounds (at least for up to the first 2500 or so values). The line shows in other step==2 paths too, but not as clearly. In the C for instance the beginning is up at Y=40, and in the C path it's a diagonal. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same pyramid pattern. For example to start at 0, =cut # math-image --path=PyramidSides,n_start=0 --all --output=numbers --size=48x5 =pod n_start => 0 20 4 19 12 21 3 18 11 6 13 22 2 17 10 5 2 7 14 23 1 16 9 4 1 0 3 8 15 24 <- Y=0 -------------------------- -4 -3 -2 -1 X=0 1 2 3 4 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::PyramidSides-Enew ()> =item C<$path = Math::PlanePath::PyramidSides-Enew (n_start =E $n)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n < 0.5> the return is an empty list, it being considered there are no negative points in the pyramid. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer which has the effect of treating points in the pyramid as a squares of side 1, so the half-plane y>=-0.5 is entirely covered. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head1 FORMULAS =head2 Rectangle to N Range For C, in each column N increases so the biggest N is in the topmost row and and smallest N in the bottom row. In each row N increases along the sequence X=0,-1,1,-2,2,-3,3, etc. So the biggest N is at the X of biggest absolute value and preferring the positive X=k over the negative X=-k. The smallest N conversely is at the X of smallest absolute value. If the X range crosses 0, ie. C<$x1> and C<$x2> have different signs, then X=0 is the smallest. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back n_start=1 (the default) A049240 abs(dY), being 0=horizontal step at N=square A002522 N on X negative axis, x^2+1 A033951 N on X=Y diagonal, 4d^2+3d+1 A004201 N for which X>=0, ie. right hand half A020703 permutation N at -X,Y n_start=0 A196199 X coordinate, runs -n to +n A053615 abs(X), runs n to 0 to n A000196 abs(X)+abs(Y), being floor(sqrt(N)), k repeated 2k+1 times starting 0 =head1 SEE ALSO L, L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/CellularRule.pm0000644000175000017500000017404113734026674020175 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=CellularRule --all --scale=10 # # math-image --path=CellularRule --all --output=numbers --size=80x50 # Maybe: # @rules = Math::PlanePath::CellularRule->rule_equiv_list($rule) # list of equivalents # $bool = Math::PlanePath::CellularRule->rules_are_equiv($rule1,$rule2) # $rule = Math::PlanePath::CellularRule->rule_to_first($rule) # first equivalent # $bool = Math::PlanePath::CellularRule->rules_are_mirror($rule1,$rule2) # $rule = Math::PlanePath::CellularRule->rule_to_mirror($rule) # or undef if no mirror # $bool = Math::PlanePath::CellularRule->rule_is_symmetric($rule) package Math::PlanePath::CellularRule; use 5.004; use strict; use Carp 'croak'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::CellularRule54; *_rect_for_V = \&Math::PlanePath::CellularRule54::_rect_for_V; # uncomment this to run the ### lines # use Smart::Comments; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; use constant 1.02 _default_rule => 30; use constant parameter_info_array => [ { name => 'rule', display => 'Rule', type => 'integer', default => _default_rule(), minimum => 0, maximum => 255, width => 3, type_hint => 'cellular_rule', description => 'Rule number 0 to 255, encoding how triplets 111 through 000 turn into 0 or 1 in the next row.', }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; sub turn_any_straight { my ($self) = @_; return (($self->{'rule'} & 0x17) == 0 # single cell only || ($self->{'rule'} & 0x5F) == 0x0E # left line 2 || ($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? 0 # never straight : 1); } sub turn_any_left { my ($self) = @_; return (($self->{'rule'} & 0x17) == 0 # single cell only ? 0 : 1); } sub turn_any_right { my ($self) = @_; return (($self->{'rule'} & 0x17) == 0 # single cell only ? 0 : 1); } #------------------------------------------------------------------------------ # x,y range # rule=1 000->1 goes negative if 001->0 to keep left empty # so rule&3 == 1 # # any 001->1, rule&2 goes left initially sub x_negative { my ($self) = @_; return (($self->{'rule'} & 2) || ($self->{'rule'} & 3) == 1); } sub x_maximum { my ($self) = @_; return (($self->{'rule'} & 0x17) == 0 # single cell only || $self->{'rule'}==70 || $self->{'rule'}==198 || $self->{'rule'}==78 || $self->{'rule'}==110 || $self->{'rule'}==230 ? 0 : undef); } { my @x_negative_at_n = ( undef, 2, undef, 1, undef, 3, undef, 1, # rule=0 undef, 3, undef, 1, undef, 4, undef, 1, # rule=8 undef, 2, undef, 1, undef, 3, 1, 1, # rule=16 undef, 2, undef, 1, undef, 4, 1, 1, # rule=24 undef, 2, undef, 1, undef, 2, undef, 1, # rule=32 undef, 3, undef, 1, undef, 2, undef, 1, # rule=40 undef, 2, undef, 1, undef, 3, undef, 1, # rule=48 undef, undef, undef, 1, undef, 3, 1, 1, # rule=56 undef, 1, undef, 1, undef, 2, 1, 1, # rule=64 undef, 1, undef, 1, undef, 2, 1, 1, # rule=72 undef, 2, undef, 1, undef, 3, 1, 1, # rule=80 undef, 2, undef, 1, undef, 3, 1, 1, # rule=88 undef, 1, undef, undef, undef, 2, undef, 1, # rule=96 undef, 1, undef, 1, undef, 2, 1, 1, # rule=104 undef, 2, undef, 1, undef, 3, 1, 1, # rule=112 undef, 2, undef, 1, undef, 3, 1, 1, # rule=120 undef, 2, undef, 1, undef, 4, undef, 1, # rule=128 undef, 5, undef, 1, undef, 7, undef, 1, # rule=136 undef, 2, undef, 1, undef, 5, 1, undef, # rule=144 undef, 2, undef, 1, undef, 6, 1, undef, # rule=152 undef, 2, undef, 1, undef, 2, undef, 1, # rule=160 undef, 6, undef, 1, undef, 2, undef, 1, # rule=168 undef, 2, undef, undef, undef, 3, 1, undef, # rule=176 undef, 2, undef, 1, undef, 3, undef, undef, # rule=184 undef, 1, undef, 1, undef, 2, 1, 1, # rule=192 undef, 1, undef, 1, undef, 2, undef, 1, # rule=200 undef, 2, undef, 1, undef, 3, 1, undef, # rule=208 undef, 2, undef, 1, undef, 3, undef, undef, # rule=216 undef, 1, undef, 1, undef, 2, 1, 1, # rule=224 undef, 1, undef, 1, undef, 2, undef, 1, # rule=232 undef, 2, undef, 1, undef, 3, undef, undef, # rule=240 undef, 2, undef, 1, undef, 3, # rule=248 ); sub x_negative_at_n { my ($self) = @_; my $x_negative_at_n = $x_negative_at_n[$self->{'rule'}]; return (defined $x_negative_at_n ? $self->n_start + $x_negative_at_n : undef); } } sub y_maximum { my ($self) = @_; return (($self->{'rule'} & 0x17) == 0 # single cell only ? 0 : undef); } #------------------------------------------------------------------------------ # sumxy,diffxy range use constant sumxy_minimum => 0; # triangular X>=-Y so X+Y>=0 sub sumxy_maximum { my ($self) = @_; if (($self->{'rule'} & 0x5F) == 0x0E) { # left line 2 return 1; } return undef; } sub diffxy_minimum { my ($self) = @_; if (($self->{'rule'} & 0x5F) == 0x54) { # right line 2 return -1; } return undef; } use constant diffxy_maximum => 0; # triangular X<=Y so X-Y<=0 #------------------------------------------------------------------------------ # dx range sub dx_minimum { my ($self) = @_; return (($self->{'rule'} & 0x17) == 0 # single cell only || ($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? 0 : ($self->{'rule'} & 0x5F) == 0x0E # left line 2 ? -2 : undef); } { # Eg. rule=25 jumps +5 my @dx_maximum = ( undef, 4, undef, 3, undef, 2, undef, 1, undef, 2, undef, 2, undef, 2, 1, 2, undef, 3, undef, 2, undef, 1, undef, 1, undef, 5, undef, 2, 2, 2, undef, 1, undef, 4, undef, 3, undef, undef, undef, 2, undef, 3, undef, 2, undef, undef, 1, 2, undef, 3, undef, 2, undef, 2, undef, 1, undef, undef, undef, 2, undef, 4, 4, 1, undef, 2, undef, 5, undef, 2, 2, 2, undef, undef, undef, undef, undef, 2, 2, 2, undef, 1, undef, 2, 1, 1, undef, 1, undef, undef, undef, 4, 2, 2, 2, 1, undef, 3, undef, undef, undef, undef, undef, 4, undef, undef, undef, undef, undef, 4, undef, 4, undef, 1, undef, 2, 1, 1, 4, 1, undef, undef, undef, 2, undef, 4, undef, 1, undef, undef, undef, 5, undef, 2, undef, undef, undef, undef, undef, 3, undef, 2, 1, 3, undef, 5, undef, 4, undef, undef, undef, undef, undef, undef, undef, 3, 2, 2, 3, undef, undef, undef, undef, 3, undef, 2, undef, 2, undef, undef, undef, 3, undef, 1, 1, 2, undef, 3, undef, undef, undef, 2, 2, undef, undef, 2, undef, 2, 2, 1, undef, undef, undef, undef, undef, undef, undef, 2, 2, 2, undef, 4, undef, 2, undef, 2, undef, 2, undef, 3, undef, 3, 1, 3, 3, undef, undef, 2, undef, 2, undef, 2, undef, undef, undef, undef, undef, 2, undef, 1, 2, 1, undef, 2, undef, 2, undef, 1, undef, 1, undef, 3, undef, 2, 1, 2, undef, undef, undef, 2, undef, 2, undef, 1, ); sub dx_maximum { my ($self) = @_; return $dx_maximum[$self->{'rule'}]; } } #------------------------------------------------------------------------------ # dy range # 23, 31, 55, 63,87,95, 119, 127 # 0x17,0x1F,0x37,0x3F,..., 0x77,0x7F alts # is rule & 0x98 = 0x17 # Math::PlanePath::CellularRule::Line handles the dY=+1 always lines, # everything else has some row with 2 or more (except the single cell only # patterns). use constant dy_minimum => 0; sub dy_maximum { my ($self) = @_; # 0x1,0x9,0x return (($self->{'rule'} & 0x17) == 1 # single cell only || $self->{'rule'}==7 || $self->{'rule'}==21 # alternating rows || $self->{'rule'}==19 # alternating rows || ($self->{'rule'} & 0x97) == 0x17 ? 2 : 1); } #------------------------------------------------------------------------------ # absdx # left 2 cell line 14,46,142,174 # 111 -> any, doesn't occur # 110 -> 0 # 101 -> any, doesn't occur # 100 -> 0 # 011 -> 1 # 010 -> 1 # 001 -> 1 # 000 -> 0 # so (rule & 0x5F) == 0x0E # # left 1,2 cell line 6,38,134,166 # 111 -> any, doesn't occur # 110 -> 0 # 101 -> any, doesn't occur # 100 -> 0 # 011 -> 0 # 010 -> 1 # 001 -> 1 # 000 -> 0 # so (rule & 0x5F) == 0x06 # { my @absdx_minimum = ( undef, 0, undef, 1, undef, 0, undef, 1, undef, 0, undef, 1, undef, 0, 1, 1, undef, 1, undef, 1, undef, 0, 1, 1, undef, 1, undef, 0, 0, 0, 1, 1, undef, 0, undef, 1, undef, 0, undef, 1, undef, 0, undef, 1, undef, 0, 1, 1, undef, 1, undef, 1, undef, 0, undef, 1, undef, undef, undef, 1, undef, 0, 1, 1, undef, 0, undef, 0, undef, 0, 1, 0, undef, 1, undef, 0, undef, 0, 1, 0, undef, 0, undef, 1, 0, 0, 1, 1, undef, 1, undef, 1, 0, 0, 1, 1, undef, 1, undef, undef, undef, 0, undef, 0, undef, 1, undef, 0, undef, 0, 1, 0, undef, 0, undef, 1, 0, 0, 1, 1, undef, 1, undef, 1, 0, 0, 1, 1, undef, 0, undef, 1, undef, 0, undef, 1, undef, 0, undef, 1, undef, 0, 1, 1, undef, 1, undef, 1, undef, 0, 1, undef, undef, 1, undef, 1, 0, 0, 1, undef, undef, 0, undef, 1, undef, 0, undef, 1, undef, 0, undef, 1, undef, 0, 1, 1, undef, 1, undef, undef, undef, 0, 1, undef, undef, 1, undef, 1, 0, 0, undef, undef, undef, 1, undef, 1, undef, 0, 1, 1, undef, 1, undef, 1, undef, 0, undef, 1, undef, 1, undef, 1, 0, 0, 1, undef, undef, 1, undef, 1, undef, 0, undef, undef, undef, 1, undef, 1, undef, 0, 1, 1, undef, 1, undef, 1, undef, 0, undef, 1, undef, 1, undef, 1, 0, 0, undef, undef, undef, 1, undef, 1, undef, 0, ); sub absdx_minimum { my ($self) = @_; return $absdx_minimum[$self->{'rule'}]; } } #------------------------------------------------------------------------------ # dsumxy sub dsumxy_minimum { my ($self) = @_; return (($self->{'rule'} & 0x5F) == 0x54 # right line 2, const dSum=+1 ? 1 : ($self->{'rule'} & 0x5F) == 0x0E # left line 2 ? -1 : undef); } { my @dsumxy_maximum = ( undef, 4, undef, 3, undef, 2, undef, 1, undef, 3, undef, 3, undef, 2, 1, 3, undef, 3, undef, 2, undef, 1, undef, 1, undef, 5, undef, 2, 2, 2, undef, 1, undef, 4, undef, 3, undef, undef, undef, 2, undef, 4, undef, 3, undef, undef, 1, 3, undef, 3, undef, 2, undef, 2, undef, 1, undef, undef, undef, 2, undef, 4, 4, 1, undef, 3, undef, 5, undef, 2, 2, 2, undef, undef, undef, undef, undef, 2, 2, 2, undef, 2, undef, 2, 1, 1, undef, 1, undef, undef, undef, 4, 2, 2, 2, 1, undef, 3, undef, undef, undef, undef, undef, 4, undef, undef, undef, undef, undef, 4, undef, 4, undef, 2, undef, 2, 1, 1, 4, 1, undef, undef, undef, 2, undef, 4, undef, 1, undef, undef, undef, 5, undef, 2, undef, undef, undef, undef, undef, 3, undef, 2, 1, 3, undef, 5, undef, 4, undef, undef, undef, undef, undef, undef, undef, 3, 2, 2, 3, undef, undef, undef, undef, 3, undef, 2, undef, 2, undef, undef, undef, 3, undef, 1, 1, 2, undef, 3, undef, undef, undef, 2, 2, undef, undef, 2, undef, 2, 2, 1, undef, undef, undef, undef, undef, undef, undef, 2, 2, 2, undef, 4, undef, 2, undef, 2, undef, 2, undef, 3, undef, 3, 1, 3, 3, undef, undef, 2, undef, 2, undef, 2, undef, undef, undef, undef, undef, 2, undef, 1, 2, 1, undef, 2, undef, 2, undef, 1, undef, 1, undef, 3, undef, 2, 1, 2, undef, undef, undef, 2, undef, 2, undef, 1, ); sub dsumxy_maximum { my ($self) = @_; return $dsumxy_maximum[$self->{'rule'}]; } } # sub dsumxy_maximum { # my ($self) = @_; # return (($self->{'rule'} & 0x5F) == 0x54 # right line 2 # ? 1 # is constant dSum=+1 # : ($self->{'rule'} & 0x5F) == 0x0E # left line 2 # ? 1 # : $self->{'rule'}==3 || $self->{'rule'}==35 ? 3 # : $self->{'rule'} == 5 ? 2 # : $self->{'rule'} == 7 ? 1 # : $self->{'rule'} == 9 ? 3 # : $self->{'rule'}==11 || $self->{'rule'}==43 ? 3 # : $self->{'rule'} == 13 ? 2 # : $self->{'rule'} == 15 ? 3 # : $self->{'rule'}==17 || $self->{'rule'}==49 ? 3 # : $self->{'rule'}==19 ? 2 # : $self->{'rule'}==21 ? 1 # : ($self->{'rule'} & 0x97) == 0x17 # 0x17,...,0x7F # ? 1 # : $self->{'rule'}==27 ? 2 # : $self->{'rule'}==28 || $self->{'rule'}==156 ? 2 # : $self->{'rule'}==29 ? 2 # : $self->{'rule'}==31 ? 1 # : $self->{'rule'}==39 ? 2 # : $self->{'rule'}==47 ? 3 # : $self->{'rule'}==51 ? 2 # : $self->{'rule'}==53 ? 2 # : $self->{'rule'}==59 ? 2 # : $self->{'rule'}==65 ? 3 # : $self->{'rule'}==69 ? 2 # : $self->{'rule'}==70 || $self->{'rule'}==198 ? 2 # : $self->{'rule'}==71 ? 2 # : $self->{'rule'}==77 ? 2 # : $self->{'rule'}==78 ? 2 # : $self->{'rule'}==79 ? 2 # : $self->{'rule'}==81 || $self->{'rule'}==113 ? 2 # : undef); # } #------------------------------------------------------------------------------ # ddiffxy range sub ddiffxy_minimum { my ($self) = @_; return (($self->{'rule'} & 0x5F) == 0x54 # right line 2, dDiffXY=-1 or +1 ? -1 : ($self->{'rule'} & 0x5F) == 0x0E # left line 2, dDiffXY=-3 or +1 ? -3 : undef); } { my @ddiffxy_maximum = ( undef, 4, undef, 3, undef, 2, undef, 1, undef, 2, undef, 1, undef, 2, 1, 1, undef, 3, undef, 2, undef, 1, undef, 1, undef, 5, undef, 2, 2, 2, undef, 1, undef, 4, undef, 3, undef, undef, undef, 2, undef, 3, undef, 1, undef, undef, 1, 1, undef, 3, undef, 2, undef, 2, undef, 1, undef, undef, undef, 2, undef, 4, 4, 1, undef, 2, undef, 5, undef, 2, 2, 2, undef, undef, undef, undef, undef, 2, 2, 2, undef, 1, undef, 2, 1, 1, undef, 1, undef, undef, undef, 4, 2, 2, 2, 1, undef, 3, undef, undef, undef, undef, undef, 4, undef, undef, undef, undef, undef, 4, undef, 4, undef, 1, undef, 2, 1, 1, 4, 1, undef, undef, undef, 2, undef, 4, undef, 1, undef, undef, undef, 5, undef, 2, undef, undef, undef, undef, undef, 3, undef, 2, 1, 3, undef, 5, undef, 4, undef, undef, undef, undef, undef, undef, undef, 3, 2, 2, 3, undef, undef, undef, undef, 3, undef, 2, undef, 2, undef, undef, undef, 3, undef, 1, 1, 2, undef, 3, undef, undef, undef, 2, 2, undef, undef, 2, undef, 2, 2, 1, undef, undef, undef, undef, undef, undef, undef, 2, 2, 2, undef, 4, undef, 2, undef, 2, undef, 2, undef, 3, undef, 3, 1, 3, 3, undef, undef, 2, undef, 2, undef, 2, undef, undef, undef, undef, undef, 2, undef, 1, 2, 1, undef, 2, undef, 2, undef, 1, undef, 1, undef, 3, undef, 2, 1, 2, undef, undef, undef, 2, undef, 2, undef, 1, ); sub ddiffxy_maximum { my ($self) = @_; return $ddiffxy_maximum[$self->{'rule'}]; } } # sub ddiffxy_maximum { # my ($self) = @_; # return (($self->{'rule'} & 0x5F) == 0x0E # left line 2 # ? 1 # : $self->{'rule'}==3 || $self->{'rule'}==35 ? 3 # : $self->{'rule'} == 5 ? 2 # : $self->{'rule'} == 7 ? 1 # : $self->{'rule'} == 9 ? 2 # : $self->{'rule'}==11 || $self->{'rule'}==43 ? 1 # : $self->{'rule'} == 13 ? 2 # : $self->{'rule'} == 15 ? 1 # : $self->{'rule'}==17 || $self->{'rule'}==49 ? 3 # : $self->{'rule'}==19 ? 2 # : $self->{'rule'}==21 ? 1 # : ($self->{'rule'} & 0x97) == 0x17 # 0x17=23,...,0x7F # ? 1 # : $self->{'rule'}==27 ? 2 # : $self->{'rule'}==28 || $self->{'rule'}==156 ? 2 # : $self->{'rule'}==29 ? 2 # : $self->{'rule'}==31 ? 1 # : $self->{'rule'}==39 ? 2 # : $self->{'rule'}==41 ? 3 # : $self->{'rule'}==47 ? 1 # : $self->{'rule'}==51 ? 2 # : $self->{'rule'}==53 ? 2 # : $self->{'rule'}==55 ? 1 # : $self->{'rule'}==59 ? 2 # : $self->{'rule'}==65 ? 2 # : $self->{'rule'}==69 ? 2 # : $self->{'rule'}==70 || $self->{'rule'}==198 ? 2 # : $self->{'rule'}==71 ? 2 # : $self->{'rule'}==77 ? 2 # : $self->{'rule'}==78 ? 2 # : $self->{'rule'}==79 ? 2 # : $self->{'rule'}==81 || $self->{'rule'}==113 ? 1 # : undef); # } #------------------------------------------------------------------------------ # dir range sub dir_maximum_dxdy { my ($self) = @_; return (($self->{'rule'} & 0x5F) == 0x54 # right line 2 ? (0,1) # north : ($self->{'rule'} & 0x5F) == 0x0E # left line 2 ? (-2,1) : (-1,0)); # supremum, west and 1 up } #------------------------------------------------------------------------------ # cf 60 is right half Sierpinski # 129 is inverse Sierpinski, except for initial N=1 cell # 119 etc alternate rows PyramidRows step=4 with 2*Y # 50 PyramidRows with 2*N # my @rule_to_class; { my $store = sub { my ($rule, $aref) = @_; if ($rule_to_class[$rule] && $rule_to_class[$rule] != $aref) { die "Oops, already have rule_to_class[] $rule"; } $rule_to_class[$rule] = $aref; }; $store->(54, [ 'Math::PlanePath::CellularRule54' ]); $store->(57, [ 'Math::PlanePath::CellularRule57' ]); $store->(99, [ 'Math::PlanePath::CellularRule57', mirror => 1 ]); $store->(190, [ 'Math::PlanePath::CellularRule190' ]); $store->(246, [ 'Math::PlanePath::CellularRule190', mirror => 1 ]); { # ************* whole solid # *********** # ********* # ******* # ***** # *** # * # 0xDE and 0xFE = 222, 254 # 111 -> 1 solid # 110 -> 1 right side # 101 any, doesn't occur # 100 -> 1 initial # 011 -> 1 left side # 010 -> 1 initial # 001 -> 1 initial # 000 -> 0 sides blank # # -*************- whole solid with full sides # --***********-- # ---*********--- # ----*******---- # -----*****----- # ------***------ # * # and with sides # 111 -> 1 solid middle # 110 any, doesn't occur # 101 any, doesn't occur # 100 -> 1 initial # 011 any, doesn't occur # 010 -> 1 initial # 001 -> 1 initial # 000 -> 1 sides blank my $solid = [ 'Math::PlanePath::PyramidRows', step => 2 ]; $store->(222, $solid); $store->(254, $solid); foreach my $i (0 .. 255) { $store->(($i&0x68)|0x97, $solid); } } { # ******* right half solid # ****** # ***** # **** # *** # ** # * # 111 -> 1 solid # 110 -> 1 to right # 101 any, doesn't occur # 100 -> 1 initial # 011 -> 1 vertical # 010 -> 1 initial # 001 -> 0 not to left # 000 -> 0 my $solid_half = [ 'Math::PlanePath::PyramidRows', step => 1 ]; $store->(220, $solid_half); $store->(252, $solid_half); } { # * * * * * * * * # * * * * # * * * * # * * # * * * * # * * # * * # * # 18,26,82,90,146,154,210,218 # 111 any, doesn't occur # 110 any, doesn't occur # 101 -> 0 # 100 -> 1 initial # 011 any, doesn't occur # 010 -> 0 initial # 001 -> 1 initial # 000 -> 0 for outsides # my $sierpinski_triangle = [ 'Math::PlanePath::SierpinskiTriangle', n_start => 1 ]; foreach my $i (0 .. 255) { $store->(($i&0xC8)|0x12, $sierpinski_triangle); } } $store->(60, [ 'Math::PlanePath::SierpinskiTriangle', n_start => 1, align => "right" ]); $store->(102, [ 'Math::PlanePath::SierpinskiTriangle', n_start => 1, align => "left" ]); { # left negative line, rule=2,10,... # 111 any, doesn't occur # 110 any, doesn't occur # 101 any, doesn't occur # 100 -> 0 initial # 011 any, doesn't occur # 010 -> 0 initial # 001 -> 1 initial towards left # 000 -> 0 for outsides # my $left_line = [ 'Math::PlanePath::CellularRule::Line', align => 'left' ]; foreach my $i (0 .. 255) { $store->(($i&0xE8)|0x02, $left_line); } } { # right positive line, rule=16,... # 111 any, doesn't occur # 110 any, doesn't occur # 101 any, doesn't occur # 100 -> 1 initial # 011 any, doesn't occur # 010 -> 0 initial # 001 -> 0 initial towards left # 000 -> 0 for outsides # my $right_line = [ 'Math::PlanePath::CellularRule::Line', align => 'right' ]; foreach my $i (0 .. 255) { $store->(($i&0xE8)|0x10, $right_line); } } { # central vertical line 4,... # 111 any # 110 any # 101 any # 100 -> 0 # 011 any # 010 -> 1 initial cell # 001 -> 0 # 000 -> 0 my $centre_line = [ 'Math::PlanePath::CellularRule::Line', align => 'centre' ]; foreach my $i (0 .. 255) { $store->(($i&0xE8)|0x04, $centre_line); } } { # 1,2 alternating line left rule=6,38,134,166 # 111 any, doesn't occur # 110 -> 0 # 101 any, doesn't occur # 100 -> 0 initial # 011 -> 0 # 010 -> 1 initial # 001 -> 1 angle towards left # 000 -> 0 for outsides # my $left_onetwo = [ 'Math::PlanePath::CellularRule::OneTwo', align => 'left' ]; foreach my $i (0 .. 255) { $store->(($i&0xA0)|0x06, $left_onetwo); } } { # 1,2 alternating line right rule=20,52,148,180 = 0x14,34,94,B4 # 111 any, doesn't occur # 110 -> 0 # 101 any, doesn't occur # 100 -> 1 angle towards right # 011 -> 0 # 010 -> 1 vertical # 001 -> 0 not to left # 000 -> 0 for outsides # so (rule & 0x5F) == 0x14 # my $right_onetwo = [ 'Math::PlanePath::CellularRule::OneTwo', align => 'right' ]; foreach my $i (0 .. 255) { $store->(($i&0xA0)|0x14, $right_onetwo); } } { # left line 2 rule=14,46,142,174 # 111 any, doesn't occur # 110 -> 0 # 101 any, doesn't occur # 100 -> 0 initial # 011 -> 1 # 010 -> 1 initial # 001 -> 1 angle towards left # 000 -> 0 for outsides # my $left_onetwo = [ 'Math::PlanePath::CellularRule::Two', align => 'left' ]; foreach my $i (0 .. 255) { $store->(($i&0xA0)|0x0E, $left_onetwo); } } { # right line 2 rule=84,116,212,244 # 111 any, doesn't occur # 110 -> 1 # 101 any, doesn't occur # 100 -> 1 right, including initial # 011 -> 0 # 010 -> 1 initial vertical # 001 -> 0 not to left # 000 -> 0 for outsides # so (rule & 0x5F) == 0x54 # my $right_onetwo = [ 'Math::PlanePath::CellularRule::Two', align => 'right' ]; foreach my $i (0 .. 255) { $store->(($i&0xA0)|0x54, $right_onetwo); } } { # solid every second cell, 50,58,114,122,178,186,242,250, 179 # http://mathworld.wolfram.com/Rule250.html # 111 any, doesn't occur # 110 any, doesn't occur # 101 -> 1 middle # 100 -> 1 initial # 011 any, doesn't occur # 010 -> 0 initial # 001 -> 1 initial # 000 -> 0 outsides # my $odd_solid = [ 'Math::PlanePath::CellularRule::OddSolid' ]; foreach my $i (0 .. 255) { $store->(($i&0xC8)|0x32, $odd_solid); } $store->(179, $odd_solid); } { # ******* left half solid 206,238 = 0xCE,0xEE # ****** # ***** # **** # *** # ** # * # 111 -> 1 middle # 110 -> 1 vertical # 101 any, doesn't occur # 100 -> 0 initial # 011 -> 1 left # 010 -> 1 initial # 001 -> 1 initial # 000 -> 0 outsides my $left_solid = [ 'Math::PlanePath::PyramidRows', step => 1, align => 'left' ]; foreach my $i (0 .. 255) { $store->(($i&0x20)|0xCE, $left_solid); } } } ### rule_to_class count: do { my @k = grep {defined} @rule_to_class; scalar(@k) } ### rule_to_class: [ map {defined($_) && join(',',@$_)} @rule_to_class ] # ### zap %rule_to_class for testing ... # %rule_to_class = (); sub new { ### CellularRule new() ... my $self = shift->SUPER::new(@_); my $rule = $self->{'rule'}; if (! defined $rule) { $rule = $self->{'rule'} = _default_rule(); } ### $rule my $n_start = $self->{'n_start'}; if (! defined $n_start) { $n_start = $self->{'n_start'} = $self->default_n_start; } unless ($self->{'use_bitwise'}) { # secret undocumented option if (my $aref = $rule_to_class[$rule]) { my ($class, @args) = @$aref; ### $class ### @args $class->can('new') or eval "require $class; 1" or die; return $class->new (rule => $rule, n_start => $n_start, @args); } } $self->{'rows'} = [ "\001" ]; $self->{'row_end_n'} = [ $n_start ]; $self->{'left'} = 0; $self->{'right'} = 0; $self->{'rule_table'} = [ map { ($rule >> $_) & 1 } 0 .. 7 ]; ### $self return $self; } # # Y=2 L 0 1 2 3 4 R right=2*Y+2 # Y=1 L 0 1 2 R # Y=0 L 0 R sub _extend { my ($self) = @_; ### _extend() my $rule_table = $self->{'rule_table'}; my $rows = $self->{'rows'}; my $row = $rows->[-1]; my $newrow = ''; my $rownum = $#$rows; my $count = 0; my $bits = $self->{'left'} * 7; $self->{'left'} = $rule_table->[$bits]; ### $row ### $rownum foreach my $i (0 .. 2*$rownum) { $bits = (($bits<<1) + vec($row,$i,1)) & 7; ### $i ### $bits ### new: $rule_table->[$bits] $count += (vec($newrow,$i,1) = $rule_table->[$bits]); } my $rbit = $self->{'right'}; $self->{'right'} = $rule_table->[7*$rbit]; ### $rbit ### new right: $self->{'right'} # right, second last $bits = (($bits<<1) + $rbit) & 7; $count += (vec($newrow,2*$rownum+1,1) = $rule_table->[$bits]); ### $bits ### new second last: $rule_table->[$bits] # right end $bits = (($bits<<1) + $rbit) & 7; $count += (vec($newrow,2*$rownum+2,1) = $rule_table->[$bits]); ### $bits ### new right end: $rule_table->[$bits] ### $count ### $newrow push @$rows, $newrow; my $row_end_n = $self->{'row_end_n'}; push @$row_end_n, $row_end_n->[-1] + $count; } sub n_to_xy { my ($self, $n) = @_; ### CellularRule n_to_xy(): $n my $int = int($n); $n -= $int; # now fraction part if (2*$n >= 1) { $n -= 1; $int += 1; } # -0.5 <= $n < 0.5 fractional part ### assert: 2*$n >= -1 || $n+1==$n || $n!=$n ### assert: 2*$n < 1 || $n+1==$n || $n!=$n if ($int < $self->{'n_start'}) { return; } if (is_infinite($int)) { return ($int,$int); } my $row_end_n = $self->{'row_end_n'}; my $y = 0; for (;;) { if (scalar(@$row_end_n) >= 3 && $row_end_n->[-1] == $row_end_n->[-2] && $row_end_n->[-2] == $row_end_n->[-3]) { ### no more cells in three rows means rest is blank ... return; } if ($y > $#$row_end_n) { _extend($self); } if ($int <= $row_end_n->[$y]) { last; } $y++; } ### $y ### row_end_n: $row_end_n->[$y] ### remainder: $int - $row_end_n->[$y] $int -= $row_end_n->[$y]; my $row = $self->{'rows'}->[$y]; my $x = 2*$y+1; # for first vec 2*Y ### $row for ($x = 2*$y+1; $x >= 0; $x--) { if (vec($row,$x,1)) { ### step bit: "x=$x" if (++$int > 0) { last; } } } ### result: ($n + $x - $y).",$y" return ($n + $x - $y, $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### CellularRule xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } if ($y < 0 || ! ($x <= $y && ($x+=$y) >= 0)) { return undef; } my $row_end_n = $self->{'row_end_n'}; while ($y > $#$row_end_n) { if (scalar(@$row_end_n) >= 3 && $row_end_n->[-1] == $row_end_n->[-2] && $row_end_n->[-2] == $row_end_n->[-3]) { ### no more cells in three rows means rest is blank ... return undef; } _extend($self); } my $row = $self->{'rows'}->[$y]; if (! vec($row,$x,1)) { return undef; } my $n = $row_end_n->[$y]; foreach my $i ($x+1 .. 2*$y) { $n -= vec($row,$i,1); } return $n; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### CellularRule rect_to_n_range(): "$x1,$y1 $x2,$y2" ($x1,$y1, $x2,$y2) = _rect_for_V ($x1,$y1, $x2,$y2) or return (1,0); # rect outside pyramid if (is_infinite($y1)) { return ($self->{'n_start'}, $y1); } # for nan if (is_infinite($y2)) { return ($self->{'n_start'}, $y2); } # for nan or inf my $row_end_n = $self->{'row_end_n'}; while ($#$row_end_n < $y2) { if (scalar(@$row_end_n) >= 3 && $row_end_n->[-1] == $row_end_n->[-2] && $row_end_n->[-2] == $row_end_n->[-3]) { ### rect_to_n_range() no more cells in three rows means rest is blank ... last; } _extend($self); } $y1 -= 1; # to be 1 past end of prev row if ($y1 > $#$row_end_n) { $y1 = $#$row_end_n; } if ($y2 > $#$row_end_n) { $y2 = $#$row_end_n; } ### y range: "$y1 to $y2" return ($y1 < 0 ? $self->{'n_start'} : $row_end_n->[$y1] + 1, $row_end_n->[$y2]); } #------------------------------------------------------------------------------ # 000,001,010,100 = 0,1,2,4 used always # if 000=1 then 111 used sub _UNDOCUMENTED__rule_is_finite { my ($class, $rule) = @_; # zeros 1,0,0 -> bit4 # total 16 finites # 0,1,0 -> bit2 # 0,0,1 -> bit1 # 0,0,0 -> bit0 return ($rule & ((1<<4)|(1<<2)|(1<<1)|(1<<0))) == 0; } sub _any_101 { my ($rule) = @_; # or 0,0,0 -> bit0 1 & 111 == 011 # 0,1,0 -> bit2 0 # 0,0,1 -> bit1 1 # or 0,0,0 -> bit0 1 & 111 == 011 # 0,1,0 -> bit2 0 # 1,0,0 -> bit4 1 return ($rule & 1) || ($rule & 0x16) == 0x16; } sub _any_110 { my ($rule) = @_; } sub _any_011 { my ($rule) = @_; } sub _any_111 { my ($rule) = @_; return ($rule & 1) || ($rule & 0x16) == 0x16; } # $bool = Math::PlanePath::CellularRule->_NOTWORKING__rules_are_equiv($rule) sub _NOTWORKING__rules_are_equiv { my ($class, $a,$b) = @_; my $a_low = $a & 0x17; # same 1,0,0 -> bit4 # 00010111 = 0x17 # 0,1,0 -> bit2 # 0,0,1 -> bit1 # 0,0,0 -> bit0 return 0 unless $a_low == ($b & 0x17); # if 1,0,0 -> bit4 1 # & 00010111 = 10010 # 0,1,0 -> bit2 0 # 0,0,1 -> bit1 1 # 0,0,0 -> bit0 any # or 1,0,0 -> bit4 0 # & 00010111 = 00101 # 0,1,0 -> bit2 1 # 0,0,1 -> bit1 any # 0,0,0 -> bit0 1 # or 1,0,0 -> bit4 any # & 00010111 = 00101 # 0,1,0 -> bit2 1 # 0,0,1 -> bit1 0 # 0,0,0 -> bit0 1 # then # same 1,0,1 -> bit5 # 01001000 = 0x48 if ($a_low == 0x12 || $a_low == 5) { return 0 unless ($a & (1<<5)) == ($b & (1<<5)); } return 1; } # $bool = Math::PlanePath::CellularRule->rule_is_symmetric($rule) sub _NOTWORKING__rule_is_symmetric { my ($class, $rule) = @_; return ($class->_UNDOCUMENTED__rule_is_finite($rule) # single cell || # same 1,1,0 -> bit6 # if it is ever reached # 0,1,1 -> bit3 (($rule & (1<<6)) >> 3) == ($rule & (1<<3)) && # same 1,0,0 -> bit4 # if it is ever reached # 0,0,1 -> bit1 (($rule & (1<<4)) >> 3) == ($rule & (1<<1))); } # =item C<$mirror_rule = Math::PlanePath::CellularRule-Erule_to_mirror ($rule)> # # Return a rule number which is a horizontal mirror image of C<$rule>. This # is a swap of bits 3E-E6 and 1E-E4. # # If the pattern is already symmetric then the returned C<$mirror_rule> will # generate the same pattern, though its value might be different. This # occurs if some bits in the rule value never occur and so don't affect the # result. # sub _UNDOCUMENTED__rule_to_mirror { my ($class, $rule) = @_; # 7,6,5,4,3,2,1,0 # 1 0 1 0 0 1 0 1 = 0xA5 return (($rule & 0xA5) # swap 1,1,0 -> bit6 # 0,1,1 -> bit3 | (($rule & (1<<6)) >> 3) | (($rule & (1<<3)) << 3) # swap 1,0,0 -> bit4 # 0,0,1 -> bit1 | (($rule & (1<<4)) >> 3) | (($rule & (1<<1)) << 3) ); } #------------------------------------------------------------------------------ { package Math::PlanePath::CellularRule::Line; use strict; use Carp 'croak'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use constant parameter_info_array => [ { name => 'align', display => 'Align', type => 'enum', default => 'left', choices => ['left','centre','right'], }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; sub x_negative { my ($self) = @_; return ($self->{'align'} eq 'left'); } sub x_maximum { my ($self) = @_; return ($self->{'align'} eq 'right' ? undef : 0); } sub x_negative_at_n { my ($self) = @_; return ($self->{'align'} eq 'left' ? $self->n_start + 1 : undef); } use constant sumxy_minimum => 0; # triangular X>=-Y so X+Y>=0 sub sumxy_maximum { my ($self) = @_; return ($self->{'align'} eq 'left' ? 0 # left X=-Y so X+Y=0 always : undef); } sub diffxy_minimum { my ($self) = @_; return ($self->{'align'} eq 'right' ? 0 # right X=Y so X-Y=0 always : undef); } use constant diffxy_maximum => 0; # triangular X<=Y so X-Y<=0 # always dX=sign,dY=+1 so dSumXY = sign+1 sub dsumxy_minimum { my ($self) = @_; return $self->{'sign'}+1; } *dsumxy_maximum = \&dsumxy_minimum; # always dX=sign,dY=+1 so dDiffXY = sign-1 sub ddiffxy_minimum { my ($self) = @_; return $self->{'sign'}-1; } *ddiffxy_maximum = \&ddiffxy_minimum; sub absdx_minimum { my ($self) = @_; return ($self->{'align'} eq 'centre' ? 0 : 1); } use constant absdy_minimum => 1; # dY=1 always sub dir_minimum_dxdy { my ($self) = @_; return ($self->dx_minimum, 1); } *dir_maximum_dxdy = \&dir_minimum_dxdy; # same direction always sub dx_minimum { my ($self) = @_; return $self->{'sign'}; } *dx_maximum = \&dx_minimum; # same step always use constant dy_minimum => 1; use constant dy_maximum => 1; sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; return ($self->{'sign'}, 1); } *_UNDOCUMENTED__dxdy_list_at_n = __PACKAGE__->can('n_start'); # straight ahead only use constant turn_any_left => 0; use constant turn_any_right => 0; #----------------------------------------------------------- my %align_to_sign = (left => -1, centre => 0, right => 1); sub new { my $self = shift->SUPER::new (@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } $self->{'align'} ||= 'left'; $self->{'sign'} = $align_to_sign{$self->{'align'}}; if (! defined $self->{'sign'}) { croak "Unrecognised align parameter: ",$self->{'align'}; } return $self; } sub n_to_xy { my ($self, $n) = @_; ### CellularRule-Line n_to_xy(): $n $n = $n - $self->{'n_start'}; # to N=0 basis my $int = int($n); $n -= $int; # now fraction part if (2*$n >= 1) { $n -= 1; $int += 1; } # -0.5 <= $n < 0.5 fractional part ### assert: 2*$n >= -1 ### assert: 2*$n < 1 ### $int if ($int < 0) { return; } if (is_infinite($int)) { return ($int,$int); } return ($n + $int*$self->{'sign'}, $int); } sub n_to_radius { my ($self, $n) = @_; $n = $n - $self->{'n_start'}; # to N=0 start if ($n < 0) { return undef; } if ($self->{'align'} ne 'centre') { $n *= sqrt(2 + $n*0); # inherit bigfloat etc from $n } return $n; } sub n_to_rsquared { my ($self, $n) = @_; $n = $n - $self->{'n_start'}; # to N=0 start if ($n < 0) { return undef; } $n *= $n; # squared if ($self->{'align'} ne 'centre') { $n *= 2; } return $n; } sub xy_to_n { my ($self, $x, $y) = @_; ### CellularRule-Line xy_to_n(): "$x,$y" $x = round_nearest ($x); $y = round_nearest ($y); if (is_infinite($x)) { return $x; } if ($y >= 0 && $x == $y*$self->{'sign'}) { return $y + $self->{'n_start'}; } else { return undef; } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # swap to y1<=y2 if ($y2 < 0) { return (1, 0); } if ($y1 < 0) { $y1 *= 0; } return ($y1 + $self->{'n_start'}, $y2 + $self->{'n_start'}); } } #------------------------------------------------------------------------------ { package Math::PlanePath::CellularRule::OddSolid; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::PyramidRows; use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; sub x_negative_at_n { my ($self) = @_; return $self->n_start + 1; } use constant sumxy_minimum => 0; # triangular X>=-Y so X+Y>=0 use constant diffxy_maximum => 0; # triangular X<=Y so X-Y<=0 use constant dx_maximum => 2; use constant dy_minimum => 0; use constant dy_maximum => 1; use constant absdx_minimum => 1; use constant dsumxy_maximum => 2; # straight E dX=+2 use constant ddiffxy_maximum => 2; # straight E dX=+2 use constant dir_maximum_dxdy => (-1,0); # West, supremum sub new { my $self = shift->SUPER::new (@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } # delegate to sub-object $self->{'pyramid'} = Math::PlanePath::PyramidRows->new (n_start => $self->{'n_start'}, step => 1); return $self; } sub n_to_xy { my ($self, $n) = @_; ### CellularRule-OddSolid n_to_xy(): $n my ($x,$y) = $self->{'pyramid'}->n_to_xy($n) or return; ### pyramid: "$x, $y" return ($x+round_nearest($x) - $y, $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### CellularRule-OddSolid xy_to_n(): "$x,$y" $x = round_nearest ($x); $y = round_nearest ($y); if (($x+$y)%2) { return undef; } return $self->{'pyramid'}->xy_to_n(($x+$y)/2, $y); } # (y2+1)*(y2+2)/2 - 1 # = (y2^2 + 3*y2 + 2 - 2)/2 # = y2*(y2+3)/2 # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### OddSolid rect_to_n_range() ... $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # swap to y1<=y2 if ($y1 < 0) { $y1 *= 0; } return ($y1*($y1+1)/2 + $self->{'n_start'}, # start of row, triangular+1 $y2*($y2+3)/2 + $self->{'n_start'}); # end of row, prev triangular } } #------------------------------------------------------------------------------ { package Math::PlanePath::CellularRule::OneTwo; use strict; use Carp 'croak'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # rule=6,38,134,166 sign=-1 # ** # * # ** # * # # rule=20,52,148,180 sign=1 # ** # * # ** # * # use constant parameter_info_array => [ { name => 'align', display => 'Align', type => 'enum', default => 'left', choices => ['left','right'], }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; sub x_negative { my ($self) = @_; return ($self->{'align'} eq 'left'); } sub x_negative_at_n { my ($self) = @_; return ($self->{'align'} eq 'left' ? $self->n_start + 1 : undef); } sub x_maximum { my ($self) = @_; return ($self->{'align'} eq 'left' ? 0 : undef); } use constant sumxy_minimum => 0; # triangular X>=-Y so X+Y>=0 { my %sumxy_maximum = (left => 1); sub sumxy_maximum { my ($self) = @_; return $sumxy_maximum{$self->{'align'}}; } } { my %diffxy_minimum = (right => -1); sub diffxy_minimum { my ($self) = @_; return $diffxy_minimum{$self->{'align'}}; } } use constant diffxy_maximum => 0; # triangular X<=Y so X-Y<=0 { my %dx_minimum = (left => -2, right => 0); sub dx_minimum { my ($self) = @_; return $dx_minimum{$self->{'align'}}; } } use constant dx_maximum => 1; use constant dy_minimum => 0; use constant dy_maximum => 1; { my %_UNDOCUMENTED__dxdy_list = (left => [ 1,0, # E -1,1, # NW -2,1 ], # WNW right => [ 1,0, # E 1,1, # NE 0,1 ]); # N sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; return @{$_UNDOCUMENTED__dxdy_list{$self->{'align'}}}; } } { my %_UNDOCUMENTED__dxdy_list_at_n = (left => 2, right => 2); sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + $_UNDOCUMENTED__dxdy_list_at_n{$self->{'align'}}; } } { my %absdx_minimum = (left => 1, # -2 or +1, so minimum abs is 1 right => 0); # 0 or +1, so minimum abs is 0 sub absdx_minimum { my ($self) = @_; return $absdx_minimum{$self->{'align'}}; } } sub dsumxy_minimum { my ($self) = @_; return $self->{'sign'}; # ? -1 # left, ENE # : 1); # right, N, going as a stairstep so always increase } sub dsumxy_maximum { my ($self) = @_; return ($self->{'sign'} < 0 ? 1 # left, East : 2); # right, NE diagonal } sub ddiffxy_minimum { my ($self) = @_; return ($self->{'sign'} < 0 ? -3 # left, ENE : -1); # right, N, going as a stairstep so always increase } sub ddiffxy_maximum { my ($self) = @_; return ($self->{'sign'} < 0 ? 1 # left, East : 1); # right, NE diagonal } sub dir_maximum_dxdy { my ($self) = @_; return ($self->{'align'} eq 'left' ? (-2,1) : (0,1)); # North } use constant turn_any_straight => 0; # never straight #--------------------------------------------- my %align_to_sign = (left => -1, right => 1); sub new { my $self = shift->SUPER::new (@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } $self->{'align'} ||= 'left'; $self->{'sign'} = $align_to_sign{$self->{'align'}} || croak "Unrecognised align parameter: ",$self->{'align'}; return $self; } sub n_to_xy { my ($self, $n) = @_; ### CellularRule-OneTwo n_to_xy(): $n $n = $n - $self->{'n_start'} + 1; # to N=1 basis, and warn if $n undef my $int = int($n); $n -= $int; # $n now fraction part if (2*$n >= 1) { $n -= 1; } else { $int -= 1; # to N=0 basis } # -0.5 <= $n < 0.5 fractional part ### $int if ($int < 0) { return; } if (is_infinite($int)) { return ($int,$int); } ### assert: 2*$n >= -1 || $n+1==$n || $n!=$n ### assert: 2*$n < 1 || $n+1==$n || $n!=$n my $x = _divrem_mutate($int,3); $int *= 2; if ($x) { $int += 1; $x += ($self->{'align'} eq 'left' ? -1 : -2); } return ($n + $x + $int*$self->{'sign'}, $int); } sub xy_to_n { my ($self, $x, $y) = @_; ### CellularRule-OneTwo xy_to_n(): "$x,$y" $x = round_nearest ($x); $y = round_nearest ($y); if ($y < 0) { return undef; } if (is_infinite($y)) { return $y; } $x -= $y*$self->{'sign'}; if ($y % 2) { ### odd row: "x=$x y=$y" if ($self->{'sign'} > 0) { $x += 1; } if ($x < 0 || $x > 1) { return undef; } return (3*$y-1)/2 + $x + $self->{'n_start'}; } else { ### even row: "x=$x y=$y" if ($x != 0) { return undef; } return ($y/2)*3 + $self->{'n_start'}; } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # swap to y1<=y2 if ($y2 < 0) { return (1, 0); } if ($y1 < 0) { $y1 *= 0; } if (is_infinite($y1)) { return (1, $y1+1); } if (is_infinite($y2)) { return (1, $y2+1); } $y1 -= ($y1%2); $y2 += ($y2%2); return ($y1/2*3 + $self->{'n_start'}, $y2/2*3 + $self->{'n_start'}); } } #------------------------------------------------------------------------------ { package Math::PlanePath::CellularRule::Two; use strict; use Carp 'croak'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem = \&Math::PlanePath::_divrem; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # left 2 cell line rule=14,46,142,174 sign=-1 # ** # ** # ** # * # # right 2 cell line rule=84,116,212,244 sign=1 # rule & 0x5F == 0x54 # ** # ** # ** # * # use constant parameter_info_array => [ { name => 'align', display => 'Align', type => 'enum', default => 'left', choices => ['left','right'], }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; sub x_negative { my ($self) = @_; return ($self->{'align'} eq 'left'); } sub x_maximum { my ($self) = @_; return ($self->{'align'} eq 'left' ? 0 : undef); } sub x_negative_at_n { my ($self) = @_; return ($self->{'align'} eq 'left' ? $self->n_start + 1 : undef); } use constant sumxy_minimum => 0; # triangular X>=-Y so X+Y>=0 { my %sumxy_maximum = (left => 1); sub sumxy_maximum { my ($self) = @_; return $sumxy_maximum{$self->{'align'}}; } } { my %diffxy_minimum = (right => -1); sub diffxy_minimum { my ($self) = @_; return $diffxy_minimum{$self->{'align'}}; } } use constant diffxy_maximum => 0; { my %dx_minimum = (left => -2, right => 0); sub dx_minimum { my ($self) = @_; return $dx_minimum{$self->{'align'}}; } } use constant dx_maximum => 1; use constant dy_minimum => 0; use constant dy_maximum => 1; { my %_UNDOCUMENTED__dxdy_list = (left => [ 1,0, # E -1,1, # NW at N=1 -2,1, # WNW ], right => [ 1,0, # E 0,1, # N ]); sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; return @{$_UNDOCUMENTED__dxdy_list{$self->{'align'}}}; } } { my %_UNDOCUMENTED__dxdy_list_at_n = (left => 2, right => 1); sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + $_UNDOCUMENTED__dxdy_list_at_n{$self->{'align'}}; } } { my %absdx_minimum = (left => 1, # -2 or +1, so minimum abs is 1 right => 0); # 0 or +1, so minimum abs is 0 sub absdx_minimum { my ($self) = @_; return $absdx_minimum{$self->{'align'}}; } } # left => -1, # WNW dX=-2,dY=1 # right => 1; # N or E sub dsumxy_minimum { my ($self) = @_; return $self->{'sign'}; } use constant dsumxy_maximum => 1; # E for left, or N or E for right sub ddiffxy_minimum { my ($self) = @_; return ($self->{'sign'} < 0 ? -3 # left, ENE : -1); # right, N, going as a stairstep so always increase } sub ddiffxy_maximum { my ($self) = @_; return ($self->{'sign'} < 0 ? 1 # left, East : 1); # right, NE diagonal } sub dir_maximum_dxdy { my ($self) = @_; return ($self->{'align'} eq 'left' ? (-2,1) : (0,1)); # North } use constant turn_any_straight => 0; # never straight #--------------------------------------------- my %align_to_sign = (left => -1, right => 1); sub new { my $self = shift->SUPER::new (@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } $self->{'align'} ||= 'left'; $self->{'sign'} = $align_to_sign{$self->{'align'}} || croak "Unrecognised align parameter: ",$self->{'align'}; return $self; } # N=-.5 X=-.5, Y=0 # N=0 X=0, Y=0 # N=.49 X=.49, Y=0 # # N=.5 X=-.5, Y=1 2N=1 Y=(2N+3)/4 # N=1 X=0, Y=1 X= # N=2 X=1, Y=1 # N=2.4 X=1.4, Y=1 # # N=2.5 X=-.5, Y=2 2N=5 # N=3 X=0, Y=2 # N=4 X=1, Y=2 # N=4.4 X=1.4, Y=2 # sub n_to_xy { my ($self, $n) = @_; ### CellularRule-Two n_to_xy(): $n $n = 2*($n - $self->{'n_start'}); # to N=0 basis, and warn if $n undef if ($n < -1) { return; } my ($y, $x) = _divrem ($n+3, 4); if ($y == 0) { $x += $self->{'sign'} - 1; } return (($x - $self->{'sign'} - 2)/2 + $y*$self->{'sign'}, $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### CellularRule-Two xy_to_n(): "$x,$y" $x = round_nearest ($x); $y = round_nearest ($y); if ($y < 0) { return undef; } if (is_infinite($y)) { return $y; } if ($self->{'align'} eq 'left') { $x += $y; if ($y) { $x -= 1; } } else { $x -= $y; } if ($x < ($y ? -1 : 0) || $x > 0) { return undef; } return 2*$y + $x + $self->{'n_start'}; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### CellularRule-Two rect_to_n_range() ... $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # swap to y1<=y2 if ($y2 < 0) { return (1, 0); } if ($y1 < 0) { $y1 *= 0; } ### $y1 ### $y2 if (is_infinite($y1)) { return (1, $y1); } if (is_infinite($y2)) { return (1, $y2); } return (2*$y1 + $self->{'n_start'} - ($y1 == 0 ? 0 : 1), 2*$y2 + $self->{'n_start'}); } } 1; __END__ # For reference the specifics currently are # # 54 CellularRule54 # 57,99 CellularRule57 # 190,246 CellularRule190 # 18,26,82,90,146,154,210,218 SierpinskiTriangle n_start=1 # 151,159,183,191,215,223,247, PyramidRows step=2 # 254,222,255 # 220,252 PyramidRows step=1 # 206,238 PyramidRows step=1 left # 4,12,36,44,68,76,100,108,132, Rows width=1 # 140,164,172,196,204,228,236 single-cell column =for stopwords Ryde Math-PlanePath PlanePath ie Xmax-Xmin superclass eg OEIS MathWorld =head1 NAME Math::PlanePath::CellularRule -- cellular automaton points of binary rule =head1 SYNOPSIS use Math::PlanePath::CellularRule; my $path = Math::PlanePath::CellularRule->new (rule => 30); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis is the patterns of Stephen Wolfram's bit-rule cellular automatons =over L =back Points are numbered left to right in rows so for example rule => 30 51 52 53 54 55 56 57 58 59 60 61 62 9 44 45 46 47 48 49 50 8 32 33 34 35 36 37 38 39 40 41 42 43 7 27 28 29 30 31 6 18 19 20 21 22 23 24 25 26 5 14 15 16 17 4 8 9 10 11 12 13 3 5 6 7 2 2 3 4 1 1 <- Y=0 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 The automaton starts from a single point N=1 at the origin and grows into the rows above. The C parameter controls how the 3 cells below and diagonally below produce a new cell, +-----+ | new | next row, Y+1 +-----+ ^ ^ ^ / | \ / | \ +-----+ +-----+ +-----+ | A | | B | | C | row Y +-----+ +-----+ +-----+ There's 8 combinations of ABC being each 0 or 1. Each such combination can become 0 or 1 in the "new" cell. Those 0 or 1 for "new" is encoded as 8 bits to make a rule number 0 to 255, ABC cells below new cell bit from rule 1,1,1 -> bit7 1,1,0 -> bit6 1,0,1 -> bit5 ... 0,0,1 -> bit1 0,0,0 -> bit0 When cells 0,0,0 become 1, ie. C bit0 is 1 (an odd number), the "off" cells either side of the initial N=1 become all "on" infinitely to the sides. Or if rule bit7 for 1,1,1 is a 0 (ie. S 128>) then they turn on and off alternately in odd and even rows. In both cases only the pyramid portion part -YE=XE=Y is considered for the N points but the infinite cells to the sides are included in the calculation. The full set of patterns can be seen at the MathWorld page above, or can be printed with the F here. The patterns range from simple to complex. For some, the N=1 cell doesn't grow at all such as rule 0 or rule 8. Some grow to mere straight lines such as rule 2 or rule 5. Others make columns or patterns with "quadratic" style stepping of 1 or 2 rows, or self-similar replications such as the Sierpinski triangle of rule 18 and 60. Some rules have complicated non-repeating patterns when there's feedback across from one half to the other, such as rule 30. For some rules there's specific PlanePath code which this class dispatches to, such as C, C, C or C (with C). For rules without specific code, the current implementation is not particularly efficient as it builds and holds onto the bit pattern for all rows through to the highest N or X,Y used. There's no doubt better ways to iterate an automaton, but this module offers the patterns in PlanePath style. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same pattern. For example to start at 0, =cut # math-image --path=CellularRule,rule=62,n_start=0 --all --output=numbers --size=75x6 =pod n_start => 0, rule => 62 18 19 20 21 22 23 24 25 5 13 14 15 16 17 4 7 8 9 10 11 12 3 4 5 6 2 1 2 3 1 0 <- Y=0 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::CellularRule-Enew (rule =E 123)> =item C<$path = Math::PlanePath::CellularRule-Enew (rule =E 123, n_start =E $n)> Create and return a new path object. C should be an integer 0 to 255. A C should be given always. There is a default, but it's secret and likely to change. If there's specific PlanePath code implementing the pattern then the returned object is from that class and generally is not C. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each cell as a square of side 1. If C<$x,$y> is outside the pyramid or on a skipped cell the return is C. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path can be found in the OEIS index =over L =back and in addition the following =over L (etc) =back rule=50,58,114,122,178,186,242,250, 179 (solid every second cell) A061579 permutation N at -X,Y (mirror horizontal) =head1 SEE ALSO L, L, L, L, L, L L L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/GosperIslands.pm0000644000175000017500000005240113734026671020347 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=GosperIslands --lines --scale=10 # math-image --path=GosperIslands --all --output=numbers_dash # # fractal dimension 2*log(3) / log(7) = 1.12915, A113211 decimal # Check: # A017926 boundary length, unit equilateral triangles # A229977 boundary length, initial hexagon=1 package Math::PlanePath::GosperIslands; use 5.004; use strict; use POSIX 'ceil'; use Math::Libm 'hypot'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh'; use Math::PlanePath::SacksSpiral; # uncomment this to run the ### lines # use Smart::Comments; use constant n_frac_discontinuity => 0; use constant x_negative_at_n => 3; use constant y_negative_at_n => 5; use constant sumabsxy_minimum => 2; # minimum X=2,Y=0 or X=1,Y=1 use constant rsquared_minimum => 2; # minimum X=1,Y=1 # jump across rings is upwards, so dY maximum unbounded but minimum=-1 use constant dy_minimum => -1; # dX and dY unbounded jumping between rings, with the jump position rotating # around slowly with the twistiness of the ring. use constant absdx_minimum => 1; # jump across rings is ENE, so dSum maximum unbounded but minimum=-2 use constant dsumxy_minimum => -2; # jump across rings is ENE, so dDiffXY minimum unbounded but maximum=+2 use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); $self->{'sides'} ||= 6; # default return $self; } # innermost hexagon level 0 # level 0 len=6 # level 1 len=18 # each sidelen = 3^level # each ringlen = 6*3^level # # Nstart(level) = 1 + ringlen(0) + ... + ringlen(level-1) # = 1 + 6* [ 3^0 + ... + 3^(level-1) ] # = 1 + 6* [ (3^level - 1) / 2 ] # = 1 + 3*(3^level - 1) # = 1 + 3*3^level - 3 # = 3^(level+1) - 2 # # 3^(level+1) = N+2 # level+1 = log3(N+2) # level = log3(N+2) - 1 # # sides=3 # ringlen = 3*3^level # Nstart(level) = 1 + 3* [ 3^0 + ... + 3^(level-1) ] # = 1 + 3* [ (3^level - 1) / 2 ] # = 1 + 3*(3^level - 1)/2 # = 1 + (3*3^level - 3)/2 # = (3*3^level - 3 + 2)/2 # = (3^(level+1) - 1)/2 # 3^(level+1) - 1 = 2*N # 3^(level+1) = 2*N+1 my @_xend = (2, 5); my @_yend = (0, 1); my @_hypotend = (4, 28); sub _ends_for_level { my ($level) = @_; if ($#_xend < $level) { my $x = $_xend[-1]; my $y = $_yend[-1]; do { ($x,$y) = ((5*$x - 3*$y)/2, # 2*$x + rotate +60 ($x + 5*$y)/2); # 2*$y + rotate +60 ### _ends_for_level() push: scalar(@_xend)." $x,$y" # ### assert: "$x,$y" eq join(','__PACKAGE__->n_to_xy(scalar(@xend) ** 3)) push @_xend, $x; push @_yend, $y; push @_hypotend, $_hypotend[-1] * 7; } while ($#_xend < $level); } } my @level_x = (0); my @level_y = (0); sub n_to_xy { my ($self, $n) = @_; ### GosperIslands n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n)) { return ($n,$n); } my $sides = $self->{'sides'}; my ($pow, $level) = round_down_pow (($sides == 6 ? $n+2 : 2*$n+1), 3); my $base = ($sides == 6 ? $pow-2 : ($pow-1)/2); ### $level ### $base $n -= $base; # remainder my $sidelen = $pow / 3; $level--; my $side = int ($n / $sidelen); my ($x, $y) = _side_n_to_xy ($n - $side*$sidelen); _ends_for_level($level); ### raw xy: "$x,$y" ### pos: "$_xend[$level],$_yend[$level]" if ($sides == 6) { ($x,$y) = (($x+3*$y)/-2, # rotate +120 ($x-$y)/2); ### rotate xy: "$x,$y" $x += $_xend[$level]; $y += $_yend[$level]; if ($side >= 3) { $x = -$x; # rotate 180 $y = -$y; $side -= 3; } if ($side == 1) { ($x,$y) = (($x-3*$y)/2, # rotate +60 ($x+$y)/2); } elsif ($side == 2) { ($x,$y) = (($x+3*$y)/-2, # rotate +120 ($x-$y)/2); } } else { my $xend = $_xend[$level]; my $yend = $_yend[$level]; my $xp = 0; my $yp = 0; foreach (0 .. $level-1) { $xp +=$_xend[$_]; $yp +=$_yend[$_]; } ### ends: "$xend,$yend prev $xp,$yp" ($xp,$yp) = (($xp-3*$yp)/-2, # rotate -120 ($xp+$yp)/-2); # ($xp,$yp) = (($xp-3*$yp)/2, # rotate +60 # ($xp+$yp)/2); if ($side == 0) { $x += $xp; $y += $yp; } elsif ($side == 1) { ($x,$y) = (($x+3*$y)/-2, # rotate +120 ($x-$y)/2); $x += $xp; $y += $yp; $x += $xend; $y += $yend; } elsif ($side == 2) { ($x,$y) = (($x-3*$y)/-2, # rotate +240==-120 ($x+$y)/-2); $x += $xp; $y += $yp; $x += $xend; $y += $yend; ($xend,$yend) = (($xend+3*$yend)/-2, # rotate +120 ($xend-$yend)/2); $x += $xend; $y += $yend; } } return ($x,$y); } # ENHANCE-ME: share with GosperSide ? sub _side_n_to_xy { my ($n) = @_; ### _side_n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $x; my $y = 0; { my $int = int($n); $x = 2*($n - $int); $n = $int; # BigFloat int() gives BigInt, use that } my $xend = 2; my $yend = 0; foreach my $digit (digit_split_lowtohigh($n,3)) { my $xend_offset = 3*($xend-$yend)/2; # end and end +60 my $yend_offset = ($xend+3*$yend)/2; ### at: "$x,$y" ### $digit ### $xend ### $yend ### $xend_offset ### $yend_offset if ($digit == 1) { ($x,$y) = (($x-3*$y)/2 + $xend, # rotate +60 ($x+$y)/2 + $yend); } elsif ($digit == 2) { $x += $xend_offset; # offset and offset +60 $y += $yend_offset; } $xend += $xend_offset; # offset and offset +60 $yend += $yend_offset; } ### final: "$x,$y" return ($x, $y); } #------------------------------------------------------------------------------ # xy_to_n() # innermost origin 0,0 N=0 level 0, # first hexagon level 1 # # Nstart(level) = 3^level - 2 # sidelength(level) = 3^(level-1) # so Nstart(level) + side * sidelength(level) # = 3^level - 2 + side * 3^(level-1) # = (3 + side) * 3^(level-1) - 2 # sub xy_to_n { my ($self, $x_full, $y_full) = @_; $x_full = round_nearest($x_full); $y_full = round_nearest($y_full); ### GosperIslands xy_to_n(): "$x_full,$y_full" foreach my $overflow (3*$x_full+3*$y_full, 3*$x_full-3*$y_full) { if (is_infinite($overflow)) { return $overflow; } } if (($x_full + $y_full) % 2) { ### odd point, not reached ... return undef; } my $r = hypot($x_full,$y_full); my $level_limit = ceil(log($r+1)/log(sqrt(7))); ### $r ### $level_limit if (is_infinite($level_limit)) { return $level_limit; } for my $level (0 .. $level_limit + 1) { _ends_for_level($level); my $xend = $_xend[$level]; my $yend = $_yend[$level]; my $x = $x_full - $xend; my $y = $y_full - $yend; ### level end: "$xend,$yend" ### level subtract to: "$x,$y" ($x,$y) = ((3*$y-$x)/2, # rotate -120; ($x+$y)/-2); ### level rotate to: "$x,$y" foreach my $side (0 .. 5) { ### try: "level=$level side=$side $x,$y" if (defined (my $n = _xy_to_n_in_level($x,$y,$level))) { return ($side+3)*3**$level + $n - 2; } $x -= $xend; $y -= $yend; ### subtract to: "$x,$y" ($x,$y) = (($x+3*$y)/2, # rotate -60 ($y-$x)/2); } } return undef; } sub _xy_to_n_in_level { my ($x, $y, $level) = @_; _ends_for_level($level); my @pending_n = (0); my @pending_x = ($x); my @pending_y = ($y); my @pending_level = ($level); while (@pending_n) { my $n = pop @pending_n; $x = pop @pending_x; $y = pop @pending_y; $level = pop @pending_level; ### consider: "$x,$y n=$n level=$level" if ($level == 0) { if ($x == 0 && $y == 0) { return $n; } ### level=0 and not 0,0 next; } if (($x*$x+3*$y*$y) > $_hypotend[$level] * 1.1) { ### radius out of range: ($x*$x+3*$y*$y)." cf end ".$_hypotend[$level] next; } $level--; $n *= 3; my $xend = $_xend[$level]; my $yend = $_yend[$level]; ### descend: "end=$xend,$yend on level=$level" # digit 0 push @pending_n, $n; push @pending_x, $x; push @pending_y, $y; push @pending_level, $level; ### push: "$x,$y digit=0" # digit 1 $x -= $xend; $y -= $yend; ($x,$y) = (($x+3*$y)/2, # rotate -60 ($y-$x)/2); push @pending_n, $n + 1; push @pending_x, $x; push @pending_y, $y; push @pending_level, $level; ### push: "$x,$y digit=1" # digit 2 $x -= $xend; $y -= $yend; ($x,$y) = (($x-3*$y)/2, # rotate +60 ($x+$y)/2); push @pending_n, $n + 2; push @pending_x, $x; push @pending_y, $y; push @pending_level, $level; ### push: "$x,$y digit=2" } return undef; } # Each # *--- # / # ---* # is width=5 heightflat=1 is # hypot^2 = 5*5 + 3 * 1*1 # = 25+3 # = 28 # hypot = 2*sqrt(7) # # comes in closer to # level=1 n=9,x=2,y=2 is hypot=sqrt(2*2+3*2*2) = sqrt(16) = 4 # level=2 n=31,x=2,y=6 is hypot=sqrt(2*2+3*6*6) = sqrt(112) = sqrt(7)*4 # so # radius = 4 * sqrt(7)^(level-1) # radius/4 = sqrt(7)^(level-1) # level-1 = log(radius/4) / log(sqrt(7)) # level = log(radius/4) / log(sqrt(7)) + 1 # # Or # level=1 n=9,x=2,y=2 is h=2*2+3*2*2 = 16 # level=2 n=31,x=2,y=6 is h=2*2+3*6*6 = 112 = 7*16 # h = 16 * 7^(level-1) # h/16 = 7^(level-1) # level-1 = log(h/16) / log(7) # level = log(h/16) / log(7) + 1 # # is the next outer level, so high covering is the end of the previous # level, # # Nstart(level) - 1 = 3^(level+2) - 2 - 1 # = 3^(level+2) - 3 # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $y1 *= sqrt(3); $y2 *= sqrt(3); my ($r_lo, $r_hi) = Math::PlanePath::SacksSpiral::_rect_to_radius_range ($x1,$y1, $x2,$y2); $r_hi *= 2; my $level_plus_1 = ceil( log(max(1,$r_hi/4)) / log(sqrt(7)) ) + 2; return (1, 3**$level_plus_1 - 3); } 1; __END__ =for stopwords eg Ryde Gosper Nstart wiggliness versa PlanePath Math-PlanePath =head1 NAME Math::PlanePath::GosperIslands -- concentric Gosper islands =head1 SYNOPSIS use Math::PlanePath::GosperIslands; my $path = Math::PlanePath::GosperIslands->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis path is integer versions of the Gosper island at successive levels, arranged as concentric rings on a triangular lattice (see L). Each island is the outline of a self-similar tiling of the plane by hexagons, and the sides are self-similar expansions of line segments 35----34 8 / \ ..-36 33----32 29----28 7 \ / \ 31----30 27----26 6 \ 25 5 78 4 \ 11-----10 77 3 / \ / 13----12 9---- 8 76 2 / \ \ 14 3---- 2 7 ... 1 \ / \ 15 4 1 24 <- Y=0 / \ \ 16 5----- 6 23 -1 \ / 17----18 21----22 -2 \ / 19----20 -3 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 10 11 =head2 Hexagon Replications The islands can be thought of as a self-similar tiling of the plane by hexagonal shapes. The innermost hexagon N=1 to N=6 is replicated six times to make the next outline N=7 to N=24. *---* / \ *---* *---* / \ / \ * *---* * \ / \ / *---* *---* / \ / \ * *---* * \ / \ / *---* *---* \ / *---* Then that shape is in turn replicated six times around itself to make the next level. *---E / \ *---* *---* *---* / \ / \ * *---* *---* \ / \ * * D / \ / *---* * * / \ / \ *---* *---* *---* * / \ / \ / * *---* *---* *---* \ / \ / \ * * C---* *---* / \ / \ * * O * * \ / \ / *---* *---* * * \ / \ / \ *---* *---* *---* * / \ / \ / * *---* *---* *---* \ / \ / * * *---* / \ / * * * \ / \ *---* *---* * \ / \ / *---* *---* *---* \ / *---* The shapes here and at higher level are like hexagons with wiggly sides. The sides are symmetric, so they mate on opposite sides but the join "corner" is not on the X axis (after the first level). For example the point marked "C" (N=7) is above the axis at X=5,Y=1. The next replication joins at point "D" (N=25) at X=11,Y=5. =head2 Side and Radial Lines The sides at each level is a self-similar line segment expansion, *---* *---* becomes / *---* This expanding side shape is also the radial line or spoke from the origin out to "corner" join points. At level 0 they're straight lines, ...----* / \ / \ side / \ / \ O---------* / / Then at higher levels they're wiggly, such as level 1, ...--* / \ * *---* \ \ * *---C / / / O---* ... Or level 2, *---E ... / \ * *---* *---* \ \ / \ * *---* *---* / \ * *---D \ / / *---* *---* ... \ / * * / \ * * \ / * *---C / / O---* The lines become ever wigglier at higher levels, and in fact become "S" shapes with the ends spiralling around and in (and in fact middle sections likewise S and spiralling, to a lesser extent). The spiralling means that the X axis is crossed multiple times at higher levels. For example in level 11 X>0,Y=0 occurs 22 times between N=965221 and N=982146. Likewise on diagonal lines X=Y and X=-Y which are "sixths" of the initial hexagon. The self-similar spiralling means the Y axis too is crossed multiple times at higher levels. In level 11 again X=0,Y>0 is crossed 7 times between N=1233212 and N=1236579. (Those N's are bigger than the X axis crossing, because the Nstart position at level 11 has rotated around some 210 degrees to just under the negative X axis.) In general any radial straight line is crossed many times way eventually. =head2 Level Ranges Counting the inner hexagon as level=0, the side length and whole ring is sidelen = 3^level ringlen = 6 * 3^level The starting N for each level is the total points preceding it, so Nstart = 1 + ringlen(0) + ... + ringlen(level-1) = 3^(level+1) - 2 For example level=2 starts at Nstart=3^3-2=25. The way the side/radial lines expand as described above makes the Nstart position rotate around at each level. N=7 is at X=5,Y=1 which is angle angle = arctan(1*sqrt(3) / 5) = 19.106.. degrees The sqrt(3) factor as usual turns the flattened integer X,Y coordinates into proper equilateral triangles. The further levels are then multiples of that angle. For example N=25 at X=11,Y=5 is 2*angle, or N=79 at X=20,Y=18 at 3*angle. The N=7 which is the first radial replication at X=5,Y=1, scaled to unit sided equilateral triangles, has distance from the origin d1 = hypot(5, 1*sqrt(3)) / 2 = sqrt(7) The subsequent levels are powers of that sqrt(7), Xstart,Ystart of the Nstart(level) position d = hypot(Xstart,Ystart*sqrt(3))/2 = sqrt(7) ^ level This multiple of the angle and powering of the radius means the Nstart points are on a logarithmic spiral. =head2 Fractal Island The Gosper island is usually conceived as a fractal, with the initial hexagon in a fixed position and the sides having the line segment substitution described above, for ever finer levels of "S" spiralling wiggliness. The code here can be used for that by rotating the Nstart position back to the X axis and scaling down to a desired unit radius. Xstart,Ystart of the Nstart(level) position scale factor = 0.5 / hypot(Ystart*sqrt(3), Xstart) rotate angle = - atan2 (Ystart*sqrt(3), Xstart) This scale and rotate puts the Nstart point at X=1,Y=0 and further points of the ring around from that. Remember the sqrt(3) factor on Y for all points to turn the integer coordinates into proper equilateral triangles. Notice the line segment substitution doesn't change the area of the initial hexagon. Effectively (and not to scale), * / \ *-------* becomes * / * \ / * So the area lost below is gained above (or vice versa). The result is a line of ever greater length enclosing an unchanging area. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::GosperIslands-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 1 and if C<$n E 0> then the return is an empty list. =back =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/WunderlichMeander.pm0000644000175000017500000003575213734026653021204 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::WunderlichMeander; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; use constant 1.02 _UNDOCUMENTED__dxdy_list_at_n => 5; #------------------------------------------------------------------------------ # tables generated by tools/wunderlich-meander-table.pl # my @next_state = (18,18, 0, 0, 0, 9, 27,27, 0, # 0 27,27, 9, 9, 9, 0, 18,18, 9, # 9 0, 0,18, 18,18,27, 9, 9,18, # 18 9, 9,27, 27,27,18, 0, 0,27); # 27 my @digit_to_x = (0,1,2, 2,2,1, 1,0,0, # 0 2,1,0, 0,0,1, 1,2,2, # 9 0,0,0, 1,2,2, 1,1,2, # 18 2,2,2, 1,0,0, 1,1,0); # 27 my @digit_to_y = (0,0,0, 1,2,2, 1,1,2, # 0 2,2,2, 1,0,0, 1,1,0, # 9 0,1,2, 2,2,1, 1,0,0, # 18 2,1,0, 0,0,1, 1,2,2); # 27 my @xy_to_digit = (0,7,8, 1,6,5, 2,3,4, # 0 4,3,2, 5,6,1, 8,7,0, # 9 0,1,2, 7,6,3, 8,5,4, # 18 4,5,8, 3,6,7, 2,1,0); # 27 my @min_digit = (0,0,0,1,2,1, # 0 0,0,0,1,2,1, 0,0,0,1,2,1, 7,5,3,3,3,5, 8,5,4,4,4,5, 7,6,3,3,3,6, 4,4,4,5,8,5, # 36 3,3,3,5,7,5, 2,1,0,0,0,1, 2,1,0,0,0,1, 2,1,0,0,0,1, 3,3,3,6,7,6, 0,0,0,7,8,7, # 72 0,0,0,5,5,6, 0,0,0,3,4,3, 1,1,1,3,4,3, 2,2,2,3,4,3, 1,1,1,5,5,6, 4,3,2,2,2,3, # 108 4,3,1,1,1,3, 4,3,0,0,0,3, 5,5,0,0,0,6, 8,7,0,0,0,7, 5,5,1,1,1,6); my @max_digit = (0,1,2,2,2,1, # 0 7,7,7,6,3,6, 8,8,8,6,4,6, 8,8,8,6,4,6, 8,8,8,5,4,5, 7,7,7,6,3,6, 4,5,8,8,8,5, # 36 4,6,8,8,8,6, 4,6,8,8,8,6, 3,6,7,7,7,6, 2,2,2,1,0,1, 3,6,7,7,7,6, 0,7,8,8,8,7, # 72 1,7,8,8,8,7, 2,7,8,8,8,7, 2,6,6,6,5,6, 2,3,4,4,4,3, 1,6,6,6,5,6, 4,4,4,3,2,3, # 108 5,6,6,6,2,6, 8,8,8,7,2,7, 8,8,8,7,1,7, 8,8,8,7,0,7, 5,6,6,6,1,6); sub n_to_xy { my ($self, $n) = @_; ### WunderlichMeander n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { # ENHANCE-ME: determine dx/dy direction from last state, not full # calculation of N+1 my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } my @digits = digit_split_lowtohigh($n,9); my $len = ($n*0 + 3) ** scalar(@digits); # inherit bignum 3 ### digits: join(', ',@digits)." count ".scalar(@digits) ### $len my $state = ($#digits & 1 ? 18 : 0); my $x = 0; my $y = 0; while (@digits) { $len /= 3; $state += pop @digits; # high to low ### $len ### $state ### digit_to_x: $digit_to_x[$state] ### digit_to_y: $digit_to_y[$state] ### next_state: $next_state[$state] $x += $len * $digit_to_x[$state]; $y += $len * $digit_to_y[$state]; $state = $next_state[$state]; } ### final: "$x,$y" return ($x, $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### WunderlichMeander xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my @xdigits = digit_split_lowtohigh ($x, 3); my @ydigits = digit_split_lowtohigh ($y, 3); my $level = max($#xdigits,$#ydigits); my $state = ($level & 1 ? 18 : 0); my @ndigits; foreach my $i (reverse 0 .. $level) { # high to low my $ndigit = $xy_to_digit[$state + 3*($xdigits[$i]||0) + ($ydigits[$i]||0)]; $ndigits[$i] = $ndigit; $state = $next_state[$state+$ndigit]; } return digit_join_lowtohigh (\@ndigits, 9, $x * 0 * $y); # bignum zero } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### WunderlichMeander rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 0) { return (1, 0); } my ($len, $level) = round_down_pow (max($x2,$y2), 3); ### $len ### $level if (is_infinite($level)) { return (0, $level); } my $n_min = my $n_max = my $x_min = my $y_min = my $x_max = my $y_max = ($x1 * 0 * $x2 * $y1 * $y2); # inherit bignum 0 my $min_state = my $max_state = ($level & 1 ? 18 : 0); # x__ 0 # xx_ 1 # xxx 2 # _xx 3 # __x 4 # _x_ 5 # while ($level >= 0) { my $l2 = 2*$len; { my $x_cmp1 = $x_min + $len; my $y_cmp1 = $y_min + $len; my $x_cmp2 = $x_min + $l2; my $y_cmp2 = $y_min + $l2; my $digit = $min_digit[4*$min_state # 4*9=36 apart + ($x1 >= $x_cmp2 ? 4 : $x1 >= $x_cmp1 ? ($x2 < $x_cmp2 ? 5 : 3) : ($x2 < $x_cmp1 ? 0 : $x2 < $x_cmp2 ? 1 : 2)) + ($y1 >= $y_cmp2 ? 6*4 : $y1 >= $y_cmp1 ? ($y2 < $y_cmp2 ? 6*5 : 6*3) : ($y2 < $y_cmp1 ? 6*0 : $y2 < $y_cmp2 ? 6*1 : 6*2))]; # my $key = 4*$min_state # 4*9=36 apart # + ($x1 >= $x_cmp2 ? 4 # : $x1 >= $x_cmp1 ? ($x2 < $x_cmp2 ? 5 : 3) # : ($x2 < $x_cmp1 ? 0 # : $x2 < $x_cmp2 ? 1 : 2)) # + ($y1 >= $y_cmp2 ? 6*4 # : $y1 >= $y_cmp1 ? ($y2 < $y_cmp2 ? 6*5 : 6*3) # : ($y2 < $y_cmp1 ? 6*0 # : $y2 < $y_cmp2 ? 6*1 : 6*2)); # ### $min_state # ### $len # ### $l2 # ### $key # ### $x_cmp1 # ### $x_cmp2 # ### $digit $n_min = 9*$n_min + $digit; $min_state += $digit; $x_min += $len * $digit_to_x[$min_state]; $y_min += $len * $digit_to_y[$min_state]; $min_state = $next_state[$min_state]; } { my $x_cmp1 = $x_max + $len; my $y_cmp1 = $y_max + $len; my $x_cmp2 = $x_max + $l2; my $y_cmp2 = $y_max + $l2; my $digit = $max_digit[4*$max_state # 4*9=36 apart + ($x1 >= $x_cmp2 ? 4 : $x1 >= $x_cmp1 ? ($x2 < $x_cmp2 ? 5 : 3) : ($x2 < $x_cmp1 ? 0 : $x2 < $x_cmp2 ? 1 : 2)) + ($y1 >= $y_cmp2 ? 6*4 : $y1 >= $y_cmp1 ? ($y2 < $y_cmp2 ? 6*5 : 6*3) : ($y2 < $y_cmp1 ? 6*0 : $y2 < $y_cmp2 ? 6*1 : 6*2))]; # my $key = 4*$max_state # 4*9=36 apart # + ($x1 >= $x_cmp2 ? 4 # : $x1 >= $x_cmp1 ? ($x2 < $x_cmp2 ? 5 : 3) # : ($x2 < $x_cmp1 ? 0 # : $x2 < $x_cmp2 ? 1 : 2)) # + ($y1 >= $y_cmp2 ? 4 # : $y1 >= $y_cmp1 ? ($y2 < $y_cmp2 ? 5 : 3) # : ($y2 < $y_cmp1 ? 0 # : $y2 < $y_cmp2 ? 1 : 2)); # ### $max_state # ### $len # ### $l2 # ### $x_key # ### $key # ### $x_max # ### $y_max # ### $x_cmp1 # ### $x_cmp2 # ### $y_cmp1 # ### $y_cmp2 # ### $digit # ### max digit: $max_digit[$key] $n_max = 9*$n_max + $digit; $max_state += $digit; $x_max += $len * $digit_to_x[$max_state]; $y_max += $len * $digit_to_y[$max_state]; $max_state = $next_state[$max_state]; } $len = int($len/3); $level--; } return ($n_min, $n_max); } #----------------------------------------------------------------------------- # level_to_n_range() use Math::PlanePath::SquareReplicate; *level_to_n_range = \&Math::PlanePath::SquareReplicate::level_to_n_range; *n_to_level = \&Math::PlanePath::SquareReplicate::n_to_level; #----------------------------------------------------------------------------- 1; __END__ =for stopwords eg Ryde ie Math-PlanePath Wunderlich Wunderlich's Uber Peano-Kurven Elemente der Mathematik =head1 NAME Math::PlanePath::WunderlichMeander -- 3x3 self-similar "R" shape =head1 SYNOPSIS use Math::PlanePath::WunderlichMeander; my $path = Math::PlanePath::WunderlichMeander->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is an integer version of the 3x3 self-similar meander from =over Walter Wunderlich, "Uber Peano-Kurven", Elemente der Mathematik, volume 28, number 1, 1973, pages 1-10. L, L =back 8 20--21--22 29--30--31 38--39--40 | | | | | | 7 19 24--23 28 33--32 37 42--41 | | | | | | 6 18 25--26--27 34--35--36 43--44 | | 5 17 14--13 56--55--54--53--52 45 | | | | | | 4 16--15 12 57 60--61 50--51 46 | | | | | | 3 9--10--11 58--59 62 49--48--47 | | 2 8 5-- 4 65--64--63 74--75--76 | | | | | | 1 7-- 6 3 66 69--70 73 78--77 | | | | | | Y=0-> 0-- 1-- 2 67--68 71--72 79--80-... X=0 1 2 3 4 5 6 7 8 The base pattern is the N=0 to N=8 section. It works as a traversal of a 3x3 square starting in one corner and going along one side. The base figure goes upwards and it's then used rotated by 180 degrees and/or transposed to go in other directions, +----------------+----------------+---------------+ | ^ | * | ^ | | | | rotate 180 | | | base | | | 8 | 5 | | | 4 | | | base | | | | | | * | v | * | +----------------+----------------+---------------+ | <------------* | <------------* | ^ | | | | | | | 7 | 6 | | 3 | | rotate 180 | rotate 180 | | base | | + transpose | + transpose | * | +----------------+----------------+---------------+ | | | ^ | | | | | | | 0 | 1 | | 2 | | transpose | transpose | | base | | *-----------> | *------------> | * | +----------------+----------------+---------------+ The base 0 to 8 goes upwards, so the across sub-parts are an X,Y transpose. The transpose in the 0 part means the higher levels go alternately up or across. So N=0 to N=8 goes up, then the next level N=0,9,18,.,72 goes right, then N=81,162,..,648 up again, etc. Wunderlich's conception is successive lower levels of detail as a space-filling curve. The transposing in that case applies to ever smaller parts. But for the integer version here, the start direction is held fixed and the successively higher levels alternate. The first move N=0 to N=1 is rightwards per the "Schema" shown in Wunderlich's paper (and like various other C curves). =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::WunderlichMeander-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 9**$level - 1)>. =back =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/KochPeaks.pm0000644000175000017500000003660213734026667017454 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::KochPeaks; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow'; use Math::PlanePath::KochCurve; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; # uncomment this to run the ### lines #use Devel::Comments; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; use constant x_negative_at_n => 1; use constant sumabsxy_minimum => 1; # minimum X=1,Y=0 use constant absdiffxy_minimum => 1; # X=Y never occurs use constant rsquared_minimum => 1; # minimum X=1,Y=0 use constant dx_maximum => 2; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant absdx_minimum => 1; # never vertical use constant dsumxy_maximum => 2; # diagonal NE use constant ddiffxy_maximum => 2; # diagonal NW use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ # N=1 to 3 3 of, level=0 # N=4 to 12 9 of, level=1 # N=13 to 45 33 of, level=2 # # N=0.5 to 3.49 diff=3 # N=3.39 to 12.49 diff=9 # N=12.5 to 45.5 diff=33 # # each length = 2*4^level + 1 # # Nstart = 1 + 2*4^0 + 1 + 2*4^1 + 1 + ... + 2*4^(level-1) + 1 # = 1 + level + 2*[ 4^0 + 4^1 + ... + 4^(level-1) ] # = level+1 + 2*[ (4^level - 1)/3 ] # = level+1 + (2*4^level - 2)/3 # = level + (2*4^level - 2 + 3)/3 # = level + (2*4^level + 1)/3 # # 3*n = 2*4^level + 1 # 3*n-1 = 2*4^level # (3*n-1)/2 = 4^level # # Nbase = 0.5 + 2*4^0 + 1 + 2*4^1 + 1 + ... + 2*4^(level-1) + 1 # = level + (2*4^level + 1)/3 - 1/2 # = level + 2/3*4^level + 1/3 - 1/2 # = level + 2/3*4^level - 1/6 # = level + 4/6*4^level - 1/6 # = level + (4*4^level - 1)/6 # = level + (4^(level+1) - 1)/6 # # 6*N = 4^(level+1) - 1 # 6*N + 1 = 4^(level+1) # level+1 = log4(6*N + 1) # level = log4(6*N + 1) - 1 # ### loop 1: (2*4**1 + 1)/3 ### loop 2: (2*4**2 + 1)/3 ### loop 3: (2*4**3 + 1)/3 # sub _n_to_level { # my ($n) = @_; # my ($side, $level) = round_down_pow(6*$n + 1, 4); # my $base = $level + (2*$side + 1)/3 - .5; # ### $level # ### $base # if ($base > $n) { # $level--; # $side /= 4; # $base = $level + (2*$side + 1)/3 - .5; # ### $level # ### $base # } # return ($level, $base, $side + .5); # } # sub _level_to_base { # my ($level) = @_; # return $level + (2*$side + 1)/3 - .5; # } sub _n_to_side_level_base { my ($n) = @_; my ($side, $level) = round_down_pow((3*$n-1)/2, 4); my $base = $level + (2*$side + 1)/3; ### $level ### $base if (2*$n+1 < 2*$base) { $level--; $side /= 4; $base = $level + (2*$side + 1)/3; ### $level ### $base } return ($side, $level, $base); } sub n_to_xy { my ($self, $n) = @_; ### KochPeaks n_to_xy(): $n # $n<0.5 no good for Math::BigInt circa Perl 5.12, compare in integers return if 2*$n < 1; if (is_infinite($n)) { return ($n,$n); } my ($side, $level, $base) = _n_to_side_level_base($n); my $rem = $n - $base; my $frac; if ($rem < 0) { ### neg frac $frac = $rem; $rem = 0; } elsif ($rem > 2*$side) { ### excess frac $frac = $rem - 2*$side; $rem -= $frac; } else { ### no frac $frac = 0; } ### $frac ### $rem ### $n ### next base would be: ($level+1) + (2*4**($level+1) + 1)/3 ### assert: $n-$frac >= $base ### assert: $n-$frac < ($level+1) + (2*4**($level+1) + 1)/3 ### assert: $rem>=0 ### assert: $rem < 2 * 4 ** $level + 1 ### assert: $rem <= 2*$side+1 my $pos = 3**$level; if ($rem < $side) { my ($x, $y) = Math::PlanePath::KochCurve->n_to_xy($rem); ### left side: $rem ### flat: "$x,$y" $x += 2*$frac; return (($x-3*$y)/2 - $pos, # rotate +60 ($x+$y)/2); } else { my ($x, $y) = Math::PlanePath::KochCurve->n_to_xy($rem-$side); ### right side: $rem-$side ### flat: "$x,$y" $x += 2*$frac; return (($x+3*$y)/2, # rotate -60 ($y-$x)/2 + $pos); } } sub xy_to_n { my ($self, $x, $y) = @_; ### KochPeaks xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($y < 0 || ! (($x ^ $y) & 1)) { ### neg y or parity... return undef; } my ($len,$level) = round_down_pow ($y+abs($x), 3); ### $level ### $len if (is_infinite($level)) { return $level; } my $n; if ($x < 0) { $x += $len; ($x,$y) = (($x+3*$y)/2, # rotate -60 ($y-$x)/2); $n = 0; ### left rotate -60 to: "x=$x,y=$y n=$n" } else { $y -= $len; ($x,$y) = (($x-3*$y)/2, # rotate +60 ($x+$y)/2); $n = 1; ### right rotate +60 to: "x=$x,y=$y n=$n" } foreach (1 .. $level) { $n *= 4; ### at: "level=$level len=$len x=$x,y=$y n=$n" if ($x < $len) { $len /= 3; my $rel = 2*$len; if ($x < $rel) { ### digit 0 } else { ### digit 1 sub: "$rel to x=".($x-$rel) $x -= $rel; ($x,$y) = (($x+3*$y)/2, # rotate -60 ($y-$x)/2); $n += 1; } } else { $len /= 3; $x -= 4*$len; if ($x < $y) { # before diagonal ### digit 2... ($x,$y) = (($x-3*$y)/2 + 2*$len, # rotate +60 ($x+$y)/2); $n += 2; } else { #### digit 3... $n += 3; } } } ### end at: "x=$x,y=$y n=$n" if ($x) { ### endmost point $n += 1; $x -= 2; } if ($x != 0 || $y != 0) { return undef; } return $n + $level + (2*4**$level + 1)/3 + ($x == 2); } # level extends to x= +/- 3^level # y= 0 to 3^level # # diagonal X=Y or Y=-X is lowest in a level, so round down abs(X)+Y to pow 3 # # end of level is 1 before base of level+1 # basenext = (level+1) + (2*4^(level+1) + 1)/3 # basenext-1 = level + (2*4^(level+1) + 1)/3 # = level + (8*4^level + 1)/3 # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### KochPeaks rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ### rounded: "$x1,$y1 $x2,$y2" if ($y1 < 0 && $y2 < 0) { return (1,0); } # can't make use of the len=3**$level returned by round_down_pow() my ($len, $level) = round_down_pow (max(abs($x1),abs($x2)) + max($y1, $y2), 3); ### $level return (1, $level + (8 * 4**$level + 1)/3); } # peak Y is at N = Nstart + (count-1)/2 # = level + (2*4^level + 1)/3 + (2*4^level + 1 - 1)/2 # = level + (2*4^level + 1)/3 + (2*4^level)/2 # = level + (2*4^level + 1)/3 + 4^level # = level + (2*4^level + 1 + 3*4^level)/3 # = level + (5*4^level + 1)/3 #------------------------------------------------------------------------------ sub level_to_n_range { my ($self, $level) = @_; my $pow = 4**$level; return ((2*$pow + 1)/3 + $level, (8*$pow + 1)/3 + $level); } sub n_to_level { my ($self, $n) = @_; if ($n < 1) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($side, $level, $base) = _n_to_side_level_base($n); return $level; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Math-PlanePath Nlast Xlo Xhi Xlo=-9 Xhi=+9 Ypeak Xlo,Xhi =head1 NAME Math::PlanePath::KochPeaks -- Koch curve peaks =head1 SYNOPSIS use Math::PlanePath::KochPeaks; my $path = Math::PlanePath::KochPeaks->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path traces out concentric peaks made from integer versions of the self-similar C at successively greater replication levels. 29 9 / \ 27----28 30----31 8 \ / 23 26 32 35 7 / \ / \ / \ 21----22 24----25 33----34 36----37 6 \ / 20 38 5 / \ 19----18 40----39 4 \ / 17 8 41 3 / / \ \ 15----16 6---- 7 9----10 42----43 2 \ \ / / 14 5 2 11 44 1 / / / \ \ \ 13 4 1 3 12 45 <- Y=0 ^ -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 ... The initial figure is the peak N=1,2,3 then for the next level each straight side expands to 3x longer with a notch in the middle like N=4 through N=8, * / \ *---* becomes *---* *---* The angle is maintained in each replacement so * / *---* \ * * / becomes / * * For example the segment N=1 to N=2 becomes N=4 to N=8, or in the next level N=5 to N=6 becomes N=17 to N=21. The X,Y coordinates are arranged as integers on a square grid. The result is flattened triangular segments with diagonals at a 45 degree angle. Unlike other triangular grid paths C uses the "odd" squares, with one of X,Y odd and the other even. This means the rotation formulas etc described in L don't apply directly. =head2 Level Ranges Counting the innermost N=1 to N=3 peak as level 0, each peak is Nstart = level + (2*4^level + 1)/3 Nend = level + (8*4^level + 1)/3 points = Nend-Nstart+1 = 2*4^level + 1 =for GP-DEFINE Nstart(k) = k + (2*4^k + 1)/3 =for GP-DEFINE Nend(k) = k + (8*4^k + 1)/3 =for GP-DEFINE points(k) = 2*4^k + 1 =for GP-Test vector(20,k,my(k=k-1); Nend(k)-Nstart(k)+1) == vector(20,k,my(k=k-1); points(k)) =for GP-Test 2+(2*4^2+1)/3 == 13 =for GP-Test Nstart(0) == 1 =for GP-Test Nend(0) == 3 =for GP-Test Nstart(2) == 13 =for GP-Test 2+(8*4^2+1)/3 == 45 =for GP-Test Nend(2) == 45 =for GP-Test points(2) == 33 =for GP-Test 2*4^2+1 == 33 =for GP-Test 45-13+1 == 33 For example the outer peak shown above is level 2 starting at Nstart=2+(2*4^2+1)/3=13 through to Nend=2+(8*4^2+1)/3=45 with points=2*4^2+1=33 inclusive (45-13+1=33). The X width at a given level is the endpoints at Xlo = -(3^level) Xhi = +(3^level) For example the level 2 above runs from Xlo=-9 to Xhi=+9. The highest Y is the centre peak half-way through the level at Ypeak = 3^level Npeak = level + (5*4^level + 1)/3 =for GP-DEFINE Npeak(k) = k + (5*4^k + 1)/3 =for GP-Test vector(20,k,my(k=k-1); (Nstart(k) + Nend(k))/2) == vector(20,k,my(k=k-1); Npeak(k)) =for GP-Test 2+(5*4^2+1)/3 == 29 =for GP-Test Npeak(2) == 29 For example the level 2 outer peak above is Ypeak=3^2=9 at N=2+(5*4^2+1)/3=29. For each level the Xlo,Xhi and Ypeak extents grow by a factor of 3. The triangular notches in each segment are not big enough to go past the Xlo and Xhi end points. The new triangular part can equal the ends, such as N=6 or N=19, but not go beyond. In general a segment like N=5 to N=6 which is at the Xlo end will expand to give two such segments and two points at the limit in the next level, as for example N=5 to N=6 expands to N=19,20 and N=20,21. So the count of points at Xlo doubles each time, CountLo = 2^level CountHi = 2^level same at Xhi =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::KochPeaks-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional C<$n> gives an X,Y position along a straight line between the integer positions. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return per L
above, ((2 * 4**$level + 1)/3 + $level, (8 * 4**$level + 1)/3 + $level) =back =head1 FORMULAS =head2 Rectangle to N Range The baseline for a given level is along a diagonal X+Y=3^level or -X+Y=3^level. The containing level can thus be found as level = floor(log3( Xmax + Ymax )) with Xmax as maximum absolute value, max(abs(X)) The endpoint in a level is simply 1 before the start of the next, so Nlast = Nstart(level+1) - 1 = (level+1) + (2*4^(level+1) + 1)/3 - 1 = level + (8*4^level + 1)/3 Using this Nlast is an over-estimate of the N range needed, but an easy calculation. It's not too difficult to work down for an exact range, by considering which parts of the curve might intersect a rectangle. But some backtracking and level descending is necessary because a rectangle might extend into the empty part of a notch and so be past its baseline but not intersect any. There's plenty of room for a rectangle to intersect nothing at all too. =head1 SEE ALSO L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/GosperSide.pm0000644000175000017500000003306513734026670017642 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # arms begin at 0,0 or at 1 in ? # math-image --path=GosperSide --lines --scale=10 # math-image --path=GosperSide --output=numbers package Math::PlanePath::GosperSide; use 5.004; use strict; use List::Util 'min','max'; use POSIX 'ceil'; use Math::PlanePath::GosperIslands; use Math::PlanePath::SacksSpiral; use vars '$VERSION', '@ISA', '@_xend','@_yend'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh'; # uncomment this to run the ### lines #use Devel::Comments; use constant n_start => 0; # secret experimental as yet ... # # use constant parameter_info_array => [ { name => 'arms', # share_key => 'arms_6', # type => 'integer', # minimum => 1, # maximum => 6, # default => 1, # width => 1, # description => 'Arms', # } ]; use constant x_negative_at_n => 113; use constant y_negative_at_n => 11357; use constant dx_minimum => -2; use constant dx_maximum => 2; use constant dy_minimum => -1; use constant dy_maximum => 1; *_UNDOCUMENTED__dxdy_list = \&Math::PlanePath::_UNDOCUMENTED__dxdy_list_six; # 2,0, # E N=0 # 1,1, # NE N=1 # -1,1, # NW N=4 # -2,0, # W N=13 # -1,-1, # SW N=40 # 1,-1, # SE N=121 use constant 1.02 _UNDOCUMENTED__dxdy_list_at_n => 121; use constant absdx_minimum => 1; use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(6, $self->{'arms'} || 1)); return $self; } sub n_to_xy { my ($self, $n) = @_; ### GosperSide n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $x; my $y = my $yend = ($n * 0); # inherit bignum 0 my $xend = $y + 2; # inherit bignum 2 { my $int = int($n); $x = 2 * ($n - $int); $n = $int; } if ((my $arms = $self->{'arms'}) > 1) { my $rot = _divrem_mutate ($n, $arms); if ($rot >= 3) { $rot -= 3; $x = -$x; # rotate 180, knowing y=0,yend=0 $xend = -2; } if ($rot == 1) { $x = $y = $x/2; # rotate +60, knowing y=0,yend=0 $xend = $yend = $xend/2; } elsif ($rot == 2) { $y = $x/2; # rotate +120, knowing y=0,yend=0 $x = -$y; $yend = $xend/2; $xend = -$yend; } } foreach my $digit (digit_split_lowtohigh($n,3)) { my $xend_offset = 3*($xend-$yend)/2; # end and end +60 my $yend_offset = ($xend+3*$yend)/2; ### at: "$x,$y" ### $digit ### $xend ### $yend ### $xend_offset ### $yend_offset if ($digit == 1) { ($x,$y) = (($x-3*$y)/2 + $xend, # rotate +60 ($x+$y)/2 + $yend); } elsif ($digit == 2) { $x += $xend_offset; # offset and offset +60 $y += $yend_offset; } $xend += $xend_offset; # offset and offset +60 $yend += $yend_offset; } ### final: "$x,$y" return ($x, $y); } # level = (log(hypot) + log(2*.99)) * 1/log(sqrt(7)) # = (log(hypot^2)/2 + log(2*.99)) * 1/log(sqrt(7)) # = (log(hypot^2) + 2*log(2*.99)) * 1/(2*log(sqrt(7))) # sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### GosperSide xy_to_n(): "$x, $y" if (($x ^ $y) & 1) { return undef; } my $h2 = $x*$x + $y*$y*3 + 1; my $level = max (0, ceil ((log($h2) + 2*log(2*.99)) * (1/2*log(sqrt(7))))); if (is_infinite($level)) { return $level; } return Math::PlanePath::GosperIslands::_xy_to_n_in_level($x,$y,$level); } # Points beyond N=3^level only go a small distance back before that N # hypotenuse. # hypot = .99 * 2 * sqrt(7)^level # sqrt(7)^level = hypot / (2*.99) # sqrt(7)^level = hypot / (2*.99) # level = log(hypot / (2*.99)) / log(sqrt(7)) # = (log(hypot) + log(2*.99)) * 1/log(sqrt(7)) # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $y1 *= sqrt(3); $y2 *= sqrt(3); my ($r_lo, $r_hi) = Math::PlanePath::SacksSpiral::_rect_to_radius_range ($x1,$y1, $x2,$y2); my $level = max (0, ceil ((log($r_hi+.1) + log(2*.99)) * (1/log(sqrt(7))))); return (0, $self->{'arms'} * 3 ** $level - 1); } #------------------------------------------------------------------------------ # levels use Math::PlanePath::SierpinskiArrowhead; *level_to_n_range = \&Math::PlanePath::SierpinskiArrowhead::level_to_n_range; *n_to_level = \&Math::PlanePath::SierpinskiArrowhead::n_to_level; #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Math-PlanePath Gosper OEIS terdragon =head1 NAME Math::PlanePath::GosperSide -- one side of the Gosper island =head1 SYNOPSIS use Math::PlanePath::GosperSide; my $path = Math::PlanePath::GosperSide->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis path is a single side of the Gosper island, in integers (L). 20-... 14 / 18----19 13 / 17 12 \ 16 11 / 15 10 \ 14----13 9 \ 12 8 / 11 7 \ 10 6 / 8---- 9 5 / 6---- 7 4 / 5 3 \ 4 2 / 2---- 3 1 / 0---- 1 <- Y=0 ^ X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 ... The path slowly spirals around counter clockwise, with a lot of wiggling in between. The N=3^level point is at N = 3^level angle = level * atan(sqrt(3)/5) = level * 19.106 degrees radius = sqrt(7) ^ level A full revolution for example takes roughly level=19 which is about N=1,162,000,000. Both ends of such levels are in fact sub-spirals, like an "S" shape. The path is both the sides and the radial spokes of the C path, as described in L. Each N=3^level point is the start of a C ring. The path is the same as the C except the turns here are by 60 degrees each, whereas C is by 120 degrees. See L for the turn sequence and total direction formulas etc. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::GosperSide-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional C<$n> gives a point on the straight line between integer N. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 3**$level)>. =back =head1 FORMULAS =head2 Level Endpoint The endpoint of each level N=3^k is at X + Y*i*sqrt(3) = b^k where b = 2 + w = 5/2 + sqrt(3)/2*i where w=1/2 + sqrt(3)/2*i sixth root of unity X(k) = ( 5*X(k-1) - 3*Y(k-1) )/2 for k>=1 Y(k) = ( X(k-1) + 5*Y(k-1) )/2 starting X(0)=2 Y(0)=0 X(k) = 5*X(k-1) - 7*X(k-2) for k>=2 starting X(0)=2 X(1)=5 = 2, 5, 11, 20, 23, -25, -286, -1255, -4273, -12580, -32989,.. Y(k) = 5*Y(k-1) - Y*X(k-2) for k>=2 starting Y(0)=0 Y(1)=1 = 0, 1, 5, 18, 55, 149, 360, 757, 1265, 1026, -3725, ... (A099450) =for GP-DEFINE X(k) = if(k==0,2, k==1,5, 5*X(k-1)-7*X(k-2)) =for GP-DEFINE Y(k) = if(k==0,0, k==1,1, 5*Y(k-1)-7*Y(k-2)) =for GP-DEFINE X_samples = [ 2, 5, 11, 20, 23, -25, -286, -1255, -4273, -12580, -32989 ] =for GP-DEFINE Y_samples = [ 0, 1, 5, 18, 55, 149, 360, 757, 1265, 1026, -3725 ] =for GP-Test vector(length(X_samples),k,my(k=k-1); X(k)) == X_samples =for GP-Test vector(length(Y_samples),k,my(k=k-1); Y(k)) == Y_samples =for GP-Test vector(20,k, X(k)) == vector(20,k, (5*X(k-1)-3*Y(k-1))/2) /* k>=1 */ =for GP-Test vector(20,k, Y(k)) == vector(20,k, (X(k-1)+5*Y(k-1))/2) /* k>=1 */ The curve base figure is XY(k)=XY(k-1)+rot60(XY(k-1))+XY(k-1) giving XY(k) = (2+w)^k = b^k where w is the sixth root of unity giving the rotation by +60 degrees. The mutual recurrences are similar with the rotation done by (X-3Y)/2, (Y+X)/2 per L. The separate recurrences are found by using the first to get Y(k-1) = -2/3*X(k) + 5/3*X(k-1) and substitute into the other to get X(k+1). Similar the other way around for Y(k+1). =cut # w=1/2 + sqrt(3)/2*I; b = 2+w # nearly(x) = if(abs(x-round(x))<1e-10,round(x),x) # Y(k) = nearly(2*imag(b^k/sqrt(3))) # vector(20,k,my(k=k-1); Y(k)) # 0, 1, 5, 18, 55, 149, 360, 757, 1265, 1026, -3725, -25807, -102960, -334151, # A099450 Expansion of 1/(1-5x+7x^2). # X(k) = nearly(2*real(b^k)) # vector(20,k,my(k=k-1); X(k)) # 2, 5, 11, 20, 23, -25, -286, -1255, -4273, -12580, -32989, -76885, -153502, # *---* 2+w # / # *---* # X+I*Y = a+b*w # = a+b/2 + sqrt(3)/2*b*i # X = a+b/2 Y=sqrt(3)/2*b # b=2*Y/sqrt(3) # a = X - b = X - 2*Y/sqrt(3) # # a(k) = X(k) - 2*Y(k) # vector(20,k,my(k=k-1); a(k)) # 2, 3, 1, -16, -87, -323, -1006, -2769, -6803, -14632, -25539, -25271, 52418, # XY(k) = 2*XY(k-1) + rot60 XY(k-1) # X(k) = 2*X(k-1) + (X(k-1) - 3*Y(k-1))/2 # Y(k) = 2*Y(k-1) + (X(k-1) + Y(k-1))/2 # # X(k) = (5*X(k-1) - 3*Y(k-1))/2 # Y(k) = ( X(k-1) + 5*Y(k-1))/2 # 2 5 11 # 0 1 5 # # sep # Y(k-1) = -2/3*X(k) + 5/3*X(k-1) # X(k-1) = 2 *Y(k) - 5 *Y(k-1) # # subst Y # Y(k) = ( X(k-1) + 5*Y(k-1))/2 # -2/3*X(k+1) + 5/3*X(k) = ( X(k-1) + 5*(-2/3*X(k) + 5/3*X(k-1)))/2 # -2/3*X(k+1) + 5/3*X(k) = 1/2*X(k-1) + -5/2*2/3*X(k) + 5/2*5/3*X(k-1) # -2/3*X(k+1) + 10/3*X(k) = 14/3*X(k-1) # X(k+1) = 5*X(k) - 7*X(k-1) # # subst X # X(k) = (5*X(k-1) - 3*Y(k-1))/2 # 2 *Y(k+1) - 5 *Y(k) = (5*(2 *Y(k) - 5 *Y(k-1)) - 3*Y(k-1))/2 # 2 *Y(k+1) - 5 *Y(k) = 5*2/2 *Y(k) - 5*5/2 *Y(k-1) - 3/2*Y(k-1) # 2 *Y(k+1) = 10*Y(k) - 14*Y(k-1) # Y(k+1) = 5*Y(k) - 7*Y(k-1) # 5*5-7=18 =pod =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A229215 direction 1,2,3,-1,-2,-3 (clockwise) A099450 Y at N=3^k (for k>=1) Also the turn sequence is the same as the terdragon curve, see L for the several turn forms, N positions of turns, etc. =head1 SEE ALSO L, L, L, L L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/FilledRings.pm0000644000175000017500000003336013734026671017777 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=FilledRings --all --output=numbers_dash --size=70x30 # # offset 0 to 1 # same PixelRings fractional offset for midpoint # # default 0.5 |-------int-------| 0.5 # |------------------| 0 # |------------------| 0.99 or 1.0 # # default 0 |-------int-------|--------int-------| # |------------------| 0.5 # |------------------| 0.99 or 1.0 # # innermost # |-------0-------|-------1--------| # # # offset -0.5 to +0.5 # h-0.5 < R < h+0.5 # h-0.5 <= R-0.5 < h+0.5 # h-0.5 < R+0.5 <= h+0.5 # A036702 count Gaussian |z| <= n # A036706 count Gaussian n-1/2 < |z| < n+1/2 with a>0,b>=0, so 1/4 # A036707 count Gaussian |z| < n+1/2 with b>=0, so 1/2 plane # A036708 count Gaussian n-1/2 < |z| < n+1/2 with b>=0, so 1/4 # # A000328 num points <= circle radius n # 1, 5, 13, 29, 49, 81, 113, 149, 197, 253, 317, 377, 441, # A046109 num points == circle radius n # A051132 num points < circle radius n # 0, 1, 9, 25, 45, 69, 109, 145, 193, 249, 305, 373, 437, # A057655 num points x^2+y^2 <= n # 1, 5, 9, 9, 13, 21, 21, 21, 25, 29, 37, 37, 37, 45, 45, # package Math::PlanePath::FilledRings; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem = \&Math::PlanePath::_divrem; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::SacksSpiral; *_rect_to_radius_corners = \&Math::PlanePath::SacksSpiral::_rect_to_radius_corners; # uncomment this to run the ### lines #use Smart::Comments; # N(r) = 1 + 4*sum floor(r^2/(4i+1)) - floor(r^2/(4i+3)) # # N(r+1) - N(r) # = 1 + 4*sum floor((r+1)^2/(4i+1)) - floor((r+1)^2/(4i+3)) # - 1 + 4*sum floor(r^2/(4i+1)) - floor(r^2/(4i+3)) # = 4*sum floor(((r+1)^2-r^2)/(4i+1)) - floor(((r+1)^2-r^2)/(4i+3)) # = 4*sum floor((2r+1)/(4i+1)) - floor((2r+1)/(4i+3)) # # _cumul[0] index=0 is r=1/2 # r = index+1/2 # 2r+1 = 2(index+1/2)+1 # = 2*index+1+1 # = 2*index+2 # # 2r+1 >= 4i+1 # 2r >= 4i # i <= (2*index+2)/2 # i <= index+1 # # r=3.5 # sqrt(3*3+3*3) = 4.24 out # sqrt(3*3+2*2) = 3.60 out # sqrt(3*3+1*1) = 3.16 in # # * * * # * * * * * # * * * * * * * # * * * o * * * 3+5+7+7+7+5+3 = 37 # * * * * * * * # * * * * * # * * * # # N(r) = 1 + 4*( floor(12.25/1)-floor(12.25/3) # + floor(12.25/5)-floor(12.25/7) # + floor(12.25/9)-floor(12.25/11) ) # = 37 # # (index+1/2)^2 = index^2 + index + 1/4 # >= index*(index+1) # (end+1 + 1/2)^2 # = (end+3/2)^2 # = end^2 + 3*end + 9/4 # = end*(end+3) + 2 + 1/4 # # (r+1/2)^2 = r^2+r+1/4 floor=r*(r+1) # (r-1/2)^2 = r^2-r+1/4 ceil=r*(r-1)+1 use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; use constant n_frac_discontinuity => 0; use constant xy_is_visited => 1; use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; sub x_negative_at_n { my ($self) = @_; return $self->n_start + 4; } sub y_negative_at_n { my ($self) = @_; return $self->n_start + 6; } *_UNDOCUMENTED__dxdy_list = \&Math::PlanePath::_UNDOCUMENTED__dxdy_list_eight; use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; return $self->n_start + 40; } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); # parameters if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } # internals $self->{'cumul'} = [ 1 ]; # N=0 basis $self->{'offset'} ||= 0; return $self; } sub _cumul_extend { my ($self) = @_; ### _cumul_extend() ... my $cumul = $self->{'cumul'}; my $r2 = ($#$cumul + 3) * $#$cumul + 2; my $c = 0; for (my $d = 1; $d <= $r2; $d += 4) { $c += int($r2/$d) - int($r2/($d+2)); } push @$cumul, 4*$c + 1; ### $cumul } sub n_to_xy { my ($self, $n) = @_; ### FilledRings n_to_xy(): $n $n = $n - $self->{'n_start'}; # to N=0 basis, warning if $n==undef if ($n <= 1) { if ($n < 0) { return; } else { return ($n, 0); # 0<=N<=1 } } if (is_infinite($n)) { return ($n,$n); } { # ENHANCE-ME: direction of N+1 from the cumulative lookup my $int = int($n); if ($n != $int) { my $frac = $n - $int; my ($x1,$y1) = $self->n_to_xy($int + $self->{'n_start'}); my ($x2,$y2) = $self->n_to_xy($int+1 + $self->{'n_start'}); if ($y2 == 0 && $x2 > 0) { $x2 -= 1; } my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } ### search cumul for: "n=$n" my $cumul = $self->{'cumul'}; my $r = 1; for (;;) { if ($r > $#$cumul) { _cumul_extend ($self); } if ($cumul->[$r] > $n) { last; } $r++; } ### $r $n -= $cumul->[$r-1]; my $len = $cumul->[$r] - $cumul->[$r-1]; # length of this ring ### cumul: "$cumul->[$r-1] to $cumul->[$r]" ### $len ### n rem: $n $len /= 4; # length of a quadrant of this ring (my $quadrant, $n) = _divrem ($n, $len); ### len of quadrant: $len ### $quadrant ### n into quadrant: $n ### assert: $quadrant >= 0 ### assert: $quadrant < 4 my $rev; if ($rev = ($n > $len/2)) { $n = $len - $n; } ### $rev ### $n # my $rhi = ($r+1)*$r; # my $rlo = ($r-1)*$r+1; my $rlo = ($r-0.5+$self->{'offset'})**2; my $rhi = ($r+0.5+$self->{'offset'})**2; my $x = $r; my $y = 0; while ($n > 0) { ### at: "$x,$y n=$n" $y++; ### inc y to: $y if ($x*$x + $y*$y > $rhi) { $x--; ### dec x to: $x ### assert: $x*$x + $y*$y <= $rhi ### assert: $x*$x + $y*$y >= $rlo } $n--; last if $n <= 0; if (($x-1)*($x-1) + $y*$y >= $rlo) { ### another dec x to: $x $x--; $n--; last if $n <= 0; } } # if ($n) { # ### n frac: $n # } if ($rev) { ($x,$y) = ($y,$x); } if ($quadrant & 2) { $x = -$x; $y = -$y; } if ($quadrant & 1) { ($x,$y) = (-$y, $x); } ### return: "$x, $y" return ($x, $y); } # h=x^2+y^2 # h >= (r-1/2)^2 # sqrt(h) >= r-1/2 # sqrt(h)+1/2 >= r # r = int (sqrt(h)+1/2) # = int( (2*sqrt(h)+1)/2 } # = int( (sqrt(4*h) + 1)/2 } sub xy_to_n { my ($self, $x, $y) = @_; ### FilledRings xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x == 0 && $y == 0) { return $self->{'n_start'}; } my $r = int ((sqrt(4*($x*$x+$y*$y)) + 1) / 2); ### $r if (is_infinite($r)) { return undef; } my $cumul = $self->{'cumul'}; while ($#$cumul < $r) { _cumul_extend ($self); } my $n = $cumul->[$r-1]; ### n base: $n my $len = $cumul->[$r] - $n; ### $len $len /= 4; ### len/4: $len if ($y < 0) { ### y neg, rotate 180 $y = -$y; $x = -$x; $n += 2*$len; } if ($x < 0) { $n += $len; ($x,$y) = ($y,-$x); ### neg x, rotate 90 ### n base now: $n } ### assert: $x >= 0 ### assert: $y >= 0 my $rev; if ($rev = ($x < $y)) { ### top octant, reverse: "x=$x len/4=".($len/4)." gives ".($len/4 - $x) ($x,$y) = ($y,$x); } my $offset = 0; my $rhi = ($r+1)*$r; my $rlo = ($r-1)*$r+1; ### assert: $x*$x + $y*$y <= $rhi ### assert: $x*$x + $y*$y >= $rlo my $tx = $r; my $ty = 0; while ($ty < $y) { ### at: "$tx,$ty offset=$offset" $ty++; ### inc ty to: $ty if ($tx*$tx + $ty*$ty > $rhi) { $tx--; ### dec tx to: $tx ### assert: $tx*$tx + $ty*$ty <= $rhi ### assert: $tx*$tx + $ty*$ty >= $rlo } $offset++; last if $x == $tx && $y == $ty; if (($tx-1)*($tx-1) + $ty*$ty >= $rlo) { ### another dec tx to: "tx=$tx" $tx--; $offset++; last if $y == $ty; } } $n += $self->{'n_start'}; if ($rev) { return $n + $len - $offset; } else { return $n + $offset; } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### FilledRings rect_to_n_range(): "$x1,$y1 $x2,$y2" ($x1,$y1, $x2,$y2) = _rect_to_radius_corners ($x1,$y1, $x2,$y2); ### radius range: "$x1,$y1 $x2,$y2" if ($x1 >= 1) { $x1 -= 1; } if ($y1 >= 1) { $y1 -= 1; } $x2 += 1; $y2 += 1; return (int((21*($x1*$x1 + $y1*$y1)) / 7) + $self->{'n_start'}, int((22*($x2*$x2 + $y2*$y2)) / 7) + $self->{'n_start'} - 1); } 1; __END__ =for stopwords Ryde Math-PlanePath OEIS =head1 NAME Math::PlanePath::FilledRings -- concentric filled lattice rings =head1 SYNOPSIS use Math::PlanePath::FilledRings; my $path = Math::PlanePath::FilledRings->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path puts points on integer X,Y pixels of filled rings with radius 1 unit each ring. 110-109-108-107-106 6 / \ 112-111 79--78--77--76--75 105-104 5 | / \ | 114-113 80 48--47--46--45--44 74 103-102 4 | / | | \ | 115 81 50--49 27--26--25 43--42 73 101 3 / / | / \ | \ \ 116 82 52--51 28 14--13--12 24 41--40 72 100 2 | | | / / \ \ | | | 117 83 53 29 15 5-- 4-- 3 11 23 39 71 99 1 | | | | | | | | | | | | 118 84 54 30 16 6 1-- 2 10 22 38 70 98 <- Y=0 | | | | | | / / / / / / 119 85 55 31 17 7-- 8-- 9 21 37 69 97 137 -1 | | | \ \ / / | | | 120 86 56--57 32 18--19--20 36 67--68 96 136 -2 \ \ | \ / | / / 121 87 58--59 33--34--35 65--66 95 135 -3 | \ | | / | 122-123 88 60--61--62--63--64 94 133-134 -4 | \ / | 124-125 89--90--91--92--93 131-132 -5 \ / 126-127-128-129-130 ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 For example the ring N=22 to N=37 is all the points 2.5 < hypot(X,Y) < 3.5 where hypot(X,Y) = sqrt(X^2+Y^2) =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start with the same shape. For example to start at 0, =cut # math-image --path=FilledRings,n_start=0 --all --output=numbers_dash --size=37x16 =pod 26-25-24 n_start => 0 / \ 27 13-12-11 23 / / \ \ 28 14 4--3--2 10 22 | | | | | | 29 15 5 0--1 9 21 | | | / / / 30 16 6--7--8 20 36 \ \ / / 31 17-18-19 35 \ / 8 32-33-34 The only effect is to push the N values by a constant amount but can help match N on the axes to counts of X,Y points E R or similar. =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::FilledRings-Enew ()> =item C<$path = Math::PlanePath::FilledRings-Enew (n_start =E $n)> Create and return a new path object. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include, =over L (etc) =back A036705 first diffs of N on X axis, being count of X,Y points n-1/2 < norm <= n+1/2 A036706 1/4 of those diffs n_start=1 (the default) A036707 N/2+X-1 along X axis, being count norm <= n+1/2 in half plane A036708 (N(X,0)-N(X-1,0))/2+1, first diffs of the half plane count n_start=0 A036704 N on X axis, from X=1 onwards count of X,Y points norm <= n+1/2 =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/TriangleSpiralSkewed.pm0000644000175000017500000004217113734026654021661 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::TriangleSpiralSkewed; use 5.004; use strict; #use List::Util 'max','min'; *max = \&Math::PlanePath::_max; *min = \&Math::PlanePath::_min; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; *_sqrtint = \&Math::PlanePath::_sqrtint; # uncomment this to run the ### lines # use Smart::Comments; use constant xy_is_visited => 1; use constant parameter_info_array => [ { name => 'skew', type => 'enum', share_key => 'skew_lrud', display => 'Skew', default => 'left', choices => ['left', 'right','up','down' ], choices_display => ['Left', 'Right','Up','Down' ], }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; { my %x_negative_at_n = (left => 3, right => 5, up => 3, down => 5); sub x_negative_at_n { my ($self) = @_; return $self->n_start + $x_negative_at_n{$self->{'skew'}}; } } { my %y_negative_at_n = (left => 6, right => 6, up => 5, down => 1); sub y_negative_at_n { my ($self) = @_; return $self->n_start + $y_negative_at_n{$self->{'skew'}}; } } use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; { my %_UNDOCUMENTED__dxdy_list = (left => [1,0, # E -1,1, # NW 0,-1], # S right => [1,0, # E 0,1, # N -1,-1], # SW up => [1,1, # NE -1,0, # W 0,-1], # S down => [0,1, # N -1,0, # W 1,-1], # SE ); sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; return @{$_UNDOCUMENTED__dxdy_list{$self->{'skew'}}}; } } { my %dsumxy_minimum = (left => -1, # diagonal only NW across right => -2, # SW up => -1, # S down => -1); # W sub dsumxy_minimum { my ($self) = @_; return $dsumxy_minimum{$self->{'skew'}}; } } { my %dsumxy_maximum = (left => 1, # E right => 1, # N up => 2, # NE down => 1); # N sub dsumxy_maximum { my ($self) = @_; return $dsumxy_maximum{$self->{'skew'}}; } } { my %ddiffxy_minimum = (left => -2, # North-West right => -1, # N up => -1, # W down => -1); # W sub ddiffxy_minimum { my ($self) = @_; return $ddiffxy_minimum{$self->{'skew'}}; } } { my %ddiffxy_maximum = (left => 1, # S right => 1, # S up => 1, # S down => 2); # South-East sub ddiffxy_maximum { my ($self) = @_; return $ddiffxy_maximum{$self->{'skew'}}; } } { my %dir_minimum_dxdy = (left => [1,0], # East right => [1,0], # East up => [1,1], # NE down => [0,1]); # North sub dir_minimum_dxdy { my ($self) = @_; return @{$dir_minimum_dxdy{$self->{'skew'}}}; } } { my %dir_maximum_dxdy = (left => [0,-1], # South right => [-1,-1], # South-West up => [0,-1], # South down => [1,-1]); # South-East sub dir_maximum_dxdy { my ($self) = @_; return @{$dir_maximum_dxdy{$self->{'skew'}}}; } } use constant turn_any_right => 0; # only left or straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } $self->{'skew'} ||= 'left'; return $self; } # base at bottom left corner, N=0 basis, first loop d=1 # d = [ 1, 2, 3 ] # n = [ 0, 6, 21 ] # d = 5/6 + sqrt(2/9 * $n + 1/36) # = (5 + sqrt(8N + 1))/6 # N = (9/2 d^2 - 15/2 d + 3) # = (9/2*$d**2 - 15/2*$d + 3) # = ((9/2*$d - 15/2)*$d + 3) # = (9*$d - 15)*$d/2 + 3 # # bottom right corner is further 3*$d along, so # rem = $n - (9/2 d^2 - 15/2 d + 3) - 3*d # = $n - (9/2 d^2 - 9/2 d + 3) # = $n - (9/2*$d + -9/2)*$d - 3 # = $n - (9*$d + -9)*$d/2 - 3 # = $n - ($d - 1)*$d*9/2 - 3 # is rem < 0 bottom horizontal # rem <= 3*d-1 right slope # rem >= 3*d-1 left vertical # sub n_to_xy { my ($self, $n) = @_; #### TriangleSpiralSkewed n_to_xy: $n $n = $n - $self->{'n_start'}; # starting N==0, and warning if $n==undef if ($n < 0) { return; } my $d = int((_sqrtint(8*$n + 1) + 5) / 6); # first loop d=1 at n=0 #### $d $n -= ($d-1)*$d/2 * 9; #### remainder: $n my $zero = $n*0; # inherit BigFloat frac rather than $d=BigInt my ($x,$y); if ($n <= 1) { ### bottom horizontal: "nrem=$n" $d -= 1; $y = $zero - $d; $x = $n + 2*$d; } elsif (($n -= 3*$d) <= 0) { ### right slope: "nrem=$n" $x = -$n - $d; $y = $n + 2*$d; } else { ### left vertical: "nrem=$n" $x = $zero - $d; $y = - $n + 2*$d; } ### xy skew=left: "$x,$y" if ($self->{'skew'} eq 'right') { $x += $y; } elsif ($self->{'skew'} eq 'up') { $y += $x; } elsif ($self->{'skew'} eq 'down') { ($x,$y) = ($x+$y, -$x); } return ($x,$y); } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### xy_to_n(): "$x,$y" if ($self->{'skew'} eq 'right') { $x -= $y; } elsif ($self->{'skew'} eq 'up') { $y -= $x; } elsif ($self->{'skew'} eq 'down') { ($x,$y) = (-$y, $x+$y); } # now $x,$y in skew="left" style my $n; if ($y < 0 && $y <= $x && $x <= -2*$y) { ### bottom horizontal ... # negative y, vertical at x=0 # [ -1, -2, -3, -4 ] # [ 8, 24, 49, 83 ] # n = (9/2*$d**2 + -5/2*$d + 1) # $n = (9*$y - 5)*$y/2 + $x; } elsif ($x < 0 && $x <= $y && $y <= -2*$x) { ### upper left vertical ... # negative x, horizontal at y=0 # [ -1, -2, -3, -4 ] # [ 6, 20, 43, 75 ] # n = (9/2*$d**2 + -1/2*$d + 1) # $n = (9*$x - 1)*$x/2 - $y; } else { my $d = $x + $y; ### upper right slope ... ### $d # positive y, vertical at x=0 # [ 1, 2, 3, 4 ] # [ 3, 14, 34, 63 ] # n = (9/2*$d**2 + -5/2*$d + 1) # $n = (9*$d - 5)*$d/2 - $x; } return $n + $self->{'n_start'}; } # n_hi exact, n_lo not # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); return ($self->{'n_start'}, max ($self->xy_to_n ($x1,$y1), $self->xy_to_n ($x1,$y2), $self->xy_to_n ($x2,$y1), $self->xy_to_n ($x2,$y2))); } # my $d = 0; # foreach my $x ($x1, $x2) { # foreach my $y ($y1, $y2) { # $d = max ($d, # 1 + ($y < 0 && $y <= $x && $x <= -2*$y # ? -$y # bottom horizontal # : $x < 0 && $x <= $y && $y <= 2*-$x # ? -$x # left vertical # : abs($x) + $y)); # right slope # } # } # (9*$d - 9)*$d + 1 + $self->{'n_start'}); 1; __END__ =for stopwords Ryde Math-PlanePath 11-gonals hendecagonal hendecagonals OEIS =head1 NAME Math::PlanePath::TriangleSpiralSkewed -- integer points drawn around a skewed equilateral triangle =head1 SYNOPSIS use Math::PlanePath::TriangleSpiralSkewed; my $path = Math::PlanePath::TriangleSpiralSkewed->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes an spiral shaped as an equilateral triangle (each side the same length), but skewed to the left to fit on a square grid, =cut # math-image --path=TriangleSpiralSkewed --expression='i<=31?i:0' --output=numbers_dash =pod 16 4 |\ 17 15 3 | \ 18 4 14 2 | |\ \ 19 5 3 13 1 | | \ \ 20 6 1--2 12 ... <- Y=0 | | \ \ 21 7--8--9-10-11 30 -1 | \ 22-23-24-25-26-27-28-29 -2 ^ -2 -1 X=0 1 2 3 4 5 The properties are the same as the spread-out C. The triangle numbers fall on straight lines as they do in the C but the skew means the top corner goes up at an angle to the vertical and the left and right downwards are different angles plotted (but are symmetric by N count). =head2 Skew Right Option C 'right'> directs the skew towards the right, giving =cut # math-image --path=TriangleSpiralSkewed,skew=right --expression='i<=31?i:0' --output=numbers_dash =pod 4 16 skew="right" / | 3 17 15 / | 2 18 4 14 / / | | 1 ... 5 3 13 / | | Y=0 -> 6 1--2 12 / | -1 7--8--9-10-11 ^ -2 -1 X=0 1 2 This is a shear "X -E X+Y" of the default skew="left" shown above. The coordinates are related by Xright = Xleft + Yleft Xleft = Xright - Yright Yright = Yleft Yleft = Yright =head2 Skew Up =cut # math-image --path=TriangleSpiralSkewed,skew=up --expression='i<=31?i:0' --output=numbers_dash =pod 2 16-15-14-13-12-11 skew="up" | / 1 17 4--3--2 10 | | / / Y=0 -> 18 5 1 9 | | / -1 ... 6 8 |/ -2 7 ^ -2 -1 X=0 1 2 This is a shear "Y -E X+Y" of the default skew="left" shown above. The coordinates are related by Xup = Xleft Xleft = Xup Yup = Yleft + Xleft Yleft = Yup - Xup =head2 Skew Down =cut # math-image --path=TriangleSpiralSkewed,skew=down --expression='i<=31?i:0' --output=numbers_dash =pod 2 ..-18-17-16 skew="down" | 1 7--6--5--4 15 \ | | Y=0 -> 8 1 3 14 \ \ | | -1 9 2 13 \ | -2 10 12 \ | 11 ^ -2 -1 X=0 1 2 This is a rotate by -90 degrees of the skew="up" above. The coordinates are related Xdown = Yup Xup = - Ydown Ydown = - Xup Yup = Xdown Or related to the default skew="left" by Xdown = Yleft + Xleft Xleft = - Ydown Ydown = - Xleft Yleft = Xdown + Ydown =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, with the same shape etc. For example to start at 0, =cut # math-image --path=TriangleSpiralSkewed,n_start=0 --expression='i<=31?i:0' --output=numbers_dash =pod 15 n_start => 0 |\ 16 14 | \ 17 3 13 ... | |\ \ \ 18 4 2 12 31 | | \ \ \ 19 5 0--1 11 30 | | \ \ 20 6--7--8--9-10 29 | \ 21-22-23-24-25-26-27-28 With this adjustment for example the X axis N=0,1,11,30,etc is (9X-7)*X/2, the hendecagonal numbers (11-gonals). And South-East N=0,8,25,etc is the hendecagonals of the second kind, (9Y-7)*Y/2 with Y negative. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::TriangleSpiralSkewed-Enew ()> =item C<$path = Math::PlanePath::TriangleSpiralSkewed-Enew (skew =E $str, n_start =E $n)> Create and return a new skewed triangle spiral object. The C parameter can be "left" (the default) "right" "up" "down" =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each N in the path as centred in a square of side 1, so the entire plane is covered. =back =head1 FORMULAS =head2 Rectangle to N Range Within each row there's a minimum N and the N values then increase monotonically away from that minimum point. Likewise in each column. This means in a rectangle the maximum N is at one of the four corners of the rectangle. | x1,y2 M---|----M x2,y2 maximum N at one of | | | the four corners -------O--------- of the rectangle | | | | | | x1,y1 M---|----M x1,y1 | =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back n_start=1, skew="left" (the defaults) A204439 abs(dX) A204437 abs(dY) A010054 turn 1=left,0=straight, extra initial 1 A117625 N on X axis A064226 N on Y axis, but without initial value=1 A006137 N on X negative A064225 N on Y negative A081589 N on X=Y leading diagonal A038764 N on X=Y negative South-West diagonal A081267 N on X=-Y negative South-East diagonal A060544 N on ESE slope dX=+2,dY=-1 A081272 N on SSE slope dX=+1,dY=-2 A217010 permutation N values of points in SquareSpiral order A217291 inverse A214230 sum of 8 surrounding N A214231 sum of 4 surrounding N n_start=0 A051682 N on X axis (11-gonal numbers) A081268 N on X=1 vertical (next to Y axis) A062708 N on Y axis A062725 N on Y negative axis A081275 N on X=Y+1 North-East diagonal A062728 N on South-East diagonal (11-gonal second kind) A081266 N on X=Y negative South-West diagonal A081270 N on X=1-Y North-West diagonal, starting N=3 A081271 N on dX=-1,dY=2 NNW slope up from N=1 at X=1,Y=0 n_start=-1 A023531 turn 1=left,0=straight, being 1 at N=k*(k+3)/2 A023532 turn 1=straight,0=left n_start=1, skew="right" A204435 abs(dX) A204437 abs(dY) A217011 permutation N values of points in SquareSpiral order but with 90-degree rotation A217292 inverse A214251 sum of 8 surrounding N n_start=1, skew="up" A204439 abs(dX) A204435 abs(dY) A217012 permutation N values of points in SquareSpiral order but with 90-degree rotation A217293 inverse A214252 sum of 8 surrounding N n_start=1, skew="down" A204435 abs(dX) A204439 abs(dY) The square spiral order in A217011,A217012 and their inverses has first step at 90-degrees to the first step of the triangle spiral, hence the rotation by 90 degrees when relating to the C path. A217010 on the other hand has no such rotation since it reckons the square and triangle spirals starting in the same direction. =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/File.pm0000644000175000017500000001675313734026671016463 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # maybe file type for sequence of turns instead of x,y package Math::PlanePath::File; use 5.004; use strict; use Carp 'croak'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest', 'is_infinite'; # uncomment this to run the ### lines #use Devel::Comments; sub n_start { my ($self) = @_; if (ref $self) { _read($self); } return $self->SUPER::n_start; } sub x_negative { return _read($_[0])->{'x_negative'} } sub y_negative { return _read($_[0])->{'y_negative'} } sub figure { return _read($_[0])->{'figure'} } use constant parameter_info_array => [ { name => 'filename', display => 'Filename', type => 'filename', width => 40, default => '', description => 'File name to read.', } ]; sub n_to_xy { my ($self, $n) = @_; if ($n < $self->{'n_start'}) { return; } if (is_infinite($n)) { return; } if (defined (my $x = _read($self)->{'x_array'}->[$n])) { return ($x, $self->{'y_array'}->[$n]); } return; } sub xy_to_n { my ($self, $x, $y) = @_; # lazy xy_hash creation if (! defined $self->{'xy_hash'}) { my %xy_hash; _read($self)->{'xy_hash'} = \%xy_hash; my $x_array = $self->{'x_array'}; my $y_array = $self->{'y_array'}; for (my $n = 0; $n <= $#$x_array; $n++) { if (defined (my $nx = $x_array->[$n])) { $xy_hash{"$nx,$y_array->[$n]"} = $n; # && $nx == int($nx) # if ($ny == int($ny)) { # } } } } { my $key = ($self->{'figure'} eq 'square' ? round_nearest($x).','.round_nearest($y) : "$x,$y"); if (defined (my $n = _read($self)->{'xy_hash'}->{$key})) { return $n; } } my $x_array = $self->{'x_array'}; my $y_array = $self->{'y_array'}; for (my $n = 0; $n <= $#$x_array; $n++) { defined (my $nx = $x_array->[$n]) or next; my $ny = $y_array->[$n]; if (($x-$nx)**2 + ($y-$ny)**2 <= .25) { return $n; } } return undef; } # exact sub rect_to_n_range { my ($self) = @_; _read($self); return ($self->{'n_start'}, $self->{'n_last'}); } my $num = "-?(?:\\.[0-9]+|[0-9]+(?:\\.[0-9]*)?)(?:[eE]-?[0-9]+)?"; sub _read { my ($self) = @_; if (defined $self->{'n_start'}) { return $self; } my $n = 1; $self->{'n_start'} = $n; $self->{'n_last'} = $n-1; # default no range $self->{'x_negative'} = 0; $self->{'y_negative'} = 0; $self->{'figure'} = 'square'; my $filename = $self->{'filename'}; if (! defined $filename || $filename =~ /^\s*$/) { return $self; } my $fh; ($] >= 5.006 ? open $fh, '<', $filename : open $fh, "< $filename") or croak "Cannot open ",$filename,": ",$!; my $n_start; my @x_array; my @y_array; my $x_negative = 0; my $y_negative = 0; my $any_frac = 0; while (my $line = <$fh>) { $line =~ /^\s*-?\.?[0-9]/ or next; $line =~ /^\s*($num)[ \t,]+($num)([ \t,]+($num))?/o or do { warn $filename,':',$.,": File unrecognised line: ",$line; next; }; my ($x,$y); if (defined $4) { $n = $1; $x = $2; $y = $4; } else { $x = $1; $y = $2; } $x_array[$n] = $x; $y_array[$n] = $y; $x_negative ||= ($x < 0); $y_negative ||= ($y < 0); $any_frac ||= ($x != int($x) || $y != int($y)); if (! defined $n_start || $n < $n_start) { $n_start = $n; } ### $x ### $y $n++; } close $fh or croak "Error closing ",$filename,": ",$!; $self->{'x_array'} = \@x_array; $self->{'y_array'} = \@y_array; $self->{'x_negative'} = $x_negative; $self->{'y_negative'} = $y_negative; $self->{'n_start'} = $n_start; $self->{'n_last'} = $#x_array; # last n index if ($any_frac) { $self->{'figure'} = 'circle' } return $self; } 1; __END__ =for stopwords Ryde Math-PlanePath PlanePath =head1 NAME Math::PlanePath::File -- points from a file =head1 SYNOPSIS use Math::PlanePath::File; my $path = Math::PlanePath::File->new (filename => 'foo.txt'); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path reads X,Y points from a file to present in PlanePath style. It's slightly preliminary yet but is handy to get numbers from elsewhere into a PlanePath program. The intention is to be flexible about the file format and to auto-detect as far as possible. Currently the only format is plain text, with an X,Y pair, or N,X,Y triplet on each line 5,6 # X,Y 123 5 6 # N,X,Y Numbers can be separated by a comma or just spaces and tabs. Lines not starting with a number are ignored as comments (or blanks). N values must be integers, but the X,Y values can be fractions like 1.5 too, including exponential floating point 1500.5e-1 etc. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::File-Enew (filename =E "/my/file/name.txt")> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. In the current code an C<$x,$y> within a unit circle or square of a point from the file gives that point. But perhaps in the future some attention could be paid to apparent spacing of points closer than that. =item C<$bool = $path-Ex_negative()> =item C<$bool = $path-Ey_negative()> Return true if there are any negative X or negative Y coordinates in the file. =item C<$n = $path-En_start()> Return the first N in the path. For files of just X,Y points the start is N=1, for N,X,Y data it's the first N. =item C<$str = $path-Efigure()> Return a string name of the figure (shape) intended to be drawn at each C<$n> position. In the current code if all X,Y are integers then this is "square", otherwise it's "circle". But perhaps that will change. =back =head1 SEE ALSO L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/CretanLabyrinth.pm0000644000175000017500000002615613734026673020675 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=CretanLabyrinth --output=numbers_dash # http://labyrinthlocator.com/labyrinth-typology/4341-classical-labyrinths package Math::PlanePath::CretanLabyrinth; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; *_sqrtint = \&Math::PlanePath::_sqrtint; # uncomment this to run the ### lines #use Smart::Comments; use constant xy_is_visited => 1; use constant x_negative_at_n => 7; use constant y_negative_at_n => 13; #------------------------------------------------------------------------------ # 81-80-79 78 77 76 75 74 73 72 71 70 69 # | # 82 x--x-- x--x 68 # | | | # x x--x- x--x x 67 # | | | | # x 49-50-51-52-53-54-55 x 66 # | | | # 48 9--8--7--6--5 56 65 # | | | # 47 10 25-26-27 4 57 64 # | | | | | | | | | | # 46 11 24 29-28 3 58 x--x 63 x x--x x # | | | | | | | | # 45 12 23 30 1--2 59-60-61-62 x--x--x--x # | | | # 44 13 22 31-32-33 x--x--x--x x--x--x--x # | | | | | | | # 43 14 21-20-19 34 x x--x x # | | | | | | | # 42 15-16-17-18 35 # | # 41 40 39 38-37-36 # my @initial_n = (1,2, 5, 9,15,18,19,21,25,27,28,29,31,33,36,41,49,55,59); my @initial_dx = (1,0,-1, 0, 1, 0,-1, 0, 1, 0,-1, 0, 1, 0,-1, 0, 1, 0, 1); my @initial_dy = (0,1, 0,-1, 0, 1, 0, 1, 0,-1, 0,-1, 0,-1, 0, 1, 0,-1, 0); my @initial_x = (0); my @initial_y = (0); { my $x = 0; my $y = 0; foreach my $i (1 .. $#initial_n) { my $len = $initial_n[$i] - $initial_n[$i-1]; $x += $initial_dx[$i-1] * $len; $y += $initial_dy[$i-1] * $len; $initial_x[$i] = $x; $initial_y[$i] = $y; } } ### @initial_x ### @initial_y my @len = ( 4,3,7,12,14,11,5, 1, 4, 9,12,10, 5, 1,4, 8,10,7,4,3, 7,13,16,14, 8); my @dlen = ( 1,0,1, 2, 2, 2,1, 0, 1, 2, 2, 2, 1, 0,1, 2, 2,2,1,0, 1, 2, 2, 2, 1); my @dx = ( 0,1,0,-1, 0, 1,0,-1, 0,-1, 0, 1, 0,-1,0,-1, 0,1,0,1, 0,-1, 0, 1, 0); my @dy = (-1,0,1, 0,-1, 0,1, 0,-1, 0, 1, 0,-1, 0,1, 0,-1,0,1,0,-1, 0, 1, 0,-1); # [0,1,2],[59, 247, 563,] # N = (64 d^2 + 124 d + 59) # = ((64*$d + 124)*$d + 59) # d = -31/32 + sqrt(1/64 * $n + 17/1024) # = (-31 + 32*sqrt(1/64 * $n + 17/1024)) / 32 # = (-31 + sqrt(32*32/64 * $n + 32*32*17/1024)) / 32 # = (-31 + sqrt(16*$n + 17)) / 32 # [0,1,2],[55, 239, 551,] # N = (64 d^2 + 120 d + 55) # = ((64*$d + 120)*$d + 55) # d = -15/16 + sqrt(1/64 * $n + 5/256) # = (-15 + sqrt(16*16/64 * $n + 16*16*5/256))/16 # = ((-15 + sqrt(4*$n + 5))/16) sub n_to_xy { my ($self, $n) = @_; ### CretanLabyrinth n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n)) { return ($n, $n); } if ($n < 55) { foreach my $i (0 .. $#initial_n-1) { if ($initial_n[$i+1] > $n) { $n -= $initial_n[$i]; ### $n return ($initial_x[$i] + $initial_dx[$i] * $n, $initial_y[$i] + $initial_dy[$i] * $n); } } } my $d = int( (-15 + _sqrtint(4*$n+5))/16 ); $n -= ((64*$d + 120)*$d + 55); my $x = 4*$d + 2; my $y = 4*$d + 4; ### $d ### $n ### $x ### $y foreach my $i (0 .. $#len) { my $len = $len[$i] + 4*$d*$dlen[$i]; if ($n <= $len) { ### $i return ($n*$dx[$i] + $x, # $n first to inherit BigRat $n*$dy[$i] + $y); } $n -= $len; $x += $dx[$i]*$len; $y += $dy[$i]*$len; } die "oops, end of lengths table reached"; } sub xy_to_n { my ($self, $x, $y) = @_; ### CretanLabyrinth xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); my $d = _xy_to_d($x,$y); ### $d if ($d < 1) { foreach my $i (0 .. $#initial_n-1) { my $len = $initial_n[$i+1] - $initial_n[$i]; my $rx = $x - $initial_x[$i]; my $ry = $y - $initial_y[$i]; if ($initial_dx[$i]) { $rx *= $initial_dx[$i]; } else { next if $rx; } if ($initial_dy[$i]) { $ry *= $initial_dy[$i]; } else { next if $ry; } if ($rx >= 0 && $rx <= $len && $ry >= 0 && $ry <= $len) { return $initial_n[$i] + $rx + $ry; } } } else { $d -= 1; ### $d my $tx = 4*$d + 2; my $ty = 4*$d + 4; my $n = ((64*$d + 120)*$d + 55); foreach my $i (0 .. $#len) { ### at: "txy=$tx,$ty n=$n" my $len = $len[$i] + 4*$d*$dlen[$i]; my $rx = $x - $tx; my $ry = $y - $ty; $tx += $dx[$i]*$len; $ty += $dy[$i]*$len; $n += $len; ### rxy: "$rx,$ry" if ($dx[$i]) { $rx *= $dx[$i]; } else { next if $rx; } if ($dy[$i]) { $ry *= $dy[$i]; } else { next if $ry; } if ($rx >= 0 && $rx <= $len && $ry >= 0 && $ry <= $len) { ### found: "n=".($n-$len)." plus ".($rx+$ry) return $n-$len + $rx + $ry; } } } return undef; } sub _xy_to_d { my ($x, $y) = @_; ### _xy_to_d(): "$x,$y" if ($x >= abs($y)-2) { ### right ... return int(($x+2)/4); } if ($x <= -abs($y)) { ### left ... return int((-1-$x)/4); } ### vertical ... return int((abs($y)-1)/4); } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### CretanLabyrinth rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest($x1); $y1 = round_nearest($y1); $x2 = round_nearest($x2); $y2 = round_nearest($y2); my $d = max (_xy_to_d($x1,$y1), _xy_to_d($x2,$y1), _xy_to_d($x1,$y2), _xy_to_d($x2,$y2)); return (1, (64*$d + 120)*$d + 54); } 1; __END__ =for stopwords eg Ryde Math-PlanePath =head1 NAME Math::PlanePath::CretanLabyrinth -- infinite Cretan labyrinth =head1 SYNOPSIS use Math::PlanePath::CretanLabyrinth; my $path = Math::PlanePath::CretanLabyrinth->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is a Cretan 7-circuit style labyrinth extended out infinitely. 81--80--79--78--77--76--75--74--73--72--71--70--69 7 | | 82 137-138-139-140-141-142-143-144-145-146-147 68 6 | | | | 83 136 165-164-163-162-161-160-159-158-157 148 67 5 | | | | | | 84 135 166 49--50--51--52--53--54--55 156 149 66 4 | | | | | | | | 85 134 167 48 9-- 8-- 7-- 6-- 5 56 155 150 65 3 | | | | | | | | | | 86 133 168 47 10 25--26--27 4 57 154 151 64 2 | | | | | | | | | | | | 87 132 169 46 11 24 29--28 3 58 153-152 63 1 | | | | | | | | | | 88 131 170 45 12 23 30 1-- 2 59--60--61--62 <- Y=0 | | | | | | | 89 130 171 44 13 22 31--32--33 186-187-188-189 -1 | | | | | | | | | 90 129 172 43 14 21--20--19 34 185 112-111 190 -2 | | | | | | | | | | | 91 128 173 42 15--16--17--18 35 184 113 110 ... -3 | | | | | | | | 92 127 174 41--40--39--38--37--36 183 114 109 -4 | | | | | | 93 126 175-176-177-178-179-180-181-182 115 108 -5 | | | | 94 125-124-123-122-121-120-119-118-117-116 107 -6 | | 95--96--97--98--99-100-101-102-103-104-105-106 -7 ^ -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 The repeating part is the N=59 to N=189 style groups of 4 circuits going back and forward. The gaps between the path are the labyrinth walls. Notice at N=2,59,33,186 the "+" joining of those walls which is characteristic of this style labyrinth. | 3 | 58 | | | | ------+ | +------- | 1 2 | 59 60 | -------------+-------------- walls | 32 33 | 186 187 | ------+ | +------- | | | | 34 | 185 | See F for a sample program carving out the path from a solid block to leave the walls. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::CretanLabyrinth-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. If there's nothing at C<$x,$y> then return C. =item C<$n = $path-En_start()> Return 1, the first N in the path. =back =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/DragonCurve.pm0000644000175000017500000014175013774432751020023 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=DragonCurve --lines --scale=20 # math-image --path=DragonCurve --all --scale=10 # math-image --path=DragonCurve --output=numbers_dash # # math-image --wx --path=DragonCurve,arms=4 --expression='i<16384?i:0' package Math::PlanePath::DragonCurve; use 5.004; use strict; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_up_pow', 'bit_split_lowtohigh', 'digit_split_lowtohigh'; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::DragonMidpoint; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; use constant parameter_info_array => [ { name => 'arms', share_key => 'arms_4', display => 'Arms', type => 'integer', minimum => 1, maximum => 4, default => 1, width => 1, description => 'Arms', } ]; { my @x_negative_at_n = (undef, 5,5,5,6); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } { my @y_negative_at_n = (undef, 14,11,8,7); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } { my @_UNDOCUMENTED__dxdy_list_at_n = (undef, 5, 5, 5, 3); sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $_UNDOCUMENTED__dxdy_list_at_n[$self->{'arms'}]; } } use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(4, $self->{'arms'} || 1)); return $self; } { # sub state_string { # my ($state) = @_; # my $digit = $state & 3; $state >>= 2; # my $rot = $state & 3; $state >>= 2; # my $rev = $state & 1; $state >>= 1; # return "rot=$rot rev=$rev (digit=$digit)"; # } # generated by tools/dragon-curve-table.pl # next_state length 32 my @next_state = (12,16, 4,16, 0,20, 8,20, 4,24,12,24, 8,28, 0,28, 0,20, 0,28, 4,24, 4,16, 8,28, 8,20, 12,16,12,24); my @digit_to_x = ( 0, 0, 1, 1, 0, 1, 1, 0, 0, 0,-1,-1, 0,-1,-1, 0, 0, 1, 1, 2, 0, 0,-1,-1, 0,-1,-1,-2, 0, 0, 1, 1); my @digit_to_y = ( 0,-1,-1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0,-1,-1, 0, 0, 1, 1, 0, 1, 1, 2, 0, 0,-1,-1, 0,-1,-1,-2); my @digit_to_dxdy = ( 1, 0,undef,undef, 0, 1,undef,undef, -1, 0,undef,undef, 0,-1,undef,undef, 1, 0,undef,undef, 0, 1,undef,undef, -1, 0,undef,undef, 0,-1); sub n_to_xy { my ($self, $n) = @_; ### DragonCurve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } my $int = int($n); # integer part $n -= $int; # $n = fraction part my $zero = ($int * 0); # inherit bignum 0 my $arm = _divrem_mutate ($int, $self->{'arms'}); my @digits = digit_split_lowtohigh($int,4); ### @digits # initial state from rotation by arm and number of digits my $state = ((scalar(@digits) + $arm) & 3) << 2; my $len = (2+$zero) ** $#digits; my $x = $zero; my $y = $zero; foreach my $digit (reverse @digits) { # high to low ### at: "x=$x,y=$y len=$len digit=$digit state=$state" # ### state is: state_string($state) $state += $digit; $x += $len * $digit_to_x[$state]; $y += $len * $digit_to_y[$state]; $state = $next_state[$state]; $len /= 2; } ### final: "x=$x y=$y state=$state" # ### state is: state_string($state) ### final: "frac dx=$digit_to_dxdy[$state], dy=$digit_to_dxdy[$state+1]" return ($n * $digit_to_dxdy[$state] + $x, $n * $digit_to_dxdy[$state+1] + $y); } } { # generated by tools/dragon-curve-dxdy.pl # next_state length 32 my @next_state = ( 0, 6,20, 2, 4,10,24, 6, 8,14,28,10, 12, 2,16,14, 0,22,20,18, 4,26,24,22, 8,30,28,26, 12,18,16,30); my @state_to_dxdy = ( 1, 0,-1, 1, 0, 1,-1,-1, -1, 0, 1,-1, 0,-1, 1, 1, 1, 0,-1,-1, 0, 1, 1,-1, -1, 0, 1, 1, 0,-1,-1, 1); sub n_to_dxdy { my ($self, $n) = @_; ### n_to_dxdy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } my $int = int($n); $n -= $int; # $n fraction part ### $int ### $n my $state = 4 * _divrem_mutate ($int, $self->{'arms'}); ### arm as initial state: $state foreach my $bit (reverse bit_split_lowtohigh($int)) { # high to low $state = $next_state[$state + $bit]; } $state &= 0x1C; # mask out "prevbit" from state, leaving state==0 mod 4 ### final state: $state ### dx: $state_to_dxdy[$state] ### dy: $state_to_dxdy[$state+1], ### frac dx: $state_to_dxdy[$state+2], ### frac dy: $state_to_dxdy[$state+3], return ($state_to_dxdy[$state] + $n * $state_to_dxdy[$state+2], $state_to_dxdy[$state+1] + $n * $state_to_dxdy[$state+3]); } } # point N=2^(2k) at XorY=+/-2^k radius 2^k # N=2^(2k-1) at X=Y=+/-2^(k-1) radius sqrt(2)*2^(k-1) # radius = sqrt(2^level) # R(l)-R(l-1) = sqrt(2^level) - sqrt(2^(level-1)) # = sqrt(2^level) * (1 - 1/sqrt(2)) # about 0.29289 # my @try_dx = (0,0,-1,-1); my @try_dy = (0,1,1,0); sub xy_to_n { return scalar((shift->xy_to_n_list(@_))[0]); } sub xy_to_n_list { my ($self, $x, $y) = @_; ### DragonCurve xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); if (is_infinite($x)) { return $x; # infinity } if (is_infinite($y)) { return $y; # infinity } if ($x == 0 && $y == 0) { return (0 .. $self->arms_count - 1); } my @n_list; my $xm = $x+$y; # rotate -45 and mul sqrt(2) my $ym = $y-$x; foreach my $dx (0,-1) { foreach my $dy (0,1) { my $t = $self->Math::PlanePath::DragonMidpoint::xy_to_n ($xm+$dx, $ym+$dy); next unless defined $t; my ($tx,$ty) = $self->n_to_xy($t) or next; if ($tx == $x && $ty == $y) { ### found: $t if (@n_list && $t < $n_list[0]) { unshift @n_list, $t; } else { push @n_list, $t; } if (@n_list == 2) { return @n_list; } } } } return @n_list; } #------------------------------------------------------------------------------ sub xy_is_visited { my ($self, $x, $y) = @_; my $arms_count = $self->{'arms'}; if ($arms_count == 4) { # yes, whole plane visited return 1; } my $xm = $x+$y; my $ym = $y-$x; { my $arm = Math::PlanePath::DragonMidpoint::_xy_to_arm($xm,$ym); if ($arm < $arms_count) { # yes, segment $xm,$ym is on the desired arms return 1; } if ($arm == 2 && $arms_count == 1) { # no, segment $xm,$ym is on arm 2, which means its opposite is only on # arm 1,2,3 not arm 0 so arms_count==1 cannot be visited return 0; } } return (Math::PlanePath::DragonMidpoint::_xy_to_arm($xm-1,$ym+1) < $arms_count); } #------------------------------------------------------------------------------ # f = (1 - 1/sqrt(2) = .292 # 1/f = 3.41 # N = 2^level # Rend = sqrt(2)^level # Rmin = Rend / 2 maybe # Rmin^2 = (2^level)/4 # N = 4 * Rmin^2 # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### DragonCurve rect_to_n_range(): "$x1,$y1 $x2,$y2" my $xmax = int(max(abs($x1),abs($x2))); my $ymax = int(max(abs($y1),abs($y2))); return (0, $self->{'arms'} * ($xmax*$xmax + $ymax*$ymax + 1) * 7); } # Not quite right yet ... # # sub rect_to_n_range { # my ($self, $x1,$y1, $x2,$y2) = @_; # ### DragonCurve rect_to_n_range(): "$x1,$y1 $x2,$y2" # # # my ($length, $level_limit) = round_down_pow # ((max(abs($x1),abs($x2))**2 + max(abs($y1),abs($y2))**2 + 1) * 7, # 2); # $level_limit += 2; # ### $level_limit # # if (is_infinite($level_limit)) { # return ($level_limit,$level_limit); # } # # $x1 = round_nearest ($x1); # $y1 = round_nearest ($y1); # $x2 = round_nearest ($x2); # $y2 = round_nearest ($y2); # ($x1,$x2) = ($x2,$x1) if $x1 > $x2; # ($y1,$y2) = ($y2,$y1) if $y1 > $y2; # ### sorted range: "$x1,$y1 $x2,$y2" # # # my @xend = (0, 1); # my @yend = (0, 0); # my @xmin = (0, 0); # my @xmax = (0, 1); # my @ymin = (0, 0); # my @ymax = (0, 0); # my @sidemax = (0, 1); # my $extend = sub { # my ($i) = @_; # ### extend(): $i # while ($i >= $#xend) { # ### extend from: $#xend # my $xend = $xend[-1]; # my $yend = $yend[-1]; # ($xend,$yend) = ($xend-$yend, # rotate +45 # $xend+$yend); # push @xend, $xend; # push @yend, $yend; # my $xmax = $xmax[-1]; # my $xmin = $xmin[-1]; # my $ymax = $ymax[-1]; # my $ymin = $ymin[-1]; # ### assert: $xmax >= $xmin # ### assert: $ymax >= $ymin # # # ### at: "end=$xend,$yend $xmin..$xmax $ymin..$ymax" # push @xmax, max($xmax, $xend + $ymax); # push @xmin, min($xmin, $xend + $ymin); # # push @ymax, max($ymax, $yend - $xmin); # push @ymin, min($ymin, $yend - $xmax); # # push @sidemax, max ($xmax[-1], -$xmin[-1], # $ymax[-1], -$ymin[-1], # abs($xend), # abs($yend)); # } # ### @sidemax # }; # # my $rect_dist = sub { # my ($x,$y) = @_; # my $xd = ($x < $x1 ? $x1 - $x # : $x > $x2 ? $x - $x2 # : 0); # my $yd = ($y < $y1 ? $y1 - $y # : $y > $y2 ? $y - $y2 # : 0); # return max($xd,$yd); # }; # # my $arms = $self->{'arms'}; # ### $arms # my $n_lo; # { # my $top = 0; # for (;;) { # ARM_LO: foreach my $arm (0 .. $arms-1) { # my $i = 0; # my @digits; # if ($top > 0) { # @digits = ((0)x($top-1), 1); # } else { # @digits = (0); # } # # for (;;) { # my $n = 0; # foreach my $digit (reverse @digits) { # high to low # $n = 2*$n + $digit; # } # $n = $n*$arms + $arm; # my ($nx,$ny) = $self->n_to_xy($n); # my $nh = &$rect_dist ($nx,$ny); # # ### lo consider: "i=$i digits=".join(',',reverse @digits)." is n=$n xy=$nx,$ny nh=$nh" # # if ($i == 0 && $nh == 0) { # ### lo found inside: $n # if (! defined $n_lo || $n < $n_lo) { # $n_lo = $n; # } # next ARM_LO; # } # # if ($i == 0 || $nh > $sidemax[$i+2]) { # ### too far away: "nxy=$nx,$ny nh=$nh vs ".$sidemax[$i+2]." at i=$i" # # while (++$digits[$i] > 1) { # $digits[$i] = 0; # if (++$i <= $top) { # ### backtrack up ... # } else { # ### not found within this top and arm, next arm ... # next ARM_LO; # } # } # } else { # ### lo descend ... # ### assert: $i > 0 # $i--; # $digits[$i] = 0; # } # } # } # # # if an $n_lo was found on any arm within this $top then done # if (defined $n_lo) { # last; # } # # ### lo extend top ... # if (++$top > $level_limit) { # ### nothing below level limit ... # return (1,0); # } # &$extend($top+3); # } # } # # my $n_hi = 0; # ARM_HI: foreach my $arm (reverse 0 .. $arms-1) { # &$extend($level_limit+2); # my @digits = ((1) x $level_limit); # my $i = $#digits; # for (;;) { # my $n = 0; # foreach my $digit (reverse @digits) { # high to low # $n = 2*$n + $digit; # } # # $n = $n*$arms + $arm; # my ($nx,$ny) = $self->n_to_xy($n); # my $nh = &$rect_dist ($nx,$ny); # # ### hi consider: "arm=$arm i=$i digits=".join(',',reverse @digits)." is n=$n xy=$nx,$ny nh=$nh" # # if ($i == 0 && $nh == 0) { # ### hi found inside: $n # if ($n > $n_hi) { # $n_hi = $n; # next ARM_HI; # } # } # # if ($i == 0 || $nh > $sidemax[$i+2]) { # ### too far away: "$nx,$ny nh=$nh vs ".$sidemax[$i+2]." at i=$i" # # while (--$digits[$i] < 0) { # $digits[$i] = 1; # if (++$i < $level_limit) { # ### hi backtrack up ... # } else { # ### hi nothing within level limit for this arm ... # next ARM_HI; # } # } # # } else { # ### hi descend # ### assert: $i > 0 # $i--; # $digits[$i] = 1; # } # } # } # # if ($n_hi == 0) { # ### oops, lo found but hi not found # $n_hi = $n_lo; # } # # return ($n_lo, $n_hi); # } #------------------------------------------------------------------------------ # level ranges # arms=1 arms=2 arms=4 # level 0 0..1 = 2 0..3 = 4 0..7 = 8 # level 1 0..2 = 3 0..5 = 6 0..11 = 12 # level 2 0..4 = 5 0..9 = 10 0..19 = 20 # level 3 0..8 = 9 0..17 = 18 0..35 = 36 # 2^k 2*2^k+1 4*2^k+3 # sub level_to_n_range { my ($self, $level) = @_; return (0, (2**$level + 1) * $self->{'arms'} - 1); } # 0 .. 2^level # -1 .. 2^level-1 # level = round_up_pow(N) # eg N=13 -> 2^4=16 level=4 # sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, $self->{'arms'}); my ($pow, $exp) = round_up_pow ($n, 2); return $exp; } #------------------------------------------------------------------------------ { my @_UNDOCUMENTED_level_to_left_line_boundary = (1,2,4); sub _UNDOCUMENTED_level_to_left_line_boundary { my ($self, $level) = @_; if ($level < 0) { return undef; } if ($level <= 2) { return $_UNDOCUMENTED_level_to_left_line_boundary[$level]; } if (is_infinite($level)) { return $level; } my $l0 = 2; my $l1 = 4; my $l2 = 8; foreach (4 .. $level) { ($l2,$l1,$l0) = ($l2 + 2*$l0, $l2, $l1); } return $l2; } } { my @level_to_right_line_boundary = (1,2,4,8,undef); sub _UNDOCUMENTED_level_to_right_line_boundary { my ($self, $level) = @_; if ($level < 0) { return undef; } if ($level <= 3) { return $level_to_right_line_boundary[$level]; } if (is_infinite($level)) { return $level; } my $r0 = 2; my $r1 = 4; my $r2 = 8; my $r3 = 16; foreach (5 .. $level) { ($r3,$r2,$r1,$r0) = (2*$r3 - $r2 + 2*$r1 - 2*$r0, $r3, $r2, $r1); } return $r3; } } sub _UNDOCUMENTED_level_to_line_boundary { my ($self, $level) = @_; if ($level < 0) { return undef; } return $self->_UNDOCUMENTED_level_to_right_line_boundary($level+1); } sub _UNDOCUMENTED_level_to_u_left_line_boundary { my ($self, $level) = @_; if ($level < 0) { return undef; } return ($level == 0 ? 3 : $self->_UNDOCUMENTED_level_to_right_line_boundary($level) + 4); } sub _UNDOCUMENTED_level_to_u_right_line_boundary { my ($self, $level) = @_; if ($level < 0) { return undef; } return ($self->_UNDOCUMENTED_level_to_right_line_boundary($level) + $self->_UNDOCUMENTED_level_to_right_line_boundary($level+1)); } sub _UNDOCUMENTED_level_to_u_line_boundary { my ($self, $level) = @_; if ($level < 0) { return undef; } return ($self->_UNDOCUMENTED_level_to_u_left_line_boundary($level) + $self->_UNDOCUMENTED_level_to_u_right_line_boundary($level)); } sub _UNDOCUMENTED_level_to_enclosed_area { my ($self, $level) = @_; # A[k] = 2^(k-1) - B[k]/4 if ($level < 0) { return undef; } if ($level == 0) { return 0; } # avoid 2**(-1) return 2**($level-1) - $self->_UNDOCUMENTED_level_to_line_boundary($level) / 4; } *_UNDOCUMENTED_level_to_doubled_points = \&_UNDOCUMENTED_level_to_enclosed_area; { my @_UNDOCUMENTED_level_to_single_points = (2,3,5); sub _UNDOCUMENTED_level_to_single_points { my ($self, $level) = @_; if ($level < 0) { return undef; } if ($level <= 2) { return $_UNDOCUMENTED_level_to_single_points[$level]; } if (is_infinite($level)) { return $level; } my $l0 = 3; my $l1 = 5; my $l2 = 9; foreach (4 .. $level) { ($l2,$l1,$l0) = ($l2 + 2*$l0, $l2, $l1); } return $l2; } } { my @_UNDOCUMENTED_level_to_enclosed_area_join = (0,0,0,1); sub _UNDOCUMENTED_level_to_enclosed_area_join { my ($self, $level) = @_; if ($level < 0) { return undef; } if ($level <= 3) { return $_UNDOCUMENTED_level_to_enclosed_area_join[$level]; } if (is_infinite($level)) { return $level; } my ($j0,$j1,$j2,$j3) = @_UNDOCUMENTED_level_to_enclosed_area_join; $j3 += $level*0; foreach (4 .. $level) { ($j3,$j2,$j1,$j0) = (2*$j3 - $j2 + 2*$j1 - 2*$j0, $j3, $j2, $j1); } return $j3; } } #------------------------------------------------------------------------------ # points visited { my @_UNDOCUMENTED_level_to_visited = (2, 3, 5, 9, 16); sub _UNDOCUMENTED_level_to_visited { my ($self, $level) = @_; if ($level < 0) { return undef; } if ($level <= $#_UNDOCUMENTED_level_to_visited) { return $_UNDOCUMENTED_level_to_visited[$level]; } if (is_infinite($level)) { return $level; } my ($p0,$p1,$p2,$p3,$p4) = @_UNDOCUMENTED_level_to_visited; foreach (5 .. $level) { ($p4,$p3,$p2,$p1,$p0) = (4*$p4 - 5*$p3 + 4*$p2 - 6*$p1 + 4*$p0, $p4, $p3, $p2, $p1); } return $p4; } } #------------------------------------------------------------------------------ { my @_UNDOCUMENTED__n_segment_is_right_boundary # R M A B C D F G H # 1 2 3 4 5 6 7 8 9 = ([undef,1,3,1,6,7,9,3 ], [undef,2,4,5,4,8,5,0,0,4 ]); sub _UNDOCUMENTED__n_segment_is_right_boundary { my ($self, $n) = @_; if (is_infinite($n)) { return 0; } unless ($n >= 0) { return 0; } $n = int($n); my $state = 1; foreach my $bit (reverse bit_split_lowtohigh($n)) { # high to low $state = $_UNDOCUMENTED__n_segment_is_right_boundary[$bit][$state] || return 0; } return 1; } } 1; __END__ #------------------------------------------------------------------------------ # n_to_xy() old code based on i+1 multiply up. # # my @dir4_to_dx = (1,0,-1,0); # my @dir4_to_dy = (0,1,0,-1); # # sub n_to_xy { # my ($self, $n) = @_; # ### DragonCurve n_to_xy(): $n # # if ($n < 0) { return; } # if (is_infinite($n)) { return ($n, $n); } # # my $int = int($n); # integer part # $n -= $int; # fraction part # my $zero = ($int * 0); # inherit bignum 0 # # # arm as initial rotation # my $rot = _divrem_mutate ($int, $self->{'arms'}); # # my @digits = bit_split_lowtohigh($int); # ### @digits # # my @sx; # my @sy; # { # my $sy = $zero; # inherit BigInt 0 # my $sx = $zero + 1; # inherit BigInt 1 # ### $sx # ### $sy # # foreach (@digits) { # push @sx, $sx; # push @sy, $sy; # # (sx,sy) + rot+90(sx,sy), is multiply (i+1) # ($sx,$sy) = ($sx - $sy, # $sy + $sx); # } # } # # my $rev = 0; # my $x = $zero; # my $y = $zero; # while (defined (my $digit = pop @digits)) { # high to low # my $sx = pop @sx; # my $sy = pop @sy; # ### at: "$x,$y $digit side $sx,$sy" # ### $rot # # if ($rot & 2) { # ($sx,$sy) = (-$sx,-$sy); # } # if ($rot & 1) { # ($sx,$sy) = (-$sy,$sx); # } # # if ($rev) { # if ($digit) { # $x -= $sy; # $y += $sx; # ### rev add to: "$x,$y next is still rev" # } else { # $rot ++; # $rev = 0; # } # } else { # if ($digit) { # $rot ++; # $x += $sx; # $y += $sy; # $rev = 1; # ### add to: "$x,$y next is rev" # } # } # } # # $rot &= 3; # $x = $n * $dir4_to_dx[$rot] + $x; # $y = $n * $dir4_to_dy[$rot] + $y; # # ### final: "$x,$y" # return ($x,$y); # } # n_to_dxdy() by separate direction and frac next turn. # # my @dir4_to_dx = (1,0,-1,0); # my @dir4_to_dy = (0,1,0,-1); # # sub n_to_dxdy { # my ($self, $n) = @_; # ### n_to_dxdy(): $n # # my $int = int($n); # $n -= $int; # $n fraction part # ### $int # ### $n # # my $dir = _divrem_mutate ($int, $self->{'arms'}); # ### arm as initial dir: $dir # # my @digits = bit_split_lowtohigh($int); # ### @digits # # my $prev = 0; # foreach my $digit (reverse @digits) { # $dir += ($digit != $prev); # $prev = $digit; # } # $dir &= 3; # my $dx = $dir4_to_dx[$dir]; # my $dy = $dir4_to_dy[$dir]; # ### $dx # ### $dy # # if ($n) { # ### apply fraction part: $n # # # maybe: # # +/- $n as dx or dy # # +/- (1-$n) as other dy or dx # # # strip any low 1-bits, and the 0-bit above them # while (shift @digits) { } # # $dir += ($digits[0] ? -1 : 1); # bit above lowest 0-bit, 1=right,0=left # $dir &= 3; # $dx += $n*($dir4_to_dx[$dir] - $dx); # $dy += $n*($dir4_to_dy[$dir] - $dy); # # # my $sign = ($digits[0] ? 1 : -1); # bit above lowest 0-bit # # ($dx,$dy) = ($dx - $n*($dx - $sign*$dy), # # $dy - $n*($dy + $sign*$dx)); # # # my ($next_dx, $next_dy); # # if ($digits[0]) { # bit above lowest 0-bit # # # right # # $next_dx = $dy; # # $next_dy = -$dx; # # } else { # # # left # # $next_dx = -$dy; # # $next_dy = $dx; # # } # # ### $next_dx # # ### $next_dy # # # # $dx += $n*($next_dx - $dx); # # $dy += $n*($next_dy - $dy); # } # # ### result: "$dx, $dy" # return ($dx,$dy); # } #------------------------------------------------------------------------------ =for stopwords eg Ryde Dragon Math-PlanePath Heighway Harter et al vertices doublings OEIS Online Jorg Arndt fxtbook versa Nlevel Nlevel-1 Xlevel,Ylevel lengthways Lmax Lmin Wmin Wmax Ns Shallit Kmosek Seminumerical dX,dY bitwise lookup dx dy ie Xmid,Ymid Xlevel Xmid =head1 NAME Math::PlanePath::DragonCurve -- dragon curve =head1 SYNOPSIS use Math::PlanePath::DragonCurve; my $path = Math::PlanePath::DragonCurve->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is the dragon or paper folding curve by Heighway, Harter, et al. =cut # math-image --path=DragonCurve --all --output=numbers_dash --size=70x30 =pod 9----8 5---4 2 | | | | 10--11,7---6 3---2 1 | | 17---16 13---12 0---1 <- Y=0 | | | 18-19,15-14,22-23 -1 | | | 20--21,25-24 -2 | 26---27 -3 | --32 29---28 -4 | | 31---30 -5 ^ ^ ^ ^ ^ ^ ^ -5 -4 -3 -2 -1 X=0 1 ... The curve visits "inside" X,Y points twice. The first of these is X=-2,Y=1 which is N=7 and also N=11. The segments N=6,7,8 and N=10,11,12 have touched, but the path doesn't cross itself. The doubled vertices are all like this, touching but not crossing and no edges repeating. =head2 Arms The curve fills a quarter of the plane and four copies mesh together perfectly when rotated by 90, 180 and 270 degrees. The C parameter can choose 1 to 4 curve arms successively advancing. For example arms=4 begins as follows, with N=0,4,8,12,etc being the first arm, N=1,5,9,13 the second, N=2,6,10,14 the third and N=3,7,11,15 the fourth. arms => 4 20 ------ 16 | 9 ------5/12 ----- 8 23 | | | | 17 --- 13/6 --- 0/1/2/3 --- 4/15 --- 19 | | | | 21 10 ----- 14/7 ----- 11 | 18 ------ 22 With four arms every X,Y point is visited twice (except the origin 0,0 where all four begin) and every edge between the points is traversed once. =head2 Level Angle The first step N=1 is to the right along the X axis and the path then slowly spirals anti-clockwise and progressively fatter. The end of each replication is N=2^level which is at level*45 degrees around, N X,Y angle radial dist ---- ----- ----- ----------- 1 1,0 0 1 2 1,1 45 sqrt(2) 4 0,2 90 sqrt(4)=2 8 -2,2 135 sqrt(8) 16 -4,0 180 sqrt(16)=4 32 -4,-4 225 sqrt(32) ... Here's points N=0 to N=2^9=512. "0" is the origin and "+" is N=512. Notice it's spiralled around full-circle to angle 45 degrees up again, like the initial N=2. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 0 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * At a power of two Nlevel=2^level for N=2 or higher, the curve always goes upward from Nlevel-1 to Nlevel, and then goes to the left for Nlevel+1. For example at N=16 the curve goes up N=15 to N=16, then left for N=16 to N=17. Likewise at N=32, etc. The spiral curls around ever further but the self-similar twist back means the Nlevel endpoint is always at this same up/left orientation. See L below for the net direction in general. =head2 Level Ranges The X,Y extents of the path through to Nlevel=2^level can be expressed as a "length" in the direction of the Xlevel,Ylevel endpoint and a "width" across. level even, so endpoint is a straight line k = level/2 +--+ <- Lmax | | | E <- Lend = 2^k at Nlevel=2^level | +-----+ | O | <- Lstart=0 | | +--+ <- Lmin ^ ^ Wmin Wmax Lmax = (7*2^k - 4)/6 if k even (7*2^k - 2)/6 if k odd Lmin = - (2^k - 1)/3 if k even - (2^k - 2)/3 if k odd Wmax = (2*2^k - 2) / 3 if k even (2*2^k - 1) / 3 if k odd Wmin = Lmin For example level=2 is to Nlevel=2^2=4 and k=level/2=1 is odd so it measures as follows, 4 <- Lmax = (7*2^1 - 2)/6 = 2 | 3--2 | 0--1 <- Lmin = -(2^1 - 2)/3 = 0 ^ ^Wmax = (2*2^1 - 1)/3 = 1 | Wmin = Lmin = 0 Or level=4 is to Nlevel=2^4=16 and k=4/2=2 is even. It measures as follows. The lengthways "L" measures are in the direction of the N=16 endpoint and the "W" measures are across. 9----8 5---4 <- Wmax = (2*2^2 - 2)/3 = 2 | | | | 10--11,7---6 3---2 | | 16 13---12 0---1 | | 15---14 <- Wmin = -(2^2 - 1)/3 = -1 ^ ^Lmin = Wmin = -1 | Lmax = (7*2^2 - 4)/6 = 4 The formulas are all integer values, but the fractions 7/6, 1/3 and 2/3 show the limits as the level increases. If scaled so that length Lend=2^k is reckoned as 1 unit then Lmax extends 1/6 past the end, Lmin and Wmin extend 1/3, and Wmax extends across 2/3. +--------+ -- | - | 1/6 total length || | | = 1/6+1+1/3 = 3/2 || E | -- || | || | | \ | 1 | \ | | --\ | | \ | | || | O || -- | | || | | || 1/3 | ---- | +--------+ -- 1/3| 2/3 total width = 1/3+2/3 = 1 =head2 Paper Folding The path is called a paper folding curve because it can be generated by thinking of a long strip of paper folded in half repeatedly and then unfolded so each crease is a 90 degree angle. The effect is that the curve repeats in successive doublings turned by 90 degrees and reversed. The first segment unfolds, pivoting at the "1", 2 -> | unfold / | ===> | | | 0-------1 0-------1 Then the same again with that L shape, pivoting at the "2", then after that pivoting at the "4", and so on. 4 | | | 3--------2 2 | | unfold ^ | | ===> \_ | | | 0------1 0--------1 It can be shown that this unfolding doesn't overlap itself but the corners may touch, such as at the X=-2,Y=1 etc noted above. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::DragonCurve-Enew ()> =item C<$path = Math::PlanePath::DragonCurve-Enew (arms =E $int)> Create and return a new path object. The optional C parameter can make 1 to 4 copies of the curve, each arm successively advancing. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional C<$n> gives an X,Y position along a straight line between the integer positions. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. If there's nothing at C<$x,$y> then return C. The curve visits an C<$x,$y> twice for various points (all the "inside" points). The smaller of the two N values is returned. =item C<@n_list = $path-Exy_to_n_list ($x,$y)> Return a list of N point numbers for coordinates C<$x,$y>. The origin 0,0 has C many N since it's the starting point for each arm. Other points have up to two Ns for a given C<$x,$y>. If arms=4 then every C<$x,$y> except the origin has exactly two Ns. =item C<$n = $path-En_start()> Return 0, the first N in the path. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 2**$level)>, or for multiple arms return C<(0, $arms * 2**$level + ($arms-1))>. There are 2^level segments in a curve level, so 2^level+1 points numbered from 0. For multiple arms there are arms*(2^level+1) points, numbered from 0 so n_hi = arms*(2^level+1)-1. =back =head1 FORMULAS Various formulas for coordinates, lengths and area can be found in the author's long mathematical write-up =over L =back =cut # =head2 N to X,Y # # The location of a given N is found from the bits of N taken high to low # # s = 1, X,Y=0,0 # for bits of N high to low # (X,Y) = (X+Y+s*bit, X-Y) multiply b=1+i, add s if bit==1 # if bit above and this bit == 01 then s = -s # # At the high bit of N the bit above is 0, so there is a sign change s=-s at # the highest 1 in N. The effect is a change of base from binary to base b, # but with a sign change below each 01 bit pair. This works by considering # the second half as a rotated reversed curve, or alternatively by maintaining # and following a direction s. =pod =head2 X,Y to N The current code uses the C C by rotating -45 degrees and offsetting to the midpoints of the four edges around the target X,Y. The C algorithm then gives four candidate N values and those which convert back to the desired X,Y in the C C are the results for C. Xmid,Ymid = X+Y, Y-X # rotate -45 degrees for dx = 0 or -1 for dy = 0 or 1 N candidate = DragonMidpoint xy_to_n(Xmid+dx,Ymid+dy) Since there's at most two C Ns at a given X,Y the loop can stop when two Ns are found. Only the "leaving" edges will convert back to the target N, so only two of the four edges actually need to be considered. Is there a way to identify them? For arm 1 and 3 the leaving edges are up,down on odd points (meaning sum X+Y odd) and right,left for even points (meaning sum X+Y even). But for arm 2 and 4 it's the other way around. Without an easy way to determine the arm this doesn't seem to help. =head2 X,Y is Visited Whether a given X,Y is visited by the curve can be determined from one or two segments (rather then up to four for X,Y to N). | S midpoint Xmid = X+Y | Ymid = Y-X *---T--X,Y--S---* | T midpoint Xmid-1 | Ymid+1 Segment S is to the East of X,Y. The arm it falls on can be determined as per L. Numbering arm(S) = 0,1,2,3 then X,Y Visited ----------- if arms_count()==4 yes # whole plane if arm(S) < arms_count() yes if arm(S)==2 and arms_count()==1 no if arm(T) < arms_count() yes This works because when two arms touch they approach and leave by a right angle, without crossing. So two opposite segments S and T identify the two possible arms coming to the X,Y point. | | \ ---- ---- \ | | An arm only touches its immediate neighbour, ie. arm-1 or arm+1 mod 4. This means if arm(S)==2 then arm(T) can only be 1,2,3, not 0. So if C is 1 then arm(T) cannot be on the curve and no need to run its segment check. The only exception to the right-angle touching rule is at the origin X,Y = 0,0. In that case Xmid,Ymid = 0,0 is on the first arm and X,Y is correctly determined to be on the curve. If S was not to the East but some other direction away from X,Y then this wouldn't be so. =head2 Turn At each point the curve always turns either left or right, it never goes straight ahead. The bit above the lowest 1-bit in N gives the turn direction. N = 0b...z10000 (possibly no trailing 0s) z bit Turn ----- ---- 0 left 1 right For example N=12 is binary 0b1100, the lowest 1 bit is 0b_1__ and the bit above that is a 1, which means turn to the right. Or N=18 is binary 0b10010, the lowest 1 is 0b___1_ and the bit above that is 0, so turn left there. This z bit can be picked out with some bit twiddling $mask = $n & -$n; # lowest 1 bit, 000100..00 $z = $n & ($mask << 1); # the bit above it $turn = ($z == 0 ? 'left' : 'right'); This sequence is in Knuth volume 2 "Seminumerical Algorithms" answer to section 4.5.3 question 41 and is called the "dragon sequence". It's expressed there recursively as d(0) = 1 # unused, since first turn at N=1 d(2N) = d(N) # shift down looking for low 1-bit d(4N+1) = 0 # bit above lowest 1-bit is 0 d(4N+3) = 1 # bit above lowest 1-bit is 1 =head2 Next Turn The bits also give the turn after next by looking at the bit above the lowest 0-bit. This works because 011..11 + 1 = 100..00 so the bit above the lowest 0 becomes the bit above the lowest 1. N = 0b...w01111 (possibly no trailing 1s) w bit Next Turn ---- --------- 0 left 1 right For example at N=12=0b1100 the lowest 0 is the least significant bit 0b___0, and above that is a 0 too, so at N=13 the turn is to the left. Or for N=18=0b10010 the lowest 0 is again the least significant bit, but above it is a 1, so at N=19 the turn is to the right. This too can be found with some bit twiddling, as for example $mask = $n ^ ($n+1); # low one and below 000111..11 $w = $n & ($mask + 1); # the bit above there $turn = ($w == 0 ? 'left' : 'right'); =head2 Total Turn The total turn is the count of 0E-E1 transitions in the runs of bits of N, which is the same as how many bit pairs of N (including overlaps) are different so "01" or "10". This can be seen from the segment replacements resulting from bits of N, N bits from high to low, start in "plain" state plain state 0 bit -> no change 1 bit -> count left, and go to reversed state reversed state 0 bit -> count left, and go to plain state 1 bit -> no change The 0 or 1 counts are from the different side a segment expands on in plain or reversed state. Segment A to B expands to an "L" shape bend which is on the right in plain state, but on the left in reversed state. plain state reverse state A = = = = B + \ ^ 0bit / \ \ / turn / \ 1bit 0bit \ / 1bit left/ \ \ / turn / v + left A = = = = B In both cases a rotate of +45 degrees keeps the very first segment of the whole curve in a fixed direction (along the X axis), which means the south-east slope shown is no-change. This is the 0 of plain or the 1 of reversed. And the north-east slope which is the other new edge is a turn towards the left. It can be seen the "state" above is simply the previous bit, so the effect for the bits of N is to count a left turn at each transition from 0-E1 or 1-E0. Initial "plain" state means the infinite zero bits at the high end of N are included. For example N=9 is 0b1001 so three left turns for curve direction south to N=10 (as can be seen in the diagram above). 1 00 1 N=9 ^ ^ ^ +-+--+---three transitions, so three left turns for direction south The transitions can also be viewed as a count of how many runs of contiguous 0s or 1s, up to the highest 1-bit. 1 00 1 three blocks of 0s and 1s XXThis can be calculated by some bit twiddling with a shift and xor to turn transitions into 1-bits which can then be counted, as per Jorg Arndt (fxtbook section 1.31.3.1 "The Dragon Curve"). total turn = count_1_bits ($n ^ ($n >> 1)) The reversing structure of the curve shows up in the total turn at each point. The total turns for a block of 2^N is followed by its own reversal plus 1. For example, -------> N=0 to N=7 0, 1, 2, 1, 2, 3, 2, 1 N=15 to N=8 1, 2, 3, 2, 3, 4, 3, 2 each is +1 <------- =head2 N to dX,dY C is the "total turn" per above, or for fractional N then an offset according to the "next turn" above. If using the bit twiddling operators described then the two can be calculated separately. The current C code tries to support floating point or other number types without bitwise XOR etc by processing bits high to low with a state table which combines the calculations for total turn and next turn. The state encodes total turn 0 to 3 next turn 0 or 1 previous bit 0 or 1 (the bit above the current bit) The "next turn" remembers the bit above lowest 0 seen so far (or 0 initially). The "total turn" counts 0-E1 or 1-E0 transitions. The "previous bit" shows when there's a transition, or what bit is above when a 0 is seen. It also works not to have this previous bit in the state but instead pick out two bits each time. At the end of bit processing any "previous bit" in state is no longer needed and can be masked out to lookup the final four dx, dy, next dx, next dy. =head1 OEIS The Dragon curve is in Sloane's Online Encyclopedia of Integer Sequences in many forms (and see C for its forms too), =over L (etc) =back A332383 X coordinate A332384 Y coordinate A038189 turn, 0=left,1=right, bit above lowest 1, extra 0 A089013 turn, 0=left,1=right, bit above lowest 1, extra 1 A082410 turn, 1=left,0=right, reversing complement, extra 0 A099545 turn, 1=left,3=right, as [odd part n] mod 4 so turn by 90 degrees * 1 or 3 A034947 turn, 1=left,-1=right, Jacobi (-1/n) A112347 turn, 1=left,-1=right, Kronecker (-1/n), extra 0 A121238 turn, 1=left,-1=right, -1^(n + some partitions) extra 1 A119972 turn, n=left,-n=right A014577 next turn, 1=left,0=right A014707 next turn, 0=left,1=right A014709 next turn, 1=left,2=right A014710 next turn, 2=left,1=right A090678 1=same turn as previous, 0=different A143347 paperfolding constant, bits 0=left,1=right in decimal These numerous turn sequences differ only in having left or right represented as 0, 1, -1, etc, and possibly "extra" initial 0 or 1 at n=0 arising from the definitions and the first turn being at n=N=1. The "next turn" forms begin at n=0 for turn at N=1 and so are the turn at N=n+1. A005811 direction total turn A088748 direction total turn + 1 A037834 direction total turn - 1 A136004 direction total turn + 4 A246960 direction 0,1,2,3 = total turn mod 4 A173318 cumulative(total turn) A164910 cumulative(total turn + 1) A166242 2^(total turn), by double/halving A000975 N of new maximum total turn, binary 10101... A268411 direction of horizontals, 0=East, 1=West A043724 N of East A043725 N of North A043726 N of West A043727 N of South A088431 turn sequence run lengths A007400 2*runlength A091072 N positions of the left turns, being odd part form 4K+1 A091067 N positions of the right turns, being odd part form 4K+3 A255068 N positions where next turn right A060833 N positions where previous turn right A106837 N positions of consecutive turns R,R A106838 N positions of consecutive turns R,R,R A106840 N positions of consecutive turns L,L A106841 N positions of consecutive turns L,L,L A106836 N steps between right turns A088742 N steps between left turns A255070 num right turns 1 to N A236840 2* num right turns 1 to N A003460 turns N=1 to N=2^n-1 packed as bits 1=left,0=right low to high, then written in octal A126937 coordinates coded by SquareSpiral (start N=0 and flip Y) A038503 num segments East in level k A038504 num segments North in level k A038505 num segments West in level k A000749 num segments South in level k A146559 X at N=2^k, for k>=1, being Re((i+1)^k) A009545 Y at N=2^k, for k>=1, being Im((i+1)^k) A227036 boundary length N=0 to N=2^k also right boundary length to N=2^(k+1) A203175 left boundary length N=0 to N=2^k = differences of total boundary = squares on left boundary A003476 squares on right boundary = single points N=0 to N=2^(k-1) inclusive A164395 single points N=0 to N=2^k-1 inclusive, for k=4 up A003230 area enclosed N=0 to N=2^k, for k=4 up same as double points A003478 area enclosed by left side, also area increment A003477 area of each blob (touching enclosed unit squares) A003479 join area between N=2^k replications A003229 join area increment, also area left side extra over doubling A077949 same A289265 growth rate r = 1.695 of boundaries etc A272031 fractal dimension log(r)/log(sqrt(2)) arms=4 A165211 abs(dY), 0,1,0,1,1,0,1,0 repeating For reference, "dragon-like" A059125 is similar to the turn sequence A014707, but differs in having the "middle" values for each replication come from successive values of the sequence itself, or some such. =head2 A088431 and A007400 The run lengths A088431 and A007400 are from a continued fraction expansion of an infinite sum 1 1 1 1 1 1 1 + - + - + -- + --- + ----- + ... + ------- + ... 2 4 16 256 65536 2^(2^k) Jeffrey Shallit and independently M. Kmosek show how continued fraction terms repeated in reverse give rise to this sort of power sum, =over Jeffrey Shallit, "Simple Continued Fractions for Some Irrational Numbers", Journal of Number Theory, volume 11, 1979, pages 209-217. L, L (And which appears in Knuth "Art of Computer Programming", volume 2, section 4.5.3 exercise 41.) =cut # M. Kmosek, "Rozwiniecie niektorych liczb niewymiernych na ulamki lancuchowe", Master's # thesis, Uniwersytet Warszawski, 1979. -- is that right? =pod =back =head2 A126937 The A126937 C numbering has the dragon curve and square spiralling with their Y axes in opposite directions, as shown in its F. So the dragon curve turns up towards positive Y but the square spiral is numbered down towards negative Y (or vice versa). C code for this starting at C<$i=0> would be my $dragon = Math::PlanePath::DragonCurve->new; my $square = Math::PlanePath::SquareSpiral->new (n_start => 0); my ($x, $y) = $dragon->n_to_xy ($i); my $A126937_of_i = $square->xy_to_n ($x, -$y); =head1 HOUSE OF GRAPHS House of Graphs entries for the dragon curve as a graph include =over L etc =back 19655 level=0 (1-segment path) 32234 level=1 (2-segment path) 286 level=2 (4-segment path) 414 level=3 (8-segment path) 33739 level=4 33741 level=5 33743 level=6 33745 level=7 33747 level=8 And for just a blob (the biggest 2-connected component in its level) 674 level=4 (4-cycle single unit square) 25223 level=5 33749 level=6 33751 level=7 33753 level=8 34163 level=9 =head1 SEE ALSO L, L, L, L, L L, L, L, L L L =for comment http://wiki.tcl.tk/10745 recursive curves =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/CubicBase.pm0000644000175000017500000003533013734026673017416 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=CubicBase --all --output=numbers --size=60x20 # math-image --path=CubicBase --values=Multiples,multiples=27 --output=numbers --size=60x20 # math-image --path=CubicBase --expression='i<128?i:0' --output=numbers --size=132x20 # package Math::PlanePath::CubicBase; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'parameter_info_array', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_even; # use constant parameter_info_array => # [ Math::PlanePath::Base::Digits::parameter_info_radix2(), # # # Experimental ... # # { name => 'skewed', # # type => 'boolean', # # default => 0, # # }, # ]; sub x_negative_at_n { my ($self) = @_; return $self->{'radix'}; } sub y_negative_at_n { my ($self) = @_; return $self->{'radix'}**2; } use constant absdx_minimum => 2; use constant dir_maximum_dxdy => (-1, -3); # supremum sub turn_any_straight { my ($self) = @_; return $self->{'radix'} > 2; # never straight in radix=2 } sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; return $self->{'radix'} - 1; } sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; return $self->{'radix'}; } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); my $radix = $self->{'radix'}; if (! defined $radix || $radix <= 2) { $radix = 2; } $self->{'radix'} = $radix; return $self; } sub n_to_xy { my ($self, $n) = @_; ### CubicBase n_to_xy(): "$n" if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } # is this sort of midpoint worthwhile? not documented yet { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $x = 0; my $y = 0; my $radix = $self->{'radix'}; if (my @digits = digit_split_lowtohigh($n,$radix)) { my $len = ($n * 0) + 1; # inherit bignum 1 my $ext = 1; for (;;) { { # 0 degrees $x += (2*(shift @digits)) * $len; # low to high } @digits || last; if ($ext ^= 1) { $len *= $radix; } { # +120 degrees my $dlen = (shift @digits) * $len; # low to high $x -= $dlen; $y += $dlen; } @digits || last; if ($ext ^= 1) { $len *= $radix; } { # +240 degrees my $dlen = (shift @digits) * $len; # low to high $x -= $dlen; $y -= $dlen; } @digits || last; if ($ext ^= 1) { $len *= $radix; } } if ($self->{'skewed'}) { $x = ($x + $y) / 2; } } ### result: "$x,$y" return ($x,$y); } sub xy_to_n { my ($self, $x, $y) = @_; ### CubicBase xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if (is_infinite($x)) { return ($x); } if (is_infinite($y)) { return ($y); } if ($self->{'skewed'}) { $x = 2*$x - $y; } else { if (($x + $y) % 2) { # nothing on odd squares, only A2 even squares return undef; } } # $x = ($x-$y)/2; # into i,j coordinates foreach my $overflow ($x+$y, $x-$y) { if (is_infinite($overflow)) { return $overflow; } } my $radix = $self->{'radix'}; my $zero = ($x * 0 * $y); # inherit bignum 0 my @n; # digits low to high if ($x || $y) { my $ext = 1; for (;;) { ### at: "x=$x y=$y" { my $digit = (($x+$y)/2) % $radix; push @n, $digit; $x -= 2*$digit; ### 0deg digit: $digit ### subtract to: "x=$x y=$y" } last unless $x || $y; if ($ext ^= 1) { ### assert: ($x % $radix) == 0 ### assert: ($y % $radix) == 0 $x = int($x/$radix); $y = int($y/$radix); ### divide out to: "x=$x y=$y" } { my $digit = (($y-$x)/2) % $radix; push @n, $digit; $x += $digit; $y -= $digit; ### 120deg digit: $digit ### subtract to: "x=$x y=$y" } last unless $x || $y; if ($ext ^= 1) { ### assert: ($x % $radix) == 0 ### assert: ($y % $radix) == 0 $x = int($x/$radix); $y = int($y/$radix); ### divide out to: "x=$x y=$y" } { my $digit = (-$y) % $radix; push @n, $digit; $x += $digit; $y += $digit; ### 240deg digit: $digit ### subtract to: "x=$x y=$y" } last unless $x || $y; if ($ext ^= 1) { ### assert: ($x % $radix) == 0 ### assert: ($y % $radix) == 0 $x = int($x/$radix); $y = int($y/$radix); ### divide out to: "x=$x y=$y" } } } return digit_join_lowtohigh (\@n, $radix, $zero); } # ENHANCE-ME: Can probably do better by measuring extents in 3 directions # for a hexagonal boundary. # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### CubicBase rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); my $radix = $self->{'radix'}; my $xm = max(abs($x1),abs($x2)) * $radix*$radix*$radix; my $ym = max(abs($y1),abs($y2)) * $radix*$radix*$radix; return (0, $xm*$xm+$ym*$ym); } #------------------------------------------------------------------------------ # levels use Math::PlanePath::ImaginaryBase; *level_to_n_range = \&Math::PlanePath::ImaginaryBase::level_to_n_range; *n_to_level = \&Math::PlanePath::ImaginaryBase::n_to_level; #------------------------------------------------------------------------------ 1; __END__ # xy_to_n() high to low # # use Math::PlanePath::Base::Digits # 'round_down_pow'; # # my ($len, $level) = round_down_pow(abs($x)+abs($y), $radix); # $len *= $radix; # $level++; # $len *= $radix; # ### $level # ### $len # # for (;;) { # ### at: "x=$x y=$y" # # { # my $k = -$y; # my $digit = ($k >= 0 # ? int($k/$len) # : -int(-$k/$len)); # $n = $n*$radix + $digit; # $x += $digit*$len; # $y += $digit*$len; # # ### 240deg digit: $digit # ### add to: "x=$x y=$y" # } # # $len = int($len/$radix); # ### $len # # { # my $k = $y; # my $digit = int($k/$len) % $radix; # $n = $n*$radix + $digit; # $x += $digit*$len; # $y -= $digit*$len; # # ### 120deg digit: $digit # ### subtract to: "x=$x y=$y" # } # # { # my $digit = ($x >= 0 # ? int($x/(2*$len)) # : -int(-$x/(2*$len))); # $n = $n*$radix + $digit; # $x -= 2*$digit; # # ### 0deg digit: $digit # ### subtract to: "x=$x y=$y" # } # # last unless $level-- > 0; # # } =for stopwords eg Ryde Math-PlanePath Radix ie radix =head1 NAME Math::PlanePath::CubicBase -- replications in three directions =head1 SYNOPSIS use Math::PlanePath::CubicBase; my $path = Math::PlanePath::CubicBase->new (radix => 4); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is a pattern of replications in three directions 0, 120 and 240 degrees. =cut # these numbers generated with # math-image --path=CubicBase --expression='i<64?i:0' --output=numbers --size=132x20 =pod 18 19 26 27 5 16 17 24 25 4 22 23 30 31 3 20 21 28 29 2 50 51 58 59 2 3 10 11 1 48 49 56 57 0 1 8 9 <- Y=0 54 55 62 63 6 7 14 15 -1 52 53 60 61 4 5 12 13 -2 34 35 42 43 -3 32 33 40 41 -4 38 39 46 47 -5 36 37 44 45 -6 ^ -11-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 The points are on a triangular grid by using every second integer X,Y, as per L. All points on that triangular grid are visited. The initial N=0,N=1 is replicated at +120 degrees. Then that trapezoid at +240 degrees +-----------+ +-----------+ \ 2 3 \ \ 2 3 \ +-----------+ \ \ \ 0 1 \ \ 0 1 \ +-----------+ --------- -----------+ \ 6 7 \ replicate +120deg \ \ rep +240deg \ 4 5 \ +----------+ Then that bow-tie N=0to7 is replicated at 0 degrees again. Each replication is 1/3 of the circle around, 0, 120, 240 degrees repeating. The relative layout within a replication is unchanged. ----------------------- \ 18 19 26 27 \ \ \ \ 16 17 24 25 \ ---------- ---------- \ 22 23 30 31 \ \ \ \ 20 21 28 29 \ --------- ------------ +----------- ----------- \ 50 51 58 59 \ 2 3 \ 10 11 \ \ +-----------+ \ \ 48 49 56 57 \ 0 1 \ 8 9 \ ---------- --------- +----------- ---------+ \ 54 55 62 63 \ 6 7 \ 14 15 \ \ \ \ \ \ 52 53 60 61 \ 4 5 \ 12 13 \ -------------- +----------+------------ \ 34 35 42 43 \ \ \ \ 32 33 40 41 \ ---------+ ----------- \ 38 39 46 47 \ \ \ \ 36 37 44 45 \ ----------------------- The radial distance doubles on every second replication, so N=1 and N=2 are at 1 unit from the origin, then N=4 and N=8 at 2 units, then N=16 and N=32 at 4 units. N=64 is not shown but is then at 8 units away (X=8,Y=0). This is similar to the C, but where that path repeats in 4 directions based on i=squareroot(-1), here it's 3 directions based on w=cuberoot(1) = -1/2+i*sqrt(3)/2. =head2 Radix The C parameter controls the "r" used to break N into X,Y. For example radix 4 gives 4x4 blocks, with r-1 replications of the preceding level at each stage. =cut # math-image --path=CubicBase,radix=4 --expression='i<64?i:0' --output=numbers --size=150x30 =pod 3 radix => 4 12 13 14 15 2 8 9 10 11 1 4 5 6 7 Y=0 -> 0 1 2 3 -1 28 29 30 31 -2 24 25 26 27 -3 20 21 22 23 -4 16 17 18 19 -5 44 45 46 47 ... 40 41 42 43 36 37 38 39 32 33 34 35 60 61 62 63 56 57 58 59 52 53 54 55 48 49 50 51 ^ -15-14-13-12-11-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 Notice the parts always replicate away from the origin, so the block N=16 to N=31 is near the origin at X=-4, then N=32,48,64 are further away. In this layout the replications still mesh together perfectly and all points on the triangular grid are visited. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::CubicBase-Enew ()> =item C<$path = Math::PlanePath::CubicBase-Enew (radix =E $r)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, $radix**$level - 1)>. =back =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/PixelRings.pm0000644000175000017500000003736613734026660017671 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # ENHANCE-ME: What formula for the cumulative pixel count, and its inverse? # Not floor(k*4*sqrt(2)). # ENHANCE-ME: Maybe n_start package Math::PlanePath::PixelRings; use 5.004; use strict; use Math::Libm 'hypot'; #use List::Util 'min','max'; *min = \&Math::PlanePath::_min; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; # use constant parameter_info_array => # [ # { # name => 'offset', # share_key => 'offset_05', # type => 'float', # description => 'Radial offset for the centre of each ring.', # default => 0, # minimum => -0.5, # maximum => 0.5, # page_increment => 0.05, # step_increment => 0.005, # width => 7, # decimals => 4, # }, # ]; use constant n_frac_discontinuity => 0; use constant x_negative_at_n => 4; use constant y_negative_at_n => 5; use constant dx_minimum => -1; use constant dx_maximum => 2; # jump N=5 to N=6 use constant dy_minimum => -1; use constant dy_maximum => 1; # eight plus ENE use constant 1.02; use constant _UNDOCUMENTED__dxdy_list => (1,0, # E N=1 2,1, # ENE N=5 <-- extra 1,1, # NE N=16 0,1, # N N=6 -1,1, # NW N=2 -1,0, # W N=8 -1,-1, # SW N=3 0,-1, # S N=11 1,-1, # SE N=4 ); use constant _UNDOCUMENTED__dxdy_list_at_n => 16; use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 3; # dx=2,dy=1 at jump N=5 to N=6 use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East use constant _UNDOCUMENTED__turn_any_right_at_n => 81; #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'offset'} ||= 0; $self->{'cumul'} = [ 1, 2 ]; $self->{'cumul_x'} = 0; $self->{'cumul_y'} = 0; $self->{'cumul_add'} = 0; return $self; } sub _cumul_extend { my ($self) = @_; ### _cumul_extend(): "length of r=".($#{$self->{'cumul'}}) my $cumul = $self->{'cumul'}; my $r = $#$cumul; $self->{'cumul_add'} += 4; if ($self->{'cumul_x'} == $self->{'cumul_y'}) { ### at: "$self->{'cumul_x'},$self->{'cumul_y'}" ### step across and maybe up $self->{'cumul_x'}++; ### xy hypot: ($self->{'cumul_x'}+.5)**2 + ($self->{'cumul_y'})**2 ### r squared: $r*$r ### E: ($self->{'cumul_x'}+.5)**2 + $self->{'cumul_y'}**2 - ($r+$self->{'offset'})**2 if (($self->{'cumul_x'}+.5)**2 + $self->{'cumul_y'}**2 < ($r+$self->{'offset'})**2) { ### midpoint of x,y inside, increment to x,y+1 $self->{'cumul_y'}++; $self->{'cumul_add'} += 4; } } else { ### at: "$self->{'cumul_x'},$self->{'cumul_y'}" ### try y+1 with x or x+1 is: ($self->{'cumul_x'}+.5).",".($self->{'cumul_y'}+1) $self->{'cumul_y'}++; ### xy hypot: ($self->{'cumul_x'}+.5)**2 + ($self->{'cumul_y'})**2 ### r squared: $r*$r ### E: ($self->{'cumul_x'}+.5)**2 + $self->{'cumul_y'}**2 - ($r+$self->{'offset'})**2 if (($self->{'cumul_x'}+.5)**2 + $self->{'cumul_y'}**2 < ($r+$self->{'offset'})**2) { ### midpoint inside, increment x too $self->{'cumul_x'}++; $self->{'cumul_add'} += 4; } } ### to: "$self->{'cumul_x'},$self->{'cumul_y'}" ### cumul extend: scalar(@$cumul).' = '.($cumul->[-1] + $self->{'cumul_add'}) ### cumul_add: $self->{'cumul_add'} push @$cumul, $cumul->[-1] + $self->{'cumul_add'}; } sub n_to_xy { my ($self, $n) = @_; ### PixelRings n_to_xy(): $n if ($n < 2) { if ($n < 1) { return; } return ($n-1, 0); } if (is_infinite($n)) { return ($n,$n); } { # ENHANCE-ME: direction of N+1 from the cumulative lookup my $int = int($n); if ($n != $int) { my $frac = $n - $int; my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); if ($y2 == 0 && $x2 > 0) { $x2 -= 1; } my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } ### search cumul for n: $n my $cumul = $self->{'cumul'}; my $r = 1; for (;;) { if ($r >= @$cumul) { _cumul_extend ($self); } if ($cumul->[$r] > $n) { last; } $r++; } $r--; $n -= $cumul->[$r]; my $len = $cumul->[$r+1] - $cumul->[$r]; ### cumul: "$cumul->[$r] to $cumul->[$r+1]" ### $len ### n rem: $n $len /= 4; my $quadrant = $n / $len; $n %= $len; ### len of quadrant: $len ### $quadrant ### n into quadrant: $n my $rev; if ($rev = ($n > $len/2)) { $n = $len - $n; } ### $rev ### $n my $y = $n; my $x = int (sqrt (max (0, ($r+$self->{'offset'})**2 - $y*$y)) + .5); if ($rev) { ($x,$y) = ($y,$x); } if ($quadrant & 2) { $x = -$x; $y = -$y; } if ($quadrant & 1) { ($x,$y) = (-$y, $x); } ### return: "$x, $y" return ($x, $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### PixelRings xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x == 0 && $y == 0) { return 1; } my $r; { my $xa = abs($x); my $ya = abs($y); if ($xa < $ya) { ($xa,$ya) = ($ya,$xa); } $r = int (hypot ($xa+.5,$ya)); ### r frac: hypot ($xa+.5,$ya) ### $r ### r < inside frac: hypot ($xa-.5,$ya) if ($r < hypot ($xa-.5,$ya)) { ### pixel not crossed return undef; } if ($xa == $ya) { ### and pixel below for diagonal ### r < below frac: $r . " < " . hypot ($xa+.5,$ya-1) if ($r < hypot ($xa+.5,$ya-1)) { ### same loop, no sharp corner return undef; } } } if (is_infinite($r)) { return undef; } my $cumul = $self->{'cumul'}; while ($#$cumul <= $r) { ### extend cumul for r: $r _cumul_extend ($self); } my $n = $cumul->[$r]; my $len = $cumul->[$r+1] - $n; ### $r ### n base: $n ### $len ### len/4: $len/4 if ($y < 0) { ### y neg, rotate 180 $y = -$y; $x = -$x; $n += $len/2; } if ($x < 0) { $n += $len/4; ($x,$y) = ($y,-$x); ### neg x, rotate 90 ### n base now: $n + $len/4 ### transpose: "$x,$y" } ### assert: $x >= 0 ### assert: $y >= 0 if ($y > $x) { ### top octant, reverse: "x=$x len/4=".($len/4)." gives ".($len/4 - $x) $y = $len/4 - $x; } ### n return: $n + $y return $n + $y; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### PixelRings rect_to_n_range(): "$x1,$y1 $x2,$y2" # ENHANCE-ME: use an estimate from rings no bigger than sqrt(2), so can # get a range for big x,y $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); my $r_min = ((($x1<0) ^ ($x2<0)) || (($y1<0) ^ ($y2<0)) ? 0 : max (0, int (hypot (min(abs($x1),abs($x2)), min(abs($y1),abs($y2)))) - 1)); my $r_max = 2 + int (hypot (max(abs($x1),abs($x2)), max(abs($y1),abs($y2)))); ### $r_min ### $r_max if (is_infinite($r_min)) { return ($r_min, $r_min); } my ($n_max, $r_target); if (is_infinite($r_max)) { $n_max = $r_max; # infinity $r_target = $r_min; } else { $r_target = $r_max; } my $cumul = $self->{'cumul'}; while ($#$cumul < $r_target) { ### extend cumul for r: $r_target _cumul_extend ($self); } if (! defined $n_max) { $n_max = $cumul->[$r_max]; } return ($cumul->[$r_min], $n_max); } 1; __END__ # # =head1 FORMULAS # # # =head2 Pixel Ring Length # # When the algorithm crosses the X=Y central diagonal it might include an X=Y # point or it might not. The case where it doesn't looks like # # +-------+ X=Y line # | | . # | | . # | * | .. # | | .. # | |. # +-------.-------+ # .| | # . | | # .. % * | <- Y=k-1 # .. | | # . | | # +-------+ # ^ ^ ^ # | X=k | # X=k-.5 X=k+.5 # # The algorithm draws a pixel when the exact circle line X^2+Y^2=R^2 passes is # within that pixel, ie. on its side of the midpoint between adjacent pixels. # This means to the right of the X=k-0.5, Y=k-1 point marked "%" above. So # # X^2 + Y^2 < R^2 # (k-.5)^2 + (k-1)^2 < R^2 # 2*k^2 - 3k + 5/4 < R^2 # k = floor (3 + sqrt(3*3 - 4*2*(5/4 - R^2))) # = floor (3 + sqrt(8*R^2 - 1)) # # The circle line is never precisely on such a "%" point, as can be seen from # the formula since 8*R^2-1 is never a perfect square (squares are 0,1,4 # mod 8). # # Now in the first octant, up to this k pixel, there's one pixel per row, and # likewise symmetrically above the line, so the total in a ring passing the # X=Y this way is # # ringlength = 8*k-4 # # The second case is when the ring includes an X=Y point, # # +-------+ # | | # | | .. # | * | .. # | | . # | | |. # +-------+-------+- # | .| # | X=Y. | # | * | # | .. | # |. | # -+-------+-------+ # .| | | # .. | | | # . % @ * | <- Y=k-1 # .. | | | # | | | # +-------+-------+ # | X=k | # X=k-.5 X=k+.5 # # The two cases are distinguished by which side of the X=k+.5 midpoint "@" the # circle line passes. If the circle is outside the "@" then the outer pixel # is drawn, thus giving this X=Y included case. The test is # # X^2 + Y^2 < R^2 # (k+.5)^2 + (k-1)^2 < R^2 # 2*k^2 - k + 5/4 < R^2 # # The extra X=Y pixel adds 4 to the ringlength above, one on the diagonal in # each of the four quadrants, so # # ringlength = 8*k if X=Y pixel included # 8*k-4 if X=Y pixel not included # # The k calculation above is effectively asking where the circle line # intersects a diagonal X=Y+.5 and rounding down to integer Y on that # diagonal. The test at X=k+.5 is asking about a different diagonal X=Y+1.5 # and it doesn't seem there's a particularly easy relation between where the # circle falls on the first diagonal and where on the second. =for stopwords Ryde pixellated Math-PlanePath =head1 NAME Math::PlanePath::PixelRings -- pixellated concentric circles =head1 SYNOPSIS use Math::PlanePath::PixelRings; my $path = Math::PlanePath::PixelRings->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path puts points on the pixels of concentric circles using the midpoint ellipse drawing algorithm. 63--62--61--60--59 5 / \ 64 . 40--39--38 . 58 4 / / \ \ 65 . 41 23--22--21 37 . 57 3 / / / \ \ \ 66 . 42 24 10-- 9-- 8 20 36 . 56 2 | / / / \ \ \ | 67 43 25 11 . 3 . 7 19 35 55 1 | | | | / \ | | | | 67 44 26 12 4 1 2 6 18 34 54 Y=0 | | | | \ / 68 45 27 13 . 5 . 17 33 53 80 -1 | \ \ \ / / / | 69 . 46 28 14--15--16 32 52 . 79 -2 \ \ \ / / / 70 . 47 29--30--31 51 . 78 -3 \ \ / / 71 . 48--49--50 . 77 -4 \ / 72--73--74--75--76 -5 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 The way the algorithm works means the rings don't overlap. Each is 4 or 8 pixels longer than the preceding. If the ring follows the preceding tightly then it's 4 longer, for example N=18 to N=33. If it goes wider then it's 8 longer, for example N=54 to N=80 ring. The average extra is approximately 4*sqrt(2). The rings can be thought of as part-way between the diagonals like C and the corners like C. * ** ***** * * * * * * * * * * * * diagonal ring corner 5 points 6 points 9 points For example the N=54 to N=80 ring has a vertical part N=54,55,56 like a corner then a diagonal part N=56,57,58,59. In bigger rings the verticals are intermingled with the diagonals but the principle is the same. The number of vertical steps determines where it crosses the 45-degree line, which is at R*sqrt(2) but rounded according to the midpoint algorithm. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::PixelRings-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> For C<$n < 1> the return is an empty list, it being considered there are no negative points. The behaviour for fractional C<$n> is unspecified as yet. =item C<$n = $path-Exy_to_n ($x,$y)> Return an integer point number for coordinates C<$x,$y>. Each integer N is considered the centre of a unit square and an C<$x,$y> within that square returns N. Not every point of the plane is covered (like those marked by a "." in the sample above). If C<$x,$y> is not reached then the return is C. =back =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/RationalsTree.pm0000644000175000017500000015562113760375476020367 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # SB,CW N with same X,Y is those N which are palindromes below high 1-bit # as noted Claudio Bonanno and Stefano Isola, ``Orderings of the Rationals # and Dynamical Systems'', May 16, 2008. # cf A006995 binary palindromes, so always odd # A178225 characteristic of binary palindromes # A048700 binary palindromes odd length # A048701 binary palindromes even length # A044051 binary palindromes (B+1)/2, B odd so B+1 even # A044051-1 = (B-1)/2 strips low 1-bit to be palindromes below high 1-bit # Boyko B. Bantchev, "Fraction Space Revisited" # http://www.math.bas.bg/bantchev/articles/fractions.pdf # cf Ronald L. Graham, Donald E. Knuth, and Oren Patashnik, Concrete # Mathematics: A Foundation for Computer Science, Second # Edition. Addison-Wesley. 1994. # On Stern-Brocot tree. # cf A054429 permutation reverse within binary row # A057114 - permutation SB X -> X+1 # A057115 - permutation SB X -> X-1 # # high-to-low low-to-high # (X+Y)/Y Y/(X+Y) HCS AYT # X/(X+Y) (X+Y)/Y CW SB \ alt bit flips # Y/(X+Y) (X+Y)/X Drib Bird / # # 9 10 12 10 # 8 11 8 14 # 12 13 9 13 # 14 11 # 15 15 # # Stern-Brocot Calkin-Wilf #------------------------------------------------------------------------------ # HCS turn left when even number of 1-bits in N+1 # turn right when odd number of 1-bits in N+1 # # A010059 start=0: 1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,0 # match 1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,0 # PlanePathTurn planepath=RationalsTree,tree_type=HCS, turn_type=Left # # A010060 start=0: 0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0,1,1,0,1 # match 0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0,1,1,0,1 # PlanePathTurn planepath=RationalsTree,tree_type=HCS, turn_type=Right # # 10 | 768 50 58 896 # 9 | 384 49 52 60 57 448 640 # 8 | 192 27 31 224 320 # 7 | 96 25 26 30 29 112 160 41 42 # 6 | 48 56 80 # 5 | 24 13 15 28 40 21 23 44 # 4 | 12 14 20 22 36 # 3 | 6 7 10 11 18 19 34 # 2 | 3 5 9 17 33 # 1 | 1 2 4 8 16 32 64 128 256 512 # Y=0 | # +----------------------------------------------------- # X=0 1 2 3 4 5 6 7 8 9 10 # # 1/1 # /------------- -------------\ # 2/1 1/2 2,3 L,R # /---- ----\ /---- ----\ # 3/1 3/2 1/3 2/3 4,5,6,7 L,L,R,R # / \ / \ / \ / \ 8 12 # 4/1 5/2 4/3 5/3 1/4 2/5 3/4 3/5 L,L,R,L, R,R,L,R # / \ / \ / \ / \ / \ / \ / \ / \ # 5/1 7/2 7/3 8/3 5/4 7/5 7/4 8/5 1/5 2/7 3/7 3/8 4/5 5/7 4/7 5/8 # # * # / \ U=0 = X+Y, Y shear # / * D=1 = Y, X+Y shear+transpose # / \a = 0.1^k.1 # N # \ /b = 1.0^k.0 # \ * # \ / \c = 1.0^k.1 c=even bits, left # * # # F[-1]=1 F[0]=0 F[1]=1 F[2]=1 F[3]=2 F[4]=3 F[5]=5 ... # 1^k is F[k-1]*X+F[k]*Y, F[k]*X+F[k+1]*Y # X , Y 0 # Y, X+ Y 1 # X+ Y, X+2Y 2 # X+2Y, 2X+3Y 3 # 2X+3Y, 3X+5Y 4 # # then aX = F[k]*X+F[k+1]*Y + F[k+1]*X+F[k+2]*Y # = (F[k]+F[k+1])*X + (F[k+1]+F[k+2])*Y # = F[k+2]*X + F[k+3]*Y # aY = F[k+1]*X + F[k+2]*Y near X=phi*Y big # # 0^k is X+k*Y, Y # so bX = Y # bY = X+k*Y + Y = X+(k+1)*Y near Y axis # # c1X = Y # c1Y = X+Y # c2X = Y + k*(X+Y) = k*X + (k+1)*Y # c2Y = X+Y # cX = X+Y # cY = k*X + (k+1)*Y + X+Y = (k+1)X + (k+2)Y near X=Y # # * # / \ /a = 0.1^k.0 # / * # / \b = 0.1^k.1 # N # \ /c = 1.0^k.1 c=even bits, left # \ * # \ / # * #------------------------------------------------------------------------------ package Math::PlanePath::RationalsTree; use 5.004; use strict; use Carp 'croak'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'bit_split_lowtohigh', 'digit_join_lowtohigh'; *_divrem = \&Math::PlanePath::_divrem; use Math::PlanePath::CoprimeColumns; *_coprime = \&Math::PlanePath::CoprimeColumns::_coprime; # uncomment this to run the ### lines #use Smart::Comments; use constant parameter_info_array => [ { name => 'tree_type', share_key => 'tree_type_rationalstree', display => 'Tree Type', type => 'enum', default => 'SB', choices => ['SB','CW','AYT','HCS','Bird','Drib','L'], choices_display => ['SB','CW','AYT','HCS','Bird','Drib','L'], }, ]; use constant class_x_negative => 0; use constant class_y_negative => 0; sub x_minimum { my ($self) = @_; return ($self->{'tree_type'} eq 'L' ? 0 : 1); } use constant y_minimum => 1; use constant gcdxy_maximum => 1; # no common factor use constant tree_num_children_list => (2); # complete binary tree use constant tree_n_to_subheight => undef; # complete tree, all infinity { my %absdy_minimum = (# SB => 0, CW => 1, # AYT => 0, # Bird => 0, # Drib => 0, L => 1); sub absdy_minimum { my ($self) = @_; return $absdy_minimum{$self->{'tree_type'}} || 0; } } { # Drib apparent minimum dX=k dY=2*k+1 approaches dX=1,dY=2 my %dir_minimum_dxdy = (CW => [0,1], Drib => [1,2], L => [1,1], # at N=0 dX=1,dY=1 ); sub dir_minimum_dxdy { my ($self) = @_; return @{$dir_minimum_dxdy{$self->{'tree_type'}} || [1,0]}; } } { my %dir_maximum_dxdy = (SB => [1,-1], # CW => [0,0], Bird => [1,-1], # Drib => [0,0], HCS => [2,-1], # AYT => [0,0], # L => [0,0], # at 2^k-1 dX=k+1,dY=-1 so approach Dir=4 ); sub dir_maximum_dxdy { my ($self) = @_; return @{$dir_maximum_dxdy{$self->{'tree_type'}} || [0,0]}; } } { my %turn_any_straight = (# SB => 0, # CW => 0, Bird => 1, # straight at N=7 and N=8 # Drib => 0, AYT => 1, # straight at N=7 # HCS => 0, # L => 0, ); sub turn_any_straight { my ($self) = @_; return $turn_any_straight{$self->{'tree_type'}}; } } #------------------------------------------------------------------------------ my %attributes = (CW => [ n_start => 1, ], SB => [ n_start => 1, reverse_bits => 1 ], Drib => [ n_start => 1, alternating => 1 ], Bird => [ n_start => 1, alternating => 1, reverse_bits => 1 ], AYT => [ n_start => 1, sep1s => 1 ], HCS => [ n_start => 1, sep1s => 1, reverse_bits => 1 ], L => [ n_start => 0 ], ); sub new { my $self = shift->SUPER::new(@_); my $tree_type = ($self->{'tree_type'} ||= 'SB'); my $attributes = $attributes{$tree_type} || croak "Unrecognised tree type: ",$tree_type; %$self = (%$self, @$attributes); return $self; } sub n_to_xy { my ($self, $n) = @_; ### RationalsTree n_to_xy(): "$n" if ($n < $self->{'n_start'}) { return; } if (is_infinite($n)) { return ($n,$n); } # what to do for fractional $n? { my $int = int($n); if ($n != $int) { ### frac ... my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; ### x1,y1: "$x1, $y1" ### x2,y2: "$x2, $y2" ### dx,dy: "$dx, $dy" ### result: ($frac*$dx + $x1).', '.($frac*$dy + $y1) return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } my $zero = ($n * 0); # inherit bignum 0 my $one = $zero + 1; # inherit bignum 1 if ($self->{'n_start'} == 0) { # L tree adjust; $n += 2; } my @nbits = bit_split_lowtohigh($n); pop @nbits; ### lowtohigh sans high: @nbits if (! $self->{'reverse_bits'}) { @nbits = reverse @nbits; ### reverse to: @nbits } my $x = $one; my $y = $one; if ($self->{'sep1s'}) { foreach my $nbit (@nbits) { $x += $y; if ($nbit) { ($x,$y) = ($y,$x); } } } elsif ($self->{'alternating'}) { foreach my $nbit (@nbits) { ($x,$y) = ($y,$x); if ($nbit) { $x += $y; # (x,y) -> (x+y,x), including swap } else { $y += $x; # (x,y) -> (y,x+y), including swap } } } elsif ($self->{'tree_type'} eq 'L') { my $sub = 2; foreach my $nbit (@nbits) { if ($nbit) { $y += $x; # (x,y) -> (x,x+y) $sub = 0; } else { $x += $y; # (x,y) -> (x+y,y) } } $x -= $sub; # -2 at N=00...000 all zero bits } else { ### nbits apply CW: @nbits foreach my $nbit (@nbits) { # high to low if ($nbit) { $x += $y; # (x,y) -> (x+y,y) } else { $y += $x; # (x,y) -> (x,x+y) } } } ### result: "$x, $y" return ($x,$y); } sub xy_is_visited { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); if ($self->{'tree_type'} eq 'L' && $x == 0 && $y == 1) { return 1; } if ($x < 1 || $y < 1 || ! _coprime($x,$y)) { return 0; } return 1; } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### RationalsTree xy_to_n(): "$x,$y $self->{'tree_type'}" if ($x < $self->{'n_start'} || $y < 1) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my @quotients = _xy_to_quotients($x,$y) or return undef; # $x,$y have a common factor ### @quotients my @nbits; if ($self->{'sep1s'}) { $quotients[0]++; # the integer part, making it 1 or more foreach my $q (@quotients) { push @nbits, (0) x ($q-1), 1; # runs of "000..0001" } pop @nbits; # no high 1-bit separator } else { if ($quotients[0] < 0) { # X=0,Y=1 in tree_type="L" return $self->{'n_start'}; } my $bit = 1; foreach my $q (@quotients) { push @nbits, ($bit) x $q; $bit ^= 1; # alternate runs of "00000" or "11111" } ### nbits in quotient order: @nbits if ($self->{'alternating'}) { # Flip every second bit, starting from the second lowest. for (my $i = 1; $i <= $#nbits; $i += 2) { $nbits[$i] ^= 1; } } if ($self->{'tree_type'} eq 'L') { # Flip all bits. my $anyones = 0; foreach my $nbit (@nbits) { $nbit ^= 1; # mutate array $anyones ||= $nbit; } unless ($anyones) { push @nbits, 0,0; } } } if ($self->{'reverse_bits'}) { @nbits = reverse @nbits; } push @nbits, 1; # high 1-bit ### @nbits my $n = digit_join_lowtohigh (\@nbits, 2, $x*0*$y); # inherit bignum 0 if ($self->{'tree_type'} eq 'L') { return $n-2; } else { return $n; } } # Return a list of the quotients from Euclid's greatest common divisor # algorithm on X,Y. This is also the terms of the continued fraction # expansion of rational X/Y. # # The last term, the last in the list, is decremented since this is what the # code above requires. This term is the top-most quotient in for example # gcd(7,1) is 7=7*1+0 with q=7 returned as 6. # # If $x,$y have a common factor then the return is an empty list. # If $x,$y have no common factor then the returned list is always one or # more quotients. # sub _xy_to_quotients { my ($x,$y) = @_; my @ret; for (;;) { my ($q, $r) = _divrem($x,$y); push @ret, $q; last unless $r; $x = $y; $y = $r; } if ($y > 1) { ### found Y>1 common factor, no N at this X,Y ... return; } $ret[-1]--; return @ret; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### rect_to_n_range() $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### $x2 ### $y2 if ($x2 < 1 || $y2 < 1) { ### no values, rect below first quadrant if ($self->{'n_start'}) { return (1,0); } else { return (0,0); } } my $zero = ($x1 * 0 * $y1 * $x2 * $y2); # inherit bignum ### $zero if ($x1 < 1) { $x1 = 1; } if ($y1 < 1) { $y1 = 1; } # # big x2, small y1 # # big y2, small x1 # my $level = _bingcd_max ($y2,$x1); # ### $level # { # my $l2 = _bingcd_max ($x2,$y1); # ### $l2 # if ($l2 > $level) { $level = $l2; } # } my $level = max($x1,$x2,$y1,$y2); return ($self->{'n_start'}, $self->{'n_start'} + (2+$zero) ** ($level + 3)); } sub _bingcd_max { my ($x,$y) = @_; ### _bingcd_max(): "$x,$y" if ($x < $y) { ($x,$y) = ($y,$x) } ### div: int($x/$y) ### bingcd: int($x/$y) + $y return int($x/$y) + $y + 1; } # ### fib: _fib_log($y) # # ENHANCE-ME: log base PHI, or something close for BigInt # # 2*log2() means log base sqrt(2)=1.4 instead of PHI=1.6 # # # # use constant 1.02; # for leading underscore # # use constant _PHI => (1 + sqrt(5)) / 2; # # # sub _fib_log { # my ($x) = @_; # ### _fib_log(): $x # my $f0 = ($x * 0); # my $f1 = $f0 + 1; # my $count = 0; # while ($x > $f0) { # $count++; # ($f0,$f1) = ($f1,$f0+$f1); # } # return $count; # } #------------------------------------------------------------------------------ use constant tree_num_roots => 1; # N=1 basis children 2N,2N+1 # N=S basis 2(N-(S-1))+(S-1) # = 2N - 2(S-1) + (S-1) # = 2N - (S-1) sub tree_n_children { my ($self, $n) = @_; my $n_start = $self->{'n_start'}; if ($n >= $n_start) { $n = 2*$n - $n_start; return ($n+1, $n+2); } else { return; } } sub tree_n_num_children { my ($self, $n) = @_; return ($n >= $self->{'n_start'} ? 2 : undef); } sub tree_n_parent { my ($self, $n) = @_; $n = $n - $self->{'n_start'}; # N=0 basis, and warn if $n==undef if ($n > 0) { return int(($n-1)/2) + $self->{'n_start'}; } else { return undef; } } sub tree_n_to_depth { my ($self, $n) = @_; ### RationalsTree tree_n_to_depth(): $n $n = $n - $self->{'n_start'}; # N=0 basis, and warn if $n==undef unless ($n >= 0) { return undef; } my ($pow, $exp) = round_down_pow ($n+1, 2); return $exp; } sub tree_depth_to_n { my ($self, $depth) = @_; return ($depth >= 0 ? 2**$depth + $self->{'n_start'}-1 : undef); } # (2^(d+1)+s-1)-1 = 2^(d+1)+s-2 sub tree_depth_to_n_end { my ($self, $depth) = @_; return ($depth >= 0 ? 2**($depth+1) + $self->{'n_start'}-2 : undef); } sub tree_depth_to_n_range { my ($self, $depth) = @_; if ($depth >= 0) { my $pow = 2**$depth; return ($pow + $self->{'n_start'}-1, 2*$pow + $self->{'n_start'}-2); } return; # no such $depth } sub tree_depth_to_width { my ($self, $depth) = @_; return ($depth >= 0 ? 2**$depth : undef); } 1; __END__ # xy_to_n() post-processing CW to make AYT # # if ($self->{'tree_type'} eq 'AYT') { # # AYT shift-xor "N xor (N<<1)" each bit xor with the one below it. But # # the high 1-bit is left unchanged, hence "$#nbits-1". At the low end # # for "N<<1" a 1-bit is shifted in, which is arranged by letting $i-1 # # become -1 to get the endmost array element which is the high 1-bit. # foreach my $i (reverse 0 .. $#nbits-1) { # $nbits[$i] ^= $nbits[$i-1]; # } # } # # =head2 Calkin-Wilf Tree -- X,Y to Next X,Y # # Successive values of the CW tree can be calculated using a method be Moshe # Newman. # # X Y # N is --- N+1 is --------------- # Y X+Y - 2*(X % Y) 0 <= X%Y < Y # # This means that the tree X,Y values can be iterated by keeping just a # current X,Y pair. # # dX = Y - X # dY = X+Y - 2*(X%Y) - Y # = X - 2*(X%Y) # # floor(X/Y) = count trailing 1-bits of N # 10111 # 11000 increment # floor(X/Y) = integer part = first term of continued fraction =for stopwords eg Ryde OEIS ie Math-PlanePath coprime encodings Moritz Achille Brocot Stern-Brocot mediant Calkin Wilf Calkin-Wilf 1abcde 1edcba Andreev Yu-Ting Shen AYT Ralf Hinze Haskell subtrees xoring Drib RationalsTree unflipped GCD Luschny Jerzy Czyz Minkowski Nstart Shallit's HCS Ndepth N-Ndepth Nparent subtree LRRL parameterization parameterized Jacobsthal Thue-Morse ceil Matematicheskoe Prosveshchenie Ser DOI generalization =head1 NAME Math::PlanePath::RationalsTree -- rationals by tree =head1 SYNOPSIS use Math::PlanePath::RationalsTree; my $path = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path enumerates reduced rational fractions X/Y E 0, ie. X and Y having no common factor. The rationals are traversed by rows of a binary tree which represents a coprime pair X,Y by steps of a subtraction-only greatest common divisor algorithm which proves them coprime. Or equivalently by bit runs with lengths which are the quotients in the division-based Euclidean GCD algorithm and which are also the terms in the continued fraction representation of X/Y. The SB, CW, AYT, HCS, Bird and Drib trees all have the same set of X/Y rationals in a row, but in a different order due to different encodings of the N value. See the author's mathematical write-up for a proof that these are the only trees with a fixed set of matrices. =over L =back The bit runs mean that N values are quite large for relatively modest sized rationals. For example in the SB tree 167/3 is N=288230376151711741, a 58-bit number. The tendency is for the tree to make excursions out to large rationals while only slowly filling in small ones. The worst is the integer X/1 for which N has X many bits, and similarly 1/Y is Y bits. See F for a printout of all the trees. =head2 Stern-Brocot Tree XXThe default C"SB"> is the tree of Moritz Stern and Achille Brocot. depth N ----- ------- 0 1 1/1 ------ ------ 1 2 to 3 1/2 2/1 / \ / \ 2 4 to 7 1/3 2/3 3/2 3/1 | | | | | | | | 3 8 to 15 1/4 2/5 3/5 3/4 4/3 5/3 5/2 4/1 Within a row the fractions increase in value. Each row of the tree is a repeat of the previous row as first X/(X+Y) and then (X+Y)/Y. For example depth=1 1/2, 2/1 depth=2 1/3, 2/3 X/(X+Y) of previous row 3/2, 3/1 (X+Y)/Y of previous row Plotting the N values by X,Y is as follows. The unused X,Y positions are where X and Y have a common factor. For example X=6,Y=2 has common factor 2 so is never reached. tree_type => "SB" 10 | 512 35 44 767 9 | 256 33 39 40 46 383 768 8 | 128 18 21 191 384 7 | 64 17 19 20 22 95 192 49 51 6 | 32 47 96 5 | 16 9 10 23 48 25 26 55 4 | 8 11 24 27 56 3 | 4 5 12 13 28 29 60 2 | 2 6 14 30 62 1 | 1 3 7 15 31 63 127 255 511 1023 Y=0 | ---------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 The X=1 vertical is the fractions 1/Y which is at the left of each tree row, at N value Nstart = 2^depth The Y=1 horizontal is the X/1 integers at the end each row which is Nend = 2^(depth+1)-1 Numbering nodes of the tree by rows starting from 1 means N without the high 1 bit is the offset into the row. For example binary N="1011" is "011"=3 into the row. Those bits after the high 1 are also the directions to follow down the tree to a node, with 0=left and 1=right. So N="1011" binary goes from the root 0=left then twice 1=right to reach X/Y=3/4 at N=11 decimal. =cut # O/O O/E E/O X/(X+Y) -> O/E O/O E/O A B C -> B A C # (X+Y)/Y -> E/O O/E O/O -> C B A =pod =head2 Stern-Brocot Mediant Writing the parents between the children as an "in-order" tree traversal to a given depth has all values in increasing order (the same as each row individually is in increasing order). 1/1 1/2 | 2/1 1/3 | 2/3 | 3/2 | 3/1 | | | | | | | 1/3 1/2 2/3 1/1 3/2 2/1 3/1 ^ | next level (1+3)/(1+2) = 4/3 mediant New values at the next level of this flattening are a "mediant" (x1+x2)/(y1+y2) formed from the left and right parent. So the next level 4/3 shown is left parent 1/1 and right parent 3/2 giving mediant (1+3)/(1+2)=4/3. At the left end a preceding 0/1 is imagined. At the right end a following 1/0 is imagined, so as to have 1/(depth+1) and (depth+1)/1 at the ends for a total 2^depth many new values. The turn sequence left or right along the row depth E= 2 is by a repeating LRRL pattern, except the first and last are always R. (See the author's mathematical write-up for details.) RRRL,LRRL,LRRL,LRRL,LRRL,LRRL,LRRL,LRRR # row N=32 to N=63 =head2 Calkin-Wilf Tree XXC"CW"> selects the tree of Calkin and Wilf, =over Neil Calkin and Herbert Wilf, "Recounting the Rationals", American Mathematical Monthly, volume 107, number 4, April 2000, pages 360-363. L, L, L =back As noted above, the values within each row are the same as the Stern-Brocot, but in a different order. N=1 1/1 ------ ------ N=2 to N=3 1/2 2/1 / \ / \ N=4 to N=7 1/3 3/2 2/3 3/1 | | | | | | | | N=8 to N=15 1/4 4/3 3/5 5/2 2/5 5/3 3/4 4/1 Going by rows the denominator of one value becomes the numerator of the next. So at 4/3 the denominator 3 becomes the numerator of 3/5 to the right. These values are Stern's diatomic sequence. Each row is symmetric in reciprocals, ie. reading from right to left is the reciprocals of reading left to right. The numerators read left to right are the denominators read right to left. A node descends as X/Y / \ X/(X+Y) (X+Y)/Y Taking these formulas in reverse up the tree shows how it relates to a subtraction-only greatest common divisor. At a given node the smaller of P or Q is subtracted from the bigger, P/(Q-P) (P-Q)/P / or \ P/Q P/Q Plotting the N values by X,Y is as follows. The X=1 vertical and Y=1 horizontal are the same as the SB above, but the values in between are re-ordered. tree_type => "CW" 10 | 512 56 38 1022 9 | 256 48 60 34 46 510 513 8 | 128 20 26 254 257 7 | 64 24 28 18 22 126 129 49 57 6 | 32 62 65 5 | 16 12 10 30 33 25 21 61 4 | 8 14 17 29 35 3 | 4 6 9 13 19 27 39 2 | 2 5 11 23 47 1 | 1 3 7 15 31 63 127 255 511 1023 Y=0 | ------------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 At each node the left leg is S 1> and the right leg is S<(X+Y)/Y E 1>, which means N is even above the X=Y diagonal and odd below. In general each right leg increments the integer part of the fraction, X/Y right leg each time (X+Y)/Y = 1 + X/Y (X+2Y)/Y = 2 + X/Y (X+3Y)/Y = 3 + X/Y etc This means the integer part is the trailing 1-bits of N, floor(X/Y) = count trailing 1-bits of N eg. 7/2 is at N=23 binary "10111" which has 3 trailing 1-bits for floor(7/2)=3 N values for the SB and CW trees are converted by reversing bits except the highest. So at a given X,Y position SB N = 1abcde SB <-> CW by reversing bits CW N = 1edcba except the high 1-bit For example at X=3,Y=4 the SB tree has N=11 = "1011" binary and the CW has N=14 binary "1110", a reversal of the bits below the high 1. N to X/Y in the CW tree can be calculated keeping track of just an X,Y pair and descending to X/(X+Y) or (X+Y)/Y using the bits of N from high to low. The relationship between the SB and CW N's means the same can be used to calculate the SB tree by taking the bits of N from low to high instead. See also L for a generalization of CW to ternary or higher trees, ie. descending to 3 or more children at each node. =head2 Yu-Ting and Andreev Tree XXC"AYT"> selects the tree described independently by Yu-Ting and Andreev. =over Shen Yu-Ting, "A Natural Enumeration of Non-Negative Rational Numbers -- An Informal Discussion", American Mathematical Monthly, 87, 1980, pages 25-29. L D. N. Andreev, "On a Wonderful Numbering of Positive Rational Numbers", Matematicheskoe Prosveshchenie, Series 3, volume 1, 1997, pages 126-134 L =back =cut # Andreev also at # L =pod Their constructions are a one-to-one mapping between integer N and rational X/Y as a way of enumerating the rationals. This is not designed to be a tree as such, but the result is the same 2^level rows as the above trees. The X/Y values within each row are the same, but in a different order. N=1 1/1 ------ ------ N=2 to N=3 2/1 1/2 / \ / \ N=4 to N=7 3/1 1/3 3/2 2/3 | | | | | | | | N=8 to N=15 4/1 1/4 4/3 3/4 5/2 2/5 5/3 3/5 Each fraction descends as follows. The left is an increment and the right is reciprocal of the increment. X/Y / \ X/Y + 1 1/(X/Y + 1) which means X/Y / \ (X+Y)/Y Y/(X+Y) The left leg (X+Y)/Y is the same the CW has on its right leg. But Y/(X+Y) is not the same as the CW (the other there being X/(X+Y)). The left leg increments the integer part, so the integer part is given by (in a fashion similar to CW 1-bits above) floor(X/Y) = count trailing 0-bits of N plus one extra if N=2^k N=2^k is one extra because its trailing 0-bits started from N=1 where floor(1/1)=1 whereas any other odd N starts from some floor(X/Y)=0. XThe Y/(X+Y) right leg forms the Fibonacci numbers F(k)/F(k+1) at the end of each row, ie. at Nend=2^(level+1)-1. And as noted by Andreev, successive right leg fractions N=4k+1 and N=4k+3 add up to 1, X/Y at N=4k+1 + X/Y at N=4k+3 = 1 Eg. 2/5 at N=13 and 3/5 at N=15 add up to 1 Plotting the N values by X,Y gives =cut # math-image --path=RationalsTree,tree_type=AYT --all --output=numbers_xy --size=70x11 =pod tree_type => "AYT" 10 | 513 41 43 515 9 | 257 49 37 39 51 259 514 8 | 129 29 31 131 258 7 | 65 25 21 23 27 67 130 50 42 6 | 33 35 66 5 | 17 13 15 19 34 26 30 38 4 | 9 11 18 22 36 3 | 5 7 10 14 20 28 40 2 | 3 6 12 24 48 1 | 1 2 4 8 16 32 64 128 256 512 Y=0 | ---------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 N=1,2,4,8,etc on the Y=1 horizontal is the X/1 integers at Nstart=2^level=2^X. N=1,3,5,9,etc in the X=1 vertical is the 1/Y fractions. Those fractions always immediately follow the corresponding integer, so N=Nstart+1=2^(Y-1)+1 in that column. In each node the left leg (X+Y)/Y E 1 and the right leg Y/(X+Y) E 1, which means odd N is above the X=Y diagonal and even N is below. XThe tree structure corresponds to Johannes Kepler's tree of fractions (see L). That tree starts from 1/2 and makes fractions A/B with AEB by descending to A/(A+B) and B/(A+B). Those descents are the same as the AYT tree and the two are related simply by A = Y AYT denominator is Kepler numerator B = X+Y AYT sum num+den is the Kepler denominator X = B-A inverse Y = A =head2 HCS Continued Fraction XXXC"HCS"> selects continued fraction terms coded as bit runs 1000...00 from high to low, as per Paul D. Hanna and independently Czyz and Self. =over L Jerzy Czyz and William Self, "The Rationals Are Countable: Euclid's Proof", The College Mathematics Journal, volume 34, number 5, November 2003, page 367. L L, L =back =cut # also at, but an awful site, # http://www.academia.edu/5233434/Numeri =pod This arises also in a radix=1 variation of Jeffrey Shallit's digit-based continued fraction encoding. See L. If the continued fraction of X/Y is 1 X/Y = a + ------------ a >= 0 1 b + ----------- b,c,etc >= 1 1 c + ------- ... + 1 --- z >= 2 z then the N value is bit runs of lengths a,b,c etc. N = 1000 1000 1000 ... 1000 \--/ \--/ \--/ \--/ a+1 b c z-1 Each group is 1 or more bits. The +1 in "a+1" makes the first group 1 or more bits, since a=0 occurs for any X/YE=1. The -1 in "z-1" makes the last group 1 or more since zE=2. N=1 1/1 ------ ------ N=2 to N=3 2/1 1/2 / \ / \ N=4 to N=7 3/1 3/2 1/3 2/3 | | | | | | | | N=8 to N=15 4/1 5/2 4/3 5/3 1/4 2/5 3/4 3/5 The result is a bit reversal of the N values in the AYT tree. AYT N = binary "1abcde" AYT <-> HCS bit reversal HCS N = binary "1edcba" For example at X=4,Y=7 the AYT tree is N=11 binary "10111" whereas HCS there has N=30 binary "11110", a reversal of the bits below the high 1. Plotting by X,Y gives =cut # math-image --path=RationalsTree,tree_type=HCS --all --output=numbers_xy --size=70x11 =pod tree_type => "HCS" 10 | 768 50 58 896 9 | 384 49 52 60 57 448 640 8 | 192 27 31 224 320 7 | 96 25 26 30 29 112 160 41 42 6 | 48 56 80 5 | 24 13 15 28 40 21 23 44 4 | 12 14 20 22 36 3 | 6 7 10 11 18 19 34 2 | 3 5 9 17 33 1 | 1 2 4 8 16 32 64 128 256 512 Y=0 | +----------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 N=1,2,4,etc in the row Y=1 are powers-of-2, being integers X/1 having just a single group of bits N=1000..000. N=1,3,6,12,etc in the column X=1 are 3*2^(Y-1) corresponding to continued fraction S<0 + 1/Y> so terms 0,Y making runs 1,Y-1 and so bits N=11000...00. XThe turn sequence left or right following successive X,Y points is the Thue-Morse sequence. A proof of this can be found in the author's mathematical write-up (above). count 1-bits in N+1 turn at N ------------------- --------- odd right even left =head2 Bird Tree XC"Bird"> selects the Bird tree, =over Ralf Hinze, "Functional Pearls: The Bird tree", Journal of Functional Programming, volume 19, issue 5, September 2009, pages 491-508. DOI 10.1017/S0956796809990116 L =back It's expressed recursively, illustrating Haskell programming features. The left subtree is the tree plus one and take the reciprocal. The right subtree is conversely the reciprocal first then add one, 1 1 -------- and ---- + 1 tree + 1 tree which means Y/(X+Y) and (X+Y)/X taking N bits low to high. N=1 1/1 ------ ------ N=2 to N=3 1/2 2/1 / \ / \ N=4 to N=7 2/3 1/3 3/1 3/2 | | | | | | | | N=8 to N=15 3/5 3/4 1/4 2/5 5/2 4/1 4/3 5/3 Plotting by X,Y gives tree_type => "Bird" 10 | 682 41 38 597 9 | 341 43 45 34 36 298 938 8 | 170 23 16 149 469 7 | 85 20 22 17 19 74 234 59 57 6 | 42 37 117 5 | 21 11 8 18 58 28 31 61 4 | 10 9 29 30 50 3 | 5 4 14 15 25 24 54 2 | 2 7 12 27 52 1 | 1 3 6 13 26 53 106 213 426 853 Y=0 | ---------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 Notice that unlike the other trees N=1,2,5,10,etc in the X=1 vertical for fractions 1/Y is not the row start or end, but instead are on a zigzag through the middle of the tree binary N=1010...etc alternate 1 and 0 bits. The integers X/1 in the Y=1 vertical are similar, but N=11010...etc starting the alternation from a 1 in the second highest bit, since those integers are in the right hand half of the tree. The Bird tree N values are related to the SB tree by inverting every second bit starting from the second after the high 1-bit, Bird N=1abcdefg.. binary 101010.. xor, so b,d,f etc flip 0<->1 SB N=1aBcDeFg.. to make B,D,F For example 3/4 in the SB tree is at N=11 = binary 1011. Xor with 0010 for binary 1001 N=9 which is 3/4 in the Bird tree. The same xor goes back the other way Bird tree to SB tree. This xoring is a mirroring in the tree, swapping left and right at each level. Only every second bit is inverted because mirroring twice puts it back to the ordinary way on even rows. =head2 Drib Tree XC"Drib"> selects the Drib tree by Ralf Hinze. =over L =back It reverses the bits of N in the Bird tree (in a similar way that the SB and CW are bit reversals of each other). N=1 1/1 ------ ------ N=2 to N=3 1/2 2/1 / \ / \ N=4 to N=7 2/3 3/1 1/3 3/2 | | | | | | | | N=8 to N=15 3/5 5/2 1/4 4/3 3/4 4/1 2/5 5/3 The descendants of each node are X/Y / \ Y/(X+Y) (X+Y)/X XThe endmost fractions of each row are Fibonacci numbers, F(k)/F(k+1) on the left and F(k+1)/F(k) on the right. =cut # math-image --path=RationalsTree,tree_type=Drib --all --output=numbers_xy =pod tree_type => "Drib" 10 | 682 50 44 852 9 | 426 58 54 40 36 340 683 8 | 170 30 16 212 427 7 | 106 18 22 24 28 84 171 59 51 6 | 42 52 107 5 | 26 14 8 20 43 19 31 55 4 | 10 12 27 23 41 3 | 6 4 11 15 25 17 45 2 | 2 7 9 29 37 1 | 1 3 5 13 21 53 85 213 341 853 Y=0 | ------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 In each node descent the left Y/(X+Y) E 1 and the right (X+Y)/X E 1, which means even N is above the X=Y diagonal and odd N is below. Because Drib/Bird are bit reversals like CW/SB are bit reversals, the xor procedure described above which relates BirdE-ESB applies to DribE-ECW, but working from the second lowest bit upwards, ie. xor binary "0..01010". For example 4/1 is at N=15 binary 1111 in the CW tree. Xor with 0010 for 1101 N=13 which is 4/1 in the Drib tree. =head2 L Tree XC"L"> selects the L-tree by Peter Luschny. =over L =back It's a row-reversal of the CW tree with a shift to include zero as 0/1. N=0 0/1 ------ ------ N=1 to N=2 1/2 1/1 / \ / \ N=3 to N=8 2/3 3/2 1/3 2/1 | | | | | | | | N=9 to N=16 3/4 5/3 2/5 5/2 3/5 4/3 1/4 3/1 Notice in the N=9 to N=16 row rationals 3/4 to 1/4 are the same as in the CW tree but read right-to-left. =cut # math-image --path=RationalsTree,tree_type=L --all --output=numbers_xy --size=70x11 =pod tree_type => "L" 10 | 1021 37 55 511 9 | 509 45 33 59 47 255 1020 8 | 253 25 19 127 508 7 | 125 21 17 27 23 63 252 44 36 6 | 61 31 124 5 | 29 9 11 15 60 20 24 32 4 | 13 7 28 16 58 3 | 5 3 12 8 26 18 54 2 | 1 4 10 22 46 1 | 0 2 6 14 30 62 126 254 510 1022 2046 Y=0 | ------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 N=0,2,6,14,30,etc along the row at Y=1 are powers 2^(X+1)-2. N=1,5,13,29,etc in the column at X=1 are similar powers 2^Y-3. =head2 Common Characteristics The SB, CW, Bird, Drib, AYT and HCS trees have the same set of rationals in each row, just in different orders. The properties of Stern's diatomic sequence mean that within a row the totals are row N=2^depth to N=2^(depth+1)-1 inclusive sum X/Y = (3 * 2^depth - 1) / 2 sum X = 3^depth sum 1/(X*Y) = 1 For example the SB tree depth=2, N=4 to N=7, sum X/Y = 1/3 + 2/3 + 3/2 + 3/1 = 11/2 = (3*2^2-1)/2 sum X = 1+2+3+3 = 9 = 3^2 sum 1/(X*Y) = 1/(1*3) + 1/(2*3) + 1/(3*2) + 1/(3*1) = 1 Many permutations are conceivable within a row, but the ones here have some relationship to X/Y descendants, tree sub-forms or continued fractions. As an encoding of continued fraction terms by bit runs the combinations are bit encoding high to low low to high ---------------- ----------- ----------- 0000 1111 runs SB CW 0101 1010 alternating Bird Drib 1000 1000 runs HCS AYT A run of alternating 101010 ends where the next bit is the oppose of the expected alternating 0,1. This is a doubled bit 00 or 11. An electrical engineer would think of it as a phase shift. =head2 Minkowski Question Mark The Minkowski question mark function is a sum of the terms in the continued fraction representation of a real number. If q0,q1,q2,etc are those terms then the question mark function "?(r)" is 1 1 1 ?(r) = 2 * (1 - ---- * (1 - ---- * (1 - ---- * (1 - ... 2^q0 2^q1 2^q2 1 1 1 = 2 * (1 - ---- + --------- - ------------ + ... ) 2^q0 2^(q0+q1) 2^(q0+q1+q2) For rational r the continued fraction q0,q1,q2,etc is finite and so the ?(r) sum is finite and rational. The pattern of + and - in the terms gives runs of bits the same as the N values in the Stern-Brocot tree. The RationalsTree code can calculate the ?(r) function by rational r=X/Y N = xy_to_n(X,Y) tree_type=>"SB" depth = floor(log2(N)) # row containing N (depth=0 at top) Ndepth = 2^depth # start of row containing N 2*(N-Ndepth) + 1 ?(r) = ---------------- Ndepth The effect of N-Ndepth is to remove the high 1-bit, leaving an offset into the row. 2*(..)+1 appends an extra 1-bit at the end. The division by Ndepth scales down from integer N to a fraction. N = 1abcdef integer, in binary ?(r) = a.bcdef1 binary fraction For example ?(2/3) is X=2,Y=3 which is N=5 in the SB tree. It is at depth=2, Ndepth=2^2=4, and so ?(2/3)=(2*(5-4)+1)/4=3/4. Or written in binary N=101 gives Ndepth=100 and N-Ndepth=01 so 2*(N-Ndepth)+1=011 and divide by Ndepth=100 for ?=0.11. In practice this is not a very efficient way to handle the question function, since the bit runs in the N values may become quite large for relatively modest fractions. (L may be better, and also allows repeating terms from quadratic irrationals to be represented exactly.) =head2 Pythagorean Triples Pythagorean triples A^2+B^2=C^2 can be generated by A=P^2-Q^2, B=2*P*Q. If PEQE1 with P,Q no common factor and one odd the other even then this gives all primitive triples, being primitive in the sense of A,B,C no common factor (L). In the Calkin-Wilf tree the parity of X,Y pairs are as follows. Pairs X,Y with one odd the other even are N=0 or 2 mod 3. CW tree X/Y -------- N=0 mod 3 even/odd N=1 mod 3 odd/odd N=2 mod 3 odd/even This occurs because the numerators are the Stern diatomic sequence and the denominators likewise but offset by 1. The Stern diatomic sequence is a repeating pattern even,odd,odd (eg. per L). The XEY pairs in the CW tree are the right leg of each node, which is N odd. so CW tree N=3 or 5 mod 6 gives X>Y one odd the other even index t=1,2,3,etc to enumerate such pairs N = 3*t if t odd 3*t-1 if t even X2 of each 6 points are used. In a given row it's width/3 but rounded up or down according to where the 3,5mod6 falls on the N=2^depth start, which is either floor or ceil according to depth odd or even, NumPQ(depth) = floor(2^depth / 3) for depth=even ceil (2^depth / 3) for depth=odd = 0, 1, 1, 3, 5, 11, 21, 43, 85, 171, 341, ... These are the Jacobsthal numbers, which in binary are 101010...101 and 1010...1011. For the other tree types the various bit transformations which map N positions between the trees can be applied to the above N=3or5 mod 6. The simplest is the L tree where the N offset and row reversal gives N=0or4 mod 6. The SB tree is a bit reversal of the CW, as described above, and for the Pythagorean N this gives SB tree N=0 or 2 mod 2 and N="11...." in binary gives X>Y one odd the other even N="11..." binary is the bit reversal of the CW N=odd "1...1" condition. This bit pattern is those N in the second half of each row, which is where the X/Y E 1 rationals occur. The N=0or2 mod 3 condition is unchanged by the bit reversal. N=0or2 mod 3 precisely when bitreverse(N)=0or2 mod 3. For SB whether it's odd/even or even/odd at N=0or2 mod 3 alternates between rows. The two are both wanted, they just happen to switch in each row. SB tree X/Y depth=even depth=odd ---------- --------- N=0 mod 3 odd/even even/odd N=1 mod 3 odd/odd odd/odd <- exclude for Pythagorean N=2 mod 3 even/odd odd/even =head1 FUNCTIONS See L for behaviour common to all path classes. =over =item C<$path = Math::PlanePath::RationalsTree-Enew ()> =item C<$path = Math::PlanePath::RationalsTree-Enew (tree_type =E $str)> Create and return a new path object. C (a string) can be "SB" Stern-Brocot "CW" Calkin-Wilf "AYT" Yu-Ting, Andreev "HCS" "Bird" "Drib" "L" =item C<$n = $path-En_start()> Return the first N in the path. This is 1 for SB, CW, AYT, HCS, Bird and Drib, but 0 for L. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> Return a range of N values which occur in a rectangle with corners at C<$x1>,C<$y1> and C<$x2>,C<$y2>. The range is inclusive. For reference, C<$n_hi> can be quite large because within each row there's only one new X/1 integer and 1/Y fraction. So if X=1 or Y=1 is included then roughly C<$n_hi = 2**max(x,y)>. If min(x,y) is bigger than 1 then it reduces a little to roughly 2**(max/min + min). =back =head2 Tree Methods XEach point has 2 children, so the path is a complete binary tree. =over =item C<@n_children = $path-Etree_n_children($n)> Return the two children of C<$n>, or an empty list if C<$n E 1> (ie. before the start of the path). This is simply C<2*$n, 2*$n+1>. Written in binary the children are C<$n> with an extra bit appended, a 0-bit or a 1-bit. =item C<$num = $path-Etree_n_num_children($n)> Return 2, since every node has two children. If C<$nE1>, ie. before the start of the path, then return C. =item C<$n_parent = $path-Etree_n_parent($n)> Return the parent node of C<$n>. Or return C if C<$n E= 1> (the top of the tree). This is simply Nparent = floor(N/2), ie. strip the least significant bit from C<$n>. (Undo what C appends.) =item C<$depth = $path-Etree_n_to_depth($n)> Return the depth of node C<$n>, or C if there's no point C<$n>. The top of the tree at N=1 is depth=0, then its children depth=1, etc. This is simply floor(log2(N)) since the tree has 2 nodes per point. For example N=4 through N=7 are all depth=2. The L tree starts at N=0 and the calculation becomes floor(log2(N+1)) there. =item C<$n = $path-Etree_depth_to_n($depth)> =item C<$n = $path-Etree_depth_to_n_end($depth)> Return the first or last N at tree level C<$depth> in the path, or C if nothing at that depth or not a tree. The top of the tree is depth=0. The structure of the tree means the first N is at C<2**$depth>, or for the L tree S>. The last N is C<2**($depth+1)-1>, or for the L tree C<2**($depth+1)>. =back =head2 Tree Descriptive Methods =over =item C<$num = $path-Etree_num_children_minimum()> =item C<$num = $path-Etree_num_children_maximum()> Return 2 since every node has 2 children so that's both the minimum and maximum. =item C<$bool = $path-Etree_any_leaf()> Return false, since there are no leaf nodes in the tree. =back =cut # =head1 FORMULAS =pod =head1 OEIS The trees are in Sloane's Online Encyclopedia of Integer Sequences in various forms, =over L (etc) =back tree_type=SB A007305 X, extra initial 0,1 A047679 Y A057431 X,Y pairs (initial extra 0/1,1/0) A007306 X+Y sum, Farey 0 to 1 part (extra 1,1) A153036 int(X/Y), integer part A088696 length of continued fraction SB left half (X/Y<1) A153778 X mod 2 A004755 N of frac+1, so X+Y,Y tree_type=CW A002487 X and Y, Stern diatomic sequence (extra 0) A070990 Y-X diff, Stern diatomic first diffs (less 0) A070871 X*Y product A007814 int(X/Y), integer part, count trailing 1-bits which is count trailing 0-bits of N+1 A086893 N position of Fibonacci F[n+1]/F[n], N = binary 1010..101 A061547 N position of Fibonacci F[n]/F[n+1], N = binary 11010..10 A047270 3or5 mod 6, being N positions of X>Y not both odd which can generate primitive Pythagorean triples tree_type=AYT A020650 X A020651 Y (Kepler numerator) A086592 X+Y sum (Kepler denominator) A135523 int(X/Y), integer part, count trailing 0-bits plus 1 extra if N=2^k tree_type=HCS A229742 X, extra initial 0/1 A071766 Y A071585 X+Y sum tree_type=Bird A162909 X A162910 Y A081254 N of row Y=1, N = binary 1101010...10 A000975 N of column X=1, N = binary 101010...10 tree_type=Drib A162911 X A162912 Y A086893 N of row Y=1, N = binary 110101..0101 (ending 1) A061547 N of column X=1, N = binary 110101..010 (ending 0) tree_type=L A174981 X A002487 Y, same as CW X,Y, Stern diatomic A047233 0or4 mod 6, being N positions of X>Y not both odd which can generate primitive Pythagorean triples tree_type=SB,CW,AYT,HCS,Bird,Drib,L A008776 total X+Y in row, being 2*3^depth A000523 tree_n_to_depth(), being floor(log2(N)) A059893 permutation SB<->CW, AYT<->HCS, Bird<->Drib reverse bits below highest A258746 permutation SB<->Bird, flip every second bit, from 3rd highest downward A003188 permutation SB->HCS, Gray code shift+xor A006068 permutation HCS->SB, Gray code inverse A154435 permutation HCS->Bird, Lamplighter bit flips A154436 permutation Bird->HCS, Lamplighter variant A258996 permutation CW<->Drib, phase shift code high to low A153153 permutation CW->AYT, reverse and un-Gray A153154 permutation AYT->CW, reverse and Gray code A154437 permutation AYT->Drib, Lamplighter low to high A154438 permutation Drib->AYT, un-Lamplighter low to high A054429 permutation SB,CW,Bird,Drib N at transpose Y/X, (mirror binary tree, runs 0b11..11 down to 0b10..00) A004442 permutation AYT N at transpose Y/X, from N=2 onwards (xor 1, ie. flip least significant bit) A063946 permutation HCS N at transpose Y/X, extra initial 0 (xor 2, ie. flip second least significant bit) A054424 permutation DiagonalRationals -> SB A054426 permutation SB -> DiagonalRationals A054425 DiagonalRationals -> SB with 0s at non-coprimes A054427 permutation coprimes -> SB right hand X/Y>1 A044051 N+1 of those N where SB and CW have same X,Y same Bird<->Drib and HCS<->AYT begin N+1 of N binary palindrome below high 1-bit The sequences marked "extra ..." have one or two extra initial values over what the RationalsTree here gives, but are the same after that. And the Stern first differences "less ..." means it has one less term than what the code here gives. =cut # 6 trees, 6*5/2=15 pairs for permutations =pod =head1 SEE ALSO L, L, L, L L, L, L, L, L L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/FlowsnakeCentres.pm0000644000175000017500000007256413734026671021063 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=FlowsnakeCentres --lines --scale=10 # # http://80386.nl/projects/flowsnake/ # package Math::PlanePath::FlowsnakeCentres; use 5.004; use strict; use POSIX 'ceil'; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest', 'xy_is_even'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'round_up_pow'; use Math::PlanePath::SacksSpiral; *_rect_to_radius_range = \&Math::PlanePath::SacksSpiral::_rect_to_radius_range; # uncomment this to run the ### lines #use Devel::Comments; use constant n_start => 0; use constant parameter_info_array => [ { name => 'arms', share_key => 'arms_3', display => 'Arms', type => 'integer', minimum => 1, maximum => 3, default => 1, width => 1, description => 'Arms', } ]; { my @x_negative_at_n = (undef, 3, 1, 1); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } { my @y_negative_at_n = (undef, 8597, 7, 2); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } use constant dx_minimum => -2; use constant dx_maximum => 2; use constant dy_minimum => -1; use constant dy_maximum => 1; *_UNDOCUMENTED__dxdy_list = \&Math::PlanePath::_UNDOCUMENTED__dxdy_list_six; { my @_UNDOCUMENTED__dxdy_list_at_n = (undef, 10, 6, 8); sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $_UNDOCUMENTED__dxdy_list_at_n[$self->{'arms'}]; } } use constant absdx_minimum => 1; use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East #------------------------------------------------------------------------------ # * # / \ # / \ # *-----* # # (b/2)^2 + h^2 = s # (1/2)^2 + h^2 = 1 # h^2 = 1 - 1/4 # h = sqrt(3)/2 = 0.866 # sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(3, $self->{'arms'} || 1)); return $self; } # # next_state length 84 # my @next_state = (0, 35,49,14, 0,70, 7, 0,21, 7,21,42,28, 7, # 0,7 # 14,49,63,28,14, 0,21, 14,35,21,35,56,42,21, # 14,21 # 28,63,77,42,28,14,35, 28,49,35,49,70,56,35, # 28,35 # 42,77, 7,56,42,28,49, 42,63,49,63, 0,70,49, # 42,49 # 56, 7,21,70,56,42,63, 56,77,63,77,14, 0,63, # 56,63 # 70,21,35, 0,70,56,77, 70, 7,77, 7,28,14,77); # 70,77 # my @digit_to_i = (0, 1, 0,-1,-1, 0, 1, 0, 1, 2, 3, 3, 2, 1, # 0,7 # 0, 0,-1,-1,-2,-2,-1, 0, 0, 1, 1, 0, 0,-1, # 14,21 # 0, -1,-1, 0,-1,-2,-2, 0,-1,-1,-2,-3,-2,-2, # 28,35 # 0, -1, 0, 1, 1, 0,-1, 0,-1,-2,-3,-3,-2,-1, # 42,49 # 0, 0, 1, 1, 2, 2, 1, 0, 0,-1,-1, 0, 0, 1, # 56,63 # 0, 1, 1, 0, 1, 2, 2, 0, 1, 1, 2, 3, 2,2); # 70,77 # my @digit_to_j = (0, 0, 1, 1, 2, 2, 1, 0, 0,-1,-1, 0, 0, 1, # 0,7 # 0, 1, 1, 0, 1, 2, 2, 0, 1, 1, 2, 3, 2, 2, # 14,21 # 0, 1, 0,-1,-1, 0, 1, 0, 1, 2, 3, 3, 2, 1, # 28,35 # 0, 0,-1,-1,-2,-2,-1, 0, 0, 1, 1, 0, 0,-1, # 42,49 # 0, -1,-1, 0,-1,-2,-2, 0,-1,-1,-2,-3,-2,-2, # 56,63 # 0, -1, 0, 1, 1, 0,-1, 0,-1,-2,-3,-3,-2,-1); # 70,77 # my @state_to_di = ( 1, 1, 0, 0,-1,-1, -1,-1, 0, 0, 1,1); # my @state_to_dj = ( 0, 0, 1, 1, 1, 1, 0, 0,-1,-1,-1,-1); # # # sub n_to_xy { # my ($self, $n) = @_; # ### Flowsnake n_to_xy(): $n # # if ($n < 0) { return; } # if (is_infinite($n)) { return ($n,$n); } # # my $int = int($n); # $n -= $int; # fraction part # ### $int # ### frac: $n # # my $state; # { # my $arm = _divrem_mutate ($int, $self->{'arms'}); # $state = 28 * $arm; # initial rotation # # # adjust so that for arms=2 point N=1 has $int==1 # # or for arms=3 then points N=1 and N=2 have $int==1 # if ($arm) { $int += 1; } # } # ### initial state: $state # # my $i = my $j = $int*0; # bignum zero # # foreach my $digit (reverse digit_split_lowtohigh($int,7)) { # high to low # ### at: "state=$state digit=$digit i=$i,j=$j di=".$digit_to_i[$state+$digit]." dj=".$digit_to_j[$state+$digit] # # # i,j * (2+w), being 2*(i,j)+rot60(i,j) # # then add low digit position # # # $state += $digit; # ($i, $j) = (2*$i - $j + $digit_to_i[$state], # 3*$j + $i + $digit_to_j[$state]); # $state = $next_state[$state]; # } # ### integer: "i=$i, j=$j" # # # fraction in final $state direction # if ($n) { # ### apply: "frac=$n state=$state" # $state /= 7; # $i = $n * $state_to_di[$state] + $i; # $j = $n * $state_to_dj[$state] + $j; # } # # ### ret: "$i, $j x=".(2*$i+$j)." y=$j" # return (2*$i+$j, # $j); # # } # 4-->5 # ^ ^ # / \ # 3--- 2 6-- # \ # v # 0-->1 # my @digit_reverse = (0,1,1,0,0,0,1); # 1,2,6 sub n_to_xy { my ($self, $n) = @_; ### FlowsnakeCentres n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } # ENHANCE-ME: work $frac into initial $x,$y somehow # my $frac; # { # my $int = int($n); # $frac = $n - $int; # inherit possible BigFloat/BigRat # $n = $int; # BigInt instead of BigFloat # } { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'}); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } # arm as initial rotation my $rot = _divrem_mutate ($n, $self->{'arms'}); my @digits = digit_split_lowtohigh($n,7); ### @digits my $x = 0; my $y = 0; { # if (! @n || $digits[0] == 0) { # $x = 2*$frac; # } elsif ($digits[0] == 1) { # $x = $frac; # $y = -$frac; # } elsif ($digits[0] == 2) { # $x = -2*$frac; # } elsif ($digits[0] == 3) { # $x = $frac; # $y = -$frac; # } elsif ($digits[0] == 4) { # $x = 2*$frac; # } elsif ($digits[0] == 5) { # $x = $frac; # $y = -$frac; # } elsif ($digits[0] == 6) { # $x = -$frac; # $y = -$frac; # } my $rev = 0; foreach my $digit (reverse @digits) { # high to low ### $digit if ($rev) { ### reverse: "$digit to ".(6 - $digit) $digit = 6 - $digit; # mutate the array } $rev ^= $digit_reverse[$digit]; ### now rev: $rev } ### reversed n: @n } my ($ox,$oy,$sx,$sy); if ($rot == 0) { $ox = 0; $oy = 0; $sx = 2; $sy = 0; } elsif ($rot == 1) { $ox = -1; # at +120 $oy = 1; $sx = -1; # rot to +120 $sy = 1; } else { $ox = -2; # at 180 $oy = 0; $sx = -1; # rot to +240 $sy = -1; } while (@digits) { my $digit = shift @digits; # low to high ### digit: "$digit $x,$y side $sx,$sy origin $ox,$oy" if ($digit == 0) { $x += (3*$sy - $sx)/2; # at -120 $y += ($sx + $sy)/-2; } elsif ($digit == 1) { ($x,$y) = ((3*$y-$x)/2, # rotate -120 ($x+$y)/-2); $x += ($sx + 3*$sy)/2; # at -60 $y += ($sy - $sx)/2; } elsif ($digit == 2) { # centre } elsif ($digit == 3) { ($x,$y) = (($x+3*$y)/-2, # rotate +120 ($x-$y)/2); $x -= $sx; # at -180 $y -= $sy; } elsif ($digit == 4) { $x += ($sx + 3*$sy)/-2; # at +120 $y += ($sx - $sy)/2; } elsif ($digit == 5) { $x += ($sx - 3*$sy)/2; # at +60 $y += ($sx + $sy)/2; } elsif ($digit == 6) { ($x,$y) = (($x+3*$y)/-2, # rotate +120 ($x-$y)/2); $x += $sx; # at X axis $y += $sy; } $ox += $sx; $oy += $sy; # 2*(sx,sy) + rot+60(sx,sy) ($sx,$sy) = ((5*$sx - 3*$sy) / 2, ($sx + 5*$sy) / 2); } ### digits to: "$x,$y" ### origin sum: "$ox,$oy" ### origin rotated: (($ox-3*$oy)/2).','.(($ox+$oy)/2) $x += ($ox-3*$oy)/2; # rotate +60 $y += ($ox+$oy)/2; ### final: "$x,$y" return ($x,$y); } # all even points when arms==3 sub xy_is_visited { my ($self, $x, $y) = @_; if ($self->{'arms'} == 3) { return xy_is_even($self,$x,$y); } else { return defined($self->xy_to_n($x,$y)); } } # 4-->5 # ^ ^ forw # / \ # 3--- 2 6--- # \ # v # 0-->1 # # 5 3 # \ rev # / \ / v # --6 4 2 # / # v # 0-->1 # my @modulus_to_digit = (0,3,1,2,4,6,5, 0,42,14,28, 0,56, 0, # 0 right forw 0 0,5,1,4,6,2,3, 0,42,14,70,14,14,28, # 14 +120 rev 1 6,3,5,4,2,0,1, 28,56,70, 0,28,42,28, # 28 left rev 2 4,5,3,2,6,0,1, 42,42,70,56,14,42,28, # 42 +60 forw 3 2,1,3,4,0,6,5, 56,56,14,42,70,56, 0, # 56 -60 rev 6 6,1,5,2,0,4,3, 28,56,70,14,70,70, 0, # 70 forw ); sub xy_to_n { my ($self, $x, $y) = @_; ### FlowsnakeCentres xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); if (($x ^ $y) & 1) { ### odd x,y ... return undef; } my $level_limit = log($x*$x + 3*$y*$y + 1) * 0.835 * 2; if (is_infinite($level_limit)) { return $level_limit; } my @digits; my $arm; my $state; for (;;) { if ($level_limit-- < 0) { ### oops, level limit ... return undef; } if ($x == 0 && $y == 0) { ### found first arm 0,0 ... $arm = 0; $state = 0; last; } if ($x == -2 && $y == 0) { ### found second arm -2,0 ... $arm = 1; $state = 42; last; } if ($x == -1 && $y == -1) { ### found third arm -1,-1 ... $arm = 2; $state = 70; last; } # if ((($x == -1 || $x == 1) && $y == -1) # || ($x == 0 && $y == -2)) { # ### below island ... # return undef; # } my $m = ($x + 2*$y) % 7; ### at: "$x,$y digits=".join(',',@digits) ### mod remainder: $m # 0,0 is m=0 if ($m == 2) { # 2,0 = 2 $x -= 2; } elsif ($m == 3) { # 1,1 = 1+2 = 3 $x -= 1; $y -= 1; } elsif ($m == 1) { # -1,1 = -1+2 = 1 $x += 1; $y -= 1; } elsif ($m == 4) { # 0,2 = 0+2*2 = 4 $y -= 2; } elsif ($m == 6) { # 2,2 = 2+2*2 = 6 $x -= 2; $y -= 2; } elsif ($m == 5) { # 3,1 = 3+2*1 = 5 $x -= 3; $y -= 1; } push @digits, $m; ### digit: "$m to $x,$y" ### shrink to: ((3*$y + 5*$x) / 14).','.((5*$y - $x) / 14) ### assert: (3*$y + 5*$x) % 14 == 0 ### assert: (5*$y - $x) % 14 == 0 # shrink ($x,$y) = ((3*$y + 5*$x) / 14, (5*$y - $x) / 14); } ### @digits my $arms = $self->{'arms'}; if ($arm >= $arms) { return undef; } my $n = 0; foreach my $m (reverse @digits) { # high to low ### $m ### digit: $modulus_to_digit[$state + $m] ### state: $state ### next state: $modulus_to_digit[$state+7 + $m] $n = 7*$n + $modulus_to_digit[$state + $m]; $state = $modulus_to_digit[$state+7 + $m]; } ### final n along arm: $n return $n*$arms + $arm; } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### FlowsnakeCentres rect_to_n_range(): "$x1,$y1 $x2,$y2" my ($r_lo, $r_hi) = _rect_to_radius_range ($x1,$y1*sqrt(3), $x2,$y2*sqrt(3)); $r_hi *= 2; my $level_plus_1 = ceil( log(max(1,$r_hi/4)) / log(sqrt(7)) ) + 2; # return (0, 7**$level_plus_1); my $level_limit = $level_plus_1; ### $level_limit if (is_infinite($level_limit)) { return ($level_limit,$level_limit); } $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### sorted range: "$x1,$y1 $x2,$y2" my $rect_dist = sub { my ($x,$y) = @_; my $xd = ($x < $x1 ? $x1 - $x : $x > $x2 ? $x - $x2 : 0); my $yd = ($y < $y1 ? $y1 - $y : $y > $y2 ? $y - $y2 : 0); return ($xd*$xd + 3*$yd*$yd); }; my $arms = $self->{'arms'}; ### $arms my $n_lo; { my @hypot = (6); my $top = 0; for (;;) { ARM_LO: foreach my $arm (0 .. $arms-1) { my $i = 0; my @digits; if ($top > 0) { @digits = ((0)x($top-1), 1); } else { @digits = (0); } for (;;) { my $n = 0; foreach my $digit (reverse @digits) { # high to low $n = 7*$n + $digit; } $n = $n*$arms + $arm; ### lo consider: "i=$i digits=".join(',',reverse @digits)." is n=$n" my ($nx,$ny) = $self->n_to_xy($n); my $nh = &$rect_dist ($nx,$ny); if ($i == 0 && $nh == 0) { ### lo found inside: $n if (! defined $n_lo || $n < $n_lo) { $n_lo = $n; } next ARM_LO; } if ($i == 0 || $nh > $hypot[$i]) { ### too far away: "nxy=$nx,$ny nh=$nh vs ".$hypot[$i] while (++$digits[$i] > 6) { $digits[$i] = 0; if (++$i <= $top) { ### backtrack up ... } else { ### not found within this top and arm, next arm ... next ARM_LO; } } } else { ### lo descend ... ### assert: $i > 0 $i--; $digits[$i] = 0; } } } # if an $n_lo was found on any arm within this $top then done if (defined $n_lo) { last; } ### lo extend top ... if (++$top > $level_limit) { ### nothing below level limit ... return (1,0); } $hypot[$top] = 7 * $hypot[$top-1]; } } my $n_hi = 0; ARM_HI: foreach my $arm (reverse 0 .. $arms-1) { my @digits = ((6) x $level_limit); my $i = $#digits; for (;;) { my $n = 0; foreach my $digit (reverse @digits) { # high to low $n = 7*$n + $digit; } $n = $n*$arms + $arm; ### hi consider: "arm=$arm i=$i digits=".join(',',reverse @digits)." is n=$n" my ($nx,$ny) = $self->n_to_xy($n); my $nh = &$rect_dist ($nx,$ny); if ($i == 0 && $nh == 0) { ### hi found inside: $n if ($n > $n_hi) { $n_hi = $n; next ARM_HI; } } if ($i == 0 || $nh > (6 * 7**$i)) { ### too far away: "$nx,$ny nh=$nh vs ".(6 * 7**$i) while (--$digits[$i] < 0) { $digits[$i] = 6; if (++$i < $level_limit) { ### hi backtrack up ... } else { ### hi nothing within level limit for this arm ... next ARM_HI; } } } else { ### hi descend ### assert: $i > 0 $i--; $digits[$i] = 6; } } } if ($n_hi == 0) { ### oops, lo found but hi not found $n_hi = $n_lo; } return ($n_lo, $n_hi); } #------------------------------------------------------------------------------ # levels # arms=1 arms=2 # level 1 0..6 = 7 0..13 = 14 # level 2 0..48 = 49 0..97 = 98 # 7^k-1 2*7^k-1 # level 7^k points # or arms*7^k # counting from 0 sub level_to_n_range { my ($self, $level) = @_; return (0, 7**$level * $self->{'arms'} - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, $self->{'arms'}); my ($pow, $exp) = round_up_pow ($n+1, 7); return $exp; } #------------------------------------------------------------------------------ 1; __END__ # if (@n) { # my $digit = shift @n; # # $ox += $sx; # $oy += $sy; # # if ($rev) { # if ($digit == 0) { # $x += $sx; # at X axis # $y += $sy; # # $x += ($sx + 3*$sy)/2; # at -60 # # $y += ($sy - $sx)/2; # # $x += ($sx + 3*$sy)/-2; # at +120 # # $y += ($sx - $sy)/2; # # $x += (3*$sy - $sx)/2; # at -120 # # $y += ($sx + $sy)/-2; # # } elsif ($digit == 1) { # ($x,$y) = ((3*$y-$x)/2, # rotate -120 # ($x+$y)/-2); # return; # # } elsif ($digit == 2) { # return; # } elsif ($digit == 3) { # $x = -$x; # rotate 180 # $y = -$y; # $x += $sx + ($sx - 3*$sy)/2; # at +60 + X axis # $y += $sy + ($sx + $sy)/2; # return; # } elsif ($digit == 4) { # ($x,$y) = ((3*$y-$x)/2, # rotate -120 # ($x+$y)/-2); # $x += ($sx - 3*$sy)/2; # at +60 # $y += ($sx + $sy)/2; # return; # } elsif ($digit == 5) { # ($x,$y) = (($x+3*$y)/-2, # rotate +120 # ($x-$y)/2); # # centre # return; # } elsif ($digit == 6) { # ($x,$y) = (($x-3*$y)/2, # rotate +60 # ($x+$y)/2); # return; # } # # } else { # if ($digit == 0) { # $x += (3*$sy - $sx)/2; # at -120 # $y += ($sx + $sy)/-2; # # } elsif ($digit == 1) { # ($x,$y) = ((3*$y-$x)/2, # rotate -120 # ($x+$y)/-2); # $x += ($sx + 3*$sy)/2; # at -60 # $y += ($sy - $sx)/2; # # } elsif ($digit == 2) { # $x = -$x; # rotate 180 # $y = -$y; # $x += $sx; # at X axis # $y += $sy; # # } elsif ($digit == 3) { # ($x,$y) = (($x+3*$y)/-2, # rotate +120 # ($x-$y)/2); # # centre # # } elsif ($digit == 4) { # $x += ($sx + 3*$sy)/-2; # at +120 # $y += ($sx - $sy)/2; # # } elsif ($digit == 5) { # $x += ($sx - 3*$sy)/2; # at +60 # $y += ($sx + $sy)/2; # # } elsif ($digit == 6) { # ($x,$y) = (($x+3*$y)/-2, # rotate +120 # ($x-$y)/2); # $x += $sx + ($sx - 3*$sy)/2; # at +60 + X axis # $y += $sy + ($sx + $sy)/2; # } # } # # # 2*(sx,sy) + rot+60(sx,sy) # ($sx,$sy) = ((5*$sx - 3*$sy) / 2, # ($sx + 5*$sy) / 2); # } =for stopwords eg Ryde flowsnake Gosper Schouten's lookup Math-PlanePath multi-arm =head1 NAME Math::PlanePath::FlowsnakeCentres -- self-similar path of hexagon centres =head1 SYNOPSIS use Math::PlanePath::FlowsnakeCentres; my $path = Math::PlanePath::FlowsnakeCentres->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis path is a variation of the flowsnake curve by William Gosper which follows the flowsnake tiling the same way but the centres of the hexagons instead of corners across. The result is the same overall shape, but a symmetric base figure. =cut # math-image --path=FlowsnakeCentres --all --output=numbers_dash --size=78x45 =pod 39----40 8 / \ 32----33 38----37 41 7 / \ \ \ 31----30 34----35----36 42 47 6 \ / / \ 28----29 16----15 43 46 48--... 5 / / \ \ \ 27 22 17----18 14 44----45 4 / / \ \ \ 26 23 21----20----19 13 10 3 \ \ / / \ 25----24 4---- 5 12----11 9 2 / \ / 3---- 2 6---- 7---- 8 1 \ 0---- 1 <- Y=0 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 The points are spread out on every second X coordinate to make little triangles with integer coordinates, per L. The base pattern is the seven points 0 to 6, 4---- 5 / \ 3---- 2 6--- \ 0---- 1 This repeats at 7-fold increasing scale, with sub-sections rotated according to the edge direction, and the 1, 2 and 6 sub-sections in reverse. Eg. N=7 to N=13 is the "1" part taking the base figure in reverse and rotated so the end points towards the "2". The next level can be seen at the midpoints of each such group, being N=2,11,18,23,30,37,46. ---- 37 ---- --- 30---- --- | --- | 46 | | ----18 | ----- --- 23--- --- --- --- 11 ----- 2 --- =head2 Arms The optional C parameter can give up to three copies of the curve, each advancing successively. For example C3> is as follows. Notice the N=3*k points are the plain curve, and N=3*k+1 and N=3*k+2 are rotated copies of it. =cut # math-image --path=FlowsnakeCentres,arms=3 --all --output=numbers_dash =pod 84---... 48----45 5 / / \ 81 66 51----54 42 4 / / \ \ \ 28----25 78 69 63----60----57 39 30 3 / \ \ \ / / \ 31----34 22 75----72 12----15 36----33 27 2 \ \ / \ / 40----37 19 4 9---- 6 18----21----24 1 / / / \ \ 43 58 16 7 1 0---- 3 77----80 <- Y=0 / / \ \ \ / \ 46 55 61 13----10 2 11 74----71 83 -1 \ \ \ / / \ \ \ 49----52 64 73 5---- 8 14 65----68 86 -2 / / \ / / / ... 67----70 76 20----17 62 53 ... -3 \ / / / / \ 85----82----79 23 38 59----56 50 -4 / / \ / 26 35 41----44----47 -5 \ \ 29----32 -6 ^ -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 As described in L the flowsnake essentially fills a hexagonal shape with wiggly sides. For this Centres variation the start of each arm corresponds to the centre of a little hexagon. The N=0 little hexagon is at the origin, and the 1 and 2 beside and below, ^ / \ / \ \ \ / \ | \ | | | 1 | 0---> | | | \ / \ / \ / \ / | | | 2 | | / | / / v \ / Like the main Flowsnake the sides of the arms mesh perfectly and three arms fill the plane. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::FlowsnakeCentres-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> In the current code the returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle, but don't rely on that yet since finding the exact range is a touch on the slow side. (The advantage of which though is that it helps avoid very big ranges from a simple over-estimate.) =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 7**$level - 1)>, or for multiple arms return C<(0, $arms * 7**$level - 1)>. There are 7^level points in a level, or arms*7^level for multiple arms, numbered starting from 0. =back =head1 FORMULAS =head2 N to X,Y The C calculation follows Ed Schouten's method =over L =back breaking N into base-7 digits, applying reversals from high to low according to digits 1, 2, or 6, then applying rotation and position according to the resulting digits. Unlike Ed's code, the path here starts from N=0 at the edge of the Gosper island shape and for that reason doesn't cover the plane. An offset of N-2*7^21 and suitable X,Y offset can be applied to get the same result. =head2 X,Y to N The C calculation also follows Ed Schouten's method. It's based on a nice observation that the seven cells of the base figure can be identified from their X,Y coordinates, and the centre of those seven cell figures then shrunk down a level to be a unit apart, thus generating digits of N from low to high. In triangular grid X,Y a remainder is formed m = (x + 2*y) mod 7 Taking the base figure's N=0 at 0,0 the remainders are 4---- 6 / \ 1---- 3 5 \ 0---- 2 The remainders are unchanged when the shape is moved by some multiple of the next level X=5,Y=1 or the same at 120 degrees X=1,Y=3 or 240 degrees X=-4,Y=1. Those vectors all have X+2*Y==0 mod 7. From the m remainder an offset can be applied to move X,Y to the 0 position, leaving X,Y a multiple of the next level vectors X=5,Y=1 etc. Those vectors can then be shrunk down with Xshrunk = (3*Y + 5*X) / 14 Yshrunk = (5*Y - X) / 14 This gives integers since 3*Y+5*X and 5*Y-X are always multiples of 14. For example the N=35 point at X=2,Y=6 reduces to X = (3*6+5*2)/14 = 2 and Y = (5*6-2)/14 = 2, which is then the "5" part of the base figure. The remainders can be mapped to digits and then reversals and rotations applied, from high to low, according to the edge orientation. Those steps can be combined in a single lookup table with 6 states (three rotations, and each one forward or reverse). For the main curve the reduction ends at 0,0. For the multi-arm form the second arm ends to the right at -2,0 and the third below at -1,-1. Notice the modulo and shrink procedure maps those three points back to themselves unchanged. The calculation can be done without paying attention to which arms are supposed to be in use. On reaching one of the three ends the "arm" is determined and the original X,Y can be rejected or accepted accordingly. The key to this approach is that the base figure is symmetric around a central point, so the tiling can be broken down first, and the rotations or reversals in the path applied afterwards. Can it work on a non-symmetric base figure like the "across" style of the main Flowsnake, or something like the C for that matter? =head1 SEE ALSO L, L, L L, L, L, L L -- Ed Schouten's code =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/HilbertSpiral.pm0000644000175000017500000003337013734026670020341 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::HilbertSpiral; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh'; use Math::PlanePath::BetaOmega 52; *_y_round_down_len_level = \&Math::PlanePath::BetaOmega::_y_round_down_len_level; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant xy_is_visited => 1; use constant x_negative_at_n => 4; use constant y_negative_at_n => 8; #------------------------------------------------------------------------------ # generated by tools/hilbert-spiral-table.pl # my @next_state = (8,0,0,12, 12,4,4,8, 0,8,8,4, 4,12,12,0, 20,0,0,12, 16,4,4,8); my @digit_to_x = (0,1,1,0, 1,0,0,1, 0,0,1,1, 1,1,0,0, 0,1,1,0, 1,0,0,1); my @digit_to_y = (0,0,1,1, 1,1,0,0, 0,1,1,0, 1,0,0,1, 0,0,1,1, 1,1,0,0); my @xy_to_digit = (0,3,1,2, 2,1,3,0, 0,1,3,2, 2,3,1,0, 0,3,1,2, 2,1,3,0); my @min_digit = (0,0,1,0, 0,1,3,2, 2,undef,undef,undef, 2,2,3,1, 0,0,1,0, 0,undef,undef,undef, 0,0,3,0, 0,2,1,1, 2,undef,undef,undef, 2,1,1,2, 0,0,3,0, 0,undef,undef,undef, 0,0,1,0, 0,1,3,2, 2,undef,undef,undef, 2,2,3,1, 0,0,1,0, 0); my @max_digit = (0,1,1,3, 3,2,3,3, 2,undef,undef,undef, 2,3,3,2, 3,3,1,1, 0,undef,undef,undef, 0,3,3,1, 3,3,1,2, 2,undef,undef,undef, 2,2,1,3, 3,1,3,3, 0,undef,undef,undef, 0,1,1,3, 3,2,3,3, 2,undef,undef,undef, 2,3,3,2, 3,3,1,1, 0); # neg state 20 sub n_to_xy { my ($self, $n) = @_; ### HilbertSpiral n_to_xy(): $n ### hex: sprintf "%#X", $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; my @digits = digit_split_lowtohigh($int,4); my $len = ($n*0 + 2) ** scalar(@digits); # inherit possible bigint 1 my $state = ($#digits & 1 ? 4 : 0); my $dir = $state + 2; # default if all $digit==3 ### @digits my $x = my $y = 0; while (defined (my $digit = pop @digits)) { # high to low $len /= 2; $state += $digit; if ($digit != 3) { $dir = $state; # lowest non-3 digit } ### at: "$x,$y len=$len" ### $state ### $dir ### digit_to_x: $digit_to_x[$state] ### digit_to_y: $digit_to_y[$state] ### next_state: $next_state[$state] my $offset = scalar(@digits) & 1; $x += $len * ($digit_to_x[$state] - $offset); $y += $len * ($digit_to_y[$state] - $offset); $state = $next_state[$state]; } ### frac: $n ### $dir ### dir dx: ($digit_to_x[$dir+1] - $digit_to_x[$dir]) ### dir dy: ($digit_to_y[$dir+1] - $digit_to_y[$dir]) ### x: $n * ($digit_to_x[$dir+1] - $digit_to_x[$dir]) + $x ### y: $n * ($digit_to_y[$dir+1] - $digit_to_y[$dir]) + $y # with $n fractional part return ($n * ($digit_to_x[$dir+1] - $digit_to_x[$dir]) + $x, $n * ($digit_to_y[$dir+1] - $digit_to_y[$dir]) + $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### HilbertSpiral xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); my $n = ($x * 0 * $y); my ($len, $level) = _y_round_down_len_level ($x); { my ($ylen, $ylevel) = _y_round_down_len_level ($y); ### y len/level: "$ylen $ylevel" if ($ylevel > $level) { $level = $ylevel; $len = $ylen; } } if (is_infinite($len)) { return $len; } ### $len ### $level my $state; { my $offset; if ($level & 1) { $state = 4; $offset = 4*$len; } else { $state = 0; $offset = 2*$len; } $offset -= 2; $offset /= 3; $y += $offset; $x += $offset; # $x,$y now relative to Xmin(level),Ymin(level), # so in range 0 <= $x,$y < 2*len } ### offset x,y to: "$x, $y" for (;;) { ### at: "$x,$y len=$len" ### assert: $x >= 0 ### assert: $y >= 0 ### assert: $x < 2*$len ### assert: $y < 2*$len my $xo; if ($xo = ($x >= $len)) { $x -= $len; } my $yo; if ($yo = ($y >= $len)) { $y -= $len; } ### xy bits: ($xo+0).", ".($yo+0) my $digit = $xy_to_digit[$state + 2*$xo + $yo]; $n = 4*$n + $digit; $state = $next_state[$state+$digit]; last if --$level < 0; $len /= 2; } ### assert: $x == 0 ### assert: $y == 0 return $n; } # This finds the exact minimum/maximum N in the given rectangle. # # The strategy is similar to xy_to_n(), except that at each bit position # instead of taking a bit of x,y from the input instead those bits are # chosen from among the 4 sub-parts according to which has the maximum N and # is within the given target rectangle. The final result is both an $n_max # and a $x_max,$y_max which is its position, but only the $n_max is # returned. # # At a given sub-part the comparisons ask whether x1 is above or below the # midpoint, and likewise x2,y1,y2. Since x2>=x1 and y2>=y1 there's only 3 # combinations of x1>=cmp,x2>=cmp, not 4. # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### HilbertSpiral rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; # If y1/y2 both positive or both negative then only look at the bigger of # the two. If y1 negative and y2 positive then consider both. my $len = 1; my $level = 0; foreach my $z (($x2 > 0 ? ($x2) : ()), ($x1 < 0 ? ($x1) : ()), ($y2 > 0 ? ($y2) : ()), ($y1 < 0 ? ($y1) : ())) { my ($zlen, $zlevel) = _y_round_down_len_level ($z); ### y len/level: "$zlen $zlevel" if ($zlevel > $level) { $level = $zlevel; $len = $zlen; } } if (is_infinite($len)) { return (0, $len); } # At this point an easy over-estimate would be: # return (0, $len*$len*4-1); my $n_min = my $n_max = 0; my $x_min = my $x_max = my $y_min = my $y_max = - (4**int(($level+1)/2) - 1) * 2 / 3; my $min_state = my $max_state = ($level & 1 ? 20 : 16); ### $x_min ### $y_min while ($level >= 0) { ### $level ### $len { my $x_cmp = $x_min + $len; my $y_cmp = $y_min + $len; my $digit = $min_digit[3*$min_state + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0)]; $n_min = 4*$n_min + $digit; $min_state += $digit; if ($digit_to_x[$min_state]) { $x_min += $len; } $y_min += $len * $digit_to_y[$min_state]; $min_state = $next_state[$min_state]; } { my $x_cmp = $x_max + $len; my $y_cmp = $y_max + $len; my $digit = $max_digit[3*$max_state + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0)]; $n_max = 4*$n_max + $digit; $max_state += $digit; if ($digit_to_x[$max_state]) { $x_max += $len; } $y_max += $len * $digit_to_y[$max_state]; $max_state = $next_state[$max_state]; } $len = int($len/2); $level--; } return ($n_min, $n_max); } #------------------------------------------------------------------------------ # levels use Math::PlanePath::HilbertCurve; *level_to_n_range = \&Math::PlanePath::HilbertCurve::level_to_n_range; *n_to_level = \&Math::PlanePath::HilbertCurve::n_to_level; #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde ie Math-PlanePath OEIS =head1 NAME Math::PlanePath::HilbertSpiral -- 2x2 self-similar spiral =head1 SYNOPSIS use Math::PlanePath::HilbertSpiral; my $path = Math::PlanePath::HilbertSpiral->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is a Hilbert curve variation which fills the plane by spiralling around into negative X,Y on every second replication level. ..--63--62 49--48--47 44--43--42 5 | | | | | 60--61 50--51 46--45 40--41 4 | | | 59 56--55 52 33--34 39--38 3 | | | | | | | 58--57 54--53 32 35--36--37 2 | 5-- 4-- 3-- 2 31 28--27--26 1 | | | | | 6-- 7 0-- 1 30--29 24--25 <- Y=0 | | 9-- 8 13--14 17--18 23--22 -1 | | | | | | 10--11--12 15--16 19--20--21 -2 -2 -1 X=0 1 2 3 4 5 The curve starts with the same N=0 to N=3 as the C, then the following 2x2 blocks N=4 to N=15 go around in negative X,Y. The top-left corner for this negative direction is at Ntopleft=4^level-1 for an odd numbered level. The parts of the curve in the X,Y negative parts are the same as the plain C, just mirrored along the anti-diagonal. For example. N=4 to N=15 HilbertSpiral HilbertCurve \ 5---6 9--10 \ | | | | \ 4 7---8 11 \ | 5-- 4 \ 13--12 | \ | 6-- 7 \ 14--15 | \ 9-- 8 13--14 \ | | | \ 10--11--12 15 This mirroring has the effect of mapping HilbertCurve X,Y -> -Y,-X for HilbertSpiral Notice the coordinate difference (-Y)-(-X) = X-Y so that difference, representing a projection onto the X=-Y opposite diagonal, is the same in both paths. =head2 Level Ranges Reckoning the initial N=0 to N=3 as level 1, a replication level extends to Nstart = 0 Nlevel = 4^level - 1 (inclusive) Xmin = Ymin = - (4^floor(level/2) - 1) * 2 / 3 = binary 1010...10 Xmax = Ymax = (4^ceil(level/2) - 1) / 3 = binary 10101...01 width = height = Xmax - Xmin = Ymax - Ymin = 2^level - 1 The X,Y range doubles alternately above and below, so the result is a 1 bit going alternately to the max or min, starting with the max for level 1. level X,Ymin binary X,Ymax binary ----- --------------- -------------- 0 0 0 1 0 0 1 = 1 2 -2 = -10 1 = 01 3 -2 = -010 5 = 101 4 -10 = -1010 5 = 0101 5 -10 = -01010 21 = 10101 6 -42 = -101010 21 = 010101 7 -42 = -0101010 85 = 1010101 The power-of-4 formulas above for Ymin/Ymax have the effect of producing alternating bit patterns like this. This is the same sort of level range as C has on its Y coordinate, but on this C it applies to both X and Y. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::HilbertSpiral-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 4**$level - 1)>. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A059285 X-Y coordinate diff The difference X-Y is the same as the C, since the "negative" spiral parts are mirrored across the X=-Y anti-diagonal, which means coordinates (-Y,-X) and -Y-(-X) = X-Y. =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/PentSpiral.pm0000644000175000017500000002466613734026667017674 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::PentSpiral; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; *_sqrtint = \&Math::PlanePath::_sqrtint; # uncomment this to run the ### lines #use Smart::Comments; use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; sub x_negative_at_n { my ($self) = @_; return $self->n_start + 3; } sub y_negative_at_n { my ($self) = @_; return $self->n_start + 4; } sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + 6; } use constant dx_minimum => -2; use constant dx_maximum => 2; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant 1.02 _UNDOCUMENTED__dxdy_list => (2,0, # E by 2 1,1, # NE -2,1, # WNW -2,-1, # WSW 1,-1, # SE ); use constant absdx_minimum => 1; use constant dsumxy_minimum => -3; # SW -2,-1 use constant dsumxy_maximum => 2; # dX=+2 and NE diag use constant ddiffxy_minimum => -3; # NW dX=-2,dY=+1 use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_right => 0; # only left or straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # base South-West diagonal # d = [ 1, 2, 3, 4 ] # n = [ 0, 4, 13, 27 ] # N = (5/2 d^2 - 7/2 d + 1) # = (5/2*$d**2 - 7/2*$d + 1) # = ((5/2*$d - 7/2)*$d + 1) # d = 7/10 + sqrt(2/5 * $n + 9/100) # = (sqrt(40*$n + 9) + 7) / 10 # # split Y axis # d = [ 1, 2, 3 ] # n = [ 2, 9, 21 ] # N = ((5/2*$d - 1/2)*$d) sub n_to_xy { my ($self, $n) = @_; #### n_to_xy: $n # adjust to N=0 at origin X=0,Y=0 $n = $n - $self->{'n_start'}; if ($n < 0) { return; } my $d = int( (_sqrtint(40*$n+9)+7)/10 ); $n -= (5*$d-1)*$d/2; if ($n < -$d) { $n += 2*$d; if ($n < 1) { # bottom horizontal return (2*$n+$d-1, -$d+1); } else { # lower right diagonal ... return ($n+$d, $n-$d); } } else { if ($n <= $d) { ### top 2,1 slope left and right diagonals ... return (-2*$n, -abs($n) + $d); } else { ### lower left diagonal ... return ($n - 3*$d, -$n + $d); } } } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); # nothing on odd points # when y>=0 any odd x is not covered # when y<0 the uncovered alternates, x even on y=-1, x odd on y=-2, x even # y=-3 etc if (($x%2) ^ ($y < 0 ? $y%2 : 0)) { return undef; } if ($y >= 0) { ### top left and right slopes # vertical at x=0 # d = [ 1, 2, 3 ] # n = [ 3, 10, 22 ] # n = (5/2*$d**2 + -1/2*$d + 1) # ### assert: ($x%2)==0 $x /= 2; my $d = abs($x) + $y; return (5*$d - 1)*$d/2 - $x + $self->{'n_start'}; } if ($x < $y) { ### lower left slope # horizontal leftwards at y=0 # d = [ 1, 2, 3 ] # n = [ 4, 12, 25 ] # n = (5/2*$d**2 + 1/2*$d + 1) # = (2.5*$d + 0.5)*$d + 1 my $d = -($x+$y)/2; return (5*$d + 1)*$d/2 - $y + $self->{'n_start'}; } if ($x > -$y) { ### lower right slope # horizontal rightwards at y=0 # d = [ 1, 2, 3, ] # n = [ 2, 8, 19,] # n = (5/2*$d**2 + -3/2*$d + 1) # = (2.5*$d - 1.5)*$d + 1 my $d = ($x-$y)/2; return (5*$d - 3)*$d/2 + $y + $self->{'n_start'}; } ### bottom horizontal # vertical downwards at x=0 is # y = [ -1, -2, -3 ] # n = [ 5.5, 15, 29.5 ] # n = (5/2*$y**2 + -2*$y + 1) # = (2.5*$y - 2)*$y + 1 # so # N = (2.5*$y - 2)*$y + 1 + $x/2 # = ((5*$y - 4)*$y + $x)/2 + 1 # return ((5*$y-4)*$y + $x)/2 + $self->{'n_start'}; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### PentSpiral rect_to_n_range(): $x1,$y1, $x2,$y2 my $d = 0; foreach my $x ($x1, $x2) { $x = round_nearest ($x); foreach my $y ($y1, $y2) { $y = round_nearest ($y); my $this_d = 1 + ($y >= 0 ? abs($x) + $y : $x < $y ? -($x+$y)/2 : $x > -$y ? ($x-$y)/2 : -$y); ### $x ### $y ### $this_d $d = max($d, $this_d); } } ### $d return ($self->{'n_start'}, $self->{'n_start'} + 5*$d*($d-1)/2 + 2); } 1; __END__ =for stopwords Ryde Math-PlanePath OEIS =head1 NAME Math::PlanePath::PentSpiral -- integer points in a pentagonal shape =head1 SYNOPSIS use Math::PlanePath::PentSpiral; my $path = Math::PlanePath::PentSpiral->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes a pentagonal (five-sided) spiral with points spread out to fit on a square grid. 22 3 23 10 21 2 24 11 3 9 20 1 25 12 4 1 2 8 19 <- Y=0 26 13 5 6 7 18 ... -1 27 14 15 16 17 33 -2 28 29 30 31 32 -2 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 Each horizontal gap is 2, so for instance n=1 is at x=0,y=0 then n=2 is at x=2,y=0. The lower diagonals are 1 across and 1 down, so n=17 is at x=4,y=-2 and n=18 is x=5,y=-1. But the upper angles go 2 across and 1 up, so n=20 is x=4,y=1 then n=21 is x=2,y=2. The effect is to make the sides equal length, except for a kink at the lower right corner. Only every second square in the plane is used. In the top half (y>=0) those points line up, in the lower half (y<0) they're offset on alternate rows. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same pattern. For example to start at 0, =cut # math-image --path=PentSpiral,n_start=0 --expression='i<=57?i:0' --output=numbers --size=120x11 =pod n_start => 0 38 39 21 37 ... 40 22 9 20 36 57 41 23 10 2 8 19 35 56 42 24 11 3 0 1 7 18 34 55 43 25 12 4 5 6 17 33 54 44 26 13 14 15 16 32 53 45 27 28 29 30 31 52 46 47 48 49 50 51 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::PentSpiral-Enew ()> =item C<$path = Math::PlanePath::PentSpiral-Enew (n_start =E $n)> Create and return a new pentagon spiral object. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each point in the path as a square of side 1. =back =head1 FORMULAS =head2 N to X,Y It's convenient to work in terms of Nstart=0 and to take each loop as beginning on the South-West diagonal, 21 loop d=3 -- -- 22 20 -- -- 23 19 -- -- 24 0 18 \ / 25 . 17 \ / 26 13----14----15----16 \ . The SW diagonal is N=0,4,13,27,46,etc which is N = (5d-7)*d/2 + 1 # starting d=1 first loop This can be inverted to get d from N d = floor( (sqrt(40*N + 9) + 7) / 10 ) Each side is length d, except the lower right diagonal slope which is d-1. For the very first loop that lower right is length 0. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back n_start=1 (the default) A192136 N on X axis, (5*n^2 - 3*n + 2)/2 A140066 N on Y axis A116668 N on X negative axis A005891 N on South-East diagonal, centred pentagonals A134238 N on South-West diagonal n_start=0 A000566 N on X axis, heptagonal numbers A005476 N on Y axis A028895 N on South-East diagonal =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/LTiling.pm0000644000175000017500000004434713734026667017153 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::LTiling; use 5.004; use strict; use Carp 'croak'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'round_up_pow', 'digit_split_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant parameter_info_array => [ { name => 'L_fill', display => 'L Fill', type => 'enum', default => 'middle', choices => ['middle','left','upper','ends','all'], choices_display => ['Middle','Left','Upper','Ends','All'], description => 'Which points to number with each "L".', }, ]; my %sumxy_minimum = (middle => 0, # X=0,Y=0 left => 1, # X=1,Y=0 upper => 1, # X=0,Y=1 ends => 1, # X=1,Y=0 and X=0,Y=1 all => 0, # X=0,Y=0 ); sub sumxy_minimum { my ($self) = @_; return $sumxy_minimum{$self->{'L_fill'}}; } *sumabsxy_minimum = \&sumxy_minimum; *absdiffxy_minimum = \&sumxy_minimum; *rsquared_minimum = \&sumxy_minimum; { my %turn_any_straight = (# middle => 0, left => 1, # upper => 0, ends => 1, all => 1, ); sub turn_any_straight { my ($self) = @_; return $turn_any_straight{$self->{'L_fill'}}; } } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); my $L_fill = $self->{'L_fill'}; if (! defined $L_fill) { $self->{'L_fill'} = 'middle'; } elsif (! exists $sumxy_minimum{$L_fill}) { croak "Unrecognised L_fill option: ",$L_fill; } return $self; } sub n_to_xy { my ($self, $n) = @_; ### LTiling n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $x = my $y = ($n * 0); # inherit bignum 0 my $len = $x + 1; # inherit bignum 1 my $L_fill = $self->{'L_fill'}; if ($L_fill eq 'left') { $x += 1; } elsif ($L_fill eq 'upper') { $y += 1; } elsif ($L_fill eq 'ends') { my $rem = _divrem_mutate ($n, 2); if ($rem) { # low digit==1 $y = $len; # 1 } else { # low digit==0 $x = $len; # 1 } } elsif ($L_fill eq 'all') { my $rem = _divrem_mutate ($n, 3); if ($rem == 1) { $x = $len; # 1 } elsif ($rem == 2) { $y = $len; # 1 } } foreach my $digit (digit_split_lowtohigh($n,4)) { ### at: "$x,$y digit=$digit" if ($digit == 1) { ($x,$y) = (4*$len-1-$y,$x); } elsif ($digit == 2) { $x += $len; $y += $len; } elsif ($digit == 3) { ($x,$y) = ($y,4*$len-1-$x); } $len *= 2; } ### final: "$x,$y" return ($x,$y); } my @yx_to_digit = ([0,0,1,1], [0,2,2,1], [3,2], [3,3]); my %fill_factor = (middle => 1, left => 1, upper => 1, ends => 2, all => 3); my %yx_to_fill = (middle => [[0]], left => [[undef,0]], upper => [[], [0]], ends => [[undef,0], [1]], all => [[0,1], [2]]); sub xy_to_n { my ($self, $x, $y) = @_; ### LTiling xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } my ($len, $level) = round_down_pow (max($x,$y), 2); if (is_infinite($level)) { return $level; } my $n = ($x * 0 * $y); # inherit bignum 0 while ($level-- >= 0) { ### assert: $x >= 0 ### assert: $y >= 0 ### assert: ($y < 2*$len && $x < 4*$len) || ($x < 2*$len && $y < 4*$len) ### $len ### x: int($x/$len) ### y: int($y/$len) my $digit = $yx_to_digit[int($y/$len)]->[int($x/$len)]; if ($digit == 1) { ($x,$y) = ($y,4*$len-1-$x); } elsif ($digit == 2) { $x -= $len; $y -= $len; } elsif ($digit == 3) { ($x,$y) = (4*$len-1-$y,$x); } ### to: "digit=$digit xy=$x,$y" $n = $n*4 + $digit; $len /= 2; } ### assert: ($x==0 && $y== 0) || ($x==1 && $y== 0) || ($x==0 && $y== 1) my $fill = $self->{'L_fill'}; if (defined (my $digit = $yx_to_fill{$fill}->[$y]->[$x])) { return $n*$fill_factor{$fill} + $digit; } return undef; } my %range_factor = (middle => 3, left => 3, upper => 3, ends => 6, all => 8); # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### LTiling rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### rect: "X = $x1 to $x2, Y = $y1 to $y2" if ($x2 < 0 || $y2 < 0) { ### rectangle outside first quadrant ... return (1, 0); } my ($len, $level) = round_down_pow (max($x2,$y2), 2); ### $len ### $level if (is_infinite($level)) { return (0,$level); } return (0, $len*$len * $range_factor{$self->{'L_fill'}}); } #------------------------------------------------------------------------------ # levels sub level_to_n_range { my ($self, $level) = @_; return (0, 4**$level * $fill_factor{$self->{'L_fill'}} - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, $fill_factor{$self->{'L_fill'}}); my ($pow, $exp) = round_up_pow ($n+1, 4); return $exp; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde ie Math-PlanePath Asano Ranjan Roos Welzl Widmayer Informatics Nlevel OEIS bitwise =head1 NAME Math::PlanePath::LTiling -- 2x2 self-similar of four pattern parts =head1 SYNOPSIS use Math::PlanePath::LTiling; my $path = Math::PlanePath::LTiling->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is a self-similar tiling by "L" shapes. A base "L" is replicated four times with end parts turned +90 and -90 degrees to make a larger L, +-----+-----+ |12 | 15| | +--+--+ | | |14 | | +--+ +--+--+ | | |11 | | +--+ +--+ |13 | | | +-----+ +-----+--+ +--+--+-----+ | 3 | | 3 | |10 | | 5| | +--+ --> | +--+ +--+--+ +--+ | | | | | | | 8 | 9 | | | +--+ +--+ +--+--+ +--+ +--+--+--+--+ +--+ | | --> | | 2 | | | | 2 | | | 6 | | | +--+ | +--+--+ | | +--+--+ | +--+--+ | | 0 | | 0 | 1 | | 0 | 1 | 7 | 4 | +-----+ +-----+-----+ +-----+-----+-----+-----+ The parts are numbered to the left then middle then upper. This relative numbering is maintained when rotated at the next replication level, as for example N=4 to N=7. The result is to visit 1 of every 3 points in the first quadrant with a subtle layout of points and spaces making diagonal lines and little 2x2 blocks. 15 | 48 51 61 60 140 143 163 14 | 50 62 142 168 13 | 56 59 139 162 12 | 49 58 63 141 160 11 | 55 44 47 131 138 10 | 57 46 136 137 9 | 54 43 130 134 8 | 52 53 45 128 129 135 7 | 12 15 35 42 37 21 6 | 14 40 41 22 5 | 11 34 38 25 4 | 13 32 33 39 36 3 | 3 10 5 31 26 2 | 8 9 27 24 1 | 2 6 30 18 Y=0 | 0 1 7 4 28 29 19 +------------------------------------------------------------ X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 On the X=Y leading diagonal N=0,2,8,10,32,etc is the integers made from only digits 0 and 2 in base 4. Or equivalently integers which have zero bits at all even numbered positions, binary c0d0e0f0. =head2 Left or Upper Option C "left"> or C "upper"> numbers the tiles instead at their left end or upper end respectively. L_fill => 'left' 8 | 52 45 43 7 | 15 42 +-----+ 6 | 12 35 40 | | 5 | 14 34 33 | +--+ 4 | 13 11 32 | 3| | 3 | 10 9 5 +--+ +--+--+ 2 | 3 8 6 31 | | 2| 1| 1 | 2 1 4 | +--+--+ | Y=0 | 0 7 | 0| | +------------------------------------ +-----+-----+ X=0 1 2 3 4 5 6 7 8 L_fill => 'upper' 8 | 53 42 7 | 12 35 40 +-----+ 6 | 14 15 34 41 | 3| 5 | 13 11 32 39 | +--+ 4 | 10 33 | | 2| 3 | 3 8 +--+ +--+--+ 2 | 2 9 5 | 0| | | 1 | 0 7 6 28 | +--+--+ | Y=0 | 1 4 | | 1 | +------------------------------------ +-----+-----+ X=0 1 2 3 4 5 6 7 8 The effect is to disrupt the pattern a bit though the overall structure of the replications is unchanged. "left" is as viewed looking towards the L from above. It may have been better to call it "right", but won't change that now. =head2 Ends Option C "ends"> numbers the two endpoints within each "L", first the left then upper. This is the inverse of the default middle shown above, ie. it visits all the points which the middle option doesn't, and so 2 of every 3 points in the first quadrant. +-----+ | 7| | +--+ | 6| 5| +--+ +--+--+ | 1| 4| 2| | +--+--+ | | 0| 3 | +-----+-----+ 15 | 97 102 123 120 281 286 327 337 14 | 96 101 103 122 124 121 280 285 287 326 325 13 | 99 100 113 118 125 126 283 284 279 321 324 12 | 98 112 117 119 127 282 278 277 320 323 11 | 111 115 116 89 94 263 273 276 274 266 10 | 110 109 114 88 93 95 262 261 272 275 268 9 | 105 108 106 91 92 87 257 260 258 271 269 8 | 104 107 90 86 85 256 259 270 265 7 | 25 30 71 81 84 82 74 43 40 6 | 24 29 31 70 69 80 83 76 75 42 44 5 | 27 28 23 65 68 66 79 77 72 50 45 4 | 26 22 21 64 67 78 73 52 51 47 3 | 7 17 20 18 10 63 55 53 48 34 2 | 6 5 16 19 12 11 62 61 54 49 36 1 | 1 4 2 15 13 8 57 60 58 39 37 Y=0 | 0 3 14 9 56 59 38 33 +------------------------------------------------------------ X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 =head2 All Option C "all"> numbers all three points of each "L", as middle, left then right. With this the path visits all points of the first quadrant. 7 | 36 38 46 45 105 107 122 126 +-----+ 6 | 37 42 44 47 106 104 120 121 | 9 11| 5 | 41 43 33 35 98 102 103 100 | +--+ 4 | 39 40 34 32 96 97 101 99 |10| 8| 3 | 9 11 26 30 31 28 16 15 +--+ +--+--+ 2 | 10 8 24 25 29 27 19 17 | 2| 6 7| 4| 1 | 2 6 7 4 23 20 18 13 | +--+--+ | Y=0 | 0 1 5 3 21 22 14 12 | 0 1| 5 3| +-------------------------------- +-----+-----+ X=0 1 2 3 4 5 6 7 Along the X=Y leading diagonal N=0,6,24,30,96,etc are triples of the values from the single-point case, so 3* numbers using digits 0 and 2 in base 4, which is the same as 2* numbers using 0 and 3 in base 4. =head2 Level Ranges For the "middles", "left" or "upper" cases with one N per tile, and taking the initial N=0 tile as level 0, a replication level is Nstart = 0 to Nlevel = 4^level - 1 inclusive Xmax = Ymax = 2 * 2^level - 1 For example level 2 which is the large tiling shown in the introduction is N=0 to N=4^2-1=15 and extends to Xmax=Ymax=2*2^2-1=7. For the "ends" variation there's two points per tile, or for "all" there's three, in which case the Nlevel increases to Nlevel_ends = 2 * 4^level - 1 Nlevel_all = 3 * 4^level - 1 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::LTiling-Enew ()> =item C<$path = Math::PlanePath::LTiling-Enew (L_fill =E $str)> Create and return a new path object. The C choices are "middle" the default "left" "upper" "ends" "all" =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return 0, 4**$level - 1 middle, left, upper 0, 2*4**$level - 1 ends 0, 3*4**$level - 1 all There are 4^level L shapes in a level, each containing 1, 2 or 3 points, numbered starting from 0. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back L_fill=middle A062880 N on X=Y diagonal, base 4 digits 0,2 only A048647 permutation N at transpose Y,X base4 digits 1<->3 and 0,2 unchanged A112539 X+Y+1 mod 2, parity inverted L_fill=left or upper A112539 X+Y mod 2, parity A112539 is a parity of bits at even positions in N, ie. count 1-bits at even bit positions (least significant is bit position 0), then add 1 and take mod 2. This works because in the pattern sub-blocks 0 and 2 are unchanged and 1 and 3 are turned so as to be on opposite X,Y odd/even parity, so a flip for every even position 1-bit. L_fill=middle starts on a 0 even parity, and L_fill=left and upper start on 1 odd parity. The latter is the form in A112539 and L_fill=middle is the bitwise 0E-E1 inverse. =head1 SEE ALSO L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/WunderlichSerpentine.pm0000644000175000017500000005743713734026653021751 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Archive of all Wunderlich's papers: # http://sodwana.uni-ak.ac.at/geom/mitarbeiter/wallner/wunderlich/ # http://sodwana.uni-ak.ac.at/geom/mitarbeiter/wallner/wunderlich/pdf/125.pdf # [8.5mb] # https://eudml.org/doc/141086 # # Walter Wunderlich. Uber Peano-Kurven. Elemente der Mathematik, 28(1):1-10, # 1973. # # Coil order 111 111 111 # # math-image --path=WunderlichSerpentine --all --output=numbers_dash # math-image --path=WunderlichSerpentine,radix=5 --all --output=numbers_dash # math-image --path=WunderlichSerpentine,serpentine_type=170 --all --output=numbers_dash # # # cf diagonals of alternating # turn(n) = n=n/9^valuation(n,9); [-1,1,1,1,-1,-1,-1,1][n%9]; # vector(20,n,turn(n)) # not in OEIS: -1,1,1,1,-1,-1,-1,1,-1,-1,1,1,1,-1,-1,-1,1,1,-1,1 # vector(20,n,turn(9*n+3)) == vector(20,n, 1) # vector(20,n,turn(9*n+6)) == vector(20,n, -1) # [-1,1, 1, 1,-1, -1, -1,1] # 1 2 0 1 2 0 1 2 n mod 3 # 01,02 11,12, 21,22 package Math::PlanePath::WunderlichSerpentine; use 5.004; use strict; use Carp 'croak'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem = \&Math::PlanePath::_divrem; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; use constant parameter_info_array => [ { name => 'serpentine_type', display => 'Serpentine Type', type => 'string', default => '010 101 010', choices => ['alternating','coil','Peano'], width => 11, type_hint => 'bit_string', description => 'Serpentine type, as a bit string or one of the predefined choices.', }, { name => 'radix', share_key => 'radix_3', display => 'Radix', type => 'integer', minimum => 2, default => 3, width => 3, }, ]; # same as PeanoCurve use Math::PlanePath::PeanoCurve; *dx_minimum = Math::PlanePath::PeanoCurve->can('dx_minimum'); *dx_maximum = Math::PlanePath::PeanoCurve->can('dx_maximum'); *dy_minimum = Math::PlanePath::PeanoCurve->can('dy_minimum'); *dy_maximum = Math::PlanePath::PeanoCurve->can('dy_maximum'); *_UNDOCUMENTED__dxdy_list = Math::PlanePath::PeanoCurve->can('_UNDOCUMENTED__dxdy_list'); # same # # bit=0 bit=1 # *--- b^2-1 -- b^2 # | | # *------- | # | | # 0 ----- b # # bit=0 bit=0 # *--- b^2-1 -- b^2 ---- b^2+b-1 # | | # *------- # | # 0 ----- b # # *-------- # | # *-------* bit=1 # | # *--- b^2-1 # | # *------- bit=1 # | # 0 ----- b # sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; my $radix = $self->{'radix'}; my $n = $radix*$radix; if ($self->{'serpentine_array'}->[0]) { foreach my $i (1 .. $self->{'radix'}-1) { if ($self->{'serpentine_array'}->[$i] == 0) { return $n*$i + $radix; } } if ($self->{'serpentine_array'}->[$radix] == 0) { return $n*$radix; } else { return $n*$radix + $radix-1; } } else { if ($self->{'serpentine_array'}->[1]) { return $n; } else { return $n + $radix-1; } } } *dsumxy_minimum = \&dx_minimum; *dsumxy_maximum = \&dx_maximum; *ddiffxy_minimum = \&dy_minimum; *ddiffxy_maximum = \&dy_maximum; # radix=2 0101 is straight NSEW parts, other evens are diagonal sub dir_maximum_dxdy { my ($self) = @_; return (($self->{'radix'} % 2) || join('',@{$self->{'serpentine_array'}}) eq '0101' ? (0,-1) # odd, South : (0,0)); # even, supremum } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); my $radix = $self->{'radix'}; if (! $self->{'radix'} || $self->{'radix'} < 2) { $radix = $self->{'radix'} = 3; } my @serpentine_array; my $serpentine_type = $self->{'serpentine_type'}; if (! defined $serpentine_type) { $serpentine_type = 'alternating'; } $serpentine_type = lc($serpentine_type); ### $serpentine_type if ($serpentine_type eq 'alternating') { @serpentine_array = map {$_&1} 0 .. $radix*$radix - 1; } elsif ($serpentine_type eq 'coil') { @serpentine_array = (1) x ($radix*$radix); } elsif (lc($serpentine_type) eq 'peano') { @serpentine_array = (0) x ($radix*$radix); } elsif ($serpentine_type =~ /^([01_,.]|\s)*$/) { # bits 010,101,010 etc $serpentine_type =~ tr/01//cd; # keep only 0,1 chars @serpentine_array = map {$_+0} # numize for xor split //, $serpentine_type; # each char, 0,1 push @serpentine_array, (0) x max(0,$radix*$radix-scalar(@serpentine_array)); # foreach my $char { # if ($char eq '0' || $char eq '1') { # push , $char; # } # my @parts = split /[^01]+/, $serpentine_type; # ### @parts # @parts = grep {$_ ne ''} @parts; # no empty parts # foreach my $part (@parts) { # my @bits = split //, $part; # push @bits, (0) x max(0,scalar(@bits)-$radix); # $#bits = $radix-1; # push @serpentine_array, @bits; # radix many row # } } else { croak "Unrecognised serpentine_type \"$serpentine_type\""; } ### @serpentine_array $self->{'serpentine_array'} = \@serpentine_array; return $self; } sub n_to_xy { my ($self, $n) = @_; ### WunderlichSerpentine n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { # ENHANCE-ME: for odd radix the ends join and the direction can be had # without a full N+1 calculation my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } # high to low my $radix = $self->{'radix'}; my $rsquared = $radix * $radix; my $radix_minus_1 = $radix - 1; my $serpentine_array = $self->{'serpentine_array'}; my @digits = digit_split_lowtohigh($n,$rsquared); my $x = 0; my $y = 0; my $transpose = ($#digits & 1) && $serpentine_array->[0]; my $xk = my $yk = 0; while (@digits) { my $ndigit = pop @digits; # high to low my ($highdigit, $lowdigit) = _divrem ($ndigit, $radix); ### $lowdigit ### $highdigit if ($transpose) { $yk ^= $highdigit; $x *= $radix; $x += ($xk & 1 ? $radix_minus_1-$highdigit : $highdigit); $xk ^= $lowdigit; $y *= $radix; $y += ($yk & 1 ? $radix_minus_1-$lowdigit : $lowdigit); } else { $xk ^= $highdigit; $y *= $radix; $y += ($yk & 1 ? $radix_minus_1-$highdigit : $highdigit); $yk ^= $lowdigit; $x *= $radix; $x += ($xk & 1 ? $radix_minus_1-$lowdigit : $lowdigit); } ### $serpentine_array ### $ndigit $transpose ^= $serpentine_array->[$ndigit]; } return ($x, $y); # my $x = 0; # my $y = 0; # my $power = $x + 1; # inherit bignum 1 # my $odd = 1; # my $transpose = 0; # while ($n) { # $odd ^= 1; # ### $n # ### $power # # my $xdigit = $n % $radix; # $n = int($n/$radix); # my $ydigit = $n % $radix; # $n = int($n/$radix); # # if ($transpose) { # ($xdigit,$ydigit) = ($ydigit,$xdigit); # } # $transpose ^= $serpentine_array->[$xdigit + $radix*$ydigit]; # # ### $xdigit # ### $ydigit # # if ($xdigit & 1) { # $y = $power-1 - $y; # 99..99 - Y # } # $x += $power * $xdigit; # # $y += $power * $ydigit; # $power *= $radix; # if ($ydigit & 1) { # $x = $power-1 - $x; # } # } # # # # # if ($odd) { # # ($x,$y) = ($y,$x); # # ### final transpose to: "$x,$y" # # } # # return ($x, $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### WunderlichSerpentine xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0 || is_infinite($x) || is_infinite($y)) { return undef; } my $radix = $self->{'radix'}; my $radix_minus_1 = $radix - 1; my @xdigits = digit_split_lowtohigh($x,$radix); my @ydigits = digit_split_lowtohigh($y,$radix); ### @xdigits ### @ydigits my $serpentine_array = $self->{'serpentine_array'}; my $xk = 0; my $yk = 0; my $highpos = max($#xdigits,$#ydigits); my $transpose = $serpentine_array->[0] && ($highpos & 1); my $n = ($x * 0 * $y); # inherit bignum 0 foreach my $i (reverse 0 .. $highpos) { # high to low my $xdigit = $xdigits[$i] || 0; my $ydigit = $ydigits[$i] || 0; my $ndigit; ### $n ### $xk ### $yk ### $transpose ### $xdigit ### $ydigit if ($transpose) { if ($xk & 1) { $xdigit = $radix_minus_1 - $xdigit; } $n *= $radix; $n += $xdigit; $yk ^= $xdigit; if ($yk & 1) { $ydigit = $radix_minus_1 - $ydigit; } $n *= $radix; $n += $ydigit; $xk ^= $ydigit; $ndigit = $radix*$xdigit + $ydigit; } else { if ($yk & 1) { $ydigit = $radix_minus_1 - $ydigit; } $n *= $radix; $n += $ydigit; $xk ^= $ydigit; if ($xk & 1) { $xdigit = $radix_minus_1 - $xdigit; } $n *= $radix; $n += $xdigit; $yk ^= $xdigit; $ndigit = $radix*$ydigit + $xdigit; } ### ndigits: "$ydigit, $xdigit" $transpose ^= $serpentine_array->[$ndigit]; } return $n; # my $n = 0; # while (@x) { # if (($xk ^ $yk) & 1) { # { # my $digit = pop @x; # if ($xk & 1) { # $digit = $radix_minus_1 - $digit; # } # $n = ($n * $radix) + $digit; # $yk ^= $digit; # } # { # my $digit = pop @y; # if ($yk & 1) { # $digit = $radix_minus_1 - $digit; # } # $n = ($n * $radix) + $digit; # $xk ^= $digit; # } # } else { # { # my $digit = pop @y; # if ($yk & 1) { # $digit = $radix_minus_1 - $digit; # } # $n = ($n * $radix) + $digit; # $xk ^= $digit; # } # { # my $digit = pop @x; # if ($xk & 1) { # $digit = $radix_minus_1 - $digit; # } # $n = ($n * $radix) + $digit; # $yk ^= $digit; # } # } # } # # return $n; # return $n; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### rect_to_n_range(): "$x1,$y1 to $x2,$y2" if ($x2 < 0 || $y2 < 0) { return (1, 0); } my $radix = $self->{'radix'}; my ($pow, $level) = round_down_pow (max($x2,$y2), $radix); if (is_infinite($level)) { return (0, $level); } $pow *= $radix; return (0, $pow*$pow - 1); # my $n_power = $power * $power; # my $max_x = 0; # my $max_y = 0; # my $max_n = 0; # my $max_xk = 0; # my $max_yk = 0; # # my $min_x = 0; # my $min_y = 0; # my $min_n = 0; # my $min_xk = 0; # my $min_yk = 0; # # my $serpentine_array = $self->{'serpentine_array'}; # if ($serpentine_array->[0] && ($level&1)) { # $max_xk = $max_yk = $min_xk = $min_yk = 1; # } # # # # l<=cc2 or h-1c2 or h<=c1 # # so does overlap if # # l<=c2 and h>c1 # # # my $radix_minus_1 = $radix - 1; # my $overlap = sub { # my ($c,$ck,$digit, $c1,$c2) = @_; # if ($ck & 1) { # $digit = $radix_minus_1 - $digit; # } # ### overlap consider: "inv@{[$ck&1]}digit=$digit ".($c+$digit*$power)."<=c<".($c+($digit+1)*$power)." cf $c1 to $c2 incl" # return ($c + $digit*$power <= $c2 # && $c + ($digit+1)*$power > $c1); # }; # # while ($power > 1) { # $power = int($power/$radix); # $n_power = int($n_power/$radix); # # my $min_transpose = ($min_xk ^ $min_yk) & 1; # my $max_transpose = ($min_xk ^ $min_yk) & 1; # # ### $power # ### $n_power # ### $max_n # ### $min_n # if ($max_transpose) { # my $digit; # for ($digit = $radix_minus_1; $digit > 0; $digit--) { # last if &$overlap ($max_x,$max_xk,$digit, $x1,$x2); # } # $max_n += $n_power * $digit; # $max_yk ^= $digit; # if ($max_xk&1) { $digit = $radix_minus_1 - $digit; } # $max_x += $power * $digit; # } else { # my $digit; # for ($digit = $radix_minus_1; $digit > 0; $digit--) { # last if &$overlap ($max_y,$max_yk,$digit, $y1,$y2); # } # $max_n += $n_power * $digit; # $max_xk ^= $digit; # if ($max_yk&1) { $digit = $radix_minus_1 - $digit; } # $max_y += $power * $digit; # } # # if ($min_transpose) { # my $digit; # for ($digit = 0; $digit < $radix_minus_1; $digit++) { # last if &$overlap ($min_x,$min_xk,$digit, $x1,$x2); # } # $min_n += $n_power * $digit; # $min_yk ^= $digit; # if ($min_xk&1) { $digit = $radix_minus_1 - $digit; } # $min_x += $power * $digit; # } else { # my $digit; # for ($digit = 0; $digit < $radix_minus_1; $digit++) { # last if &$overlap ($min_y,$min_yk,$digit, $y1,$y2); # } # $min_n += $n_power * $digit; # $min_xk ^= $digit; # if ($min_yk&1) { $digit = $radix_minus_1 - $digit; } # $min_y += $power * $digit; # } # # $n_power = int($n_power/$radix); # if ($max_transpose) { # my $digit; # for ($digit = $radix_minus_1; $digit > 0; $digit--) { # last if &$overlap ($max_y,$max_yk,$digit, $y1,$y2); # } # $max_n += $n_power * $digit; # $max_xk ^= $digit; # if ($max_yk&1) { $digit = $radix_minus_1 - $digit; } # $max_y += $power * $digit; # } else { # my $digit; # for ($digit = $radix_minus_1; $digit > 0; $digit--) { # last if &$overlap ($max_x,$max_xk,$digit, $x1,$x2); # } # $max_n += $n_power * $digit; # $max_yk ^= $digit; # if ($max_xk&1) { $digit = $radix_minus_1 - $digit; } # $max_x += $power * $digit; # } # # if ($min_transpose) { # my $digit; # for ($digit = 0; $digit < $radix_minus_1; $digit++) { # last if &$overlap ($min_y,$min_yk,$digit, $y1,$y2); # } # $min_n += $n_power * $digit; # $min_xk ^= $digit; # if ($min_yk&1) { $digit = $radix_minus_1 - $digit; } # $min_y += $power * $digit; # } else { # my $digit; # for ($digit = 0; $digit < $radix_minus_1; $digit++) { # last if &$overlap ($min_x,$min_xk,$digit, $x1,$x2); # } # $min_n += $n_power * $digit; # $min_yk ^= $digit; # if ($min_xk&1) { $digit = $radix_minus_1 - $digit; } # $min_x += $power * $digit; # } # } # ### is: "$min_n at $min_x,$min_y to $max_n at $max_x,$max_y" # return ($min_n, $max_n); } #------------------------------------------------------------------------------ # levels use Math::PlanePath::ZOrderCurve; *level_to_n_range = \&Math::PlanePath::ZOrderCurve::level_to_n_range; *n_to_level = \&Math::PlanePath::ZOrderCurve::n_to_level; #----------------------------------------------------------------------------- 1; __END__ =for stopwords Walter Wunderlich Wunderlich's there'll eg Ryde OEIS trit-twiddling ie bignums prepending trit Math-PlanePath versa Online radix Uber Peano-Kurven Elemente der Mathematik Peano =head1 NAME Math::PlanePath::WunderlichSerpentine -- transpose parts of Peano curve, including coil order =head1 SYNOPSIS use Math::PlanePath::WunderlichSerpentine; my $path = Math::PlanePath::WunderlichSerpentine->new (serpentine_type => '111_000_111'); my ($x, $y) = $path->n_to_xy (123); # or another radix digits ... my $path5 = Math::PlanePath::WunderlichSerpentine->new (radix => 5); =head1 DESCRIPTION XThis is an integer version of Walter Wunderlich's variations on the C. =over Walter Wunderlich, "Uber Peano-Kurven", Elemente der Mathematik, volume 28, number 1, 1973, pages 1-10. L, L =back A "serpentine type" controls transposing of selected 3x3 sub-parts. The default is "alternating" 010,101,010 which transposes every second sub-part, 8 | 60--61--62--63 68--69 78--79--80--81 | | | | | | | 7 | 59--58--57 64 67 70 77--76--75 ... | | | | | | 6 | 54--55--56 65--66 71--72--73--74 | | 5 | 53 48--47 38--37--36--35 30--29 | | | | | | | | 4 | 52 49 46 39--40--41 34 31 28 | | | | | | | | 3 | 51--50 45--44--43--42 33--32 27 | | 2 | 6-- 7-- 8-- 9 14--15 24--25--26 | | | | | | 1 | 5-- 4-- 3 10 13 16 23--22--21 | | | | | | Y=0 | 0-- 1-- 2 11--12 17--18--19--20 | +------------------------------------- X=0 1 2 3 4 5 6 7 8 C can be a string of 0s and 1s, with optional space, comma or _ separators at each group of 3, "011111011" 0/1 string "011,111,011" "011_111_011" "011 111 011" or special values "alternating" 01010101.. the default "coil" 11111... all 1s described below "Peano" 00000... all 0s, gives PeanoCurve Each "1" sub-part is transposed. The string is applied in order of the N parts, irrespective of what net reversals and transposes are in force on a particular part. When no parts are transposed, which is a string of all 0s, the result is the same as the C. The special C "Peano"> gives that. =head2 Coil Order C "coil"> means "111 111 111" to transpose all parts. The result is like a coil viewed side-on, 8 24--25--26--27--28--29 78--79--80--81--... | | | 7 23--22--21 32--31--30 77--76--75 | | | 6 18--19--20 33--34--35 72--73--74 | | | 5 17--16--15 38--37--36 71--70--69 | | | 4 12--13--14 39--40--41 66--67--68 | | | 3 11--10-- 9 44--43--42 65--64--63 | | | 2 6-- 7-- 8 45--46--47 60--61--62 | | | 1 5-- 4-- 3 50--49--48 59--58--57 | | | Y=0 0-- 1-- 2 51--52--53--54--55--56 X=0 1 2 3 4 5 6 7 8 Whenever C begins with a "1", the initial sub-part is transposed at each level. The first step N=0 to N=1 is kept fixed along the X axis, then the higher levels are transposed. For example in the coil above The N=9 to N=17 part is upwards, and then the N=81 to N=161 part is to the right, and so on. =head2 Radix The optional C parameter gives the size of the sub-parts, similar to the C C parameter (see L). For example radix 5 gives radix => 5 4 | 20-21-22-23-24-25 34-35 44-45 70-71-72-73-74-75 84-85 | | | | | | | | | | | 3 | 19-18-17-16-15 26 33 36 43 46 69-68-67-66-65 76 83 86 | | | | | | | | | | | 2 | 10-11-12-13-14 27 32 37 42 47 60-61-62-63-64 77 82 87 | | | | | | | | | | | 1 | 9--8--7--6--5 28 31 38 41 48 59-58-57-56-55 78 81 88 | | | | | | | | | | | Y=0 | 0--1--2--3--4 29-30 39-40 49-50-51-52-53-54 79-80 89-.. +--------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Like the C, if the radix is even then the ends of each sub-part don't join up. For example in radix 4 N=15 isn't next to N=16, nor N=31 to N=32, etc. | | 3 | 15--14--13--12 16 23--24 31 47--46--45--44 48 55--56 63 | | | | | | | | | | | 2 | 8-- 9--10--11 17 22 25 30 40--41--42--43 49 54 57 62 | | | | | | | | | | | 1 | 7-- 6-- 5-- 4 18 21 26 29 39--38--37--36 50 53 58 61 | | | | | | | | | | | Y=0 | 0-- 1-- 2-- 3 19--20 27--28 32--33--34--35 51--52 59--60 +---------------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 In the C 0,1 form, space, comma, etc, separators should group C many values, so for example serpentine_type => "00000_11111_00000_00000_11111" The intention is to do something friendly if the separators are not on such boundaries, so that say 000_111_000 can have a sensible meaning in a radix higher than 3. But exactly what is not settled, so always give a full string of desired 0,1 for now. =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::WunderlichSerpentine-Enew ()> =item C<$path = Math::PlanePath::WunderlichSerpentine-Enew (serpentine_type =E $str, radix =E $r)> Create and return a new path object. The optional C parameter gives the base for digit splitting. The default is ternary, radix 3. The radix should be an odd number, 3, 5, 7, 9 etc. =back =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/DiagonalRationals.pm0000644000175000017500000002676613734026672021205 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Maybe: # including_zero=>1 to have 0/1 for A038567 package Math::PlanePath::DiagonalRationals; use 5.004; use strict; use Carp 'croak'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_rect_for_first_quadrant = \&Math::PlanePath::_rect_for_first_quadrant; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::CoprimeColumns; *_extend = \&Math::PlanePath::CoprimeColumns::_extend; *_coprime = \&Math::PlanePath::CoprimeColumns::_coprime; use vars '@_x_to_n'; *_x_to_n = \@Math::PlanePath::CoprimeColumns::_x_to_n; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ { name => 'direction', share_key => 'direction_downup', display => 'Direction', type => 'enum', default => 'down', choices => ['down','up'], choices_display => ['Down','Up'], description => 'Number points downwards or upwards along the diagonals.', }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; use constant default_n_start => 1; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; use constant x_minimum => 1; use constant y_minimum => 1; use constant gcdxy_maximum => 1; # no common factor sub absdx_minimum { my ($self) = @_; return ($self->{'direction'} eq 'down' ? 0 : 1); } sub absdy_minimum { my ($self) = @_; return ($self->{'direction'} eq 'down' ? 1 : 0); } use constant dsumxy_minimum => 0; use constant dsumxy_maximum => 1; # to next diagonal stripe sub dir_minimum_dxdy { my ($self) = @_; return ($self->{'direction'} eq 'down' ? (0,1) # North : (1,0)); # East } sub dir_maximum_dxdy { my ($self) = @_; return ($self->{'direction'} eq 'down' ? (1,-1) # South-East : (2,-1)); # ESE at N=3 down to X axis } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } my $direction = ($self->{'direction'} ||= 'down'); if (! ($direction eq 'up' || $direction eq 'down')) { croak "Unrecognised direction option: ", $direction; } return $self; } sub n_to_xy { my ($self, $n) = @_; ### DiagonalRationals n_to_xy(): $n if (2*($n - $self->{'n_start'}) < -1) { ### before n_start ... return; } my ($x,$y) = $self->Math::PlanePath::CoprimeColumns::n_to_xy($n+1) or return; ### CoprimeColumns returned: "x=$x y=$y" $x -= $y; ### shear to: "x=$x y=$y" return ($x,$y); } # Note: shared by FactorRationals sub xy_is_visited { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 1 || $y < 1 || ! _coprime($x,$y)) { return 0; } return 1; } sub xy_to_n { my ($self, $x, $y) = @_; ### DiagonalRationals xy_to_n(): "$x,$y" my $n = Math::PlanePath::CoprimeColumns::xy_to_n($self,$x+$y,$y); # not the N=0 at Xcol=1,Ycol=1 which is Xdiag=1,Ydiag=0 if (defined $n && $n > $self->{'n_start'}) { return $n-1; } else { return undef; } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### DiagonalRationals rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest($x1); $y1 = round_nearest($y1); $x2 = round_nearest($x2); $y2 = round_nearest($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 1 || $y2 < 1) { ### outside quadrant ... return (1, 0); } ### rect: "$x1,$y1 $x2,$y2" my $d2 = $x2 + $y2 + 1; if (is_infinite($d2)) { return (1, $d2); } while ($#_x_to_n < $d2) { _extend(); } my $d1 = max (2, $x1 + $y1); ### $d1 ### $d2 return ($_x_to_n[$d1] - 1 + $self->{'n_start'}, $_x_to_n[$d2] + $self->{'n_start'}); } 1; __END__ =for stopwords Ryde Math-PlanePath coprime coprimes coprimeness totient totients Euler's onwards OEIS =head1 NAME Math::PlanePath::DiagonalRationals -- rationals X/Y by diagonals =head1 SYNOPSIS use Math::PlanePath::DiagonalRationals; my $path = Math::PlanePath::DiagonalRationals->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path enumerates positive rationals X/Y with no common factor, going in diagonal order from Y down to X. 17 | 96... 16 | 80 15 | 72 81 14 | 64 82 13 | 58 65 73 83 97 12 | 46 84 11 | 42 47 59 66 74 85 98 10 | 32 48 86 9 | 28 33 49 60 75 87 8 | 22 34 50 67 88 7 | 18 23 29 35 43 51 68 76 89 99 6 | 12 36 52 90 5 | 10 13 19 24 37 44 53 61 77 91 4 | 6 14 25 38 54 69 92 3 | 4 7 15 20 30 39 55 62 78 93 2 | 2 8 16 26 40 56 70 94 1 | 1 3 5 9 11 17 21 27 31 41 45 57 63 71 79 95 Y=0 | +--------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 The order is the same as the C path, but only those X,Y with no common factor are numbered. 1/1, N = 1 1/2, 1/2, N = 2 .. 3 1/3, 1/3, N = 4 .. 5 1/4, 2/3, 3/2, 4/1, N = 6 .. 9 1/5, 5/1, N = 10 .. 11 N=1,2,4,6,10,etc at the start of each diagonal (in the column at X=1) is the cumulative totient, totient(i) = count numbers having no common factor with i i=K cumulative_totient(K) = sum totient(i) i=1 =head2 Direction Up Option C 'up'> reverses the order within each diagonal to count upward from the X axis. =cut # math-image --path=DiagonalRationals,direction=up --all --output=numbers --size=50x10 =pod direction => "up" 8 | 27 7 | 21 26 6 | 17 5 | 11 16 20 25 4 | 9 15 24 3 | 5 8 14 19 2 | 3 7 13 23 1 | 1 2 4 6 10 12 18 22 Y=0| +--------------------------- X=0 1 2 3 4 5 6 7 8 =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start with the same shape, For example to start at 0, =cut # math-image --path=DiagonalRationals,n_start=0 --all --output=numbers --size=50x10 =pod n_start => 0 8 | 21 7 | 17 22 6 | 11 5 | 9 12 18 23 4 | 5 13 24 3 | 3 6 14 19 2 | 1 7 15 25 1 | 0 2 4 8 10 16 20 26 Y=0| +--------------------------- X=0 1 2 3 4 5 6 7 8 =head2 Coprime Columns The diagonals are the same as the columns in C. For example the diagonal N=18 to N=21 from X=0,Y=8 down to X=8,Y=0 is the same as the C vertical at X=8. In general the correspondence is Xdiag = Ycol Ydiag = Xcol - Ycol Xcol = Xdiag + Ydiag Ycol = Xdiag C has an extra N=0 at X=1,Y=1 which is not present in C. (It would be Xdiag=1,Ydiag=0 which is 1/0.) The points numbered or skipped in a column up to X=Y is the same as the points numbered or skipped on a diagonal, simply because X,Y no common factor is the same as Y,X+Y no common factor. Taking the C as enumerating fractions F = Ycol/Xcol with S<0 E F E 1> the corresponding diagonal rational S<0 E R E infinity> is 1 F R = ------- = --- 1/F - 1 1-F 1 R F = ------- = --- 1/R + 1 1+R which is a one-to-one mapping between the fractions S 1> and all rationals. =cut # R = 1 / (1/F - 1) # F = Ycol/Xcol # R = 1 / (Xcol/Ycol - 1) # = 1 / (Xcol-Ycol)/Ycol # = Ycol / (Xcol-Ycol) # # R = 1 / (1/F - 1) # = 1 / (1-F)/F # = F/(1-F) # # 1/R = 1/F - 1 # 1/R + 1 = 1/F # F = 1 / (1/R + 1) # = 1 / (1+R)/R # = R/(1+R) # # F = 1 / (1/R + 1) # R = Xdiag/Ydiag # F = 1 / (Ydiag/Xdiag + 1) # = 1 / (Ydiag+Xdiag)/Xdiag # = Xdiag/(Ydiag+Xdiag) # = Ycol/Xcol # Xcol = Ydiag+Xdiag # Ycol = Xdiag # # R = 1 / (1/F - 1) # = 1 / ((1+R)/R - 1) # = 1 / ((1+R-R)/R) # = 1 / (1/R) # = R =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::DiagonalRationals-Enew ()> =item C<$path = Math::PlanePath::DiagonalRationals-Enew (direction =E $str, n_start =E $n)> Create and return a new path object. C (a string) can be "down" (the default) "up" =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 1 and if C<$n E 1> then the return is an empty list. =back =head1 BUGS The current implementation is fairly slack and is slow on medium to large N. A table of cumulative totients is built and retained for the diagonal d=X+Y. =head1 OEIS This enumeration of rationals is in Sloane's Online Encyclopedia of Integer Sequences in the following forms =over L (etc) =back direction=down, n_start=1 (the defaults) A020652 X, numerator A020653 Y, denominator A038567 X+Y sum, starting from X=1,Y=1 A054431 by diagonals 1=coprime, 0=not (excluding X=0 row and Y=0 column) A054430 permutation N at Y/X reverse runs of totient(k) many integers A054424 permutation DiagonalRationals -> RationalsTree SB A054425 padded with 0s at non-coprimes A054426 inverse SB -> DiagonalRationals A060837 permutation DiagonalRationals -> FactorRationals direction=down, n_start=0 A157806 abs(X-Y) difference direction=up swaps X,Y. =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/HIndexing.pm0000644000175000017500000004416513734026670017456 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # http://theinf1.informatik.uni-jena.de/~niedermr/publications.html # # Rolf Niedermeier # http://fpt.akt.tu-berlin.de/niedermr/publications.html # # # H second part down per paper # | # | *--* * *- # | | | | | # | * *--* * # | | | # | * *--* * # | | | | | # | O * *--* # | # +------------ # # eight similar to AlternatePaper # # | # *--* *--* * *- # | | | | | | # --* * * *--* *--* # | | | # * * *--*--*--* # | | | # *--* * O *--*--*--* # | | # *--*--*--* * * *--* # | | | # *--*--*--* * * *- # | | | # *--* *--* * * *- # | | | | | | # *--* *--* # package Math::PlanePath::HIndexing; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'round_up_pow', 'digit_split_lowtohigh'; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant diffxy_maximum => 0; # upper octant X<=Y so X-Y<=0 use constant 1.02 _UNDOCUMENTED__dxdy_list_at_n => 9; #------------------------------------------------------------------------------ sub n_to_xy { my ($self, $n) = @_; ### HIndexing n_to_xy(): $n if ($n < 0) { # negative return; } if (is_infinite($n)) { return ($n,$n); } { # ENHANCE-ME: get direction without full N+1 calculation my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $low = _divrem_mutate ($n, 2); ### $low ### $n my @digits = digit_split_lowtohigh($n,4); my $len = ($n*0 + 2) ** scalar(@digits); # inherit bignum 2 my $x = 0; my $y = 0; my $rev = 0; my $xinvert = 0; my $yinvert = 0; while (@digits) { my $digit = pop @digits; ### $len ### $rev ### $digit my $new_xinvert = $xinvert; my $new_yinvert = $yinvert; my $xo = 0; my $yo = 0; if ($rev) { if ($digit == 1) { $xo = $len-1; $yo = $len-1; $rev ^= 1; $new_yinvert = $yinvert ^ 1; } elsif ($digit == 2) { $xo = 2*$len-2; $yo = 0; $rev ^= 1; $new_xinvert = $xinvert ^ 1; } elsif ($digit == 3) { $xo = $len; $yo = $len; } } else { if ($digit == 1) { $xo = $len-2; $yo = $len; $rev ^= 1; $new_xinvert = $xinvert ^ 1; } elsif ($digit == 2) { $xo = 1; $yo = 2*$len-1; $rev ^= 1; $new_yinvert = $yinvert ^ 1; } elsif ($digit == 3) { $xo = $len; $yo = $len; } } ### $xo ### $yo if ($xinvert) { $x -= $xo; } else { $x += $xo; } if ($yinvert) { $y -= $yo; } else { $y += $yo; } $xinvert = $new_xinvert; $yinvert = $new_yinvert; $len /= 2; } ### final: "$x,$y" if ($yinvert) { $y -= $low; } else { $y += $low; } ### is: "$x,$y" return ($x, $y); } # uncomment this to run the ### lines #use Smart::Comments; sub xy_to_n { my ($self, $x, $y) = @_; ### HIndexing xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0 || $x > $y - ($y&1)) { return undef; } if (is_infinite($x)) { return $x; } my ($len, $level) = round_down_pow (int($y/1), 2); ### $len ### $level if (is_infinite($level)) { return $level; } my $n = 0; my $npower = $len*$len/2; my $rev = 0; while (--$level >= 0) { ### at: "$x,$y rev=$rev len=$len n=$n" my $digit; my $new_rev = $rev; if ($y >= $len) { $y -= $len; if ($x >= $len) { ### digit 3 ... $digit = 3; $x -= $len; } else { my $yinv = $len-1-$y; ### digit 1 or 2: "y reduce to $y, x cmp ".($yinv-($yinv&1)) if ($x > $yinv-($yinv&1)) { ### digit 2, x invert to: $len-1-$x $digit = 2; $x = $len-1-$x; } else { ### digit 1, y invert to: $yinv $digit = 1; $y = $yinv; } $new_rev ^= 1; } } else { ### digit 0 ... $digit = 0; } if ($rev) { $digit = 3 - $digit; ### reversed digit: $digit } $rev = $new_rev; ### add n: $npower*$digit $n += $npower*$digit; $len /= 2; $npower /= 4; } ### end at: "$x,$y n=$n rev=$rev" ### assert: $x == 0 ### assert: $y == 0 || $y == 1 return $n + $y^$rev; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### HIndexing rect_to_n_range(): "$x1,$y1 to $x2,$y2" # y2 & 1 excluding the X=Y diagonal on odd Y rows if ($x2 < 0 || $y2 < 0 || $x1 > $y2 - ($y2&1)) { return (1, 0); } my ($len, $level) = round_down_pow (($y2||1), 2); return (0, 2*$len*$len-1); } #------------------------------------------------------------------------------ sub level_to_n_range { my ($self, $level) = @_; return (0, 2*4**$level - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, 2); my ($pow,$exp) = round_up_pow ($n+1, 4); return $exp; } sub _UNDOCUMENTED__level_to_area { my ($self, $level) = @_; return (2**$level - 1)**2; } sub _UNDOCUMENTED__level_to_area_Y { my ($self, $level) = @_; if ($level == 0) { return 0; } return 2**(2*$level-1) - 2**$level; } sub _UNDOCUMENTED__level_to_area_up { my ($self, $level) = @_; if ($level == 0) { return 0; } return 2**(2*$level-1) - 2**$level + 1; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde ie Math-PlanePath Rolf Niedermeier octant Indexings OEIS =head1 NAME Math::PlanePath::HIndexing -- self-similar right-triangle traversal =head1 SYNOPSIS use Math::PlanePath::HIndexing; my $path = Math::PlanePath::HIndexing->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XXXThis is an infinite integer version of H-indexing per =over Rolf Niedermeier, Klaus Reinhardt and Peter Sanders, "Towards Optimal Locality In Mesh Indexings", Discrete Applied Mathematics, volume 117, March 2002, pages 211-237. L =back It traverses an eighth of the plane by self-similar right triangles. Notice the "H" shapes that arise from the backtracking, for example N=8 to N=23, and repeating above it. | | 15 | 63--64 67--68 75--76 79--80 111-112 115-116 123-124 127 | | | | | | | | | | | | | | | | 14 | 62 65--66 69 74 77--78 81 110 113-114 117 122 125-126 | | | | | | | | 13 | 61 58--57 70 73 86--85 82 109 106-105 118 121 | | | | | | | | | | | | | | 12 | 60--59 56 71--72 87 84--83 108-107 104 119-120 | | | | 11 | 51--52 55 40--39 88 91--92 99-100 103 | | | | | | | | | | | | 10 | 50 53--54 41 38 89--90 93 98 101-102 | | | | | | 9 | 49 46--45 42 37 34--33 94 97 | | | | | | | | | | 8 | 48--47 44--43 36--35 32 95--96 | | 7 | 15--16 19--20 27--28 31 | | | | | | | | 6 | 14 17--18 21 26 29--30 | | | | 5 | 13 10-- 9 22 25 | | | | | | 4 | 12--11 8 23--24 | | 3 | 3-- 4 7 | | | | 2 | 2 5-- 6 | | 1 | 1 | | Y=0 | 0 +------------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 The tiling is essentially the same as the Sierpinski curve (see L). The following is with two points per triangle. Or equally well it could be thought of with those triangles further divided to have one point each, a little skewed. +---------+---------+--------+--------/ | \ | / | \ | / | 15 \ 16| 19 /20 |27\ 28 |31 / | | \ || | / | | | \ | | | / | 14 \17| 18/ 21 |26 \29 |30 / | \ | / | \ | / +---------+---------+---------/ | / | \ | / | 13 /10 | 9 \ 22 | 25 / | | / | | | \ | | | / | 12/ 11 | 8 \23 | 24/ | / | \ | / +-------------------/ | \ | / | 3 \ 4 | 7 / | | \ | | | / | 2 \ 5 | 6 / | \ | / +----------/ | / | 1 / | | / | 0 / | / +/ The correspondence to the C path is as follows. The 4-point verticals like N=0 to N=3 are a Sierpinski horizontal, and the 4-point "U" parts like N=4 to N=7 are a Sierpinski vertical. In both cases there's an X,Y transpose and bit of stretching. 3 7 | / 2 1--2 5--6 6 | <=> / \ | | <=> | 1 0 3 4 7 5 | \ 0 4 =head2 Level Ranges Counting the initial N=0 to N=7 section as level 1, the X,Y ranges for a given level is Nlevel = 2*4^level - 1 Xmax = 2*2^level - 2 Ymax = 2*2^level - 1 For example level=3 is N through to Nlevel=2*4^3-1=127 and X,Y ranging up to Xmax=2*2^3-2=14 and Xmax=2*2^3-1=15. On even Y rows, the N on the X=Y diagonal is found by duplicating each bit in Y except the low zero (which is unchanged). For example Y=10 decimal is 1010 binary, duplicate to binary 1100110 is N=102. It would be possible to take a level as N=0 to N=4^k-1 too, which would be a triangle against the Y axis. The 2*4^level - 1 is per the paper above. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::HIndexing-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 2*4**$level - 1)>. =back =head1 FORMULAS =head2 Area The area enclosed by curve in its triangular level k is A[k] = (2^k-1)^2 = 0, 1, 9, 49, 225, 961, 3969, 16129, ... (A060867) =for GP-DEFINE A(k) = (2^k-1)^2 =for GP-DEFINE A_samples = [ 0, 1, 9, 49, 225, 961, 3969, 16129 ] =for GP-Test vector(length(A_samples),k,my(k=k-1); A(k)) == A_samples For example level k=2 enclosed area marked by "@" signs, 7 | *---*---*---*---*---*---31 | | | @ | | @ | | @ | 6 | * *---* * * *---* | | | @ | 5 | * *---* * * | | | @ | | @ | 4 | *---* * *---* level k=2 | | @ @ | N=0 to N=31 3 | *-- * * | | | @ | A[2] = 9 2 | * *-- * | | 1 | * | | Y=0 | 0 +------------------------------ X=0 1 2 3 4 5 6 The block breakdowns are +---------------+ ^ | \ ^ | | ^ / | |\ \ 2 | | 3 / | = 2^k - 1 | \ \ | | / | | 1\ \ | | / | | v \ \+--+/ v +----+ | | +----+ | ^ / | 0 / | / | / +/ <----> = 2^k - 2 Parts 0 and 3 are identical. Parts 1 and 2 are mirror images of 0 and 3 respectively. Parts 0 and 1 have an area in between 1 high and 2^k-2 wide (eg. 2^2-2=2 wide in the k=2 above). Parts 2 and 3 have an area in between 1 wide 2^k-1 high (eg. 2^2-1=3 high in the k=2 above). So the total area is A[k] = 4*A[k-1] + 2^k-2 + 2^k-1 starting A[0] = 0 = 4^0 * (2*2^k - 3) + 4^1 * (2*2^(k-1) - 3) + 4^2 * (2*2^(k-2) - 3) + ... + 4^(k-1) * (2*2^1 - 3) + 4^k * A[0] = 2*2*(4^k - 2^k)/(4-2) - 3*(4^k - 1)/(4-1) = (2^k - 1)^2 =for GP-Test A(0) == 0 =for GP-Test vector(50,k, 4*A(k-1) + 2^k-2 + 2^k-1) == vector(50,k, A(k)) =for GP-Test vector(50,k, sum(i=0,k-1, 4^i*(2*2^(k-i) - 3))) == vector(50,k, A(k)) =for GP-Test vector(50,k, 2*2*(4^k - 2^k)/(4-2) - 3*(4^k - 1)/(4-1)) == vector(50,k, A(k)) =cut # = 2*2*( 2^(k-1) + 4*2^(k-2) + ... + 4^(k-1) # - 3*( 1 + 4 + ... + 4^*(k-1) ) # # 2*(2^(k-1)*2^(k-1) - 2*2^(k-1) + 1) + 2^k - 2 # = 2*(2^(k-1)*2^(k-1) - 2*2^(k-1)) + 2*2^(k-1) # = 2*2^(k-1)*(2^(k-1)*2^(k-1) - 2) # = 2^k * (2^(k-1) - 2) # # vector(10,k,my(k=k-1); A(k)) # vector(10,k,my(k=k-1); Afirst(k)) =pod =head2 Half Level Areas Block 1 ends at the top-left corner and block 2 start there. The area before that midpoint enclosed to the Y axis can be calculated. Likewise the area after that midpoint to the top line. Both are two blocks, and with either 2^k-2 or 2^k-1 in between. They're therefore half the total area A[k], with the extra unit square going to the top AT[k]. AY[k] = floor(A[k]/2) = 0, 0, 4, 24, 112, 480, 1984, 8064, 32512, ... (A059153) AT[k] = ceil(A[k]/2) = 0, 1, 5, 25, 113, 481, 1985, 8065, 32513, ... (A092440) =for GP-DEFINE AY(k) = floor(A(k)/2) =for GP-DEFINE AT(k) = ceil(A(k)/2) =for GP-DEFINE AY_samples = [0, 0, 4, 24, 112, 480, 1984, 8064, 32512, 130560] =for GP-DEFINE AT_samples = [0, 1, 5, 25, 113, 481, 1985, 8065, 32513, 130561] =for GP-Test vector(length(AY_samples),k,my(k=k-1); AY(k)) == AY_samples =for GP-Test vector(length(AT_samples),k,my(k=k-1); AT(k)) == AT_samples =for GP-DEFINE AY(k) = 2*(2^(k-1)*2^(k-1) - 2*2^(k-1)) + 2*2^(k-1) =for GP-DEFINE AY(k) = 2*(2^(k-1)*2^(k-1) - 2^(k-1)) =for GP-DEFINE AY(k) = 2^k * (2^(k-1) - 1) =for GP-DEFINE AY(k) = 4^k + 2^k * (2^(k-1) - 1 - 2^k) =for GP-DEFINE AY(k) = 4^k + 2^k * (-2^(k-1) - 1) =for GP-DEFINE AY(k) = (2^k-1)^2 - (2^k-1)^2 + 2^k * (2^(k-1) - 1) =for GP-Test vector(50,k, AY(k)) == vector(50,k, 2*A(k-1)+2^k-2) =for GP-Test vector(50,k, AT(k)) == vector(50,k, 2*A(k-1)+2^k-1) 15 | 14 | 13 10-- 9 | | @ | 12--11 8 @ @ | 3 3-- 4 7 | | | @ | 2 2 5-- 6 | | 1 1 | | 0 0 0 AY[0] = 0 AY[1] = 0 AY[2] = 4 =cut =pod 1 3-- 4 7 15--16 19--20 27--28 31 | @ | | @ | | @ | | @ | 5-- 6 17--18 21 26 29--30 | @ | 22 25 | @ | 23--24 AT[0] = 0 AT[1] = 1 AT[2] = 5 =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A334235 X coordinate A334236 Y coordinate A097110 Y at N=2^k, being successively 2^j-1, 2^j A060867 area of level A059153 area of level first half A092440 area of level second half =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/R5DragonMidpoint.pm0000644000175000017500000003563413734026657020734 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=R5DragonMidpoint --lines --scale=40 # # math-image --path=R5DragonMidpoint --all --output=numbers_dash --size=78x60 # math-image --path=R5DragonMidpoint,arms=6 --all --output=numbers_dash --size=78x60 package Math::PlanePath::R5DragonMidpoint; use 5.004; use strict; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_up_pow', 'digit_join_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; use constant parameter_info_array => [ { name => 'arms', share_key => 'arms_4', display => 'Arms', type => 'integer', minimum => 1, maximum => 4, default => 1, width => 1, description => 'Arms', } ]; { my @x_negative_at_n = (undef, 14,5,2,2); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } { my @y_negative_at_n = (undef, 93,35,11,7); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(4, $self->{'arms'} || 1)); return $self; } sub n_to_xy { my ($self, $n) = @_; ### R5DragonMidpoint n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } { my $int = int($n); if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'}); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } # ENHANCE-ME: own code ... # require Math::PlanePath::R5DragonCurve; my ($x1,$y1) = $self->Math::PlanePath::R5DragonCurve::n_to_xy($n); my ($x2,$y2) = $self->Math::PlanePath::R5DragonCurve::n_to_xy($n+$self->{'arms'}); # x = x1+x2 # y = y1+y2 # rotx = x+y = (y1+y2)+(x1+x2) # roty = y-x = (y1+y2)-(x1+x2) # cx = (rotx-1)/2 # cy = (rotx+1)/2 $x1 += $x2; $y1 += $y2; return (($x1+$y1-1)/2, ($y1-$x1+1)/2); } # table of triplets $ndigit,$dx,$dy for ($y%10)*10+($x%10), total 300 entries my @yx_to_digdxdy # 30 each row = (0,0,0, 1,-1,0, 1,0,-1, 2,-1,-1, 3,-2,-1, 3,-1,-2, 4,-2,-2, 0,-2,0, 2,-1,-1, 4,0,-2, 4,-2,0, 2,-1,-1, 0,0,-2, 4,0,0, 3,-1,0, 3,0,-1, 2,-1,-1, 1,-2,-1, 1,-1,-2, 0,-2,-2, 3,-2,-1, 3,-1,-2, 4,-2,-2, 0,-2,0, 2,-1,-1, 4,0,-2, 0,0,0, 1,-1,0, 1,0,-1, 2,-1,-1, 3,-1,0, 3,0,-1, 2,-1,-1, 1,-2,-1, 1,-1,-2, 0,-2,-2, 4,-2,0, 2,-1,-1, 0,0,-2, 4,0,0, 2,-1,-1, 4,0,-2, 0,0,0, 1,-1,0, 1,0,-1, 2,-1,-1, 3,-2,-1, 3,-1,-2, 4,-2,-2, 0,-2,0, 1,-1,-2, 0,-2,-2, 4,-2,0, 2,-1,-1, 0,0,-2, 4,0,0, 3,-1,0, 3,0,-1, 2,-1,-1, 1,-2,-1, 1,0,-1, 2,-1,-1, 3,-2,-1, 3,-1,-2, 4,-2,-2, 0,-2,0, 2,-1,-1, 4,0,-2, 0,0,0, 1,-1,0, 0,0,-2, 4,0,0, 3,-1,0, 3,0,-1, 2,-1,-1, 1,-2,-1, 1,-1,-2, 0,-2,-2, 4,-2,0, 2,-1,-1, 4,-2,-2, 0,-2,0, 2,-1,-1, 4,0,-2, 0,0,0, 1,-1,0, 1,0,-1, 2,-1,-1, 3,-2,-1, 3,-1,-2, 2,-1,-1, 1,-2,-1, 1,-1,-2, 0,-2,-2, 4,-2,0, 2,-1,-1, 0,0,-2, 4,0,0, 3,-1,0, 3,0,-1, ); # arm $x $y 2 | 1 Y=1 # 0 0 0 3 | 0 Y=0 # 1 0 1 ----+---- # 2 -1 1 X=-1 X=0 # 3 -1 0 my @xy_to_arm = ([0, # x=0,y=0 1], # x=0,y=1 [3, # x=-1,y=0 2]); # x=-1,y=1 sub xy_to_n { my ($self, $x, $y) = @_; ### R5DragonMidpoint xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); foreach my $overflow (2*$x + 2*$y, 2*$x - 2*$y) { if (is_infinite($overflow)) { return $overflow; } } my $zero = ($x * 0 * $y); # inherit bignum 0 my @ndigits; # low to high; for (;;) { last if ($x <= 0 && $x >= -1 && $y <= 1 && $y >= 0); my $k = 3*(10*($y%10) + ($x%10)); ### at: "x=$x,y=$y (k=$k) ndigits=".join(',',@ndigits)." digit=$yx_to_digdxdy[$k] offset=$yx_to_digdxdy[$k+1],$yx_to_digdxdy[$k+2] to ".($x+$yx_to_digdxdy[$k+1]).",".($y+$yx_to_digdxdy[$k+2]) push @ndigits, $yx_to_digdxdy[$k++]; # ndigit $x += $yx_to_digdxdy[$k++]; # dx $y += $yx_to_digdxdy[$k]; # dy # (x+iy)/(1+2i) # = (x+iy)*(1-2i) / (1+4) # = (x+iy)*(1-2i) / 5 # = (x+2y +i(y-2x)) / 5 # ### assert: abs($x + 2 * $y) % 5 == 0 ### assert: abs($y - 2 * $x) % 5 == 0 ($x,$y) = (($x+2*$y) / 5, # divide 1+2i ($y-2*$x) / 5); ### divide down to: "$x,$y" } ### final: "xy=$x,$y" my $arm = $xy_to_arm[$x]->[$y]; ### $arm my $arms_count = $self->arms_count; if ($arm >= $arms_count) { return undef; } if ($arm & 1) { ### flip ... @ndigits = map {4-$_} @ndigits; } return digit_join_lowtohigh(\@ndigits, 5, $zero) * $arms_count + $arm; } #------------------------------------------------------------------------------ # whole plane covered when arms==4 sub xy_is_visited { my ($self, $x, $y) = @_; return ($self->{'arms'} == 4 || defined($self->xy_to_n($x,$y))); } #------------------------------------------------------------------------------ # FIXME: half size of R5DragonCurve ? # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### R5DragonCurve rect_to_n_range(): "$x1,$y1 $x2,$y2" my $xmax = int(max(abs($x1),abs($x2))); my $ymax = int(max(abs($y1),abs($y2))); return (0, ($xmax*$xmax + $ymax*$ymax) * 6 * $self->{'arms'}); } #----------------------------------------------------------------------------- # level_to_n_range() # arms=1 arms=2 # level 0 0..0 = 1 0..1 = 2 # level 1 0..4 = 5 0..9 = 10 # level 2 0..24 = 25 0..49 = 50 sub level_to_n_range { my ($self, $level) = @_; return (0, 5**$level * $self->{'arms'} - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, $self->{'arms'}); my ($pow, $exp) = round_up_pow ($n+1, 5); return $exp; } #----------------------------------------------------------------------------- 1; __END__ =for stopwords eg Ryde Math-PlanePath Nlevel et al terdragon ie Xmod10 Ymod10 Jorg Arndt =head1 NAME Math::PlanePath::R5DragonMidpoint -- R5 dragon curve midpoints =head1 SYNOPSIS use Math::PlanePath::R5DragonMidpoint; my $path = Math::PlanePath::R5DragonMidpoint->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis is midpoints of the R5 dragon curve by Jorg Arndt, 31--30 11 | | 32 29 10 | | 51--50 35--34--33 28--27--26 9 | | | | 52 49 36--37--38 23--24--25 8 | | | | 55--54--53 48--47--46 41--40--39 22 7 | | | | 56--57--58 63--64 45 42 19--20--21 6 | | | | | | 81--80 59 62 65 44--43 18--17--16 11--10 5 | | | | | | | | 82 79 60--61 66--67--68 15 12 9 4 | | | | | | ..-83 78--77--76 71--70--69 14--13 8-- 7-- 6 3 | | | 75 72 3-- 4-- 5 2 | | | 74--73 2 1 | 0-- 1 <- Y=0 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 The points are the middle of each edge of the C, rotated -45 degrees, shrunk by sqrt(2). and shifted to the origin. *--11--* *--7--* R5DragonCurve | | | | and its midpoints 12 10 8 6 | | | | *--17--*--13--*--9--*--5--* | | | | 18 16 14 4 | | | | ..-* *--15--* *--3--* | 2 | +--1--* =head2 Arms Multiple copies of the curve can be selected, each advancing successively. Like the main C this midpoint curve covers 1/4 of the plane and 4 arms rotated by 0, 90, 180, 270 degrees mesh together perfectly. With 4 arms all integer X,Y points are visited. C 4> begins as follows. N=0,4,8,12,16,etc is the first arm (the same shape as the plain curve above), then N=1,5,9,13,17 the second, N=2,6,10,14 the third, etc. arms=>4 76--80-... 6 | 72--68--64 44--40 5 | | | 25--21 60 48 36 4 | | | | | 29 17 56--52 32--28--24 75--79 3 | | | | | 41--37--33 13-- 9-- 5 12--16--20 71 83 2 | | | | | 45--49--53 6-- 2 1 8 59--63--67 ... 1 | | | | ... 65--61--57 10 3 0-- 4 55--51--47 <- Y=0 | | | | | 81 69 22--18--14 7--11--15 35--39--43 -1 | | | | | 77--73 26--30--34 54--58 19 31 -2 | | | | | 38 50 62 23--27 -3 | | | 42--46 66--70--74 -4 | ...-82--78 -5 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::R5DragonMidpoint-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =item C<$n = $path-En_start()> Return 0, the first N in the path. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 5**$level - 1)>, or for multiple arms return C<(0, $arms * 5**$level - 1)>. There are 5^level segments comprising the curve, or arms*5^level when multiple arms, numbered starting from 0. =back =head1 FORMULAS =head2 X,Y to N An X,Y point can be turned into N by dividing out digits of a complex base 1+2i. At each step the low base-5 digit is formed from X,Y and an adjustment applied to move X,Y to a multiple of 1+2i ready to divide out. A 10x10 table is used for the digit and adjustments, indexed by Xmod10 and Ymod10. There's probably an a*X+b*Y mod 5 or mod 20 for a smaller table. But in any case once the adjustment is found the result is Ndigit = digit_table[X mod 10, Y mod 10] # low to high Xm = X + Xadj_table [X mod 10, Y mod 10] Ym = Y + Yadj_table [X mod 10, Y mod 10] new X,Y = (Xm,Ym) / (1+2i) = (Xm,Ym) * (1-2i) / 5 = ((Xm+2*Ym)/5, (Ym-2*Xm)/5) These X,Y reductions eventually reach one of the starting points for the four arms X,Y endpoint Arm +---+---+ ------------ --- | 2 | 1 | Y=1 0, 0 0 +---+---+ 0, 1 1 | 3 | 0 | Y=0 -1, 1 2 +---+---+ -1, 0 3 X=-1 X=0 For arms 1 and 3 the digits must be flipped 4-digit, so 0,1,2,3,4 -> 4,3,2,1,0. The arm number and hence whether this flip is needed is not known until reaching the endpoint. if arm odd then N = 5^numdigits - 1 - N If only some of the arms are of interest then reaching one of the other arm numbers means the original X,Y was outside the desired curve. =head1 SEE ALSO L, L L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/TriangularHypot.pm0000644000175000017500000010152413734026654020730 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=TriangularHypot # A034017 - loeschian primatives xx+xy+yy, primes 3k+1 and a factor of 3 # which is when x^2-x+1 mod n has a solution # # A092572 - all x^2+3*y^2 # A158937 - all x^2+3*y^2 with repetitions x>0,y>0 # # A092572 - 6n+1 primes # A055664 - norms of Eisenstein-Jacobi primes # A008458 - hex coordination sequence, 1 and multiples of 6 # # A2 centred at lattice point: # A014201 - x*x+x*y+y*y solutions excluding 0,0 # A038589 - lattice sizes, =A014201+1 # A038590 - sizes, uniques of A038589 # A038591 - 3fold symmetry, union A038588 and A038590 # # A2 centred at hole # A038587 - centred deep hole # A038588 - centred deep hole uniques of A038587 # A005882 - theta relative hole # 3,3,6,0,6,3,6,0,3,6,6,0,6,0,6,0,9,6,0,0,6,3,6,0,6,6,6,0,0,0,12, # A033685 - theta series of hexagonal lattice A_2 with respect to deep hole. # 1/3 steps of norm, so extra zeros # 0,3,0,0,3,0,0,6,0,0,0,0,0,6,0,0,3,0,0,6,0,0,0,0,0,3,0,0,6,0,0,6, # # A005929 Theta series of hexagonal net with respect to mid-point of edge. # [27] [28] [31] # [12] [13] [16] [21] [28] # [7] [4] [3] [4] [7] [12] [19] [28] # [25] [16] [9] [4] [1] [0] [1] [4] [9] [16] [25] [36] # [7] [4] [3] [4] [7] # [12] # [27] # mirror across +60 # (X,Y) = ((X+3Y)/2, (Y-X)/2); # rotate -60 # Y = -Y; # mirror # (X,Y) = ((X-3Y)/2, (X+Y)/2); # rotate +60 # # (X,Y) = ((X+3Y)/2, (Y-X)/2); # rotate -60 # (X,Y) = ((X+3Y)/2, (X-Y)/2); # # (X,Y) = (((X+3Y)/2+3(Y-X)/2)/2, ((X+3Y)/2-(Y-X)/2)/2); # = (((X+3Y)+3(Y-X))/4, ((X+3Y)-(Y-X))/4); # = ((X + 3Y + 3Y - 3X)/4, (X + 3Y - Y + X)/4); # = ((-2X + 6Y)/4, (2X + 2Y)/4); # = ((-X + 3Y)/2, (X+Y)/2); # # eg X=6,Y=0 -> X=-6/2=-3 Y=(6+0)/2=3 package Math::PlanePath::TriangularHypot; use 5.004; use strict; use Carp 'croak'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ { name => 'points', share_type => 'points_eoahrc', display => 'Points', type => 'enum', default => 'even', choices => ['even','odd', 'all', 'hex','hex_rotated','hex_centred', ], choices_display => ['Even','Odd', 'All', 'Hex','Hex Rotated','Hex Centred', ], description => 'Which X,Y points visit, either X+Y even or odd, or all points, or hexagonal grid points.', }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; { my %x_negative_at_n = (even => 3, odd => 1, all => 2, hex => 2, hex_rotated => 2, hex_centred => 2, ); sub x_negative_at_n { my ($self) = @_; return $self->n_start + $x_negative_at_n{$self->{'points'}}; } } { my %y_negative_at_n = (even => 5, odd => 3, all => 4, hex => 3, hex_rotated => 3, hex_centred => 4, ); sub y_negative_at_n { my ($self) = @_; return $self->n_start + $y_negative_at_n{$self->{'points'}}; } } sub rsquared_minimum { my ($self) = @_; return ($self->{'points'} eq 'odd' ? 1 # at X=1,Y=0 : $self->{'points'} eq 'hex_centred' ? 2 # at X=1,Y=1 : 0); # even,all,hex,hex_rotated at X=0,Y=0 } *sumabsxy_minimum = \&rsquared_minimum; sub absdiffxy_minimum { my ($self) = @_; return ($self->{'points'} eq 'odd' ? 1 # odd, line X=Y not included : 0); # even,all includes X=Y } { my %_UNDOCUMENTED__turn_any_left_at_n = (even => 1, odd => 3, all => 4, hex => 1, hex_rotated => 1, hex_centred => 1, ); sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; my $n = $_UNDOCUMENTED__turn_any_left_at_n{$self->{'points'}}; return (defined $n ? $self->n_start + $n : undef); } } { # even,hex, left or straight only # odd,all both left or right my %turn_any_right = (# even => 0, odd => 1, all => 1, # hex => 0, # hex_rotated => 0, # hex_centred => 0, ); sub turn_any_right { my ($self) = @_; return $turn_any_right{$self->{'points'}}; } } sub turn_any_straight { my ($self) = @_; return ($self->{'points'} eq 'hex' || $self->{'points'} eq 'odd' ? 0 # never straight : 1); } { my %_UNDOCUMENTED__turn_any_straight_at_n = (even => 30, # odd => undef, # never straight all => 1, # hex => undef, # never straight hex_rotated => 57, hex_centred => 23, ); sub _UNDOCUMENTED__turn_any_straight_at_n { my ($self) = @_; my $n = $_UNDOCUMENTED__turn_any_straight_at_n{$self->{'points'}}; return (defined $n ? $self->n_start + $n : undef); } } #------------------------------------------------------------------------------ sub new { ### TriangularHypot new() ... my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } my $points = ($self->{'points'} ||= 'even'); if ($points eq 'all') { $self->{'n_to_x'} = [0]; $self->{'n_to_y'} = [0]; $self->{'hypot_to_n'} = [0]; # N=0 at X=0,Y=0 $self->{'y_next_x'} = [1-1]; $self->{'y_next_hypot'} = [3*0**2 + 1**2]; $self->{'x_inc'} = 1; $self->{'x_inc_factor'} = 2; # ((x+1)^2 - x^2) = 2*x+1 $self->{'x_inc_squared'} = 1; $self->{'symmetry'} = 4; } elsif ($points eq 'even') { $self->{'n_to_x'} = [0]; $self->{'n_to_y'} = [0]; $self->{'hypot_to_n'} = [0]; # N=0 at X=0,Y=0 $self->{'y_next_x'} = [2-2]; $self->{'y_next_hypot'} = [3*0**2 + 2**2]; $self->{'x_inc'} = 2; $self->{'x_inc_factor'} = 4; # ((x+2)^2 - x^2) = 4*x+4 $self->{'x_inc_squared'} = 4; $self->{'skip_parity'} = 1; $self->{'symmetry'} = 12; } elsif ($points eq 'odd') { $self->{'n_to_x'} = []; $self->{'n_to_y'} = []; $self->{'hypot_to_n'} = []; $self->{'y_next_x'} = [1-2]; $self->{'y_next_hypot'} = [1]; $self->{'x_inc'} = 2; $self->{'x_inc_factor'} = 4; $self->{'x_inc_squared'} = 4; $self->{'skip_parity'} = 0; $self->{'symmetry'} = 4; } elsif ($points eq 'hex') { $self->{'n_to_x'} = [0]; # N=0 at X=0,Y=0 $self->{'n_to_y'} = [0]; $self->{'hypot_to_n'} = [0]; # N=0 at X=0,Y=0 $self->{'y_next_x'} = [2-2]; $self->{'y_next_hypot'} = [2**2 + 3*0**2]; # next at X=2,Y=0 $self->{'x_inc'} = 2; $self->{'x_inc_factor'} = 4; # ((x+2)^2 - x^2) = 4*x+4 $self->{'x_inc_squared'} = 4; $self->{'skip_parity'} = 1; # should be even $self->{'skip_hex'} = 4; # x+3y==0,2 only $self->{'symmetry'} = 6; } elsif ($points eq 'hex_rotated') { $self->{'n_to_x'} = [0]; # N=0 at X=0,Y=0 $self->{'n_to_y'} = [0]; $self->{'hypot_to_n'} = [0]; # N=0 at X=0,Y=0 $self->{'y_next_x'} = [4-2, 1-2]; $self->{'y_next_hypot'} = [4**2 + 3*0**2, # next at X=4,Y=0 1**2 + 3*1**2]; # next at X=1,Y=1 $self->{'x_inc'} = 2; $self->{'x_inc_factor'} = 4; # ((x+2)^2 - x^2) = 4*x+4 $self->{'x_inc_squared'} = 4; $self->{'skip_parity'} = 1; # should be even $self->{'skip_hex'} = 2; # x+3y==0,4 only $self->{'symmetry'} = 6; } elsif ($points eq 'hex_centred') { $self->{'n_to_x'} = []; $self->{'n_to_y'} = []; $self->{'hypot_to_n'} = []; $self->{'y_next_x'} = [2-2]; # for first at X=2 $self->{'y_next_hypot'} = [2**2 + 3*0**2]; # at X=2,Y=0 $self->{'x_inc'} = 2; $self->{'x_inc_factor'} = 4; # ((x+2)^2 - x^2) = 4*x+4 $self->{'x_inc_squared'} = 4; $self->{'skip_parity'} = 1; # should be even $self->{'skip_hex'} = 0; # x+3y==2,4 only $self->{'symmetry'} = 12; } else { croak "Unrecognised points option: ", $points; } ### $self ### assert: $self->{'y_next_hypot'}->[0] == (3 * 0**2 + ($self->{'y_next_x'}->[0]+$self->{'x_inc'})**2) return $self; } sub _extend { my ($self) = @_; ### _extend() ... my $n_to_x = $self->{'n_to_x'}; my $n_to_y = $self->{'n_to_y'}; my $hypot_to_n = $self->{'hypot_to_n'}; my $y_next_x = $self->{'y_next_x'}; my $y_next_hypot = $self->{'y_next_hypot'}; ### $y_next_x ### $y_next_hypot # set @y to the Y with the smallest $y_next_hypot->[$y], and if there's some # Y's with equal smallest hypot then all those Y's in ascending order my @y = (0); my $hypot = $y_next_hypot->[0]; for (my $i = 1; $i < @$y_next_x; $i++) { if ($hypot == $y_next_hypot->[$i]) { push @y, $i; } elsif ($hypot > $y_next_hypot->[$i]) { @y = ($i); $hypot = $y_next_hypot->[$i]; } } ### chosen y list: @y # if the endmost of the @$y_next_x, @y_next_hypot arrays are used then # extend them by one if ($y[-1] == $#$y_next_x) { my $y = scalar(@$y_next_x); # new Y value ### highest y: $y[-1] ### so grow y: $y my $points = $self->{'points'}; if ($points eq 'even') { # h = (3 * $y**2 + $x**2) # = (3 * $y**2 + ($3*y)**2) # = (3*$y*$y + 9*$y*$y) # = (12*$y*$y) $y_next_x->[$y] = 3*$y - $self->{'x_inc'}; # X=3*Y, so X-2=3*Y-2 $y_next_hypot->[$y] = 12*$y*$y; } elsif ($points eq 'odd') { my $odd = ! ($y%2); $y_next_x->[$y] = $odd - $self->{'x_inc'}; $y_next_hypot->[$y] = 3*$y*$y + $odd; } elsif ($points eq 'hex') { my $x = $y_next_x->[$y] = (($y % 3) == 1 ? $y : $y-2); $x += 2; $y_next_hypot->[$y] = $x*$x + 3*$y*$y; ### assert: (($x+$y*3) % 6 == 0 || ($x+$y*3) % 6 == 2) } elsif ($points eq 'hex_rotated') { my $x = $y_next_x->[$y] = (($y % 3) == 2 ? $y : $y-2); $x += 2; $y_next_hypot->[$y] = $x*$x + 3*$y*$y; ### assert: (($x+$y*3) % 6 == 4 || ($x+$y*3) % 6 == 0) } elsif ($points eq 'hex_centred') { my $x = $y_next_x->[$y] = 3*$y; $x += 2; $y_next_hypot->[$y] = $x*$x + 3*$y*$y; ### assert: (($x+$y*3) % 6 == 2 || ($x+$y*3) % 6 == 4) } else { ### assert: $points eq 'all' $y_next_x->[$y] = - $self->{'x_inc'}; # X=0, so X-1=0 $y_next_hypot->[$y] = 3*$y*$y; } ### new y_next_x (with adjustment): $y_next_x->[$y]+$self->{'x_inc'} ### new y_next_hypot: $y_next_hypot->[$y] ### assert: ($points ne 'even' || (($y ^ ($y_next_x->[$y]+$self->{'x_inc'})) & 1) == 0) ### assert: $y_next_hypot->[$y] == (3 * $y**2 + ($y_next_x->[$y]+$self->{'x_inc'})**2) } # @x is the $y_next_x->[$y] for each of the @y smallests, and step those # selected elements next X and hypot for that new X,Y my @x = map { ### assert: (3 * $_**2 + ($y_next_x->[$_]+$self->{'x_inc'})**2) == $y_next_hypot->[$_] my $x = ($y_next_x->[$_] += $self->{'x_inc'}); ### map y _: $_ ### map inc x to: $x if (defined $self->{'skip_hex'} && ($x+2 + 3*$_) % 6 == $self->{'skip_hex'}) { ### extra inc for hex ... $y_next_x->[$_] += 2; $y_next_hypot->[$_] += 8*$x+16; # (X+4)^2-X^2 = 8X+16 } else { $y_next_hypot->[$_] += $self->{'x_inc_factor'}*$x + $self->{'x_inc_squared'}; } ### $x ### y_next_x (including adjust): $y_next_x->[$_]+$self->{'x_inc'} ### y_next_hypot[]: $y_next_hypot->[$_] ### assert: $y_next_hypot->[$_] == (3 * $_**2 + ($y_next_x->[$_]+$self->{'x_inc'})**2) ### assert: $self->{'points'} ne 'hex' || (($x+3*$_) % 6 == 0 || ($x+3*$_) % 6 == 2) ### assert: $self->{'points'} ne 'hex_rotated' || (($x+$_*3) % 6 == 4 || ($x+$_*3) % 6 == 0) ### assert: $self->{'points'} ne 'hex_centred' || (($x+$_*3) % 6 == 2 || ($x+$_*3) % 6 == 4) $x } @y; ### $hypot my $p2; if ($self->{'symmetry'} == 12) { ### base twelvth: join(' ',map{"$x[$_],$y[$_]"} 0 .. $#x) my $p1 = scalar(@y); my @base_x = @x; my @base_y = @y; unless ($y[0]) { # no mirror of x,0 shift @base_x; shift @base_y; } if ($x[-1] == 3*$y[-1]) { # no mirror of x=3*y line pop @base_x; pop @base_y; } $#x = $#y = ($p1+scalar(@base_x))*6-1; # pre-extend arrays for (my $i = $#base_x; $i >= 0; $i--) { $x[$p1] = ($base_x[$i] + 3*$base_y[$i]) / 2; $y[$p1++] = ($base_x[$i] - $base_y[$i]) / 2; } ### with mirror 30: join(' ',map{"$x[$_],$y[$_]"} 0 .. $p1-1) $p2 = 2*$p1; foreach my $i (0 .. $p1-1) { $x[$p1] = ($x[$i] - 3*$y[$i])/2; # rotate +60 $y[$p1++] = ($x[$i] + $y[$i])/2; $x[$p2] = ($x[$i] + 3*$y[$i])/-2; # rotate +120 $y[$p2++] = ($x[$i] - $y[$i])/2; } ### with rotates 60,120: join(' ',map{"$x[$_],$y[$_]"} 0 .. $p2-1) foreach my $i (0 .. $p2-1) { $x[$p2] = -$x[$i]; # rotate 180 $y[$p2++] = -$y[$i]; } ### with rotate 180: join(' ',map{"$x[$_],$y[$_]"} 0 .. $#x) } elsif ($self->{'symmetry'} == 6) { my $p1 = scalar(@x); my @base_x = @x; my @base_y = @y; unless ($y[0]) { # no mirror of x,0 shift @base_x; shift @base_y; } if ($x[-1] == $y[-1]) { # no mirror of X=Y line pop @base_x; pop @base_y; } ### base xy: join(' ',map{"$base_x[$_],$base_y[$_]"} 0 .. $#base_x) for (my $i = $#base_x; $i >= 0; $i--) { $x[$p1] = ($base_x[$i] - 3*$base_y[$i]) / -2; # mirror +60 $y[$p1++] = ($base_x[$i] + $base_y[$i]) / 2; } ### with mirror 60: join(' ',map{"$x[$_],$y[$_]"} 0 .. $p1-1) $p2 = 2*$p1; foreach my $i (0 .. $#x) { $x[$p1] = ($x[$i] + 3*$y[$i])/-2; # rotate +120 $y[$p1++] = ($x[$i] - $y[$i])/2; $x[$p2] = ($x[$i] - 3*$y[$i])/-2; # rotate +240 == -120 $y[$p2++] = ($x[$i] + $y[$i])/-2; # should be on correct grid # ### assert: (($x[$p1-1]+$y[$p1-1]*3) % 6 == 0 || ($x[$p1-1]+$y[$p1-1]*3) % 6 == 2) # ### assert: (($x[$p2-1]+$y[$p2-1]*3) % 6 == 0 || ($x[$p2-1]+$y[$p2-1]*3) % 6 == 2) } ### with rotates 120,240: join(' ',map{"$x[$_],$y[$_]"} 0 .. $p2-1) } else { ### assert: $self->{'symmetry'} == 4 ### base quarter: join(' ',map{"$x[$_],$y[$_]"} 0 .. $#x) my $p1 = $#x; push @y, reverse @y; push @x, map {-$_} reverse @x; if ($x[$p1] == 0) { splice @x, $p1, 1; # don't duplicate X=0 in mirror splice @y, $p1, 1; } if ($y[-1] == 0) { pop @y; # omit final Y=0 ready for rotate pop @x; } $p2 = scalar(@y); ### with mirror +90: join(' ',map{"$x[$_],$y[$_]"} 0 .. $p2-1) foreach my $i (0 .. $p2-1) { $x[$p2] = -$x[$i]; # rotate 180 $y[$p2++] = -$y[$i]; } ### with rotate 180: join(' ',map{"$x[$_],$y[$_]"} 0 .. $#x) } ### store: join(' ',map{"$x[$_],$y[$_]"} 0 .. $#x) ### at n: scalar(@$n_to_x) ### hypot_to_n: "h=$hypot n=".scalar(@$n_to_x) $hypot_to_n->[$hypot] = scalar(@$n_to_x); push @$n_to_x, @x; push @$n_to_y, @y; # ### hypot_to_n now: join(' ',map {defined($hypot_to_n->[$_]) && "h=$_,n=$hypot_to_n->[$_]"} 0 .. $#hypot_to_n) } sub n_to_xy { my ($self, $n) = @_; ### TriangularHypot n_to_xy(): $n $n = $n - $self->{'n_start'}; # starting $n==0, warn if $n==undef if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; # fraction part my $n_to_x = $self->{'n_to_x'}; my $n_to_y = $self->{'n_to_y'}; while ($int >= $#$n_to_x) { _extend($self); } my $x = $n_to_x->[$int]; my $y = $n_to_y->[$int]; return ($x + $n * ($n_to_x->[$int+1] - $x), $y + $n * ($n_to_y->[$int+1] - $y)); } sub xy_is_visited { my ($self, $x, $y) = @_; if (defined $self->{'skip_parity'}) { $x = round_nearest ($x); $y = round_nearest ($y); if ((($x%2) ^ ($y%2)) == $self->{'skip_parity'}) { ### XY wrong parity, no point ... return 0; } } if (defined $self->{'skip_hex'}) { $x = round_nearest ($x); $y = round_nearest ($y); if ((($x%6) + 3*($y%6)) % 6 == $self->{'skip_hex'}) { ### XY wrong hex, no point ... return 0; } } return 1; } sub xy_to_n { my ($self, $x, $y) = @_; ### TriangularHypot xy_to_n(): "$x, $y points=$self->{'points'}" $x = round_nearest ($x); $y = round_nearest ($y); ### parity xor: ($x%2) ^ ($y%2) ### hex modulo: (($x%6) + 3*($y%6)) % 6 if (defined $self->{'skip_parity'} && (($x%2) ^ ($y%2)) == $self->{'skip_parity'}) { ### XY wrong parity, no point ... return undef; } if (defined $self->{'skip_hex'} && (($x%6) + 3*($y%6)) % 6 == $self->{'skip_hex'}) { ### XY wrong hex, no point ... return undef; } my $hypot = 3*$y*$y + $x*$x; if (is_infinite($hypot)) { # avoid infinite loop extending @hypot_to_n return undef; } ### $hypot my $hypot_to_n = $self->{'hypot_to_n'}; my $n_to_x = $self->{'n_to_x'}; my $n_to_y = $self->{'n_to_y'}; while ($hypot > $#$hypot_to_n) { _extend($self); } my $n = $hypot_to_n->[$hypot]; for (;;) { if ($x == $n_to_x->[$n] && $y == $n_to_y->[$n]) { return $n + $self->{'n_start'}; } $n += 1; if ($n_to_x->[$n]**2 + 3*$n_to_y->[$n]**2 != $hypot) { ### oops, hypot_to_n no good ... return undef; } } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = abs (round_nearest ($x1)); $y1 = abs (round_nearest ($y1)); $x2 = abs (round_nearest ($x2)); $y2 = abs (round_nearest ($y2)); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # xyradius r^2 = 1/4 * $x2**2 + 3/4 * $y2**2 # (r+1/2)^2 = r^2 + r + 1/4 # circlearea = pi*(r+1/2)^2 # each hexagon area outradius 1/2 is hexarea = sqrt(27/64) my $r2 = $x2*$x2 + 3*$y2*$y2; my $n = (3.15 / sqrt(27/64) / 4) * ($r2 + sqrt($r2)) * (3 - $self->{'x_inc'}); # *2 for odd or even, *1 for all return ($self->{'n_start'}, $self->{'n_start'} + int($n)); } 1; __END__ =for stopwords Ryde Math-PlanePath hypot ie OEIS =head1 NAME Math::PlanePath::TriangularHypot -- points of triangular lattice in order of hypotenuse distance =head1 SYNOPSIS use Math::PlanePath::TriangularHypot; my $path = Math::PlanePath::TriangularHypot->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path visits X,Y points on a triangular "A2" lattice in order of their distance from the origin 0,0 and anti-clockwise around from the X axis among those of equal distance. =cut # math-image --all --output=numbers --path=TriangularHypot =pod 58 47 39 46 57 4 48 34 23 22 33 45 3 40 24 16 9 15 21 38 2 49 25 10 4 3 8 20 44 1 35 17 5 1 2 14 32 <- Y=0 50 26 11 6 7 13 31 55 -1 41 27 18 12 19 30 43 -2 51 36 28 29 37 54 -3 60 52 42 53 61 -4 ^ -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 The lattice is put on a square X,Y grid using every second point per L. Scaling X/2,Y*sqrt(3)/2 gives equilateral triangles of side length 1 making a distance from X,Y to the origin dist^2 = (X/2^2 + (Y*sqrt(3)/2)^2 = (X^2 + 3*Y^2) / 4 For example N=19 at X=2,Y=-2 is sqrt((2**2+3*-2**2)/4) = sqrt(4) from the origin. The next smallest after that is X=5,Y=1 at sqrt(7). The key part is X^2 + 3*Y^2 as the distance measure to order the points. =head2 Equal Distances Points with the same distance are taken in anti-clockwise order around from the X axis. For example N=14 at X=4,Y=0 is sqrt(4) from the origin, and so are the rotated X=2,Y=2 and X=-2,Y=2 etc in other sixth segments, for a total 6 points N=14 to N=19 all the same distance. Symmetry means there's a set of 6 or 12 points with the same distance, so the count of same-distance points is always a multiple of 6 or 12. There are 6 symmetric points when on the six radial lines X=0, X=Y or X=-Y, and on the lines Y=0, X=3*Y or X=-3*Y which are midway between them. There's 12 symmetric points for anything else, ie. anything in the twelve slices between those twelve lines. The first set of 12 equal is N=20 to N=31 all at sqrt(28). There can also be further ways for the same distance to arise, as multiple solutions to X^2+3*Y^3=d^2, but the 6-way or 12-way symmetry means there's always a multiple of 6 or 12 in total. =head2 Odd Points Option C "odd"> visits just the odd points, meaning sum X+Y odd, which is X,Y one odd the other even. =cut # math-image --path=TriangularHypot,points=odd --output=numbers --expression='i<=70?i:0' =pod points => "odd" 69 5 66 50 45 44 49 65 4 58 40 28 25 27 39 57 3 54 32 20 12 11 19 31 53 2 36 16 6 3 5 15 35 1 46 24 10 2 1 9 23 43 <- Y=0 37 17 7 4 8 18 38 -1 55 33 21 13 14 22 34 56 -2 59 41 29 26 30 42 60 -3 67 51 47 48 52 68 -4 70 -5 ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 =head2 All Points Option C "all"> visits all integer X,Y points. =cut # math-image --path=TriangularHypot,points=all --output=numbers --expression='i<=71?i:0' =pod points => "all" 64 59 49 44 48 58 63 3 69 50 39 30 25 19 24 29 38 47 68 2 51 35 20 13 8 4 7 12 18 34 46 1 65 43 31 17 9 3 1 2 6 16 28 42 62 <- Y=0 52 36 21 14 10 5 11 15 23 37 57 -1 70 53 40 32 26 22 27 33 41 56 71 -2 66 60 54 45 55 61 67 -3 ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 =head2 Hex Points Option C "hex"> visits X,Y points making a hexagonal grid, =cut # math-image --path=TriangularHypot,points=hex --output=numbers --expression='i<=61?i:0' --size=150x20 =pod points => "hex" 50----42 49----59 5 / \ / \ 51----39 27----33 48 4 / \ / \ / 43 22----15 21----32 3 \ / \ / \ 28----16 6----11 26----41 2 / \ / \ / \ 52----34 7---- 3 5----14 47 1 / \ / \ / \ / 60 23----12 1-----2 20----38 <- Y=0 \ / \ / \ / \ 53----35 8---- 4 10----19 58 -1 \ / \ / \ / 29----17 9----13 31----46 -2 / \ / \ / 44 24----18 25----37 -3 \ / \ / \ 54----40 30----36 57 -4 \ / \ / 55----45 56----61 -5 ^ -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 N=1 is at the origin X=0,Y=0, then N=2,3,4 are all at X^2+3Y^2=4 away from the origin, etc. The joining lines drawn above show the grid pattern but points are in order of distance from the origin. The points are all integer X,Y with X+3Y mod 6 == 0 or 2. This is a subset of the default "even" points in that X+Y is even but with 1 of each 3 points skipped to make the hexagonal outline. =head2 Hex Rotated Points Option C "hex_rotated"> is the same hexagonal points but rotated around so N=2 is at +60 degrees instead of on the X axis. =cut # math-image --path=TriangularHypot,points=hex_rotated --output=numbers --expression='i<=61?i:0' --size=150x20 =pod points => "hex_rotated" 60----50 42----49 5 / \ / \ 51 33----27 38----48 4 \ / \ / \ 34----22 15----21 41 3 / \ / \ / 43----28 12-----6 14----26 2 / \ / \ / \ 52 16-----7 2-----5 32----47 1 \ / \ / \ / \ 39----23 3-----1 11----20 59 <- Y=0 / \ / \ / \ / 53 17-----8 4----10 37----58 -1 \ / \ / \ / 44----29 13-----9 19----31 -2 \ / \ / \ 35----24 18----25 46 -3 / \ / \ / 54 36----30 40----57 -4 \ / \ / 61----55 45----56 -5 ^ -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 Points are still numbered from the X axis clockwise. The sets of points at equal hypotenuse distances are the same as plain "hex" but the numbering is changed by the rotation. The points visited are all integer X,Y with X+3Y mod 6 == 0 or 4. This grid can be viewed either as a +60 degree or a +180 degree rotation of the plain hex. =head2 Hex Centred Points Option C "hex_centred"> is the same hexagonal grid as hex above, but with the origin X=0,Y=0 in the centre of a hexagon, =cut # math-image --path=TriangularHypot,points=hex_centred --output=numbers --expression='i<=61?i:0' --size=150x20 =pod points => "hex_centred" 46----45 5 / \ 39----28 27----38 4 / \ / \ 47----29 16----15 26----44 3 / \ / \ / \ 48 17-----9 8----14 43 2 \ / \ / \ / 30----18 3-----2 13----25 1 / \ / \ / \ 40 10-----4 . 1-----7 37 <- Y=0 \ / \ / \ / 31----19 5-----6 24----36 -1 / \ / \ / \ 49 20----11 12----23 54 -2 \ / \ / \ / 50----32 21----22 35----53 -3 \ / \ / 41----33 34----42 -4 \ / 51----52 -5 ^ -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 N=1,2,3,4,5,6 are all at X^2+3Y^2=4 away from the origin, then N=7,8,9,10,11,12, etc. The points visited are all integer X,Y with X+3Y mod 6 == 2 or 4. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::TriangularHypot-Enew ()> =item C<$path = Math::PlanePath::TriangularHypot-Enew (points =E $str)> Create and return a new hypot path object. The C option can be "even" only points with X+Y even (the default) "odd" only points with X+Y odd "all" all integer X,Y "hex" hexagonal X+3Y==0,2 mod 6 "hex_rotated" hexagonal X+3Y==0,4 mod 6 "hex_centred" hexagonal X+3Y==2,4 mod 6 Create and return a new triangular hypot path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n E 1> the return is an empty list as the first point at X=0,Y=0 is N=1. Currently it's unspecified what happens if C<$n> is not an integer. Successive points are a fair way apart, so it may not make much sense to say give an X,Y position in between the integer C<$n>. =item C<$n = $path-Exy_to_n ($x,$y)> Return an integer point number for coordinates C<$x,$y>. Each integer N is considered the centre of a unit square and an C<$x,$y> within that square returns N. For "even" and "odd" options only every second square in the plane has an N and if C<$x,$y> is a position not covered then the return is C. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include, =over L (etc) =back points="even" (the default) A003136 norms (X^2+3*Y^2)/4 which occur A004016 count of points of norm==n A035019 skipping zero counts A088534 counting only in the twelfth 0<=X<=Y The counts in these sequences are expressed as norm = x^2+x*y+y^2. That x,y is related to the "even" X,Y on the path here by a -45 degree rotation, x = (Y-X)/2 X = 2*(x+y) y = (X+Y)/2 Y = 2*(y-x) norm = x^2+x*y+y^2 = ((Y-X)/2)^2 + (Y-X)/2 * (X+Y)/2 + ((X+Y)/2)^2 = (X^2 + 3*Y^2) / 4 The X^2+3*Y^2 is the dist^2 described above for equilateral triangles of unit side. The factor of /4 scales the distance but of course doesn't change the sets of points of the same distance. points="all" A092572 norms X^2+3*Y^2 which occur A158937 norms X^2+3*Y^2 which occur, X>0,Y>0 with repeats A092573 count of points norm==n for X>0,Y>0 A092574 norms X^2+3*Y^2 which occur for X>0,Y>0, gcd(X,Y)=1 A092575 count of points norm==n for X>0,Y>0, gcd(X,Y)=1 ie. X,Y no common factor =cut # ((Y-X)/2)^2 + (Y-X)/2 * (X+Y)/2 + ((X+Y)/2)^2 # = YY-2XY+XX + YY-XX + XX+2XY+YY / 4 # = 3YY + XX =pod points="hex" A113062 count of points norm=X^2+3*Y^2=4*n (theta series) A113063 divided by 3 points="hex_centred" A217219 count of points norm=X^2+3*Y^2=4*n (theta series) =head1 SEE ALSO L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/CornerAlternating.pm0000644000175000017500000003704413775022362021217 0ustar gggg# Copyright 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::CornerAlternating; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; *_sqrtint = \&Math::PlanePath::_sqrtint; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines # use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; use Math::PlanePath::SquareSpiral; *parameter_info_array = \&Math::PlanePath::SquareSpiral::parameter_info_array; use constant dx_maximum => 1; use constant dy_minimum => -1; # | 4---5---6 first South at 6 completing all NSEW # | | | # 1 | 3---2 first right turn at 3 # | | # Y=0 | 0---1 first left turn at 1 # +----------- sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; return $self->{'n_start'} + $self->{'wider'} + 1; } sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; return $self->{'n_start'} + 2*$self->{'wider'} + 3; } sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->{'n_start'} + 3*$self->{'wider'} + 6; } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } $self->{'wider'} ||= 0; # default return $self; } sub n_to_xy { my ($self, $n) = @_; ### Corner n_to_xy: $n # adjust to N=0 at origin X=0,Y=0 $n = $n - $self->{'n_start'}; if ($n < 0) { return; } my $wider = $self->{'wider'}; my $int = int($n); $n -= $int; # frac part # wider==0 n_start=0 # row start N=0, 1, 4, 9, 16, 25 # N = Y^2 # Y = floor sqrt(N) # # wider==2 n_start=0 # N=0, 3, 8, 15, 24 # N = Y^2 + 2*Y # Y = floor (-w + sqrt(w^2 + 4*N))/2 # gnomon number d, # starting d=0 for point N=0 at the origin (and more when wider), # with point immediately before each gnomon included in the following one # my $d = int ((_sqrtint(4*($int+1) + $wider*$wider) - $wider) / 2); ### d frac: (sqrt(int(4*($int+1)) + $wider*$wider) - $wider) / 2 ### $d # $r ranges -1 upwards, with -1 being the point immediately before gnomon $d my $r = $int - $d*($d+$wider); ### subtract start: $d*($d+$wider) ### $r if ($d % 2) { if ($r < 0) { ### X axis rightward ... return ($d+$wider+$n-1, 0); } elsif ($r < $d) { ### right upward ... return ($d+$wider, $r+$n); } else { ### top leftward ... return ($d+$wider-($r-$d)-$n, $d); } } else { if ($r < 0) { ### Y axis upward ... return (0, $d-1+$n); } elsif ($r < $d + $wider) { ### top rightward ... return ($r+$n, $d); } else { ### right downward ... return ($d+$wider, $d-($r-$d-$wider) - $n); } } } sub xy_to_n { my ($self, $x, $y) = @_; ### Corner xy_to_n(): "$x,$y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } my $wider = $self->{'wider'}; my $xw = $x - $wider; if ($y >= $xw) { ### top edge ... return ($y*($y+$wider) + ($y % 2 ? 2*$y+$wider - $x : $x) + $self->{'n_start'}); } else { ### right vertical ... return ($x*$xw + ($xw % 2 ? $y : $x+$xw - $y) + $self->{'n_start'}); } } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### Corner rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } if ($y2 < 0 || $x2 < 0) { return (1, 0); # rect all negative, no N } if ($x1 < 0) { $x1 *= 0; } # "*=" to preserve bigint x1 or y1 if ($y1 < 0) { $y1 *= 0; } my $wider = $self->{'wider'}; my $xmin = $x1; my $ymin = $y1; my $t = $wider + $y1; # x where diagonal goes through row y1 if ($x1 <= $t) { ### for min, x1,y1 at or before diagonal ... # | +-------+ / y2 # | | |/ | # | | / | / # | | /| | +------+ / y2 # | | / | | | | / # | @----@--+ y1 | @------@ / y1 # | x1 / x2 | x1 x2 / # +------------------ +-------------------- # ..wider if ($y1 % 2) { ### leftward row y1, min at smaller of x2 or diagonal ... $xmin = ($x2 < $t ? $x2 : $t); } } else { ### for min, x1,y1 after diagonal ... # / # | +------+ y2 | # | | / | | / # | |/ | | / # | @ | | / @------+ y2 # | /| | | / | | # | / @------+ y1 | / @------+ y1 # | / x1 x2 | / x1 x2 # +------------------ +------------------ # ^...^xw # wider $t = $x1 - $wider; unless ($t % 2) { ### column x1 even, downward ... $ymin = ($y2 < $t ? $y2 : $t); } } #----- my $xmax = $x2; my $ymax = $y2; # | / # | @------/ y2 x2,y2 on the diagonal # | | /| executes both "on or before" # | | / | and "on or after" # | | / | selecting one or other of # | | / | the opposite points # | +-/----@ y1 according as direction of # | x1/ x2 the gnomon # +--------------- $t = $x2 - $wider; # y where diagonal passes column x2 if ($y2 >= $t) { ### for max, x2,y2 on or before diagonal ... # max is x1 in an odd row (leftward) # # | / # | @------@ /y2 # | | |/ # | | / # | | /| # | | / | # | +---/--+ y1 # | x1 / x2 # +---------------- if ($y2 % 2) { ### top row odd, max at leftward x1 ... $xmax = $x1; } } if ($y2 <= $t) { ### for max, x2,y2 on or after of diagonal ... # max is y1 in a downward column ... # # | / # | +--/---@ y2 # | | / | # | |/ | # | / | # | /| | # | / +------@ y1 # | / x2 # +----------------- # ^ # wider # unless ($t % 2) { ### x2 column even, downward ... $ymax = $y1; } } ### min xy: "$xmin,$ymin" ### max xy: "$xmax,$ymax" return ($self->xy_to_n ($xmin,$ymin), $self->xy_to_n ($xmax,$ymax)); } #------------------------------------------------------------------------------ sub _NOTDOCUMENTED_n_to_figure_boundary { my ($self, $n) = @_; ### _NOTDOCUMENTED_n_to_figure_boundary(): $n # adjust to N=1 at origin X=0,Y=0 $n = $n - $self->{'n_start'} + 1; if ($n < 1) { return undef; } my $wider = $self->{'wider'}; if ($n <= $wider) { # single block row, nothing special at diagonal # +---+-----+----+ # | 1 | ... | $n | boundary = 2*N + 2 # +---+-----+----+ return 2*$n + 2; } my $d = int((_sqrtint(4*$n + $wider*$wider - 2) - $wider) / 2); ### $d ### $wider if ($n > $d*($d+1+$wider) + ($d%2 ? 0 : $wider)) { $wider++; ### increment for +2 after turn on diagonal ... } return 4*$d + 2*$wider + 2; } #------------------------------------------------------------------------------ 1; __END__ # cf A219159 going alternating two rows, the flip # A213928 going alternating three rows, the flip # corners alternating "shell" # # A319514 interleaved x,y # x=OEIS_bfile_func("A319289"); # y=OEIS_bfile_func("A319290"); # plothraw(vector(3^3,n,n--; x(n)), \ # vector(3^3,n,n--; y(n)), 1+8+16+32) =for stopwords pronic PlanePath Ryde Math-PlanePath ie OEIS gnomon Nstart =head1 NAME Math::PlanePath::CornerAlternating -- points shaped around a corner alternately =head1 SYNOPSIS use Math::PlanePath::CornerAlternating; my $path = Math::PlanePath::CornerAlternating->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path is points in layers around a square outwards from a corner in the first quadrant, alternately upward or downward. XEach row/column "gnomon" added to a square makes a one-bigger square. =cut # math-image --path=CornerAlternating --output=numbers_dash --all --size=30x14 =pod 4 | 17--18--19--20--21 ... | | | | 3 | 16--15--14--13 22 29 | | | | 2 | 5---6---7 12 23 28 | | | | | | 1 | 4---3 8 11 24 27 | | | | | | Y=0 | 1---2 9--10 25--26 +------------------------- X=0 1 2 3 4 5 This is like the Corner path, but here gnomons go back and forward and in particular so points are always a unit step apart. =head2 Wider An optional C $integer> makes the path wider horizontally, becoming a rectangle. For example =cut # math-image --path=CornerAlternating,wider=3 --all --output=numbers_dash --size=38x12 =pod 4 | 29--30--31--32--33--34--35--36 ... | | | | 3 | 28--27--26--25--24--23--22 37 44 wider => 3 | | | | 2 | 11--12--13--14--15--16 21 38 43 | | | | | | 1 | 10---9---8---7---6 17 20 39 42 | | | | | | Y=0 | 1---2---3---4---5 18--19 40--41 +-------------------------------------- X=0 1 2 3 4 5 6 7 8 Each gnomon has the horizontal part C many steps longer. For wider=3 shown, the additional points are 2,3,4 in the first row, then 5..10 are the next gnomon. Each gnomon is still 2 longer than the previous since this widening is a constant amount in each. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start with the same shape etc. For example to start at 0, =cut # math-image --path=CornerAlternating,n_start=0 --all --output=numbers --size=50x11 =pod 4 | 16 17 18 19 20 3 | 15 14 13 12 21 n_start => 0 2 | 4 5 6 11 22 1 | 3 2 7 10 23 Y=0 | 0 1 8 9 24 --------------------- X=0 1 2 3 4 With Nstart=0, the pronic numbers are on the X=Y leading diagonal. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::CornerAlternating-Enew ()> =item C<$path = Math::PlanePath::CornerAlternating-Enew (wider =E $w, n_start =E $n)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n < n_start()> the return is an empty list. Fractional C<$n> gives an X,Y position along a straight line between the integer positions. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each point as a square of side 1, so the quadrant x>=-0.5 and y>=-0.5 is entirely covered. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head1 FORMULAS Most calculations are similar to the Corner path (without the 0.5 fractional part), and a reversal applied when the d gnomon number is odd. When wider>0, that reversal must allow for the horizontals and verticals different lengths. =head2 Rectangle N Range For C, the largest gnomon is either the top or right of the rectangle, depending where the top right corner x2,y2 falls relative to the leading diagonal, | A---B / x2y2 | | |/ top | +------B right | | | row | | / | side | | /| biggest | | / | biggest | +---+ gnomon | +------C gnomon | / | / +--------- +----------- Then the maximum is at A or B, or B or C according as which way that gnomon goes, so odd or even. If it happens that B is on the diagonal, so x2=y2, then it's either A or C according as the gnomon odd or even | / | A----+ x2=y2 | | /| | | / | | +----C | / +----------- For wider E 0, the diagonal shifts across so that x2-wider E=E y2 is the relevant test. =head1 OEIS This path is in Sloane's Online Encyclopedia of Integer Sequences as, =over L (etc) =back wider=0, n_start=1 (the defaults) A220603 X+1 coordinate A220604 Y+1 coordinate A213088 X+Y sum A081346 N on X axis A081345 N on Y axis A002061 N on X=Y diagonal, extra initial 1 A081344 permutation N by diagonals A194280 inverse A020703 permutation N at transpose Y,X A027709 boundary length of N unit squares A078633 grid sticks of N points n_start=0 A319290 X coordinate A319289 Y coordinate A319514 Y,X coordinate pairs A329116 X-Y diff A053615 abs(X-Y) diff A000196 max(X,Y), being floor(sqrt(N)) A339265 dX-dY increments (runs +1,-1) A002378 N on X=Y diagonal, pronic numbers A220516 permutation N by diagonals n_start=2 A014206 N on X=Y diagonal, pronic+2 wider=1, n_start=1 A081347 N on X axis A081348 N on Y axis A080335 N on X=Y diagonal A093650 permutation N by diagonals wider=1, n_start=0 A180714 X-Y diff wider=2, n_start=1 A081350 N on X axis A081351 N on Y axis A081352 N on X=Y diagonal A081349 permutation N by diagonals =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/BetaOmega.pm0000644000175000017500000006224113734026674017424 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=BetaOmega --lines --scale=20 # # math-image --path=BetaOmega --all --output=numbers_dash # http://www.upb.de/pc2/papers/files/pdfps399main.toappear.ps # gone # http://www.uni-paderborn.de/pc2/papers/files/pdfps399main.toappear.ps # http://wwwcs.upb.de/pc2/papers/files/399.ps # gone # # copy ? # http://www.cs.uleth.ca/~wismath/cccg/papers/27l.ps package Math::PlanePath::BetaOmega; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'bit_split_lowtohigh', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant y_negative_at_n => 4; *xy_is_visited = \&Math::PlanePath::Base::Generic::_xy_is_visited_x_positive; use constant 1.02 _UNDOCUMENTED__dxdy_list_at_n => 4; #------------------------------------------------------------------------------ # tables generated by tools/beta-omega-table.pl # my @next_state = (28, 8,36,88, 8,28,32,76, 4,16,44,64, 16, 4,40,84, 12,24,52,72, 24,12,48,92, 20, 0,60,80, 0,20,56,68, 68, 4,40,60, 64, 0,60,40, 76,12,48,36, 72, 8,36,48, 84,20,56,44, 80,16,44,56, 92,28,32,52, 88,24,52,32, 28, 8,36,48, 8,28,32,52, 4,16,44,56, 16, 4,40,60, 12,24,52,32, 24,12,48,36, 20, 0,60,40, 0,20,56,44); my @digit_to_x = (0,0,1,1, 0,1,1,0, 1,0,0,1, 1,1,0,0, 1,1,0,0, 1,0,0,1, 0,1,1,0, 0,0,1,1, 1,1,0,0, 0,1,1,0, 1,0,0,1, 0,0,1,1, 0,0,1,1, 1,0,0,1, 0,1,1,0, 1,1,0,0, 0,0,1,1, 0,1,1,0, 1,0,0,1, 1,1,0,0, 1,1,0,0, 1,0,0,1, 0,1,1,0, 0,0,1,1); my @digit_to_y = (0,1,1,0, 0,0,1,1, 0,0,1,1, 0,1,1,0, 1,0,0,1, 1,1,0,0, 1,1,0,0, 1,0,0,1, 0,1,1,0, 1,1,0,0, 1,1,0,0, 0,1,1,0, 1,0,0,1, 0,0,1,1, 0,0,1,1, 1,0,0,1, 0,1,1,0, 0,0,1,1, 0,0,1,1, 0,1,1,0, 1,0,0,1, 1,1,0,0, 1,1,0,0, 1,0,0,1); my @xy_to_digit = (0,1,3,2, 0,3,1,2, 1,2,0,3, 3,2,0,1, 2,3,1,0, 2,1,3,0, 3,0,2,1, 1,0,2,3, 3,2,0,1, 3,0,2,1, 2,1,3,0, 0,1,3,2, 1,0,2,3, 1,2,0,3, 0,3,1,2, 2,3,1,0, 0,1,3,2, 0,3,1,2, 1,2,0,3, 3,2,0,1, 2,3,1,0, 2,1,3,0, 3,0,2,1, 1,0,2,3); my @min_digit = (0,0,3,0, 0,2,1,1, 2,undef,undef,undef, 0,0,1,0, 0,1,3,2, 2,undef,undef,undef, 1,0,0,1, 0,0,2,2, 3,undef,undef,undef, 3,0,0,2, 0,0,2,1, 1,undef,undef,undef, 2,1,1,2, 0,0,3,0, 0,undef,undef,undef, 2,2,3,1, 0,0,1,0, 0,undef,undef,undef, 3,2,2,0, 0,1,0,0, 1,undef,undef,undef, 1,1,2,0, 0,2,0,0, 3,undef,undef,undef, 3,0,0,2, 0,0,2,1, 1,undef,undef,undef, 3,2,2,0, 0,1,0,0, 1,undef,undef,undef, 2,2,3,1, 0,0,1,0, 0,undef,undef,undef, 0,0,3,0, 0,2,1,1, 2,undef,undef,undef, 1,1,2,0, 0,2,0,0, 3,undef,undef,undef, 1,0,0,1, 0,0,2,2, 3,undef,undef,undef, 0,0,1,0, 0,1,3,2, 2,undef,undef,undef, 2,1,1,2, 0,0,3,0, 0,undef,undef,undef, 0,0,3,0, 0,2,1,1, 2,undef,undef,undef, 0,0,1,0, 0,1,3,2, 2,undef,undef,undef, 1,0,0,1, 0,0,2,2, 3,undef,undef,undef, 3,0,0,2, 0,0,2,1, 1,undef,undef,undef, 2,1,1,2, 0,0,3,0, 0,undef,undef,undef, 2,2,3,1, 0,0,1,0, 0,undef,undef,undef, 3,2,2,0, 0,1,0,0, 1,undef,undef,undef, 1,1,2,0, 0,2,0,0, 3); my @max_digit = (0,3,3,1, 3,3,1,2, 2,undef,undef,undef, 0,1,1,3, 3,2,3,3, 2,undef,undef,undef, 1,1,0,2, 3,3,2,3, 3,undef,undef,undef, 3,3,0,3, 3,1,2,2, 1,undef,undef,undef, 2,2,1,3, 3,1,3,3, 0,undef,undef,undef, 2,3,3,2, 3,3,1,1, 0,undef,undef,undef, 3,3,2,3, 3,2,0,1, 1,undef,undef,undef, 1,2,2,1, 3,3,0,3, 3,undef,undef,undef, 3,3,0,3, 3,1,2,2, 1,undef,undef,undef, 3,3,2,3, 3,2,0,1, 1,undef,undef,undef, 2,3,3,2, 3,3,1,1, 0,undef,undef,undef, 0,3,3,1, 3,3,1,2, 2,undef,undef,undef, 1,2,2,1, 3,3,0,3, 3,undef,undef,undef, 1,1,0,2, 3,3,2,3, 3,undef,undef,undef, 0,1,1,3, 3,2,3,3, 2,undef,undef,undef, 2,2,1,3, 3,1,3,3, 0,undef,undef,undef, 0,3,3,1, 3,3,1,2, 2,undef,undef,undef, 0,1,1,3, 3,2,3,3, 2,undef,undef,undef, 1,1,0,2, 3,3,2,3, 3,undef,undef,undef, 3,3,0,3, 3,1,2,2, 1,undef,undef,undef, 2,2,1,3, 3,1,3,3, 0,undef,undef,undef, 2,3,3,2, 3,3,1,1, 0,undef,undef,undef, 3,3,2,3, 3,2,0,1, 1,undef,undef,undef, 1,2,2,1, 3,3,0,3, 3); sub n_to_xy { my ($self, $n) = @_; ### BetaOmega n_to_xy(): $n ### hex: sprintf "%#X", $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; # remaining fraction, preserve possible BigFloat/BigRat my $zero = $int * 0; # inherit bignum my @ndigits = digit_split_lowtohigh($int,4); ### ndigits: join(', ',@ndigits)." count ".scalar(@ndigits) my $state = ($#ndigits & 1 ? 28 : 0); my $dirstate = ($#ndigits & 1 ? 0 : 28); # default if all $ndigit==3 my @xbits; my @ybits; foreach my $i (reverse 0 .. $#ndigits) { my $ndigit = $ndigits[$i]; # high to low $state += $ndigit; if ($ndigit != 3) { $dirstate = $state; # lowest non-3 digit } ### $ndigit ### $state ### $dirstate ### digit_to_x: $digit_to_x[$state] ### digit_to_y: $digit_to_y[$state] ### next_state: $next_state[$state] $xbits[$i] = $digit_to_x[$state]; $ybits[$i] = $digit_to_y[$state]; $state = $next_state[$state]; } ### $dirstate ### frac: $n ### Ymin: - (((4+$zero)**int($#ndigits/2) - 1) * 2 / 3) # with $n fractional part return ($n * ($digit_to_x[$dirstate+1] - $digit_to_x[$dirstate]) + digit_join_lowtohigh(\@xbits, 2, $zero), $n * ($digit_to_y[$dirstate+1] - $digit_to_y[$dirstate]) + (digit_join_lowtohigh(\@ybits, 2, $zero) # Ymin = - (4^floor(level/2) - 1) * 2 / 3 - (((4+$zero)**int(scalar(@ndigits)/2) - 1) * 2 / 3))); } # ($len,$level) rounded down for $y ... sub _y_round_down_len_level { my ($y) = @_; my $pos; if ($pos = ($y >= 0)) { # eg. 1 becomes 3, or 5 becomes 15, 2^k-1 $y = 3 * $y; } else { # eg. -2 becomes 7, or -10 becomes 31, 2^k-1 $y = 1 - 3*$y; } my ($len, $level) = round_down_pow($y,2); # Make positive y give even level, and negative y give odd level. # If positive and odd then reduce, or if negative and even then reduce. if (($level & 1) == $pos) { $level--; $len /= 2; } return ($len, $level); } sub xy_to_n { my ($self, $x, $y) = @_; ### BetaOmega xy_to_n(): "$x, $y" $x = round_nearest ($x); if ($x < 0) { return undef; } if (is_infinite($x)) { return $x; } my @xbits = bit_split_lowtohigh($x); $y = round_nearest ($y); my $zero = ($x * 0 * $y); my ($len, $level) = _y_round_down_len_level ($y); ### y: "len=$len level=$level" if ($#xbits > $level) { ### increase level to xbits ... $level = $#xbits; $len = (2+$zero) ** $level; } ### $len ### $level $y += (($level&1 ? 4 : 2) * $len - 2) / 3; ### offset y to: $y if (is_infinite($y)) { return $y; } my @ybits = bit_split_lowtohigh($y); my $state = ($level & 1 ? 28 : 0); my @ndigits; foreach my $i (reverse 0 .. $level) { # high to low ### at: "i=$i state=$state xbit=".($xbits[$i]||0)." ybit=".($ybits[$i]||0) my $ndigit = $xy_to_digit[$state + 2*($xbits[$i]||0) + ($ybits[$i]||0)]; $ndigits[$i] = $ndigit; $state = $next_state[$state+$ndigit]; } return digit_join_lowtohigh(\@ndigits, 4, $zero); } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### BetaOmega rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; if ($x2 < 0) { return (1, 0); } $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); ($y1,$y2) = ($y2,$y1) if $y1 > $y2; my ($len, $level) = round_down_pow ($x2, 2); ### x len/level: "$len $level" # If y1/y2 both positive or both negative then only look at the bigger of # the two. If y1 negative and y2 positive then consider both. foreach my $y (($y2 > 0 ? ($y2) : ()), ($y1 < 0 ? ($y1) : ())) { my ($ylen, $ylevel) = _y_round_down_len_level ($y); ### y len/level: "$ylen $ylevel" if ($ylevel > $level) { $level = $ylevel; $len = $ylen; } } if (is_infinite($len)) { return (0, $len); } my $n_min = my $n_max = 0; my $y_min = my $y_max = - (4**int(($level+1)/2) - 1) * 2 / 3; my $x_min = my $x_max = 0; my $min_state = my $max_state = ($level & 1 ? 28 : 0); ### $x_min ### $y_min while ($level >= 0) { ### $level ### $len { my $x_cmp = $x_min + $len; my $y_cmp = $y_min + $len; my $digit = $min_digit[3*$min_state + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0)]; # my $xr = ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0); # my $yr = ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0); # my $key = 3*$min_state + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0); # ### min at: "min_state=$min_state $x_min,$y_min cmp $x_cmp,$y_cmp" # ### min_state: state_string($min_state) # ### $xr # ### $yr # ### $key # ### min digit: $digit # ### min key: $key # ### y offset: $digit_to_y[$max_state+$digit] $n_min = 4*$n_min + $digit; $min_state += $digit; if ($digit_to_x[$min_state]) { $x_min += $len; } $y_min += $len * $digit_to_y[$min_state]; $min_state = $next_state[$min_state]; } { my $x_cmp = $x_max + $len; my $y_cmp = $y_max + $len; my $digit = $max_digit[3*$max_state + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0)]; # my $xr = ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0); # my $yr = ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0); # my $key = 3*$min_state + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0); # ### max at: "max_state=$max_state $x_max,$y_max cmp $x_cmp,$y_cmp" # ### $x_cmp # ### $y_cmp # ### $xr # ### $yr # ### $key # ### max digit: $digit # ### x offset: $digit_to_x[$max_state+$digit] # ### y offset: $digit_to_y[$max_state+$digit] # ### y digit offset: $digit_to_y[$max_state+$digit] # ### y min shift part: - ($level&1) $n_max = 4*$n_max + $digit; $max_state += $digit; if ($digit_to_x[$max_state]) { $x_max += $len; } $y_max += $len * $digit_to_y[$max_state]; $max_state = $next_state[$max_state]; } $len = int($len/2); $level--; } return ($n_min, $n_max); } #------------------------------------------------------------------------------ # levels use Math::PlanePath::HilbertCurve; *level_to_n_range = \&Math::PlanePath::HilbertCurve::level_to_n_range; *n_to_level = \&Math::PlanePath::HilbertCurve::n_to_level; #------------------------------------------------------------------------------ 1; __END__ # | # 5 25--26 29--30 33--34 37--38 249-250 255-254 233-232-231-230 # | | | | | | | | | | | | | # 4 24 27--28 31--32 35--36 39 248 251-252-253 234-235 228-229 # | | | | | # 3 23 20--19--18 45--44--43 40 247 244-243 240-239 236 227-226 # | | | | | | | | | | | | | # 2 22--21 16--17 46--47 42--41 246-245 242-241 238-237 224-225 # | | | # 1 1-- 2 15--14 49--48 53--54 201-202 205-206 209-210 223-222 # | | | | | | | | | | | | | # Y=0-> 0 3 12--13 50--51--52 55 200 203-204 207-208 211 220-221 # | | | | | | # -1 5-- 4 11--10 61--60--59 56 199 196-195-194 213-212 219-218 # | | | | | | | | | | # -2 6-- 7-- 8-- 9 62--63 58--57 198-197 192-193 214-215-216-217 # | | # -3 89--88--87--86 65--64 69--70 185-186 191-190 169-168-167-166 # | | | | | | | | | | # -4 90--91 84--85 66--67--68 71 184 187-188-189 170-171 164-165 # | | | | | | # -5 93--92 83 80--79 76--75 72 183 180-179 176-175 172 163-162 # | | | | | | | | | | | | | | # -6 94--95 82--81 78--77 74--73 182-181 178-177 174-173 160-161 # | | # -7 97--96 109-110 113-114 125-126 129-130 141-142 145-146 159-158 # | | | | | | | | | | | | | | # -8 98--99 108 111-112 115 124 127-128 131 140 143-144 147 156-157 # | | | | | | | | # -9 101-100 107-106 117-116 123-122 133-132 139-138 149-148 155-154 # | | | | | | | | # -10 102-103-104-105 118-119-120-121 134-135-136-137 150-151-152-153 # # ^ # X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 =for stopwords eg Ryde OEIS ie bignums prepending Math-PlanePath Jens-Michael Wierum Ymin Ymax Wierum's Paderborn CCCG'02 MERCHANTABILITY 14th ybit =head1 NAME Math::PlanePath::BetaOmega -- 2x2 half-plane traversal =head1 SYNOPSIS use Math::PlanePath::BetaOmega; my $path = Math::PlanePath::BetaOmega->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis is an integer version of the Beta-Omega curve =over Jens-Michael Wierum, "Definition of a New Circular Space-Filling Curve: Beta-Omega-Indexing", Technical Report TR-001-02, Paderborn Centre for Parallel Computing, March 2002. =back The curve form here makes a 2x2 self-similar traversal of the half plane XE=0. 5 25--26 29--30 33--34 37--38 | | | | | | | | 4 24 27--28 31--32 35--36 39 | | 3 23 20--19--18 45--44--43 40 | | | | | | 2 22--21 16--17 46--47 42--41 | | 1 1-- 2 15--14 49--48 53--54 | | | | | | Y=0-> 0 3 12--13 50--51--52 55 | | | -1 5-- 4 11--10 61--60--59 56 | | | | | -2 6-- 7-- 8-- 9 62--63 58--57 | -3 ... X=0 1 2 3 4 5 6 7 Each level extends square parts 2^level x 2^level alternately up or down. The initial N=0 to N=3 extends upwards from Y=0 and exits the block downwards at N=3. N=4 extends downwards and goes around back upwards to exit N=15. N=16 then extends upwards through to N=63 which exits downwards, etc. The curve is named for the two base shapes Beta Omega *---* *---* | | | | --* * --* *-- | The beta is made from three betas and an omega sub-parts. The omega is made from four betas. In each case the sub-parts are suitably rotated, transposed or reversed, so expanding to Beta = 3*Beta+Omega Omega = 4*Beta *---*---*---* *---*---*---* | | | | *---* *---* *---* *---* | | | | --* * *---* --* * * *-- | | | | | | | *---* *---* *---* *---* | The sub-parts represent successive ever-smaller substitutions. They have the effect of making the start a beta going alternately up or down. For this integer version the start direction is kept fixed as a beta going upwards and the higher levels then alternate up and down from there. =head2 Level Ranges Reckoning the initial N=0 to N=3 as level 1, a replication level extends to Nlevel = 4^level - 1 Xmin = 0 Xmax = 2^level - 1 Ymin = - (4^floor(level/2) - 1) * 2 / 3 = binary 1010...10 Ymax = (4^ceil(level/2) - 1) / 3 = binary 10101...01 height = Ymax - Ymin = 2^level - 1 The Y range increases alternately above and below by a power of 2, so the result for Ymin and Ymax is a 1 bit going alternately to Ymax and Ymin, starting with Ymax for level 1. level Ymin binary Ymax binary ----- -------------- ------------- 0 0 0 1 0 0 1 = 1 2 -2 = -10 1 = 01 3 -2 = -010 5 = 101 4 -10 = -1010 5 = 0101 5 -10 = -01010 21 = 10101 6 -42 = -101010 21 = 010101 7 -42 = -0101010 85 = 1010101 The power of 4 divided by 3 formulas above for Ymin/Ymax have the effect of producing alternating bit patterns like this. For odd levels -Ymin/height approaches 1/3 and Ymax/height approaches 2/3, ie. the start point is about 1/3 up the total extent. For even levels it's the other way around, with -Ymin/height approaching 2/3 and Ymax/height approaching 1/3. =head2 Closed Curve Wierum's idea for the curve is a closed square made from four betas, *---* *---* | | | | * *-- --* * | | | | * *-- --* * | | | | *---* *---* And at the next expansion level *---*---*---* *---*---*---* | | | | *---* *---* *---* *---* | | | | *---* * *-- --* * *---* | | | | | | *---* *---* *---* *---* | | | | *---* *---* *---* *---* | | | | | | *---* * *-- --* * *---* | | | | *---* *---* *---* *---* | | | | *---*---*---* *---*---*---* The code here could be used for that by choosing a level and applying four copies of the path suitably mirrored and offset in X and Y. For an odd level, the path N=0 to N=4^level-1 here is the top-right quarter, entering on the left and exiting downwards. For an even level it's the bottom-right shape instead, exiting upwards. The difference arises because when taking successively greater detail sub-parts the initial direction alternates up or down, but in the code here it's kept fixed (as noted above). The start point here is also fixed at Y=0, so an offset Ymin must be applied if say the centre of the sections is to be Y=0 instead of the side entry point. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::BetaOmega-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 4**$level - 1)>. =back =head1 FORMULAS =head2 N to X,Y Each 2 bits of N become a bit each for X and Y in a "U" arrangement, but which way around is determined by sub-part orientation and beta/omega type per above, beta rotation 4 of transpose 2 of reverse 2 of omega rotation 4 of transpose 2 of ---- total states 24 = 4*2*2 + 4*2 The omega pattern is symmetric so its reverse is the same, hence only rotate and transpose forms for it. Omitting omega reverse reduces the states from 32 to 24, saving a little space in a table driven approach. But if using separate variables for rotate, transpose and reverse then the reverse can be kept for both beta and omega without worrying that it makes no difference in the omega. Adding bits to Y produces a positive value measured up from Ymin(level), where level is the number of base 4 digits in N. That Ymin can be incorporated by adding -(2^level) for each even level. A table driven calculation can work that in as for example digit = N base 4 digits from high to low xbit = digit_to_x[state,digit] ybit = digit_to_y[state,digit] state = next_state[state,digit] X += 2^level * xbit Y += 2^level * (ybit - !(level&1)) The (ybit-!(level&1)) means either 0,1 or -1,0. Another possibility there would be to have -!(level&1) in the digit_to_y[] table, doubling the states so as to track the odd/even level within the state and having the digit_to_y[] as -1,0 in the even and 0,1 in the odd. =head2 N to X,Y Fraction If N includes a fractional part, it can be put on a line towards the next integer point by taking the direction as at the least significant non-3 digit. If the least significant base 4 digit is 3 then the direction along the curve is determined by the curve part above. For example at N=7 (13 base 4) it's rightwards as per the inverted beta which is the N=4 towards N=8 part of the surrounding pattern. Or likewise N=11 (23 base 4) in the N=8 to N=12 direction. | 0 12-- 5---4 | | | | | 6---7-- ... 4-----8 If all digits are 3 base 4, which is N=3, N=15, N=63, etc, then the direction is down for an odd number of digits, up for an even number. So N=3 downwards, N=15 upwards, N=63 downwards, etc. This curve direction calculation might be of interest in its own right, not merely to apply a fractional N as done in the code here. There's nothing offered for that in the C modules as such. For it the X,Y values can be ignored just follow the state or orientations changes using the base 4 digits of N. =head1 SEE ALSO L, L, L =over L (cached copy) =back Jens-Michael Wierum, "Logarithmic Path-Length in Space-Filling Curves", 14th Canadian Conference on Computational Geometry (CCCG'02), 2002. =over L, L (shorter), L (longer) =back =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/ArchimedeanChords.pm0000644000175000017500000005066113734026674021146 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Also possible would be circle involute spiral, unrolling string around # centre of circumference 1, but is only very slightly different radius from # an Archimedean spiral. package Math::PlanePath::ArchimedeanChords; use 5.004; use strict; use Math::Libm 'hypot', 'asinh'; use POSIX 'ceil'; #use List::Util 'min', 'max'; *min = \&Math::PlanePath::_min; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::MultipleRings; # uncomment this to run the ### lines # use Smart::Comments; use constant figure => 'circle'; use constant n_start => 0; use constant x_negative_at_n => 3; use constant y_negative_at_n => 5; use constant gcdxy_maximum => 1; use constant dx_minimum => -1; # infimum when straight use constant dx_maximum => 1; # at N=0 use constant dy_minimum => -1; use constant dy_maximum => 1; use constant dsumxy_minimum => -sqrt(2); # supremum when diagonal use constant dsumxy_maximum => sqrt(2); use constant ddiffxy_minimum => -sqrt(2); # supremum when diagonal use constant ddiffxy_maximum => sqrt(2); use constant turn_any_right => 0; # left always use constant turn_any_straight => 0; # left always #------------------------------------------------------------------------------ use constant 1.02 _PI => 2*atan2(1,0); # Starting at polar angle position t in radians, # # r = t / 2pi # # x = r * cos(t) = t * cos(t) / 2pi # y = r * sin(t) = t * sin(t) / 2pi # # Want a polar angle amount u to move by a chord distance of 1. Hypot # square distance from t to t+u is # # dist(u) = ( (t+u)/2pi*cos(t+u) - t/2pi*cos(t) )^2 # X # + ( (t+u)/2pi*sin(t+u) - t/2pi*sin(t) )^2 # Y # = [ (t+u)^2*cos^2(t+u) - 2*(t+u)*t*cos(t+u)*cos(t) + t^2*cos^2(t) # + (t+u)^2*sin^2(t+u) - 2*(t+u)*t*sin(t+u)*sin(t) + t^2*sin^2(t) # ] / (4*pi^2) # # and from sin^2 + cos^2 = 1 # and addition cosA*cosB + sinA*sinB = cos(A-B) # # = [ (t+u)^2 - 2*(t+u)*t*cos((t+u)-t) + t^2 ] /4pi^2 # = [ (t+u)^2 + t^2 - 2*t*(t+u)*cos(u) ] / (4*pi^2) # # then double angle cos(u) = 1 - 2*sin^2(u/2) to go to the sine since if u # is small then cos(u) near 1.0 might lose accuracy # # dist(u) = [(t+u)^2 + t^2 - 2*t*(t+u)*(1 - 2*sin^2(u/2))] / (4*pi^2) # = [(t+u)^2 + t^2 - 2*t*(t+u) + 2*t*(t+u)*2*sin^2(u/2)] / (4*pi^2) # = [((t+u) - t)^2 + 4*t*(t+u)*sin^2(u/2)] / (4*pi^2) # = [ u^2 + 4*t*(t+u)*sin^2(u/2) ] / (4*pi^2) # # Seek d(u) = 1 by letting f(u)=4*pi^2*(d(u)-1) and seeking f(u)=0 # # f(u) = u^2 + 4*t*(t+u)*sin^2(u/2) - 4*pi^2 # # Derivative f'(u) for the slope, starting from the cosine form, # # f(u) = (t+u)^2 + t^2 - 2*t*(t+u)*cos(u) - 4*pi^2 # # f'(u) = 2*(t+u) - 2*t*[ cos(u) - (t+u)*sin(u) ] # = 2*(t+u) - 2*t*[ 1 - 2*sin^2(u/2) - (t+u)*sin(u) ] # = 2*t + 2*u - 2*t + 2*t*2*sin^2(u/2) + 2*t*(t+u)*sin(u) # = 2*[ u + 2*t*sin^2(u/2) + t*(t+u)*sin(u) ] # = 2*[ u + t * [2*sin^2(u/2) + (t+u)*sin(u) ] ] # # Newton's method # */ <- f(x) high # */| # * / | # * / | # ---------*------------------ # +---+ <- subtract # # f(x) / sub = f'(x) # sub = f(x) / f'(x) # # # _chord_angle_inc() takes $t is a polar angle around the Archimedean spiral. # Returns an increment polar angle $u which may be added to $t to move around # the spiral by a chord length 1 unit. # # The loop is Newton's method, $f=f(u), $slope=f'(u) so $u-$f/$slope is a # better $u, ie. f($u) closer to 0. Stop when the subtract becomes small, # usually only about 3 iterations. # sub _chord_angle_inc { my ($t) = @_; # ### _chord_angle_inc(): $t my $u = 2*_PI/$t; # estimate foreach (0 .. 10) { my $shu = sin($u/2); $shu *= $shu; # sin^2(u/2) my $tu = ($t+$u); my $f = $u*$u + 4*$t*$tu*$shu - 4*_PI*_PI; my $slope = 2 * ( $u + $t*(2*$shu + $tu*sin($u))); # unsimplified versions ... # $f = ($t+$u)**2 + $t**2 - 2*$t*($t+$u)*cos($u) - 4*_PI*_PI; # $slope = 2*($t+$u) - 2*$t*( cos($u) - ($t+$u)*sin($u) ); my $sub = $f/$slope; $u -= $sub; # printf ("f=%.6f slope=%.6f sub=%.20f u=%.6f\n", $f, $slope, $sub, $u); last if (abs($sub) < 1e-15); } # printf ("return u=%.6f\n", $u); return $u; } use constant 1.02; # for leading underscore use constant _SAVE => 500; my @save_n = (1); my @save_t = (2*_PI); my $next_save = $save_n[0] + _SAVE; sub new { ### ArchimedeanChords new() ... return shift->SUPER::new (i => $save_n[0], t => $save_t[0], @_); } sub n_to_xy { my ($self, $n) = @_; if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } if ($n <= 1) { return ($n, 0); # exactly Y=0 } { # ENHANCE-ME: look at the N+1 position for the frac directly, without # the full call for N+1 my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } } my $i = $self->{'i'}; my $t = $self->{'t'}; if ($i > $n) { my $pos = min ($#save_n, int (($n - $save_n[0]) / _SAVE)); $i = $save_n[$pos]; $t = $save_t[$pos]; ### resume: "$i t=$t" } while ($i < $n) { $t += _chord_angle_inc($t); if (++$i == $next_save) { push @save_n, $i; push @save_t, $t; $next_save += _SAVE; } } $self->{'i'} = $i; $self->{'t'} = $t; my $r = $t * (1 / (2*_PI)); return ($r*cos($t), $r*sin($t)); } sub _xy_to_nearest_r { my ($x, $y) = @_; my $frac = Math::PlanePath::MultipleRings::_xy_to_angle_frac($x,$y); ### assert: 0 <= $frac && $frac < 1 # if $frac > 0.5 then 0.5-$frac is negative and int() rounds towards zero # giving $r==$frac return int(hypot($x,$y) + 0.5 - $frac) + $frac; } sub xy_to_n { my ($self, $x, $y) = @_; ### ArchimedeanChords xy_to_n(): "$x, $y" my $r = _xy_to_nearest_r($x,$y); my $r_limit = 1.001 * $r; ### hypot: hypot($x,$y) ### $r ### $r_limit ### save_t: "end index=$#save_t save_t[0]=".($save_t[0]//'undef') if (is_infinite($r_limit)) { ### infinite range, r inf or too big ... return undef; } my $theta = 0.999 * 2*_PI*$r; my $n_lo = 0; foreach my $i (1 .. $#save_t) { if ($save_t[$i] > $theta) { $n_lo = $save_n[$i-1]; if ($n_lo == 1) { $n_lo = 0; } # for finding X=0,Y=0 last; } } ### $n_lo # loop with for(;;) since $n_lo..$n_hi limited to IV range for (my $n = $n_lo; ; $n += 1) { my ($nx,$ny) = $self->n_to_xy($n); # #### $n # #### $nx # #### $ny # #### hypot: hypot ($x-$nx,$y-$ny) if (hypot($x-$nx,$y-$ny) <= 0.5) { ### hypot in range ... return $n; } if (hypot($nx,$ny) >= $r_limit) { last; } } ### n not found ... return undef; } # int (max (0, int(_PI*$r2) - 4*$r)); # # my $r2 = $r * $r; # my $n_lo = int (max (0, int(_PI*$r2) - 4*$r)); # my $n_hi = $n_lo + 7*$r + 2; # ### $r2 # $n_lo == $n_lo-1 || # x,y has radius hypot(x,y), then want the next higher spiral arc which is r # >= hypot(x,y)+0.5, with the 0.5 being the width of the circle figure on # the spiral. # # The polar angle of x,y is a=atan2(y,x) and frac=a/2pi is the extra away # from an integer radius for the spiral. So seek integer k with k+a/2pi >= # h with h=hypot(x,y)+0.5. # # k + a/2pi >= h # k >= h-a/2pi # k = ceil(h-a/2pi) # = ceil(hypot(x,y) + 0.5 - atan2(y,x)/2pi) # # # circle radius i has circumference 2*pi*i and at most that many N on it # rectangle corner at radius Rcorner = hypot(x,y) # # sum i=1 to i=Rlimit of 2*pi*i = 2*pi/2 * Rlimit*(Rlimit+1) # = pi * Rlimit*(Rlimit+1) # is an upper bound, though a fairly slack one # # # cf. arc length along the spiral r=a*theta with a=1/2pi # arclength = (1/2) * a * (theta*sqrt(1+theta^2) + asinh(theta)) # = (1/4*pi) * (theta*sqrt(1+theta^2) + asinh(theta)) # and theta = 2*pi*r # = (1/4*pi) * (4*pi^2*r^2 * sqrt(1+1/theta^2) + asinh(theta)) # = pi * (r^2 * sqrt(1+1/r^2) + asinh(theta)/(4*pi^2)) # # and to compare to the circles formula # # = pi * (r*(r+1) * r/(r+1) * sqrt(1+1/r^2) # + asinh(theta)/(4*pi^2)) # # so it's smaller hence better upper bound. Only a little smaller than the # squaring once get to 100 loops or so. # # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### rect_to_n_range() ... my $rhi = 0; foreach my $x ($x1, $x2) { foreach my $y ($y1, $y2) { my $frac = atan2($y,$x) / (2*_PI); # -0.5 <= $frac <= 0.5 $frac += ($frac < 0); # 0 <= $frac < 1 $rhi = max ($rhi, ceil(hypot($x,$y)+0.5 - $frac) + $frac); } } ### $rhi # arc length pi * (r^2 * sqrt(1+1/r^2) + asinh(theta)/(4*pi^2)) # = pi*r^2*sqrt(1+1/r^2) + asinh(theta)/4pi my $rhi2 = $rhi*$rhi; return (0, ceil (_PI * $rhi2 * sqrt(1+1/$rhi2) + asinh(2*_PI*$rhi) / (4*_PI))); # # each loop begins at N = pi*k^2 - 2 or thereabouts # return (0, # int(_PI*$rhi*($rhi+1) + 1)); } 1; __END__ # my $slope = 2*($t + (-$c1-$s1*$t)*cos($t) + ($c1*$t-$s1)*sin($t)); # my $dist = ( ($t*cos($t) - $c1) ** 2 # + ($t*sin($t) - $s1) ** 2 # - 4*_PI*_PI ); # my $slope = (2*($t*cos($t)-$c1)*(cos($t) - $t*sin($t)) # + 2*($t*sin($t)-$s1)*(sin($t) + $t*cos($t))); # my $c1 = $t1 * cos($t1); # my $s1 = $t1 * sin($t1); # my $c1_2 = $c1*2; # my $s1_2 = $s1*2; # my $t = $t1 + 2*_PI/$t1; # estimate # my $ct = cos($t); # my $st = sin($t); # my $dist = (($t - $ct*$c1_2 - $st*$s1_2) * $t + $t1sqm); # my $slope = 2 * (($t*$ct - $c1) * ($ct - $t*$st) # + ($t*$st - $s1) * ($st + $t*$ct)); # # my $sub = $dist/$slope; # $t -= $sub; # use constant _A => 1 / (2*_PI); # my @radius = (0, 1); # # my $theta = _inverse($n); # # my $r = _A * $theta; # # return ($r * cos($theta), # # $r * sin($theta)); # # # # $n = floor($n); # # # # for (my $i = scalar(@radius); $i <= $n; $i++) { # # my $prev = $radius[$i-1]; # # # my $step = 8 * asin (.25/4 / $prev) / pi(); # # my $step = (.5 / pi()) / $prev; # # $radius[$i] = $prev + $step; # # } # # # # my $r = $radius[$n]; # # my $theta = 2 * pi() * ($r - int($r)); # radians 0 to 2*pi # # return ($r * cos($theta), # # $r * sin($theta)); # sub _arc_length { # my ($theta) = @_; # my $hyp = hypot(1,$theta); # return 0.5 * _A * ($theta*$hyp + asinh($theta)); # } # # # upper bound $hyp >= $theta # # a/2 * $theta * $theta # # so theta = sqrt (2/_A * $length) # # # # lower bound $hyp <= $theta+1, log(x)<=x # # length <= a/2 * ($theta * ($theta+1))^2 # # 2/a * length <= (2*$theta * $theta)^2 # # so theta >= sqrt (1/(2*_A) * $length) # # # sub _inverse { # my ($length) = @_; # my $lo_theta = sqrt (1/(2*_A) * $length); # my $hi_theta = sqrt ((2/_A) * $length); # my $lo_length = _arc_length($lo_theta); # my $hi_length = _arc_length($hi_theta); # #### $length # #### $lo_theta # #### $hi_theta # #### $lo_length # #### $hi_length # die if $lo_length > $length; # die if $hi_length < $length; # my $m_theta; # for (;;) { # $m_theta = ($hi_theta + $lo_theta) / 2; # last if ($hi_length - $lo_length) < 0.000001; # my $m_length = _arc_length($m_theta); # if ($m_length < $length) { # $lo_theta = $m_theta; # $lo_length = $m_length; # } else { # $hi_theta = $m_theta; # $hi_length = $m_length; # } # } # return $m_theta; # } =for stopwords Archimedean Ryde ie cartesian Math-PlanePath arcsin =head1 NAME Math::PlanePath::ArchimedeanChords -- radial spiral chords =head1 SYNOPSIS use Math::PlanePath::ArchimedeanChords; my $path = Math::PlanePath::ArchimedeanChords->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path puts points at unit chord steps along an Archimedean spiral. The spiral goes outwards by 1 unit each revolution and the points are spaced 1 apart. R = theta/(2*pi) The result is roughly 31 32 30 ... 3 33 29 14 34 15 13 28 50 2 12 16 3 35 2 27 49 1 4 11 17 36 5 0 1 26 48 <- Y=0 10 18 37 6 25 47 -1 9 19 7 8 24 46 38 -2 20 23 39 21 22 45 -3 40 44 41 42 43 ^ -3 -2 -1 X=0 1 2 3 4 X,Y positions returned are fractional. Each revolution is about 2*pi longer than the previous, so the effect is a kind of 6.28 increment looping. Because the spacing is by unit chords, adjacent unit circles centred on each N position touch but don't overlap. The spiral spacing of 1 unit per revolution means they don't overlap radially either. The unit chords here are a little like the C. But the C goes by unit steps at a fixed right-angle and approximates an Archimedean spiral (of 3.14 radial spacing). Whereas this C is an actual Archimedean spiral (of radial spacing 1), with unit steps angling along that. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::ArchimedeanChords-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. C<$n> can be any value C<$n E= 0> and fractions give positions on the chord between the integer points (ie. straight line between the points). C<$n==0> is the origin 0,0. For C<$n < 0> the return is an empty list, it being considered there are no negative points in the spiral. =item C<$n = $path-Exy_to_n ($x,$y)> Return an integer point number for coordinates C<$x,$y>. Each integer N is considered the centre of a circle of diameter 1 and an C<$x,$y> within that circle returns N. The unit spacing of the spiral means those circles don't overlap, but they also don't cover the plane and if C<$x,$y> is not within one then the return is C. The current implementation is a bit slow. =item C<$n = $path-En_start ()> Return 0, the first C<$n> on the path. =item C<$str = $path-Efigure ()> Return "circle". =back =head1 FORMULAS =head2 N to X,Y The current code keeps a position as a polar angle t and calculates an increment u needed to move along by a unit chord. If dist(u) is the straight-line distance between t and t+u, then squared is the hypotenuse dist^2(u) = ((t+u)/2pi*cos(t+u) - t/2pi*cos(t))^2 # X + ((t+u)/2pi*sin(t+u) - t/2pi*sin(t))^2 # Y which simplifies to dist^2(u) = [ (t+u)^2 + t^2 - 2*t*(t+u)*cos(u) ] / (4*pi^2) Switch from cos to sin using the half angle cos(u) = 1 - 2*sin^2(u/2) in case if u is small then the cos(u) near 1.0 might lose floating point accuracy, and also as a slight simplification, dist^2(u) = [ u^2 + 4*t*(t+u)*sin^2(u/2) ] / (4*pi^2) Then want the u which has dist(u)=1 for a unit chord. The u*sin(u) part probably doesn't have a good closed form inverse, so the current code is a Newton/Raphson iteration on f(u) = dist^2(u)-1, seeking f(u)=0 f(u) = u^2 + 4*t*(t+u)*sin^2(u/2) - 4*pi^2 Derivative f'(u) for the slope from the cos form is f'(u) = 2*(t+u) - 2*t*[ cos(u) - (t+u)*sin(u) ] And again switching from cos to sin in case u is small, f'(u) = 2*[ u + t*[2*sin^2(u/2) + (t+u)*sin(u)] ] =head2 X,Y to N A given x,y point is at a fraction of a revolution frac = atan2(y,x) / 2pi # -.5 <= frac <= .5 frac += (frac < 0) # 0 <= frac < 1 And the nearest spiral arm measured radially from x,y is then r = int(hypot(x,y) + .5 - frac) + frac Perl's C is the same as the C library and gives -pi E= angle E= pi, hence allowing for fracE0. It may also be "unspecified" for x=0,y=0, and give +/-pi for x=negzero, which has to be a special case so 0,0 gives r=0. The C rounds towards zero, so frac>.5 ends up as r=0. So the N point just before or after that spiral position may cover the x,y, but how many N chords it takes to get around to there is 's not so easily calculated. The current code looks in saved C positions for an N below the target, and searches up from there until past the target and thus not covering x,y. With C points saved 500 apart this means searching somewhere between 1 and 500 points. One possibility for calculating a lower bound for N, instead of the saved positions, and both for C and C, would be to add up chords in circles. A circle of radius k fits pi/arcsin(1/2k) many unit chords, so k=floor(r) pi total = sum ------------ k=0 arcsin(1/2k) and this is less than the chords along the spiral. Is there a good polynomial over-estimate of arcsin, to become an under-estimate total, without giving away so much? =head2 Rectangle to N Range For the C upper bound, the current code takes the arc length along with spiral with the usual formula arc = 1/4pi * (theta*sqrt(1+theta^2) + asinh(theta)) Written in terms of the r radius (theta = 2pi*r) as calculated from the biggest of the rectangle x,y corners, arc = pi*r^2*sqrt(1+1/r^2) + asinh(2pi*r)/4pi The arc length is longer than chords, so N=ceil(arc) is an upper bound for the N range. An upper bound can also be calculated simply from the circumferences of circles 1 to r, since a spiral loop from radius k to k+1 is shorter than a circle of radius k. k=ceil(r) total = sum 2pi*k k=1 = pi*r*(r+1) This is bigger than the arc length, thus a poorer upper bound, but an easier calculation. (Incidentally, for smallish r have arc length E= pi*(r^2+1) which is a tighter bound and an easy calculation, but it only holds up to somewhere around r=10^7.) =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/PentSpiralSkewed.pm0000644000175000017500000001777513734026664021037 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::PentSpiralSkewed; use 5.004; use strict; #use List::Util 'min','max'; *min = \&Math::PlanePath::_min; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_sqrtint = \&Math::PlanePath::_sqrtint; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; use constant xy_is_visited => 1; sub x_negative_at_n { my ($self) = @_; return $self->n_start + 3; } sub y_negative_at_n { my ($self) = @_; return $self->n_start + 4; } sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + 6; } use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant 1.02 _UNDOCUMENTED__dxdy_list => (1,0, # E 0,1, # N -1,1, # NW -1,-1, # SW 1,-1, # SE ); use constant dsumxy_minimum => -2; # SW diagonal use constant dsumxy_maximum => 1; use constant ddiffxy_minimum => -2; # NW diagonal use constant ddiffxy_maximum => 2; # SE diagonal use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_right => 0; # only left or straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } sub n_to_xy { my ($self, $n) = @_; #### n_to_xy: $n # adjust to N=0 at origin X=0,Y=0 $n = $n - $self->{'n_start'}; if ($n < 0) { return; } my $d = int( (_sqrtint(40*$n+9)+7) / 10); $n -= (5*$d-1)*$d/2; if ($n < -$d) { $n += 2*$d; if ($n < 1) { # bottom horizontal return ($n+$d-1, -$d+1); } else { # lower right vertical ... return ($d, $n-$d); } } else { if ($n <= $d) { ### top diagonals left and right ... return (-$n, -abs($n) + $d); } else { ### lower left diagonal ... return ($n - 2*$d, -$n + $d); } } } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); if ($x > 0 && $y < 0) { # vertical downwards at x=0 # d = [ 1, 2, 3 ] # n = [ 5, 14, 28 ] # n = (5/2*$d**2 + 3/2*$d + 1) # so my $d = max($x-1, -$y); ### lower right square part ### $d return ((5*$d + 3)*$d/2 + $x + ($x > $d ? $y+$d : 0) + $self->{'n_start'}); } # vertical at x=0 # d = [ 1, 2, 3 ] # n = [ 3, 10, 22 ] # n = (5/2*$d**2 + -1/2*$d + 1) # my $d = abs($x)+abs($y); return ((5*$d - 1)*$d/2 - $x + ($y < 0 ? 2*($d+$x) : 0) + $self->{'n_start'}); } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### PentSpiralSkewed rect_to_n_range(): $x1,$y1, $x2,$y2 my $d = 0; foreach my $x ($x1, $x2) { $x = round_nearest ($x); foreach my $y ($y1, $y2) { $y = round_nearest ($y); my $this_d = 1 + ($x > 0 && $y < 0 ? max($x,-$y) : abs($x)+abs($y)); ### $x ### $y ### $this_d $d = max($d, $this_d); } } ### $d return ($self->{'n_start'}, $self->{'n_start'} + 5*$d*($d-1)/2 + 2); } 1; __END__ =for stopwords Ryde Math-PlanePath OEIS =head1 NAME Math::PlanePath::PentSpiralSkewed -- integer points in a pentagonal shape =head1 SYNOPSIS use Math::PlanePath::PentSpiralSkewed; my $path = Math::PlanePath::PentSpiralSkewed->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes a pentagonal (five-sided) spiral with points skewed so as to fit a square grid and fully cover the plane. 10 ... 2 / \ \ 11 3 9 20 1 / / \ \ \ 12 4 1--2 8 19 <- Y=0 \ \ | | 13 5--6--7 18 -1 \ | 14-15-16-17 -2 ^ ^ ^ ^ ^ ^ -2 -1 X=0 1 2 3 ... The pattern is similar to the C but cuts three corners which makes each cycle is faster. Each cycle is just 5 steps longer than the previous (where it's 8 for a C). =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same pattern. For example to start at 0, =cut # math-image --path=PentSpiralSkewed,n_start=0 --expression='i<=57?i:0' --output=numbers --size=60x11 =pod 38 n_start => 0 39 21 37 ... 40 22 9 20 36 57 41 23 10 2 8 19 35 56 42 24 11 3 0 1 7 18 34 55 43 25 12 4 5 6 17 33 54 44 26 13 14 15 16 32 53 45 27 28 29 30 31 52 46 47 48 49 50 51 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::PentSpiral-Enew ()> =item C<$path = Math::PlanePath::PentSpiral-Enew (n_start =E $n)> Create and return a new path object. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each point in the path as a square of side 1. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back n_start=1 (the default) A192136 N on X axis, (5*n^2 - 3*n + 2)/2 A140066 N on Y axis A116668 N on X negative axis, (5n^2 + n + 2)/2 A134238 N on Y negative axis A158187 N on North-West diagonal, 10*n^2 + 1 A005891 N on South-East diagonal, centred pentagonals n_start=0 A000566 N on X axis, heptagonal numbers A005476 N on Y axis A005475 N on X negative axis A147875 N on Y negative axis, second heptagonals A033583 N on North-West diagonal, 10*n^2 A028895 N on South-East diagonal, 5*triangular =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/TheodorusSpiral.pm0000644000175000017500000003461113734026654020725 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::TheodorusSpiral; use 5.004; use strict; use Math::Libm 'hypot'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant figure => 'circle'; use constant x_negative_at_n => 4; use constant y_negative_at_n => 7; use constant gcdxy_maximum => 1; use constant dx_minimum => -1; # supremum when straight use constant dx_maximum => 1; # at N=0 use constant dy_minimum => -1; use constant dy_maximum => 1; # at N=1 use constant dsumxy_minimum => -sqrt(2); # supremum diagonal use constant dsumxy_maximum => sqrt(2); use constant ddiffxy_minimum => -sqrt(2); # supremum diagonal use constant ddiffxy_maximum => sqrt(2); use constant turn_any_right => 0; # left always use constant turn_any_straight => 0; # left always #------------------------------------------------------------------------------ # This adding up of unit steps isn't very good. The last x,y,n is kept # anticipating successively higher n, not necessarily consecutive, plus past # x,y,n at _SAVE intervals for going backwards. # # The simplest formulas for the polar angle, possibly with the analytic # continuation version don't seem much better, but theta approaches # 2*sqrt(N) + const, or 2*sqrt(N) + 1/(6*sqrt(N+1)) + const + O(n^(3/2)), so # more terms of that might have tolerably rapid convergence. # # The arctan sums for the polar angle end up as the generalized Riemann # zeta, or the generalized minus the plain. Is there a good formula for # that which would converge quickly? use constant 1.02; # for leading underscore use constant _SAVE => 1000; my @save_n = (1); my @save_x = (1); my @save_y = (0); my $next_save = _SAVE; sub new { return shift->SUPER::new (i => 1, x => 1, y => 0, @_); } # r = sqrt(int) # (frac r)^2 # = hypot(r, frac)^2 frac at right angle to radial # = r^2 + $frac^2 # = sqrt(int)^2 + $frac^2 # = $int + $frac^2 # sub n_to_rsquared { my ($self, $n) = @_; if ($n < 0) { return undef; } my $int = int($n); $n -= $int; # fractional part return $n*$n + $int; } # r = sqrt(i) # x,y angle # r*x/hypot, r*y/hypot # # newx = x - y/r # newy = y + x/r # (x-y/r)^2 + (y+x/r)^2 # = x^2 - 2y/r + y^2/r^2 # + y^2 + 2x/r + x^2/r^2 sub n_to_xy { my ($self, $n) = @_; #### TheodorusSpiral n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } if ($n < 1) { return ($n, 0); } my $frac = $n; $n = int($n); $frac -= $n; my $i = $self->{'i'}; my $x = $self->{'x'}; my $y = $self->{'y'}; #### n_to_xy(): "$n from state $i $x,$y" if ($i > $n) { for (my $pos = $#save_n; $pos >= 0; $pos--) { if ($save_n[$pos] <= $n) { $i = $save_n[$pos]; $x = $save_x[$pos]; $y = $save_y[$pos]; last; } } ### resume: "$i $x,$y" } while ($i < $n) { my $r = sqrt($i); ($x,$y) = ($x - $y/$r, $y + $x/$r); $i++; if ($i == $next_save) { push @save_n, $i; push @save_x, $x; push @save_y, $y; $next_save += _SAVE; ### save: $i ### @save_n ### @save_x ### @save_y } } $self->{'i'} = $i; $self->{'x'} = $x; $self->{'y'} = $y; if ($frac) { my $r = sqrt($n); return ($x - $frac*$y/$r, $y + $frac*$x/$r); } else { #### integer return: "$i $x,$y" return ($x,$y); } } sub xy_to_n { my ($self, $x, $y) = @_; ### TheodorusSpiral xy_to_n(): "$x, $y" my $r = hypot ($x,$y); my $n_lo = int (max (0, $r - .51) ** 2); my $n_hi = int (($r + .51) ** 2); ### $n_lo ### $n_hi if (is_infinite($n_lo) || is_infinite($n_hi)) { ### infinite range, r inf or too big ... return undef; } # for(;;) loop since $n_lo..$n_hi limited to IV range for (my $n = $n_lo; $n <= $n_hi; $n += 1) { my ($nx,$ny) = $self->n_to_xy($n); #### $n #### $nx #### $ny #### hypot: hypot ($x-$nx,$y-$ny) if (hypot ($x-$nx,$y-$ny) <= 0.5) { return $n; } } return undef; } use Math::PlanePath::SacksSpiral; # not exact *rect_to_n_range = \&Math::PlanePath::SacksSpiral::rect_to_n_range; 1; __END__ # cf: # Detlef Gronau "The Spiral of Theodorus", AMM, 111(3), March 2004, # http://www.uni-graz.at/~gronau/monthly230-237.pdf # Philip J. Davis, book "Spirals: From Theodorus to Chaos", published # A. K. Peters, 1993, pages 7-11, 37-43. # K. J. Heuvers, D.S. Moak, B.Boursaw, "The Functional Equation of the # Square Root Spiral", Functional Equations and Inequalities, # ed. T. M. Rassias, Kluwer 2000, pages 111-117, MR1792078 (2001k:39033) # David Brink, "The Spiral of Theodorus and Sums of Zeta-values at the # Half-integers", American Mathematical Monthly, Vol. 119, No. 9 (November # 2012), # pp. 779-786. http://www.jstor.org/stable/10.4169/amer.math.monthly.119.09.779 # A226317 constant of theodorus in decimal # Hlawka, angles of point N is # phi(n) = sum k=1 to n of arcsin 1/sqrt(k+1) # is equidistributed mod 2pi =for stopwords Theodorus Ryde Math-PlanePath Archimedean Nhi Nlo arctan xlo,ylo xhi,yhi rlo Nlo Nhi Nhi-Nlo RSquared ceil OEIS xlo xhi =head1 NAME Math::PlanePath::TheodorusSpiral -- right-angle unit step spiral =head1 SYNOPSIS use Math::PlanePath::TheodorusSpiral; my $path = Math::PlanePath::TheodorusSpiral->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path puts points on the spiral of Theodorus, also called the square root spiral. 61 6 60 27 26 25 24 5 28 23 59 29 22 58 4 30 21 57 3 31 20 4 56 2 32 5 3 19 6 2 55 1 33 18 7 0 1 54 <- Y=0 34 17 8 53 -1 35 16 9 52 -2 36 15 10 14 51 -3 37 11 12 13 50 -4 38 49 39 48 -5 40 47 41 46 -6 42 43 44 45 ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 Each step is a unit distance at right angles to the previous radial spoke. So for example, 3 <- Y=1+1/sqrt(2) \ \ ..2 <- Y=1 .. | . | 0-----1 <- Y=0 ^ X=0 X=1 1 to 2 is a unit step at right angles to the 0 to 1 radial. Then 2 to 3 steps at a right angle to radial 0 to 2 which is 45 degrees, etc. The radial distance 0 to 2 is sqrt(2), 0 to 3 is sqrt(3), and in general R = sqrt(N) because each step is a right triangle with radius(N+1)^2 = S. The resulting shape is very close to an Archimedean spiral with successive loops increasing in radius by pi = 3.14159 or thereabouts each time. X,Y positions returned are fractional and each integer N position is exactly 1 away from the previous. Fractional N values give positions on the straight line between the integer points. (An analytic continuation for a rounded curve between points is possible, but not currently implemented.) Each loop is just under 2*pi^2 = 19.7392 many N points longer than the previous. This means quadratic values 9.8696*k^2 for integer k are an almost straight line. Quadratics close to 9.87 (or a square multiple of that) nearly line up. For example the 22-polygonal numbers have 10*k^2 and at low values are nearly straight because 10 is close to 9.87, but then spiral away. =head1 FUNCTIONS See L for behaviour common to all path classes. The code is currently implemented by adding unit steps in X,Y coordinates, so it's not particularly fast. The last X,Y is saved in the object anticipating successively higher N (not necessarily consecutive), and previous positions 1000 apart are saved for re-use or to go back. =over 4 =item C<$path = Math::PlanePath::TheodorusSpiral-Enew ()> Create and return a new Theodorus spiral object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. C<$n> can be any value C<$n E= 0> and fractions give positions on the spiral in between the integer points. For C<$n < 0> the return is an empty list, it being currently considered there are no negative points in the spiral. (The analytic continuation by Davis would be a possibility, though the resulting "inner spiral" makes positive and negative points overlap a bit. A spiral starting at X=-1 would fit in between the positive points.) =item C<$rsquared = $path-En_to_rsquared ($n)> Return the radial distance R^2 of point C<$n>, or C if C<$n> is negative. For integer C<$n> this is simply C<$n> itself. =item C<$n = $path-Exy_to_n ($x,$y)> Return an integer point number for coordinates C<$x,$y>. Each integer N is considered the centre of a circle of diameter 1 and an C<$x,$y> within that circle returns N. The unit steps of the spiral means those unit circles don't overlap, but the loops are roughly 3.14 apart so there's gaps in between. If C<$x,$y> is not within one of the unit circles then the return is C. =item C<$str = $path-Efigure ()> Return string "circle". =back =head1 FORMULAS =head2 N to RSquared For integer N the spiral has radius R=sqrt(N) and the square is simply RSquared=R^2=N. For fractional N, the point is on a straight line at right angles to the integer position, so R = hypot(sqrt(Ninteger), Nfrac) RSquared = (sqrt(Ninteger))^2 + Nfrac^2 = Ninteger + Nfrac^2 =head2 X,Y to N For a given X,Y the radius R=hypot(X,Y) determines the N position as N=R^2. An N point up to 0.5 away radially might cover X,Y, so the range of N to consider is Nlo = (R-.5)^2 Nhi = (R+.5)^2 A simple search is made through those N's seeking which, if any, covers X,Y. The number of N's searched is Nhi-Nlo = 2*R+1 which is about 1/3 of a loop around the spiral (2*R/2*pi*R ~= 1/3). Actually 0.51 is used so as to guard against floating point round-off, which is then about 4*.51 = 2.04*R many points. The angle of the X,Y position determines which part of the spiral is intersected, but using that doesn't seem particularly easy. The angle for a given N is an arctan sum and don't currently have a good closed-form or converging series to invert, or apply some Newton's method, or whatever. =head2 Rectangle to N Range For C the corner furthest from the origin determines the high N. For that corner Rhi = hypot(xhi,yhi) Nhi = (Rhi+.5)^2 The extra .5 is since a unit circle figure centred as much as .5 further out might intersect the xhi,yhi. The square root hypot() can be avoided by the following over-estimate, and ceil can keep it in integers for integer Nhi. Nhi = Rhi^2 + Rhi + 1/4 <= Xhi^2+Yhi^2 + Xhi+Yhi + 1 # since Rhi<=Xhi+Yhi = Xhi*(Xhi+1) + Yhi*(Yhi+1) + 1 <= ceilXhi*(ceilXhi+1) + ceilYhi*(ceilYhi+1) + 1 With either formula the worst case is when Nhi doesn't intersect the xhi,yhi corner but is just before it, anti-clockwise. Nhi is then a full revolution bigger than it need be, depending where the other corners fall. Similarly for the corner or axis crossing nearest the origin (when the origin itself isn't covered by the rectangle), Rlo = hypot(Xlo,Ylo) Nlo = (Rlo-.5)^2, or 0 if origin covered by rectangle And again in integers without a square root if desired, Nlo = Rlo^2 - Rlo + 1/4 >= Xlo^2+Ylo^2 - (Xlo+Ylo) # since Xlo+Ylo>=Rlo = Xlo*(Xlo-1) + Ylo*(Ylo-1) >= floorXlo*(floorXlo-1) + floorYlo(floorYlo-1) The worst case is when this Nlo doesn't intersect the xlo,ylo corner but is just after it anti-clockwise, so Nlo is a full revolution smaller than it need be. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A072895 N just below X axis A137515 N-1 just below X axis counting num points for n revolutions A172164 loop length increases A164102 2*pi^2 =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/Staircase.pm0000644000175000017500000002123513775045423017512 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::Staircase; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dsumxy_minimum => -1; # straight S use constant dsumxy_maximum => 2; # next row use constant ddiffxy_maximum => 1; # straight S,E use constant dir_maximum_dxdy => (0,-1); # South use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # start from 0.5 back # d = [ 0, 1, 2, 3 ] # n = [ 1.5, 6.5, 15.5 ] # n = ((2*$d - 1)*$d + 0.5) # d = 1/4 + sqrt(1/2 * $n + -3/16) # # start from integer vertical # d = [ 0, 1, 2, 3, 4 ] # n = [ 1, 2, 7, 16, 29 ] # n = ((2*$d - 1)*$d + 1) # d = 1/4 + sqrt(1/2 * $n + -7/16) # = [1 + sqrt(8*$n-7) ] / 4 # sub n_to_xy { my ($self, $n) = @_; #### Staircase n_to_xy: $n # adjust to N=1 start $n = $n - $self->{'n_start'} + 1; my $d; { my $r = 8*$n - 3; if ($r < 1) { return; # N < 0.5, so before start of path } $d = int( (_sqrtint($r) + 1)/4 ); } ### $d ### base: ((2*$d - 1)*$d + 0.5) $n -= (2*$d - 1)*$d; ### fractional: $n my $int = int($n); $n -= $int; my $rem = _divrem_mutate ($int, 2); if ($rem) { ### down ... return ($int, -$n + 2*$d - $int); } else { ### across ... return ($n + $int-1, 2*$d - $int); } } # d = [ 1 2, 3, 4 ] # N = [ 2, 7, 16, 29 ] # N = (2 d^2 - d + 1) # and add 2*$d # base = 2*d^2 - d + 1 + 2*d # = 2*d^2 + d + 1 # = (2*$d + 1)*$d + 1 # sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } my $d = int(($x + $y + 1) / 2); return (2*$d + 1)*$d - $y + $x + $self->{'n_start'}; } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### Staircase rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } # x2 > x1 if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # y2 > y1 if ($x2 < 0 || $y2 < 0) { return (1, 0); # nothing outside first quadrant } if ($x1 < 0) { $x1 *= 0; } if ($y1 < 0) { $y1 *= 0; } my $y_min = $y1; if ((($x1 ^ $y1) & 1) && $y1 < $y2) { # y2==y_max $y1 += 1; ### y1 inc: $y1 } if (! (($x2 ^ $y2) & 1) && $y2 > $y_min) { $y2 -= 1; ### y2 dec: $y2 } return ($self->xy_to_n($x1,$y1), $self->xy_to_n($x2,$y2)); } 1; __END__ =for stopwords eg Ryde Math-PlanePath Legendre's OEIS =head1 NAME Math::PlanePath::Staircase -- integer points in stair-step diagonal stripes =head1 SYNOPSIS use Math::PlanePath::Staircase; my $path = Math::PlanePath::Staircase->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes a staircase pattern down from the Y axis to the X, =cut # math-image --path=Staircase --all --output=numbers_dash --size=70x30 =pod 8 29 | 7 30---31 | 6 16 32---33 | | 5 17---18 34---35 | | 4 7 19---20 36---37 | | | 3 8--- 9 21---22 38---39 | | | 2 2 10---11 23---24 40... | | | 1 3--- 4 12---13 25---26 | | | Y=0 -> 1 5--- 6 14---15 27---28 ^ X=0 1 2 3 4 5 6 XThe 1,6,15,28,etc along the X axis at the end of each run are the hexagonal numbers k*(2*k-1). The diagonal 3,10,21,36,etc up from X=0,Y=1 is the second hexagonal numbers k*(2*k+1), formed by extending the hexagonal numbers to negative k. The two together are the Xtriangular numbers k*(k+1)/2. Legendre's prime generating polynomial 2*k^2+29 bounces around for some low values then makes a steep diagonal upwards from X=19,Y=1, at a slope 3 up for 1 across, but only 2 of each 3 drawn. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same pattern. For example to start at 0, =cut # math-image --path=Staircase,n_start=0 --expression='i<=38?i:0' --output=numbers --size=80x10 =pod n_start => 0 28 29 30 15 31 32 16 17 33 34 6 18 19 35 36 7 8 20 21 37 38 1 9 10 22 23 .... 2 3 11 12 24 25 0 4 5 13 14 26 27 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::Staircase-Enew ()> =item C<$path = Math::PlanePath::AztecDiamondRings-Enew (n_start =E $n)> Create and return a new staircase path object. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are rounded to the nearest integers, which has the effect of treating each point C<$n> as a square of side 1, so the quadrant x>=-0.5, y>=-0.5 is covered. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head1 FORMULAS =head2 Rectangle to N Range Within each row increasing X is increasing N, and in each column increasing Y is increasing pairs of N. Thus for C the lower left corner vertical pair is the minimum N and the upper right vertical pair is the maximum N. A given X,Y is the larger of a vertical pair when ((X^Y)&1)==1. If that happens at the lower left corner then it's X,Y+1 which is the smaller N, as long as Y+1 is in the rectangle. Conversely at the top right if ((X^Y)&1)==0 then it's X,Y-1 which is the bigger N, again as long as Y-1 is in the rectangle. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back n_start=1 (the default) A084849 N on diagonal X=Y A210521 permutation N by diagonals, upwards A199855 inverse n_start=0 A014105 N on diagonal X=Y, second hexagonal numbers n_start=2 A128918 N on X axis, except initial 1,1 A096376 N on diagonal X=Y =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/ComplexMinus.pm0000644000175000017500000006144214000732315020204 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=ComplexMinus --lines --scale=10 # math-image --path=ComplexMinus --all --output=numbers_dash --size=80x50 # Penney numerals in tcl # http://wiki.tcl.tk/10761 # cf A003476 = boundary length of i-1 ComplexMinus # is same as DragonCurve single points N=0 to N=2^k inclusive # Mandelbrot "Fractals: Form, Chance and Dimension" # distance along the boundary between any two points is infinite # Fractal Tilings Derived from Complex Bases # Sara Hagey and Judith Palagallo # The Mathematical Gazette # Vol. 85, No. 503 (Jul., 2001), pp. 194-201 # Published by: The Mathematical Association # Article Stable URL: http://www.jstor.org/stable/3622004 # cf http://szdg.lpds.sztaki.hu/szdg/desc_numsys_es.php # in more than 2 dimensions, by vectors and matrix multiply package Math::PlanePath::ComplexMinus; use 5.004; use strict; use List::Util 'min'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_up_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; use constant parameter_info_array => [ { name => 'realpart', display => 'Real Part', type => 'integer', default => 1, minimum => 1, width => 2, description => 'Real part r in the i-r complex base.', } ]; sub x_negative_at_n { my ($self) = @_; return $self->{'norm'}; } sub y_negative_at_n { my ($self) = @_; return $self->{'norm'} ** 2; } sub absdx_minimum { my ($self) = @_; return ($self->{'realpart'} == 1 ? 0 # i-1 N=3 dX=0,dY=-3 : 1); # i-r otherwise always diff } # realpart=1 # dx=1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0 = (6*16^k-2)/15 # dy=1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,1 = ((9*16^5-1)/15-1)/2+1 # approaches dx=6/15=12/30, dy=9/15/2=9/30 # FIXME: are others smaller than East ? sub dir_maximum_dxdy { my ($self) = @_; if ($self->{'realpart'} == 1) { return (12,-9); } else { return (0,0); } } sub turn_any_straight { my ($self) = @_; return ($self->{'realpart'} != 1); # realpart=1 never straight } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); my $realpart = $self->{'realpart'}; if (! defined $realpart || $realpart < 1) { $self->{'realpart'} = $realpart = 1; } $self->{'norm'} = $realpart*$realpart + 1; return $self; } sub n_to_xy { my ($self, $n) = @_; ### ComplexMinus n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } # is this sort of midpoint worthwhile? not documented yet { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $x = 0; my $y = 0; my $dy = ($n * 0); # 0, inherit bignum from $n my $dx = $dy + 1; # 1, inherit bignum from $n my $realpart = $self->{'realpart'}; my $norm = $self->{'norm'}; foreach my $digit (digit_split_lowtohigh($n,$norm)) { ### at: "$x,$y digit=$digit" $x += $digit * $dx; $y += $digit * $dy; # multiply i-r, ie. (dx,dy) = (dx + i*dy)*(i-$realpart) ($dx,$dy) = (-$dy - $realpart*$dx, $dx - $realpart*$dy); } # GP-Test (dx+I*dy)*(I-'r) == -dy - 'r*dx + I*(dx - 'r*dy) ### final: "$x,$y" return ($x,$y); } sub xy_to_n { my ($self, $x, $y) = @_; ### ComplexMinus xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); my $realpart = $self->{'realpart'}; { my $rx = $realpart*$x; my $ry = $realpart*$y; foreach my $overflow ($rx+$ry, $rx-$ry) { if (is_infinite($overflow)) { return $overflow; } } } my $norm = $self->{'norm'}; my $zero = ($x * 0 * $y); # inherit bignum 0 my @n; # digits low to high while ($x || $y) { my $new_y = $y*$realpart + $x; my $digit = $new_y % $norm; push @n, $digit; $x -= $digit; $new_y = $digit - $new_y; # div i-realpart, # is (i*y + x) * -(i+realpart)/norm # x = [ x*realpart - y ] / -norm # = [ y - x*realpart ] / norm # y = - [ y*realpart + x ] / norm # ### assert: (($y - $x*$realpart) % $norm) == 0 ### assert: ($new_y % $norm) == 0 ($x,$y) = (($y - $x*$realpart) / $norm, $new_y / $norm); } return digit_join_lowtohigh (\@n, $norm, $zero); } # for i-1 need level=6 to cover 8 points surrounding 0,0 # for i-2 and higher level=3 is enough # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### ComplexMinus rect_to_n_range(): "$x1,$y1 $x2,$y2" my $xm = max(abs($x1),abs($x2)); my $ym = max(abs($y1),abs($y2)); return (0, int (($xm*$xm + $ym*$ym) * $self->{'norm'} ** ($self->{'realpart'} > 1 ? 4 : 8))); } #------------------------------------------------------------------------------ sub _UNDOCUMENTED_level_to_figure_boundary { my ($self, $level) = @_; ### _UNDOCUMENTED_level_to_figure_boundary(): "level=$level realpart=$self->{'realpart'}" if ($level < 0) { return undef; } if (is_infinite($level)) { return $level; } my $b0 = 4; if ($level == 0) { return $b0; } my $norm = $self->{'norm'}; my $b1 = 2*$norm + 2; if ($level == 1) { return $b1; } # 2*(norm-1)*(realpart + 2) + 4; # = 2*(n*r + 2*n -r - 2) + 4 # = 2*n*r + 4n -2r - 4 + 4 # = 2*n*r + 4n -2r my $realpart = $self->{'realpart'}; my $b2 = 2*($norm-1)*($realpart + 2) + 4; my $f1 = $norm - 2*$realpart; my $f2 = 2*$realpart - 1; foreach (3 .. $level) { ($b2,$b1,$b0) = ($f2*$b2 + $f1*$b1 + $norm*$b0, $b2, $b1); } return $b2; } #------------------------------------------------------------------------------ { my @table = ('',''); # 6-bit blocks per Penney foreach my $i (064,067,060,063, 4,7,0,3) { vec($table[0],$i,1) = 1; } foreach my $i (020,021,034,035, 0,1,014,015) { vec($table[1],$i,1) = 1; } sub _UNDOCUMENTED__n_is_y_axis { my ($self, $n) = @_; if (is_infinite($n)) { return 0; } if ($n < 0) { return 0; } if ($self->{'realpart'} == 1) { my $pos = 0; foreach my $digit (digit_split_lowtohigh($n,64)) { unless (vec($table[$pos&1],$digit,1)) { ### bad digit: "pos=$pos digit=$digit" return 0; } $pos++; } ### good ... return 1; } else { my ($x,$y) = $self->n_to_xy($n) or return 0; return $x == 0; } } } #------------------------------------------------------------------------------ # levels sub level_to_n_range { my ($self, $level) = @_; return (0, $self->{'norm'}**$level - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($pow, $exp) = round_up_pow ($n+1, $self->{'norm'}); return $exp; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Math-PlanePath 0.abcde twindragon ie 0xC 0,1,0xC,0xD OEIS ACM abcde Xnew Ynew Realpart Khmelnik Specialized DragonCurve visualize =head1 NAME Math::PlanePath::ComplexMinus -- i-1 and other complex number bases i-r =head1 SYNOPSIS use Math::PlanePath::ComplexMinus; my $path = Math::PlanePath::ComplexMinus->new (realpart => 1); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XXThis path traverses points by a complex number base i-r for given integer r. The default is base i-1 as per =over Solomon I. Khmelnik "Specialized Digital Computer for Operations with Complex Numbers" (in Russian), Questions of Radio Electronics, volume 12, number 2, 1964. L Walter Penney, "A 'Binary' System for Complex Numbers", Journal of the ACM, volume 12, number 2, April 1965, pages 247-248. L =back XWhen continued to a power-of-2 extent, this is sometimes called the "twindragon" since the shape (and fractal limit) corresponds to two DragonCurve back-to-back. But the numbering of points in the twindragon is not the same as here. =cut # math-image --path=ComplexMinus --expression='i<64?i:0' --output=numbers =pod 26 27 10 11 3 24 25 8 9 2 18 19 30 31 2 3 14 15 1 16 17 28 29 0 1 12 13 <- Y=0 22 23 6 7 58 59 42 43 -1 20 21 4 5 56 57 40 41 -2 50 51 62 63 34 35 46 47 -3 48 49 60 61 32 33 44 45 -4 54 55 38 39 -5 52 53 36 37 -6 ^ -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 A complex integer can be represented as a set of powers, X+Yi = a[n]*b^n + ... + a[2]*b^2 + a[1]*b + a[0] base b=i-1 digits a[n] to a[0] each = 0 or 1 N = a[n]*2^n + ... + a[2]*2^2 + a[1]*2 + a[0] N is the a[i] digits as bits and X,Y is the resulting complex number. It can be shown that this is a one-to-one mapping so every integer X,Y of the plane is visited once each. The shape of points N=0 to 2^level-1 repeats as N=2^level to 2^(level+1)-1. For example N=0 to N=7 is repeated as N=8 to N=15, but starting at position X=2,Y=2 instead of the origin. That position 2,2 is because b^3 = 2+2i. There's no rotations or mirroring etc in this replication, just position offsets. N=0 to N=7 N=8 to N=15 repeat shape 2 3 10 11 0 1 8 9 6 7 14 15 4 5 12 13 For b=i-1 each N=2^level point starts at X+Yi=(i-1)^level. The powering of that b means the start position rotates around by +135 degrees each time and outward by a radius factor sqrt(2) each time. So for example b^3 = 2+2i is followed by b^4 = -4, which is 135 degrees around and radius |b^3|=sqrt(8) becomes |b^4|=sqrt(16). =head2 Real Part The C $r> option gives a complex base b=i-r for a given integer rE=1. For example C 2> is 20 21 22 23 24 4 15 16 17 18 19 3 10 11 12 13 14 2 5 6 7 8 9 1 45 46 47 48 49 0 1 2 3 4 <- Y=0 40 41 42 43 44 -1 35 36 37 38 39 -2 30 31 32 33 34 -3 70 71 72 73 74 25 26 27 28 29 -4 65 66 67 68 69 -5 60 61 62 63 64 -6 55 56 57 58 59 -7 50 51 52 53 54 -8 ^ -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 10 N is broken into digits of base=norm=r*r+1, ie. digits 0 to r*r inclusive. This makes horizontal runs of r*r+1 many points, such as N=5 to N=9 etc above. In the default r=1 these runs are 2 long whereas for r=2 they're 2*2+1=5 long, or r=3 would be 3*3+1=10, etc. The offset back for each run like N=5 shown is the r in i-r, then the next level is (i-r)^2 = (-2r*i + r^2-1) so N=25 begins at Y=-2*2=-4, X=2*2-1=3. The successive replications tile the plane for any r, though the N values needed to rotate all the way around become big if norm=r*r+1 is big. =head2 Fractal The i-1 twindragon is usually conceived as taking fractional N like 0.abcde in binary and giving fractional complex X+iY. The twindragon is then all the points of the complex plane reached by such fractional N. This set of points can be shown to be connected and to fill a certain radius around the origin. The code here might be pressed into use for that to some finite number of bits by multiplying up to make an integer N Nint = Nfrac * 256^k Xfrac = Xint / 16^k Yfrac = Yint / 16^k 256 is a good power because b^8=16 is a positive real and so there's no rotations to apply to the resulting X,Y, only a power-of-16 division (b^8)^k=16^k each. Using b^4=-4 for a multiplier 16^k and divisor (-4)^k would be almost as easy too, requiring just sign changes if k odd. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::ComplexMinus-Enew ()> =item C<$path = Math::PlanePath::ComplexMinus-Enew (realpart =E $r)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. C<$n> should be an integer, it's unspecified yet what will be done for a fraction. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 2**$level - 1)>, or with C option return C<(0, $norm**$level - 1)> where norm=realpart^2+1. =back =head1 FORMULAS Various formulas and pictures etc for the i-1 case can be found in the author's long mathematical write-up (section "Complex Base i-1") =over L =back =head2 X,Y to N A given X,Y representing X+Yi can be turned into digits of N by successive complex divisions by i-r. Each digit of N is a real remainder 0 to r*r inclusive from that division. The base formula above is X+Yi = a[n]*b^n + ... + a[2]*b^2 + a[1]*b + a[0] and want the a[0]=digit to be a real 0 to r*r inclusive. Subtracting a[0] and dividing by b will give (X+Yi - digit) / (i-r) = - (X-digit + Y*i) * (i+r) / norm = (Y - (X-digit)*r)/norm + i * - ((X-digit) + Y*r)/norm which is Xnew = Y - (X-digit)*r)/norm Ynew = -((X-digit) + Y*r)/norm The a[0] digit must make both Xnew and Ynew parts integers. The easiest one to calculate from is the imaginary part, from which require - ((X-digit) + Y*r) == 0 mod norm so digit = X + Y*r mod norm This digit value makes the real part a multiple of norm too, as can be seen from Xnew = Y - (X-digit)*r = Y - X*r - (X+Y*r)*r = Y - X*r - X*r + Y*r*r = Y*(r*r+1) = Y*norm Notice Ynew is the quotient from (X+Y*r)/norm rounded downwards (towards negative infinity). Ie. in the division "X+Y*r mod norm" which calculates the digit, the quotient is Ynew and the remainder is the digit. =cut # Is this quite right ? ... # # =head2 Radius Range # # In general for base i-1 after the first few innermost levels each # N=2^level increases the covered radius around by a factor sqrt(2), ie. # # N = 0 to 2^level-1 # Xmin,Ymin closest to origin # Xmin^2+Ymin^2 approx 2^(level-7) # # The "level-7" is since the innermost few levels take a while to cover the # points surrounding the origin. Notice for example X=1,Y=-1 is not reached # until N=58. But after that it grows like N approx = pi*R^2. =pod =head2 X Axis N for Realpart 1 For base i-1, Penney shows the N on the X axis are X axis N in hexadecimal uses only digits 0, 1, C, D = 0, 1, 12, 13, 16, 17, 28, 29, 192, 193, 204, 205, 208, ... Those on the positive X axis have an odd number of digits and on the X negative axis an even number of digits. To be on the X axis the imaginary parts of the base powers b^k must cancel out to leave just a real part. The powers repeat in an 8-long cycle k b^k for b=i-1 0 +1 1 i -1 2 -2i +0 \ pair cancel 3 2i +2 / 4 -4 5 -4i +4 6 8i +0 \ pair cancel 7 -8i -8 / The k=0 and k=4 bits are always reals and can always be included. Bits k=2 and k=3 have imaginary parts -2i and 2i which cancel out, so they can be included together. Similarly k=6 and k=7 with 8i and -8i. The two blocks k=0to3 and k=4to7 differ only in a negation so the bits can be reckoned in groups of 4, which is hexadecimal. Bit 1 is digit value 1 and bits 2,3 together are digit value 0xC, so adding one or both of those gives combinations are 0,1,0xC,0xD. The high hex digit determines the sign, positive or negative, of the total real part. Bits k=0 or k=2,3 are positive. Bits k=4 or k=6,7 are negative, so N for X>0 N for X<0 0x01.. 0x1_.. even number of hex 0,1,C,D following 0x0C.. 0xC_.. "_" digit any of 0,1,C,D 0x0D.. 0xD_.. which is equivalent to XE0 is an odd number of hex digits or XE0 is an even number. For example N=28=0x1C is at X=-2 since that N is XE0 form "0x1_". The order of the values on the positive X axis is obtained by taking the digits in reverse order on alternate positions 0,1,C,D high digit D,C,1,0 0,1,C,D ... D,C,1,0 0,1,C,D low digit For example in the following notice the first and third digit increases, but the middle digit decreases, X=4to7 N=0x1D0,0x1D1,0x1DC,0x1DD X=8to11 N=0x1C0,0x1C1,0x1CC,0x1CD X=12to15 N=0x110,0x111,0x11C,0x11D X=16to19 N=0x100,0x101,0x10C,0x10D X=20to23 N=0xCD0,0xCD1,0xCDC,0xCDD For the negative X axis it's the same if reading by increasing X, ie. upwards toward +infinity, or the opposite way around if reading decreasing X, ie. more negative downwards toward -infinity. =head2 Y Axis N for Realpart 1 For base i-1 Penny also characterises the N values on the Y axis, Y axis N in base-64 uses only at even digits 0, 3, 4, 7, 48, 51, 52, 55 at odd digit 0, 1, 12, 13, 16, 17, 28, 29 = 0,3,4,7,48,51,52,55,64,67,68,71,112,115,116,119, ... Base-64 means taking N in 6-bit blocks. Digit positions are counted starting from the least significant digit as position 0 which is even. So the low digit can be only 0,3,4,etc, then the second digit only 0,1,12,etc, and so on. This arises from (i-1)^6 = 8i which gives a repeating pattern of 6-bit blocks. The different patterns at odd and even positions are since i^2 = -1. =head2 Boundary Length XThe length of the boundary of unit squares for the first norm^k many points, ie. N=0 to N=norm^k-1 inclusive, is calculated in =over William J. Gilbert, "The Fractal Dimension of Sets Derived From Complex Bases", Canadian Mathematical Bulletin, volume 29, number 4, 1986. L =back The boundary formula is a 3rd-order recurrence. For the twindragon case it is for realpart=1 boundary[k] = boundary[k-1] + 2*boundary[k-3] = 4, 6, 10, 18, 30, 50, 86, 146, 246, 418, ... (2*A003476) 4 + 2*x + 4*x^2 generating function --------------- 1 - x - 2*x^3 =cut # GP-Test 2*4 + 10 == 18 # GP-Test 2*6 + 18 == 30 # GP-DEFINE gB1(x) = (4 + 2*x + 4*x^2) / (1 - x - 2*x^3) # GP-Test Vec(gB1(x) - O(x^11)) == [4,6,10,18,30,50,86,146,246,418,710] =pod The first three boundaries are as follows. Then the recurrence gives the next boundary[3] = 10+2*4 = 18. k area boundary[k] --- ---- ----------- +---+ 0 2^k = 1 4 | 0 | +---+ +---+---+ 1 2^k = 2 6 | 0 1 | +---+---+ +---+---+ | 2 3 | 2 2^k = 4 10 +---+ +---+ | 0 1 | +---+---+ Gilbert calculates for any i-r by taking the boundary in three parts A,B,C and showing how in the next replication level those boundary parts transform into multiple copies of the preceding level parts. The replication is easier to visualize for a bigger "r" than for i-1 because in bigger r it's clearer how the A, B and C parts differ. The length replications are A -> A * (2*r-1) + C * 2*r B -> A * (r^2-2*r+2) + C * (r-1)^2 C -> B starting from A = 2*r B = 2 C = 2 - 2*r total boundary = A+B+C For i-1 realpart=1 these A,B,C are already in the form of a recurrence A-EA+2*C, B-EA, C-EB, per the formula above. For other real parts a little matrix rearrangement turns the A,B,C parts into recurrence boundary[k] = boundary[k-1] * (2*r - 1) + boundary[k-2] * (norm - 2*r) + boundary[k-3] * norm starting from boundary[0] = 4 # single square cell boundary[1] = 2*norm + 2 # oblong of norm many cells boundary[2] = 2*(norm-1)*(r+2) + 4 For example for realpart=2 boundary[k] = 3*boundary[k-1] + 1*boundary[k-2] + 5*boundary[k-3] = 4, 12, 36, 140, 516, 1868, 6820, 24908, ... 4 - 4*x^2 generating function --------------------- 1 - 3*x - x^2 - 5*x^3 =cut # GP-DEFINE gB2(x) = (4 - 4*x^2) / (1 - 3*x - x^2 - 5*x^3) # GP-Test Vec(gB2(x) - O(x^9)) == [4,12,36,140,516,1868,6820,24908,90884] # GP-Test 5*4+1*12+3*36 == 140 # GP-Test 5*12+1*36+3*140 == 516 # not in OEIS: 4, 12, 36, 140, 516, 1868, 6820, 24908 =pod If calculating for large k values then the matrix form can be powered up rather than repeated additions. (As usual for all such linear recurrences.) =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back realpart=1 (base i-1, the default) A318438 X coordinate A318439 Y coordinate A318479 norm X^2 + Y^2 A066321 N on X>=0 axis, or N/2 of North-West diagonal A271472 same in binary A066323 number of 1 bits A256441 N on negative X axis, X<=0 A073791 X axis X sorted by N A320283 Y axis Y sorted by N A137426 dX/2 at N=2^(k+2)-1 dY at N=2^k-1 (step to next replication level) A066322 diffs (N at X=16k+4) - (N at X=16k+3) A340566 permutation N by diagonals +/- A003476 boundary length / 2 recurrence a(n) = a(n-1) + 2*a(n-3) A203175 boundary length, starting from 4 (believe its conjectured recurrence is true) A052537 boundary length part A, B or C, per Gilbert's paper A193239 reverse-add steps to N binary palindrome A193240 reverse-add trajectory of binary 110 A193241 reverse-add trajectory of binary 10110 A193306 reverse-subtract steps to 0 (plain-rev) A193307 reverse-subtract steps to 0 (rev-plain) realpart=1 (base i-2) A011658 turn 0=straight, 1=not straight, repeating 0,0,0,1,1 =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/HexSpiralSkewed.pm0000644000175000017500000003073213734026670020636 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::HexSpiralSkewed; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_sqrtint = \&Math::PlanePath::_sqrtint; @ISA = ('Math::PlanePath'); use Math::PlanePath::HexSpiral; use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines #use Devel::Comments; use Math::PlanePath::SquareSpiral; *parameter_info_array = \&Math::PlanePath::SquareSpiral::parameter_info_array; use constant xy_is_visited => 1; use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant 1.02 _UNDOCUMENTED__dxdy_list => (1,0, # E four plus 0,1, # N NW and SE -1,1, # NW -1,0, # W 0,-1, # S 1,-1, # SE ); *x_negative_at_n = \&Math::PlanePath::HexSpiral::x_negative_at_n; *y_negative_at_n = \&Math::PlanePath::HexSpiral::y_negative_at_n; *_UNDOCUMENTED__dxdy_list_at_n = \&Math::PlanePath::HexSpiral::_UNDOCUMENTED__dxdy_list_at_n; use constant dsumxy_minimum => -1; # W,S straight use constant dsumxy_maximum => 1; # N,E straight use constant ddiffxy_minimum => -2; # NW diagonal use constant ddiffxy_maximum => 2; # SE diagonal use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_right => 0; # only left or straight sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; return $self->n_start + $self->{'wider'} + 1; } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); # parameters $self->{'wider'} ||= 0; # default if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # Same as HexSpiral, but diagonal down and to the left is the downwards # vertical at x=-$w_left. sub n_to_xy { my ($self, $n) = @_; ### HexSpiralSkewed n_to_xy(): $n $n = $n - $self->{'n_start'}; # N=0 basis if ($n < 0) { return; } my $w = $self->{'wider'}; my $w_right = int($w/2); my $w_left = $w - $w_right; #### $w #### $w_left #### $w_right my $d = int((_sqrtint(3*$n + ($w+2)*$w + 1) - 1 - $w) / 3); #### d frac: (_sqrtint(3*$n + ($w+2)*$w + 1) - 1 - $w) / 3 #### $d $n -= (3*$d + 2 + 2*$w)*$d + 1; #### remainder: $n $n += 1; # N=1 basis if ($n <= $d+1+$w) { #### bottom horizontal return ($n - $w_left, -$d); } $n -= $d+1+$w; if ($n <= $d) { #### right lower vertical, being 1 shorter: $n return ($d + 1 + $w_right, $n - $d); } $n -= $d; if ($n <= $d+1) { #### right upper diagonal: $n return (-$n + $d + 1 + $w_right, $n); } $d = $d + 1; # no warnings if $d==infinity $n -= $d; if ($n <= $d+$w) { #### top horizontal return (-$n + $w_right, $d); } $n -= $d+$w; if ($n <= $d) { #### left upper vertical return (-$d - $w_left, -$n + $d); } #### left lower diagonal $n -= $d; return ($n - $d - $w_left, -$n); } sub xy_to_n { my ($self, $x, $y) = @_; ### xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); my $w = $self->{'wider'}; my $w_right = int($w/2); my $w_left = $w - $w_right; if ($y > 0) { $x -= $w_right; if ($x < -$y-$w) { ### left upper vertical my $d = -$x - $w; ### $d ### base: (3*$d + 1 + 2*$w)*$d return ((3*$d + 1 + 2*$w)*$d - $y + $self->{'n_start'}); } else { my $d = $y + max($x,0); ### right upper diagonal and top horizontal ### $d ### base: (3*$d - 1 + 2*$w)*$d - $w return ((3*$d - 1 + 2*$w)*$d - $w - $x + $self->{'n_start'}); } } else { # $y < 0 $x += $w_left; if ($x-$w <= -$y) { my $d = -$y + max(-$x,0); ### left lower diagonal and bottom horizontal ### $d ### base: (3*$d + 2 + 2*$w)*$d + 1 return ((3*$d + 2 + 2*$w)*$d + $x + $self->{'n_start'}); } else { ### right lower vertical my $d = $x - $w; ### $d ### base: (3*$d - 2 + 2*$w)*$d + 1 - $w return ((3*$d - 2 + 2*$w)*$d - $w + $y + $self->{'n_start'}); } } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### HexSpiralSkewed rect_to_n_range(): $x1,$y1, $x2,$y2 $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); my $w = $self->{'wider'}; my $w_right = int($w/2); my $w_left = $w - $w_right; my $d = 0; foreach my $x ($x1, $x2) { $x += $w_left; if ($x >= $w) { $x -= $w; } foreach my $y ($y1, $y2) { $d = max ($d, (($y > 0) == ($x > 0) ? abs($x) + abs($y) # top right or bottom left diagonals : max(abs($x),abs($y)))); # top left or bottom right squares } } $d += 1; # diagonal downwards bottom right being the end of a revolution # s=0 # s=1 n=7 # s=2 n=19 # s=3 n=37 # s=4 n=61 # n = 3*$d*$d + 3*$d + 1 # ### gives: "sum $d is " . (3*$d*$d + 3*$d + 1) # ENHANCE-ME: find actual minimum if rect doesn't cover 0,0 return ($self->{'n_start'}, (3*$d + 3 + 2*$self->{'wider'})*$d + $self->{'n_start'}); } 1; __END__ =for stopwords PlanePath Ryde Math-PlanePath OEIS =head1 NAME Math::PlanePath::HexSpiralSkewed -- integer points around a skewed hexagonal spiral =head1 SYNOPSIS use Math::PlanePath::HexSpiralSkewed; my $path = Math::PlanePath::HexSpiralSkewed->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes a hexagonal spiral with points skewed so as to fit a square grid and fully cover the plane. 13--12--11 ... 2 | \ \ 14 4---3 10 23 1 | | \ \ \ 15 5 1---2 9 22 <- Y=0 \ \ | | 16 6---7---8 21 -1 \ | 17--18--19--20 -2 ^ ^ ^ ^ ^ ^ -2 -1 X=0 1 2 3 ... The kinds of N=3*k^2 numbers which fall on straight lines in the plain C also fall on straight lines when skewed. See L for notes on this. =head2 Skew The skewed path is the same shape as the plain C, but fits more points on a square grid. The skew pushes the top horizontal to the left, as shown by the following parts, and the bottom horizontal is similarly skewed but to the right. HexSpiralSkewed HexSpiral 13--12--11 13--12--11 | \ / \ 14 10 14 10 | \ / \ 15 9 15 9 -2 -1 X=0 1 2 -4 -3 -2 X=0 2 3 4 In general the coordinates can be converted each way by plain X,Y -> skewed (X-Y)/2, Y skewed X,Y -> plain 2*X+Y, Y =head1 Corners C is similar to the C but cuts off the top-right and bottom-left corners so that each loop is 6 steps longer than the previous, whereas for the C it's 8. See L for other corner cutting. =head2 Wider An optional C parameter makes the path wider, stretched along the top and bottom horizontals. For example $path = Math::PlanePath::HexSpiralSkewed->new (wider => 2); gives 21--20--19--18--17 2 | \ 22 8---7---6---5 16 1 | | \ \ 23 9 1---2---3---4 15 <- Y=0 \ \ | 24 10--11--12--13--14 ... -1 \ | 25--26--27--28--29--30 -2 ^ ^ ^ ^ ^ ^ ^ ^ -4 -3 -2 -1 X=0 1 2 3 ... The centre horizontal from N=1 is extended by C many further places, then the path loops around that shape. The starting point 1 is shifted to the left by wider/2 places (rounded up to an integer) to keep the spiral centred on the origin X=0,Y=0. Each loop is still 6 longer than the previous, since the widening is basically a constant amount added into each loop. The result is the same as the plain C of the same widening too. The effect looks better in the plain C. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start with the same shape etc. For example to start at 0, =cut # math-image --path=HexSpiralSkewed,n_start=0 --all --output=numbers --size=70x9 =pod n_start => 0 27 26 25 24 3 28 12 11 10 23 2 29 13 3 2 9 22 1 30 14 4 0 1 8 21 ... <- Y=0 31 15 5 6 7 20 39 -1 32 16 17 18 19 38 -2 33 34 35 36 37 -3 -3 -2 -1 X=0 1 2 3 4 In this numbering the X axis N=0,1,8,21,etc is the octagonal numbers 3*X*(X+1). =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::HexSpiralSkewed-Enew ()> =item C<$path = Math::PlanePath::HexSpiralSkewed-Enew (wider =E $w)> Create and return a new hexagon spiral object. An optional C parameter widens the spiral path, it defaults to 0 which is no widening. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each point in the path as a square of side 1. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A056105 N on X axis, 3n^2-2n+1 A056106 N on Y axis, 3n^2-n+1 A056107 N on North-West diagonal, 3n^2+1 A056108 N on X negative axis, 3n^2+n+1 A056109 N on Y negative axis, 3n^2+2n+1 A003215 N on South-East diagonal, centred hexagonals n_start=0 A000567 N on X axis, octagonal numbers A049450 N on Y axis A049451 N on X negative axis A045944 N on Y negative axis, octagonal numbers second kind A062783 N on X=Y diagonal north-east A033428 N on north-west diagonal, 3*k^2 A063436 N on south-west diagonal A028896 N on south-east diagonal =head1 SEE ALSO L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/KnightSpiral.pm0000644000175000017500000003561613734026667020207 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::KnightSpiral; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use constant xy_is_visited => 1; sub x_negative_at_n { my ($self) = @_; return $self->n_start + 3; } sub y_negative_at_n { my ($self) = @_; return $self->n_start + 1; } sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + 8; } use constant dx_minimum => -2; use constant dx_maximum => 2; use constant dy_minimum => -2; use constant dy_maximum => 2; use constant 1.02 _UNDOCUMENTED__dxdy_list => (2,1, # ENE 1,2, # NNE -1,2, # NNW -2,1, # WNW -2,-1, # WSW -1,-2, # SSW 1,-2, # SSE 2,-1, # ESE ); use constant absdx_minimum => 1; use constant absdy_minimum => 1; use constant dsumxy_minimum => -3; # -2,-1 use constant dsumxy_maximum => 3; # +2,+1 use constant ddiffxy_minimum => -3; use constant ddiffxy_maximum => 3; use constant dir_minimum_dxdy => (2,1); # X=2,Y=1 angle use constant dir_maximum_dxdy => (2,-1); # Maybe ... # use constant parameter_info_array => # [ # Math::PlanePath::Base::Generic::parameter_info_nstart1(), # ]; #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } sub _odd { my ($n) = @_; ### _odd(): $n $n -= 2*int($n/2); ### rem: "$n" if ($n > 1) { return 2-$n; } else { return $n; } # return (int($n) % 2); } sub n_to_xy { my ($self, $n) = @_; #### KnightSpiral n_to_xy: $n # adjust to N=1 at origin X=0,Y=0 $n = $n - $self->{'n_start'} + 1; if ($n < 2) { if ($n < 1) { return; } $n--; return (2*$n, -$n); } my $d = int ((7 + _sqrtint($n-1)) / 4); my $d1 = $d-1; my $outer = 2*$d1; my $inner = $outer - 1; my $p = 2*$d1; my $p1 = $p - 1; # use Smart::Comments; #### s frac: .25 * (7 + sqrt($n - 1)) #### $d #### $d1 #### $inner #### $outer #### $p #### $p1 $n -= $d*(16*$d - 56) + 50; #### remainder: $n # one # if ($n < $p1) { #### right upwards, eg 2 ... return (- _odd($n) + $outer, 2*$n - $inner); } $n -= $p1; if ($n < $p1) { #### top leftwards, eg 3 ... return (-2*$n + $inner, _odd($n) + $inner); } $n -= $p1; if ($n < $p) { #### left downwards ... return ( - _odd($n) - $inner, -2*$n + $outer); } $n -= $p; if ($n < $p1) { #### bottom rightwards: $n return (2*$n - $inner, _odd($n) - $outer); } $n -= $p1; ### two ... # if ($n < $p1) { ### right upwards ... return (_odd($n) + $inner, 2*$n - $inner); } $n -= $p1; if ($n < $p) { #### top leftwards return (-2*$n + $outer, _odd($n) + $inner); } $n -= $p; if ($n < $p1) { #### left downwards return (_odd($n) - $outer, -2*$n + $inner); } $n -= $p1; if ($n < $p1) { #### bottom rightwards: $n return (2*$n - $inner, - _odd($n) - $inner); } $n -= $p1; ### three ... # if ($n < $p) { ### right upwards, eg 12 ... return (_odd($n) + $inner, 2*$n - $outer); } $n -= $p; if ($n < $p1) { ### top leftwards, eg 14 ... return (-2*$n + $inner, - _odd($n) + $outer); } $n -= $p1; if ($n < $p1) { ### left downwards, eg 15 ... return (- _odd($n) - $inner, -2*$n + $inner); } $n -= $p1; if ($n < $p1) { ### bottom rightwards, eg 16 ... return (2*$n - $outer, - _odd($n) - $inner); } $n -= $p1; ### four ... # if ($n <= 1) { ### special 17 upwards ... return ($n + $outer - 2, 2*$n - $outer); } if ($n < $p) { ### right upwards ... return (- _odd($n) + $outer, 2*$n - $outer); } $n -= $p; if ($n < $p) { ### top leftwards, eg 19 ... return (-2*$n + $outer, - _odd($n) + $outer); } $n -= $p; if ($n < $p) { ### left downwards, eg 21 ... return (_odd($n) - $outer, -2*$n + $outer); } $n -= $p; if ($n < $p) { ### bottom rightwards, eg 23 ... return (2*$n - $outer, _odd($n) - $outer); } $n -= $p; ### step outwards, eg 25 ... return (2*$n + $outer, - _odd($n) - $outer); } # 157 92 113 134 155 90 111 132 153 88 109 130 151 # 114 135 156 91 112 133 154 89 110 131 152 87 108 # 93 158 73 32 45 58 71 30 43 56 69 150 129 # 136 115 46 59 72 31 44 57 70 29 42 107 86 # 159 94 33 74 21 4 9 14 19 68 55 128 149 # 116 137 60 47 10 15 20 3 8 41 28 85 106 # 95 160| 75 34 | 5 22 1 18 13 | 54 67| 148 127 # 138 117 48 61 16 11 24 7 2 27 40 105 84 # 161 96 35 76 23 6 17 12 25 66 53 126 147 # 118 139 62 49 78 37 64 51 80 39 26 83 104 # 97 162 77 36 63 50 79 38 65 52 81 146 125 # 140 119 164 99 142 121 166 101 144 123 168 103 82 # 163 98 141 120 165 100 143 122 167 102 145 124 169 sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); if ($x == 0 && $y == 0) { return $self->{'n_start'}; } my $r = max(abs($x),abs($y)); my $d = int (($r+1)/2); # ring number, counting $x=1,2 as $d==1 $r -= (~$r & 1); # next lower odd number ### $d ### $r if ($y >= $r) { ### top horizontal my $xodd = ($x & 1); $x = ($x - $xodd) / 2; ### $xodd ### $x # x odd # [3,30,89,180,303] (16*$d**2 + -21*$d + 8) # [14,57,132,239,378,549] (16*$d**2 + -5*$d + 3) # # [9,44,111,210,341,504] (16*$d**2 + -13*$d + 6) # [20,71,154,269,416] (16*$d**2 + 3*$d + 1) my $n = 16*$d*$d - $x; if (($x ^ $y ^ $d) & 1) { if ($xodd) { return $n -5*$d + 2 + $self->{'n_start'}; } else { return $n -13*$d + 5 + $self->{'n_start'}; } } else { if ($xodd) { return $n -21*$d + 7 + $self->{'n_start'}; } else { return $n + 3*$d + $self->{'n_start'}; } } } # the lower left outer corner 25,81,169,etc belongs on the bottom # horizontal, it's not an extension downwards from the right vertical # (positions N=18,66,146,etc), hence $x!=-$y # if ($x >= $r && $x != -$y) { ### right vertical my $yodd = ($y & 1); $y = ($y - $yodd) / 2; ### $yodd ### $y # y odd # [3, 28,85, 174,295, 448,633] (16*$d**2 + -23*$d + 10) # [8,41, 106,203, 332,493] (16*$d**2 + -15*$d + 7) # # y even # [13,54,127,232,369,538] (16*$d**2 + -7*$d + 4) # [18,67,148,261,406,583,792] (16*$d**2 + $d + 1) # my $n = 16*$d*$d + $y; if (($x ^ $y ^ $d) & 1) { if ($yodd) { return $n -15*$d + 6 + $self->{'n_start'}; } else { return $n -7*$d + 3 + $self->{'n_start'}; } } else { if ($yodd) { return $n -23*$d + 9 + $self->{'n_start'}; } else { return $n + $d + $self->{'n_start'}; } } } if ($y <= -$r) { ### bottom horizontal my $xodd = ($x & 1); $x = ($x - $xodd) / 2; ### $xodd ### $x # x odd # [7,38,101,196,323] (16*$d**2 + -17*$d + 8) # [12,51,122,225,360,527] (16*$d**2 + -9*$d + 5) # # x even # [17,64,143,254,397,572] (16*$d**2 + -1*$d + 2) # [24,79,166,285,436] (16*$d**2 + 7*$d + 1) my $n = 16*$d*$d + $x; if (($x ^ $y ^ $d) & 1) { if ($xodd) { return $n -9*$d + 4 + $self->{'n_start'}; } else { return $n -1*$d + 1 + $self->{'n_start'}; } } else { if ($xodd) { return $n -17*$d + 7 + $self->{'n_start'}; } else { return $n + 7*$d + $self->{'n_start'}; } } } if ($x <= -$r) { ### left vertical my $yodd = ($y & 1); $y = ($y - $yodd) / 2; ### $yodd ### $y # y odd # [10,47,116,217,350,515] (16*$d**2 + -11*$d + 5) # [15,60,137,246,387] (16*$d**2 + -3*$d + 2) # # y even # [5,34,95,188,313] (16*$d**2 + -19*$d + 8) # [22,75,160,277,426] (16*$d**2 + 5*$d + 1) # my $n = 16*$d*$d - $y; if (($x ^ $y ^ $d) & 1) { if ($yodd) { return $n -11*$d + 4 + $self->{'n_start'}; } else { return $n -19*$d + 7 + $self->{'n_start'}; } } else { if ($yodd) { return $n -3*$d + 1 + $self->{'n_start'}; } else { return $n + 5*$d + $self->{'n_start'}; } } } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); my $x = max(abs($x1),abs($x2)); my $y = max(abs($y1),abs($y2)); my $d = max(abs($x),abs($y)); $d += ($d & 1); # next even number if not already even ### $x ### $y ### $d ### is: $d*$d $d = 2*$d+1; # width of whole square # ENHANCE-ME: find actual minimum if rect doesn't cover 0,0 return ($self->{'n_start'}, $self->{'n_start'} + $d*$d); } 1; __END__ =for stopwords versa Ryde Math-PlanePath OEIS =head1 NAME Math::PlanePath::KnightSpiral -- integer points around a square, by chess knight moves =head1 SYNOPSIS use Math::PlanePath::KnightSpiral; my $path = Math::PlanePath::KnightSpiral->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path traverses the plane by an infinite "knight's tour" in the form of a square spiral. ... 21 4 9 14 19 2 10 15 20 3 8 28 1 5 22 1 18 13 <- Y=0 16 11 24 7 2 27 1 23 6 17 12 25 2 26 ^ -2 -1 X=0 1 2 3 Each step is a chess knight's move 1 across and 2 along, or vice versa. The pattern makes 4 cycles on a 2-wide path around a square before stepping outwards to do the same again to a now bigger square. The above sample shows the first 4-cycle around the central 1, then stepping out at 26 and beginning to go around the outside of the 5x5 square. An attractive traced out picture of the path appeared in the past at C, =over L L L =back (HTML colours might might make the text invisible. Try deleting, or browser option to ignore page colours, or a text browser.) =cut # www.borderschess.org is their book shop site, umm, maybe. # Some time in 2016 it changed to wordpress and lost past goodness. # # KTart.htm about 86 captures: # http://web.archive.org/cdx/search/cdx?limit=200&fl=length,timestamp,original&filter=statuscode:200&url=www.borderschess.org/KTart.htm* # # Last 20170608194423 is a domain expired something. # Second last 20161028114643 is the page at its final revision. =pod See L to draw the path lines too. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::KnightSpiral-Enew ()> Create and return a new knight spiral object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n < 1> the return is an empty list, it being considered the path starts at 1. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each N in the path as centred in a square of side 1, so the entire plane is covered. =back =head1 OEIS This Knight's tour is in Sloane's OEIS following the Knight spiral and giving the resulting X,Y location by the C numbering. There's eight forms for 4 rotations and the two spirals same or opposite directions. =over L (etc) =back permutations A068608 same knight and square spiral directions A068609 rotate 90 degrees A068610 rotate 180 degrees A068611 rotate 270 degrees A068612 rotate 180 degrees, spiral opp dir (X negate) A068613 rotate 270 degrees, spiral opp dir A068614 spiral opposite direction (Y negate) A068615 rotate 90 degrees, spiral opp dir (X,Y transpose) See F for a sample program printing the values of A068608. =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/AR2W2Curve.pm0000644000175000017500000006555113734026674017411 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=AR2W2Curve --all --output=numbers_dash # # http://www.springerlink.com/content/y1l60g7125038668/ [pay] # # Google Books LATIN'95 link: page 44 definition # http://books.google.com.au/books?id=_aKhJUJunYwC&lpg=PA44&ots=ARyDkP_hjU&dq=%22Space-Filling%20Curves%20and%20Their%20Use%20in%20the%20Design%20of%20Geometric%20Data%20Structures%22&pg=PA44#v=onepage&q&f=false # package Math::PlanePath::AR2W2Curve; use 5.004; use strict; use Carp 'croak'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'bit_split_lowtohigh', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; use constant parameter_info_array => [ { name => 'start_shape', share_key => 'start_shape_ar2w2', display => 'Start Shape', type => 'enum', default => 'A1', choices => ['A1','D2', 'B2','B1rev', 'D1rev','A2rev', ], choices_display => ['A1','D2', 'B2','B1rev', 'D1rev','A2rev', ], description => 'The starting 2x2 pattern in the bottom left corner.', }, ]; use constant dx_minimum => -1; # NSEW+diagonals use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; *_UNDOCUMENTED__dxdy_list = \&Math::PlanePath::_UNDOCUMENTED__dxdy_list_eight; { my %_UNDOCUMENTED__dxdy_list_at_n = (A1 => 201, D2 => 329, B2 => 201, B1rev => 201, D1rev => 329, A2rev => 201, ); sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $_UNDOCUMENTED__dxdy_list_at_n{$self->{'start_shape'}}; } } use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East #------------------------------------------------------------------------------ # tables generated by tools/ar2w2-curve-table.pl # my @next_state = (224, 92,132,120, 228, 80,136,124, 232, 84,140,112, 236, 88,128,116, 104,148, 76,240, 108,152, 64,244, 96,156, 68,248, 100,144, 72,252, 92,160,120,196, 80,164,124,200, 84,168,112,204, 88,172,116,192, 212,104,176, 76, 216,108,180, 64, 220, 96,184, 68, 208,100,188, 72, 220,160, 64,116, 208,164, 68,120, 212,168, 72,124, 216,172, 76,112, 100, 80,176,204, 104, 84,180,192, 108, 88,184,196, 96, 92,188,200, 92, 96,128,244, 80,100,132,248, 84,104,136,252, 88,108,140,240, 228,144,112, 76, 232,148,116, 64, 236,152,120, 68, 224,156,124, 72, 32, 68, 12,116, 36, 72, 0,120, 40, 76, 4,124, 44, 64, 8,112, 100, 28, 84, 48, 104, 16, 88, 52, 108, 20, 92, 56, 96, 24, 80, 60, 92, 32,108, 12, 80, 36, 96, 0, 84, 40,100, 4, 88, 44,104, 8, 28,124, 48, 76, 16,112, 52, 64, 20,116, 56, 68, 24,120, 60, 72, 220, 32,172, 44, 208, 36,160, 32, 212, 40,164, 36, 216, 44,168, 40, 60,188, 48,204, 48,176, 52,192, 52,180, 56,196, 56,184, 60,200, 0,132, 12,244, 4,136, 0,248, 8,140, 4,252, 12,128, 8,240, 228, 28,148, 16, 232, 16,152, 20, 236, 20,156, 24, 224, 24,144, 28); my @digit_to_x = (0,1,0,1, 1,1,0,0, 1,0,1,0, 0,0,1,1, 1,0,1,0, 0,0,1,1, 0,1,0,1, 1,1,0,0, 0,0,1,1, 1,0,1,0, 1,1,0,0, 0,1,0,1, 1,1,0,0, 0,1,0,1, 0,0,1,1, 1,0,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0, 0,1,1,0, 1,1,0,0, 1,0,0,1, 0,0,1,1, 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0, 0,1,1,0, 1,1,0,0, 1,0,0,1, 0,0,1,1, 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0, 0,1,1,0, 1,1,0,0, 1,0,0,1, 0,0,1,1, 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0, 0,1,1,0, 1,1,0,0, 1,0,0,1, 0,0,1,1, 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0, 0,1,1,0, 1,1,0,0, 1,0,0,1, 0,0,1,1, 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0, 0,1,1,0, 1,1,0,0, 1,0,0,1, 0,0,1,1, 0,1,1,0); my @digit_to_y = (0,0,1,1, 0,1,0,1, 1,1,0,0, 1,0,1,0, 1,1,0,0, 1,0,1,0, 0,0,1,1, 0,1,0,1, 0,1,0,1, 0,0,1,1, 1,0,1,0, 1,1,0,0, 1,0,1,0, 1,1,0,0, 0,1,0,1, 0,0,1,1, 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0, 0,1,1,0, 1,1,0,0, 1,0,0,1, 0,0,1,1, 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0, 0,1,1,0, 1,1,0,0, 1,0,0,1, 0,0,1,1, 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0, 0,1,1,0, 1,1,0,0, 1,0,0,1, 0,0,1,1, 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0, 0,1,1,0, 1,1,0,0, 1,0,0,1, 0,0,1,1, 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0, 0,1,1,0, 1,1,0,0, 1,0,0,1, 0,0,1,1, 0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0, 0,1,1,0, 1,1,0,0, 1,0,0,1, 0,0,1,1); my @yx_to_digit = (0,1,2,3, 2,0,3,1, 3,2,1,0, 1,3,0,2, 3,2,1,0, 1,3,0,2, 0,1,2,3, 2,0,3,1, 0,2,1,3, 1,0,3,2, 3,1,2,0, 2,3,0,1, 3,1,2,0, 2,3,0,1, 0,2,1,3, 1,0,3,2, 0,3,1,2, 1,0,2,3, 2,1,3,0, 3,2,0,1, 3,0,2,1, 2,3,1,0, 1,2,0,3, 0,1,3,2, 0,3,1,2, 1,0,2,3, 2,1,3,0, 3,2,0,1, 3,0,2,1, 2,3,1,0, 1,2,0,3, 0,1,3,2, 0,3,1,2, 1,0,2,3, 2,1,3,0, 3,2,0,1, 3,0,2,1, 2,3,1,0, 1,2,0,3, 0,1,3,2, 0,3,1,2, 1,0,2,3, 2,1,3,0, 3,2,0,1, 3,0,2,1, 2,3,1,0, 1,2,0,3, 0,1,3,2, 0,3,1,2, 1,0,2,3, 2,1,3,0, 3,2,0,1, 3,0,2,1, 2,3,1,0, 1,2,0,3, 0,1,3,2, 0,3,1,2, 1,0,2,3, 2,1,3,0, 3,2,0,1, 3,0,2,1, 2,3,1,0, 1,2,0,3, 0,1,3,2); my @min_digit = (0,0,1, 0,0,1, 2,2,3, undef,undef,undef, # 3* 0 2,0,0, 2,0,0, 3,1,1, undef,undef,undef, # 3* 4 3,2,2, 1,0,0, 1,0,0, undef,undef,undef, # 3* 8 1,1,3, 0,0,2, 0,0,2, undef,undef,undef, # 3* 12 3,2,2, 1,0,0, 1,0,0, undef,undef,undef, # 3* 16 1,1,3, 0,0,2, 0,0,2, undef,undef,undef, # 3* 20 0,0,1, 0,0,1, 2,2,3, undef,undef,undef, # 3* 24 2,0,0, 2,0,0, 3,1,1, undef,undef,undef, # 3* 28 0,0,2, 0,0,2, 1,1,3, undef,undef,undef, # 3* 32 1,0,0, 1,0,0, 3,2,2, undef,undef,undef, # 3* 36 3,1,1, 2,0,0, 2,0,0, undef,undef,undef, # 3* 40 2,2,3, 0,0,1, 0,0,1, undef,undef,undef, # 3* 44 3,1,1, 2,0,0, 2,0,0, undef,undef,undef, # 3* 48 2,2,3, 0,0,1, 0,0,1, undef,undef,undef, # 3* 52 0,0,2, 0,0,2, 1,1,3, undef,undef,undef, # 3* 56 1,0,0, 1,0,0, 3,2,2, undef,undef,undef, # 3* 60 0,0,3, 0,0,2, 1,1,2, undef,undef,undef, # 3* 64 1,0,0, 1,0,0, 2,2,3, undef,undef,undef, # 3* 68 2,1,1, 2,0,0, 3,0,0, undef,undef,undef, # 3* 72 3,2,2, 0,0,1, 0,0,1, undef,undef,undef, # 3* 76 3,0,0, 2,0,0, 2,1,1, undef,undef,undef, # 3* 80 2,2,3, 1,0,0, 1,0,0, undef,undef,undef, # 3* 84 1,1,2, 0,0,2, 0,0,3, undef,undef,undef, # 3* 88 0,0,1, 0,0,1, 3,2,2, undef,undef,undef, # 3* 92 0,0,3, 0,0,2, 1,1,2, undef,undef,undef, # 3* 96 1,0,0, 1,0,0, 2,2,3, undef,undef,undef, # 3* 100 2,1,1, 2,0,0, 3,0,0, undef,undef,undef, # 3* 104 3,2,2, 0,0,1, 0,0,1, undef,undef,undef, # 3* 108 3,0,0, 2,0,0, 2,1,1, undef,undef,undef, # 3* 112 2,2,3, 1,0,0, 1,0,0, undef,undef,undef, # 3* 116 1,1,2, 0,0,2, 0,0,3, undef,undef,undef, # 3* 120 0,0,1, 0,0,1, 3,2,2, undef,undef,undef, # 3* 124 0,0,3, 0,0,2, 1,1,2, undef,undef,undef, # 3* 128 1,0,0, 1,0,0, 2,2,3, undef,undef,undef, # 3* 132 2,1,1, 2,0,0, 3,0,0, undef,undef,undef, # 3* 136 3,2,2, 0,0,1, 0,0,1, undef,undef,undef, # 3* 140 3,0,0, 2,0,0, 2,1,1, undef,undef,undef, # 3* 144 2,2,3, 1,0,0, 1,0,0, undef,undef,undef, # 3* 148 1,1,2, 0,0,2, 0,0,3, undef,undef,undef, # 3* 152 0,0,1, 0,0,1, 3,2,2, undef,undef,undef, # 3* 156 0,0,3, 0,0,2, 1,1,2, undef,undef,undef, # 3* 160 1,0,0, 1,0,0, 2,2,3, undef,undef,undef, # 3* 164 2,1,1, 2,0,0, 3,0,0, undef,undef,undef, # 3* 168 3,2,2, 0,0,1, 0,0,1, undef,undef,undef, # 3* 172 3,0,0, 2,0,0, 2,1,1, undef,undef,undef, # 3* 176 2,2,3, 1,0,0, 1,0,0, undef,undef,undef, # 3* 180 1,1,2, 0,0,2, 0,0,3, undef,undef,undef, # 3* 184 0,0,1, 0,0,1, 3,2,2, undef,undef,undef, # 3* 188 0,0,3, 0,0,2, 1,1,2, undef,undef,undef, # 3* 192 1,0,0, 1,0,0, 2,2,3, undef,undef,undef, # 3* 196 2,1,1, 2,0,0, 3,0,0, undef,undef,undef, # 3* 200 3,2,2, 0,0,1, 0,0,1, undef,undef,undef, # 3* 204 3,0,0, 2,0,0, 2,1,1, undef,undef,undef, # 3* 208 2,2,3, 1,0,0, 1,0,0, undef,undef,undef, # 3* 212 1,1,2, 0,0,2, 0,0,3, undef,undef,undef, # 3* 216 0,0,1, 0,0,1, 3,2,2, undef,undef,undef, # 3* 220 0,0,3, 0,0,2, 1,1,2, undef,undef,undef, # 3* 224 1,0,0, 1,0,0, 2,2,3, undef,undef,undef, # 3* 228 2,1,1, 2,0,0, 3,0,0, undef,undef,undef, # 3* 232 3,2,2, 0,0,1, 0,0,1, undef,undef,undef, # 3* 236 3,0,0, 2,0,0, 2,1,1, undef,undef,undef, # 3* 240 2,2,3, 1,0,0, 1,0,0, undef,undef,undef, # 3* 244 1,1,2, 0,0,2, 0,0,3, undef,undef,undef, # 3* 248 0,0,1, 0,0,1, 3,2,2); my @max_digit = (0,1,1, 2,3,3, 2,3,3, undef,undef,undef, # 3* 0 2,2,0, 3,3,1, 3,3,1, undef,undef,undef, # 3* 4 3,3,2, 3,3,2, 1,1,0, undef,undef,undef, # 3* 8 1,3,3, 1,3,3, 0,2,2, undef,undef,undef, # 3* 12 3,3,2, 3,3,2, 1,1,0, undef,undef,undef, # 3* 16 1,3,3, 1,3,3, 0,2,2, undef,undef,undef, # 3* 20 0,1,1, 2,3,3, 2,3,3, undef,undef,undef, # 3* 24 2,2,0, 3,3,1, 3,3,1, undef,undef,undef, # 3* 28 0,2,2, 1,3,3, 1,3,3, undef,undef,undef, # 3* 32 1,1,0, 3,3,2, 3,3,2, undef,undef,undef, # 3* 36 3,3,1, 3,3,1, 2,2,0, undef,undef,undef, # 3* 40 2,3,3, 2,3,3, 0,1,1, undef,undef,undef, # 3* 44 3,3,1, 3,3,1, 2,2,0, undef,undef,undef, # 3* 48 2,3,3, 2,3,3, 0,1,1, undef,undef,undef, # 3* 52 0,2,2, 1,3,3, 1,3,3, undef,undef,undef, # 3* 56 1,1,0, 3,3,2, 3,3,2, undef,undef,undef, # 3* 60 0,3,3, 1,3,3, 1,2,2, undef,undef,undef, # 3* 64 1,1,0, 2,3,3, 2,3,3, undef,undef,undef, # 3* 68 2,2,1, 3,3,1, 3,3,0, undef,undef,undef, # 3* 72 3,3,2, 3,3,2, 0,1,1, undef,undef,undef, # 3* 76 3,3,0, 3,3,1, 2,2,1, undef,undef,undef, # 3* 80 2,3,3, 2,3,3, 1,1,0, undef,undef,undef, # 3* 84 1,2,2, 1,3,3, 0,3,3, undef,undef,undef, # 3* 88 0,1,1, 3,3,2, 3,3,2, undef,undef,undef, # 3* 92 0,3,3, 1,3,3, 1,2,2, undef,undef,undef, # 3* 96 1,1,0, 2,3,3, 2,3,3, undef,undef,undef, # 3* 100 2,2,1, 3,3,1, 3,3,0, undef,undef,undef, # 3* 104 3,3,2, 3,3,2, 0,1,1, undef,undef,undef, # 3* 108 3,3,0, 3,3,1, 2,2,1, undef,undef,undef, # 3* 112 2,3,3, 2,3,3, 1,1,0, undef,undef,undef, # 3* 116 1,2,2, 1,3,3, 0,3,3, undef,undef,undef, # 3* 120 0,1,1, 3,3,2, 3,3,2, undef,undef,undef, # 3* 124 0,3,3, 1,3,3, 1,2,2, undef,undef,undef, # 3* 128 1,1,0, 2,3,3, 2,3,3, undef,undef,undef, # 3* 132 2,2,1, 3,3,1, 3,3,0, undef,undef,undef, # 3* 136 3,3,2, 3,3,2, 0,1,1, undef,undef,undef, # 3* 140 3,3,0, 3,3,1, 2,2,1, undef,undef,undef, # 3* 144 2,3,3, 2,3,3, 1,1,0, undef,undef,undef, # 3* 148 1,2,2, 1,3,3, 0,3,3, undef,undef,undef, # 3* 152 0,1,1, 3,3,2, 3,3,2, undef,undef,undef, # 3* 156 0,3,3, 1,3,3, 1,2,2, undef,undef,undef, # 3* 160 1,1,0, 2,3,3, 2,3,3, undef,undef,undef, # 3* 164 2,2,1, 3,3,1, 3,3,0, undef,undef,undef, # 3* 168 3,3,2, 3,3,2, 0,1,1, undef,undef,undef, # 3* 172 3,3,0, 3,3,1, 2,2,1, undef,undef,undef, # 3* 176 2,3,3, 2,3,3, 1,1,0, undef,undef,undef, # 3* 180 1,2,2, 1,3,3, 0,3,3, undef,undef,undef, # 3* 184 0,1,1, 3,3,2, 3,3,2, undef,undef,undef, # 3* 188 0,3,3, 1,3,3, 1,2,2, undef,undef,undef, # 3* 192 1,1,0, 2,3,3, 2,3,3, undef,undef,undef, # 3* 196 2,2,1, 3,3,1, 3,3,0, undef,undef,undef, # 3* 200 3,3,2, 3,3,2, 0,1,1, undef,undef,undef, # 3* 204 3,3,0, 3,3,1, 2,2,1, undef,undef,undef, # 3* 208 2,3,3, 2,3,3, 1,1,0, undef,undef,undef, # 3* 212 1,2,2, 1,3,3, 0,3,3, undef,undef,undef, # 3* 216 0,1,1, 3,3,2, 3,3,2, undef,undef,undef, # 3* 220 0,3,3, 1,3,3, 1,2,2, undef,undef,undef, # 3* 224 1,1,0, 2,3,3, 2,3,3, undef,undef,undef, # 3* 228 2,2,1, 3,3,1, 3,3,0, undef,undef,undef, # 3* 232 3,3,2, 3,3,2, 0,1,1, undef,undef,undef, # 3* 236 3,3,0, 3,3,1, 2,2,1, undef,undef,undef, # 3* 240 2,3,3, 2,3,3, 1,1,0, undef,undef,undef, # 3* 244 1,2,2, 1,3,3, 0,3,3, undef,undef,undef, # 3* 248 0,1,1, 3,3,2, 3,3,2); # state length 256 in each of 4 tables # grand total 2554 # cycle 0/224 part=A1 rot=0 digit=0 <-> part=D2 rot=0 digit=0 # cycle 224/0 part=D2 rot=0 digit=0 <-> part=A1 rot=0 digit=0 # cycle 56/220 part=A2rev rot=2 digit=0 <-> part=D1rev rot=3 digit=0 # cycle 220/56 part=D1rev rot=3 digit=0 <-> part=A2rev rot=2 digit=0 # cycle 92/96 part=B1rev rot=3 digit=0 <-> part=B2 rot=0 digit=0 # cycle 96/92 part=B2 rot=0 digit=0 <-> part=B1rev rot=3 digit=0 # my %start_state = (A1 => [0, 224], D2 => [224, 0], B2 => [96, 92], B1rev => [92, 96], D1rev => [220, 56], A2rev => [56, 220], ); sub new { my $self = shift->SUPER::new (@_); my $start_shape = ($self->{'start_shape'} ||= 'A1'); # default $start_state{$start_shape} || croak "Unrecognised start_shape option: ",$start_shape; return $self; } sub n_to_xy { my ($self, $n) = @_; ### AR2W2Curve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; my @digits = digit_split_lowtohigh($int,4); my $len = ($n*0 + 2) ** scalar(@digits); # inherit possible bigint ### digits: join(', ',@digits)." count ".scalar(@digits) ### $len # $dir default if all $digit==3 my ($state,$dir) = @{$start_state{$self->{'start_shape'}}}; if ($#digits & 1) { ($state,$dir) = ($dir,$state); } ### initial ... ### $state ### $dir my $x = 0; my $y = 0; while (@digits) { $len /= 2; $state += (my $digit = pop @digits); # high to low digits if ($digit != 3) { $dir = $state; # lowest non-3 digit } ### $len ### $state ### state: state_string($state) ### digit_to_x: $digit_to_x[$state] ### digit_to_y: $digit_to_y[$state] ### next_state: $next_state[$state] $x += $len * $digit_to_x[$state]; $y += $len * $digit_to_y[$state]; $state = $next_state[$state]; } ### $dir ### frac: $n # with $n fractional part return ($n * ($digit_to_x[$dir+1] - $digit_to_x[$dir]) + $x, $n * ($digit_to_y[$dir+1] - $digit_to_y[$dir]) + $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### AR2W2Curve xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my @xdigits = bit_split_lowtohigh ($x); my @ydigits = bit_split_lowtohigh ($y); my $level = max($#xdigits,$#ydigits); my $state = $start_state{$self->{'start_shape'}}->[$level & 1]; my @ndigits; foreach my $i (reverse 0 .. max($#xdigits,$#ydigits)) { # high to low my $ndigit = $yx_to_digit[$state + 2*($ydigits[$i]||0) + ($xdigits[$i]||0)]; $ndigits[$i] = $ndigit; $state = $next_state[$state+$ndigit]; } return digit_join_lowtohigh (\@ndigits, 4, $x * 0 * $y); # bignum zero } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### AR2W2Curve rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 0) { return (1, 0); } my ($len, $level) = round_down_pow (max($x2,$y2), 2); ### len/level: "$len $level" if (is_infinite($level)) { return (0, $level); } # At this point an easy over-estimate would be # return (0, 4*$len*$len-1); my $n_min = my $n_max = my $y_min = my $y_max = my $x_min = my $x_max = 0; my $min_state = my $max_state = $start_state{$self->{'start_shape'}}->[$level & 1]; ### $x_min ### $y_min while ($level >= 0) { ### $level ### $len { my $x_cmp = $x_min + $len; my $y_cmp = $y_min + $len; my $digit = $min_digit[3*$min_state + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0)]; # my $xr = ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0); # my $yr = ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0); # ### $min_state # ### min_state: state_string($min_state) # ### $xr # ### $yr # ### $digit $n_min = 4*$n_min + $digit; $min_state += $digit; if ($digit_to_x[$min_state]) { $x_min += $len; } if ($digit_to_y[$min_state]) { $y_min += $len; } $min_state = $next_state[$min_state]; } { my $x_cmp = $x_max + $len; my $y_cmp = $y_max + $len; my $digit = $max_digit[3*$max_state + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0)]; $n_max = 4*$n_max + $digit; $max_state += $digit; if ($digit_to_x[$max_state]) { $x_max += $len; } if ($digit_to_y[$max_state]) { $y_max += $len; } $max_state = $next_state[$max_state]; } $len = int($len/2); $level--; } return ($n_min, $n_max); } #------------------------------------------------------------------------------ # levels use Math::PlanePath::HilbertCurve; *level_to_n_range = \&Math::PlanePath::HilbertCurve::level_to_n_range; *n_to_level = \&Math::PlanePath::HilbertCurve::n_to_level; #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde ie Math-PlanePath Asano Ranjan Roos Welzl Widmayer Informatics =head1 NAME Math::PlanePath::AR2W2Curve -- 2x2 self-similar curve of four patterns =head1 SYNOPSIS use Math::PlanePath::AR2W2Curve; my $path = Math::PlanePath::AR2W2Curve->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XXXXXThis is an integer version of the AR2W2 curve per =over Asano, Ranjan, Roos, Welzl and Widmayer "Space-Filling Curves and Their Use in the Design of Geometric Data Structures", Theoretical Computer Science, volume 181, issue 1, pages 3-15, July 1997. And in LATIN'95 Theoretical Informatics which is at Google Books L =back =cut # volume 181 issue 1 # http://www.sciencedirect.com/science/journal/03043975/181/1 # article # http://www.sciencedirect.com/science/article/pii/S0304397596002599 =pod It traverses the first quadrant in self-similar 2x2 blocks which are a mixture of "U" and "Z" shapes. The mixture is designed to improve some locality measures (how big the N range for a given region). | 7 42--43--44 47--48--49 62--63 \ | | | | 6 40--41 45--46 51--50 61--60 | | | 5 39 36--35--34 52 55--56 59 | | / | | | | 4 38--37 33--32 53--54 57--58 \ 3 6-- 7-- 8 10 31 28--27--26 | |/ | | | | 2 5-- 4 9 11 30--29 24--25 | | | 1 2-- 3 13--12 17--18 23--22 \ | | | | Y=0 -> 0-- 1 14--15--16 19--20--21 X=0 1 2 3 4 5 6 7 =head2 Shape Parts There's four base patterns A to D. A2 is a mirror image of A1, B2 a mirror of B1, etc. The start is A1, and above that D2, then A1 again, alternately. ^----> ^ 2---3 C1 | B2 1 3 C2 D1 | A1 \ | A2 | \ | ----> | 0---1 ^ 0 2 ^ ----> D2 | B1 |B1 B2 ---->| | 1---2 C2 B1 1---2 B2 C1 B1 | | ---->----> B2 | | ---->----> 0 3 ^ | 0 3 ^ | |D1 B2| |B1 D2| | v | v ^ \ ^ | 1---2 B1| \A1 1---2 A2/ | B2 C1 | | | v C2 | | / v 0 3 ^ | 0 3 ^ \ /A2 B2| |B1 \A1 / v | v ^ | ^ \ 1---2 A2/ | C2 1---2 C1| \A1 D1 | | / v D2 | | | v 0 3 ^ \ 0 3 ^ | |D1 \A2 /A1 D2| | v / v For parts which fill on the right such as the B1 and B2 sub-parts of A1, the numbering must be reversed. This doesn't affect the shape of the curve as such, but it matters for enumerating it as done here. =head2 Start Shape The default starting shape is the A1 "Z" part, and above it D2. Notice the starting sub-part of D2 is A1 and in turn the starting sub-part of A1 is D2, so those two alternate at successive higher levels. Their sub-parts reach all other parts (in all directions, and forward or reverse). The C $str> option can select a different starting shape. The choices are "A1" \ pair "D2" / "B2" \ pair "B1rev" / "D1rev" \ pair "A2rev" / B2 begins with a reversed B1 and in turn a B1 reverse begins with B2 (no reverse), so those two alternate. Similarly D1 reverse starts with A2 reverse, and A2 reverse starts with D1 reverse. The curve is conceived by the authors as descending into ever-smaller sub-parts and for that any of the patterns can be a top-level start. But to expand outwards as done here the starting part must be the start of the pattern above it, and that's so only for the 6 listed. The descent graph is D2rev -----> D2 <--> A1 B2rev -----> C2rev --> A1rev -----> B2 <--> B1rev <----- C2 C1rev -----> <----- A2 <-- C1 B1 -----> D1rev <--> A2rev D1 -----> So for example B1 is not at the start of anything. Or A1rev is at the start of C2rev, but then nothing starts with C2rev. Of the 16 total only the three pairs shown "E--E" are cycles and can thus extend upwards indefinitely. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::AR2W2Curve-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and largest in the rectangle. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 4**$level - 1)>. =back =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/HypotOctant.pm0000644000175000017500000003666213734026670020060 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Circle drop splash rings from # math-image --path=HypotOctant --values=DigitProductSteps,values_type=count # math-image --path=Hypot --values=DigitProduct # math-image --path=Hypot --values=DigitCount # math-image --path=Hypot --values=Modulo,modulus=1000 # http://stefan.guninski.com/oeisposter/ # # pi*r^2 - pi*(r-1)^2 = pi*(2r-1) # octant is 1/8 of that pi*(2x-1)/8 # pi*(2x-1)/8=100k # 2x-1 = 100k*8/pi # x = 100*4/pi*k # # A000328 Number of points of norm <= n^2 in square lattice. # 1, 5, 13, 29, 49, 81, 113, 149, 197, 253, 317, 377, 441, 529, 613, 709, 797 # a(n) = 1 + 4 * sum(j=0, n^2 / 4, n^2 / (4*j+1) - n^2 / (4*j+3) ) # # A057655 num points norm <= n in square lattice. # # A036702 num points |z=a+bi| <= n with 0<=a, 0<=b<=a, so octant # A036703 num points n-1 < z <= n, first diffs? package Math::PlanePath::HypotOctant; use 5.004; use strict; use Carp 'croak'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use constant parameter_info_array => [ { name => 'points', share_key => 'points_aeo', display => 'Points', type => 'enum', default => 'all', choices => ['all','even','odd'], choices_display => ['All','Even','Odd'], description => 'Which X,Y points visit, either all of them or just X+Y even or X+Y odd.', }, ]; use constant class_x_negative => 0; use constant class_y_negative => 0; sub x_minimum { my ($self) = @_; return ($self->{'points'} eq 'odd' ? 1 # odd, line X=Y not included : 0); # octant Y<=X so X-Y>=0 } # points=odd X=1,Y=0 # otherwise X=0,Y=0 *sumabsxy_minimum = \&x_minimum; *diffxy_minimum = \&x_minimum; # X>=Y so X-Y>=0 *absdiffxy_minimum = \&x_minimum; *rsquared_minimum = \&x_minimum; sub absdy_minimum { my ($self) = @_; return ($self->{'points'} eq 'all' ? 0 : 1); # never same Y } sub dir_minimum_dxdy { my ($self) = @_; return ($self->{'points'} eq 'all' ? (1,0) # all i=1 to X=1,Y=0 : (1,1)); # odd,even always at least NE } # max direction SE diagonal as anything else is at most tangent to the # eighth of a circle use constant dir_maximum_dxdy => (1,-1); # South-East #------------------------------------------------------------------------------ # my @n_to_x = (undef, 0); # my @n_to_y = (undef, 0); # my @hypot_to_n = (1); # my @y_next_x = (1, 1); # my @y_next_hypot = (1, 2); sub new { my $self = shift->SUPER::new(@_); my $points = ($self->{'points'} ||= 'all'); if ($points eq 'all') { $self->{'n_to_x'} = [undef]; $self->{'n_to_y'} = [undef]; $self->{'hypot_to_n'} = []; $self->{'y_next_x'} = [0]; $self->{'y_next_hypot'} = [0]; $self->{'x_inc'} = 1; $self->{'x_inc_factor'} = 2; $self->{'x_inc_squared'} = 1; $self->{'opposite_parity'} = -1; } elsif ($points eq 'even') { $self->{'n_to_x'} = [undef, 0]; $self->{'n_to_y'} = [undef, 0]; $self->{'hypot_to_n'} = [1]; $self->{'y_next_x'} = [2, 1]; $self->{'y_next_hypot'} = [4, 2]; $self->{'x_inc'} = 2; $self->{'x_inc_factor'} = 4; $self->{'x_inc_squared'} = 4; $self->{'opposite_parity'} = 1; } elsif ($points eq 'odd') { $self->{'n_to_x'} = [undef]; $self->{'n_to_y'} = [undef]; $self->{'hypot_to_n'} = [undef]; $self->{'y_next_x'} = [1]; $self->{'y_next_hypot'} = [1]; $self->{'x_inc'} = 2; $self->{'x_inc_factor'} = 4; $self->{'x_inc_squared'} = 4; $self->{'opposite_parity'} = 0; } else { croak "Unrecognised points option: ", $points; } return $self; } # at h=x^2+y^2 # step to (x+k)^2+y^2 # is add 2*x*k+k*k sub _extend { my ($self) = @_; ### _extend() n: scalar(@{$self->{'n_to_x'}}) my $n_to_x = $self->{'n_to_x'}; my $n_to_y = $self->{'n_to_y'}; my $hypot_to_n = $self->{'hypot_to_n'}; my $y_next_x = $self->{'y_next_x'}; my $y_next_hypot = $self->{'y_next_hypot'}; my @y = (0); my $hypot = $y_next_hypot->[0]; for (my $i = 1; $i < @$y_next_x; $i++) { if ($hypot == $y_next_hypot->[$i]) { push @y, $i; } elsif ($hypot > $y_next_hypot->[$i]) { @y = ($i); $hypot = $y_next_hypot->[$i]; } } if ($y[-1] == $#$y_next_x) { my $y = scalar(@$y_next_x); my $x = $y + ($self->{'points'} eq 'odd'); $y_next_x->[$y] = $x; $y_next_hypot->[$y] = $x*$x+$y*$y; ### assert: $y_next_hypot->[$y] == $y**2 + $y_next_x->[$y]**2 } ### store: join(' ',map{"$n_to_x->[$_],$n_to_y->[$_]"} 0 .. $#$n_to_x) ### at n: scalar(@$n_to_x) ### hypot_to_n: "h=$hypot n=".scalar(@$n_to_x) $hypot_to_n->[$hypot] = scalar(@$n_to_x); push @$n_to_y, @y; push @$n_to_x, map { my $x = $y_next_x->[$_]; $y_next_x->[$_] += $self->{'x_inc'}; $y_next_hypot->[$_] += $self->{'x_inc_factor'} * $x + $self->{'x_inc_squared'}; ### assert: $y_next_hypot->[$_] == $_**2 + $y_next_x->[$_]**2 $x } @y; # ### hypot_to_n now: join(' ',map {defined($hypot_to_n->[$_]) && "h=$_,n=$hypot_to_n->[$_]"} 0 .. $#$hypot_to_n) } sub n_to_xy { my ($self, $n) = @_; ### Hypot n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } } my $n_to_x = $self->{'n_to_x'}; my $n_to_y = $self->{'n_to_y'}; while ($n > $#$n_to_x) { _extend($self); } return ($n_to_x->[$n], $n_to_y->[$n]); } sub xy_to_n { my ($self, $x, $y) = @_; ### Hypot xy_to_n(): "$x, $y" ### hypot_to_n last: $#{$self->{'hypot_to_n'}} $x = round_nearest ($x); $y = round_nearest ($y); if ((($x%2) ^ ($y%2)) == $self->{'opposite_parity'}) { return undef; } my $hypot = $x*$x + $y*$y; if (is_infinite($hypot)) { return $hypot; } if ($x < 0 || $y < 0 || $y > $x) { ### outside first octant ... return undef; } my $hypot_to_n = $self->{'hypot_to_n'}; while ($hypot > $#$hypot_to_n) { _extend($self); } my $n_to_x = $self->{'n_to_x'}; my $n_to_y = $self->{'n_to_y'}; my $n = $hypot_to_n->[$hypot]; for (;;) { if ($x == $n_to_x->[$n] && $y == $n_to_y->[$n]) { return $n; } $n += 1; if ($n_to_x->[$n]**2 + $n_to_y->[$n]**2 != $hypot) { ### oops, hypot_to_n no good ... return undef; } } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } if ($x2 < 0 || $y2 < 0) { return (1, 0); } # circle area pi*r^2, with r^2 = $x2**2 + $y2**2 return (1, 1 + int (3.2/8 * (($x2+1)**2 + ($y2+1)**2))); } 1; __END__ =for stopwords Ryde Math-PlanePath hypot octant ie OEIS =head1 NAME Math::PlanePath::HypotOctant -- octant of points in order of hypotenuse distance =head1 SYNOPSIS use Math::PlanePath::HypotOctant; my $path = Math::PlanePath::HypotOctant->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path visits an octant of integer points X,Y in order of their distance from the origin 0,0. The points are a rising triangle 0E=YE=X, =cut # math-image --all --path=HypotOctant --output=numbers --size=60x9 =pod 8 | 61 7 | 47 54 6 | 36 43 49 5 | 27 31 38 44 4 | 18 23 28 34 39 3 | 12 15 19 24 30 37 2 | 6 9 13 17 22 29 35 1 | 3 5 8 11 16 21 26 33 Y=0 | 1 2 4 7 10 14 20 25 32 ... +--------------------------------------- X=0 1 2 3 4 5 6 7 8 For example N=11 at X=4,Y=1 is sqrt(4*4+1*1) = sqrt(17) from the origin. The next furthest from the origin is X=3,Y=3 at sqrt(18). This octant is "primitive" elements X^2+Y^2 in the sense that it excludes negative X or Y or swapped Y,X. =head2 Equal Distances Points with the same distance from the origin are taken in anti-clockwise order from the X axis, which means by increasing Y. Points with the same distance occur when there's more than one way to express a given distance as the sum of two squares. Pythagorean triples give a point on the X axis and also above. For example 5^2 == 4^2 + 3^2 has N=14 at X=5,Y=0 simply as 5^2 = 5^2 + 0 and then N=15 at X=4,Y=3 for the triple. Both are 5 away from the origin. Combinations like 20^2 + 15^2 == 24^2 + 7^2 occur too, and also with three or more different ways to have the same sum distance. =head2 Even Points Option C "even"> visits just the even points, meaning the sum X+Y even, so X,Y both even or both odd. =cut # math-image --all --path=HypotOctant,points=even --output=numbers --size=60 =pod 12 | 66 11 | points => "even" 57 10 | 49 58 9 | 40 50 8 | 32 41 51 7 | 25 34 43 6 | 20 27 35 45 5 | 15 21 29 37 4 | 10 16 22 30 39 3 | 7 11 17 24 33 2 | 4 8 13 19 28 38 1 | 2 5 9 14 23 31 Y=0 | 1 3 6 12 18 26 36 +--------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 Even points can be mapped to all points by a 45 degree rotate and flip. N=1,3,6,12,etc on the X axis here is on the X=Y diagonal of all-points. And conversely N=1,2,4,7,10,etc on the X=Y diagonal here is on the X axis of all-points. all_X = (even_X + even_Y) / 2 all_Y = (even_X - even_Y) / 2 even_X = (all_X + all_Y) even_Y = (all_X - all_Y) The sets of points with equal hypotenuse are the same in the even and all, but the flip takes them in reverse order. The first such reversal occurs at N=14 and N=15. In even-points they're at 7,1 and 5,5. In all-points they're at 5,0 and 4,3 and those two map 5,5 and 7,1, ie. the opposite way around. =head2 Odd Points Option C "odd"> visits just the odd points, meaning sum X+Y odd, so X,Y one odd the other even. =cut # math-image --all --path=HypotOctant,points=odd --output=numbers --size=60 =pod 12 | 66 11 | points => "odd" 57 10 | 47 58 9 | 39 49 8 | 32 41 51 7 | 25 33 42 6 | 20 26 35 45 5 | 14 21 29 37 4 | 10 16 22 30 40 3 | 7 11 17 24 34 2 | 4 8 13 19 28 38 1 | 2 5 9 15 23 31 Y=0 | 1 3 6 12 18 27 36 +------------------------------------------ X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 The X=Y diagonal is excluded because it has X+Y even. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::HypotOctant-Enew ()> =item C<$path = Math::PlanePath::HypotOctant-Enew (points =E $str)> Create and return a new hypot octant path object. The C option can be "all" all integer X,Y (the default) "even" only points with X+Y even "odd" only points with X+Y odd =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n E 1> the return is an empty list, it being considered the first point at X=0,Y=0 is N=1. Currently it's unspecified what happens if C<$n> is not an integer. Successive points are a fair way apart, so it may not make much sense to give an X,Y position in between the integer C<$n>. =item C<$n = $path-Exy_to_n ($x,$y)> Return an integer point number for coordinates C<$x,$y>. Each integer N is considered the centre of a unit square and an C<$x,$y> within that square returns N. =back =head1 FORMULAS The calculations are not very efficient currently. For each Y row a current X and the corresponding hypotenuse X^2+Y^2 are maintained. To find the next furthest a search through those hypotenuses is made seeking the smallest, including equal smallest, which then become the next N points. For C an array is built in the object used for repeat calculations. For C an array of hypot to N gives a the first N of given X^2+Y^2 distance. A search is then made through the next few N for the case there's more than one X,Y of that hypot. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back points="all" A024507 X^2+Y^2 of all points not on X axis or X=Y diagonal A024509 X^2+Y^2 of all points not on X axis being integers occurring as sum of two non-zero squares, with repetitions for multiple ways points="even" A036702 N on X=Y leading Diagonal being count of points norm<=k points="odd" A057653 X^2+Y^2 values occurring ie. odd numbers which are sum of two squares, without repetitions =head1 SEE ALSO L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/R5DragonCurve.pm0000644000175000017500000005163313734026657020232 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::R5DragonCurve; use 5.004; use strict; use List::Util 'first','sum'; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'round_up_pow'; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; use constant parameter_info_array => [ { name => 'arms', share_key => 'arms_4', display => 'Arms', type => 'integer', minimum => 1, maximum => 4, default => 1, width => 1, description => 'Arms', } ]; { my @x_negative_at_n = (undef, 9,5,5,6); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } { my @y_negative_at_n = (undef, 54,19,8,7); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(4, $self->{'arms'} || 1)); return $self; } sub n_to_xy { my ($self, $n) = @_; ### R5dragonCurve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } my $int = int($n); $n -= $int; # fraction part my $zero = ($n * 0); # inherit bignum 0 my $one = $zero + 1; # inherit bignum 1 my $x = 0; my $y = 0; my $sx = $zero; my $sy = $zero; # initial rotation from arm number { my $rot = _divrem_mutate ($int, $self->{'arms'}); if ($rot == 0) { $x = $n; $sx = $one; } elsif ($rot == 1) { $y = $n; $sy = $one; } elsif ($rot == 2) { $x = -$n; $sx = -$one; } else { $y = -$n; $sy = -$one; } # rot==3 } foreach my $digit (digit_split_lowtohigh($int,5)) { ### at: "$x,$y side $sx,$sy" ### $digit if ($digit == 1) { ($x,$y) = ($sx-$y, $sy+$x); # rotate +90 and offset } elsif ($digit == 2) { $x = $sx-$sy - $x; # rotate 180 and offset diag $y = $sy+$sx - $y; } elsif ($digit == 3) { ($x,$y) = (-$sy - $y, $sx + $x); # rotate +90 and offset vert } elsif ($digit == 4) { $x -= 2*$sy; # offset vert 2* $y += 2*$sx; } # add 2*(rot+90), which is multiply by (2i+1) ($sx,$sy) = ($sx - 2*$sy, $sy + 2*$sx); } ### final: "$x,$y side $sx,$sy" return ($x, $y); } my @digit_to_dir = (0,1,2,1,0); my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); my @digit_to_nextturn = (1,1,-1,-1); sub n_to_dxdy { my ($self, $n) = @_; ### R5dragonCurve n_to_dxdy(): $n if ($n < 0) { return; } my $int = int($n); $n -= $int; # fraction part if (is_infinite($int)) { return ($int, $int); } # direction from arm number my $dir = _divrem_mutate ($int, $self->{'arms'}); # plus direction from digits my @ndigits = digit_split_lowtohigh($int,5); $dir = sum($dir, map {$digit_to_dir[$_]} @ndigits) & 3; ### direction: $dir my $dx = $dir4_to_dx[$dir]; my $dy = $dir4_to_dy[$dir]; # fractional $n incorporated using next turn if ($n) { # lowest non-4 digit, or 0 if all 4s (implicit 0 above high digit) $dir += $digit_to_nextturn[ first {$_!=4} @ndigits, 0 ]; $dir &= 3; ### next direction: $dir $dx += $n*($dir4_to_dx[$dir] - $dx); $dy += $n*($dir4_to_dy[$dir] - $dy); } return ($dx, $dy); } sub xy_to_n { return scalar((shift->xy_to_n_list(@_))[0]); } sub xy_to_n_list { my ($self, $x, $y) = @_; ### R5DragonCurve xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); if (is_infinite($x)) { return $x; # infinity } if (is_infinite($y)) { return $y; # infinity } if ($x == 0 && $y == 0) { return (0 .. $self->arms_count - 1); } require Math::PlanePath::R5DragonMidpoint; my @n_list; my $xm = $x+$y; # rotate -45 and mul sqrt(2) my $ym = $y-$x; foreach my $dx (0,-1) { foreach my $dy (0,1) { my $t = $self->Math::PlanePath::R5DragonMidpoint::xy_to_n ($xm+$dx, $ym+$dy); ### try: ($xm+$dx).",".($ym+$dy) ### $t next unless defined $t; my ($tx,$ty) = $self->n_to_xy($t) or next; if ($tx == $x && $ty == $y) { ### found: $t if (@n_list && $t < $n_list[0]) { unshift @n_list, $t; } else { push @n_list, $t; } if (@n_list == 2) { return @n_list; } } } } return @n_list; } #------------------------------------------------------------------------------ # whole plane covered when arms==4 sub xy_is_visited { my ($self, $x, $y) = @_; return ($self->{'arms'} == 4 || defined($self->xy_to_n($x,$y))); } #------------------------------------------------------------------------------ # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### R5DragonCurve rect_to_n_range(): "$x1,$y1 $x2,$y2" my $xmax = int(max(abs($x1),abs($x2))) + 1; my $ymax = int(max(abs($y1),abs($y2))) + 1; return (0, ($xmax*$xmax + $ymax*$ymax) * 10 * $self->{'arms'}); } #------------------------------------------------------------------------------ { # This is a search for disallowed digit pairs going from low to high. # $state encodes the preceding digit, ie. of lower significance. Initial # $state=1 for no low digits yet. The initial no low digits skips low # digit=1 and then begins the allowing/disallowing on the first non-1 # digit. # # The digits are found by repeated _divrem_mutate() in the expectation # that with 8 out of 20 digit pairs disallowed, after stripping low 1s, we # should be able to usually answer "no" in less work than a full # digit_split_lowtohigh(), and since currently that code for base 5 is # only repeated divrems anyway. # my @table = (undef, # state prev allowed pairs # # ----- ---- ------------- [ 2, 1, 3, 4, 5 ], # 1 none [ 2, 2, 3 ], # 2 0 00, 20 [ 2, 3, undef, undef, 5 ], # 3 2 02, 42 [ 2, 4, undef, undef, 5 ], # 4 3 03, 43 [ 2, 5, undef, undef, 5 ], # 5 4 04, 44 ); sub _UNDOCUMENTED__n_segment_is_right_boundary { my ($self, $n) = @_; if (is_infinite($n)) { return 0; } unless ($n >= 0) { return 0; } $n = int($n); my $state = 1; while ($n) { my $digit = _divrem_mutate($n,5); # low to high $state = $table[$state][$digit] || return 0; } return 1; } sub _UNDOCUMENTED__n_segment_is_left_boundary { my ($self, $n, $level) = @_; ### _UNDOCUMENTED__n_segment_is_left_boundary(): $n if (is_infinite($n)) { return 0; } unless ($n >= 0) { return 0; } $n = int($n); my $state = 1; while ($n) { if (defined $level && ($level -= 1) < 0) { ### stop at level: "state=$state" if ($n) { ### N >= 5**$level ... return undef; } last; # return 1; # return ($state == 2); } my $digit = 4 - _divrem_mutate($n,5); # low to high $state = $table[$state][$digit] || return 0; } ### final state: $state if (defined $level) { if ($level > 0) { return ($state != 2); } else { return 1; } } return ($state != 2); # my @table # # 0 1 2 3 4 digit # = (undef, # [ 4, 3, 2, 1, 1 ], # 1 L -> ZYXLL # [undef,undef,undef, 2, 1 ], # 2 X -> ___XL # [undef,undef,undef, 3 ], # 3 Y -> ___Y_ # [ 4, 3, 2, 4 ], # 4 Z -> ZYXX_ # ); # my $state = 4; # foreach my $digit (reverse digit_split_lowtohigh($n,5)) { # high to low # $state = $table[$state][$digit] || return 0; # } # return 1; } } #----------------------------------------------------------------------------- # level_to_n_range() sub level_to_n_range { my ($self, $level) = @_; return (0, (5**$level + 1) * $self->{'arms'} - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, $self->{'arms'}); my ($pow, $exp) = round_up_pow ($n, 5); return $exp; } #----------------------------------------------------------------------------- 1; __END__ =for stopwords eg Ryde Dragon Math-PlanePath Nlevel et al vertices doublings OEIS Online terdragon ie morphism R5DragonMidpoint radix Jorg Arndt Arndt's fxtbook PlanePath min xy TerdragonCurve arctan gt lt undef diff abs dX dY characterization DDUU =head1 NAME Math::PlanePath::R5DragonCurve -- radix 5 dragon curve =head1 SYNOPSIS use Math::PlanePath::R5DragonCurve; my $path = Math::PlanePath::R5DragonCurve->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path is a "DDUU" turn pattern similar in nature to the terdragon but on a square grid and with 5 segments instead of 3. 31-----30 27-----26 5 | | | | 32---29/33--28/24----25 4 | | 35---34/38--39/23----22 11-----10 7------6 3 | | | | | | | 36---37/41--20/40--21/17--16/12---13/9----8/4-----5 2 | | | | | | --50 47---42/46--19/43----18 15-----14 3------2 1 | | | | | 49/53--48/64 45/65--44/68 69 0------1 <-Y=0 ^ ^ ^ ^ ^ ^ ^ ^ ^ -7 -6 -5 -4 -3 -2 -1 X=0 1 The name "R5" is by Jorg Arndt. The base figure is an "S" shape 4----5 | 3----2 | 0----1 which then repeats in self-similar style, so N=5 to N=10 is a copy rotated +90 degrees, as per the direction of the N=1 to N=2 segment. 10 7----6 | | | <- repeat rotated +90 9---8,4---5 | 3----2 | 0----1 Like the terdragon there are no reversals or mirroring. Each replication is the plain base curve. The shape of N=0,5,10,15,20,25 repeats the initial N=0 to N=5, 25 4 / / 10__ 3 / / ----___ 20__ / 5 2 ----__ / / 15 / 1 / 0 <-Y=0 ^ ^ ^ ^ ^ ^ -4 -3 -2 -1 X=0 1 The curve never crosses itself. The vertices touch at corners like N=4 and N=8 above, but no edges repeat. =head2 Spiralling The first step N=1 is to the right along the X axis and the path then slowly spirals anti-clockwise and progressively fatter. The end of each replication is Nlevel = 5^level Each such point is at arctan(2/1)=63.43 degrees further around from the previous, Nlevel X,Y angle (degrees) ------ ----- ----- 1 1,0 0 5 2,1 63.4 25 -3,4 2*63.4 = 126.8 125 -11,-2 3*63.4 = 190.3 =head2 Arms The curve fills a quarter of the plane and four copies mesh together perfectly rotated by 90, 180 and 270 degrees. The C parameter can choose 1 to 4 such curve arms successively advancing. C 4> begins as follows. N=0,4,8,12,16,etc is the first arm (the same shape as the plain curve above), then N=1,5,9,13,17 the second, N=2,6,10,14 the third, etc. arms => 4 16/32---20/63 | 21/60 9/56----5/12----8/59 | | | | 17/33--- 6/13--0/1/2/3---4/15---19/35 | | | | 10/57----7/14---11/58 23/62 | 22/61---18/34 With four arms every X,Y point is visited twice, except the origin 0,0 where all four begin. Every edge between the points is traversed once. =head2 Tiling The little "S" shapes of the N=0to5 base shape tile the plane with 2x1 bricks and 1x1 holes in the following pattern, +--+-----| |--+--+-----| |--+--+--- | | | | | | | | | | | |-----+-----| |-----+-----| |--- | | | | | | | | | | | +-----| |-----+-----| |-----+-----+ | | | | | | | | | | +-----+-----| |-----+-----| |-----+ | | | | | | | | | | | ---| |-----+-----| |-----+-----| | | | | | | | | | | | ---+-----| |-----o-----| |-----+--- | | | | | | | | | | | |-----+-----| |-----+-----| |--- | | | | | | | | | | | +-----| |-----+-----| |-----+-----+ | | | | | | | | | | +-----+-----| |-----+-----| |-----+ | | | | | | | | | | | ---| |-----+-----| |-----+-----| | | | | | | | | | | | ---+--+--| |-----+--+--| |-----+--+ This is the curve with each segment N=2mod5 to N=3mod5 omitted. A 2x1 block has 6 edges but the "S" traverses just 4 of them. The way the blocks mesh meshes together mean the other 2 edges are traversed by another brick, possibly a brick on another arm of the curve. This tiling is also found for example at =over L Or with enlarged square part, L =back =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::R5DragonCurve-Enew ()> =item C<$path = Math::PlanePath::R5DragonCurve-Enew (arms =E 4)> Create and return a new path object. The optional C parameter can make 1 to 4 copies of the curve, each arm successively advancing. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional C<$n> gives an X,Y position along a straight line between the integer positions. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. If there's nothing at C<$x,$y> then return C. The curve can visit an C<$x,$y> twice. The smallest of the these N values is returned. =item C<@n_list = $path-Exy_to_n_list ($x,$y)> Return a list of N point numbers for coordinates C<$x,$y>. The origin 0,0 has C many N since it's the starting point for each arm. Other points have up to two Ns for a given C<$x,$y>. If arms=4 then every C<$x,$y> except the origin has exactly two Ns. =item C<$n = $path-En_start()> Return 0, the first N in the path. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 5**$level)>, or for multiple arms return C<(0, $arms * 5**$level + ($arms-1))>. There are 5^level segments in a curve level, so 5^level+1 points numbered from 0. For multiple arms there are arms*(5^level+1) points, numbered from 0 so n_hi = arms*(5^level+1)-1. =back =head1 FORMULAS Various formulas for boundary length, area, and more, can be found in the author's mathematical write-up =over L =back =head2 Turn XXAt each point N the curve always turns 90 degrees either to the left or right, it never goes straight ahead. As per the code in Jorg Arndt's fxtbook, if N is written in base 5 then the lowest non-zero digit gives the turn lowest non-0 digit turn ------------------ ---- 1 left 2 left 3 right 4 right At a point N=digit*5^level for digit=1,2,3,4 the turn follows the shape at that digit, so two lefts then two rights, 4*5^k----5^(k+1) | | 2*5^k----2*5^k | | 0------1*5^k The first and last unit segments in each level are the same direction, so at those endpoints it's the next level up which gives the turn. =head2 Next Turn The turn at N+1 can be calculated in a similar way but from the lowest non-4 digit. lowest non-4 digit turn ------------------ ---- 0 left 1 left 2 right 3 right This works simply because in N=...z444 becomes N+1=...(z+1)000 and so the turn at N+1 is given by digit z+1. =head2 Total Turn The direction at N, ie. the total cumulative turn, is given by the direction of each digit when N is written in base 5, digit direction 0 0 1 1 2 2 3 1 4 0 direction = (sum direction for each digit) * 90 degrees For example N=13 in base 5 is "23" so digit=2 direction=2 plus digit=3 direction=1 gives direction=(2+1)*90 = 270 degrees, ie. south. Because there's no reversals etc in the replications there's no state to maintain when considering the digits, just a plain sum of direction for each digit. =head1 OEIS The R5 dragon is in Sloane's Online Encyclopedia of Integer Sequences as, =over L (etc) =back A175337 next turn 0=left,1=right (n=0 is the turn at N=1) A006495 level end X, Re(b^k) A006496 level end Y, Re(b^k) A079004 boundary length N=0 to 5^k, skip initial 7,10 being 4*3^k - 2 A048473 boundary/2 (one side), N=0 to 5^k being half whole, 2*3^n - 1 A198859 boundary/2 (one side), N=0 to 25^k being even levels, 2*9^n - 1 A198963 boundary/2 (one side), N=0 to 5*25^k being odd levels, 6*9^n - 1 A052919,A100702 U part boundary length, N=0 to 5^k A007798 1/2 * area enclosed N=0 to 5^k A016209 1/4 * area enclosed N=0 to 5^k A005058 1/2 * new area N=5^k to N=5^(k+1) being area increments, 5^n - 3^n A005059 1/4 * new area N=5^k to N=5^(k+1) being area increments, (5^n - 3^n)/2 A125831 N middle segment of level k, (5^k-1)/2 A008776 count single-visited points N=0 to 5^k, being 2*3^k A146086 count visited points N=0 to 5^k A024024 C[k] boundary lengths, 3^k-k A104743 E[k] boundary lengths, 3^k+k A135518 1/4 * sum distinct abs(n-other(n)) in level N=0 to 5^k arms=1 and arms=3 A059841 abs(dX), being simply 1,0 repeating A000035 abs(dY), being simply 0,1 repeating arms=4 A165211 abs(dY), being 0,1,0,1,1,0,1,0 repeating =head1 HOUSE OF GRAPHS House of Graphs entries for the R5 dragon curve as a graph include =over L etc =back 19655 level=0 (1-segment path) 568 level=1 (5-segment path) 25149 level=2 25147 level=3 =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/OctagramSpiral.pm0000644000175000017500000002663213734026667020516 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # ENHANCE-ME: n_to_xy() might be done with some rotates etc around its # symmetry instead of 8 or 16 cases. # package Math::PlanePath::OctagramSpiral; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use constant xy_is_visited => 1; use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; *_UNDOCUMENTED__dxdy_list = \&Math::PlanePath::_UNDOCUMENTED__dxdy_list_eight; sub x_negative_at_n { my ($self) = @_; return $self->n_start + 6; } sub y_negative_at_n { my ($self) = @_; return $self->n_start + 10; } sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + 8; } use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } sub n_to_xy { my ($self, $n) = @_; #### OctagramSpiral n_to_xy: $n # adjust to N=1 at origin X=0,Y=0 $n = $n - $self->{'n_start'} + 1; if ($n <= 2) { if ($n < 1) { return; } else { return ($n-1, 0); } } # sqrt() done in integers to avoid limited precision from Math::BigRat sqrt() # my $d = int ((_sqrtint(32*$n+17) + 7) / 16); #### d frac: ((sqrt(32*$n+17) + 7) / 16) #### $d #### base: ((8*$d - 7)*$d + 1) $n -= ((8*$d - 7)*$d + 1); #### remainder: $n if ($n < $d) { return ($d + $n, $n); } $n -= 2*$d; if ($n < $d) { if ($n < 0) { return (- $n + $d, $d); } else { return ($d, $n + $d); } } $n -= 2*$d; if ($n < $d) { return (-$n, abs($n) + $d); } $n -= 2*$d; if ($n < $d) { if ($n < 0) { return (-$d, -$n + $d); } else { return (-$n - $d, $d); } } $n -= 2*$d; if ($n < $d) { return (-$d-abs($n), -$n); } $n -= 2*$d; if ($n < $d) { if ($n < 0) { return (-$d + $n, -$d); } else { return (-$d, -$d - $n); } } $n -= 2*$d; if ($n < $d) { return ($n, - abs($n) - $d); } $n -= 2*$d; if ($n < $d+1) { if ($n < 0) { return ($d, $n - $d); } else { return ($n + $d, -$d); } } # $n >= $d+1 through to 2*$d+1 return (-$n + 3*$d+2, $n - 2*$d-1); } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### xy_to_n: "x=$x, y=$y" my $n; if ($x > 0 && $y < 0 && -2*$y < $x) { ### longer bottom right horiz $x--; $n = 1; } else { $n = 0; } my $d_offset = 0; if ($y < 0) { $y = -$y; $x = -$x; $d_offset = 8; ### rotate 180 back: "$x, $y" } if ($x < 0) { ($x, $y) = ($y, -$x); $d_offset += 4; ### rotate 90 back: "$x, $y" } if ($y > $x) { ($x, $y) = ($y, $y-$x); $d_offset += 2; ### rotate 45 back: "$x, $y" } my $d; if (2*$y < $x) { ### diag up $d = $x - $y; $n += $y; } else { ### horiz back $d = $y; $n -= $x; $d_offset += 3; } ### final ### $d ### $d_offset ### $n # horiz base 2,19,54,... return $n + (8*$d - 7 + $d_offset)*$d + $self->{'n_start'}; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; my $d = max (1, map {abs(round_nearest($_))} $x1,$y1,$x2,$y2); ### $d # ENHANCE-ME: find actual minimum if rect doesn't cover 0,0 return ($self->{'n_start'}, # bottom-right inner corner 16,47,94,... $self->{'n_start'} + (8*$d + 7)*$d); } 1; __END__ # 29 25 4 # | \ / | # 30 28 26 24 ...-56--55 3 # | \ / | / # 33--32--31 7 27 5 23--22--21 54 2 # \ | \ / | / / # 34 9-- 8 6 4-- 3 20 53 1 # \ \ / / / # 35 10 1---2 19 52 <- Y=0 # / / \ \ # 36 11--12 14 16--17--18 51 -1 # / | / \ | \ # 37--38--39 13 43 15 47--48--49--50 -2 # | / \ | # 40 42 44 46 -3 # | / \ | # 41 45 -4 # # ^ # -4 -3 -2 -1 X=0 1 2 3 4 5 ... # # # # # # # # # # 28 24 4 # | \ / | # 29 27 25 23 ...-54--53 3 # | \ / | / # 32--31--30 7 26 5 22--21--20 52 2 # \ | \ / | / / # 33 9-- 8 6 4-- 3 19 51 1 # \ \ / / / # 34 10 1---2 18 50 <- Y=0 # / / | | # 35 11--12 14 16--17 49 -1 # / | / \ | \ # 36--37--38 13 42 15 46--47--48 -2 # | / \ | # 39 41 43 45 -3 # | / \ | # 40 44 -4 # # ^ # -4 -3 -2 -1 X=0 1 2 3 4 5 ... =for stopwords Ryde Math-PlanePath octagram 18-gonal OEIS =head1 NAME Math::PlanePath::OctagramSpiral -- integer points drawn around an octagram =head1 SYNOPSIS use Math::PlanePath::OctagramSpiral; my $path = Math::PlanePath::OctagramSpiral->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes a spiral around an octagram (8-pointed star), 29 25 4 | \ / | 30 28 26 24 ...56-55 3 | \ / | / 33-32-31 7 27 5 23-22-21 54 2 \ |\ / | / / 34 9- 8 6 4- 3 20 53 1 \ \ / / / 35 10 1--2 19 52 <- Y=0 / / \ \ 36 11-12 14 16-17-18 51 -1 / |/ \ | \ 37-38-39 13 43 15 47-48-49-50 -2 | / \ | 40 42 44 46 -3 |/ \ | 41 45 -4 ^ -4 -3 -2 -1 X=0 1 2 3 4 5 ... Each loop is 16 longer than the previous. The 18-gonal numbers 18,51,100,etc fall on the horizontal at Y=-1. The inner corners like 23, 31, 39, 47 are similar to the C path, but instead of going directly between them the octagram takes a detour out to make the points of the star. Those excursions make each loops 8 longer (1 per excursion), hence a step of 16 here as compared to 8 for the C. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same pattern. For example to start at 0, =cut # math-image --path=OctagramSpiral,n_start=0 --expression='i<=55?i:0' --output=numbers --size=80x13 =pod n_start => 0 28 24 29 27 25 23 ... 55 54 32 31 30 6 26 4 22 21 20 53 33 8 7 5 3 2 19 52 34 9 0 1 18 51 35 10 11 13 15 16 17 50 36 37 38 12 42 14 46 47 48 49 39 41 43 45 40 44 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::OctagramSpiral-Enew ()> Create and return a new octagram spiral object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n < 1> the return is an empty list, it being considered the path starts at 1. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each N in the path as centred in a square of side 1, so the entire plane is covered. =back =head1 FORMULAS =head2 X,Y to N The symmetry of the octagram can be used by rotating a given X,Y back to the first star excursion such as N=19 to N=23. If Y is negative then rotate back by 180 degrees, then if X is negative rotate back by 90, and if Y>=X then by a further 45 degrees. Each such rotation, if needed, is counted as a multiple of the side-length to be added to the final N. For example at N=19 the side length is 2. Rotating by 180 degrees is 8 side lengths, by 90 degrees 4 sides, and by 45 degrees is 2 sides. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back n_start=1 (the default) A125201 N on X axis, from X=1 onwards, 18-gonals + 1 A194268 N on diagonal South-East n_start=0 A051870 N on X axis, 18-gonal numbers A139273 N on Y axis A139275 N on X negative axis A139277 N on Y negative axis A139272 N on diagonal X=Y A139274 N on diagonal North-West A139276 N on diagonal South-West A139278 N on diagonal South-East, second 18-gonals =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/KochelCurve.pm0000644000175000017500000005005413734026667020013 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::KochelCurve; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; #------------------------------------------------------------------------------ # tables generated by tools/kochel-curve-table.pl # my @next_state = (63,72, 9, 99, 0,90, 36,99, 0, # 0 36,81,18, 72, 9,99, 45,72, 9, # 9 45,90,27, 81,18,72, 54,81,18, # 18 54,99, 0, 90,27,81, 63,90,27, # 27 36,81, 0, 72,36,81, 45,90,27, # 36 45,90, 9, 81,45,90, 54,99, 0, # 45 54,99,18, 90,54,99, 63,72, 9, # 54 63,72,27, 99,63,72, 36,81,18, # 63 63,72, 9, 99,90,99, 63,72, 9, # 72 36,81,18, 72,99,72, 36,81,18, # 81 45,90,27, 81,72,81, 45,90,27, # 90 54,99, 0, 90,81,90, 54,99, 0); # 99 my @digit_to_x = (0,0,0, 1,2,2, 1,1,2, # 0 2,1,0, 0,0,1, 1,2,2, # 9 2,2,2, 1,0,0, 1,1,0, # 18 0,1,2, 2,2,1, 1,0,0, # 27 2,1,1, 2,2,1, 0,0,0, # 36 2,2,1, 1,0,0, 0,1,2, # 45 0,1,1, 0,0,1, 2,2,2, # 54 0,0,1, 1,2,2, 2,1,0, # 63 0,0,0, 1,1,1, 2,2,2, # 72 2,1,0, 0,1,2, 2,1,0, # 81 2,2,2, 1,1,1, 0,0,0, # 90 0,1,2, 2,1,0, 0,1,2); # 99 my @digit_to_y = (0,1,2, 2,2,1, 1,0,0, # 0 0,0,0, 1,2,2, 1,1,2, # 9 2,1,0, 0,0,1, 1,2,2, # 18 2,2,2, 1,0,0, 1,1,0, # 27 0,0,1, 1,2,2, 2,1,0, # 36 2,1,1, 2,2,1, 0,0,0, # 45 2,2,1, 1,0,0, 0,1,2, # 54 0,1,1, 0,0,1, 2,2,2, # 63 0,1,2, 2,1,0, 0,1,2, # 72 0,0,0, 1,1,1, 2,2,2, # 81 2,1,0, 0,1,2, 2,1,0, # 90 2,2,2, 1,1,1, 0,0,0); # 99 my @xy_to_digit = (0,1,2, 7,6,3, 8,5,4, # 0 2,3,4, 1,6,5, 0,7,8, # 9 4,5,8, 3,6,7, 2,1,0, # 18 8,7,0, 5,6,1, 4,3,2, # 27 8,7,6, 1,2,5, 0,3,4, # 36 6,5,4, 7,2,3, 8,1,0, # 45 4,3,0, 5,2,1, 6,7,8, # 54 0,1,8, 3,2,7, 4,5,6, # 63 0,1,2, 5,4,3, 6,7,8, # 72 2,3,8, 1,4,7, 0,5,6, # 81 8,7,6, 3,4,5, 2,1,0, # 90 6,5,0, 7,4,1, 8,3,2); # 99 my @min_digit = (0,0,0,7,8,7, # 0 0,0,0,5,5,6, 0,0,0,3,4,3, 1,1,1,3,4,3, 2,2,2,3,4,3, 1,1,1,5,5,6, 2,1,0,0,0,1, # 36 2,1,0,0,0,1, 2,1,0,0,0,1, 3,3,3,5,7,5, 4,4,4,5,8,5, 3,3,3,6,7,6, 4,3,2,2,2,3, # 72 4,3,1,1,1,3, 4,3,0,0,0,3, 5,5,0,0,0,6, 8,7,0,0,0,7, 5,5,1,1,1,6, 8,5,4,4,4,5, # 108 7,5,3,3,3,5, 0,0,0,1,2,1, 0,0,0,1,2,1, 0,0,0,1,2,1, 7,6,3,3,3,6, 8,1,0,0,0,1, # 144 7,1,0,0,0,1, 6,1,0,0,0,1, 6,2,2,2,3,2, 6,5,4,4,4,5, 7,2,2,2,3,2, 6,6,6,7,8,7, # 180 5,2,1,1,1,2, 4,2,0,0,0,2, 4,2,0,0,0,2, 4,3,0,0,0,3, 5,2,1,1,1,2, 4,4,4,5,6,5, # 216 3,2,2,2,6,2, 0,0,0,1,6,1, 0,0,0,1,7,1, 0,0,0,1,8,1, 3,2,2,2,7,2, 0,0,0,3,4,3, # 252 0,0,0,2,4,2, 0,0,0,2,4,2, 1,1,1,2,5,2, 8,7,6,6,6,7, 1,1,1,2,5,2, 0,0,0,5,6,5, # 288 0,0,0,4,6,4, 0,0,0,3,6,3, 1,1,1,3,7,3, 2,2,2,3,8,3, 1,1,1,4,7,4, 2,1,0,0,0,1, # 324 2,1,0,0,0,1, 2,1,0,0,0,1, 3,3,3,4,5,4, 8,7,6,6,6,7, 3,3,3,4,5,4, 8,3,2,2,2,3, # 360 7,3,1,1,1,3, 6,3,0,0,0,3, 6,4,0,0,0,4, 6,5,0,0,0,5, 7,4,1,1,1,4, 6,6,6,7,8,7, # 396 5,4,3,3,3,4, 0,0,0,1,2,1, 0,0,0,1,2,1, 0,0,0,1,2,1, 5,4,3,3,3,4); my @max_digit = (0,7,8,8,8,7, # 0 1,7,8,8,8,7, 2,7,8,8,8,7, 2,6,6,6,5,6, 2,3,4,4,4,3, 1,6,6,6,5,6, 2,2,2,1,0,1, # 36 3,6,7,7,7,6, 4,6,8,8,8,6, 4,6,8,8,8,6, 4,5,8,8,8,5, 3,6,7,7,7,6, 4,4,4,3,2,3, # 72 5,6,6,6,2,6, 8,8,8,7,2,7, 8,8,8,7,1,7, 8,8,8,7,0,7, 5,6,6,6,1,6, 8,8,8,5,4,5, # 108 8,8,8,6,4,6, 8,8,8,6,4,6, 7,7,7,6,3,6, 0,1,2,2,2,1, 7,7,7,6,3,6, 8,8,8,1,0,1, # 144 8,8,8,3,3,2, 8,8,8,5,4,5, 7,7,7,5,4,5, 6,6,6,5,4,5, 7,7,7,3,3,2, 6,7,8,8,8,7, # 180 6,7,8,8,8,7, 6,7,8,8,8,7, 5,5,5,3,1,3, 4,4,4,3,0,3, 5,5,5,2,1,2, 4,5,6,6,6,5, # 216 4,5,7,7,7,5, 4,5,8,8,8,5, 3,3,8,8,8,2, 0,1,8,8,8,1, 3,3,7,7,7,2, 0,3,4,4,4,3, # 252 1,3,5,5,5,3, 8,8,8,7,6,7, 8,8,8,7,6,7, 8,8,8,7,6,7, 1,2,5,5,5,2, 0,5,6,6,6,5, # 288 1,5,7,7,7,5, 2,5,8,8,8,5, 2,4,8,8,8,4, 2,3,8,8,8,3, 1,4,7,7,7,4, 2,2,2,1,0,1, # 324 3,4,5,5,5,4, 8,8,8,7,6,7, 8,8,8,7,6,7, 8,8,8,7,6,7, 3,4,5,5,5,4, 8,8,8,3,2,3, # 360 8,8,8,4,2,4, 8,8,8,5,2,5, 7,7,7,5,1,5, 6,6,6,5,0,5, 7,7,7,4,1,4, 6,7,8,8,8,7, # 396 6,7,8,8,8,7, 6,7,8,8,8,7, 5,5,5,4,3,4, 0,1,2,2,2,1, 5,5,5,4,3,4); # state length 108 in each of 4 tables sub n_to_xy { my ($self, $n) = @_; ### KochelCurve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; # remaining fraction, preserve possible BigFloat/BigRat my @digits = digit_split_lowtohigh($int,9); my $len = ($int*0 + 3) ** scalar(@digits); # inherit bignum ### digits: join(', ',@digits)." count ".scalar(@digits) ### $len my $state = 63; my $dir = 1; # default if all $digit==8 my $x = 0; my $y = 0; while (@digits) { $len /= 3; $state += (my $digit = pop @digits); if ($digit != 8) { $dir = $state; # lowest non-8 digit } ### $len ### $state ### digit_to_x: $digit_to_x[$state] ### digit_to_y: $digit_to_y[$state] ### next_state: $next_state[$state] $x += $len * $digit_to_x[$state]; $y += $len * $digit_to_y[$state]; $state = $next_state[$state]; } ### $dir ### frac: $n # with $n fractional part return ($n * ($digit_to_x[$dir+1] - $digit_to_x[$dir]) + $x, $n * ($digit_to_y[$dir+1] - $digit_to_y[$dir]) + $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### KochelCurve xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my @xdigits = digit_split_lowtohigh ($x, 3); my @ydigits = digit_split_lowtohigh ($y, 3); my $state = 63; my @ndigits; foreach my $i (reverse 0 .. max($#xdigits,$#ydigits)) { # high to low my $ndigit = $xy_to_digit[$state + 3*($xdigits[$i]||0) + ($ydigits[$i]||0)]; $ndigits[$i] = $ndigit; $state = $next_state[$state+$ndigit]; } return digit_join_lowtohigh (\@ndigits, 9, $x * 0 * $y); # bignum zero } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### KochelCurve rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 0) { return (1, 0); } my ($len, $level) = round_down_pow (max($x2,$y2), 3); ### $len ### $level if (is_infinite($level)) { return (0, $level); } # At this point an easy round-up range here would be: # return (0, 9*$len*$len-1); my $n_min = my $n_max = my $x_min = my $y_min = my $x_max = my $y_max = ($x1 * 0 * $x2 * $y1 * $y2); # inherit bignum 0 my $min_state = my $max_state = 63; # x__ 0 # xx_ 1 # xxx 2 # _xx 3 # __x 4 # _x_ 5 # while ($level >= 0) { my $l2 = 2*$len; { my $x_cmp1 = $x_min + $len; my $y_cmp1 = $y_min + $len; my $x_cmp2 = $x_min + $l2; my $y_cmp2 = $y_min + $l2; my $digit = $min_digit[4*$min_state # 4*9=36 apart + ($x1 >= $x_cmp2 ? 4 : $x1 >= $x_cmp1 ? ($x2 < $x_cmp2 ? 5 : 3) : ($x2 < $x_cmp1 ? 0 : $x2 < $x_cmp2 ? 1 : 2)) + ($y1 >= $y_cmp2 ? 6*4 : $y1 >= $y_cmp1 ? ($y2 < $y_cmp2 ? 6*5 : 6*3) : ($y2 < $y_cmp1 ? 6*0 : $y2 < $y_cmp2 ? 6*1 : 6*2))]; # my $key = 4*$min_state # 4*9=36 apart # + ($x1 >= $x_cmp2 ? 4 # : $x1 >= $x_cmp1 ? ($x2 < $x_cmp2 ? 5 : 3) # : ($x2 < $x_cmp1 ? 0 # : $x2 < $x_cmp2 ? 1 : 2)) # + ($y1 >= $y_cmp2 ? 6*4 # : $y1 >= $y_cmp1 ? ($y2 < $y_cmp2 ? 6*5 : 6*3) # : ($y2 < $y_cmp1 ? 6*0 # : $y2 < $y_cmp2 ? 6*1 : 6*2)); # ### $min_state # ### $len # ### $l2 # ### $key # ### $x_cmp1 # ### $x_cmp2 # ### $digit $n_min = 9*$n_min + $digit; $min_state += $digit; $x_min += $len * $digit_to_x[$min_state]; $y_min += $len * $digit_to_y[$min_state]; $min_state = $next_state[$min_state]; } { my $x_cmp1 = $x_max + $len; my $y_cmp1 = $y_max + $len; my $x_cmp2 = $x_max + $l2; my $y_cmp2 = $y_max + $l2; my $digit = $max_digit[4*$max_state # 4*9=36 apart + ($x1 >= $x_cmp2 ? 4 : $x1 >= $x_cmp1 ? ($x2 < $x_cmp2 ? 5 : 3) : ($x2 < $x_cmp1 ? 0 : $x2 < $x_cmp2 ? 1 : 2)) + ($y1 >= $y_cmp2 ? 6*4 : $y1 >= $y_cmp1 ? ($y2 < $y_cmp2 ? 6*5 : 6*3) : ($y2 < $y_cmp1 ? 6*0 : $y2 < $y_cmp2 ? 6*1 : 6*2))]; # my $key = 4*$max_state # 4*9=36 apart # + ($x1 >= $x_cmp2 ? 4 # : $x1 >= $x_cmp1 ? ($x2 < $x_cmp2 ? 5 : 3) # : ($x2 < $x_cmp1 ? 0 # : $x2 < $x_cmp2 ? 1 : 2)) # + ($y1 >= $y_cmp2 ? 4 # : $y1 >= $y_cmp1 ? ($y2 < $y_cmp2 ? 5 : 3) # : ($y2 < $y_cmp1 ? 0 # : $y2 < $y_cmp2 ? 1 : 2)); # ### $max_state # ### $len # ### $l2 # ### $x_key # ### $key # ### $x_max # ### $y_max # ### $x_cmp1 # ### $x_cmp2 # ### $y_cmp1 # ### $y_cmp2 # ### $digit # ### max digit: $max_digit[$key] $n_max = 9*$n_max + $digit; $max_state += $digit; $x_max += $len * $digit_to_x[$max_state]; $y_max += $len * $digit_to_y[$max_state]; $max_state = $next_state[$max_state]; } $len = int($len/3); $level--; } return ($n_min, $n_max); } #----------------------------------------------------------------------------- # level_to_n_range() use Math::PlanePath::SquareReplicate; *level_to_n_range = \&Math::PlanePath::SquareReplicate::level_to_n_range; *n_to_level = \&Math::PlanePath::SquareReplicate::n_to_level; #----------------------------------------------------------------------------- 1; __END__ =for stopwords eg Ryde ie Math-PlanePath Haverkort Haverkort's Rrev Frev Kochel Tilings =head1 NAME Math::PlanePath::KochelCurve -- 3x3 self-similar R and F =head1 SYNOPSIS use Math::PlanePath::KochelCurve; my $path = Math::PlanePath::KochelCurve->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is an integer version of the Kochel curve by =over Herman Haverkort, "Recursive Tilings and Space-Filling Curves with Little Fragmentation", Journal of Computational Geometry, volume 2, number 1, 2011, pages 92-127. L, L, L L (slides), L (short form) =back It fills the first quadrant in a 3x3 self-similar pattern made from two base shapes. =cut # math-image --path=KochelCurve --all --output=numbers_dash =pod | 8 80--79 72--71--70--69 60--59--58 | | | | | 7 77--78 73 66--67--68 61 56--57 | | | | | 6 76--75--74 65--64--63--62 55--54 | 5 11--12 17--18--19--20 47--48 53 | | | | | | | 4 10 13 16 25--24 21 46 49 52 | | | | | | | | | 3 9 14--15 26 23--22 45 50--51 | | | 2 8-- 7-- 6 27--28--29 44--43--42 | | | 1 1-- 2 5 32--31--30 37--38 41 | | | | | | | Y=0-> 0 3-- 4 33--34--35--36 39--40 X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 The base shapes are an "R" and an "F". The R goes along an edge, the F goes diagonally across. R pattern F pattern ^ +------+-----+-----+ +------+-----+----|+ |2 | |3\ |4 | |2 | |3\ |8 || | R | | F | R | | R | | F | R || | | | \ |-----| | | | \ | || +------+-----+-----+ +------+-----+-----+ |1 / |6 |5 / | |1 / |4 / |7 / | | F | Rrev| F | | F | F | F | | / |-----| / | | / | / | / | +------+-----+-----+ +------+-----+-----+ |0| |7\ |8 | |0| |5\ ||6 | | |Rrev| F | R | | |Rrev| F ||Rrev| | o | \ |------> | o | \ || | +------+-----+-----+ +------+-----+-----+ "Rrev" means the R pattern followed in reverse, which is +------+-----+-----+ |8<----|7\ |6 | Rrev pattern | R | F | Rrev| | | \ |-----| turned -90 degrees +------+-----+-----+ so as to start at |1 / ||2 |5 / | bottom left | F || R | F | | / || | / | +------+-----+-----+ |0| |3\ ||4 | | |Rrev| F ||Rrev| | o | \ || | +------+-----+-----+ The F pattern is symmetric, the same forward or reverse, including its sub-parts taken in reverse, so there's no separate "Frev" pattern. The initial N=0 to N=8 is the Rrev turned -90, then N=9 to N=17 is the F shape. The next higher level N=0,N=9,N=18 to N=72 is the Rrev too, as is any N=9^k to N=8*9^k. =head2 Fractal The curve is conceived by Haverkort for filling a unit square by descending into ever-smaller replacements, similar to other space-filling curves. For that, the top-level can be any of the patterns. But for the outward expanding version here, the starting pattern must occur at the start of its next higher level, which means Rrev is the only choice as it's the only start in any of the three patterns. All the patterns can be found in the path at any desired size. For example the "1" part of Rrev is an F, which means F to a desired level can be found at NFstart = 1 * 9^level NFlast = NFstart + 9^level - 1 = 2 * 9^level - 1 XFstart = 3^level YFstart = 0 level=3 for N=729 to N=1457 is a 27x27 which is the level-three F shown in Haverkort's paper, starting at XFstart=27,YFstart=0. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::KochelCurve-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 9**$level - 1)>. =back =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/Rows.pm0000644000175000017500000002066213734026656016533 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::Rows; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest', 'floor'; # uncomment this to run the ### lines #use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1() ]; sub x_maximum { my ($self) = @_; return $self->{'width'} - 1; } sub dx_minimum { my ($self) = @_; return - ($self->{'width'}-1); } sub dx_maximum { my ($self) = @_; return ($self->{'width'} <= 1 ? 0 # single column only : 1); } sub dy_minimum { my ($self) = @_; return ($self->{'width'} <= 1 ? 1 # single column only : 0); } use constant dy_maximum => 1; sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; return (($self->{'width'} >= 2 ? (1,0) # E too : ()), 1-$self->{'width'}, 1); } sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + $self->{'width'} - 1; } sub absdx_minimum { my ($self) = @_; return ($self->{'width'} <= 1 ? 0 : 1); } sub absdy_minimum { my ($self) = @_; return ($self->{'width'} <= 1 ? 1 # single column only : 0); } sub dsumxy_minimum { my ($self) = @_; return 2 - $self->{'width'}; # dX=-(width-1) dY=+1 } use constant dsumxy_maximum => 1; sub ddiffxy_minimum { my ($self) = @_; # dX=-(width-1) dY=+1 gives dDiffXY=-width+1-1=-width return - $self->{'width'}; } sub ddiffxy_maximum { my ($self) = @_; return ($self->{'width'} == 1 ? -1 # constant dY=-1 : 1); # straight E } sub dir_minimum_dxdy { my ($self) = @_; return ($self->{'width'} == 1 ? (0,1) # width=1 North only : (1,0)); # width>1 East } sub dir_maximum_dxdy { my ($self) = @_; return (1-$self->{'width'}, 1); } sub turn_any_left { my ($self) = @_; return ($self->{'width'} > 1); # width==1 only straight ahead } sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; return ($self->{'width'} == 1 ? undef : $self->n_start + $self->{'width'} - 1); } *turn_any_right = \&turn_any_left; sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; return ($self->{'width'} == 1 ? undef : $self->n_start + $self->{'width'}); } sub turn_any_straight { my ($self) = @_; return ($self->{'width'} != 2); # width=2 never straight } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); if (! exists $self->{'width'}) { $self->{'width'} = 1; } if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } ### width: $self->{'width'} return $self; } sub n_to_xy { my ($self, $n) = @_; ### Rows n_to_xy(): "$n" # no division by width=0, and width<0 not meaningful for now my $width; if (($width = $self->{'width'}) <= 0) { ### no points for width<=0 return; } $n = $n - $self->{'n_start'}; # zero based my $int = int($n); # BigFloat int() gives BigInt, use that $n -= $int; # fraction part, preserve any BigFloat if (2*$n >= 1) { # if $n >= 0.5, but BigInt friendly $n -= 1; $int += 1; } ### $n ### $int ### assert: $n >= -0.5 ### assert: $n < 0.5 my $y = int ($int / $width); $int -= $y*$width; if ($int < 0) { # ensure round down when $int negative $int += $width; $y -= 1; } ### floor y: $y ### remainder: $int return ($n + $int, $y); } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); if ($x < 0 || $x >= $self->{'width'}) { return undef; # outside the column } $y = round_nearest ($y); return $x + $y * $self->{'width'} + $self->{'n_start'}; } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### rect_to_n_range: "$x1,$y1 $x2,$y2" my $width = $self->{'width'}; $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); if ($x2 < $x1) { ($x1,$x2) = ($x2,$x1) } # swap to x1= $width || $x2 < 0) { ### completely outside 0 to width, or width<=0 return (1,0); } $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); if ($y2 < $y1) { ($y1,$y2) = ($y2,$y1) } # swap to y1= $width) { $x2 = ($x2 * 0) + $width-1; } # preserve bignum ### rect exact on: "$x1,$y1 $x2,$y2" # exact range bottom left to top right return ($x1 + $y1 * $width + $self->{'n_start'}, $x2 + $y2 * $width + $self->{'n_start'}); } 1; __END__ =for stopwords Math-PlanePath Ryde =head1 NAME Math::PlanePath::Rows -- points in fixed-width rows =head1 SYNOPSIS use Math::PlanePath::Rows; my $path = Math::PlanePath::Rows->new (width => 20); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path is rows of a given fixed width. For example width=7 is width => 7 3 | 22 23 24 ... 2 | 15 16 17 18 19 20 21 1 | 8 9 10 11 12 13 14 Y=0 | 1 2 3 4 5 6 7 ------------------------------- X=0 1 2 3 4 5 6 =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, with the same shape. For example to start at 0, =cut # math-image --path=Rows,n_start=0,width=7 --all --output=numbers =pod n_start => 0, width => 7 3 | 21 22 23 24 ... 2 | 14 15 16 17 18 19 20 1 | 7 8 9 10 11 12 13 Y=0 | 0 1 2 3 4 5 6 ------------------------------- X=0 1 2 3 4 5 6 The only effect is to push the N values around by a constant amount. It might help match coordinates with something else zero-based. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::Rows-Enew (width =E $w)> =item C<$path = Math::PlanePath::Rows-Enew (width =E $w, n_start =E $n)> Create and return a new path object. A C parameter must be supplied. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> in the path. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are rounded to the nearest integers, which has the effect of treating each point in the path as a square of side 1, so a column -0.5 <= x < width+0.5 and y>=-0.5 is covered. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/ComplexRevolving.pm0000644000175000017500000002003613734026673021076 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::ComplexRevolving; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_up_pow', 'bit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant xy_is_visited => 1; use constant x_negative_at_n => 5; use constant y_negative_at_n => 7; # use constant dir_maximum_dxdy => (0,0); # supremum, almost full way use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ # b=i+1 # X+iY = b^e0 + i*b^e1 + ... + i^t * b^et # sub n_to_xy { my ($self, $n) = @_; ### ComplexRevolving n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $x = my $y = ($n * 0); # inherit bignum 0 if (my @digits = bit_split_lowtohigh($n)) { my $bx = $x + 1; # inherit bignum 1 my $by = $x; # 0 for (;;) { if (shift @digits) { # low to high $x += $bx; $y += $by; ($bx,$by) = (-$by,$bx); # (bx+by*i)*i = bx*i - by, is rotate +90 } @digits || last; # (bx+by*i) * (i+1) # = bx*i+bx + -by + by*i # = (bx-by) + i*(bx+by) ($bx,$by) = ($bx - $by, $bx + $by); } } ### final: "$x,$y" return ($x,$y); } sub xy_to_n { my ($self, $x, $y) = @_; ### ComplexRevolving xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); foreach my $overflow ($x+$y, $x-$y) { if (is_infinite($overflow)) { return $overflow; } } my $zero = $x * 0 * $y; # inherit bignum 0 my @n; while ($x || $y) { ### at: "$x,$y power=$power n=$n" # (a+bi)*(i+1) = (a-b)+(a+b)i # if (($x % 2) == ($y % 2)) { # x+y even push @n, 0; } else { ### not multiple of 1+i, take e0=0 for b^e0=1 # [(x+iy)-1]/i # = [(x-1)+yi]/i # = y + (x-1)/i # = y + (1-x)*i # rotate -90 push @n, 1; ($x,$y) = ($y, 1-$x); ### sub and div to: "$x,$y" } # divide i+1 = mul (i-1)/(i^2 - 1^2) # = mul (i-1)/-2 # is (i*y + x) * (i-1)/-2 # x = (-x - y)/-2 = (x + y)/2 # y = (-y + x)/-2 = (y - x)/2 # ### assert: (($x+$y)%2)==0 ($x,$y) = (($x+$y)/2, ($y-$x)/2); } return digit_join_lowtohigh(\@n,2,$zero); } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### ComplexRevolving rect_to_n_range(): "$x1,$y1 $x2,$y2" my $xm = max(abs($x1),abs($x2)); my $ym = max(abs($y1),abs($y2)); return (0, int (32*($xm*$xm + $ym*$ym))); } #------------------------------------------------------------------------------ # levels sub level_to_n_range { my ($self, $level) = @_; return (0, 2**$level - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($pow, $exp) = round_up_pow ($n+1, 2); return $exp; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Math-PlanePath ie Nstart Nlevel Seminumerical et =head1 NAME Math::PlanePath::ComplexRevolving -- points in revolving complex base i+1 =head1 SYNOPSIS use Math::PlanePath::ComplexRevolving; my $path = Math::PlanePath::ComplexRevolving->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis path traverses points by a complex number base i+1 with turn factor i (+90 degrees) at each 1 bit. This is the "revolving binary representation" of Knuth's Seminumerical Algorithms section 4.1 exercise 28. =cut # math-image --path=ComplexRevolving --expression='i<64?i:0' --output=numbers --size=79x30 =pod 54 51 38 35 5 60 53 44 37 4 39 46 43 58 23 30 27 42 3 45 8 57 4 29 56 41 52 2 31 6 3 2 15 22 19 50 1 16 12 5 0 1 28 21 49 <- Y=0 55 62 59 10 7 14 11 26 -1 61 24 9 20 13 40 25 36 -2 47 18 63 34 -3 32 48 17 33 -4 ^ -4 -3 -2 -1 X=0 1 2 3 4 5 The 1 bits in N are exponents e0 to et, in increasing order, N = 2^e0 + 2^e1 + ... + 2^et e0 < e1 < ... < et and are applied to a base b=i+1 as X+iY = b^e0 + i * b^e1 + i^2 * b^e2 + ... + i^t * b^et Each 2^ek has become b^ek base b=i+1. The i^k is an extra factor i at each 1 bit of N, causing a rotation by +90 degrees for the bits above it. Notice the factor is i^k not i^ek, ie. it increments only with the 1-bits of N, not the whole exponent. A single bit N=2^k is the simplest and is X+iY=(i+1)^k. These N=1,2,4,8,16,etc are at successive angles 45, 90, 135, etc degrees (the same as in C). But points N=2^k+1 with two bits means X+iY=(i+1) + i*(i+1)^k and that factor "i*" is a rotation by 90 degrees so points N=3,5,9,17,33,etc are in the next quadrant around from their preceding 2,4,8,16,32. As per the exercise in Knuth it's reasonably easy to show that this calculation is a one-to-one mapping between integer N and complex integer X+iY, so the path covers the plane and visits all points once each. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::ComplexRevolving-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 2**$level - 1)>. =back =head1 SEE ALSO L, L, L, L Donald Knuth, "The Art of Computer Programming", volume 2 "Seminumerical Algorithms", section 4.1 exercise 28. =head1 HOME PAGE L =head1 LICENSE Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/DiagonalsOctant.pm0000644000175000017500000004052213775044540020645 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::DiagonalsOctant; use 5.004; use strict; use Carp 'croak'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_sqrtint = \&Math::PlanePath::_sqrtint; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; use constant parameter_info_array => [ { name => 'direction', share_key => 'direction_downup', display => 'Direction', type => 'enum', default => 'down', choices => ['down','up'], choices_display => ['Down','Up'], description => 'Number points downwards or upwards along the diagonals.', }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; use constant diffxy_maximum => 0; # octant X<=Y so X-Y<=0 sub dx_minimum { my ($self) = @_; return ($self->{'direction'} eq 'up' ? -1 : undef); } sub dx_maximum { my ($self) = @_; return ($self->{'direction'} eq 'down' ? 1 : undef); } sub dy_minimum { my ($self) = @_; return ($self->{'direction'} eq 'down' ? -1 : undef); } sub dy_maximum { my ($self) = @_; return ($self->{'direction'} eq 'up' ? 1 : undef); } sub absdy_minimum { my ($self) = @_; return ($self->{'direction'} eq 'down' ? 1 # 'down' always changes : 0); # 'up' N=2 dY=0 } use constant dsumxy_minimum => 0; # advancing diagonals use constant dsumxy_maximum => 1; sub ddiffxy_minimum { my ($self) = @_; return ($self->{'direction'} eq 'down' ? undef # "down" jumps back unlimited at bottom : -2); # NW diagonal } sub ddiffxy_maximum { my ($self) = @_; return ($self->{'direction'} eq 'down' ? 2 # SE diagonal : undef); # "up" jumps down unlimited at top } sub dir_minimum_dxdy { my ($self) = @_; return ($self->{'direction'} eq 'down' ? (0,1) # vertical N=1to2 : (1,0)); # horizontal N=2to3 } sub dir_maximum_dxdy { my ($self) = @_; return ($self->{'direction'} eq 'down' ? (1,-1) # South-East diagonal : (2,-1)); # N=6 to N=7 } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } my $direction = ($self->{'direction'} ||= 'down'); if (! ($direction eq 'up' || $direction eq 'down')) { croak "Unrecognised direction option: ", $direction; } return $self; } # start from integers # [ 1, 2, 3, 4, 5 ], # [ 1, 3, 7, 13, 21 ] # N = (d^2 - d + 1) # = ($d**2 - $d + 1) # = (($d - 1)*$d + 1) # # starting 0.5 back from the odd Y, numbering d=1 for the Y=1 # [ 1, 2, 3, 4, 5 ], # [ 2-.5, 5-.5, 10-.5, 17-.5, 26-.5 ] # squares +0.5 # N = (d^2 + 1/2) # = ($d**2 + 1/2) # = ($d**2 + 1/2) # d = 0 + sqrt(1 * $n + -1/2) # = sqrt($n -1/2) # = sqrt(4*$n-2)/2 # # 9 | 26 # 8 | 21 # 7 | 17 22 # 6 | 13 18 23 # 5 | 10 14 19 24 # 4 | 7 11 15 20 25 # 3 | 5 8 12 16 # 2 | 3 6 9 # 1 | 2 4 # Y=0 | 1 # +------------- # sub n_to_xy { my ($self, $n) = @_; ### DiagonalsOctant n_to_xy(): "$n ".(ref $n || '') # adjust to N=1 at origin X=0,Y=0 $n = $n - $self->{'n_start'} + 1; my $d = int(4*$n)-2; # for sqrt if ($d < 0) { ### nothing at N < 0.5 ... return; } $d = int( _sqrtint($d)/2 ); ### $d # remainder positive or negative relative to the start of the following # diagonal # $n -= $d*($d+1) + 1; ### remainder: $n # $n first in formulas to preserve n=BigFloat when d=integer is BigInt # if ($self->{'direction'} eq 'up') { if (2*$n >= -1) { return (-$n + $d, $n + $d); } else { return (-$n - 1, $n + 2*$d); } } else { if (2*$n >= -1) { # stripe of d+1 many points starting at Y even, eg. N=13 return ($n, -$n + 2*$d); } else { # stripe of d many points starting at Y odd, eg. N=10 return ($n + $d, -$n + $d - 1); } } } sub xy_to_n { my ($self, $x, $y) = @_; ### xy_to_n(): $x, $y $x = round_nearest ($x); if ($self->{'direction'} eq 'up') { $y = round_nearest ($y); } else { $y = - round_nearest (- $y); } ### rounded ### $x ### $y if ($y < 0 || $x < 0 || $x > $y) { ### outside upper octant ... return undef; } if ($self->{'direction'} eq 'up') { my $d = $x + $y + 2; ### $d return ($d*$d - ($d % 2))/4 - $x + $self->{'n_start'} - 1; } else { my $d = $x + $y + 1; ### $d return ($d*$d - ($d % 2))/4 + $x + $self->{'n_start'}; } } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); if ($self->{'direction'} eq 'up') { $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); } else { $y1 = - round_nearest (- $y1); $y2 = - round_nearest (- $y2); } # bottom-left and top-right same as Math::PlanePath::Diagonals, but also # brining $y1 up to within octant if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # x2 | / # -----+ | / # | |/ +---- # -----+ + |x1,y2 # if ($x2 < 0 || $y2 < 0 || $x1 > $y2) { ### entirely outside upper octant, no range ... return (1, 0); } # | # +---- / # | | / # +---- / # x1 | / # + # increase x1 to within octant if ($x1 < 0) { $x1 *= 0; } # zero by $x1*0 to preserve bignum # | | / # | |/ # | /| # | / +----y1 # + x1 # increase y1 so bottom-left x1,y1 is within octant if ($y1 < $x1) { $y1 = $x1; } # | / x2 # | --------+ # | / | # | -------+ # | / # + # decrease x2 so top-right is within octant if ($x2 > $y2) { $x2 = $y2; } # exact range bottom left to top right return ($self->xy_to_n ($x1,$y1), $self->xy_to_n ($x2,$y2)); } 1; __END__ =for stopwords PlanePath Ryde Math-PlanePath pronic sqrt eg flonums N-Nstart Nrem octant ie OEIS Nstart =head1 NAME Math::PlanePath::DiagonalsOctant -- points in diagonal stripes for an eighth of the plane =head1 SYNOPSIS use Math::PlanePath::DiagonalsOctant; my $path = Math::PlanePath::DiagonalsOctant->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path follows successive diagonals downwards from the Y axis down to the X=Y centre line, traversing the eighth of the plane on and above X=Y. =cut # math-image --path=DiagonalsOctant --all --output=numbers =pod 8 | 21 27 33 40 47 55 63 72 81 | \ \ \ \ \ \ \ 7 | 17 22 28 34 41 48 56 64 | \ \ \ \ \ \ 6 | 13 18 23 29 35 42 49 | \ \ \ \ \ 5 | 10 14 19 24 30 36 | \ \ \ \ 4 | 7 11 15 20 25 | \ \ \ 3 | 5 8 12 16 | \ \ 2 | 3 6 9 | \ 1 | 2 4 | Y=0 | 1 + ---------------------------- X=0 1 2 3 4 5 6 7 8 XN=1,4,9,16,etc on the X=Y leading diagonal are the perfect squares. N=2,6,12,20,etc at the ends of the other diagonals are the Xpronic numbers k*(k+1). Incidentally "octant" usually refers to an eighth of a 3-dimensional coordinate space. Since C is only 2 dimensions there's no confusion and at the risk of abusing nomenclature half a quadrant is reckoned as an "octant". =head2 Pyramid Rows Taking two diagonals running from k^2+1 to (k+1)^2 is the same as a row of the step=2 C (see L). Each endpoint is the same, but here it's two diagonals instead of one row. For example in the C the Y=3 row runs from N=10 to N=16 ending at X=3,Y=3. Here that's in two diagonals N=10 to N=12 and then N=13 to N=16, and that N=16 endpoint is the same X=3,Y=3. =head2 Direction Option C 'up'> reverses the order within each diagonal and counts upward from the centre to the Y axis. =cut # math-image --path=DiagonalsOctant,direction=up --all --output=numbers_dash =pod 8 | 25 29 34 39 45 51 58 65 73 | \ \ \ \ \ \ \ 7 | 20 24 28 33 38 44 50 57 | \ \ \ \ \ \ 6 | 16 19 23 27 32 37 43 | \ \ \ \ \ 5 | 12 15 18 22 26 31 | \ \ \ \ 4 | 9 11 14 17 21 direction => "up" | \ \ \ 3 | 6 8 10 13 | \ \ 2 | 4 5 7 | \ 1 | 2 3 | Y=0 | 1 +--------------------------- X=0 1 2 3 4 5 6 7 8 In this arrangement N=1,2,4,6,9,etc on the Y axis are alternately the squares and the pronic numbers. The squares are on even Y and pronic on odd Y. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same diagonals sequence. For example to start at 0, =cut # math-image --path=DiagonalsOctant,n_start=0 --all --output=numbers --size=35x5 # math-image --path=DiagonalsOctant,n_start=0,direction=up --all --output=numbers --size=35x5 =pod n_start => 0 n_start=>0 direction => "down" direction=>"up" 6 | 12 | 15 5 | 9 13 | 11 14 4 | 6 10 14 | 8 10 13 3 | 4 7 11 15 | 5 7 9 12 2 | 2 5 8 | 3 4 6 1 | 1 3 | 1 2 Y=0 | 0 | 0 +-------------- +-------------- X=0 1 2 3 X=0 1 2 3 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::DiagonalsOctant-Enew ()> =item C<$path = Math::PlanePath::DiagonalsOctant-Enew (direction =E $str, n_start =E $n)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n E 0.5> the return is an empty list, it being considered the path begins at 1. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each point C<$n> as a square of side 1. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head1 FORMULAS =head2 N to X,Y To break N into X,Y it's convenient to take two diagonals at a time, since the length then changes by 1 each pair making a quadratic. Starting at each X=0,Y=odd just after perfect square N allows just a sqrt. Nstart = d*d+1 where d numbers diagonal pairs, eg. d=3 for X=0,Y=5 going down. This is easily reversed as d = floor sqrt(N-1) The code reckons the start of the diagonal as 0.5 further back, so that N=9.5 is at X=-.5,Y=5.5. To do that d is formed as d = floor sqrt(N-0.5) = int( sqrt(int(4*$n)-2)/2 ) Taking /2 out of the sqrt helps with C which circa Perl 5.14 doesn't inter-operate with flonums very well. In any case N-Nstart is an offset into two diagonals, the first of length d many points and the second d+1. For example d=3 starting Y=5 for points N=10,11,12 followed by Y=6 N=13,14,15,16. The formulas are simplified by calculating a remainder relative to the second diagonal, so it's negative for the first and positive for the second, Nrem = N - (d*(d+1)+1) d*(d+1)+1 is 1 past the pronic numbers when end each first diagonal, as described above. In any case for example d=3 is relative to N=13 making Nrem=-3,-2,-1 or Nrem=0,1,2,3. To include the preceding 0.5 in the second diagonal simply means reckoning NremE=-0.5 as belonging to the second. In that base if Nrem >= -0.5 X = Nrem # direction="down" Y = 2*d - Nrem else X = Nrem + d Y = d - Nrem - 1 For example N=15 Nrem=1 is the first case, X=1, Y=2*3-1=5. Or N=11 Nrem=-2 the second X=-2+3=1, Y=3-(-2)-1=4. For "up" direction the Nrem and d are the same, but the coordinate directions reverse. if Nrem >= -0.5 X = d - Nrem # direction="up" Y = d + Nrem else X = -Nrem - 1 Y = 2d + Nrem Another way is to reckon Nstart from the X=0,Y=even diagonals, which is then two diagonals of the same length and d formed by a sqrt inverting a pronic Nstart=d*(d+1). =head2 Rectangle to N Range Within each row increasing X is increasing N, and in each column increasing Y is increasing N. This is so in both "down" and "up" arrangements. On that basis in a rectangle the lower left corner is the minimum N and the upper right is the maximum N. If the rectangle is partly outside the covered octant then the corners must be shifted to put them in range, ie. trim off any rows or columns entirely outside the rectangle. For the lower left this means, | | / | | / +-------- if x1 < 0 then x1 = 0 x1 | / increase x1 to within octant |/ + | |/ | | if y1 < x1 then y1 = x1 | /| increase y1 to bottom-left within octant |/ +----y1 + x1 And for the top right, | / x2 | ------+ y2 if x2 > y2 then x2 = y2 | / | decrease x2 so top-right within octant | / | (the end of the y2 row) |/ + =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back direction=down A002620 N at end each run X=k,Y=k and X=k,Y=k+1 direction=down, n_start=0 A055087 X coord, runs 0 to k twice A082375 Y-X, runs k to 0 or 1 stepping by 2 A005563 N on X=Y diagonal, X*(X+2) direction=up A002620 N on Y axis, end of each run, quarter squares direction=up, n_start=0 A024206 N on Y axis (starting from n=1 is Y=0, so Y=n-1) A014616 N in column X=1 (is Y axis N-1, from N=3) A002378 N on X=Y diagonal, pronic X*(X+1) either direction, n_start=0 A055086 X+Y, k repeating floor(k/2)+1 times A004652 N start and end of each even-numbered diagonal A274427 map to N of full Diagonals permutations A056536 N of PyramidRows in DiagonalsOctant order A091995 with DiagonalsOctant direction=up A091018 N-1, ie. starting from 0 A090894 N-1 and DiagonalsOctant direction=up A056537 N of DiagonalsOctant at X,Y in PyramidRows order inverse of A056536 =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/CincoCurve.pm0000644000175000017500000006171713734026673017646 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # http://www.cisl.ucar.edu/css/papers/sfc3.pdf # Hilbert + Peano-meander # # http://oceans11.lanl.gov/svn/POP/trunk/pop/source/distribution.F90 # package Math::PlanePath::CincoCurve; use 5.004; use strict; #use List::Util 'min', 'max'; *min = \&Math::PlanePath::_min; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; #------------------------------------------------------------------------------ # tables generated by tools/dekking-curve-table.pl # my @next_state = ( 0, 0,50,50,50, # 0 75,25,25,50,50, 0, 0,75,50, 0, 0, 0,25,75,75, 0,25,75,75, 0, 25,25,75,75,75, # 25 50, 0, 0,75,75, 25,25,50,75,25, 25,25, 0,50,50, 25, 0,50,50,25, 50,50, 0, 0, 0, # 50 25,75,75, 0, 0, 50,50,25, 0,50, 50,50,75,25,25, 50,75,25,25,50, 75,75,25,25,25, # 75 0,50,50,25,25, 75,75, 0,25,75, 75,75,50, 0, 0, 75,50, 0, 0,75); my @digit_to_x = (0,1,2,2,2, 1,1,0,0,0, 0,1,1,2,2, 3,4,4,3,3, 4,4,3,3,4, 4,3,2,2,2, 3,3,4,4,4, 4,3,3,2,2, 1,0,0,1,1, 0,0,1,1,0, 0,0,0,1,2, 2,1,1,2,3, 4,4,3,3,4, 4,4,3,3,2, 2,1,1,0,0, 4,4,4,3,2, 2,3,3,2,1, 0,0,1,1,0, 0,0,1,1,2, 2,3,3,4,4); my @digit_to_y = (0,0,0,1,2, 2,1,1,2,3, 4,4,3,3,4, 4,4,3,3,2, 2,1,1,0,0, 4,4,4,3,2, 2,3,3,2,1, 0,0,1,1,0, 0,0,1,1,2, 2,3,3,4,4, 0,1,2,2,2, 1,1,0,0,0, 0,1,1,2,2, 3,4,4,3,3, 4,4,3,3,4, 4,3,2,2,2, 3,3,4,4,4, 4,3,3,2,2, 1,0,0,1,1, 0,0,1,1,0); my @yx_to_digit = ( 0, 1, 2,23,24, # 0 7, 6, 3,22,21, 8, 5, 4,19,20, 9,12,13,18,17, 10,11,14,15,16, 16,15,14,11,10, # 25 17,18,13,12, 9, 20,19, 4, 5, 8, 21,22, 3, 6, 7, 24,23, 2, 1, 0, 0, 7, 8, 9,10, # 50 1, 6, 5,12,11, 2, 3, 4,13,14, 23,22,19,18,15, 24,21,20,17,16, 16,17,20,21,24, # 75 15,18,19,22,23, 14,13, 4, 3, 2, 11,12, 5, 6, 1, 10, 9, 8, 7, 0); my @min_digit = ( 0, 0, 0, 0, 0, # 0 7, 7, 7, 7, 8, 8, 8, 9, 9,10, 0, 0, 0, 0, 0, 6, 5, 5, 5, 5, 5, 5, 9, 9,10, # 25 0, 0, 0, 0, 0, 3, 3, 3, 3, 4, 4, 4, 9, 9,10, 0, 0, 0, 0, 0, 3, 3, 3, 3, 4, # 50 4, 4, 9, 9,10, 0, 0, 0, 0, 0, 3, 3, 3, 3, 4, 4, 4, 9, 9,10, 1, 1, 1, 1, 1, # 75 6, 5, 5, 5, 5, 5, 5,12,11,11, 1, 1, 1, 1, 1, 3, 3, 3, 3, 4, 4, 4,12,11,11, # 100 1, 1, 1, 1, 1, 3, 3, 3, 3, 4, 4, 4,12,11,11, 1, 1, 1, 1, 1, 3, 3, 3, 3, 4, # 125 4, 4,12,11,11, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4,13,13,14, 2, 2, 2, 2, 2, # 150 3, 3, 3, 3, 4, 4, 4,13,13,14, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4,13,13,14, # 175 23,22,19,18,15, 22,19,18,15,19, 18,15,18,15,15, 23,21,19,17,15, 21,19,17,15,19, # 200 17,15,17,15,15, 24,21,20,17,16, 21,20,17,16,20, 17,16,17,16,16, 16,16,16,16,16, # 225 17,17,17,17,20, 20,20,21,21,24, 15,15,15,15,15, 17,17,17,17,19, 19,19,21,21,23, # 250 14,13, 4, 3, 2, 13, 4, 3, 2, 4, 3, 2, 3, 2, 2, 11,11, 4, 3, 1, 12, 4, 3, 1, 4, # 275 3, 1, 3, 1, 1, 10, 9, 4, 3, 0, 9, 4, 3, 0, 4, 3, 0, 3, 0, 0, 15,15,15,15,15, # 300 18,18,18,18,19, 19,19,22,22,23, 14,13, 4, 3, 2, 13, 4, 3, 2, 4, 3, 2, 3, 2, 2, # 325 11,11, 4, 3, 1, 12, 4, 3, 1, 4, 3, 1, 3, 1, 1, 10, 9, 4, 3, 0, 9, 4, 3, 0, 4, # 350 3, 0, 3, 0, 0, 14,13, 4, 3, 2, 13, 4, 3, 2, 4, 3, 2, 3, 2, 2, 11,11, 4, 3, 1, # 375 12, 4, 3, 1, 4, 3, 1, 3, 1, 1, 10, 9, 4, 3, 0, 9, 4, 3, 0, 4, 3, 0, 3, 0, 0, # 400 11,11, 5, 5, 1, 12, 5, 5, 1, 5, 5, 1, 6, 1, 1, 10, 9, 5, 5, 0, 9, 5, 5, 0, 5, # 425 5, 0, 6, 0, 0, 10, 9, 8, 7, 0, 9, 8, 7, 0, 8, 7, 0, 7, 0, 0, 0, 0, 0, 0, 0, # 450 1, 1, 1, 1, 2, 2, 2,23,23,24, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2,22,21,21, # 475 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2,19,19,20, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, # 500 2, 2,18,17,17, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2,15,15,16, 7, 6, 3, 3, 3, # 525 6, 3, 3, 3, 3, 3, 3,22,21,21, 7, 5, 3, 3, 3, 5, 3, 3, 3, 3, 3, 3,19,19,20, # 550 7, 5, 3, 3, 3, 5, 3, 3, 3, 3, 3, 3,18,17,17, 7, 5, 3, 3, 3, 5, 3, 3, 3, 3, # 575 3, 3,15,15,16, 8, 5, 4, 4, 4, 5, 4, 4, 4, 4, 4, 4,19,19,20, 8, 5, 4, 4, 4, # 600 5, 4, 4, 4, 4, 4, 4,18,17,17, 8, 5, 4, 4, 4, 5, 4, 4, 4, 4, 4, 4,15,15,16, # 625 9, 9, 9, 9, 9, 12,12,12,12,13, 13,13,18,17,17, 9, 9, 9, 9, 9, 11,11,11,11,13, # 650 13,13,15,15,16, 10,10,10,10,10, 11,11,11,11,14, 14,14,15,15,16, 16,15,14,11,10, # 675 15,14,11,10,14, 11,10,11,10,10, 16,15,13,11, 9, 15,13,11, 9,13, 11, 9,11, 9, 9, # 700 16,15, 4, 4, 4, 15, 4, 4, 4, 4, 4, 4, 5, 5, 8, 16,15, 3, 3, 3, 15, 3, 3, 3, 3, # 725 3, 3, 5, 5, 7, 16,15, 2, 1, 0, 15, 2, 1, 0, 2, 1, 0, 1, 0, 0, 17,17,13,12, 9, # 750 18,13,12, 9,13, 12, 9,12, 9, 9, 17,17, 4, 4, 4, 18, 4, 4, 4, 4, 4, 4, 5, 5, 8, # 775 17,17, 3, 3, 3, 18, 3, 3, 3, 3, 3, 3, 5, 5, 7, 17,17, 2, 1, 0, 18, 2, 1, 0, 2, # 800 1, 0, 1, 0, 0, 20,19, 4, 4, 4, 19, 4, 4, 4, 4, 4, 4, 5, 5, 8, 20,19, 3, 3, 3, # 825 19, 3, 3, 3, 3, 3, 3, 5, 5, 7, 20,19, 2, 1, 0, 19, 2, 1, 0, 2, 1, 0, 1, 0, 0, # 850 21,21, 3, 3, 3, 22, 3, 3, 3, 3, 3, 3, 6, 6, 7, 21,21, 2, 1, 0, 22, 2, 1, 0, 2, # 875 1, 0, 1, 0, 0, 24,23, 2, 1, 0, 23, 2, 1, 0, 2, 1, 0, 1, 0, 0); my @max_digit = ( 0, 7, 8, 9,10, # 0 7, 8, 9,10, 8, 9,10, 9,10,10, 1, 7, 8,12,12, 7, 8,12,12, 8, 12,12,12,12,11, # 25 2, 7, 8,13,14, 7, 8,13,14, 8, 13,14,13,14,14, 23,23,23,23,23, 22,22,22,22,19, # 50 19,19,18,18,15, 24,24,24,24,24, 22,22,22,22,20, 20,20,18,18,16, 1, 6, 6,12,12, # 75 6, 6,12,12, 5, 12,12,12,12,11, 2, 6, 6,13,14, 6, 6,13,14, 5, 13,14,13,14,14, # 100 23,23,23,23,23, 22,22,22,22,19, 19,19,18,18,15, 24,24,24,24,24, 22,22,22,22,20, # 125 20,20,18,18,16, 2, 3, 4,13,14, 3, 4,13,14, 4, 13,14,13,14,14, 23,23,23,23,23, # 150 22,22,22,22,19, 19,19,18,18,15, 24,24,24,24,24, 22,22,22,22,20, 20,20,18,18,16, # 175 23,23,23,23,23, 22,22,22,22,19, 19,19,18,18,15, 24,24,24,24,24, 22,22,22,22,20, # 200 20,20,18,18,16, 24,24,24,24,24, 21,21,21,21,20, 20,20,17,17,16, 16,17,20,21,24, # 225 17,20,21,24,20, 21,24,21,24,24, 16,18,20,22,24, 18,20,22,24,20, 22,24,22,24,24, # 250 16,18,20,22,24, 18,20,22,24,20, 22,24,22,24,24, 16,18,20,22,24, 18,20,22,24,20, # 275 22,24,22,24,24, 16,18,20,22,24, 18,20,22,24,20, 22,24,22,24,24, 15,18,19,22,23, # 300 18,19,22,23,19, 22,23,22,23,23, 15,18,19,22,23, 18,19,22,23,19, 22,23,22,23,23, # 325 15,18,19,22,23, 18,19,22,23,19, 22,23,22,23,23, 15,18,19,22,23, 18,19,22,23,19, # 350 22,23,22,23,23, 14,14,14,14,14, 13,13,13,13, 4, 4, 4, 3, 3, 2, 14,14,14,14,14, # 375 13,13,13,13, 5, 6, 6, 6, 6, 2, 14,14,14,14,14, 13,13,13,13, 8, 8, 8, 7, 7, 2, # 400 11,12,12,12,12, 12,12,12,12, 5, 6, 6, 6, 6, 1, 11,12,12,12,12, 12,12,12,12, 8, # 425 8, 8, 7, 7, 1, 10,10,10,10,10, 9, 9, 9, 9, 8, 8, 8, 7, 7, 0, 0, 1, 2,23,24, # 450 1, 2,23,24, 2, 23,24,23,24,24, 7, 7, 7,23,24, 6, 6,23,24, 3, 23,24,23,24,24, # 475 8, 8, 8,23,24, 6, 6,23,24, 4, 23,24,23,24,24, 9,12,13,23,24, 12,13,23,24,13, # 500 23,24,23,24,24, 10,12,14,23,24, 12,14,23,24,14, 23,24,23,24,24, 7, 7, 7,22,22, # 525 6, 6,22,22, 3, 22,22,22,22,21, 8, 8, 8,22,22, 6, 6,22,22, 4, 22,22,22,22,21, # 550 9,12,13,22,22, 12,13,22,22,13, 22,22,22,22,21, 10,12,14,22,22, 12,14,22,22,14, # 575 22,22,22,22,21, 8, 8, 8,19,20, 5, 5,19,20, 4, 19,20,19,20,20, 9,12,13,19,20, # 600 12,13,19,20,13, 19,20,19,20,20, 10,12,14,19,20, 12,14,19,20,14, 19,20,19,20,20, # 625 9,12,13,18,18, 12,13,18,18,13, 18,18,18,18,17, 10,12,14,18,18, 12,14,18,18,14, # 650 18,18,18,18,17, 10,11,14,15,16, 11,14,15,16,14, 15,16,15,16,16, 16,16,16,16,16, # 675 15,15,15,15,14, 14,14,11,11,10, 17,18,18,18,18, 18,18,18,18,14, 14,14,12,12,10, # 700 20,20,20,20,20, 19,19,19,19,14, 14,14,12,12,10, 21,22,22,22,22, 22,22,22,22,14, # 725 14,14,12,12,10, 24,24,24,24,24, 23,23,23,23,14, 14,14,12,12,10, 17,18,18,18,18, # 750 18,18,18,18,13, 13,13,12,12, 9, 20,20,20,20,20, 19,19,19,19,13, 13,13,12,12, 9, # 775 21,22,22,22,22, 22,22,22,22,13, 13,13,12,12, 9, 24,24,24,24,24, 23,23,23,23,13, # 800 13,13,12,12, 9, 20,20,20,20,20, 19,19,19,19, 4, 5, 8, 5, 8, 8, 21,22,22,22,22, # 825 22,22,22,22, 4, 6, 8, 6, 8, 8, 24,24,24,24,24, 23,23,23,23, 4, 6, 8, 6, 8, 8, # 850 21,22,22,22,22, 22,22,22,22, 3, 6, 7, 6, 7, 7, 24,24,24,24,24, 23,23,23,23, 3, # 875 6, 7, 6, 7, 7, 24,24,24,24,24, 23,23,23,23, 2, 2, 2, 1, 1, 0); # state length 100 in each of 4 tables = 400 # min/max 2 of 900 each = 1800 sub n_to_xy { my ($self, $n) = @_; ### CincoCurve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; # fraction part my @digits = digit_split_lowtohigh($int,25); my $len = ($int*0 + 5) ** scalar(@digits); # inherit bignum ### digits: join(', ',@digits)." count ".scalar(@digits) ### $len my $state = my $dir = 0; my $x = 0; my $y = 0; while (defined (my $digit = pop @digits)) { $len /= 5; $state += $digit; if ($digit != 24) { $dir = $state; } ### $len ### $state ### digit_to_x: $digit_to_x[$state] ### digit_to_y: $digit_to_y[$state] ### next_state: $next_state[$state] $x += $len * $digit_to_x[$state]; $y += $len * $digit_to_y[$state]; $state = $next_state[$state]; } ### final integer: "$x,$y" ### assert: ($dir % 25) != 24 # with $n fractional part return ($n * ($digit_to_x[$dir+1] - $digit_to_x[$dir]) + $x, $n * ($digit_to_y[$dir+1] - $digit_to_y[$dir]) + $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### CincoCurve xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my @xdigits = digit_split_lowtohigh ($x, 5); my @ydigits = digit_split_lowtohigh ($y, 5); my $state = 0; my @ndigits; foreach my $i (reverse 0 .. max($#xdigits,$#ydigits)) { # high to low my $ndigit = $yx_to_digit[$state + 5*($ydigits[$i]||0) + ($xdigits[$i]||0)]; $ndigits[$i] = $ndigit; $state = $next_state[$state+$ndigit]; } return digit_join_lowtohigh (\@ndigits, 25, $x * 0 * $y); # bignum zero } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### BetaOmega rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 0) { return (1, 0); } if ($x1 < 0) { $x1 *= 0; } # "*=" to preserve bigint x1 or y1 if ($y1 < 0) { $y1 *= 0; } my ($len, $level) = round_down_pow (($x2 > $y2 ? $x2 : $y2), 5); if (is_infinite($len)) { return (0, $len); } # At this point an over-estimate would be: return (0, 25*$len*$len-1); my $n_min = my $n_max = my $y_min = my $y_max = my $x_min = my $x_max = my $min_state = my $max_state = 0; ### $x_min ### $y_min while ($level >= 0) { ### $level ### $len { my $digit = $min_digit[9*$min_state + _rect_key($x1, $x2, $x_min, $len) * 15 + _rect_key($y1, $y2, $y_min, $len)]; ### $min_state ### $x_min ### $y_min ### $digit $n_min = 25*$n_min + $digit; $min_state += $digit; $x_min += $len * $digit_to_x[$min_state]; $y_min += $len * $digit_to_y[$min_state]; $min_state = $next_state[$min_state]; } { my $digit = $max_digit[9*$max_state + _rect_key($x1, $x2, $x_max, $len) * 15 + _rect_key($y1, $y2, $y_max, $len)]; $n_max = 25*$n_max + $digit; $max_state += $digit; $x_max += $len * $digit_to_x[$max_state]; $y_max += $len * $digit_to_y[$max_state]; $max_state = $next_state[$max_state]; } $len = int($len/5); $level--; } return ($n_min, $n_max); } sub _rect_key { my ($z1, $z2, $zbase, $len) = @_; $z1 = max (0, min (4, int (($z1 - $zbase)/$len))); $z2 = max (0, min (4, int (($z2 - $zbase)/$len))); ### assert: $z1 <= $z2 return (9-$z1)*$z1/2 + $z2; } #------------------------------------------------------------------------------ # levels use Math::PlanePath::DekkingCentres; *level_to_n_range = \&Math::PlanePath::DekkingCentres::level_to_n_range; *n_to_level = \&Math::PlanePath::DekkingCentres::n_to_level; #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde ie CincoCurve Math-PlanePath Cinco COSIM =head1 NAME Math::PlanePath::CincoCurve -- 5x5 self-similar curve =head1 SYNOPSIS use Math::PlanePath::CincoCurve; my $path = Math::PlanePath::CincoCurve->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis is the 5x5 self-similar Cinco curve =over John Dennis, "Inverse Space-Filling Curve Partitioning of a Global Ocean Model", and source code from COSIM L L, L =back It makes a 5x5 self-similar traversal of the first quadrant XE0,YE0. | 4 | 10--11 14--15--16 35--36 39--40--41 74 71--70 67--66 | | | | | | | | | | | | | | 3 | 9 12--13 18--17 34 37--38 43--42 73--72 69--68 65 | | | | | | 2 | 8 5-- 4 19--20 33 30--29 44--45 52--53--54 63--64 | | | | | | | | | | | | 1 | 7-- 6 3 22--21 32--31 28 47--46 51 56--55 62--61 | | | | | | | | Y=0 | 0-- 1-- 2 23--24--25--26--27 48--49--50 57--58--59--60 | +-------------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 The base pattern is the N=0 to N=24 part. It repeats transposed and rotated to make the ends join. N=25 to N=49 is a repeat of the base, then N=50 to N=74 is a transpose to go upwards. The sub-part arrangements are as follows. +------+------+------+------+------+ | 10 | 11 | 14 | 15 | 16 | | | | | | | |----->|----->|----->|----->|----->| +------+------+------+------+------+ |^ 9 | 12 ||^ 13 | 18 ||<-----| || T | T ||| T | T || 17 | || | v|| | v| | +------+------+------+------+------+ |^ 8 | 5 ||^ 4 | 19 || 20 | || T | T ||| T | T || | || | v|| | v|----->| +------+------+------+------+------+ |<-----|<---- |^ 3 | 22 ||<-----| | 7 | 6 || T | T || 21 | | | || | v| | +------+------+------+------+------+ | 0 | 1 |^ 2 | 23 || 24 | | | || T | T || | |----->|----->|| | v|----->| +------+------+------+------+------+ Parts such as 6 going left are the base rotated 180 degrees. The verticals like 2 are a transpose of the base, ie. swap X,Y, and downward vertical like 23 is transpose plus rotate 180 (which is equivalent to a mirror across the anti-diagonal). Notice the base shape fills its sub-part to the left side and the transpose instead fills on the right. The N values along the X axis are increasing, as are the values along the Y axis. This occurs because the values along the sub-parts of the base are increasing along the X and Y axes, and the other two sides are increasing too when rotated or transposed for sub-parts such as 2 and 23, or 7, 8 and 9. Dennis conceives this for use in combination with 2x2 Hilbert and 3x3 meander shapes so that sizes which are products of 2, 3 and 5 can be used for partitioning. Such mixed patterns can't be done with the code here, mainly since a mixture depends on having a top-level target size rather than the unlimited first quadrant here. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::CincoCurve-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 25**$level - 1)>. =back =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/PyramidSpiral.pm0000644000175000017500000002250413776014143020350 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::PyramidSpiral; use 5.004; use strict; #use List::Util 'min'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_sqrtint = \&Math::PlanePath::_sqrtint; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1() ]; use constant xy_is_visited => 1; sub x_negative_at_n { my ($self) = @_; return $self->n_start + 3; } sub y_negative_at_n { my ($self) = @_; return $self->n_start + 4; } sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + 2; } use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant 1.02 _UNDOCUMENTED__dxdy_list => (1,0, # E -1,1, # NW -1,-1, # SW ); use constant absdx_minimum => 1; use constant dsumxy_minimum => -2; # SW diagonal use constant dsumxy_maximum => 1; use constant ddiffxy_minimum => -2; # NW diagonal use constant ddiffxy_maximum => 1; use constant dir_maximum_dxdy => (-1,-1); # South-West use constant turn_any_right => 0; # only left or straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # bottom left corner, N=0 basis # [ 0, 1, 2, 3 ], # [ 0, 4, 16, 36 ] # N = (4 d^2) # = (4*$d**2) # = (4*$d**2) # d = 0 + sqrt(1/4 * $n + 0) # = sqrt($n)/2 # sub n_to_xy { my ($self, $n) = @_; #### PyramidSpiral n_to_xy: $n $n = $n - $self->{'n_start'}; # start N=0, and warn if $n==undef if ($n < 0) { return; } my $d = int( _sqrtint($n)/2 ); $n -= (4*$d+2)*$d; # to $n==0 on Y negative axis if ($n <= 2*$d+1) { ### bottom horizontal ... return ($n, -$d); } $n -= 4*$d+2; ### sides, remainder pos/neg from top ... return (-$n, - abs($n) + $d + 1); } # negative y, x=0 centres # [ 1, 2, 3 ] # [ 7, 21, 43 ] # n = (4*$y*$y + 2*abs($y) + 1) # # positive y, x=0 centres # [ 1, 2, 3 ] # [ 3, 13, 31 ] # n = (4*$d*$d + -2*$d + 1) # sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### xy_to_n(): "$x,$y" if ($y < 0 && abs($x) <= 2*-$y) { ### bottom horizontal return 4*$y*$y - 2*$y + $x + $self->{'n_start'}; } ### sides diagonal my $k = 2 * (abs($x) + $y); return $k*($k-1) - $x + $self->{'n_start'}; } # Each row N increases away from some midpoint. # Each column N increase away from some midpoint. # So maximum must be at one of the corners. # # maybe: # Minimum in row is at X=0 when Y>=0 # or at X=2*Y slope when Y<0 # Minimum in column is at Y=floor(X/2) when X<=0 # or at Y=-floor(X/2) when X>=0 # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; # ENHANCE-ME: find exact minimum return ($self->n_start, max ($self->xy_to_n($x1,$y1), $self->xy_to_n($x1,$y2), $self->xy_to_n($x2,$y1), $self->xy_to_n($x2,$y2))); } 1; __END__ =for stopwords pronic PlanePath Ryde Math-PlanePath OEIS =head1 NAME Math::PlanePath::PyramidSpiral -- integer points drawn around a pyramid =head1 SYNOPSIS use Math::PlanePath::PyramidSpiral; my $path = Math::PlanePath::PyramidSpiral->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes a pyramid shaped spiral, =cut # math-image --path=PyramidSpiral --all --output=numbers_dash =pod 31 3 / \ 32 13 30 2 / / \ \ 33 14 3 12 29 1 / / / \ \ \ 34 15 4 1--2 11 28 ... <- Y=0 / / / \ \ \ 35 16 5--6--7--8--9-10 27 52 -1 / / \ \ 36 17-18-19-20-21-22-23-24-25-26 51 -2 / \ 37-38-39-40-41-42-43-44-45-46-47-48-49-50 -3 ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 XThe perfect squares 1,4,9,16 fall one before the bottom left corner of each loop, and the Xpronic numbers 2,6,12,20,30,etc are the vertical upwards from X=1,Y=0. =head2 Square Spiral This spiral goes around at the same rate as the C. It's as if two corners are cut off (like the C) and two others extended (like the C). The net effect is the same looping rate but the points pushed around a bit. Taking points up to a perfect square shows the similarity. The two triangular cut-off corners marked by "."s are matched by the two triangular extensions. +--------------------+ 7x7 square | . . . 31 . . .| | . . 32 13 30 . .| | . 33 14 3 12 29 .| |34 15 4 1 2 11 28| 35|16 5 6 7 8 9 10|27 36 17|18 19 20 21 22 23 24|25 26 37 38 39|40 41 42 43 44 45 46|47 48 49 +--------------------+ =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, with the same shape etc. For example to start at 0, =cut # math-image --path=PyramidSpiral,n_start=0 --all --output=numbers_dash --size=35x16 =pod 12 n_start => 0 / \ 13 2 11 / / \ \ 14 3 0--1 10 / / \ 15 4--5--6--7--8--9 / 16-17-18-19-20-21-22-... =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::PyramidSpiral-Enew ()> =item C<$path = Math::PlanePath::PyramidSpiral-Enew (n_start =E $n)> Create and return a new pyramid spiral object. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each N in the path as centred in a square of side 1, so the entire plane is covered. =back =head1 OEIS This path is in Sloane's Online Encyclopedia of Integer Sequences as =over L (etc) =back n_start=1 (the default) A053615 abs(X), distance to next pronic, but starts n=0 A054552 N on X axis, 4n^2 - 3n + 1 A033951 N on South-East diagonal, 4n^2 + 3n + 1 A214250 sum N of eight surrounding cells A217013 permutation N of points in SquareSpiral order rotated +90 degrees A217294 inverse In the two permutations the pyramid spiral is conceived as starting to the left and the square spiral starting upwards. The paths here start in the same direction (both to the right), hence rotate 90 to adjust the orientation. n_start=0 A329116 X coordinate A329972 Y coordinate A053615 abs(X) A339265 dX-dY increments (runs +1,-1) A001107 N on X axis, decagonal numbers A002939 N on Y axis A033991 N on X negative axis A002943 N on Y negative axis A007742 N on diagonal South-West A033954 N on diagonal South-East, decagonal second kind n_start=2 A185669 N on diagonal South-East =head1 SEE ALSO L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/WythoffArray.pm0000644000175000017500000004634313774321446020231 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Classic Sequences # http://oeis.org/classic.html # # Clark Kimberling # http://faculty.evansville.edu/ck6/integer/intersp.html # # cf A175004 similar to wythoff but rows recurrence # r(n-1)+r(n-2)+1 extra +1 in each step # floor(n*phi+2/phi) # # cf Stolarsky round_nearest(n*phi) # A035506 stolarsky by diagonals # A035507 inverse # A007067 stolarsky first column # Maybe: # my ($x,$y) = $path->pair_to_xy($a,$b); # Return the $x,$y which has ($a,$b). # Advance $a,$b if before start of row. # Carlitz and Hoggatt "Fibonacci Representations", Fibonacci Quarterly, # volume 10, number 1, January 1972 # http://www.fq.math.ca/10-1.html # http://www.fq.math.ca/Scanned/10-1/carlitz1.pdf package Math::PlanePath::WythoffArray; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_sqrtint = \&Math::PlanePath::_sqrtint; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'bit_split_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant parameter_info_array => [ { name => 'x_start', display => 'X start', type => 'integer', default => 0, width => 3, description => 'Starting X coordinate.', }, { name => 'y_start', display => 'Y start', type => 'integer', default => 0, width => 3, description => 'Starting Y coordinate.', }, ]; use constant default_n_start => 1; use constant class_x_negative => 0; use constant class_y_negative => 0; sub x_minimum { my ($self) = @_; return $self->{'x_start'}; } sub y_minimum { my ($self) = @_; return $self->{'y_start'}; } use constant absdx_minimum => 1; use constant dir_maximum_dxdy => (3,-1); # N=4 to N=5 dX=3,dY=-1 #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'x_start'} ||= 0; $self->{'y_start'} ||= 0; return $self; } sub xy_is_visited { my ($self, $x, $y) = @_; return ((round_nearest($x) >= $self->{'x_start'}) && (round_nearest($y) >= $self->{'y_start'})); } #------------------------------------------------------------------------------ # 4 | 12 20 32 52 84 136 220 356 576 932 1508 # 3 | 9 15 24 39 63 102 165 267 432 699 1131 # 2 | 6 10 16 26 42 68 110 178 288 466 754 # 1 | 4 7 11 18 29 47 76 123 199 322 521 # Y=0 | 1 2 3 5 8 13 21 34 55 89 144 # +------------------------------------------------------- # X=0 1 2 3 4 5 6 7 8 9 10 # 13,8,5,3,2,1 # 4 = 3+1 -> 1 # 6 = 5+1 -> 2 # 9 = 8+1 -> 3 # 12 = 8+3+1 -> 3+1=4 # 14 = 13+1 -> 5 sub n_to_xy { my ($self, $n) = @_; ### WythoffArray n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n) || $n == 0) { return ($n,$n); } { # fractions on straight line between integer points my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } # f1+f0 > i # f0 > i-f1 # check i-f1 as the stopping point, so that if i=UV_MAX then won't # overflow a UV trying to get to f1>=i # my @fibs; { my $f0 = ($n * 0); # inherit bignum 0 my $f1 = $f0 + 1; # inherit bignum 1 while ($f0 <= $n-$f1) { ($f1,$f0) = ($f1+$f0,$f1); push @fibs, $f1; # starting $fibs[0]=1 } } ### @fibs # indices into fib[] which are the Fibonaccis adding up to $n my @indices; for (my $i = $#fibs; $i >= 0; $i--) { ### at: "n=$n f=".$fibs[$i] if ($n >= $fibs[$i]) { push @indices, $i; $n -= $fibs[$i]; ### sub: "$fibs[$i] to n=$n" --$i; } } ### @indices # X is low index, ie. how many low 0 bits in Zeckendorf form my $x = pop @indices; ### $x # Y is indices shifted down by $x and 2 more my $y = 0; my $shift = $x+2; foreach my $i (@indices) { ### y add: "ishift=".($i-$shift)." fib=".$fibs[$i-$shift] $y += $fibs[$i-$shift]; } ### $shift ### $y return ($x+$self->{'x_start'},$y+$self->{'y_start'}); } # phi = (sqrt(5)+1)/2 # (y+1)*phi = (y+1)*(sqrt(5)+1)/2 # = ((y+1)*sqrt(5)+(y+1))/2 # = (sqrt(5*(y+1)^2)+(y+1))/2 # # from x=0,y=0 # N = floor((y+1)*Phi) * Fib(x+2) + y*Fib(x+1) # sub xy_to_n { my ($self, $x, $y) = @_; ### WythoffArray xy_to_n(): "$x, $y" $x = round_nearest($x) - $self->{'x_start'}; $y = round_nearest($y) - $self->{'y_start'}; if ($x < 0 || $y < 0) { return undef; } my $zero = $x * 0 * $y; $x += 2; if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my @bits = bit_split_lowtohigh($x); ### @bits pop @bits; # discard high 1-bit my $yplus1 = $zero + $y+1; # inherit bigint from $x perhaps # spectrum(Y+1) so Y,Ybefore are notional two values at X=-2 and X=-1 my $ybefore = int((_sqrtint(5*$yplus1*$yplus1) + $yplus1) / 2); ### $ybefore # k=1, Fk1=F[k-1]=0, Fk=F[k]=1 my $Fk1 = $zero; my $Fk = 1 + $zero; my $add = -2; while (@bits) { ### remaining bits: @bits ### Fk1: "$Fk1" ### Fk: "$Fk" # two squares and some adds # F[2k+1] = 4*F[k]^2 - F[k-1]^2 + 2*(-1)^k # F[2k-1] = F[k]^2 + F[k-1]^2 # F[2k] = F[2k+1] - F[2k-1] # $Fk *= $Fk; $Fk1 *= $Fk1; my $F2kplus1 = 4*$Fk - $Fk1 + $add; $Fk1 += $Fk; # F[2k-1] my $F2k = $F2kplus1 - $Fk1; if (pop @bits) { # high to low $Fk1 = $F2k; # F[2k] $Fk = $F2kplus1; # F[2k+1] $add = -2; } else { # $Fk1 is F[2k-1] already $Fk = $F2k; # F[2k] $add = 2; } } ### final pair ... ### Fk1: "$Fk1" ### Fk: "$Fk" ### @bits return ($Fk*$ybefore + $Fk1*$y); } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### WythoffArray rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest($x1); $y1 = round_nearest($y1); $x2 = round_nearest($x2); $y2 = round_nearest($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < $self->{'x_start'} || $y2 < $self->{'y_start'}) { ### all outside first quadrant ... return (1, 0); } # bottom left into first quadrant $x1 = max($x1, $self->{'x_start'}); $y1 = max($y1, $self->{'y_start'}); return ($self->xy_to_n($x1,$y1), # bottom left $self->xy_to_n($x2,$y2)); # top right } 1; __END__ =for stopwords eg Ryde ie Math-PlanePath Wythoff Zeckendorf concecutive fibbinary bignum OEIS Stolarsky Morrison's Knott Generalising =head1 NAME Math::PlanePath::WythoffArray -- table of Fibonacci recurrences =head1 SYNOPSIS use Math::PlanePath::WythoffArray; my $path = Math::PlanePath::WythoffArray->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XXThis path is the Wythoff array by David R. Morrison =over "A Stolarsky Array of Wythoff Pairs", in Collection of Manuscripts Related to the Fibonacci Sequence, pages 134 to 136, The Fibonacci Association, 1980. L =back It's an array of Fibonacci recurrences which positions each N according to Zeckendorf base trailing zeros. =cut # math-image --path=WythoffArray --output=numbers --all --size=60x16 =pod 15 | 40 65 105 170 275 445 720 1165 1885 3050 4935 14 | 38 62 100 162 262 424 686 1110 1796 2906 4702 13 | 35 57 92 149 241 390 631 1021 1652 2673 4325 12 | 33 54 87 141 228 369 597 966 1563 2529 4092 11 | 30 49 79 128 207 335 542 877 1419 2296 3715 10 | 27 44 71 115 186 301 487 788 1275 2063 3338 9 | 25 41 66 107 173 280 453 733 1186 1919 3105 8 | 22 36 58 94 152 246 398 644 1042 1686 2728 7 | 19 31 50 81 131 212 343 555 898 1453 2351 6 | 17 28 45 73 118 191 309 500 809 1309 2118 5 | 14 23 37 60 97 157 254 411 665 1076 1741 4 | 12 20 32 52 84 136 220 356 576 932 1508 3 | 9 15 24 39 63 102 165 267 432 699 1131 2 | 6 10 16 26 42 68 110 178 288 466 754 1 | 4 7 11 18 29 47 76 123 199 322 521 Y=0 | 1 2 3 5 8 13 21 34 55 89 144 +------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 All rows have the Fibonacci style recurrence W(X+1) = W(X) + W(X-1) eg. X=4,Y=2 is N=42=16+26, sum of the two values to its left XX axis N=1,2,3,5,8,etc is the Fibonacci numbers. XThe row Y=1 above them N=4,7,11,18,etc is the Lucas numbers. XY axis N=1,4,6,9,12,etc is the "spectrum" of the golden ratio, meaning its multiples rounded down to an integer. phi = (sqrt(5)+1)/2 spectrum(k) = floor(phi*k) N on Y axis = Y + spectrum(Y+1) Eg. Y=5 N=5+floor((5+1)*phi)=14 The recurrence in each row starts as if the row was preceded by two values Y,spectrum(Y+1) which can be thought of adding to be Y+spectrum(Y+1) on the Y axis, then Y+2*spectrum(Y+1) in the X=1 column, etc. If the first two values in a row have a common factor then that factor remains in all subsequent sums. For example the Y=2 row starts with two even numbers N=6,N=10 so all N values in the row are even. Every N from 1 upwards occurs precisely once in the table. The recurrence means that in each row N grows roughly as a power phi^X, the same as the Fibonacci numbers. This means they become large quite quickly. =head2 Zeckendorf Base XThe N values are arranged according to trailing zero bits when N is represented in the Zeckendorf base. The Zeckendorf base expresses N as a sum of Fibonacci numbers, choosing at each stage the largest possible Fibonacci. For example Fibonacci numbers F[0]=1, F[1]=2, F[2]=3, F[3]=5, etc 45 = 34 + 8 + 3 = F[7] + F[4] + F[2] = 10010100 1-bits at 7,4,2 Here F[] is indexed by bit positions starting 0 for the least signficiant (which would be Fibonacci(2) in the usual Fibonacci indexing). The Wythoff array written in Zeckendorf base bits is =cut # This table printed by tools/wythoff-array-zeck.pl =pod 8 | 1000001 10000010 100000100 1000001000 10000010000 7 | 101001 1010010 10100100 101001000 1010010000 6 | 100101 1001010 10010100 100101000 1001010000 5 | 100001 1000010 10000100 100001000 1000010000 4 | 10101 101010 1010100 10101000 101010000 3 | 10001 100010 1000100 10001000 100010000 2 | 1001 10010 100100 1001000 10010000 1 | 101 1010 10100 101000 1010000 Y=0 | 1 10 100 1000 10000 +--------------------------------------------------- X=0 1 2 3 4 The X coordinate is the number of trailing zeros, or equivalently the index of the lowest Fibonacci used in the sum. For example in the X=3 column all the N's there have F[3]=5 as their lowest term. The Y coordinate is formed by removing the trailing "0100..00", ie. all trailing zeros plus the "01" above them. For example, N = 45 = Zeck 10010100 ^^^^ strip low zeros and "01" above them Y = Zeck(1001) = F[3]+F[0] = 5+1 = 6 The Zeckendorf form never has consecutive "11" bits, because after subtracting an F[k] the remainder is smaller than the next lower F[k-1]. Numbers with no concecutive "11" bits are sometimes called the fibbinary numbers (see L). Stripping low zeros is similar to what the C does with low zero digits in an ordinary base such as binary (see L). Doing it in the Zeckendorf base is like taking out powers of the golden ratio phi=1.618. =head2 Turn Sequence The path turns straight at N=2 and N=10 right N="...101" in Zeckendorf base left otherwise For example at N=12 the path turns to the right, since N=13 is on the right hand side of the vector from N=11 to N=12. It's almost 180-degrees around and back, but on the right hand side. 4 | 12 3 | 2 | 1 | 11 Y=0 | 13 +-------------------- X=0 1 2 3 4 5 This happens because N=12 is Zeckendorf "10101" which ends "..101". For such an ending N-1 is "..100" and N+1 is "..1000". So N+1 has more trailing zeros and hence bigger X smaller Y than N-1 has. The way the curve grows in a "concave" fashion means that therefore N+1 is on the right-hand side. | N N ending "..101" | | N+1 bigger X smaller Y | N-1 than N-1 | N+1 +-------------------- Cases for N ending "..000", "..010" and "..100" can be worked through to see that everything else turns left (or the initial N=2 and N=10 go straight ahead). On the Y axis all N values end "..01", with no trailing 0s. As noted above stripping that "01" from N gives the Y coordinate. Those N ending "..101" are therefore at Y coordinates which end "..1", meaning "odd" Y in Zeckendorf base. =head2 X,Y Start Options C $x> and C $y> give a starting position for the array. For example to start at X=1,Y=1 4 | 9 15 24 39 63 x_start => 1 3 | 6 10 16 26 42 y_start => 1 2 | 4 7 11 18 29 1 | 1 2 3 5 8 Y=0 | +---------------------- X=0 1 2 3 4 5 This can be helpful to work in rows and columns numbered from 1 instead of from 0. Numbering from X=1,Y=1 corresponds to the array in Morrison's paper above. =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::WythoffArray-Enew ()> =item C<$path = Math::PlanePath::WythoffArray-Enew (x_start =E $x, y_start =E $y)> Create and return a new path object. The default C and C are 0. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 1 and if C<$n E 1> then the return is an empty list. =item C<$n = $path-Exy_to_n ($x,$y)> Return the N point number at coordinates C<$x,$y>. If C<$xE0> or C<$yE0> (or the C or C options) then there's no N and the return is C. N values grow rapidly with C<$x>. Pass in a bignum type such as C for full precision. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head1 FORMULAS =head2 Rectangle to N Range Within each row increasing X is increasing N, and in each column increasing Y is increasing N. So in any rectangle the minimum N is in the lower left corner and the maximum N is in the upper right corner. | N max | ----------+ | | ^ | | | | | | | ----> | | +---------- | N min +------------------- =head1 OEIS The Wythoff array is in Sloane's Online Encyclopedia of Integer Sequences in various forms, =over L (etc) =back x_start=0,y_start=0 (the defaults) A035614 X, column numbered from 0 A191360 X-Y, the diagonal containing N A019586 Y, the row containing N A083398 max diagonal X+Y+1 for points 1 to N x_start=1,y_start=1 A035612 X, column numbered from 1 A003603 Y, vertical para-budding sequence A143299 Zeckendorf bit count in row Y A185735 left-justified row addition A186007 row subtraction A173028 row multiples A173027 row of n * Fibonacci numbers A220249 row of n * Lucas numbers A003622 N on Y axis, odd Zeckendorfs "..1" A020941 N on X=Y diagonal A139764 N dropped down to X axis, ie. N value on the X axis, being lowest Fibonacci used in the Zeckendorf form A000045 N on X axis, Fibonacci numbers skipping initial 0,1 A000204 N on Y=1 row, Lucas numbers skipping initial 1,3 A001950 N+1 of N on Y axis, anti-spectrum of phi A022342 N not on Y axis, even Zeckendorfs "..0" A000201 N+1 of N not on Y axis, spectrum of phi A003849 bool 1,0 if N on Y axis or not, being the Fibonacci word A035336 N in second column A160997 total N along anti-diagonals X+Y=k A188436 turn 1=right,0=left or straight, skip initial five 0s A134860 N positions of right turns, Zeckendorf "..101" A003622 Y coordinate of right turns, Zeckendorf "..1" A114579 permutation N at transpose Y,X A083412 permutation N by Diagonals from Y axis downwards A035513 permutation N by Diagonals from X axis upwards A064274 inverse permutation =head1 SEE ALSO L, L, L L, L, L, L, L Ron Knott, "Generalising the Fibonacci Series", L OEIS Classic Sequences, "The Wythoff Array and The Para-Fibonacci Sequence", L =head1 HOME PAGE L =head1 LICENSE Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/Diagonals.pm0000644000175000017500000004272413734026672017503 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Leading diagonal 2,8,18 = 2*d^2 # cf A185787 lists numerous seqs for rows,columns,diagonals package Math::PlanePath::Diagonals; use 5.004; use strict; use Carp 'croak'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; *_sqrtint = \&Math::PlanePath::_sqrtint; # uncomment this to run the ### lines # use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; use constant parameter_info_array => [ { name => 'direction', share_key => 'direction_downup', display => 'Direction', type => 'enum', default => 'down', choices => ['down','up'], choices_display => ['Down','Up'], description => 'Number points downwards or upwards along the diagonals.', }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), { name => 'x_start', display => 'X start', type => 'integer', default => 0, width => 3, description => 'Starting X coordinate.', }, { name => 'y_start', display => 'Y start', type => 'integer', default => 0, width => 3, description => 'Starting Y coordinate.', }, ]; sub x_minimum { my ($self) = @_; return $self->{'x_start'}; } sub y_minimum { my ($self) = @_; return $self->{'y_start'}; } sub dx_minimum { my ($self) = @_; return ($self->{'direction'} eq 'down' ? undef # down jumps back unlimited at bottom : -1); # up at most -1 across } sub dx_maximum { my ($self) = @_; return ($self->{'direction'} eq 'down' ? 1 # down at most +1 across : undef); # up jumps back across unlimited at top } sub dy_minimum { my ($self) = @_; return ($self->{'direction'} eq 'down' ? -1 # down at most -1 : undef); # up jumps down unlimited at top } sub dy_maximum { my ($self) = @_; return ($self->{'direction'} eq 'down' ? undef # down jumps up unlimited at bottom : 1); # up at most +1 } sub absdx_minimum { my ($self) = @_; return ($self->{'direction'} eq 'down' ? 0 # N=1 dX=0,dY=1 : 1); # otherwise always changes } sub absdy_minimum { my ($self) = @_; return ($self->{'direction'} eq 'down' ? 1 # otherwise always changes : 0); # N=1 dX=1,dY=0 } # within diagonal X+Y=k is dSum=0 # end of diagonal X=Xstart+k Y=Ystart # to X=Xstart Y=Ystart+k+1 # is (Xstart + Ystart+k+1) - (Xstart+k + Ystart) = 1 always, to next diagonal # use constant dsumxy_minimum => 0; # advancing diagonals use constant dsumxy_maximum => 1; sub ddiffxy_minimum { my ($self) = @_; return ($self->{'direction'} eq 'down' ? undef # "down" jumps back unlimited at bottom : -2); # NW diagonal } sub ddiffxy_maximum { my ($self) = @_; return ($self->{'direction'} eq 'down' ? 2 # SE diagonal : undef); # "up" jumps down unlimited at top } sub dir_minimum_dxdy { my ($self) = @_; return ($self->{'direction'} eq 'down' ? (0,1) # North, vertical at N=1 : (1,0)); # East, horiz at N=1 } sub dir_maximum_dxdy { my ($self) = @_; return ($self->{'direction'} eq 'down' ? (1,-1) # South-East at N=2 : (2,-1)); # ESE at N=3 } # If Xstart>0 or Ystart>0 then the origin is not reached. sub rsquared_minimum { my ($self) = @_; return (( $self->{'x_start'} > 0 ? $self->{'x_start'}**2 : 0) + ($self->{'y_start'} > 0 ? $self->{'y_start'}**2 : 0)); } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } my $direction = ($self->{'direction'} ||= 'down'); if (! ($direction eq 'up' || $direction eq 'down')) { croak "Unrecognised direction option: ", $direction; } $self->{'x_start'} ||= 0; $self->{'y_start'} ||= 0; return $self; } # start each diagonal at 0.5 earlier than the integer point # d = [ 0, 1, 2, 3, 4 ] # n = [ -0.5, 0.5, 2.5, 5.5, 9.5 ] # +1 +2 +3 +4 # 1 1 1 # N = (1/2 d^2 + 1/2 d - 1/2) # = (1/2*$d**2 + 1/2*$d - 1/2) # = ((1/2*$d + 1/2)*$d - 1/2) # d = -1/2 + sqrt(2 * $n + 5/4) # = (sqrt(8*$n + 5) -1)/2 sub n_to_xy { my ($self, $n) = @_; ### Diagonals n_to_xy(): "$n ".(ref $n || '') # adjust to N=0 at origin X=0,Y=0 $n = $n - $self->{'n_start'}; my $d; { my $r = 8*$n + 5; if ($r < 1) { ### which is N < -0.5 ... return; } ### sqrt of: "$r" ### sqrt is: sqrt(int($r))."" $d = int((_sqrtint($r) - 1) / 2); ### assert: $d >= 0 ### d: "$d" ### $d } # subtract for offset into diagonal, range -0.5 <= $n < $d+0.5 $n -= $d*($d+1)/2; ### subtract to n: "$n" my $y = -$n + $d; # $n first so BigFloat not BigInt from $d # and X=$n if ($self->{'direction'} eq 'up') { ($n,$y) = ($y,$n); } return ($n + $self->{'x_start'}, $y + $self->{'y_start'}); } # round y on an 0.5 downwards so that x=-0.5,y=0.5 gives n=1 which is the # inverse of n_to_xy() ... or is that inconsistent with other classes doing # floor() always? # # d(d+1)/2+1 # = (d^2 + d + 2) / 2 # sub xy_to_n { my ($self, $x, $y) = @_; ### xy_to_n(): $x, $y $x = $x - $self->{'x_start'}; # "-" operator to provoke warning if x==undef $y = $y - $self->{'y_start'}; if ($self->{'direction'} eq 'up') { ($x,$y) = ($y,$x); } $x = round_nearest ($x); $y = round_nearest (- $y); ### rounded ### $x ### $y if ($x < 0 || $y > 0) { return undef; # outside } my $d = $x - $y; ### $d return $d*($d+1)/2 + $x + $self->{'n_start'}; } # bottom-left to top-right, used by DiagonalsAlternating too # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } if ($x2 < $self->{'x_start'} || $y2 < $self->{'y_start'}) { return (1, 0); # rect all negative, no N } $x1 = max ($x1, $self->{'x_start'}); $y1 = max ($y1, $self->{'y_start'}); # exact range bottom left to top right return ($self->xy_to_n ($x1,$y1), $self->xy_to_n ($x2,$y2)); } 1; __END__ =for stopwords PlanePath Ryde Math-PlanePath OEIS triangulars sqrt =head1 NAME Math::PlanePath::Diagonals -- points in diagonal stripes =head1 SYNOPSIS use Math::PlanePath::Diagonals; my $path = Math::PlanePath::Diagonals->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path follows successive diagonals going from the Y axis down to the X axis. 6 | 22 5 | 16 23 4 | 11 17 24 3 | 7 12 18 ... 2 | 4 8 13 19 1 | 2 5 9 14 20 Y=0 | 1 3 6 10 15 21 +------------------------- X=0 1 2 3 4 5 XN=1,3,6,10,etc on the X axis is the triangular numbers. N=1,2,4,7,11,etc on the Y axis is the triangular plus 1, the next point visited after the X axis. =head2 Direction Option C 'up'> reverses the order within each diagonal to count upward from the X axis. =cut # math-image --path=Diagonals,direction=up --all --output=numbers =pod direction => "up" 5 | 21 4 | 15 20 3 | 10 14 19 ... 2 | 6 9 13 18 24 1 | 3 5 8 12 17 23 Y=0 | 1 2 4 7 11 16 22 +----------------------------- X=0 1 2 3 4 5 6 This is merely a transpose changing X,Y to Y,X, but it's the same as in C and can be handy to control the direction when combining C with some other path or calculation. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same diagonals sequence. For example to start at 0, =cut # math-image --path=Diagonals,n_start=0 --all --output=numbers --size=35x5 # math-image --path=Diagonals,n_start=0,direction=up --all --output=numbers --size=35x5 =pod n_start => 0, n_start=>0 direction=>"down" direction=>"up" 4 | 10 | 14 3 | 6 11 | 9 13 2 | 3 7 12 | 5 8 12 1 | 1 4 8 13 | 2 4 7 11 Y=0 | 0 2 5 9 14 | 0 1 3 6 10 +----------------- +----------------- X=0 1 2 3 4 X=0 1 2 3 4 XN=0,1,3,6,10,etc on the Y axis of "down" or the X axis of "up" is the triangular numbers Y*(Y+1)/2. =head2 X,Y Start Options C $x> and C $y> give a starting position for the diagonals. For example to start at X=1,Y=1 7 | 22 x_start => 1, 6 | 16 23 y_start => 1 5 | 11 17 24 4 | 7 12 18 ... 3 | 4 8 13 19 2 | 2 5 9 14 20 1 | 1 3 6 10 15 21 Y=0 | +------------------ X=0 1 2 3 4 5 The effect is merely to add a fixed offset to all X,Y values taken and returned, but it can be handy to have the path do that to step through non-negatives or similar. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::Diagonals-Enew ()> =item C<$path = Math::PlanePath::Diagonals-Enew (direction =E $str, n_start =E $n, x_start =E $x, y_start =E $y)> Create and return a new path object. The C option (a string) can be direction => "down" the default direction => "up" number upwards from the X axis =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n E 0.5> the return is an empty list, it being considered the path begins at 1. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each point C<$n> as a square of side 1, so the quadrant x>=-0.5, y>=-0.5 is entirely covered. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head1 FORMULAS =head2 X,Y to N The sum d=X+Y numbers each diagonal from d=0 upwards, corresponding to the Y coordinate where the diagonal starts (or X if direction=up). d=2 \ d=1 \ \ \ d=0 \ \ \ \ \ N is then given by d = X+Y N = d*(d+1)/2 + X + Nstart The d*(d+1)/2 shows how the triangular numbers fall on the Y axis when X=0 and Nstart=0. For the default Nstart=1 it's 1 more than the triangulars, as noted above. =cut # N = (X+Y)*(X+Y+1)/2 + X + Nstart # = [ (X+Y)*(X+Y+1) + 2X ]/2 + Nstart # = [ X^2 + XY + X + XY + Y^2 + Y + 2X ]/2 + Nstart # = [ X^2 + 3X + 2XY + Y + Y^2 ]/2 + Nstart # GP-Test my(d='x+'y); d*(d+1)/2 + 'x == (('x+'y)^2 + 3*'x + 'y)/2 =pod d can be expanded out to the following quite symmetric form. This almost suggests something parabolic but is still the straight line diagonals. X^2 + 3X + 2XY + Y + Y^2 N = ------------------------ + Nstart 2 (X+Y)^2 + 3X + Y = ---------------- + Nstart (using one square) 2 =head2 N to X,Y The above formula N=d*(d+1)/2 can be solved for d as d = floor( (sqrt(8*N+1) - 1)/2 ) # with n_start=0 For example N=12 is d=floor((sqrt(8*12+1)-1)/2)=4 as that N falls in the fifth diagonal. Then the offset from the Y axis NY=d*(d-1)/2 is the X position, X = N - d*(d+1)/2 Y = X - d In the code, fractional N is handled by imagining each diagonal beginning 0.5 back from the Y axis. That's handled by adding 0.5 into the sqrt, which is +4 onto the 8*N. d = floor( (sqrt(8*N+5) - 1)/2 ) # N>=-0.5 The X and Y formulas are unchanged, since N=d*(d-1)/2 is still the Y axis. But each diagonal d begins up to 0.5 before that and therefore X extends back to -0.5. =cut # GP-DEFINE \\ diagonal length # GP-DEFINE d(n) = floor( (sqrt(8*n+1) - 1)/2 ); # GP-Test vector(16,n,n--; d(n)) == \ # GP-Test [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5] # GP-DEFINE X(n) = my(d=d(n)); n - d*(d+1)/2; # GP-DEFINE Y(n) = my(d=d(n)); d - X(n); # GP-Test vector(16,n,n--; X(n)) == \ # GP-Test [0, 0, 1, 0, 1, 2, 0, 1, 2, 3, 0, 1, 2, 3, 4, 0] # GP-Test vector(16,n,n--; Y(n)) == \ # GP-Test [0, 1, 0, 2, 1, 0, 3, 2, 1, 0, 4, 3, 2, 1, 0, 5] # GP-Test my(l=List([])); \ # GP-Test for(d=0,3, for(x=0,d, my(y=d-x); x+y==d || error(); \ # GP-Test listput(l,[x,y]))); \ # GP-Test vector(#l,n,n--; [X(n),Y(n)]) == Vec(l) =pod =head2 Rectangle to N Range Within each row increasing X is increasing N, and in each column increasing Y is increasing N. So in a rectangle the lower left corner is minimum N and the upper right is maximum N. | \ \ N max | \ ----------+ | | \ |\ | |\ \ | | \| \ \ | | +---------- | N min \ \ \ +------------------------- =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back direction=down (the default) A002262 X coordinate, runs 0 to k A025581 Y coordinate, runs k to 0 A003056 X+Y coordinate sum, k repeated k+1 times A114327 Y-X coordinate diff A101080 HammingDist(X,Y) A127949 dY, change in Y coordinate A000124 N on Y axis, triangular numbers + 1 A001844 N on X=Y diagonal A185787 total N in row to X=Y diagonal A185788 total N in row to X=Y-1 A100182 total N in column to Y=X diagonal A101165 total N in column to Y=X-1 A185506 total N in rectangle 0,0 to X,Y either direction=up,down A097806 turn 0=straight, 1=not straight direction=down, x_start=1, y_start=1 A057555 X,Y pairs A057046 X at N=2^k A057047 Y at N=2^k direction=down, n_start=0 A057554 X,Y pairs A023531 dSum = dX+dY, being 1 at N=triangular+1 (and 0) A000096 N on X axis, X*(X+3)/2 A000217 N on Y axis, the triangular numbers A129184 turn 1=left,0=right A103451 turn 1=left or right,0=straight, but extra initial 1 A103452 turn 1=left,0=straight,-1=right, but extra initial 1 direction=up, n_start=0 A129184 turn 0=left,1=right direction=up, n_start=-1 A023531 turn 1=left,0=right direction=down, n_start=-1 A023531 turn 0=left,1=right in direction=up the X,Y coordinate forms are the same but swap X,Y either direction=up,down A038722 permutation N at transpose Y,X which is direction=down <-> direction=up either direction, x_start=1, y_start=1 A003991 X*Y coordinate product A003989 GCD(X,Y) greatest common divisor starting (1,1) A003983 min(X,Y) A051125 max(X,Y) either direction, n_start=0 A049581 abs(X-Y) coordinate diff A004197 min(X,Y) A003984 max(X,Y) A004247 X*Y coordinate product A048147 X^2+Y^2 A109004 GCD(X,Y) greatest common divisor starting (0,0) A004198 X bit-and Y A003986 X bit-or Y A003987 X bit-xor Y A156319 turn 0=straight,1=left,2=right A061579 permutation N at transpose Y,X which is direction=down <-> direction=up =head1 SEE ALSO L, L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/QuintetCentres.pm0000644000175000017500000005420213734026657020554 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Boundary of unit squares: # 4*3^n # QuintetCentres unit squares boundary a(n) = 4*3^(n-3) # 12,36,108,324,972 # match 12,36,108,324,972 # A003946 G.f.: (1+x)/(1-3*x). # A025579 a(1)=1, a(2)=2, a(n) = 4*3^(n-3) for n >= 3. # A027327 a(n) = Sum{(k+1)*T(n,m-k)}, 0<=k<=m, where m=0 for n=0,1; m=n for n >= 2; T given by A026120. package Math::PlanePath::QuintetCentres; use 5.004; use strict; use POSIX 'ceil'; #use List::Util 'min','max'; *min = \&Math::PlanePath::_min; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::SacksSpiral; *_rect_to_radius_range = \&Math::PlanePath::SacksSpiral::_rect_to_radius_range; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'round_up_pow'; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; use constant parameter_info_array => [ { name => 'arms', share_key => 'arms_4', display => 'Arms', type => 'integer', minimum => 1, maximum => 4, default => 1, width => 1, description => 'Arms', } ]; { my @x_negative_at_n = (undef, 112, 9, 2, 2); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } { my @y_negative_at_n = (undef, 2, 4, 6, 7); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; *_UNDOCUMENTED__dxdy_list = \&Math::PlanePath::_UNDOCUMENTED__dxdy_list_eight; { my @_UNDOCUMENTED__dxdy_list_at_n = (undef, 18, 14, 11, 11); sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $_UNDOCUMENTED__dxdy_list_at_n[$self->{'arms'}]; } } use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East # N=9 first straight, then for other arms 18,27,36 sub _UNDOCUMENTED__turn_any_straight_at_n { my ($self) = @_; return 9*$self->arms_count; } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(4, $self->{'arms'} || 1)); return $self; } my @rot_to_x = (0,0,-1,-1); my @rot_to_y = (0,1,1,0); my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); my @digit_reverse = (0,1,0,0,1); sub n_to_xy { my ($self, $n) = @_; ### QuintetCentres n_to_xy(): "arms=$self->{'arms'} $n" if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'}); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $zero = ($n * 0); # inherit BigInt 0 # arm as initial rotation my $rot = _divrem_mutate ($n, $self->{'arms'}); my @digits = digit_split_lowtohigh($n,5); my @sx; my @sy; { my $sx = $zero + $dir4_to_dx[$rot]; my $sy = $zero + $dir4_to_dy[$rot]; foreach (@digits) { push @sx, $sx; push @sy, $sy; # 2*(sx,sy) + rot+90(sx,sy) ($sx,$sy) = (2*$sx - $sy, 2*$sy + $sx); } ### @digits my $rev = 0; for (my $i = $#digits; $i >= 0; $i--) { # high to low ### digit: $digits[$i] if ($rev) { ### reverse: "$digits[$i] to ".(5 - $digits[$i]) $digits[$i] = 4 - $digits[$i]; } $rev ^= $digit_reverse[$digits[$i]]; ### now rev: $rev } } ### reversed n: @digits my $x = my $y = my $ox = my $oy = $zero; while (defined (my $digit = shift @digits)) { # low to high my $sx = shift @sx; my $sy = shift @sy; ### at: "$x,$y digit $digit side $sx,$sy" # if ($rot & 2) { # ($sx,$sy) = (-$sx,-$sy); # } # if ($rot & 1) { # ($sx,$sy) = (-$sy,$sx); # } if ($digit == 0) { $x -= $sx; # left at 180 $y -= $sy; } elsif ($digit == 1) { # centre ($x,$y) = (-$y,$x); # rotate -90 ### rotate to: "$x,$y" # $rot--; } elsif ($digit == 2) { $x += $sy; # down at -90 $y -= $sx; ### offset to: "$x,$y" } elsif ($digit == 3) { ($x,$y) = (-$y,$x); # rotate -90 $x += $sx; # right at 0 $y += $sy; # $rot++; } else { # $digit == 4 ($x,$y) = ($y,-$x); # rotate +90 $x -= $sy; # up at +90 $y += $sx; # $rot++; } $ox += $sx; $oy += $sy; } ### final: "$x,$y origin $ox,$oy" return ($x + $ox + $rot_to_x[$rot], $y + $oy + $rot_to_y[$rot]); } # modulus 2*X+Y # 3 # 0 2 4 # 1 # # 0 is X=0,Y=0 # my @modulus_to_x = (0,1,1,1,2); my @modulus_to_y = (0,-1,0,1,0); my @modulus_to_digit = (0,2,1,4,3, 0,0,10,30,20, # 0 base 0,4,3,1,2, 0,10,50,40,10, # 10 4,0,1,3,2, 60,20,40,50,20, # 20 rotated +90 2,1,3,4,0, 30,60,0,30,50, # 30 1,0,3,2,4, 30,20,70,40,40, # 40 3,4,1,2,0, 70,10,30,50,50, # 50 rotated +180 4,2,3,0,1, 60,60,20,70,10, # 60 2,3,1,0,4, 70,0,60,70,40, # 70 rotated +270 ); sub xy_to_n { my ($self, $x, $y) = @_; ### QuintetCentres xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); foreach my $overflow (2*$x + 2*$y, 2*$x - 2*$y) { if (is_infinite($overflow)) { return $overflow; } } # my $level_limit = log($x*$x + $y*$y + 1) * 1 * 2; # if (is_infinite($level_limit)) { return $level_limit; } my @digits; my $arm; my $state; for (;;) { # if ($level_limit-- < 0) { # ### oops, level limit ... # return undef; # } if ($x == 0) { if ($y == 0) { ### found first arm 0,0 ... $arm = 0; $state = 0; last; } if ($y == 1) { ### found second arm 0,1 ... $arm = 1; $state = 20; last; } } elsif ($x == -1) { if ($y == 1) { ### found third arm -1,1 ... $arm = 2; $state = 50; last; } if ($y == 0) { ### found fourth arm -1,0 ... $arm = 3; $state = 70; last; } } my $m = (2*$x + $y) % 5; ### at: "$x,$y digits=".join(',',@digits) ### mod remainder: $m $x -= $modulus_to_x[$m]; $y -= $modulus_to_y[$m]; push @digits, $m; ### digit: "$m to $x,$y" ### shrink to: ((2*$x + $y) / 5).','.((2*$y - $x) / 5) ### assert: (2*$x + $y) % 5 == 0 ### assert: (2*$y - $x) % 5 == 0 # shrink # (2 -1) inverse (2 1) # (1 2) (-1 2) # ($x,$y) = ((2*$x + $y) / 5, (2*$y - $x) / 5); } ### @digits my $arms = $self->{'arms'}; if ($arm >= $arms) { return undef; } my $n = 0; foreach my $m (reverse @digits) { # high to low ### $m ### digit: $modulus_to_digit[$state + $m] ### state: $state ### next state: $modulus_to_digit[$state+5 + $m] $n = 5*$n + $modulus_to_digit[$state + $m]; $state = $modulus_to_digit[$state+5 + $m]; } ### final n along arm: $n return $n*$arms + $arm; } #------------------------------------------------------------------------------ # whole plane covered when arms==4 sub xy_is_visited { my ($self, $x, $y) = @_; return ($self->{'arms'} == 4 || defined($self->xy_to_n($x,$y))); } #------------------------------------------------------------------------------ # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### QuintetCurve rect_to_n_range(): "$x1,$y1 $x2,$y2" my ($r_lo, $r_hi) = _rect_to_radius_range($x1,$y1, $x2,$y2); $r_hi *= 2; my $level_plus_1 = ceil( log(max(1,$r_hi/4)) / log(sqrt(5)) ) + 2; # Simple over-estimate would be: return (0, 5**$level_plus_1); my $level_limit = $level_plus_1; ### $level_limit if (is_infinite($level_limit)) { return ($level_limit,$level_limit); } $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### sorted range: "$x1,$y1 $x2,$y2" my $rect_dist = sub { my ($x,$y) = @_; my $xd = ($x < $x1 ? $x1 - $x : $x > $x2 ? $x - $x2 : 0); my $yd = ($y < $y1 ? $y1 - $y : $y > $y2 ? $y - $y2 : 0); return ($xd*$xd + $yd*$yd); }; my $arms = $self->{'arms'}; ### $arms my $n_lo; { my @hypot = (4); my $top = 0; for (;;) { ARM_LO: foreach my $arm (0 .. $arms-1) { my $i = 0; my @digits; if ($top > 0) { @digits = ((0)x($top-1), 1); } else { @digits = (0); } for (;;) { my $n = 0; foreach my $digit (reverse @digits) { # high to low $n = 5*$n + $digit; } $n = $n*$arms + $arm; ### lo consider: "i=$i digits=".join(',',reverse @digits)." is n=$n" my ($nx,$ny) = $self->n_to_xy($n); my $nh = &$rect_dist ($nx,$ny); if ($i == 0 && $nh == 0) { ### lo found inside: $n if (! defined $n_lo || $n < $n_lo) { $n_lo = $n; } next ARM_LO; } if ($i == 0 || $nh > $hypot[$i]) { ### too far away: "nxy=$nx,$ny nh=$nh vs ".$hypot[$i] while (++$digits[$i] > 4) { $digits[$i] = 0; if (++$i <= $top) { ### backtrack up ... } else { ### not found within this top and arm, next arm ... next ARM_LO; } } } else { ### lo descend ... ### assert: $i > 0 $i--; $digits[$i] = 0; } } } # if an $n_lo was found on any arm within this $top then done if (defined $n_lo) { last; } ### lo extend top ... if (++$top > $level_limit) { ### nothing below level limit ... return (1,0); } $hypot[$top] = 5 * $hypot[$top-1]; } } my $n_hi = 0; ARM_HI: foreach my $arm (reverse 0 .. $arms-1) { my @digits = ((4) x $level_limit); my $i = $#digits; for (;;) { my $n = 0; foreach my $digit (reverse @digits) { # high to low $n = 5*$n + $digit; } $n = $n*$arms + $arm; ### hi consider: "arm=$arm i=$i digits=".join(',',reverse @digits)." is n=$n" my ($nx,$ny) = $self->n_to_xy($n); my $nh = &$rect_dist ($nx,$ny); if ($i == 0 && $nh == 0) { ### hi found inside: $n if ($n > $n_hi) { $n_hi = $n; next ARM_HI; } } if ($i == 0 || $nh > (4 * 5**$i)) { ### too far away: "$nx,$ny nh=$nh vs ".(4 * 5**$i) while (--$digits[$i] < 0) { $digits[$i] = 4; if (++$i < $level_limit) { ### hi backtrack up ... } else { ### hi nothing within level limit for this arm ... next ARM_HI; } } } else { ### hi descend ### assert: $i > 0 $i--; $digits[$i] = 4; } } } if ($n_hi == 0) { ### oops, lo found but hi not found $n_hi = $n_lo; } return ($n_lo, $n_hi); } #------------------------------------------------------------------------------ # levels # level=0 # level=1 0 to 4 # level=2 0 to 24 is 5^level-1 # # multiple arms the same full points of arms=1 # so arms*5^level points numbered starting 0 # = 5^level*arms - 1 sub level_to_n_range { my ($self, $level) = @_; return (0, 5**$level * $self->{'arms'} - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, $self->{'arms'}); my ($pow, $exp) = round_up_pow ($n+1, 5); return $exp; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Mandelbrot Math-PlanePath OEIS =head1 NAME Math::PlanePath::QuintetCentres -- self-similar "plus" shape centres =head1 SYNOPSIS use Math::PlanePath::QuintetCentres; my $path = Math::PlanePath::QuintetCentres->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This a self-similar curve tracing out a "+" shape like the C but taking the centre of each square visited by that curve. 92 12 / | 124-... 93 91--90 88 11 | \ \ / \ 122-123 120 102 94 82 89 86--87 10 \ / | / | / / | | 121 119 103 101-100 95 81 83--84--85 9 \ \ \ \ \ 114-115-116 118 104 32 99--98 96 80 78 8 | |/ / / | |/ |/ \ 112-113 110 117 105 31 33--34 97 36 79 76--77 7 \ / \ \ \ \ / \ | 111 109-108 106 30 42 35 38--37 75 6 |/ / / | | / 107 29 43 41--40--39 74 5 \ \ | 24--25--26 28 44 46 72--73 70 68 4 | |/ |/ \ \ / \ / \ 22--23 20 27 18 45 48--47 71 56 69 66--67 3 \ / \ / \ | / \ | 21 6 19 16--17 49 54--55 58--57 65 2 / \ | | \ | / 4-- 5 8-- 7 15 50--51 53 59 64 1 \ | / |/ | \ 0-- 1 3 9 14 52 60--61 63 <- Y=0 |/ | \ |/ 2 10--11 13 62 -1 |/ 12 -2 ^ -1 X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 The base figure is the initial the initial N=0 to N=4. It fills a "+" shape as ..... . . . 4 . . \. ........\.... . . .\ . . 0---1 . 3 . . . | ./ . ......|./.... . |/. . 2 . . . ..... =head2 Arms The optional C parameter can give up to four copies of the curve, each advancing successively. For example C4> is as follows. Notice the N=4*k points are the plain curve, and N=4*k+1, N=4*k+2 and N=4*k+3 are rotated copies of it. 69 ... 7 / | \ 121 113 73 65--61 53 120 6 / \ / \ \ \ / \ / ... 117 105-109 77 29 57 45--49 116 5 | / / | | \ 101 81 25 33--37--41 96-100-104 112 4 | \ \ | |/ 50 97--93 85 21 13 88--92 80 108 72 3 / | |/ |/ \ \ / \ / \ 54 46--42 89 10 17 5-- 9 84 24 76 64--68 2 \ | / | | / \ | 58 38 14 6-- 2 1 16--20 32--28 60 1 / | \ \ | / 62 30--34 22--18 3 0-- 4 12 36 56 <- Y=0 | \ / | |/ | \ 70--66 78 26 86 11-- 7 19 8 91 40--44 52 -1 \ / \ / \ \ / | / | |/ 74 110 82 94--90 15 23 87 95--99 48 -2 / | | \ \ | 114 106-102--98 43--39--35 27 83 103 -3 \ | |/ / | 118 51--47 59 31 79 111-107 119 ... -4 / \ / \ \ \ / \ / 122 55 63--67 75 115 123 -5 \ |/ ... 71 -6 ^ -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 The pattern an ever expanding "+" shape with first cell N=0 at the origin. The further parts are effectively as follows, +---+ | | +---+--- +---+ | | | +---+ +---+ +---+ | 2 | 1 | | +---+ +---+---+ +---+ | | 3 | 0 | +---+ +---+ +---+ | | | +---+ +---+---+ | | +---+ At higher replication levels the sides become wiggly and spiralling, but they're symmetric and mesh to fill the plane. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::QuintetCentres-Enew ()> =item C<$path = Math::PlanePath::QuintetCentres-Enew (arms =E $a)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =item C<$n = $path-En_start()> Return 0, the first N in the path. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> In the current code the returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle, but don't rely on that yet since finding the exact range is a touch on the slow side. (The advantage of which though is that it helps avoid very big ranges from a simple over-estimate.) =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 5**$level - 1)>, or for multiple arms return C<(0, $arms * 5**$level - 1)>. There are 5^level points in a level, or arms*5^level for multiple arms, numbered starting from 0. =back =head1 FORMULAS =head2 X,Y to N The C calculation is similar to the C. For a given X,Y a modulo 5 remainder is formed m = (2*X + Y) mod 5 This distinguishes the five squares making up the base figure. For example in the base N=0 to N=4 part the m values are +-----+ | m=3 | 1 +-----+-----+-----+ | m=0 | m=2 | m=4 | <- Y=0 +-----+-----+-----+ | m=1 | -1 +-----+ X=0 1 2 From this remainder X,Y can be shifted down to the 0 position. That position corresponds to a vector multiple of X=2,Y=1 and 90-degree rotated forms of that vector. That vector can be divided out and X,Y shrunk with Xshrunk = (Y + 2*X) / 5 Yshrunk = (2*Y - X) / 5 If X,Y are considered a complex integer X+iY the effect is a remainder modulo 2+i, subtract that to give a multiple of 2+i, then divide by 2+i. The vector X=2,Y=1 or 2+i is because that's the N=5 position after the base shape. The remainders can then be mapped to base 5 digits of N going from high to low and making suitable rotations for the sub-part orientation of the curve. The remainders alone give a traversal in the style of C. Applying suitable rotations produces the connected path of C. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A099456 level Y end, being Im((2+i)^k) arms=2 A139011 level Y end, being Re((2+i)^k) =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/DiamondArms.pm0000644000175000017500000002446713734026672020004 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=DiamondArms --lines --scale=10 # math-image --path=DiamondArms --all --output=numbers_dash # math-image --path=DiamondArms --values=Polygonal,polygonal=8 # # RepdigitsAnyBase fall on 14 or 15 lines ... # package Math::PlanePath::DiamondArms; use 5.004; use strict; #use List::Util 'min', 'max'; *min = \&Math::PlanePath::_min; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::PlanePath::Base::Generic 'round_nearest'; use Math::PlanePath::DiamondSpiral; # uncomment this to run the ### lines #use Devel::Comments; use constant arms_count => 4; use constant xy_is_visited => 1; use constant x_negative_at_n => 8; use constant y_negative_at_n => 5; use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant 1.02 _UNDOCUMENTED__dxdy_list => (1,1, # NE diagonals -1,1, # NW -1,-1, # SW 1,-1); # SE use constant absdx_minimum => 1; use constant absdy_minimum => 1; use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_minimum_dxdy => (1,1); # North-East use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_right => 0; # only left or straight #------------------------------------------------------------------------------ # 28 # 172 +144 # 444 +272 +128 # 844 +400 +128 # [ 0, 1, 2, 3,], # [ 0, 1, 3, 6 ], # N = (1/2 d^2 + 1/2 d) # = (1/2*$d**2 + 1/2*$d) # = ($d+1)*$d/2 # d = -1/2 + sqrt(2 * $n + 1/4) # = (-1 + sqrt(8*$n + 1))/2 sub n_to_xy { my ($self, $n) = @_; ### DiamondArms n_to_xy(): $n if ($n < 1) { return; } $n -= 1; my $frac; { my $int = int($n); $frac = $n - $int; $n = $int; # BigFloat int() gives BigInt, use that } # arm as initial rotation my $rot = _divrem_mutate($n,4); ### $n # if (($rot%4) != 3) { # return; # } my $d = int ((-1 + _sqrtint(8*$n+1)) / 2); ### d frac: ((-1 + sqrt(8*$n + 1)) / 2) ### $d ### base: $d*($d+1)/2 $n -= $d*($d+1)/2; ### remainder: $n ### assert: $n <= $d my $x = ($frac + $n) - $d; my $y = - ($frac + $n); ### unrot: "$x,$y" $rot = ($rot + $d) % 4; ### $rot if ($rot == 1) { ($x,$y) = (1-$y, $x); # rotate +90 and right } elsif ($rot == 2) { ($x,$y) = (1-$x, 1-$y); # rotate 180 and up+right } elsif ($rot == 3) { ($x,$y) = ($y, 1-$x); # rotate +90 and up } return ($x,$y); } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### DiamondArms xy_to_n: "$x,$y" my $rot = 0; # eg. y=2 have (0<=>$y)-$y == -1-2 == -3 if ($y >= ($x > 0)) { ### above horizontal, rot -180 ... $rot = 2; $x = 1-$x; # rotate 180 and offset $y = 1-$y; } if ($x > 0) { ### right of vertical, rot -90 ... $rot++; ($x,$y) = ($y,1-$x); # rotate -90 and offset } # horizontal negative X axis # d = -x + -y # d=0 n=1 # d=4 n=41 # d=8 n=145 # d=12 n=313 # N = (2 d^2 + 2 d + 1) # = (2*$d**2 + 2*$d + 1) # = ((2*$d + 2)*$d + 1) # my $d = -$x - $y; ### xy: "$x,$y" ### $d ### $rot ### base: ((2*$d + 2)*$d + 1) ### offset: -4 * $y ### rot d mod: (($rot+$d+2) % 4) return ((2*$d + 2)*$d + 1) - 4*$y + (($rot-$d) % 4); } # d = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ], # Nmax = [ 9, 25, 49, 81, 121, 169, 225, 289, 361 ] # being the N=5 arm one spot before the corner of each run # N = (4 d^2 + 4 d + 1) # = (2d+1)^2 # = ((4*$d + 4)*$d + 1) # or for d-1 # N = (4 d^2 - 4 d + 1) # = (2d-1)^2 # = ((4*$d - 4)*$d + 1) # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); my $x = (($x1<0) == ($x2<0) ? min(abs($x1),abs($x2)) : 0); my $y = (($y1<0) == ($y2<0) ? min(abs($y1),abs($y2)) : 0); my $d = max(0, $x + $y - 2); return (((2*$d + 2)*$d + 1), max ($self->xy_to_n($x1,$y1), $self->xy_to_n($x1,$y2), $self->xy_to_n($x2,$y1), $self->xy_to_n($x2,$y2))); } 1; __END__ # 25 4 # / \ # 29 14 21 ... 3 # / / \ \ \ # ... 18 7 10 17 32 2 # / / \ \ \ \ # 22 11 4 3 6 13 28 1 # / / / / / / # 26 15 8 1 2 9 24 <- Y=0 # \ \ \ \ / / # 30 19 12 5 20 ... -1 # \ \ \ / / # ... 23 16 31 -2 # \ / # 27 -3 # 25 4 # / \ # 29 14 21 ... 3 # / / \ \ \ # ... 18 7 10 17 32 2 # / / \ \ \ \ # 22 11 4 3 6 13 28 1 # / / / / / / # 26 15 8 1 2 9 24 <- Y=0 # \ \ \ \ / / # 30 19 12 5 20 ... -1 # \ \ \ / / # ... 23 16 31 -2 # \ / # 27 -3 =for stopwords Math-PlanePath Ryde ie =head1 NAME Math::PlanePath::DiamondArms -- four spiral arms =head1 SYNOPSIS use Math::PlanePath::DiamondArms; my $path = Math::PlanePath::DiamondArms->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path follows four spiral arms, each advancing successively in a diamond pattern, 25 ... 4 29 14 21 36 3 33 18 7 10 17 32 2 ... 22 11 4 3 6 13 28 1 26 15 8 1 2 9 24 ... <- Y=0 30 19 12 5 20 35 -1 34 23 16 31 -2 ... 27 -3 ^ -3 -2 -1 X=0 1 2 3 4 Each arm makes a spiral widening out by 4 each time around, thus leaving room for four such arms. Each arm loop is 64 longer than the preceding loop. For example N=13 to N=85 below is 84-13=72 points, and the next loop N=85 to N=221 is 221-85=136 which is an extra 64, ie. 72+64=136. 25 ... / \ \ 29 . 21 . . . 93 / \ \ 33 . . . 17 . . . 89 / \ \ 37 . . . . . 13 . . . 85 / / / 41 . . . 1 . 9 . . . 81 \ \ / / 45 . . . 5 . . . 77 \ / 49 . . . . . 73 \ / 53 . . . 69 \ / 57 . 65 \ / 61 Each arm is N=4*k+rem for a remainder rem=0,1,2,3, so sequences related to multiples of 4 or with a modulo 4 pattern may fall on particular arms. The starts of each arm N=1,2,3,4 are at X=0 or 1 and Y=0 or 1, .. \ 4 3 .. Y=1 / / .. 1 2 <- Y=0 \ .. ^ ^ X=0 X=1 They could be centred around the origin by taking X-1/2,Y-1/2 so for example N=1 would be at -1/2,-1/2. But the it's done as N=1 at 0,0 to stay in integers. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::DiamondArms-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n E 1> the return is an empty list, as the path starts at 1. Fractional C<$n> gives a point on the line between C<$n> and C<$n+4>, that C<$n+4> being the next point on the same spiralling arm. This is probably of limited use, but arises fairly naturally from the calculation. =back =head2 Descriptive Methods =over =item C<$arms = $path-Earms_count()> Return 4. =back =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/KochSquareflakes.pm0000644000175000017500000004633213734026667021040 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::KochSquareflakes; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem = \&Math::PlanePath::_divrem; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh'; # uncomment this to run the ### lines #use Devel::Comments; use constant n_frac_discontinuity => 0; use constant parameter_info_array => [ { name => 'inward', display => 'Inward', type => 'boolean', default => 0, description => 'Whether to direct the sides of the square inward, rather than outward.', } ]; use constant x_negative_at_n => 1; use constant y_negative_at_n => 1; use constant sumabsxy_minimum => 1; use constant rsquared_minimum => 0.5; # minimum X=0.5,Y=0.5 # jump across rings is South-West, so use constant dx_maximum => 1; use constant dy_maximum => 1; use constant dsumxy_maximum => 2; # diagonal NE use constant ddiffxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant dir_maximum_dxdy => (1,-1); # South-East # N=1,2,3,4 gcd(1/2,1/2) = 1/2 use constant gcdxy_minimum => 1/2; use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ # level 0 inner square # sidelen = 4^level # ring points 4*4^level # Nend = 4 * [ 1 + ... + 4^level ] # = 4 * (4^(level+1) - 1) / 3 # = (4^(level+2) - 4) / 3 # Nstart = Nend(level-1) + 1 # = (4^(level+1) - 4) / 3 + 1 # = (4^(level+1) - 4 + 3) / 3 # = (4^(level+1) - 1) / 3 # # level Nstart Nend # 0 (4-1)/3=1 (16-4)/3=12/3=4 # 1 (16-1)/3=15/3=5 (64-4)/3=60/3=20 # 2 (64-1)/3=63/3=21 (256-4)/3=252/3=84 # 3 (256-1)/3=255/3=85 # sub n_to_xy { my ($self, $n) = @_; ### KochSquareflakes n_to_xy(): $n if ($n < 1) { return; } my $frac; { my $int = int($n); $frac = $n - $int; $n = $int; # BigFloat int() gives BigInt, use that } # (4^(level+1) - 1) / 3 = N # 4^(level+1) - 1 = 3*N # 4^(level+1) = 3*N+1 # my ($pow,$level) = round_down_pow (3*$n + 1, 4); ### $level ### $pow if (is_infinite($level)) { return ($level,$level); } # Nstart = (4^(level+1)-1)/3 with $power=4^(level+1) here # $n -= ($pow-1)/3; ### base: ($pow-1)/3 ### next base would be: (4*$pow-1)/3 ### n remainder from base: $n my $sidelen = $pow/4; (my $rot, $n) = _divrem ($n, $sidelen); # high part is rot ### $sidelen ### n remainder: $n ### $rot ### assert: $n>=0 ### assert: $n < 4 ** $level my @horiz = (1); my @diag = (1); my $i = 0; while (--$level > 0) { $horiz[$i+1] = 2*$horiz[$i] + 2*$diag[$i]; $diag[$i+1] = $horiz[$i] + 2*$diag[$i]; ### horiz: $horiz[$i+1] ### diag: $diag[$i+1] $i++; } ### horiz: join(', ',@horiz) ### $i my $x = my $y = ($n * 0) + $horiz[$i]/-2; # inherit bignum if ($rot & 1) { ($x,$y) = (-$y,$x); } if ($rot & 2) { $x = -$x; $y = -$y; } $rot *= 2; my $inward = $self->{'inward'}; my @digits = digit_split_lowtohigh($n,4); while ($i > 0) { $i--; my $digit = $digits[$i] || 0; my ($dx, $dy, $drot); if ($digit == 0) { $dx = 0; $dy = 0; $drot = 0; } elsif ($digit == 1) { if ($rot & 1) { $dx = $diag[$i]; $dy = $diag[$i]; } else { $dx = $horiz[$i]; $dy = 0; } $drot = ($inward ? 1 : -1); } elsif ($digit == 2) { if ($rot & 1) { if ($inward) { $dx = $diag[$i]; $dy = $diag[$i] + $horiz[$i]; } else { $dx = $diag[$i] + $horiz[$i]; $dy = $diag[$i]; } } else { $dx = $horiz[$i] + $diag[$i]; $dy = $diag[$i]; unless ($inward) { $dy = -$dy; } } $drot = ($inward ? -1 : 1); } elsif ($digit == 3) { if ($rot & 1) { $dx = $dy = $diag[$i] + $horiz[$i]; } else { $dx = $horiz[$i] + 2*$diag[$i]; $dy = 0; } $drot = 0; } ### delta: "$dx,$dy rot=$rot drot=$drot" if ($rot & 2) { ($dx,$dy) = (-$dy,$dx); } if ($rot & 4) { $dx = -$dx; $dy = -$dy; } ### delta with rot: "$dx,$dy" $x += $dx; $y += $dy; $rot += $drot; } { my $dx = $frac; my $dy = ($rot & 1 ? $frac : 0); if ($rot & 2) { ($dx,$dy) = (-$dy,$dx); } if ($rot & 4) { $dx = -$dx; $dy = -$dy; } $x = $dx + $x; $y = $dy + $y; } return ($x,$y); } my @inner_to_n = (1,2,4,3); sub xy_to_n { my ($self, $x, $y) = @_; ### KochSquareflakes xy_to_n(): "$x, $y" # +/- 0.75 if (4*$x < 3 && 4*$y < 3 && 4*$x >= -3 && 4*$y >= -3) { return $inner_to_n[($x >= 0) + 2*($y >= 0)]; } $x = round_nearest($x); $y = round_nearest($y); # quarter curve segment and high digit my $n; { my $negx = -$x; if (($y > 0 ? $x > $y : $x >= $y)) { ### below leading diagonal ... if ($negx > $y) { ### bottom quarter ... $n = 1; } else { ### right quarter ... $n = 2; ($x,$y) = ($y, $negx); # rotate -90 } } else { ### above leading diagonal if ($y > $negx) { ### top quarter ... $n = 3; $x = $negx; # rotate 180 $y = -$y; } else { ### right quarter ... $n = 4; ($x,$y) = (-$y, $x); # rotate +90 } } } $y = -$y; ### rotate to: "$x,$y n=$n" if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my @horiz; my @diag; my $horiz = 1; my $diag = 1; for (;;) { push @horiz, $horiz; push @diag, $diag; my $offset = $horiz+$diag; my $nextdiag = $offset + $diag; # horiz + 2*diag ### $horiz ### $diag ### $offset ### $nextdiag if ($y <= $nextdiag) { ### found level at: "top=$nextdiag vs y=$y" $y -= $offset; $x += $offset; last; } $horiz = 2*$offset; # 2*horiz+2*diag $diag = $nextdiag; } ### base subtract to: "$x,$y" if ($self->{'inward'}) { $y = -$y; ### inward invert to: "$x,$y" } ### origin based side: "$x,$y horiz=$horiz diag=$diag with levels ".scalar(@horiz) # loop 4*1, 4*4, 4*4^2 etc, extra +1 on the digits to include that in the sum # my $slope; while (@horiz) { ### at: "$x,$y slope=".($slope||0)." n=$n" $horiz = pop @horiz; $diag = pop @diag; $n *= 4; if ($slope) { if ($y < $diag) { ### slope digit 0 ... $n += 1; } else { $x -= $diag; $y -= $diag; ### slope not digit 0, move to: "$x,$y" if ($y < $horiz) { ### digit 1 ... $n += 2; ($x,$y) = ($y, -$x); # rotate -90 $slope = 0; } else { $y -= $horiz; ### slope not digit 1, move to: "$x,$y" if ($x < $horiz) { ### digit 2 ... $n += 3; $slope = 0; } else { ### digit 3 ... $n += 4; $x -= $horiz; } } } } else { if ($x < $horiz) { ### digit 0 ... $n += 1; } else { $x -= $horiz; ### not digit 0, move to: "$x,$y" if ($x < $diag) { ### digit 1 ... $n += 2; $slope = 1; } else { $x -= $diag; ### not digit 1, move to: "$x,$y" if ($x < $diag) { ### digit 2 ... $n += 3; $slope = 1; ($x,$y) = ($diag-$y, $x); # offset and rotate +90 } else { ### digit 3 ... $n += 4; $x -= $diag; } } } } } ### final: "$x,$y n=$n" if ($x == 0 && $y == 0) { return $n; } else { return undef; } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### KochSquareflakes rect_to_n_range(): "$x1,$y1 $x2,$y2" foreach ($x1,$y1, $x2,$y2) { if (is_infinite($_)) { return (0, $_); } $_ = abs(round_nearest($_)); } if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } my $max = ($x2 > $y2 ? $x2 : $y2); # Nend = 4 * [ 1 + ... + 4^level ] # = 4 + 16 + ... + 4^(level+1) # my $horiz = 4; my $diag = 3; my $nhi = 4; for (;;) { $nhi += 1; $nhi *= 4; my $nextdiag = $horiz + 2*$diag; if (($self->{'inward'} ? $horiz : $nextdiag) >= 2*$max) { return (1, $nhi); } $horiz = $nextdiag + $horiz; # 2*$horiz + 2*$diag; $diag = $nextdiag; } } #------------------------------------------------------------------------------ # Nstart = (4^(k+1) - 1)/3 # Nend = Nstart(k+1) - 1 # = (4*4^(k+1) - 1)/3 - 1 # = (4*4^(k+1) - 1 - 3)/3 # = (4*4^(k+1) - 4)/3 # = 4*(4^(k+1) - 1)/3 # = 4*Nstart(k) sub level_to_n_range { my ($self, $level) = @_; my $n_lo = (4**($level+1) - 1)/3; return ($n_lo, 4*$n_lo); } sub n_to_level { my ($self, $n) = @_; if ($n < 1) { return undef; } if (is_infinite($n)) { return $n; } my ($pow,$exp) = round_down_pow (3*$n + 1, 4); return $exp-1; } #------------------------------------------------------------------------------ 1; __END__ # 15 3 # / \ # 17--16 14--13 2 # | | # 18 12 1 # / 4 -- 3 \ # 19 | 11 <- Y=0 # \ 1 -- 2 / # 20 10 -1 # | # 5-- 6 8-- 9 -2 # \ / # 7 -3 # # -4 # # -5 # # ... -6 # # 21--22 24--25 33--... -7 # \ / \ / # 23 26 32 -8 # | | # 27--28 30--31 -9 # \ / # 29 -10 =for stopwords eg Ryde ie Math-PlanePath Koch Nstart Xstart,Ystart OEIS Xstart =head1 NAME Math::PlanePath::KochSquareflakes -- four-sided Koch snowflakes =head1 SYNOPSIS use Math::PlanePath::KochSquareflakes; my $path = Math::PlanePath::KochSquareflakes->new (inward => 0); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is the Koch curve shape arranged as four-sided concentric snowflakes. =cut # math-image --path=KochSquareflakes --all --output=numbers_dash --size=132x50 =pod 61 10 / \ 63-62 60-59 9 | | 67 64 58 55 8 / \ / \ / \ 69-68 66-65 57-56 54-53 7 | | 70 52 6 / \ 71 51 5 \ / 72 50 4 | | 73 15 49 3 / / \ \ 75-74 17-16 14-13 48-47 2 | | | | 76 18 12 46 1 / / 4---3 \ \ 77 19 . | 11 45 Y=0 \ \ 1---2 / / 78 20 10 44 -1 | | | 79-80 5--6 8--9 42-43 -2 \ \ / / 81 7 41 -3 | | 82 40 -4 / \ 83 39 -5 \ / 84 38 -6 | 21-22 24-25 33-34 36-37 -7 \ / \ / \ / 23 26 32 35 -8 | | 27-28 30-31 -9 \ / 29 -10 ^ -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 10 The innermost square N=1 to N=4 is the initial figure. Its sides expand as the Koch curve pattern in subsequent rings. The initial figure is on X=+/-0.5,Y=+/-0.5 fractions. The points after that are integer X,Y. =head1 Inward The C1> option can direct the sides inward. The shape and lengths etc are the same. The angles and sizes mean there's no overlaps. 69-68 66-65 57-56 54-53 7 | \ / \ / \ / | 70 67 64 58 55 52 6 \ | | / 71 63-62 60-59 51 5 / \ / \ 72 61 50 4 | | 73 49 3 \ / 74-75 17-16 14-13 47-48 2 | | \ / | | 76 18 15 12 46 1 \ \ 4--3 / / 77 19 |11 45 <- Y=0 / / 1--2 \ \ 78 20 7 10 44 -1 | / \ | | 80-79 5--6 8--9 43-42 -2 / \ 81 41 -3 | | 82 29 40 -4 \ / \ / 83 27-28 30-31 39 -5 / | | \ 84 23 26 32 35 38 -6 / \ / \ / \ | 21-22 24-25 33-34 36-37 -7 ^ -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 =head2 Level Ranges Counting the innermost N=1 to N=4 square as level 0, a given level has looplen = 4*4^level many points. The start of a level is N=1 plus the preceding loop lengths so Nstart = 1 + 4*[ 1 + 4 + 4^2 + ... + 4^(level-1) ] = 1 + 4*(4^level - 1)/3 = (4^(level+1) - 1)/3 and the end of a level similarly the total loop lengths, or simply one less than the next Nstart, Nend = 4 * [ 1 + ... + 4^level ] = (4^(level+2) - 4) / 3 = Nstart(level+1) - 1 For example, level Nstart Nend (A002450,A080674) 0 1 4 1 5 20 2 21 84 3 85 340 XThe Xstart,Ystart position of the Nstart corner is a Lucas sequence, Xstart(0) = -0.5 Xstart(1) = -2 Xstart(2) = 4*Xstart(1) - 2*Xstart(0) = -7 Xstart(3) = 4*Xstart(2) - 2*Xstart(1) = -24 ... Xstart(level+1) = 4*Xstart(level) - 2*Xstart(level-1) 0.5, 2, 7, 24, 82, 280, 956, 3264, ... (A003480) This recurrence occurs because the replications are 4 wide when horizontal but 3 wide when diagonal. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::KochSquareflakes-Enew ()> =item C<$path = Math::PlanePath::KochSquareflakes-Enew (inward =E $bool)> Create and return a new path object. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return per L
above, ( (4**$level - 1)/3, 4*(4**$level - 1)/3 ) =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A003480 -X and -Y coordinate first point of each ring likewise A020727 A007052 X,Y coordinate of axis crossing, and also maximum height of a side A072261 N on Y negative axis (half way along first side) A206374 N on South-East diagonal (end of first side) Also: A332204 X coordinate across one side A332205 Y coordinate across one side =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/PeanoDiagonals.pm0000644000175000017500000003725413734026667020474 0ustar gggg# Copyright 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::PeanoDiagonals; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath; *max = \&Math::PlanePath::_max; use Math::PlanePath::PeanoCurve; *_n_to_xykk = \&Math::PlanePath::PeanoCurve::_n_to_xykk; *_xykk_to_n = \&Math::PlanePath::PeanoCurve::_xykk_to_n; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_up_pow', 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant turn_any_straight => 0; # never straight use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant parameter_info_array => [ { name => 'radix', share_key => 'radix_3', display => 'Radix', type => 'integer', minimum => 2, default => 3, width => 3, } ]; # odd radix is unit steps diagonally, # even radix unlimited sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; return ($self->{'radix'} % 2 ? (1,1, -1,1, -1,-1, 1,-1) : ()); # even, unlimited } sub new { my $self = shift->SUPER::new(@_); if (! $self->{'radix'} || $self->{'radix'} < 2) { $self->{'radix'} = 3; } return $self; } sub n_to_xy { my ($self, $n) = @_; ### PeanoDiagonals n_to_xy(): "$n" if ($n < 0) { # negative return; } if (is_infinite($n)) { return ($n,$n); } my $frac; { my $int = int($n); $frac = $n - $int; # inherit possible BigFloat $n = $int; } my ($x,$y, $xk,$yk) = _n_to_xykk($self,$n); ### xykk: "$x,$y $xk,$yk" return ($x + ($xk&1 ? 1-$frac : $frac), $y + ($yk&1 ? 1-$frac : $frac)); } sub xy_to_n { return scalar((shift->xy_to_n_list(@_))[0]); } sub xy_to_n_list { my ($self, $x, $y) = @_; ### PeanoDiagonals xy_to_n(): "$x, $y" # For odd radix, if X is even then segments are NE or SW, so offset 0,0 or # 1,1 to go to "middle" points. Conversely if X is odd then segments are # NW or SE so offset 0,1 or 1,0. # # ENHANCE-ME: For odd radix, the two offsets are exactly the two visits. # Should be able to pay attention to the low 0s or 2s and so have the # digits of both N in one look. # # ENHANCE-ME: Is the offset rule for even radix found as easily? $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } return sort {$a<=>$b} map {_xykk_to_n($self, $x,$y, @$_)} ($self->{'radix'}&1 ? ($x&1 ? ([0,1],[1,0]) : ([0,0],[1,1])) : ([0,0],[1,1], [0,1],[1,0])); } #------------------------------------------------------------------------------ # not exact # block 0 .. 3^k-1 contains all sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### rect_to_n_range(): "$x1,$y1 to $x2,$y2" if ($x2 < 0 || $y2 < 0) { return (1, 0); } my $radix = $self->{'radix'}; my ($power, $level) = round_down_pow (max($x2,$y2)*$radix, $radix); if (is_infinite($level)) { return (0, $level); } return (0, $power*$power - 1); } #------------------------------------------------------------------------------ # levels sub level_to_n_range { my ($self, $level) = @_; return (0, $self->{'radix'}**(2*$level)); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } $n = round_nearest($n); my ($pow, $exp) = round_up_pow ($n, $self->{'radix'}*$self->{'radix'}); return $exp; } #------------------------------------------------------------------------------ # num low ternary 0s, and whether odd or even above there which is parity of # how many 1-digits # sub _UNDOCUMENTED__n_to_turn_LSR { my ($self, $n) = @_; if ($n < 1 || is_infinite($n)) { return undef; } my $radix = $self->{'radix'}; if ($radix & 1) { my $turn = 1; until ($n % $radix) { # parity of low 0s $turn = -$turn; $n /= $radix; } return ($n % 2 ? -$turn : $turn); # and flip again if odd } return $self->SUPER::_UNDOCUMENTED__n_to_turn_LSR($n); } #------------------------------------------------------------------------------ 1; __END__ =for stopwords Giuseppe Peano Peano's eg Sur Une Courbe Qui Remplit Toute Aire Mathematische Annalen Ryde OEIS ZOrderCurve ie Math-PlanePath versa Online Radix radix HilbertCurve PeanoCurve DOI =head1 NAME Math::PlanePath::PeanoDiagonals -- 3x3 self-similar quadrant traversal across squares =head1 SYNOPSIS use Math::PlanePath::PeanoDiagonals; my $path = Math::PlanePath::PeanoDiagonals->new; my ($x, $y) = $path->n_to_xy (123); # or another radix digits ... my $path5 = Math::PlanePath::PeanoDiagonals->new (radix => 5); =head1 DESCRIPTION This path is the Peano curve with segments going diagonally across unit squares. =over Giuseppe Peano, "Sur Une Courbe, Qui Remplit Toute Une Aire Plane", Mathematische Annalen, volume 36, number 1, 1890, pages 157-160. DOI 10.1007/BF01199438. L, L =back Points N are at each corner of the squares, so even locations (X+Y even), =cut # generated by: # math-image --path=PeanoDiagonals --all --output=numbers --size=45x10 =pod 9 | 61,425 63,423 65,421 79,407 81,405 8 | 60 58,62 64,68 66,78 76,80 7 | 55,59 57,69 67,71 73,77 75,87 6 | 54 52,56 38,70 36,72 34,74 5 | 49,53 39,51 37,41 31,35 33,129 4 | 48 46,50 40,44 30,42 28,32 3 | 7,47 9,45 11,43 25,29 27,135 2 | 6 4,8 10,14 12,24 22,26 1 | 1,5 3,15 13,17 19,23 21,141 Y=0 | 0 2 16 18 20 +---------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 Moore (figure 3) draws this form, though here is transposed so first unit squares go East. =over E. H. Moore, "On Certain Crinkly Curves", Transactions of the American Mathematical Society, volume 1, number 1, 1900, pages 72-90. L, L =back =cut # Eliakim Hastings =pod Segments between the initial points can be illustrated, | \ \ +--- 47,7 ----+--- 45,9 -- | ^ | \ | ^ | \ | / | \ | / | v | / | v | / | ... 6 -----+---- 4,8 ----+-- | ^ | / | ^ | | \ | / | \ | | \ | v | \ | +-----5,1 ----+---- 3,15 | ^ | \ | ^ | | / | \ | / | | / | v | / | N=0------+------2------+-- Segment N=0 to N=1 goes from the origin X=0,Y=0 up to X=1,Y=1, then N=2 is down again to X=2,Y=0, and so on. The plain PeanoCurve is the middle of each square, so points N + 1/2 here (and reckoning the first such midpoint as the origin). The rule for block reversals is described with PeanoCurve. N is split to an X and Y digit alternately. If the sum of Y digits above is odd then the X digit is reversed, and vice versa X odd is Y reversed. A plain diagonal is North-East per N=0 to 1. Diagonals are mirrored according to the final sum of all digits. If sum of Y digits is odd then mirror horizontally. If sum of X digits is odd then mirror vertically. Such mirroring is X+1 and/or Y+1 as compared to the plain PeanoCurve. An integer N is at the start of the segment with its final reversal. Fractional N follows the diagonal across its unit square. As noted above all locations are even (X+Y even). Those on the axes are visited once and all others twice. =cut # Peano's conception for a space-filling curve is ternary digits below the # radix point to X and Y ... of a fractional f which fills a unit square going from f=0 # at X=0,Y=0 up to f=1 at X=1,Y=1. The integer form here does this with # digits above the ternary point. =pod =head2 Diamond Shape Some authors take this diagonals form and raw it rotated -45 degrees so that the segments are X,Y aligned, and the pattern fills a wedge shape between diagonals X=Y and X=-Y (for XE=0). 6----7,47 | | | | 0---1,5---4,8---9,45 | | | | | ... 2----3,15 In terms of the coordinates here, this is (X+Y)/2, (Y-X)/2. =for GP-Test ('x+I*'y)/(1+I) == ('x+'y)/2 + ('y-'x)/2 * I =head2 Even Radix In an even radix, the mirror rule for diagonals across unit squares is applied the same way. But in this case the end of one segment does not always coincide with the start of the next. =cut # compare # math-image --path=PeanoDiagonals,radix=4 --all --output=numbers --size=30x9 =pod +---15,125----+---13,127-- 16 -----+----18,98- | / | ^ | / | ^ | \ | ^ | \ | / | \ | / | \ | \ | / | \ | v | \ | v | \ | v | / | v +----- 9 --- 14 --- 11 --- 12 --- 17 -----+-- ... | ^ | \ | ^ | \ | | / | \ | / | \ | | / | v | / | v | 8 ---- 7 --- 10 ---- 5 -----+--- | / | ^ | / | ^ | | / | \ | / | \ | radix => 4 | v | \ | v | \ | +----- 1 ---- 6 ---- 3 ---- 4 -- | ^ | \ | ^ | \ | | / | \ | / | \ | | / | v | / | v | N=0------+----- 2 -----+------+--- The first row N=0 to N=3 goes left to right. The next row N=4 to N=7 is a horizontal mirror image to go right to left. N = 3.99.. < 4 follows its diagonal across its unit square, so approaches X=3.99,Y=0. There is then a discontinuity up to N=4 at X=4,Y=1. Block N=0 to N=15 repeats starting N=16, with vertical mirror image. There is a bigger discontinuity between N=15 to N=16 (like there is in even radix PeanoCurve). Some double-visited points occur, such as N=15 and N=125 both at X=1,Y=4. This is when the 4x16 block N=0 to 64 is copied above, mirrored horizontally. =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::PeanoDiagonals-Enew ()> =item C<$path = Math::PlanePath::PeanoDiagonals-Enew (radix =E $r)> Create and return a new path object. The optional C parameter gives the base for digit splitting. The default is ternary, C 3>. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional C<$n> gives an X,Y position along the diagonals across unit squares. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> Return a range of N values which covers the rectangle with corners at C<$x1>,C<$y1> and C<$x2>,C<$y2>. If the X,Y values are not integers then the curve is treated as unit squares centred on each integer point and squares which are partly covered by the given rectangle are included. In the current implementation, the returned range is an over-estimate, so that C<$n_lo> might be smaller than the smallest actually in the rectangle, and C<$n_hi> bigger than the actual biggest. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, $radix**(2*$level) - 1)>. =back =head1 FORMULAS =head2 N to Turn The curve turns left or right 90 degrees at each point N E= 1. The turn is 90 degrees turn(N) = (-1)^(N + number of low ternary 0s of N) = -1,1,1,1,-1,-1,-1,1,-1,1,-1,-1,-1,1,1,1,-1,1 by 90 degrees (+1 left, -1 right) =cut # checking in xt/PeanoDiagonals-seq.t too. # # GP-DEFINE turn(n) = (-1)^(n + valuation(n,3)); # GP-Test vector(18,n, turn(n)) == \ # GP-Test [-1,1, 1, 1,-1, -1, -1,1,-1,1,-1, -1, -1,1,1,1,-1,1] # not in OEIS: -1,1,1,1,-1,-1,-1,1,-1,1,-1,-1,-1,1,1,1,-1,1 # not in OEIS: 1,-1,-1,-1,1,1,1,-1,1,-1,1,1,1,-1,-1,-1,1,-1 \\ negated # not in OEIS: 0,1,1,1,0,0,0,1,0,1,0,0,0,1,1,1,0,1,0,1,1,1,0,0,0,1,1,1,0,0 \\ ones # not in OEIS: 1,0,0,0,1,1,1,0,1,0,1,1,1,0,0,0,1,0 \\ zeros # vector(25,n, (-1)^valuation(n,3)) # not in OEIS: 1,1,-1,1,1,-1,1,1,1,1,1,-1,1,1,-1,1,1,1,1,1,-1,1,1,-1,1,1,-1,1 # vector(100,n, valuation(n,3)%2) # A182581 num ternary low 0s mod 2 =pod The power of -1 means left or right flip for each low ternary 0 of N, and flip again if N is odd. Odd N is an odd number of ternary 1 digits. This formula follows from the turns in a new low base-9 digit. For a segment crossing a given unit square, the expanded segments have the same start and end directions, so existing turns, now 9*N, are unchanged. Then 9*N+r goes as r in the base figure, but flipped LE-ER when N odd since blocks are mirrored alternately. turn(9N) = turn(N) turn(9N+r) = turn(r)*(-1)^N for 1 <= r <= 8 =cut # GP-Test vector(900,n, turn(9*n)) == \ # GP-Test vector(900,n, turn(n)) # GP-Test matrix(90,8,n,r, turn(9*n+r)) == \ # GP-Test matrix(90,8,n,r, turn(r)*(-1)^n) =pod Or in terms of base 3, a single new low ternary digit is a transpose of what's above, and the base figure turns r=1,2 are LE-ER when N above is odd. turn(3N) = - turn(N) turn(3N+r) = turn(r)*(-1)^N for r = 1 or 2 =cut # GP-Test vector(900,n, turn(3*n)) == \ # GP-Test vector(900,n, - turn(n)) # GP-Test matrix(900,2,n,r, turn(3*n+r)) == \ # GP-Test matrix(900,2,n,r, turn(r)*(-1)^n) # GP-Test vector(900,n, turn(3*n)) == \ # GP-Test vector(900,n, -turn(n)) # GP-Test vector(900,n, turn(3*n+1)) == \ # GP-Test vector(900,n, -(-1)^n) # GP-Test vector(900,n, turn(3*n+2)) == \ # GP-Test vector(900,n, (-1)^n) =pod Similarly in any odd radix. =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/SierpinskiTriangle.pm0000644000175000017500000011717413734026656021414 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Maybe: # # rule 22 includes the midpoint between adjacent leaf points. # math-image --path=CellularRule,rule=22 --all --text # # rule 126 extra cell to the inward side of each # math-image --path=CellularRule,rule=60 --all --text # # cf rule 150 double ups, something base 2 instead # math-image --path=CellularRule,rule=150 --all # # cf rule 182 filled gaps # math-image --path=CellularRule,rule=182 --all # math-image --path=SierpinskiTriangle --all --scale=5 # math-image --path=SierpinskiTriangle --all --output=numbers # math-image --path=SierpinskiTriangle --all --text --size=80 # Number of cells in a row: # numerator of (2^k)/k! # # cf A067771 vertices of sierpinski graph, joins up replications # so 1 less each giving 3*(3^k-1)/2 # package Math::PlanePath::SierpinskiTriangle; use 5.004; use strict; use Carp 'croak'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'bit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ { name => 'align', share_key => 'align_trld', display => 'Align', type => 'enum', default => 'triangular', choices => ['triangular', 'right', 'left','diagonal'], choices_display => ['Triangular', 'Right', 'Left','Diagonal'], }, # { name => 'parts', # share_key => 'parts_alr', # display => 'Parts', # type => 'enum', # default => 'all', # choices => ['all', 'left', 'right'], # choices_display => ['All', 'Left', 'Right'], # }, Math::PlanePath::Base::Generic::parameter_info_nstart0(), ]; use constant default_n_start => 0; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; use constant tree_num_children_list => (0,1,2); sub x_negative { my ($self) = @_; return ($self->{'align'} eq 'left' || ($self->{'align'} eq 'triangular' && $self->{'parts'} ne 'right')); } sub x_negative_at_n { my ($self) = @_; return ($self->{'align'} eq 'left' || ($self->{'align'} eq 'triangular' && $self->{'parts'} ne 'right') ? $self->n_start + 1 : undef); } # Note: this method shared by SierpinskiArrowhead sub x_maximum { my ($self) = @_; return ($self->{'align'} eq 'left' || ($self->{'align'} eq 'triangular' && ($self->{'parts'}||'all') eq 'left') ? 0 # left all X<=0 : undef); # others X to +infinity } use constant sumxy_minimum => 0; # triangular X>=-Y or all X>=0 sub diffxy_minimum { my ($self) = @_; return ($self->{'align'} eq 'right' && $self->{'parts'} eq 'right' ? 0 # X>=Y so X-Y>=0 : undef); } # Note: this method shared by SierpinskiArrowhead, SierpinskiArrowheadCentres sub diffxy_maximum { my ($self) = @_; return ($self->{'align'} eq 'diagonal' ? undef : 0); # triangular X<=Y so X-Y<=0 } sub dy_minimum { my ($self) = @_; return ($self->{'align'} eq 'diagonal' ? undef : 0); } sub dy_maximum { my ($self) = @_; return ($self->{'align'} eq 'diagonal' ? undef : 1); } { my %absdx_minimum = (triangular => 1, left => 1, right => 0, # at N=0 diagonal => 0); # at N=0 sub absdx_minimum { my ($self) = @_; return $absdx_minimum{$self->{'align'}}; } } { my %absdy_minimum = (triangular => 0, # rows left => 0, # rows right => 0, # rows diagonal => 1); # diagonal always moves sub absdy_minimum { my ($self) = @_; return $absdy_minimum{$self->{'align'}}; } } sub dsumxy_minimum { my ($self) = @_; return ($self->{'align'} eq 'diagonal' ? 0 # X+Y constant along diagonals : undef); } sub dsumxy_maximum { my ($self) = @_; return ($self->{'align'} eq 'diagonal' ? 1 # X+Y increase by 1 to next diagonal : undef); } sub dir_minimum_dxdy { my ($self) = @_; return ($self->{'align'} eq 'diagonal' ? (0,1) # North : (1,0)); # East } sub dir_maximum_dxdy { my ($self) = @_; return ($self->{'align'} eq 'diagonal' ? (1,-1) # South-Eest : (-1,0)); # supremum, West and 1 up } #------------------------------------------------------------------------------ my %align_known = (triangular => 1, left => 1, right => 1, diagonal => 1); sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } $self->{'parts'} ||= 'all'; my $align = ($self->{'align'} ||= 'triangular'); if (! $align_known{$align}) { croak "Unrecognised align option: ", $align; } ### $align return $self; } sub n_to_xy { my ($self, $n) = @_; ### SierpinskiTriangle n_to_xy(): $n # written as $n - n_start() rather than "-=" so as to provoke an # uninitialized value warning if $n==undef $n = $n - $self->{'n_start'}; # N=0 basis # this frac behaviour slightly unspecified yet my $frac; { my $int = int($n); $frac = $n - $int; if (2*$frac >= 1) { # $frac>=0.5 and BigInt friendly $frac -= 1; $int += 1; } elsif (2*$frac < -1) { # $frac<0.5 and BigInt friendly $frac += 1; $int -= 1; } $n = $int; } ### $n ### $frac if ($n < 0) { return; } if ($n == 0) { return ($n,$n); } my ($depthbits, $ndepth) = _n0_to_depthbits($n, $self->{'parts'}) or return ($n,$n); # infinite ### $depthbits ### $ndepth my $zero = $n * 0; $n -= $ndepth; # offset into row my @nbits = bit_split_lowtohigh($n); # Where there's a 0-bit in the depth remains a 0-bit. # Where there's a 1-bit in the depth takes a bit from Noffset. # Small Noffset has less bits than the depth 1s, hence "|| 0". # my @xbits = map {$_ && (shift @nbits || 0)} @$depthbits; ### @xbits my $x = digit_join_lowtohigh (\@xbits, 2, $zero); my $y = digit_join_lowtohigh ($depthbits, 2, $zero); ### n_to_xy as right: "$x,$y" # $x,$y is in the style of align=right, transform to others if ($self->{'align'} eq 'left') { $x -= $y; } elsif ($self->{'align'} eq 'diagonal') { $y -= $x; } elsif ($self->{'align'} eq 'triangular') { $x = 2*$x - $y; } ### n_to_xy final: "$x,$y" return ($x, $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### SierpinskiTriangle xy_to_n(): "$x, $y" $y = round_nearest ($y); $x = round_nearest($x); # transform $x,$y to the style of align=right if ($self->{'align'} eq 'diagonal') { $y += $x; } elsif ($self->{'align'} eq 'left') { $x += $y; } elsif ($self->{'align'} eq 'triangular') { $x += $y; if (_divrem_mutate ($x, 2)) { # if odd point return undef; } } ### adjusted xy: "$x,$y" return _right_xy_to_n ($self, $x, $y); } sub _right_xy_to_n { my ($self, $x, $y) = @_; ### _right_xy_to_n(): "$x, $y" unless ($x >= 0 && $x <= $y && $y >= 0) { ### outside horizontal row range ... return undef; } if (is_infinite($y)) { return $y; } my $zero = ($y * 0); my $n = $zero; # inherit bignum 0 my $npower = $zero+1; # inherit bignum 1 my @xbits = bit_split_lowtohigh($x); my @depthbits = bit_split_lowtohigh($y); my @nbits; # N offset into row foreach my $i (0 .. $#depthbits) { # x,y bits low to high if ($depthbits[$i]) { $n = 2*$n + $npower; push @nbits, $xbits[$i] || 0; # low to high } else { if ($xbits[$i]) { return undef; } } $npower *= 3; } ### n at left end of y row: $n ### n offset for x: @nbits ### total: $n + digit_join_lowtohigh(\@nbits,2,$zero) + $self->{'n_start'} return $n + digit_join_lowtohigh(\@nbits,2,$zero) + $self->{'n_start'}; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### SierpinskiTriangle rect_to_n_range(): "$x1,$y1, $x2,$y2" $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1) } $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1) } # $y1 to $y2 is the depth range for "triangular", "right" and "left". # For "diagonal" must use X+Y to reckon by anti-diagonals. # if ($self->{'align'} eq 'diagonal') { $y2 += $x2; $y1 += $x1; } if ($y2 < 0) { return (1, 0); } if ($y1 < 0) { $y1 *= 0; # preserve any bignum $y1 } return ($self->tree_depth_to_n($y1), $self->tree_depth_to_n_end($y2)); } # To get N within a triangle row, based on the X range ... # # use Math::PlanePath::CellularRule54; # *_rect_for_V = \&Math::PlanePath::CellularRule54::_rect_for_V; # # if ($self->{'align'} eq 'diagonal') { # if ($x2 < 0 || $y2 < 0) { # return (1,0); # } # if ($x1 < 0) { $x1 *= 0; } # if ($y1 < 0) { $y1 *= 0; } # # return ($self->xy_to_n(0, $x1+$y1), # $self->xy_to_n($x2+$y2, 0)); # } # # ($x1,$y1, $x2,$y2) = _rect_for_V ($x1,$y1, $x2,$y2) # or return (1,0); # rect outside pyramid # # return ($self->xy_to_n($self->{'align'} eq 'right' ? 0 : -$y1, # $y1), # $self->xy_to_n($self->{'align'} eq 'left' ? 0 : $y2, # $y2)); #------------------------------------------------------------------------------ use constant tree_num_roots => 1; sub tree_n_num_children { my ($self, $n) = @_; $n = $n - $self->{'n_start'}; # N=0 basis if ($n < 0) { return undef; } if ($n == 0 && $self->{'parts'} ne 'all') { # parts=left or parts=right have only 1 child under the root n=0 return 1; } my ($depthbits, $ndepth) = _n0_to_depthbits($n, $self->{'parts'}) or return 1; # infinite unless (shift @$depthbits) { # low bit # Depth even (incl zero), two children under every point. return 2; } # Depth odd, either 0 or 1 child. # If depth==1mod4 then 1-child. # If depth==3mod4 so two or more trailing 1-bits then some 0-child and # some 1-child. # $n -= $ndepth; # Noffset into row my $repbit = _divrem_mutate($n,2); # low bit of $n while (shift @$depthbits) { # bits of depth low to high if (_divrem_mutate($n,2) != $repbit) { # bits of $n offset low to high return 0; } } return 1; } sub tree_n_children { my ($self, $n) = @_; ### tree_n_num_children(): $n $n = $n - $self->{'n_start'}; # N=0 basis if ($n < 0) { return; } if ($n == 0 && $self->{'parts'} ne 'all') { # parts=left or parts=right have only 1 child under the root n=0 return ($n+1 + $self->{'n_start'}); } my ($depthbits, $ndepth, $nwidth) = _n0_to_depthbits($n, $self->{'parts'}) or return $n; # infinite $n -= $ndepth; # Noffset into row if (shift @$depthbits) { # Depth odd, single child under some or all points. # When depth==1mod4 it's all points, when depth has more than one # trailing 1-bit then it's only some points. while (shift @$depthbits) { # depth==3mod4 or more low 1s my $repbit = _divrem_mutate($n,2); if (($n % 2) != $repbit) { return; } } return $n + $ndepth+$nwidth + $self->{'n_start'}; } else { # Depth even (or zero), two children under every point. $n = 2*$n + $ndepth+$nwidth + $self->{'n_start'}; return ($n,$n+1); } } sub tree_n_parent { my ($self, $n) = @_; my ($x,$y) = $self->n_to_xy($n) or return undef; if ($self->{'align'} eq 'diagonal') { my $n_parent = $self->xy_to_n($x-1, $y); if (defined $n_parent) { return $n_parent; } else { return $self->xy_to_n($x,$y-1); } } $y -= 1; my $n_parent = $self->xy_to_n($x-($self->{'align'} ne 'left'), $y); if (defined $n_parent) { return $n_parent; } return $self->xy_to_n($x+($self->{'align'} ne 'right'),$y); } sub tree_n_to_depth { my ($self, $n) = @_; ### SierpinskiTriangle n_to_depth(): $n $n = $n - $self->{'n_start'}; if ($n < 0) { return undef; } my ($depthbits) = _n0_to_depthbits($n, $self->{'parts'}) or return $n; # infinite return digit_join_lowtohigh ($depthbits, 2, $n*0); } sub tree_depth_to_n { my ($self, $depth) = @_; return ($depth >= 0 ? _right_xy_to_n($self,0,$depth) : undef); } # sub _NOTWORKING__tree_depth_to_n_range { # my ($self, $depth) = @_; # if (is_infinite($depth)) { # return $depth; # } # if ($depth < 0) { # return undef; # } # # my $zero = my $n = ($depth * 0); # inherit bignum 0 # my $width = my $npower = $zero+1; # inherit bignum 1 # # foreach my $dbit (bit_split_lowtohigh($depth)) { # if ($dbit) { # $n = 2*$n + $npower; # $width *= 2; # } # $npower *= 3; # } # $n += $self->{'n_start'}; # # return ($n, $n+$width-1); # } #------------------------------------------------------------------------------ # In align=diagonal style, height is following a straight line X increment # until hit bit in common with Y, meaning the end of Y low 0s. Or follow # straight line Y until hit bit in common with X, meaning end of X low 0s. # # If X,Y both even then X or Y lines are the same. # If X odd then follow X to limit of Y low 0s. # If Y odd then follow Y to limit of X low 0s. # # | 65 ... # | 57 66 # | 49 67 # | 45 50 58 68 # | 37 69 # | 33 38 59 70 # | 29 39 51 71 # | 27 30 34 40 46 52 60 72 # | 19 73 # | | | # | 15-20 61-74 # | | | # | 11 21 53 75 # | | | | | # | 9-12-16-22 47-54-62-76 # | | | # | 5 23 41 77 # | | | | | # | 3--6 17-24 35-42 63-78 # | | | | | # | 1 7 13 25 31 43 55 79 # | | | | | | | | | # | 0--2--4--8-10-14-18-26-28-32-36-44-48-56-64-80 # +------------------------------------------------- # X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # # depthbits 1 0 0 0 1 Y of "right" # nbits n n # xbits n 0 0 0 n # ybits 1-n 1-n of Y-X for "diagonal" # # Y odd when ylow==1,nlow==0 # follow its X low 0s by nbit==0 and invert of ybits==1 # X odd when ylow==1,nlow==1 # follow its Y low 0s by nbit==1 and invert of xbits=nbits==1 # # At a given depth<=2^k can go at most to its 2^k-1 limit, which means # height = 2^k-1 - depth which is depth with bits flipped. # Then bits of Noffset may put it in the middle of somewhere which limits # the height to a sub-part 2^j < 2^k. # sub tree_n_to_subheight { my ($self, $n) = @_; ### SierpinskiTriangle tree_n_to_subheight(): $n $n = $n - $self->{'n_start'}; if ($n < 0) { return undef; } my ($depthbits, $ndepth) = _n0_to_depthbits($n, $self->{'parts'}) or return $n; # infinite $n -= $ndepth; # offset into row my @nbits = bit_split_lowtohigh($n); my $target = $nbits[0] || 0; foreach my $i (0 .. $#$depthbits) { unless ($depthbits->[$i] ^= 1) { # flip 0<->1, at original==1 take nbit if ((shift @nbits || 0) != $target) { $#$depthbits = $i-1; return digit_join_lowtohigh($depthbits, 2, $n*0); } } } return undef; # first or last of row, infinite } #------------------------------------------------------------------------------ # \ / # 4 0 0 0 0 0 0 4 # \ / \ / \ / \ / # 1 1 1 1 # \ / \ / # 2 2 2 2 # \ / \ / # 3 3 # \ / # 4 0 0 4 # \ / \ / # 1 1 # \ / # 2 2 # \ / # 3 # sub _EXPERIMENTAL__tree_n_to_leafdist { # my ($self, $n) = @_; # ### _EXPERIMENTAL__tree_n_to_leafdist() ... # my $d = $self->tree_n_to_depth($n); # if (defined $d) { # $d = 3 - ($d % 4); # if ($d == 0 && $self->tree_n_num_children($n) != 0) { # $d = 4; # } # } # return $d; # } sub _EXPERIMENTAL__tree_n_to_leafdist { my ($self, $n) = @_; ### _EXPERIMENTAL__tree_n_to_leafdist(): $n $n = $n - $self->{'n_start'}; # N=0 basis if ($n < 0) { return undef; } my ($depthbits, $ndepth) = _n0_to_depthbits($n, $self->{'parts'}) # low to high or return 1; # infinite ### $depthbits # depth bits leafdist # 0 0,0 3 # 1 0,1 2 # 2 1,0 1 # 3 1,1 0 or 4 # my $ret = 3 - ((shift @$depthbits)||0); if (shift @$depthbits) { $ret -= 2; } ### $ret if ($ret) { return $ret; } $n -= $ndepth; ### Noffset into row: $n # Low bits of Nrem unchanging while trailing 1-bits in @depthbits, # to distinguish between leaf or non-leaf. Same as tree_n_children(). # my $repbit = _divrem_mutate($n,2); # low bit of $n ### $repbit do { ### next bit: $n%2 if (_divrem_mutate($n,2) != $repbit) { # bits of $n offset low to high return 0; # is a leaf } } while (shift @$depthbits); return 4; # is a non-leaf } #------------------------------------------------------------------------------ # Return ($depthbits, $ndepth, $nwidth). # $depthbits is an arrayref of bits low to high which are the tree depth of $n. # $ndepth is first N of the row. # $nwidth is the number of points in the row. # sub _n0_to_depthbits { my ($n, $parts) = @_; ### _n0_to_depthbits(): "$n $parts" if (is_infinite($n)) { return; } if ($n == 0) { return ([], 0, 1); } my ($nwidth, $bitpos) = round_down_pow ($parts eq 'all' ? $n : 2*$n-1, 3); ### $nwidth ### $bitpos my @depthbits; $depthbits[$bitpos] = 1; my $ndepth = ($parts eq 'all' ? $nwidth : ($nwidth + 1)/2); $nwidth *= 2; while (--$bitpos >= 0) { $nwidth /= 3; ### at: "n=$n nwidth=$nwidth bitpos=$bitpos depthbits=".join(',',map{$_||0}@depthbits) if ($n >= $ndepth + $nwidth) { $depthbits[$bitpos] = 1; $ndepth += $nwidth; $nwidth *= 2; } else { $depthbits[$bitpos] = 0; } } # Nwidth = 2**count1bits(depth), when parts=all ### @depthbits ### assert: $parts ne 'all' || $nwidth == (1 << scalar(grep{$_}@depthbits)) return (\@depthbits, $ndepth, $nwidth); } #------------------------------------------------------------------------------ # levels use Math::PlanePath::SierpinskiArrowheadCentres; *level_to_n_range = \&Math::PlanePath::SierpinskiArrowheadCentres::level_to_n_range; *n_to_level = \&Math::PlanePath::SierpinskiArrowheadCentres::n_to_level; #----------------------------------------------------------------------------- 1; __END__ =for stopwords eg Ryde Sierpinski Nlevel ie Ymin Ymax OEIS Online rowpoints Nleft Math-PlanePath Gould's Nend bitand Noffset Ndepth Nrem Dyck =head1 NAME Math::PlanePath::SierpinskiTriangle -- self-similar triangular path traversal =head1 SYNOPSIS use Math::PlanePath::SierpinskiTriangle; my $path = Math::PlanePath::SierpinskiTriangle->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis path is an integer version of Sierpinski's triangle from =over Waclaw Sierpinski, "Sur une Courbe Dont Tout Point est un Point de Ramification", Comptes Rendus Hebdomadaires des SE<233>ances de l'AcadE<233>mie des Sciences, volume 160, January-June 1915, pages 302-305. L =back =cut # PDF download pages 304 to 307 inclusive =pod Unit triangles are numbered numbered horizontally across each row. 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 15 57 58 59 60 61 62 63 64 14 49 50 51 52 53 54 55 56 13 45 46 47 48 12 37 38 39 40 41 42 43 44 11 33 34 35 36 10 29 30 31 32 9 27 28 8 19 20 21 22 23 24 25 26 7 15 16 17 18 6 11 12 13 14 5 9 10 4 5 6 7 8 3 3 4 2 1 2 1 0 <- Y=0 X= ... -9-8-7-6-5-4-3-2-1 0 1 2 3 4 5 6 7 8 9 ... The base figure is the first two rows shape N=0 to N=2. Notice the middle "." position X=0,Y=1 is skipped 1 . 2 0 This is replicated twice in the next row pair as N=3 to N=8. Then the resulting four-row shape is replicated twice again in the next four-row group as N=9 to N=26, etc. See the C path to traverse by a connected sequence rather than rows jumping across gaps. =head2 Row Ranges The number of points in each row is always a power of 2. The power is the count of 1-bits in Y. (This count is sometimes called Gould's sequence.) rowpoints(Y) = 2^count_1_bits(Y) For example Y=13 is binary 1101 which has three 1-bits so in row Y=13 there are 2^3=8 points. Because the first point is N=0, the N at the left of each row is the cumulative count of preceding points, Ndepth(Y) = rowpoints(0) + ... + rowpoints(Y-1) Since the powers of 2 are always even except for 2^0=1 in row Y=0, this Ndepth(Y) total is always odd. The self-similar nature of the triangle means the same is true of the sub-triangles, for example odd N=31,35,41,47,etc on the left of the triangle at X=8,Y=8. This means in particular the primes (being odd) fall predominately on the left side of the triangles and sub-triangles. =head2 Replication Sizes Counting the single point N=0 as level=0, then N=0,1,2 as level 1, each replication level goes from Nstart = 0 Nlevel = 3^level - 1 inclusive For example level 2 is from N=0 to N=3^2-1=8. Each level doubles in size, 0 <= Y <= 2^level - 1 - 2^level + 1 <= X <= 2^level - 1 =head2 Align Right Optional C"right"> puts points to the right of the Y axis, packed next to each other and so using an eighth of the plane. =cut # math-image --path=SierpinskiTriangle,align=right --all --output=numbers =pod align => "right" 7 | 19 20 21 22 23 24 25 26 6 | 15 16 17 18 5 | 11 12 13 14 4 | 9 10 3 | 5 6 7 8 2 | 3 4 1 | 1 2 Y=0 | 0 +------------------------- X=0 1 2 3 4 5 6 7 =head2 Align Left Optional C"left"> puts points to the left of the Y axis, ie. into negative X. The rows are still numbered starting from the left, so it's a shift across, not a negate of X. =cut # math-image --path=SierpinskiTriangle,align=left --all --output=numbers =pod align => "left" 19 20 21 22 23 24 25 26 | 7 15 16 17 18 | 6 11 12 13 14 | 5 9 10 | 4 5 6 7 8 | 3 3 4 | 2 1 2 | 1 0 | <- Y=0 -------------------------+ -7 -6 -5 -4 -3 -2 -1 X=0 =head2 Align Diagonal Optional C"diagonal"> puts rows on diagonals down from the Y axis to the X axis. This uses the whole of the first quadrant, with gaps according to the pattern. =cut # math-image --expression='i<=80?i:0' --path=SierpinskiTriangle,align=diagonal --output=numbers =pod align => "diagonal" 15 | 65 ... 14 | 57 66 13 | 49 67 12 | 45 50 58 68 11 | 37 69 10 | 33 38 59 70 9 | 29 39 51 71 8 | 27 30 34 40 46 52 60 72 7 | 19 73 6 | 15 20 61 74 5 | 11 21 53 75 4 | 9 12 16 22 47 54 62 76 3 | 5 23 41 77 ... 2 | 3 6 17 24 35 42 63 78 1 | 1 7 13 25 31 43 55 79 Y=0 | 0 2 4 8 10 14 18 26 28 32 36 44 48 56 64 80 +------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 This form visits all points X,Y where X and Y written in binary have no 1-bits in the same bit positions, ie. where S == 0. For example X=13,Y=3 is not visited because 13="1011" and 6="0110" both have bit "0010" set. This bit-and rule is an easy way to test for visited or not visited cells of the pattern. The visited cells can be calculated by this diagonal X,Y bitand, but then plotted X,X+Y for the "right" align or X-Y,X+Y for "triangular". =head2 Cellular Automaton The triangle arises in Stephen Wolfram's 1-D cellular automatons (per L and L). align rule ----- ---- "triangular" 18,26,82,90,146,154,210,218 "right" 60 "left" 102 =over L L L =back =cut # rule 60 right hand octant # rule 102 left hand octant # math-image --path=CellularRule,rule=60 --all # math-image --path=CellularRule,rule=102 --all =pod In each row the rule 18 etc pattern turns a cell "on" in the next row if one but not both its diagonal predecessors are "on". This is a mod 2 sum giving Pascal's triangle mod 2. Some other cellular rules are variations on the triangle, =over =item * Rule 22 is "triangular" but filling the gap between leaf points such as N=5 and N=6. =item * Rule 126 adds an extra point on the inward side of each visited. =item * Rule 182 fills in the big gaps leaving just a single-cell empty border delimiting them. =back =head2 N Start The default is to number points starting N=0 as shown above. An optional C parameter can give a different start, with the same shape. For example starting at 1, which is the numbering of C rule=60, =cut # math-image --path=SierpinskiTriangle,n_start=1 --expression='i<=27?i:0' --output=numbers =pod n_start => 1 20 21 22 23 24 25 26 27 16 17 18 19 12 13 14 15 10 11 6 7 8 9 4 5 2 3 1 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::SierpinskiTriangle-Enew ()> =item C<$path = Math::PlanePath::SierpinskiTriangle-Enew (align =E $str, n_start =E $n)> Create and return a new path object. C is a string, one of the following as described above. "triangular" (the default) "right" "left" "diagonal" =back =head2 Descriptive Methods =over =item C<$n = $path-En_start()> Return the first N in the path. This is 0 by default, or the given C parameter. =back =head2 Tree Methods =over =item C<@n_children = $path-Etree_n_children($n)> Return the children of C<$n>, or an empty list if C<$n E n_start> (ie. before the start of the path). The children are the points diagonally up left and right on the next row (Y+1). There can be 0, 1 or 2 such points. At even depth there's 2, on depth=1mod4 there's 1. On depth=3mod4 there's some 0s and some 1s. See L below. For example N=3 has two children N=5,N=6. Then in turn N=5 has just one child N=9 and N=6 has no children. The way points are numbered across a row means that when there's two children they're consecutive N values. =item C<$n_parent = $path-Etree_n_parent($n)> Return the parent node of C<$n>, or C if C<$n E= n_start> (the top of the triangle). =item C<$depth = $path-Etree_n_to_depth($n)> Return the depth of node C<$n>, or C if there's no point C<$n>. In the "triangular", "right" and "left" alignments this is the same as the Y coordinate from C. In the "diagonal" alignment it's X+Y. =item C<$n = $path-Etree_depth_to_n($depth)> =item C<$n = $path-Etree_depth_to_n_end($depth)> Return the first or last N at tree level C<$depth>. The start of the tree is depth=0 at the origin X=0,Y=0. This is the N at the left end of each row. So in the default triangular alignment it's the same as C. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 3**$level - 1)>. =back =head1 FORMULAS =head2 X,Y to N For calculation it's convenient to turn the X,Y coordinates into the "right" alignment style, so that Y is the depth and X is in the range 0E=XE=Y. The starting position of each row of the triangle is given by turning 1-bits of the depth into powers-of-3. Y = depth = 2^a + 2^b + 2^c + 2^d ... a>b>c>d... Ndepth = first N at this depth = 3^a + 2 * 3^b + 2^2 * 3^c + 2^3 * 3^d + ... For example depth=6=2^2+2^1 starts at Ndepth=3^2+2*3^1=15. The powers-of-3 are the three parts of the triangle replication. The power-of-2 doubling is the doubling of the row Y when replicated. Then the bits of X at the positions of the 1-bits of the depth become an N offset into the row. a b c d depth = 10010010010 binary X = m00n00p00q0 Noffset = mnpq binary N = Ndepth + Noffset For example in depth=6 binary "110" then at X=4="100" take the bits of X where depth has 1-bits, which is X="10_" so Noffset="10" binary and N=15+2=17, as per the "right" table above at X=4,Y=6. If X has any 1-bits which are a 0-bits in the depth depth then that X,Y is not visited. For example if depth=6="110" then X=3="11" is not visited because the low bit X="__1" has depth="__0" at that position. =head2 N to Depth The row containing N can be found by working down the Ndepth formula shown above. The "a" term is the highest 3^a E= N, thus giving a bit 2^a for the depth. Then for the remaining Nrem = N - 3^a find the highest "b" where 2*3^b E= Nrem. And so on until reaching an Nrem which is too small to subtract any more terms. It's convenient to go by bits high to low of the prospective depth, deciding at each bit whether Nrem is big enough to give the depth a 1-bit there, or whether it must be a 0-bit. a = floor(log3(N)) round down to power-of-3 pow = 3^a Nrem = N - pow depth = high 1-bit at bit position "a" (counting from 0) factor = 2 loop bitpos a-1 down to 0 pow /= 3 if pow*factor <= Nrem then depth 0-bit, factor *= 2 else depth 1-bit factor is 2^count1bits(depth) Noffset = Nrem offset into row 0 <= Noffset < factor =head2 N to X,Y N is turned into depth and Noffset as per above. X in "right" alignment style is formed by spreading the bits of Noffset out according to the 1-bits of the depth. depth = 100110 binary Noffset = abc binary Xright = a00bc0 For example in depth=5 this spreads an Noffset=0to3 to make X=000, 001, 100, 101 in binary and in "right" alignment style. From an X,Y in "right" alignment the other alignments are formed alignment from "right" X,Y --------- ---------------- triangular 2*X-Y, Y so -Y <= X < Y right X, Y unchanged left X-Y, Y so -Y <= X <= 0 diagonal X, Y-X downwards sloping =head2 N to Number of Children The number of children follows a pattern based on the depth. depth number of children ----- ------------------ 12 2 2 2 2 11 1 0 0 1 1 0 0 1 10 2 2 2 2 9 1 1 1 1 8 2 2 7 1 0 0 0 0 0 0 1 6 2 2 2 2 5 1 1 1 1 4 2 2 3 1 0 0 1 2 2 2 1 1 1 0 2 If depth is even then all points have 2 children. For example row depth=6 has 4 points and all have 2 children each. At odd depth the number of children is either 1 or 0 according to the Noffset position in the row masked down by the trailing 1-bits of the depth. depth = ...011111 in binary, its trailing 1s Noffset = ...00000 \ num children = 1 = ...11111 / = ...other num children = 0 For example depth=11 is binary "1011" which has low 1-bits "11". If those two low bits of Noffset are "00" or "11" then 1 child. Any other bit pattern in Noffset ("01" or "10" in this case) is 0 children. Hence the pattern 1,0,0,1,1,0,0,1 reading across the depth=11 row. In general when the depth doubles the triangle is replicated twice and the number of children is carried with the replications, except the middle two points are 0 children. For example the triangle of depth=0to3 is repeated twice to make depth=4to7, but the depth=7 row is not children 10011001 of a plain doubling from the depth=3 row, but instead 10000001 which is the middle two points becoming 0. =head2 N to Number of Siblings Each node N has either 0 or 1 siblings. This is determined by depth, depth number of siblings ----- ------------------ 4 0 0 3 1 1 1 1 2 0 0 1 1 1 0 0 depth number of siblings ----- ------------------ odd 1 even 0 In an even row the points are all spread apart so there are no siblings. The points in such a row are cousins or second cousins, etc, but none share a parent. In an odd row each parent node (an even row) has 2 children and so each of those points has 1 sibling. The effect is to conflate the NumChildren=1 and NumChildren=0 cases in the children picture above, those two becoming a single sibling. num children of N num siblings of N ----------------- ----------------- 0 or 1 1 2 0 =head2 Rectangle to N Range An easy range can be had just from the Y range by noting the diagonals X=Y and X=-Y are always visited, so just take the Ndepth of Ymin and Nend of Ymax, # align="triangular" Nmin = N at X=-Ymin,Y=Ymin Nmax = N at X=Ymax,Y=Ymax Or in "right" style the left end is at X=0 instead, # align="right" Nmin = N at X=0,Ymin Nmax = N at Ymax,Ymax For less work but a bigger over-estimate, invert the Nlevel formulas given in L above to round up to the end of a depth=2^k replication, level = floor(log2(Ymax)) + 1 Nmax = 3^level - 1 For example Y=11, level=floor(log2(11))+1=4, so Nmax=3^4-1=80, which is the end of the Y=15 row, ie. rounded up to the top of the replication block Y=8 to Y=15. =head1 OEIS The Sierpinski triangle is in Sloane's Online Encyclopedia of Integer Sequences in various forms, =over L (etc) =back A001316 number of cells in each row (Gould's sequence) A001317 rows encoded as numbers with bits 0,1 A006046 total cells to depth, being tree_depth_to_n(), A074330 Nend, right hand end of each row (starting Y=1) A001316 is the "rowpoints" described above. A006046 is the cumulative total of that sequence which is the "Ndepth", and A074330 is 1 less for "Nend". align="triangular" (the default) A047999 0,1 cells by rows A106344 0,1 cells by upwards sloping dX=3,dY=1 A130047 0,1 cells of half X<=0 by rows A047999 etc is every second point in the default triangular lattice, or all points in align="right" or "left". align="triangular" (the default) A002487 count points along dX=3,dY=1 slopes is the Stern diatomic sequence A106345 count points along dX=5,dY=1 slopes dX=3,dY=1 sloping lines are equivalent to opposite-diagonals dX=-1,dY=1 in align="right". align="right" A075438 0,1 cells by rows including 0 blanks at left of pyramid align="right", n_start=0 A006046 N on Y axis, being Ndepth A074330 N on Diagonal starting from Y=1, being Nend align="left", n_start=0 A006046 N on NW diagonal, being Ndepth A074330 N on Y axis starting from Y=1, being Nend A080263 Dyck encoding of the tree structure A080264 same in binary A080265 position in list of all balanced binary A080268 Dyck encoding breadth-first A080269 same in binary A080270 position in list of all balanced binary A080318 Dyck encoding breadth-first of branch-reduced (duplicate each bit) A080319 same in binary A080320 position in list of all balanced binary For the Dyck encoding see for example L. The position in all balanced binary which is A080265 etc corresponds to C in that C. A branch-reduced tree has any single-child node collapsed out, so that all remaining nodes are either a leaf node or have 2 (or more) children. The effect of this on the Sierpinski triangle in breadth-first encoding is to duplicate each bit, so A080269 with each bit repeated gives the branch-reduced A080319. =head1 SEE ALSO L, L, L, L, L L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/GreekKeySpiral.pm0000644000175000017500000005047413734026670020462 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=GreekKeySpiral --lines --scale=25 # http://gwydir.demon.co.uk/jo/greekkey/corners.htm package Math::PlanePath::GreekKeySpiral; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest', 'floor'; *_divrem = \&Math::PlanePath::_divrem; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; *_sqrtint = \&Math::PlanePath::_sqrtint; # uncomment this to run the ### lines # use Smart::Comments; use constant xy_is_visited => 1; use constant parameter_info_array => [ { name => 'turns', share_key => 'turns_2', display => 'Turns', type => 'integer', minimum => 0, default => 2, width => 2, }, ]; sub x_negative_at_n { my ($self) = @_; return $self->n_start + 4*($self->{'turns'}+1)**2; } sub y_negative_at_n { my ($self) = @_; return $self->n_start + 6*($self->{'turns'}+1)**2; } # 17-- 18--19--20--21 # | # 16 3t-2 -- 8 -- 2t # | | | # 15 4t-5 ---11 6 # | | | # 14-- 13-----12 5 # | # 1---- 2----- 3-- t # sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; my $turns = $self->{'turns'}; return $self->n_start + ($turns == 0 ? 4 # turns=0 : $turns <= 2 ? 6 # turns=1,2 : 3*$turns - 4); } sub turn_any_right { my ($self) = @_; return ($self->{'turns'} != 0); # SquareSpiral is left or straight only } sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; # turns=1 2,4,7,11,22,29 return ($self->{'turns'} == 0 ? undef # SquareSpiral left or straight only : $self->n_start + $self->{'midpoint'}-1); } sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; my $turns = $self->{'turns'}; # turns=1 2,4,7,11,22,29 return $self->n_start + ($turns==0 ? 1 : $turns==1 ? 3 : $turns-1); } #------------------------------------------------------------------------------ # turns=1 # 2---3 # | | # 0---1 4 # # turns=2 | # 5---6---7 18 15--14 # | | | | | # 4---3 8 17--16 13 x=1,y=1 # | | | # 0---1---2 9 10--11--12 # # turns=3 # 10--11--12--13 # | | # 9 6---5 14 x=2,y=1 # | | | | # 8---7 4 15 # | | # 0---1---2---3 16 # # turns=4 # 17--18--19--20--21 50 37--36--35--34 # | | | | | 3,3,2,1,1,1,2,3,4,down4 # 16 9---8---7 22 49 38 41--42 33 # | | | | | | | | | # 15 10--11 6 23 48 39--40 43 32 x=3,y=2 # | | | | | | | # 14--13--12 5 24 47--46--45--44 31 # | | | # 0---1---2---3---4 25--26--27--28--29--30 5,4,3,2,1,1,1,2,3,up3 # # turns=5 # 26--27--28--29--30--31 # | | 4,4,3,2,1,1,1,2,3,4,5,5 # 25 12--11--10---9 32 # | | | | # 24 13 16--17 8 33 5,4,3,2,1,1,1,2,3,4,5,rem # | | | | | | | # 35 23 14--15 18 7 34 # | | | | | x=3,y=3 # 36 22--21--20--19 6 35 # | | # 0---1---2---3---4---5 36- # # turns=6 # 37--38--39--40--41--42--43 # | | # 36 15--14--13--12--11 44 x=3,y=3 # | | | | # 35 16 23--24--25 10 45 # | | | | | | # 34 17 22--21 26 9 46 6,5,4,3,2,1,1,1,2,3,4,5,rem # | | | | | | # 33 18--19--20 27 8 47 # | | | | # 32--31--30--29--28 7 48 # | | # 0---1---2---3---4---5---6 49- # # turns=7 # 50--51--52--53--54--55--56--57 # | | # 49 18--17--16--15--14--13 58 # | | | | # 48 19 32--33--34--35 12 59 x=4,y=3 # | | | | | | # 47 20 31 28--27 36 11 60 # | | | | | | | | # 46 21 30--29 26 37 10 61 6,5,4,3,2,1,1,1,2,3,4,5,rem # | | | | | | # 45 22--23--24--25 38 9 62 # | | | | # 44--43--42--41--40--39 8 63 # | | # 0---1---2---3---4---5---6---7 64 # # turns=8 x=5,y=4 # centre # 2 1 1 # 3 2 1 # 4 3 2 # 5 3 3 # 6 3 3 # 7 4 3 # 8 5 4 # 9 5 5 # 10 5 5 # 11 6 5 # 12 7 6 # 13 7 7 # 14 7 7 # 15 8 7 # # turns 2, 3, 4, 5 # midp 4 6, 10, 15, 21 N = (1/2 d^2 + 1/2 d) # # 63, 189, 387, 657 # 9*7 9*21, 9*43, 9*73 # # 82 226 442 # 9*9+1 9*25+1 9*49+1 sub new { my $self = shift->SUPER::new (@_); my $turns = $self->{'turns'}; if (! defined $turns) { $turns = 2; } elsif ($turns < 0) { } $self->{'turns'} = $turns; my $t1 = $turns + 1; if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } $self->{'centre_x'} = int($t1/2) + (($turns%4)==0); $self->{'centre_y'} = int($turns/2) + (($turns%4)==1); $self->{'midpoint'} = $turns*$t1/2 + 1; $self->{'side'} = $t1; $self->{'squared'} = $t1*$t1; ### turns : $self->{'turns'} ### midpoint: $self->{'midpoint'} ### side : $self->{'side'} ### squared : $self->{'squared'} return $self; } sub n_to_xy { my ($self, $n) = @_; #### GreekKeySpiral n_to_xy: $n $n = $n - $self->{'n_start'}; ### n zero based: $n if ($n < 0) { return; } my $turns = $self->{'turns'}; my $squared = $self->{'squared'}; my $side = $turns + 1; ### sqrt of: ($n-1) / $squared my $d = _sqrtint($n / $squared); $n -= $squared*$d*$d - 1; my $dhalf = int($d/2); ### $d ### $dhalf ### n remainder: $n my ($x,$y); my $square_rot = 0; my $frac; { my $int = int($n); $frac = $n - int($n); $n = $int; } ### $frac ### $n if ($d % 2) { ### odd d, right and top ... if ($n >= $squared*($d+1)) { ### top ... $n -= $squared*2*$d; (my $q, $n) = _divrem ($n, $squared); $x = (-$dhalf-$q)*$side + 1; $y = ($dhalf+1)*$side; $square_rot = 2; } else { ### right ... (my $q, $n) = _divrem ($n-$turns-1 + $squared, $squared); $x = ($dhalf+1)*$side; $y = ($q-$dhalf-1)*$side; $square_rot = 1; } } else { ### even d, left and bottom ... if ($d == 0 || $n >= $squared*($d+1)) { ### bottom ... $n -= $squared*2*$d; (my $q, $n) = _divrem ($n, $squared); $x = ($dhalf+$q)*$side-1; $y = -($dhalf)*$side; $square_rot = 0; } else { ### left ... (my $q, $n) = _divrem ($n-$turns-1 + $squared, $squared); $x = -($dhalf)*$side; $y = -($q-$dhalf-1)*$side; $square_rot = 3; } } ### assert: ! ($n < 0) ### assert: ! ($n >= $squared) my $rot = $turns; my $kx = 0; my $ky = 0; my $before; ### n-midpoint: $n - $self->{'midpoint'} if (($n -= $self->{'midpoint'}) >= 0) { ### after middle ... } elsif ($n += 1) { ### before middle ... $n = -$n; if ($frac) { ### fraction ... $frac = 1-$frac; $n -= 1; } else { ### integer ... $n -= 0; } $rot += 2; $before = 1; } else { ### centre segment ... $rot += 1; $before = 1; } ### key n: $n # d: [ 0, 1, 2 ] # n: [ 0, 3, 10 ] # d = -1/4 + sqrt(1/2 * $n + 1/16) # = (-1 + sqrt(8*$n + 1)) / 4 # N = (2*$d + 1)*$d # rel = (2*$d + 1)*$d + 2*$d+1 # = (2*$d + 3)*$d + 1 # $d = int( (_sqrtint(8*$n+1) - 1)/4 ); $n -= (2*$d+3)*$d + 1; ### $d ### key signed rem: $n if ($n < 0) { ### key vertical ... $kx += $d; $ky = -$frac-$n-$d - 1 + $ky; if ($d % 2) { ### key right ... $rot += 2; $kx += 1; } else { } } else { ### key horizontal ... $kx = $frac+$n-$d + $kx; $ky += $d + 1; $rot += 2; if ($d % 2) { ### key bottom ... $rot += 2; $kx += -1; } else { } } ### kxy raw: "$kx, $ky" if ($rot & 2) { $kx = -$kx; $ky = -$ky; } if ($rot & 1) { ($kx,$ky) = (-$ky,$kx); } ### kxy rotated: "$kx,$ky" if ($before) { if (($turns % 4) == 0) { $kx -= 1; } if (($turns % 4) == 1) { $ky -= 1; } if (($turns % 4) == 2) { $kx += 1; } if (($turns % 4) == 3) { $ky += 1; } } $kx += $self->{'centre_x'}; $ky += $self->{'centre_y'}; if ($square_rot & 2) { $kx = $turns-$kx; $ky = $turns-$ky; } if ($square_rot & 1) { ($kx,$ky) = ($turns-$ky,$kx); } # kx,ky first to inherit BigRat etc from $frac return ($kx + $x, $ky + $y); } # t+(t-1)+(t-2)+(t-3) = 4t-6 # y=0 0 # y=2 0+1+2+3 total 6 # y=4 4+5+6+7 total 28 # (2 d^2 - d) # N=4*t*y/2 - (2y-1)*y # =(2t - 2y + 1)*y # x=1 0+1+2 total 3 # x=3 3+4+5+6 total 21 # x=5 7+8+9+10 total 55 # (2 d^2 + d) # N = 4*t*(x-1)/2 + 3t-3 - (2x+1)*x # = 2*t*(x-1) + 3t-3 - (2x+1)*x # = 2tx-2t + 3t-3 - (2x+1)*x # = (2t-2x-1)x - 2t + 3t-3 # = (2t-2x-1)x + t-3 # y=0 squared-t-t total 0 # y=2 - (t-1)-(t-2)-(t-3)-(t-4) total 10 # y=4 - 5+6+7+8 total 36 # (2 d^2 + d) # N = squared - 4*t*y/2 - 2t - (2y+1)*y +(x-y) # = squared - (2t+2y+1)*y - 2t + x sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### xy_to_n: "x=$x, y=$y" my $turns = $self->{'turns'}; my $side = $turns + 1; my $squared = $self->{'squared'}; my $xs = floor($x/$side); my $ys = floor($y/$side); $x %= $side; $y %= $side; my $n; if ($xs > -$ys) { ### top or right if ($xs >= $ys) { ### right going upwards $n = $squared*((4*$xs - 3)*$xs + $ys); ($x,$y) = ($y,$turns-$x); # rotate -90 if ($x == 0) { $x = $turns; $n -= $side*$turns; # +$side modulo } else { $x -= 1; $n += $side; } } else { ### top going leftwards $n = $squared*((4*$ys - 1)*$ys - $xs); $x = $turns-$x; # rotate 180 $y = $turns-$y; } } else { ### bottom or left if ($xs > $ys || ($xs == 0 && $ys == 0)) { ### bottom going rightwards: "$xs,$ys" $n = $squared*((4*$ys - 3)*$ys + $xs); } else { ### left going downwards $n = $squared*((4*$xs - 1)*$xs - $ys); ($x,$y) = ($turns-$y,$x); # rotate +90 if ($x == 0) { $x = $turns; $n -= $side*$turns; # +$side modulo } else { $x -= 1; $n += $side; } } } if ($x + $y >= $turns) { ### key top or right ... if ($x > $y) { ### key right ... $x = $turns-$x; if ($x % 2) { ### forward ... $n += (2*$turns-2*$x+2)*$x + $y - $turns; } else { ### backward ... $n += $squared - (2*$turns-2*$x+2)*$x - $y; } } else { ### key top ... $y = $turns-$y; if ($y % 2) { ### backward ... $n += (2*$turns-2*$y)*$y + $turns-$x; } else { ### forward ... $n += $squared - (2*$turns - 2*$y)*$y - 2*$turns + $x; } } } else { ### key bottom or left ... if ($x >= $y) { ### key bottom ... if ($y % 2) { ### backward ... $n += $squared - (2*$turns - 2*$y)*$y - $turns - $x - 1; } else { ### forward ... $n += (2*$turns-2*$y)*$y + $x + 1; } } else { ### key left ... if ($x % 2) { ### forward ... $n += (2*$turns-2*$x-2)*$x + 2*$turns - $y; } else { ### backward ... $n += $squared - (2*$turns - 2*$x - 2)*$x - 3*$turns + $y; } } } return $n + $self->{'n_start'}-1; } use Math::PlanePath::SquareArms; *_rect_square_range = \&Math::PlanePath::SquareArms::_rect_square_range; # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); # floor divisions to square blocks { my $side = $self->{'turns'} + 1; _divrem_mutate($x1,$side); _divrem_mutate($y1,$side); _divrem_mutate($x2,$side); _divrem_mutate($y2,$side); } my ($dlo, $dhi) = _rect_square_range ($x1, $y1, $x2, $y2); my $squared = $self->{'squared'}; ### d range sides: "$dlo, $dhi" ### right start: ((4*$squared*$dlo - 4*$squared)*$dlo + 10) return (($dlo == 0 ? 0 # special case Nlo=1 for innermost square # Nlo at right vertical start : ((4*$squared*$dlo - 4*$squared)*$dlo + $squared)) + $self->{'n_start'}, # Nhi at bottom horizontal end (4*$squared*$dhi + 4*$squared)*$dhi + $squared + $self->{'n_start'}-1); } 1; __END__ =for stopwords Ryde Math-PlanePath Edkins =head1 NAME Math::PlanePath::GreekKeySpiral -- square spiral with Greek key motif =head1 SYNOPSIS use Math::PlanePath::GreekKeySpiral; my $path = Math::PlanePath::GreekKeySpiral->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes a spiral with a Greek key scroll motif, 39--38--37--36 29--28--27 24--23 5 | | | | | | 40 43--44 35 30--31 26--25 22 4 | | | | | | 41--42 45 34--33--32 19--20--21 ... 3 | | | 48--47--46 5---6---7 18 15--14 99 96--95 2 | | | | | | | | | 49 52--53 4---3 8 17--16 13 98--97 94 1 | | | | | | | 50--51 54 1---2 9--10--11--12 91--92--93 <- Y=0 | | 57--56--55 68--69--70 77--78--79 90 87--86 -1 | | | | | | | | 58 61--62 67--66 71 76--75 80 89--88 85 -2 | | | | | | | | 59--60 63--64--65 72--73--74 81--82--83--84 -3 ^ -3 -2 -1 X=0 1 2 3 4 5 6 7 8 ... The repeating figure is a 3x3 pattern | * *---* | | | right vertical *---* * going upwards | *---*---* | The turn excursion is to the outside of the 3-wide channel and forward in the direction of the spiral. The overall spiralling is the same as the C, but composed of 3x3 sub-parts. =head2 Sub-Part Joining The verticals have the "entry" to each figure on the inside edge, as for example N=90 to N=91 above. The horizontals instead have it on the outside edge, such as N=63 to N=64 along the bottom. The innermost N=1 to N=9 is a bottom horizontal going right. *---*---* | | bottom horizontal *---* * going rightwards | | --*---* *--> On the horizontals the excursion part is still "forward on the outside", as for example N=73 through N=76, but the shape is offset. The way the entry is alternately on the inside and outside for the vertical and horizontal is necessary to make the corners join. =head2 Turn An optional C $integer> parameter controls the turns within the repeating figure. The default is C2>. Or for example C4> begins =cut # math-image --path=GreekKeySpiral,turns=4 --all --output=numbers_dash --size=78 =pod turns => 4 105-104-103-102-101-100 79--78--77--76--75 62--61--60--59 | | | | | | 106 119-120-121-122 99 80 87--88--89 74 63 66--67 58 | | | | | | | | | | | | 107 118 115-114 123 98 81 86--85 90 73 64--65 68 57 | | | | | | | | | | | | 108 117-116 113 124 97 82--83--84 91 72--71--70--69 56 | | | | | | 109-110-111-112 125 96--95--94--93--92 51--52--53--54--55 | | 130-129-128-127-126 17--18--19--20--21 50 37--36--35--34 | | | | | | 131 144-145-146-147 16 9-- 8-- 7 22 49 38 41--42 33 | | | | | | | | | | | | 132 143 140-139 148 15 10--11 6 23 48 39--40 43 32 | | | | | | | | | | | | 133 142-141 138 149 14--13--12 5 24 47--46--45--44 31 | | | | | | 134-135-136-137 150 1-- 2-- 3-- 4 25--26--27--28--29--30 | ..-152-151 The count of turns is chosen to make C0> a straight line, the same as the C. C1> is a single wiggle, =cut # math-image --path=GreekKeySpiral,turns=1 --all --output=numbers_dash --size=78 =pod turns => 1 66--65--64 61--60 57--56 53--52--51 | | | | | | | | 67--68 63--62 59--58 55--54 49--50 | | 70--69 18--17--16 13--12--11 48--47 | | | | | | 71--72 19--20 15--14 9--10 45--46 | | | | ... 22--21 2-- 3 8-- 7 44--43 | | | | | 23--24 1 4-- 5-- 6 41--42 | | 26--25 30--31 34--35 40--39 | | | | | | 27--28--29 32--33 36--37--38 In general the repeating figure is a square of turns+1 points on each side, spiralling in and then out again. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::GreekKeySpiral-Enew ()> =item C<$path = Math::PlanePath::GreekKeySpiral-Enew (turns =E $integer)> Create and return a new Greek key spiral object. The default C is 2. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n E 1> the return is an empty list, it being considered the path starts at 1. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each N in the path as centred in a square of side 1, so the entire plane is covered. =back =head1 SEE ALSO L, L Jo Edkins Greek Key pages C =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/DivisibleColumns.pm0000644000175000017500000003551513734026672021055 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # A006218 - cumulative count of divisors # # Dirichlet: # n * (log(n) + 2*gamma - 1) + O(sqrt(n)) gamma=0.57721... Euler-Mascheroni # # n * (log(n) + 2*gamma - 1) + O(log(n)*n^(1/3)) # # Chandrasekharan: bounds # n log(n) + (2 gamma - 1) n - 4 sqrt(n) - 1 # <= a(n) <= # n log(n) + (2 gamma - 1) n + 4 sqrt(n) # # a(n)=2 * sum[ i=1 to floor(sqrt(n)) of floor(n/i) ] - floor(sqrt(n))^2 # # cf A003988,A010766 - triangle with values floor(i/j) # # http://mathworld.wolfram.com/DirichletDivisorProblem.html # # compile-command: "math-image --path=DivisibleColumns --all" # # math-image --path=DivisibleColumns --output=numbers --all # package Math::PlanePath::DivisibleColumns; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_sqrtint = \&Math::PlanePath::_sqrtint; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ { name => 'divisor_type', share_key => 'divisor_type_allproper', display => 'Divisor Type', type => 'enum', choices => ['all','proper'], default => 'all', description => 'Divisor type, with "proper" meaning divisors d 'direction', # share_key => 'direction_updown', # display => 'Direction', # type => 'enum', # default => 'up', # choices => ['up','down'], # choices_display => ['Down','Up'], # description => 'Number points upwards or downwards in the columns.', # }, Math::PlanePath::Base::Generic::parameter_info_nstart0(), ]; use constant default_n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; # X=2,Y=1 when proper # X=1,Y=1 when not sub x_minimum { my ($self) = @_; return ($self->{'proper'} ? 2 : 1); } use constant y_minimum => 1; sub diffxy_minimum { my ($self) = @_; # octant Y<=X so X-Y>=0 return ($self->{'proper'} ? 1 : 0); } use constant dx_minimum => 0; use constant dx_maximum => 1; use constant dir_maximum_dxdy => (1,-1); # South-East #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); my $divisor_type = ($self->{'divisor_type'} ||= 'all'); $self->{'proper'} = ($divisor_type eq 'proper'); # bool $self->{'direction'} ||= 'up'; if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } my @x_to_n = (0,0,1); sub _extend { ### _extend(): $#x_to_n my $x = $#x_to_n; push @x_to_n, $x_to_n[$x] + _count_divisors($x); # if ($x > 2) { # if (($x & 3) == 2) { # $x >>= 1; # $next_n += $x_to_n[$x] - $x_to_n[$x-1]; # } else { # $next_n += # } # } ### last x: $#x_to_n ### second last: $x_to_n[$#x_to_n-2] ### last: $x_to_n[$#x_to_n-1] ### diff: $x_to_n[$#x_to_n-1] - $x_to_n[$#x_to_n-2] ### divisors of: $#x_to_n - 2 ### divisors: _count_divisors($#x_to_n-2) ### assert: $x_to_n[$#x_to_n-1] - $x_to_n[$#x_to_n-2] == _count_divisors($#x_to_n-2) } sub n_to_xy { my ($self, $n) = @_; ### DivisibleColumns n_to_xy(): "$n" $n = $n - $self->{'n_start'}; # to N=0 basis, and warn on undef # $n<-0.5 works with Math::BigInt circa Perl 5.12, it seems if ($n < -0.5) { return; } if (is_infinite($n)) { return ($n,$n); } my $frac; { my $int = int($n); if ($n == $int) { $frac = 0; } else { $frac = $n - $int; # -.5 <= $frac < 1 $n = $int; # BigFloat int() gives BigInt, use that if ($frac > .5) { $frac--; $n += 1; # now -.5 <= $frac < .5 } } ### $n ### n: "$n" ### $frac ### assert: $frac >= -.5 ### assert: $frac < .5 } my $proper = $self->{'proper'} || 0; # cannot add false '' to BigInt ### $proper my $x; if ($proper) { $x = 2; ### proper adjusted n: $n } else { $x = 1; } for (;;) { while ($x > $#x_to_n) { _extend(); } $n += $proper; ### consider: "n=$n x=$x x_to_n=".$x_to_n[$x] if ($x_to_n[$x] > $n) { $x--; last; } $x++; } $n -= $x_to_n[$x]; $n -= $proper; ### $x ### x_to_n: $x_to_n[$x] ### x_to_n next: $x_to_n[$x+1] ### remainder: $n my $y = 1; for (;;) { unless ($x % $y) { if (--$n < 0) { return ($x, $frac + $y); } } if (++$y > $x) { ### oops, not enough in this column return; } } } # Feturn a count of the number of integers dividing $x, including 1 and $x # itself. Cf Math::Factor::XS maybe. sub _count_divisors { my ($x) = @_; my $ret = 1; unless ($x % 2) { my $count = 1; do { $x /= 2; $count++; } until ($x % 2); $ret *= $count; } my $limit = _sqrtint($x); for (my $d = 3; $d <= $limit; $d+=2) { unless ($x % $d) { my $count = 1; do { $x /= $d; $count++; } until ($x % $d); $limit = sqrt($x); $ret *= $count; } } if ($x > 1) { $ret *= 2; } return $ret; } sub xy_is_visited { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); return ($y >= 1 && ($self->{'proper'} ? $x >= 2 && $y <= int($x/2) : $x >= 1 && $y <= $x) && ($x%$y) == 0); } sub xy_to_n { my ($self, $x, $y) = @_; ### DivisibleColumns xy_to_n(): "$x,$y" $x = round_nearest ($x); $y = round_nearest ($y); if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my $proper = $self->{'proper'}; if ($proper) { if ($x < 2 || $y < 1 || $y > int($x/2) || ($x%$y)) { return undef; } } else { if ($x < 1 || $y < 1 || $y > $x || ($x%$y)) { return undef; } } while ($#x_to_n < $x) { _extend(); } ### x_to_n: $x_to_n[$x] my $n = $x_to_n[$x] - ($proper ? $x-1 : 1); ### base n: $n for (my $i = 1+$proper; $i <= $y; $i++) { unless ($x % $i) { $n += 1; } } return $n + $self->{'n_start'}; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### DivisibleColumns rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest($x1); $y1 = round_nearest($y1); $x2 = round_nearest($x2); $y2 = round_nearest($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### rounded ... ### $x2 ### $y2 if ($self->{'proper'}) { if ($x2 < 2 # rect all negative || $y2 < 1 # rect all negative || 2*$y1 > $x2) { # rect all above X=2Y octant ### outside proper divisors ... return (1, 0); } if ($x1 < 2) { $x1 = 2; } } else { if ($x2 < 1 # rect all negative || $y2 < 1 # rect all negative || $y1 > $x2) { # rect all above X=Y diagonal ### outside all divisors ... return (1, 0); } if ($x1 < 1) { $x1 = 1; } } if (is_infinite($x2)) { return ($self->{'n_start'}, $x2); } my ($n_lo, $n_hi); if ($x1 <= $#x_to_n) { $n_lo = $x_to_n[$x1]; } else { $n_lo = _count_divisors_cumulative($x1-1); } if ($x2 < $#x_to_n) { $n_hi = $x_to_n[$x2+1]; } else { $n_hi = _count_divisors_cumulative($x2); } $n_hi -= 1; ### rect at: "x=".($x2+1)." x_to_n=".($x_to_n[$x2+1]||'none') if ($self->{'proper'}) { $n_lo -= $x1-1; $n_hi -= $x2; } return ($n_lo + $self->{'n_start'}, $n_hi + $self->{'n_start'}); } # Return a total count of all the divisors of all the integers 1 to $x # inclusive. sub _count_divisors_cumulative { my ($x) = @_; my $total = 0; my $limit = _sqrtint($x); foreach my $i (1 .. $limit) { $total += int($x/$i); } return 2*$total - $limit*$limit; } 1; __END__ =for stopwords Ryde Math-PlanePath sqrt OEIS =head1 NAME Math::PlanePath::DivisibleColumns -- X divisible by Y in columns =head1 SYNOPSIS use Math::PlanePath::DivisibleColumns; my $path = Math::PlanePath::DivisibleColumns->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path visits points X,Y where X is divisible by Y going by columns from Y=1 to YE=X. 18 | 57 17 | 51 16 | 49 15 | 44 14 | 40 13 | 36 12 | 34 11 | 28 10 | 26 9 | 22 56 8 | 19 48 7 | 15 39 6 | 13 33 55 5 | 9 25 43 4 | 7 18 32 47 3 | 4 12 21 31 42 54 2 | 2 6 11 17 24 30 38 46 53 1 | 0 1 3 5 8 10 14 16 20 23 27 29 35 37 41 45 50 52 Y=0| +--------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Starting N=0 at X=1,Y=1 means the values 1,3,5,8,etc horizontally on Y=1 are the sums i=K sum numdivisors(i) i=1 The current implementation is fairly slack and is slow on medium to large N. =head1 Proper Divisors C 'proper'> gives only proper divisors of X, meaning that Y=X itself is excluded. =cut # math-image --path=DivisibleColumns,divisor_type=proper --output=numbers --all --size=134 =pod 9 | 39 8 | 33 7 | 26 6 | 22 38 5 | 16 29 4 | 11 21 32 3 | 7 13 20 28 37 2 | 3 6 10 15 19 25 31 36 1 | 0 1 2 4 5 8 9 12 14 17 18 23 24 27 30 34 35 Y=0| +--------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 The pattern is the same, but the X=Y line skipped. The high line going up is at Y=X/2, when X is even, that being the highest proper divisor. =head2 N Start The default is to number points starting N=0 as shown above. An optional C can give a different start with the same shape, For example to start at 1, =cut # math-image --path=DivisibleColumns,n_start=1 --all --output=numbers --size=50x16 =pod n_start => 1 9 | 23 8 | 20 7 | 16 6 | 14 5 | 10 4 | 8 19 3 | 5 13 22 2 | 3 7 12 18 1 | 1 2 4 6 9 11 15 17 21 Y=0| +------------------------------ X=0 1 2 3 4 5 6 7 8 9 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::DivisibleColumns-Enew ()> =item C<$path = Math::PlanePath::DivisibleColumns-Enew (divisor_type =E $str, n_start =E $n)> Create and return a new path object. C (a string) can be "all" (the default) "proper" =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head1 FORMULAS =head2 Rectangle to N Range The cumulative divisor count up to and including a given X column can be calculated from the fairly well-known sqrt formula, a sum from 1 to sqrt(X). S = floor(sqrt(X)) / i=S \ numdivs cumulative = 2 * | sum floor(X/i) | - S^2 \ i=1 / This means the N range for 0 to X can be calculated without working out all each column count up to X. In the current code if column counts have been worked out then they're used, otherwise this formula. =head1 OEIS This pattern is in Sloane's Online Encyclopedia of Integer Sequences in the following forms, =over L (etc) =back n_start=0 (the default) A006218 N on Y=1 row, cumulative count of divisors A077597 N on X=Y diagonal, cumulative count divisors - 1 n_start=1 A061017 X coord, each n appears countdivisors(n) times A027750 Y coord, list divisors of successive k A056538 X/Y, divisors high to low divisor_type=proper (and default n_start=0) A027751 Y coord divisor_type=proper, divisors of successive n (extra initial 1) divisor_type=proper, n_start=2 A208460 X-Y, being X subtract each proper divisor A208460 has "offset" 2, hence C to match that. The same with all divisors would simply insert an extra 0 for the difference at X=Y. =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/FibonacciWordFractal.pm0000644000175000017500000003724413734026672021611 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=FibonacciWordFractal --output=numbers_dash package Math::PlanePath::FibonacciWordFractal; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; #------------------------------------------------------------------------------ my @dir4_to_dx = (0,-1,0,1); my @dir4_to_dy = (1,0,-1,0); my $moffset = 0; sub n_to_xy { my ($self, $n) = @_; ### FibonacciWordFractal n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } # my $frac; # { # my $int = int($n); # $frac = $n - $int; # inherit possible BigFloat # $n = $int; # BigFloat int() gives BigInt, use that # } { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $zero = ($n * 0); # inherit bignum 0 my $one = $zero + 1; # inherit bignum 0 my @f = ($one, 2+$zero); my @xend = ($zero, $zero, $one); # F3 N=2 X=1,Y=1 my @yend = ($zero, $one, $one); my $level = 2; while ($f[-1] < $n) { push @f, $f[-1] + $f[-2]; my ($x,$y); my $m = (($level+$moffset) % 6); if ($m == 1) { $x = $yend[-2]; # T (transpose) $y = $xend[-2]; } elsif ($m == 2) { $x = $yend[-2]; # -90 $y = - $xend[-2]; } elsif ($m == 3) { $x = $xend[-2]; # T -90 $y = - $yend[-2]; } elsif ($m == 4) { ### T ... $x = $yend[-2]; # T $y = $xend[-2]; } elsif ($m == 5) { $x = - $yend[-2]; # +90 $y = $xend[-2]; } elsif ($m == 0) { $x = - $xend[-2]; # T +90 $y = $yend[-2]; } push @xend, $xend[-1] + $x; push @yend, $yend[-1] + $y; $level++; ### new: ($level%6)." add $x,$y for $xend[-1],$yend[-1] for $f[-1]" } my $x = $zero; my $y = $zero; my $rot = 0; my $transpose = 0; while (@xend > 1) { ### at: "$x,$y rot=$rot transpose=$transpose level=$level n=$n consider f=$f[-1]" my $xo = pop @xend; my $yo = pop @yend; if ($n >= $f[-1]) { my $m = (($level+$moffset) % 6); $n -= $f[-1]; ### offset: "$xo, $yo for levelmod=$m" if ($transpose) { ($xo,$yo) = ($yo,$xo); } if ($rot & 2) { $xo = -$xo; $yo = -$yo; } if ($rot & 1) { ($xo,$yo) = (-$yo,$xo); } ### apply rot to offset: "$xo, $yo" $x += $xo; $y += $yo; if ($m == 1) { # F7 N=13 etc $transpose ^= 3; # T } elsif ($m == 2) { # F8 N=21 etc # -90 if ($transpose) { $rot++; } else { $rot--; # -90 } } elsif ($m == 3) { # F3 N=2 etc # T -90 if ($transpose) { $rot++; } else { $rot--; # -90 } $transpose ^= 3; } elsif ($m == 4) { # F4 N=3 etc $transpose ^= 3; # T } elsif ($m == 5) { # F5 N=5 etc # +90 if ($transpose) { $rot--; } else { $rot++; # +90 } } else { # ($m == 0) # F6 N=8 etc # T +90 if ($transpose) { $rot--; } else { $rot++; # +90 } $transpose ^= 3; } } pop @f; $level--; } # mod 6 twist ? # ### final rot: "$rot transpose=$transpose gives ".(($rot^$transpose)&3) # $rot = ($rot ^ $transpose) & 3; # $x = $frac * $dir4_to_dx[$rot] + $x; # $y = $frac * $dir4_to_dy[$rot] + $y; ### final with frac: "$x,$y" return ($x,$y); } sub xy_to_n { my ($self, $x, $y) = @_; ### FibonacciWordFractal xy_to_n(): "$x, $y" $x = round_nearest($x); if (is_infinite($x)) { return $x; } $y = round_nearest($y); if (is_infinite($y)) { return $y; } my $zero = ($x * 0 * $y); # inherit bignum 0 my $one = $zero + 1; # inherit bignum 0 my @f = ($one, $zero+2); my @xend = ($zero, $one); # F3 N=2 X=1,Y=1 my @yend = ($one, $one); my $level = 3; for (;;) { my ($xo,$yo); my $m = ($level % 6); if ($m == 2) { $xo = $yend[-2]; # T $yo = $xend[-2]; } elsif ($m == 3) { $xo = $yend[-2]; # -90 $yo = - $xend[-2]; } elsif ($m == 4) { $xo = $xend[-2]; # T -90 $yo = - $yend[-2]; } elsif ($m == 5) { ### T $xo = $yend[-2]; # T $yo = $xend[-2]; } elsif ($m == 0) { $xo = - $yend[-2]; # +90 $yo = $xend[-2]; } elsif ($m == 1) { $xo = - $xend[-2]; # T +90 $yo = $yend[-2]; } $xo += $xend[-1]; $yo += $yend[-1]; last if ($xo > $x && $yo > $y); push @f, $f[-1] + $f[-2]; push @xend, $xo; push @yend, $yo; $level++; ### new: "level=$level $xend[-1],$yend[-1] for N=$f[-1]" } my $n = 0; while ($level >= 2) { ### at: "$x,$y n=$n level=$level consider $xend[-1],$yend[-1] for $f[-1]" my $m = (($level+$moffset) % 6); if ($m >= 3) { ### 3,4,5 X ... if ($x >= $xend[-1]) { $n += $f[-1]; $x -= $xend[-1]; $y -= $yend[-1]; ### shift to: "$x,$y levelmod ".$m if ($m == 3) { # F3 N=2 etc ($x,$y) = (-$y,$x); # +90 } elsif ($m == 4) { # F4 N=3 etc $y = -$y; # +90 T } elsif ($m == 5) { # F5 N=5 etc ($x,$y) = ($y,$x); # T } ### rot to: "$x,$y" if ($x < 0 || $y < 0) { return undef; } } } else { ### 0,1,2 Y ... if ($y >= $yend[-1]) { $n += $f[-1]; $x -= $xend[-1]; $y -= $yend[-1]; ### shift to: "$x,$y levelmod ".$m if ($m == 0) { # F6 N=8 etc ($x,$y) = ($y,-$x); # -90 } elsif ($m == 1) { # F7 N=13 etc $x = -$x; # -90 T } elsif ($m == 2) { # F8 N=21 etc, incl F2 N=1 ($x,$y) = ($y,$x); # T } ### rot to: "$x,$y" if ($x < 0 || $y < 0) { return undef; } } } pop @f; pop @xend; pop @yend; $level--; } if ($x != 0 || $y != 0) { return undef; } return $n; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### FibonacciWordFractal rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### rect_to_n_range(): "$x1,$y1 to $x2,$y2" if ($x2 < 0 || $y2 < 0) { return (1, 0); } foreach ($x1,$x2,$y1,$y2) { if (is_infinite($_)) { return (0, $_); } } my $zero = ($x1 * 0 * $y1 * $x2 * $y2); # inherit bignum 0 my $one = $zero + 1; # inherit bignum 0 my $f0 = 1; my $f1 = 2; my $xend0 = $zero; my $xend1 = $one; my $yend0 = $one; my $yend1 = $one; my $level = 3; for (;;) { my ($xo,$yo); my $m = (($level+$moffset) % 6); if ($m == 3) { # at F3 N=2 etc $xo = $yend0; # -90 $yo = - $xend0; } elsif ($m == 4) { # at F4 N=3 etc $xo = $xend0; # T -90 $yo = - $yend0; } elsif ($m == 5) { # at F5 N=5 etc $xo = $yend0; # T $yo = $xend0; } elsif ($m == 0) { # at F6 N=8 etc $xo = - $yend0; # +90 $yo = $xend0; } elsif ($m == 1) { # at F7 N=13 etc $xo = - $xend0; # T +90 $yo = $yend0; } else { # if ($m == 2) { # at F8 N=21 etc $xo = $yend0; # T $yo = $xend0; } ($f1,$f0) = ($f1+$f0,$f1); ($xend1,$xend0) = ($xend1+$xo,$xend1); ($yend1,$yend0) = ($yend1+$yo,$yend1); $level++; ### consider: "f1=$f1 xy end $xend1,$yend1" if ($xend1 > $x2 && $yend1 > $y2) { return (0, $f1 - 1); } } } #------------------------------------------------------------------------------ # ENHANCE-ME: is this good? # # Points which are on alternate X and Y axes # n=0 # 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144 # ^ ^ ^ ^ # 0 4 20 88 # F(2+3*level) # or F(2+6*level) to go X axis each time # # sub level_to_n_range { # my ($self, $level) = @_; # return (0, F(2+3*$level)-1); # } 1; __END__ =for stopwords eg Ryde Math-PlanePath Monnerot-Dumaine OEIS =head1 NAME Math::PlanePath::FibonacciWordFractal -- turns by Fibonacci word bits =head1 SYNOPSIS use Math::PlanePath::FibonacciWordFractal; my $path = Math::PlanePath::FibonacciWordFractal->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis is an integer version of the Fibonacci word fractal =over Alexis Monnerot-Dumaine, "The Fibonacci Word Fractal", March 2009. L =back =cut # Author's copy: # http://alexis.monnerot-dumaine.neuf.fr/ # http://alexis.monnerot-dumaine.neuf.fr/articles/fibonacci%20fractal.pdf # http://web.archive.org/web/2id_/http://alexis.monnerot-dumaine.neuf.fr/articles/fibonacci%20fractal.pdf # [gone] =pod It makes turns controlled by the "Fibonacci word" sequence, sometimes called the "golden string". 11 | 27-28-29 33-34-35 53-54-55 59-60-61 | | | | | | | | | 10 | 26 30-31-32 36 52 56-57-58 62 | | | | | 9 | 25-24 38-37 51-50 64-63 | | | | | 8 | 23 39 43-44-45 49 65 | | | | | | | 7 | 21-22 40-41-42 46-47-48 66-67 | | | 6 | 20 16-15-14 74-73-72 68 | | | | | | | 5 | 19-18-17 13 75 71-70-69 | | | 4 | 11-12 76-77 | | | 3 | 10 78 | | | 2 | 9--8 80-79 | | | 1 | 1--2--3 7 81 85-86-87 | | | | | | | Y=0 | 0 4--5--6 82-83-84 88-89-... +------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 A current direction up,down,left,right is maintained, starting in the up direction. The path moves in the current direction and then may turn or go straight according to the Fibonacci word, Fib word -------- 0 turn left if even index, right if odd index 1 straight ahead The Fibonacci word is reckoned as starting from index=1, so for example at N=0 draw a line upwards to N=1 and the first Fibonacci word value is 0 and its position index=1 is odd so turn to the right. N Fibonacci word --- -------------- 1 0 turn right 2 1 3 0 turn right 4 0 turn left 5 1 6 0 turn left 7 1 The result is self-similar blocks within the first quadrant XE=0,YE=0. New blocks extend at N values which are Fibonacci numbers. For example N=21 a new block begins above, then N=34 a new block across, N=55 down, N=89 across again, etc. The new blocks are a copy of the shape starting N=0 but rotated and/or transposed according to the replication level mod 6, level mod 6 new block ----------- --------- 0 transpose 1 rotate -90 2 transpose and rotate -90 3 transpose 4 rotate +90 5 transpose and rotate +90 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::FibonacciWordFractal-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =cut # Knott form would overlap, if do that in this same module. # # =item C<$n = $path-Exy_to_n ($x,$y)> # # Return the point number for coordinates C<$x,$y>. If there's nothing at # C<$x,$y> then return C. # # The curve visits an C<$x,$y> twice for various points (all the "inside" # points). In the current code the smaller of the two N values is returned. # Is that the best way? =item C<$n = $path-En_start()> Return 0, the first N in the path. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include, =over L (etc) =back A332298 X coordinate, starting at n=1 A332299 Y-1 coordinate, starting at n=1 A156596 turn sequence, 0=straight,1=right,2=left A143668 turn sequence, 0=right,1=straight,2=left A171587 abs(dX), so 1=horizontal,0=vertical A265318 N at locations by diagonals A003849 Fibonacci word with values 0,1 A005614 Fibonacci word with values 1,0 A003842 Fibonacci word with values 1,2 A014675 Fibonacci word with values 2,1 =head1 SEE ALSO L, L, L L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/AnvilSpiral.pm0000644000175000017500000003763513734026674020035 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=AnvilSpiral --all --output=numbers_dash # math-image --path=AnvilSpiral,wider=3 --all --output=numbers_dash package Math::PlanePath::AnvilSpiral; use 5.004; use strict; #use List::Util 'min','max'; *min = \&Math::PlanePath::_min; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::PlanePath::Base::Generic 'round_nearest'; # pentagonal N = (3k-1)*k/2 # preceding # Np = (3k-1)*k/2 - 1 # = (3k^2 - k - 2)/2 # = (3k+2)(k-1)/2 # # parameters "wider","n_start" use Math::PlanePath::SquareSpiral; *parameter_info_array = \&Math::PlanePath::SquareSpiral::parameter_info_array; use constant xy_is_visited => 1; use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant 1.02 _UNDOCUMENTED__dxdy_list => (1,0, # E # no N,S 1,1, # NE -1,1, # NW -1,0, # W -1,-1, # SW 1,-1); # SE # last NW at lower right # 2w+4 ------- w+1 # \ / # * 0---- w * # / \ # 2w+6 ---------- 3w+10 w=3; 1+3*w+10=20 # sub x_negative_at_n { my ($self) = @_; return $self->n_start + ($self->{'wider'} ? 0 : 3); } sub y_negative_at_n { my ($self) = @_; return $self->n_start + 2*$self->{'wider'} + 6; } sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + 3*$self->{'wider'} + 10; } use constant absdx_minimum => 1; # abs(dX)=1 always use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; # left turn at w when w!=0 or at w+1 when w==0 return $self->n_start + ($self->{'wider'} || 1); } sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; return $self->n_start + 2*$self->{'wider'} + 5; } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); # parameters if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } $self->{'wider'} ||= 0; # default return $self; } # [1,2,3,4],[1,12,35,70] # horizontal # N = (6 d^2 - 7 d + 2) # = (6*$d**2 - 7*$d + 2) # = ((6*$d - 7)*$d + 2) # d = 7/12 + sqrt(1/6 * $n + 1/144) # = (7 + 12*sqrt(1/6 * $n + 1/144))/12 # = (7 + sqrt(144/6*$n + 1))/12 # = (7 + sqrt(24*$n + 1))/12 # # wider=1 # [1,2,3,4],[1+1,12+1+2,35+1+2+2,70+1+2+2+2] # N = (6 d^2 - 5 d + 1) # d = 5/12 + sqrt(1/6 * $n + 1/144) # # wider=2 # [1,2,3,4],[1+2,12+2+4,35+2+4+4,70+2+4+4+4] # N = (6 d^2 - 3 d) # d = 3/12 + sqrt(1/6 * $n + 9/144) # # wider=3 # [1,2,3,4],[1+3,12+3+6,35+3+6+6,70+3+6+6+6] # N = (6 d^2 - d - 1) # d = 1/12 + sqrt(1/6 * $n + 25/144) # # wider=4 # [1,2,3,4],[1+4,12+4+8,35+4+8+8,70+4+8+8+8] # N = (6 d^2 + d - 2) # d = -1/12 + sqrt(1/6 * $n + 49/144) # 49=7*7=(2w-1)*(2w-1) # # in general # N = (6 d^2 - (7-2w) d + 2-w) # = (6d - (7-2w)) d + 2-w # = (6d - 7 + 2w))*d + 2-w # d = (7-2w)/12 + sqrt(1/6 * $n + (w-1)^2/144) # = [ 7-2w + 12*sqrt(1/6 * $n + (w-1)^2/144) ] / 12 # = [ 7-2w + sqrt(144/6*$n + (w-1)^2) ] / 12 # = [ 7-2w + sqrt(24*$n + (w-1)^2) ] / 12 sub n_to_xy { my ($self, $n) = @_; ### AnvilSpiral n_to_xy(): $n $n = $n - $self->{'n_start'}; # to N=0 basis, warning if $n==undef if ($n < 0) { return; } my $w = $self->{'wider'}; my $w_right = int($w/2); my $w_left = $w - $w_right; ### $w ### $w_left ### $w_right if ($n <= $w) { ### centre horizontal return ($n - $w_left, # N=0 at $w_left 0); } my $d = int((_sqrtint(24*($n+1) + (2*$w-1)**2) + 7-2*$w) / 12); ### ($n+1) ### $d ### d frac: ((sqrt(int(24*($n+1)) + (2*$w-1)**2) + 7-2*$w) / 12) ### d sqrt add: ($w-1)*($w-1) ### d const part: 7-2*$w $n -= (6*$d - 7 + 2*$w)*$d + 2-$w; ### base: (6*$d - 7 + 2*$w)*$d + 2-$w ### remainder: $n if ($n <= 5*$d+$w-2) { if ($n+1 <= $d) { ### upper right slope ... return ($n + $d + $w_right, $n+1); } else { ### top ... return (-$n + 3*$d + $w_right - 2, $d); } } $n -= 7*$d + $w - 2; if ($n <= 0) { ### left slopes: $n return (-abs($n+$d) - $d - $w_left, -$n - $d); } $n -= 4*$d + $w; if ($n < 0) { ### bottom ... return ($n + 2*$d + $w_right, -$d); } else { ### right lower ... return (-$n + 2*$d + $w_right, $n - $d); } } sub xy_to_n { my ($self, $x, $y) = @_; ### AnvilSpiral xy_to_1 n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); my $w = $self->{'wider'}; my $w_right = int($w/2); my $w_left = $w - $w_right; ### $w ### $w_left ### $w_right my $abs_y = abs($y); if ($x-$w_right >= 2*$abs_y) { ### right slopes: "d=".($x-$w_right - $abs_y) my $d = $x-$w_right - $abs_y; # zero based return ((6*$d + 5 + 2*$w)*$d + $w + $y + $self->{'n_start'}); } if ($x+$w_left < -2*$abs_y) { ### left slopes: "d=".($x+$w_left + $abs_y) my $d = $x+$w_left + $abs_y; # negative, and zero based return ((6*$d + 1 - 2*$w)*$d - $y + $self->{'n_start'}); } if ($y > 0) { ### top horizontal ... return ((6*$y - 4 + 2*$w)*$y - $w + $w_right-$x + $self->{'n_start'}); } else { ### bottom horizontal ... # y negative return ((6*$y - 2 - 2*$w)*$y + $x+$w_left + $self->{'n_start'}); } } # uncomment this to run the ### lines #use Smart::Comments; # ...-78-77-76-75-74 # / # 43-42-41-40-39-38 73 # / / # 17-16-15-14 37 72 # / / / # -3--2 13 36 71 # / / / / # 1 12 35 70 # # column X=2, dmin decreasing until Y=1=floor(x/2) # column X=3, dmin decreasing until Y=2=ceil(x/2) # so x1 - min(y2,int((x1+1)/2)) # # # column Xmax=2, dmax increasing down until x2-y1 # # horizontal Y>=0 N increases left and right of X=Y*3/2 # so candidate max at top-left x1,y2 or top-right x2,y2 # # horizontal Y<0 N increases left and right of X=-Y*3/2 # so candidate max at bottom-left x1,y1 or bottom-right x2,y1 # # vertical Y>=0 N increases above and below Y=ceil(X/2) # so candidate max at top-right or bottom-right, or Y=0 # # vertical Y<0 N increases above and below Y=ceil(X/2) # so candidate max at top-right or bottom-right, or Y=0 # # int(($y2+1)/2), $y2 # int(($y1+1)/2), $y1 # # my @corners = ($self->xy_to_n($x1,$y1), # $self->xy_to_n($x2,$y1), # $self->xy_to_n($x1,$y2), # $self->xy_to_n($x2,$y2)); # return (($x_zero && $y_zero ? 1 : min (@corners)), # max (@corners, # ($y_zero ? ($self->xy_to_n($x1,0), # $self->xy_to_n($x2,0)) : ()))); # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### AnvilSpiral rect_to_n_range(): "$x1,$y1 $x2,$y2" my $w = $self->{'wider'}; my $w_right = int($w/2); my $w_left = $w - $w_right; $x1 = round_nearest($x1); $x2 = round_nearest($x2); $y1 = round_nearest($y1); $y2 = round_nearest($y2); my $x_zero = (($x1<0) != ($x2<0)); my $y_zero = (($y1<0) != ($y2<0)); ### $x_zero ### $y_zero $x1 += $w_left; $x2 += $w_left; if ($x1 < 0) { $x1 = $w-$x1; } if ($x2 < 0) { $x2 = $w-$x2; } $y1 = abs($y1); $y2 = abs($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x_zero) { $x1 = 0; } if ($y_zero) { $y1 = 0; } ### abs: "$x1,$y1 $x2,$y2" ### d1 slope max y: int(($x1+1)/2) ### d1 slope: $x1 - min($y2,int(($x1+1)/2)) # --------* # / # / # * <-y=0 # x=0....w # # d=x-w-y on the slope # d=y on the top horizontal # my $d1 = min ($x1-$w - min($y2,int(($x1-$w+1)/2)) - 1, $y2); my $d2 = 1 + max ($x2-$w - $y1, $y2); ### $d1 ### $d2 ### d2 right slope would be: $x2-$w_right - $y2 # d1==0 is the centre horizontal # return ($d1 <= 0 ? $self->{'n_start'} : (6*$d1 - 7 + 2*$w)*$d1 + 1-$w + $self->{'n_start'}, (6*$d2 - 6 + 2*$w)*$d2 - $w + $self->{'n_start'}); } 1; __END__ =for stopwords Ryde Math-PlanePath pentagonals OEIS factorization =head1 NAME Math::PlanePath::AnvilSpiral -- integer points around an "anvil" shape =head1 SYNOPSIS use Math::PlanePath::AnvilSpiral; my $path = Math::PlanePath::AnvilSpiral->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes a spiral around an anvil style shape, ...-78-77-76-75-74 4 / 49-48-47-46-45-44-43-42-41-40-39-38 73 3 \ / / 50 21-20-19-18-17-16-15-14 37 72 2 \ \ / / / 51 22 5--4--3--2 13 36 71 1 \ \ \ / / / / 52 23 6 1 12 35 70 <- Y=0 / / / \ \ \ 53 24 7--8--9-10-11 34 69 -1 / / \ \ 54 25-26-27-28-29-30-31-32-33 68 -2 / \ 55-56-57-58-59-60-61-62-63-64-65-66-67 -3 ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 The pentagonal numbers 1,5,12,22,etc, P(k) = (3k-1)*k/2 fall alternately on the X axis XE0, and on the Y=1 horizontal XE0. Those pentagonals are always composites, from the factorization shown, and as noted in L, the immediately preceding P(k)-1 and P(k)-2 are also composites. So plotting the primes on the spiral has a 3-high horizontal blank line at Y=0,-1,-2 for positive X, and Y=1,2,3 for negative X (after the first few values). Each loop around the spiral is 12 longer than the preceding. This is 4* more than the step=3 C so straight lines on a C like these pentagonals are also straight lines here, but split into two parts. The outward diagonal excursions are similar to the C, but there's just 4 of them here where the C has 8. This is reflected in the loop step. The basic C is step 8, but by taking 4 excursions here increases that to 12, and in the C 8 excursions adds 8 to make step 16. =head2 Wider An optional C parameter makes the path wider by starting with a horizontal section of given width. For example $path = Math::PlanePath::SquareSpiral->new (wider => 3); gives =cut # math-image --path=AnvilSpiral,wider=3 --all --output=numbers_dash --size=60x12 # but 2 chars per cell =pod 33-32-31-30-29-28-27-26-25-24-23 ... 2 \ / / 34 11-10--9--8--7--6--5 22 51 1 \ \ / / / 35 12 1--2--3--4 21 50 <- Y=0 / / \ \ 36 13-14-15-16-17-18-19-20 49 -1 / \ 37-38-39-40-41-42-43-44-45-46-47-48 -2 ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 The starting point 1 is shifted to the left by ceil(wider/2) places to keep the spiral centred on the origin X=0,Y=0. This is the same starting offset as the C C. Widening doesn't change the nature of the straight lines which arise, it just rotates them around. Each loop is still 12 longer than the previous, since the widening is essentially a constant amount in each loop. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start with the same shape. For example to start at 0, =cut # math-image --path=AnvilSpiral,n_start=0 --all --output=numbers_dash --size=37x12 =pod n_start => 0 20-19-18-17-16-15-14-13 ... \ / / 21 4--3--2--1 12 35 \ \ / / / 22 5 0 11 34 / / \ \ 23 6--7--8--9-10 33 / \ 24-25-26-27-28-29-30-31-32 The only effect is to push the N values around by a constant amount. It might help match coordinates with something else zero-based. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::AnvilSpiral-Enew ()> =item C<$path = Math::PlanePath::AnvilSpiral-Enew (wider =E $integer, n_start =E $n)> Create and return a new anvil spiral object. An optional C parameter widens the spiral path, it defaults to 0 which is no widening. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back default wider=0, n_start=1 A033570 N on X axis, alternate pentagonals (2n+1)*(3n+1) A126587 N on Y axis A136392 N on Y negative (n=-Y+1) A033568 N on X=Y diagonal, alternate second pents (2*n-1)*(3*n-1) A085473 N on south-east diagonal wider=0, n_start=0 A211014 N on X axis, 14-gonal numbers of the second kind A139267 N on Y axis, 2*octagonal A049452 N on X negative, alternate pentagonals A033580 N on Y negative, 4*pentagonals A051866 N on X=Y diagonal, 14-gonal numbers A094159 N on north-west diagonal, 3*hexagonals A049453 N on south-west diagonal, alternate second pentagonal A195319 N on south-east diagonal, 3*second hexagonals wider=1, n_start=0 A051866 N on X axis, 14-gonal numbers A049453 N on Y negative, alternate second pentagonal A033569 N on north-west diagonal A085473 N on south-west diagonal A080859 N on Y negative A033570 N on south-east diagonal alternate pentagonals (2n+1)*(3n+1) wider=2, n_start=1 A033581 N on Y axis (6*n^2) except for initial N=2 =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/AlternatePaperMidpoint.pm0000644000175000017500000004237213734026674022216 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=AlternatePaperMidpoint,arms=8 --all --output=numbers_dash # math-image --path=AlternatePaperMidpoint --lines --scale=20 # # A334576 ~/OEIS/a334576.gp.txt # Remy Sigrist, building vector of coordinates by segment expansion package Math::PlanePath::AlternatePaperMidpoint; use 5.004; use strict; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::AlternatePaper; # uncomment this to run the ### lines #use Smart::Comments; use constant parameter_info_array => [ { name => 'arms', share_key => 'arms_8', display => 'Arms', type => 'integer', minimum => 1, maximum => 8, default => 1, width => 1, description => 'Arms', } ]; use constant n_start => 0; sub x_negative { my ($self) = @_; return ($self->{'arms'} >= 3); } sub y_negative { my ($self) = @_; return ($self->{'arms'} >= 5); } { my @x_negative_at_n = (undef, undef,undef,11,3, 3,3,3,3); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } { my @y_negative_at_n = (undef, undef,undef,undef,undef, 24,11,12,7); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } sub sumxy_minimum { my ($self) = @_; return ($self->arms_count <= 3 ? 0 # 1,2,3 arms above X=-Y diagonal : undef); } sub diffxy_minimum { my ($self) = @_; return ($self->arms_count == 1 ? 0 # 1 arms right of X=Y diagonal : undef); } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(8, $self->{'arms'} || 1)); return $self; } # +-----------+ states # |\ -------/| # | \ \ 4 / | # |^ \ \ / | # || \ v / /|| # || \ / / || # ||8 / * /12|| # || / / \ || # ||/ / ^ \ || # | / \ \ v| # | / 0 \ \ | # |/ ------ \| # +-----------+ # # + state=0 digits # /|\ # / | \ # / | \ # /\ 1|3 /\ # / \ | / \ # / 0 \|/ 2 \ # +------+------+ my @next_state = (0, 12, 0, 8, # 0 forward 4, 8, 4, 12, # 4 forward NW 4, 8, 0, 8, # 8 reverse 0, 12, 4, 12, # 12 reverse NE ); my @digit_to_x = (0,0,1,1, 1,1,0,0, 0,0,0,0, 1,1,1,1, ); my @digit_to_y = (0,0,0,0, 1,1,1,1, 0,0,1,1, 1,1,0,0, ); sub n_to_xy { my ($self, $n) = @_; ### AlternatePaperMidpoint n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } { my $int = int($n); if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'}); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $zero = ($n * 0); # inherit bignum 0 my $arm = _divrem_mutate ($n, $self->{'arms'}); ### $arm ### $n my @digits = digit_split_lowtohigh($n,4); my $state = my $dirstate = 0; my @x; my @y; foreach my $i (reverse 1 .. scalar(@digits)) { my $digit = $digits[$i-1]; # high to low, all digits $state += $digit; if ($digit != 3) { $dirstate = $state; } $x[$i] = $digit_to_x[$state]; # high to low, leaving one lowest $y[$i] = $digit_to_y[$state]; $state = $next_state[$state]; } $x[0] = $digit_to_x[$state]; # state=4,12 increment $y[0] = $digit_to_y[$state + 3]; # state=4,8 increment my $x = digit_join_lowtohigh(\@x,2,$zero); my $y = digit_join_lowtohigh(\@y,2,$zero); ### final: "x=$x,y=$y state=$state" if ($arm & 1) { ($x,$y) = ($y+1,$x+1); # transpose and offset } if ($arm & 2) { ($x,$y) = (-$y,$x+1); # rotate +90 and offset } if ($arm & 4) { $x = -1 - $x; # rotate 180 and offset $y = 1 - $y; } # ### rotated return: "$x,$y" return ($x,$y); } # | | # 64-65-66 71-72-73-74 95 # | | # 63 98-97-96 # | | # 20-21 62 99 # | | | # 19 22 61-60-59 # | | | # 16-17-18 23 56-57-58 # | | | # 15 26-25-24 55 50-49-48-47 # | | | | | # 4--5 14 27-28-29 54 51 36-37 46 # | | | | | | | | | # 3 6 13-12-11 30 53-52 35 38 45-44-43 # | | | | | | | # 0--1--2 7--8--9-10 31-32-33-34 39-40-41-42 # # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 43-35 42-50-58 57-49-41 # | | | | # 91-99 51 27 34-26-18 17-25-33 # | | | | | # 83-75-67-59 19-11--3 10 9 32-40 # | | | | # 84-76-68-60 20-12--4 2 1 24 48 96-88 # | | | | | | # 92 52 28 5 6 0--8-16 56-64-72-80 # | | | | # 44-36 13 14 7-15-23 63-71-79-87 # | | | | | # 37-29-21 22-30-38 31 55 95 # | | | | # 45-53-61 62-54-46 39-47 # | | # 69 70 sub xy_to_n { my ($self, $x, $y) = @_; ### AlternatePaperMidpoint xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); if (is_infinite($x)) { return $x; # infinity } if (is_infinite($y)) { return $y; # infinity } # arm in various octants, rotate/transpose to first my $arm; if ($y >= ($x>=0?0:2)) { # Y>=0 when X positive, Y>=2 when X negative $arm = 0; } else { # lower arms 4,5,6,7 ... $arm = 4; $x = -1 - $x; # rotate 180, offset $y = 1 - $y; } if ($x < ($y>0?1:0)) { ### second quad arms 2,3 ... ($x,$y) = ($y-1,-$x); # rotate -90, offset $arm += 2; } if ($y > $x-($x%2)) { ### above diagonal, arm 1 ... ($x,$y) = ($y-1,$x-1); # offset and transpose $arm++; } ### assert: $x >= 0 ### assert: $y >= 0 ### assert: $y <= $x - ($x%2) if ($arm >= $self->{'arms'}) { return undef; } my ($len, $level) = round_down_pow ($x, 2); if (is_infinite($level)) { return ($level); } # + state=0 digits # /|\ # / | \ # / | \ # /\ 1|3 /\ # / \ | / \ # / 0 \|/ 2 \ # +------+------+ # + state=0 digits # /|\ # / | \ # / | \ # /\ 2|0 /\ # / \ | / \ # / 3 \|/ 1 \ # +------+------+ my $n = ($x * 0 * $y); # inherit bignum 0 my $rev = 0; $len *= 2; while ($level-- >= 0) { ### at: "xy=$x,$y rev=$rev len=$len n=".sprintf('%#x',$n) ### assert: $x >= 0 ### assert: $y >= 0 ### assert: $y <= $x - ($x%2) ### assert: $x+$y+($x%2) < 2*$len my $digit; if ($x < $len) { ### diagonal: $x+$y+($x%2), $len if ($x+$y+($x%2) < $len) { ### part 0 ... $digit = 0; } else { ### part 1 ... ($x,$y) = ($y,$len-1-$x); # shift, rotate -90 $rev ^= 3; $digit = 2; # becoming digit=1 with reverse } } else { $x -= $len; ### 2,3 ycmp: $y, $x-($x%2) if ($y <= $x-($x%2)) { ### part 2 ... $digit = 2; } else { ### part 3 ... ($x,$y) = ($len-1-$y,$x); # shift, rotate +90 $rev ^= 3; $digit = 0; # becoming digit=3 with reverse } } ### $digit $digit ^= $rev; # $digit = 3-$digit if reverse ### reversed digit: $digit $n *= 4; $n += $digit; $len /= 2; } ### final: "xy=$x,$y rev=$rev" ### assert: $x == 0 ### assert: $y == 0 return $n*$self->{'arms'} + $arm; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### AlternatePaperMidpoint rect_to_n_range(): "$x1,$y1 $x2,$y2 arms=$self->{'arms'}" $x1 = round_nearest($x1); $x2 = round_nearest($x2); $y1 = round_nearest($y1); $y2 = round_nearest($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; my $arms = $self->{'arms'}; if (($arms == 1 && $y1 > $x2) # x2,y1 bottom right corner || ($arms <= 2 && $x2 < 0) || ($arms <= 4 && $y2 < 0)) { ### outside ... return (1,0); } my ($len) = round_down_pow (max ($x2, ($arms >= 2 ? $y2-1 : ()), ($arms >= 4 ? -1-$x1 : ()), ($arms >= 6 ? -$y1 : ())), 2); return (0, 2*$arms*$len*$len-1); } #------------------------------------------------------------------------------ # levels use Math::PlanePath::DragonMidpoint; *level_to_n_range = \&Math::PlanePath::DragonMidpoint::level_to_n_range; *n_to_level = \&Math::PlanePath::DragonMidpoint::n_to_level; #------------------------------------------------------------------------------ 1; __END__ =for stopwords Math-PlanePath eg Ryde OEIS =head1 NAME Math::PlanePath::AlternatePaperMidpoint -- alternate paper folding midpoints =head1 SYNOPSIS use Math::PlanePath::AlternatePaperMidpoint; my $path = Math::PlanePath::AlternatePaperMidpoint->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is the midpoints of each alternate paper folding curve (L). 8 | 64-65-... | | 7 | 63 | | 6 | 20-21 62 | | | | 5 | 19 22 61-60-59 | | | | 4 | 16-17-18 23 56-57-58 | | | | 3 | 15 26-25-24 55 50-49-48-47 | | | | | | 2 | 4--5 14 27-28-29 54 51 36-37 46 | | | | | | | | | | 1 | 3 6 13-12-11 30 53-52 35 38 45-44-43 | | | | | | | | Y=0 | 0--1--2 7--8--9-10 31-32-33-34 39-40-41-42 +---------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 The C curve begins as follows and the midpoints are numbered from 0, | 9 | --8-- | | 7 | | | --2-- --6-- | | | 1 3 5 | | | *--0-- --4-- These midpoints are on fractions X=0.5,Y=0, X=1,Y=0.5, etc. For this C they're turned 45 degrees and mirrored so the 0,1,2 upward diagonal becomes horizontal along the X axis, and the 2,3,4 downward diagonal becomes a vertical at X=2, extending to X=2,Y=2 at N=4. The midpoints are distinct X,Y positions because the alternate paper curve traverses each edge only once. The curve is self-similar in 2^level sections due to its unfolding. This can be seen in the midpoints as for example N=0 to N=16 above is the same shape as N=16 to N=32, but the latter rotated +90 degrees and numbered in reverse. =head2 Arms The midpoints fill an eighth of the plane and eight copies can mesh together perfectly when mirrored and rotated by 90, 180 and 270 degrees. The C parameter can choose 1 to 8 curve arms successively advancing. For example C 8> begins as follows. N=0,8,16,24,etc is the first arm, the same as the plain curve above. N=1,9,17,25 is the second, N=2,10,18,26 the third, etc. 90-82 81-89 7 arms => 8 | | | | ... 74 73 ... 6 | | 66 65 5 | | 43-35 42-50-58 57-49-41 4 | | | | 91-.. 51 27 34-26-18 17-25-33 3 | | | | | 83-75-67-59 19-11--3 10 9 32-40 2 | | | | 84-76-68-60 20-12--4 2 1 24 48 ..-88 1 | | | | | | 92-.. 52 28 5 6 0--8-16 56-64-72-80 <- Y=0 | | | | 44-36 13 14 7-15-23 63-71-79-87 -1 | | | | | 37-29-21 22-30-38 31 55 ..-95 -2 | | | | 45-53-61 62-54-46 39-47 -3 | | 69 70 -4 | | ... 77 78 ... -5 | | | | 93-85 86-94 -6 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 With eight arms like this every X,Y point is visited exactly once, because the 8-arm C traverses every edge exactly once (L). The arm numbering doesn't correspond to the C, due to the rotate and reflect of the first arm. It ends up arms 0 and 1 of the C corresponding to arms 7 and 0 of the midpoints here, those two being a pair going horizontally corresponding to a pair in the C going diagonally into a quadrant. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::AlternatePaperMidpoint-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =item C<$n = $path-En_start()> Return 0, the first N in the path. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 2**$level - 1)>, or for multiple arms return C<(0, $arms * (2**$level - 1)*$arms)>. This is the same as the C. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A334576 X coordinate A334577 Y coordinate A016116 X/2 at N=2^k, being X/2=2^floor(k/2) =head1 SEE ALSO L, L L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/PyramidRows.pm0000644000175000017500000007047013734026657020064 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # rule=50,58,114,122,178,179,186,242,250 # spacing=2,step=1 # full V with points spaced apart # math-image --path=CellularRule,rule=50 --all --text # # A091018, A090894 using n_start=0 # A196199, A000196, A053186 using n_start=0 package Math::PlanePath::PyramidRows; use 5.004; use strict; use Carp 'croak'; #use List::Util 'min','max'; *min = \&Math::PlanePath::_min; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; use constant parameter_info_array => [ { name => 'step', share_key => 'step_2', display => 'Step', type => 'integer', minimum => 0, default => 2, width => 2, description => 'How much longer each row is than the preceding.', }, { name => 'align', type => 'enum', share_key => 'align_crl', display => 'Align', default => 'centre', choices => ['centre', 'right', 'left'], choices_display => ['Centre', 'Right', 'Left'], }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; { my %align_x_negative_step = (left => 1, centre => 2); sub x_negative { my ($self) = @_; my $align = $self->{'align'}; return ($align ne 'right' && $self->{'step'} >= $align_x_negative_step{$align}); } } sub x_maximum { my ($self) = @_; return ($self->{'step'} == 0 || $self->{'align'} eq 'left' ? 0 # X=0 vertical, or left X<=0 : undef); } { my %x_negative_at_n = (left => 3, right => 5, up => 3, down => 5); sub x_negative_at_n { my ($self) = @_; return (($self->{'align'} eq 'left' && $self->{'step'} >= 1) || ($self->{'align'} eq 'centre' && $self->{'step'} >= 2) ? $self->n_start + 1 : undef); } } sub sumxy_minimum { my ($self) = @_; # for align=left step<=1 has X>=-Y so X+Y >= 0 # for align=centre step<=3 has X>=-Y so X+Y >= 0 # for align=right X>=0 so X+Y >= 0 return (($self->{'align'} eq 'left' && $self->{'step'} <= 1) || ($self->{'align'} eq 'centre' && $self->{'step'} <= 3) || ($self->{'align'} eq 'right') ? 0 : undef); } sub diffxy_maximum { my ($self) = @_; # for align=left X<=0 so X-Y<=0 always # for align=centre step<=2 has X<=Y so X-Y<=0 # for align=right step<=1 has X<=Y so X-Y<=0 return (($self->{'align'} eq 'left') || ($self->{'align'} eq 'centre' && $self->{'step'} <= 2) || ($self->{'align'} eq 'right' && $self->{'step'} <= 1) ? 0 : undef); } sub dx_minimum { my ($self) = @_; return ($self->{'step'} == 0 ? 0 : undef); } sub dx_maximum { my ($self) = @_; return ($self->{'step'} == 0 ? 0 # vertical only : 1); # East } sub dy_minimum { my ($self) = @_; return ($self->{'step'} == 0 ? 1 : 0); } use constant dy_maximum => 1; sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; return ($self->{'step'} == 0 ? (0,1) # N always : ()); } sub absdx_minimum { my ($self) = @_; return ($self->{'step'} == 0 || $self->{'align'} eq 'right' # dX=0 at N=1 || ($self->{'step'} == 1 && $self->{'align'} eq 'centre') ? 0 : 1); } sub absdy_minimum { my ($self) = @_; return ($self->{'step'} == 0 ? 1 : 0); } # within row X increasing dSum=1 # end row decrease by big sub dsumxy_minimum { my ($self) = @_; return ($self->{'step'} == 0 ? 1 : undef); } use constant dsumxy_maximum => 1; sub ddiffxy_minimum { my ($self) = @_; return ($self->{'step'} == 0 ? -1 # constant North dY=1 : undef); } sub ddiffxy_maximum { my ($self) = @_; return ($self->{'step'} == 0 ? -1 # constant North dY=1 : 1); } sub dir_minimum_dxdy { my ($self) = @_; return ($self->{'step'} == 0 ? (0,1) # north only : (1,0)); # east } sub dir_maximum_dxdy { my ($self) = @_; return ($self->{'step'} == 0 ? (0,1) # north only : (-1,0)); # supremum, west and 1 up } sub turn_any_left { my ($self) = @_; return ($self->{'step'} != 0); # always straight vertical only } *turn_any_right = \&turn_any_left; #------------------------------------------------------------------------------ my %align_known = (left => 1, right => 1, centre => 1); sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } my $align = ($self->{'align'} ||= 'centre'); $align_known{$align} or croak "Unrecognised align option: ",$align; my $step = $self->{'step'}; $step = $self->{'step'} = (! defined $step ? 2 # default : $step < 0 ? 0 # minimum : $step); my $left_slope = $self->{'left_slope'} = ($align eq 'left' ? $step : $align eq 'right' ? 0 : int($step/2)); # 'centre' my $right_slope = $self->{'right_slope'} = $step - $left_slope; # "b" term in the quadratic giving N on the Y axis $self->{'axis_b'} = $left_slope - $right_slope + 2; ### $align ### $step ### $left_slope ### right_slope: $self->{'right_slope'} return $self; } # step==2 row line beginning at x=-0.5, # y = 0 1 2 3 4 # N start = -0.5 1.5 4.5 9.5 16.5 # # # step==1 # N = (1/2*$d^2 + 1/2*$d + 1/2) # s = -1/2 + sqrt(2 * $n + -3/4) # step==2 # N = ($d^2 + 1/2) # s = 0 + sqrt(1 * $n + -1/2) # step==3 # N = (3/2*$d^2 + -1/2*$d + 1/2) # s = 1/6 + sqrt(2/3 * $n + -11/36) # step==4 # N = (2*$d^2 + -1*$d + 1/2) # s = 1/4 + sqrt(1/2 * $n + -3/16) # # a = $step / 2 # b = 1 - $step / 2 = (2-$step)/2 # c = 0.5 # # s = (-b + sqrt(4*a*$n + b*b - 4*a*c)) / 2*a # = (-b + sqrt(2*$step*$n + b*b - 2*$step*c)) / $step # = (-b + sqrt(2*$step*$n + b*b - $step)) / $step # # N = a*s*s + b*s + c # = $step/2 *s*s + (-$step+2)/2 * s + 1/2 # = ($step * $d*$d - ($step-2)*$d + 1) / 2 # # left at - 0.5 - $d*int($step/2) # so x = $n - (($step * $d*$d - ($step-2)*$d + 1) / 2) - 0.5 - $d*int($step/2) # = $n - (($step * $d*$d - ($step-2)*$d + 1) / 2 + 0.5 + $d*int($step/2)) # = $n - ($step/2 * $d*$d - ($step-2)/2*$d + 1/2 + 0.5 + $d*int($step/2)) # = $n - ($step/2 * $d*$d - ($step-2)/2*$d + 1 + $d*int($step/2)) # = $n - ($step/2 * $d*$d - ($step-2)/2*$d + int($step/2)*$d + 1) # = $n - ($step/2 * $d*$d - (($step-2)/2 - int($step/2))*$d + 1) # = $n - ($step/2 * $d*$d - ($step/2 - int($step/2) - 1)*$d + 1) # = $n - ($step/2 * $d*$d - (($step&1)/2 - 1)*$d + 1) # = $n - ($step * $d*$d - (($step&1) - 2)*$d + 2)/2 # sub n_to_xy { my ($self, $n) = @_; ### PyramidRows n_to_xy(): $n # adjust to N=1 at origin X=0,Y=0 $n = $n - $self->{'n_start'} + 1; # $n<0.5 no good for Math::BigInt circa Perl 5.12, compare in integers return if 2*$n < 1; my $step = $self->{'step'}; if ($step == 0) { # step==0 is vertical line starting N=1 at Y=0 my $int = round_nearest($n); return ($n-$int, $int-1); } my $neg_b = $step-2; my $y = int (($neg_b + _sqrtint(8*$step*$n + $neg_b*$neg_b - 4*$step)) / (2*$step)); ### d frac: (($neg_b + sqrt(int(8*$step*$n) + $neg_b*$neg_b - 4*$step)) / (2*$step)) ### $y ### centre N: (($self->{'step'}*$y + $self->{'axis_b'})*$y/2+1) return ($n - (($self->{'step'}*$y + $self->{'axis_b'})*$y/2+1), $y); } sub n_to_radius { my ($self, $n) = @_; if ($self->{'step'} == 0) { $n = $n - $self->{'n_start'}; # to N=0 basis if ($n < 0) { return undef; } return $n; # vertical on Y axis, including $n=+infinity or nan } return $self->SUPER::n_to_radius($n); } # N = ($step * $y*$y - ($step-2)*$y + 1) / 2 # # right polygonal # P(i) = (k-2)/2 * i*(i+1) - (k-3)*i # = [(k-2)/2 *(i+1) - (k-3) ]*i # = [(k-2)*(i+1) - 2*(k-3) ]/2*i # = [(k-2)*i + k-2 - 2*(k-3) ]/2*i # = [(k-2)*i + k-2 - 2k+6) ]/2*i # = [(k-2)*i + -k+4 ]/2*i # sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); if ($y < 0 || $x < -$y*$self->{'left_slope'} || $x > $y*$self->{'right_slope'}) { return undef; } return (($self->{'step'}*$y + $self->{'axis_b'})*$y/2 + $x + $self->{'n_start'}); } # left N = ($step * $d*$d - ($step-2)*$d + 1) / 2 # plus .5 = ($step * $d*$d - ($step-2)*$d) / 2 + 1 # = (($step * $d - ($step-2))*$d) / 2 + 1 # # left X = - $d*int($step/2) # right X = $d * ceil($step/2) # # x_bottom_start = - y1 * step_left # want x2 >= x_bottom_start # x2 >= - y1 * step_left # x2/step_left >= - y1 # - x2/step_left <= y1 # y1 >= - x2/step_left # y1 >= ceil(-x2/step_left) # # x_bottom_end = y1 * step_right # want x1 <= x_bottom_end # x1 <= y1 * step_right # y1 * step_right >= x1 # y1 >= ceil(x1/step_right) # # left N = (($step * $y1 - ($step-2))*$y1) / 2 + 1 # bottom_offset = $x1 - $y1 * $step_left # N lo = leftN + bottom_offset # = ((step * y1 - (step-2))*y1) / 2 + 1 + x1 - y1 * step_left # = ((step * y1 - (step-2)-2*step_left)*y1) / 2 + 1 + x1 # step_left = floor(step/2) # 2*step_left = step - step&1 # N lo = ((step * y1 - (step-2)-2*step_left)*y1) / 2 + 1 + x1 # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### PyramidRows rect_to_n_range(): "$x1,$y1, $x2,$y2 step=$self->{'step'}" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # swap to y1<=y2 if ($y2 < 0) { return (1, 0); # rect all negative, no N } if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } # swap to x1<=x2 my $left_slope = $self->{'left_slope'}; my $right_slope = $self->{'right_slope'}; my $x_top_right = $y2 * $right_slope; ### $x_top_right ### x_top_left: - $y2 * $left_slope # \ | / # \ | / # \ | / +----- x_top_right > x1 # \ | / |x1,y2 # \|/ # -----+----------- # # \ | x_top_start = -y2*step_left # -----+ \ | x_top_start < x2 # x2,y2| \ | # \ | / # \|/ # -----------+-- # if ($x1 > $x_top_right || $x2 < - $y2 * $left_slope) { ### rect all off to the left or right, no N ... return (1, 0); } ### x1 to x2 of top row y2 intersects some of the pyramid ... ### assert: $x2 >= -$y2*$left_slope ### assert: $x1 <= $y2*$right_slope # raise y1 to the lowest row of the rectangle which intersects some of the # pyramid $y1 = max ($y1, 0, # for x2 >= x_bottom_left, round up $left_slope && int((-$x2+$left_slope-1)/$left_slope), # for x1 <= x_bottom_right, round up $right_slope && int(($x1+$right_slope-1)/$right_slope), ); ### $y1 ### y1 for bottom left: $left_slope && int((-$x2+$left_slope-1)/$left_slope) ### y1 for bottom right: $right_slope && int(($x1+$right_slope-1)/$right_slope) ### assert: $x2 >= -$y1*$left_slope ### assert: $x1 <= $y1*$right_slope return ($self->xy_to_n (max($x1, -$y1*$left_slope), $y1), $self->xy_to_n (min($x2, $x_top_right), $y2)); # my $step = $self->{'step'}; # my $sub = ($step&1) - 2; # # ### x bottom start: -$y1*$left_slope # ### x bottom end: $y1*$right_slope # ### $x1 # ### $x2 # ### bottom left x: max($x1, -$y1*$left_slope) # ### top right x: min ($x2, $x_top_end) # ### $y1 # ### $y2 # ### n_lo: (($step * $y1 - $sub)*$y1 + 2)/2 + max($x1, -$y1*$left_slope) # ### n_hi: (($step * $y2 - $sub)*$y2 + 2)/2 + min($x2, $x_top_end) # # ### assert: $y1-1==$y1 || (($step * $y1 - $sub)*$y1 + 2) == int (($step * $y1 - $sub)*$y1 + 2) # ### assert: $y2-1==$y2 || (($step * $y2 - $sub)*$y2 + 2) == int (($step * $y2 - $sub)*$y2 + 2) # (($step * $y1 - $sub)*$y1 + 2)/2 # + max($x1, -$y1*$left_slope), # x_bottom_start # # (($step * $y2 - $sub)*$y2 + 2)/2 # + min($x2, $x_top_end)); # # # return ($self->xy_to_n (max ($x1, -$y1*$left_slope), $y1), # # $self->xy_to_n (min ($x2, $x_top_end), $y2)); } 1; __END__ =for stopwords pronic PlanePath Ryde Math-PlanePath ie Pentagonals onwards OEIS factorizations factorize generalized =head1 NAME Math::PlanePath::PyramidRows -- points stacked up in a pyramid =head1 SYNOPSIS use Math::PlanePath::PyramidRows; my $path = Math::PlanePath::PyramidRows->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path arranges points in successively wider rows going upwards so as to form an upside-down pyramid. The default step is 2, ie. each row 2 wider than the preceding, an extra point at the left and the right, 17 18 19 20 21 22 23 24 25 4 10 11 12 13 14 15 16 3 step => 2 5 6 7 8 9 2 2 3 4 1 1 <- Y=0 -4 -3 -2 -1 X=0 1 2 3 4 ... XThe right end N=1,4,9,16,etc is the perfect squares. The vertical 2,6,12,20,etc at x=-1 is the Xpronic numbers s*(s+1), half way between those successive squares. The step 2 is the same as the C, C and C paths. For the C, spiral arms going to the right correspond to diagonals in the pyramid, and arms to the left correspond to verticals. =head2 Step Parameter A C parameter controls how much wider each row is than the preceding, to make wider pyramids. For example step 4 my $path = Math::PlanePath::PyramidRows->new (step => 4); makes each row 2 wider on each side successively 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 4 16 17 18 19 20 21 22 23 24 25 26 27 28 3 7 8 9 10 11 12 13 14 15 2 2 3 4 5 6 1 1 <- Y=0 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 ... If the step is an odd number then the extra is at the right, so step 3 gives 13 14 15 16 17 18 19 20 21 22 3 6 7 8 9 10 11 12 2 2 3 4 5 1 1 <- Y=0 -3 -2 -1 X=0 1 2 3 4 ... Or step 1 goes solely to the right. This is equivalent to the Diagonals path, but columns shifted up to make horizontal rows. 11 12 13 14 15 4 7 8 9 10 3 step => 1 4 5 6 2 2 3 1 1 <- Y=0 X=0 1 2 3 4 ... Step 0 means simply a vertical, each row 1 wide and not increasing. This is consistent but unlikely to be much use. The Rows path with C 1 does this too. 5 4 4 3 step => 0 3 2 2 1 1 <- Y=0 X=0 Various number sequences fall in regular patterns positions depending on the step. Large steps are not particularly interesting and quickly become very wide. A limit might be desirable in a user interface, but there's no limit in the code as such. =head2 Align Parameter An optional C parameter controls how the points are arranged relative to the Y axis. The default shown above is "centre". "right" means points to the right of the axis, =cut # math-image --path=PyramidRows,align=right --all --output=numbers =pod align=>"right" 26 27 28 29 30 31 32 33 34 35 36 5 17 18 19 20 21 22 23 24 25 4 10 11 12 13 14 15 16 3 5 6 7 8 9 2 2 3 4 1 1 <- Y=0 X=0 1 2 3 4 5 6 7 8 9 10 "left" is similar but to the left of the Y axis, ie. into negative X. =cut # math-image --path=PyramidRows,align=left --all --output=numbers =pod align=>"left" 26 27 28 29 30 31 32 33 34 35 36 5 17 18 19 20 21 22 23 24 25 4 10 11 12 13 14 15 16 3 5 6 7 8 9 2 2 3 4 1 1 <- Y=0 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 The step parameter still controls how much longer each row is than its predecessor. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same rows sequence. For example to start at 0, =cut # math-image --path=PyramidRows,n_start=0 --all --output=numbers --size=48x5 =pod n_start => 0 16 17 18 19 20 21 22 23 24 4 9 10 11 12 13 14 15 3 4 5 6 7 8 2 1 2 3 1 0 <- Y=0 -------------------------- -4 -3 -2 -1 X=0 1 2 3 4 =head2 Step 3 Pentagonals For step=3 the pentagonal numbers 1,5,12,22,etc, P(k) = (3k-1)*k/2, are at the rightmost end of each row. The second pentagonal numbers 2,7,15,26, S(k) = (3k+1)*k/2 are the vertical at x=-1. Those second numbers are obtained by P(-k), and the two together are the "generalized pentagonal numbers". Both these sequences are composites from 12 and 15 onwards, respectively, and the immediately preceding P(k)-1, P(k)-2, and S(k)-1, S(k)-2 are too. They factorize simply as P(k) = (3*k-1)*k/2 P(k)-1 = (3*k+2)*(k-1)/2 P(k)-2 = (3*k-4)*(k-1)/2 S(k) = (3*k+1)*k/2 S(k)-1 = (3*k-2)*(k+1)/2 S(k)-2 = (3*k+4)*(k-1)/2 Plotting the primes on a step=3 C has the second pentagonal S(k),S(k)-1,S(k)-2 as a 3-wide vertical gap of no primes at X=-1,-2,-3. The the plain pentagonal P(k),P(k-1),P(k)-2 are the endmost three N of each row non-prime. The vertical is much more noticeable in a plot. =cut # math-image --path=PyramidRows,step=3 --all --output=numbers --size=128x7 =pod no primes these three columns no primes these end three except the low 2,7,13 except low 3,5,11 | | | / / / 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 23 24 25 26 27 28 29 30 31 32 33 34 35 13 14 15 16 17 18 19 20 21 22 6 7 8 9 10 11 12 2 3 4 5 1 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 10 11 ... With align="left" the end values can be put into columns, =cut # math-image --path=PyramidRows,step=3,align=left --all --output=numbers --size=150x6 =pod no primes these end three align => "left" except low 3,5,11 | | | 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 5 23 24 25 26 27 28 29 30 31 32 33 34 35 4 13 14 15 16 17 18 19 20 21 22 3 6 7 8 9 10 11 12 2 2 3 4 5 1 1 <- Y=0 ... -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 In general a constant offset S(k)-c is a column and from P(k)-c is a diagonal sloping up dX=2,dY=1 right. The simple factorizations above using the roots of the quadratic P(k)-c or S(k)-c is possible whenever 24*c+1 is a perfect square. This means the further columns S(k)-5, S(k)-7, S(k)-12, etc also have no primes. The columns S(k), S(k)-1, S(k)-2 are prominent because they're adjacent. There's no other adjacent columns of this type because the squares after 49 are too far apart for 24*c+1 to be a square for successive c. Of course there could be other reasons for other columns or diagonals to have few or many primes. =cut # (3/2)*k^2 + (1/2)*k - c # roots (-1/2 +/- sqrt ((1/2)^2 - 4*(3/2)*-c)) / (2*(3/2)) # = (-1/2 +/- sqrt (1/4 + (12/2)*c)) / 3 # = -1/6 +/- sqrt (1/4 + (12/2)*c)/3 # = -1/6 +/- sqrt (1/4 + 6*c)/3 # = -1/6 +/- sqrt (1/4 + 6*c)*2/6 # = -1/6 +/- sqrt (4*(1/4 + 6*c))/6 # = -1/6 +/- sqrt(1 + 24c)/6 # must have 1+24c a perfect square to factorize by roots # # i i^2 i^2 mod 24 # 0 0 0 # 1 1 1 1+0*24 # 2 4 4 # 3 9 9 # 4 16 16 # 5 25 1 1+1*24 # 6 36 12 # 7 49 1 1+2*24 # 8 64 16 # 9 81 9 # 10 100 4 # 11 121 1 1+5*24 # 12 144 0 # 13 169 1 1+7*24 # 14 196 4 # 15 225 9 # 16 256 16 # 17 289 1 1+12*24 # 18 324 12 # 19 361 1 1+15*24 # 20 400 16 # 21 441 9 # 22 484 4 # 23 529 1 1+22*24 # =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::PyramidRows-Enew ()> =item C<$path = Math::PlanePath::PyramidRows-Enew (step =E $integer, align =E $str, n_start =E $n)> Create and return a new path object. The default C is 2. C is a string, one of "centre" the default "right" points aligned right of the Y axis "left" points aligned left of the Y axis Points are always numbered from left to right in the rows, the alignment changes where each row begins (or ends). =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n <= 0> the return is an empty list since the path starts at N=1. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each point in the pyramid as a square of side 1. If C<$x,$y> is outside the pyramid the return is C. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head2 Descriptive Methods =over =item C<$x = $path-Esumxy_minimum()> =item C<$x = $path-Esumxy_maximum()> Return the minimum or maximum values taken by coordinate sum X+Y reached by integer N values in the path. If there's no minimum or maximum then return C. The path is right and above the X=-Y diagonal, thus giving a minimum sum, in the following cases. align condition for sumxy_minimum=0 ------ ----------------------------- centre step <= 3 right always left step <= 1 =item C<$x = $path-Ediffxy_minimum()> =item C<$x = $path-Ediffxy_maximum()> Return the minimum or maximum values taken by coordinate difference X-Y reached by integer N values in the path. If there's no minimum or maximum then return C. The path is left and above the X=Y leading diagonal, thus giving a minimum X-Y difference, in the following cases. align condition for diffxy_minimum=0 ------ ----------------------------- centre step <= 2 right step <= 1 left always =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back step=1 A002262 X coordinate, runs 0 to k A003056 Y coordinate, k repeated k+1 times A051162 X+Y sum A025581 Y-X diff, runs k to 0 A079904 X*Y product A069011 X^2+Y^2, n_to_rsquared() A080099 X bitwise-AND Y A080098 X bitwise-OR Y A051933 X bitwise-XOR Y A050873 GCD(X+1,Y+1) greatest common divisor by rows A051173 LCM(X+1,Y+1) least common multiple by rows A023531 dY, being 1 at triangular numbers (but starting n=0) A167407 dX-dY, change in X-Y (extra initial 0) A129184 turn 1=left, 0=right or straight A079824 N total along each opposite diagonal A000124 N on Y axis (triangular+1) A000217 N on X=Y diagonal, extra initial 0 step=1, n_start=0 A109004 GCD(X,Y) greatest common divisor starting (0,0) A103451 turn 1=left or right,0=straight, but extra initial 1 A103452 turn 1=left,0=straight,-1=right, but extra initial 1 step=2 A196199 X coordinate, runs -n to +n A000196 Y coordinate, n appears 2n+1 times A053186 X+Y, being distance to next higher square A010052 dY, being 1 at perfect square row end A000290 N on X=Y diagonal, extra initial 0 A002522 N on X=-Y North-West diagonal (start row), Y^2+1 A004201 N for which X>=0, ie. right hand half A020703 permutation N at -X,Y step=2, n_start=0 A005563 N on X=Y diagonal, Y*(Y+2) A000290 N on X=-Y North-West diagonal (start row), Y^2 step=2, n_start=2 A059100 N on north-west diagonal (start each row), Y^2+2 A053615 abs(X), runs k..0..k step=2, align=right, n_start=0 A196199 X-Y, runs -k to +k A053615 abs(X-Y), runs k..0..k step=2, align=left, n_start=0 A005563 N on Y axis, Y*(Y+2) step=3 A180447 Y coordinate, n appears 3n+1 times A104249 N on Y axis, Y*(3Y+1)/2+1 A143689 N on X=-Y North-West diagonal step=3, n_start=0 A005449 N on Y axis, second pentagonals Y*(3Y+1)/2 A000326 N on diagonal north-west, pentagonals Y*(3Y-1)/2 step=4 A084849 N on Y axis A001844 N on X=Y diagonal (North-East) A058331 N on X=-Y North-West diagonal A221217 permutation N at -X,Y step=4, n_start=0 A014105 N on Y axis, the second hexagonal numbers A046092 N on X=Y diagonal, 4*triangular numbers step=4, align=right, n_start=0 A060511 X coordinate, amount n exceeds hexagonal number A000384 N on Y axis, the hexagonal numbers A001105 N on X=Y diagonal, 2*squares step=5 A116668 N on Y axis step=6 A056108 N on Y axis A056109 N on X=Y diagonal (North-East) A056107 N on X=-Y North-West diagonal step=8 A053755 N on X=-Y North-West diagonal step=9 A006137 N on Y axis A038764 N on X=Y diagonal (North-East) =head1 SEE ALSO L, L, L, L, L L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/ImaginaryBase.pm0000644000175000017500000004652013734026670020311 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=ImaginaryBase --lines --scale=10 # math-image --path=ImaginaryBase --all --output=numbers_dash --size=80x50 # # cf A005351 positives as negabinary index # A005352 negatives as negabinary index # A039724 positives as negabinary index, in binary # A027615 negabinary bit count # = 3 * A072894(n+1) - 2n - 3 # A098725 first diffs of A072894 # A000695 same value binary and negabinary, being base 4 digits 0,1 # A001045 abs(negabinary) of 0b11111 all ones (2^n-(-1)^n)/3 # A185269 negabinary primes # # A073785 positives as -3 index # A007608 positives as -4 index # A073786 -5 # A073787 -6 # A073788 -7 # A073789 -8 # A073790 -9 # A039723 positives as negadecimal index # A051022 same value integer and negadecimal, 0s between digits # # http://mathworld.wolfram.com/Negabinary.html # http://mathworld.wolfram.com/Negadecimal.html package Math::PlanePath::ImaginaryBase; use 5.004; use strict; #use List::Util 'min','max'; *min = \&Math::PlanePath::_min; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'parameter_info_array', # radix parameter 'round_down_pow', 'round_up_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use Math::PlanePath::ZOrderCurve; *_digit_interleave = \&Math::PlanePath::ZOrderCurve::_digit_interleave; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant xy_is_visited => 1; use constant absdx_minimum => 1; # X coord always changes sub x_negative_at_n { my ($self) = @_; return $self->{'radix'}**2; } sub y_negative_at_n { my ($self) = @_; return $self->{'radix'}**3; } sub dir_maximum_dxdy { my ($self) = @_; return ($self->{'radix'}-1, -2); } sub turn_any_straight { my ($self) = @_; return ($self->{'radix'} != 2); # radix=2 never straight } sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; return $self->{'radix'} - 1; } sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; return $self->{'radix'}; } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); my $radix = $self->{'radix'}; if (! defined $radix || $radix <= 2) { $radix = 2; } $self->{'radix'} = $radix; return $self; } sub n_to_xy { my ($self, $n) = @_; ### ImaginaryBase n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } # ENHANCE-ME: lowest non-(r-1) digit determines direction to next, or # something like that { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $radix = $self->{'radix'}; my $x = 0; my $y = 0; my $len = ($n*0)+1; # inherit bignum 1 if (my @digits = digit_split_lowtohigh($n, $radix)) { $radix = -$radix; for (;;) { $x += (shift @digits) * $len; # digits low to high @digits || last; $y += (shift @digits) * $len; # digits low to high @digits || last; $len *= $radix; # $radix negative negates each time } } ### final: "$x,$y" return ($x,$y); } # ($x-$digit) and ($y-$digit) are multiples of $radix, but apply int() in # case floating point rounding # sub xy_to_n { my ($self, $x, $y) = @_; ### ImaginaryBase xy_to_n(): "$x, $y" $x = round_nearest ($x); if (is_infinite($x)) { return ($x); } $y = round_nearest ($y); if (is_infinite($y)) { return ($y); } my $radix = $self->{'radix'}; my $zero = ($x * 0 * $y); # inherit bignum 0 my @n; # digits low to high while ($x || $y) { ### at: "x=$x,y=$y n=".join(',',@n) push @n, _divrem_mutate ($x, $radix); $x = -$x; push @n, _divrem_mutate ($y, $radix); $y = -$y; } return digit_join_lowtohigh (\@n,$radix, $zero); } # left xmax = (r-1) + (r^2 -r) + (r^3-r^2) + ... + (r^k - r^(k-1)) # = r^(k-1) - 1 # # right xmin = - (r + r^3 + ... + r^(2k+1)) # = -r * (1 + r^2 + ... + r^2k) # = -r * ((r^2)^(k+1) -1) / (r^2 - 1) # # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### ImaginaryBase rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest($x1); $y1 = round_nearest($y1); $x2 = round_nearest($x2); $y2 = round_nearest($y2); my $zero = $x1 * 0 * $y1 * $x2 * $y2; my $radix = $self->{'radix'}; my ($min_xdigits, $max_xdigits) = _negaradix_range_digits_lowtohigh($x1,$x2, $radix); unless (defined $min_xdigits) { return (0, $max_xdigits); # infinity } my ($min_ydigits, $max_ydigits) = _negaradix_range_digits_lowtohigh($y1,$y2, $radix); unless (defined $min_ydigits) { return (0, $max_ydigits); # infinity } ### $min_xdigits ### $max_xdigits ### min_x: digit_join_lowtohigh ($min_xdigits, $radix, $zero) ### max_x: digit_join_lowtohigh ($max_xdigits, $radix, $zero) ### $min_ydigits ### $max_ydigits ### min_y: digit_join_lowtohigh ($min_ydigits, $radix, $zero) ### max_y: digit_join_lowtohigh ($max_ydigits, $radix, $zero) my @min_digits = _digit_interleave ($min_xdigits, $min_ydigits); my @max_digits = _digit_interleave ($max_xdigits, $max_ydigits); ### final ... ### @min_digits ### @max_digits return (digit_join_lowtohigh (\@min_digits, $radix, $zero), digit_join_lowtohigh (\@max_digits, $radix, $zero)); } # Return arrayrefs ($min_digits, $max_digits) which are the digits making # up the index range for negaradix values $x1 to $x2 inclusive. # The arrays are lowtohigh, so $min_digits->[0] is the least significant digit. # sub _negaradix_range_digits_lowtohigh { my ($x1,$x2, $radix) = @_; ### _negaradix_range_digits(): "$x1,$x2 radix=$radix" if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } # make x1 <= x2 my $radix_minus_1 = $radix - 1; ### $radix ### $radix_minus_1 my ($len, $level, $min_base) = _negaradix_range_level ($x1,$x2, $radix); ### $len ### $level if (is_infinite($level)) { return (undef, $level); } my $max_base = $min_base; ### assert: $min_base <= $x1 ### assert: $min_base + $len > $x2 my @min_digits; # digits formed high to low, stored low to high my @max_digits; while (--$level > 0) { $len /= $radix; ### at: "len=$len reverse" # reversed digits, x1 low end for max, x2 high end for min { my $digit = max (0, min ($radix_minus_1, int (($x2 - $min_base) / $len))); ### min base: $min_base ### min diff: $x2-$min_base ### min digit raw: $digit ### min digit reversed: $radix_minus_1 - $digit $min_base += $digit * $len; $min_digits[$level] = $radix_minus_1 - $digit; } { my $digit = max (0, min ($radix_minus_1, int (($x1 - $max_base) / $len))); ### max base: $max_base ### max diff: $x1-$max_base ### max digit raw: $digit ### max digit reversed: $radix_minus_1 - $digit $max_base += $digit * $len; $max_digits[$level--] = $radix_minus_1 - $digit; } $len /= $radix; ### at: "len=$len plain" # plain digits, x1 low end for min, x2 high end for max { my $digit = max (0, min ($radix_minus_1, int (($x1 - $min_base) / $len))); ### min base: $min_base ### min diff: $x1-$min_base ### min digit: $digit $min_base += $digit * $len; $min_digits[$level] = $digit; } { my $digit = max (0, min ($radix_minus_1, int (($x2 - $max_base) / $len))); ### max base: $max_base ### max diff: $x2-$max_base ### max digit: $digit $max_base += $digit * $len; $max_digits[$level] = $digit; } } ### @min_digits ### @max_digits return (\@min_digits, \@max_digits); } # return ($len,$level,$base) # $level = number of digits in the bigest integer in negaradix $x1..$x2, # rounded up to be $level even # $len = $radix**$level # $base = lowest negaradix reached by indexes from 0 to $len-1 # # have $base <= $x1, $x2 < $base+$len # and $level is the smallest even number with that coverage # # negabinary # 0,1,5,21 # # negaternary # 1 3 9 27 81 243 # 0,2, 20 182 # -6 -60 -546 # sub _negaradix_range_level { my ($x1,$x2, $radix) = @_; ### _negaradix_range_level(): "$x1,$x2 radix=$radix" ### assert: $x1 <= $x2 my ($len, $level) = round_down_pow (max($radix - $x1*($radix + 1), (($radix+1)*$x2 - 1) * $radix), $radix); if ($level & 1) { ### increase level to even ... $len *= $radix; $level += 1; } ### $len ### $level # because level is even r^2k-1 is a multiple of r^2-1 and therefore of r+1 ### assert: ($len-1) % ($radix+1) == 0 return ($len, $level, ((1-$len) / ($radix+1)) * $radix); # base } #------------------------------------------------------------------------------ # levels # shared by ImaginaryHalf and CubicBase sub level_to_n_range { my ($self, $level) = @_; return (0, $self->{'radix'}**$level - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($pow, $exp) = round_up_pow ($n+1, $self->{'radix'}); return $exp; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Math-PlanePath quater-imaginary Radix radix ie Negabinary negabinary negaternary negadecimal NX negaradix Nmin Nmax Nmin,Nmax NX NX,NY OEIS Seminumerical CACM =head1 NAME Math::PlanePath::ImaginaryBase -- replications in four directions =head1 SYNOPSIS use Math::PlanePath::ImaginaryBase; my $path = Math::PlanePath::ImaginaryBase->new (radix => 4); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is a simple pattern arising from complex numbers expressed in a base i*sqrt(2) or other i*sqrt(r) base. Or equivalently by negabinary encoded X,Y digits interleaved. The default radix=2 is 38 39 34 35 54 55 50 51 5 36 37 32 33 52 53 48 49 4 46 47 42 43 62 63 58 59 3 44 45 40 41 60 61 56 57 2 6 7 2 3 22 23 18 19 1 4 5 0 1 20 21 16 17 <- Y=0 14 15 10 11 30 31 26 27 -1 12 13 8 9 28 29 24 25 -2 ^ -2 -1 X=0 1 2 3 4 5 The pattern can be seen by dividing into blocks as follows, +---------------------------------------+ | 38 39 34 35 54 55 50 51 | | | | 36 37 32 33 52 53 48 49 | | | | 46 47 42 43 62 63 58 59 | | | | 44 45 40 41 60 61 56 57 | +---------+---------+-------------------+ | 6 7 | 2 3 | 22 23 18 19 | | +----+----+ | | 4 5 | 0 | 1 | 20 21 16 17 | +---------+----+----+ | | 14 15 10 11 | 30 31 26 27 | | | | | 12 13 8 9 | 28 29 24 25 | +-------------------+-------------------+ After N=0 at the origin, N=1 replicates that single point to the right. Then that pair repeats above as N=2 and N=3. Then that 2x2 block repeats to the left as N=4 to N=7, then 4x2 repeated below as N=8 to N=16. Then 4x4 to the right as N=16 to N=31, etc. Each repeat is 90 degrees further around. The relative layout and orientation of a sub-part is unchanged when replicated. =head2 Complex Base This pattern arises from representing a complex number in "base" i*sqrt(r). For an integer X,Y, base b = i*sqrt(r) a[j] = digit 0 to r-1 X + Y*i*sqrt(r) = a[k]*b^k + ... + a[2]*b^2 + a[1]*b + a[0] and N is the a[j] digits in base r N = a[k]*r^k + ... + a[2]*r^2 + a[1]*r + a[0] XThe sum has imaginary part with a factor sqrt(r). Dividing that out gives an integer Y. For actual use as a number base, that factor can be omitted and instead fractional digits a[-1]*r^-1 etc used to reach smaller Y values, as for example in Knuth's "quater-imaginary" system of base 2*i, being i*sqrt(4), with digits 0,1,2,3. (Knuth Seminumerical Algorithms section 4.1 and CACM 1960 pp245-247.) The powers of i in the base give the replication direction, so i^0=1 right, i^1=i up, i^2=-1 right, i^3=-i down, etc. The power of sqrt(r) then spreads the replication in the respective direction. Two steps repeat horizontally and sqrt(r)^2=r hence the doubling of 1x1 to the right, 2x2 to the left, 4x4 to the right, etc, and similarly vertically. =head2 Negabinary The way blocks repeat horizontally first to the right and then to the left is per the negabinary system base b=-2. X = x[k]*(-2)^k + ... + x[2]*(-2)^2 + x[1]*(-2) + x[0] The effect is to represent any positive or negative X by a positive integer index NX. X, negabinary: ... -1 -2 0 1 2 3 4 5 ... index NX: 2 3 0 1 6 7 4 5 Notice how the 0 point replicates to the right as 1 and then that pair 0,1 replicates to the left as 2,3. Then the block 2,3,0,1 repeats to the right as 6,7,4,5 which the same order with 4 added to each. Then the resulting block of eight repeats to the left similarly, in the same order with 8 added to each. C takes the indexes NX and NY of these negabinary forms and forms N by interleaving the digits (bits) of NX and NY. That interleaving is in the style of the C. zX,zY = ZOrderCurve n_to_xy(N) X = to_negabinary(zX) Y = to_negabinary(zY) X,Y equals ImaginaryBase n_to_xy(N) The C replicates blocks alternately right and up, whereas for C here it's right,up,left,down, and so on repeating. =head2 Radix The C parameter controls the radix used to break N into X,Y. For example radix 3 replicates to make 3x1, 3x3, 9x3, 9x9, etc blocks. The replications are radix-1=2 copies of the preceding level at each stage, radix => 3 +------------------------+-----------+ | 24 25 26 15 16 17 | 6 7 8 | 2 | | | | 21 22 23 12 13 14 | 3 4 5 | 1 | +-----------+ | 18 19 20 9 10 11 | 0 1 2 | <- Y=0 +------------------------+-----------+ | 51 52 53 42 43 44 33 34 35 | -1 | | | 48 49 50 39 40 41 30 31 32 | -2 | | | 45 46 47 36 37 38 27 28 29 | -3 | | | 78 79 80 69 70 71 60 61 62 | -4 | | | 75 76 77 66 67 68 57 58 59 | -5 | | | 72 73 74 63 64 65 54 55 56 | -6 +------------------------------------+ ^ -6 -5 -4 -3 -2 -1 X=0 1 2 X,Y are "negaternary" in this case, and similar negaradix base=-radix for higher values. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::ImaginaryBase-Enew ()> =item C<$path = Math::PlanePath::ImaginaryBase-Enew (radix =E $r)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, $radix**$level - 1)>. =back =head1 FORMULAS =head2 Rectangle to N Range The X and Y ranges can be treated separately and then interleaved, NXmin,NXmax = negaradix range to cover x1..x2 NYmin,NYmax = negaradix range to cover y1..y2 Nmin = interleave digits NXmin, NYmin Nmax = interleave digits NXmax, NYmax If the NX,NY ranges are exact then the resulting Nmin,Nmax range is exact. An exact negaradix range can be calculated by digits high to low by considering the range taken by the negaradix form. For example two negaternary digits, N digit 2 1 0 +---------+---------+---------+ N index | 6 7 8 | 3 4 5 | 0 1 2 | +---------+---------+---------+ X negaternary -6 -5 -4 -3 -2 -1 0 1 2 ^ base Taking the base=-90909...90 which is the lowest taken (where 9 is the radix digit R-1), then the next digit of N is the position from X-base, taken alternately reverse 2,1,0 as shown here or forward 0,1,2. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include, =over L (etc) =back radix=2 A057300 permutation N at transpose Y,X (swap bit pairs) radix=3 A163327 permutation N at transpose Y,X (swap trit pairs) radix=4 A126006 permutation N at transpose Y,X (swap digit pairs) radix=16 A217558 permutation N at transpose Y,X (swap digit pairs) =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/MPeaks.pm0000644000175000017500000002337613734026667016770 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::MPeaks; use 5.004; use strict; use List::Util 'min'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad12; sub x_negative_at_n { my ($self) = @_; return $self->n_start; } # dX jumps back unbounded negative, but forward only +1 use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant dsumxy_maximum => 2; # NE diagonal use constant ddiffxy_maximum => 2; # SE diagonal use constant dir_minimum_dxdy => (1,1); # North-East use constant dir_maximum_dxdy => (1,-1); # South-East use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # starting each left side at 0.5 before # [ 1,2,3 ], # [ 1-0.5, 6-0.5, 17-0.5 ] # N = (3 d^2 - 4 d + 3/2) # = (3*$d**2 - 4*$d + 3/2) # = ((3*$d - 4)*$d + 3/2) # d = 2/3 + sqrt(1/3 * $n + -1/18) # = (2 + 3*sqrt(1/3 * $n - 1/18))/3 # = (2 + sqrt(3 * $n - 1/2))/3 # = (4 + 2*sqrt(3 * $n - 1/2))/6 # = (4 + sqrt(12*$n - 2))/6 # at n=1/2 d=(4+sqrt(12/2-2))/6 = (4+sqrt(4))/6 = 1 # # base at Y=0 # [ 1, 6, 17 ] # N = (3 d^2 - 4 d + 2) # = (3*$d**2 - 4*$d + 2) # = ((3*$d - 4)*$d + 2) # # centre # [ 3,11,25 ] # N = (3 d^2 - d + 1) # = (3*$d**2 - $d + 1) # = ((3*$d - 1)*$d + 1) # sub n_to_xy { my ($self, $n) = @_; ### MPeaks n_to_xy(): $n # adjust to N=0 at start X=-1,Y=0 $n = $n - $self->{'n_start'}; my $d; { my $r = 12*$n + 10; if ($r < 4) { return; # N < -0.5, so before start of path } $d = int( (_sqrtint($r) + 4)/6 ); } $n -= (3*$d - 1)*$d; # to $n==0 at centre ### $d ### remainder: $n if ($n >= $d) { ### right vertical ... # N-d is top of right peak # N-(3d-1) = N-3d+1 is right Y=0 # Y=-(N-2d+1)= -N+3d-1 return ($d, -$n + 3*$d - 1); } if ($n <= (my $neg_d = -$d)) { ### left vertical ... # N+(3d-1) is left Y=0 # Y=N+3d-1 return ($neg_d, $n + 3*$d - 1); } ### centre diagonals ... return ($n, abs($n) + $d-1); } sub xy_to_n { my ($self, $x, $y) = @_; ### MPeaks xy_to_n(): $x, $y $y = round_nearest ($y); if ($y < 0) { return undef; } $x = round_nearest ($x); { my $two_x = 2*$x; if ($two_x > $y) { ### right vertical ... # right end [ 5,16,33 ] # N = (3 x^2 + 2 x) return (3*$x+2)*$x - $y + $self->{'n_start'} - 1; } if ($two_x < -$y) { ### left vertical ... # Nleftend = (3 d^2 - 4 d + 2) # = (3x+4)x + 2 return (3*$x+4)*$x + 1 + $y + $self->{'n_start'}; } } ### centre diagonals ... # d=Y+abs(x) with d=0 first (not d=1 as above), N=(3 d^2 + 5 d + 3) my $d = $y - abs($x); ### $d return (3*$d+5)*$d + 2 + $x + $self->{'n_start'}; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # swap to y1<=y2 if ($y2 < 0) { return (1, 0); # rect all negative, no N } if ($y1 < 0) { $y1 *= 0; } # "*=" to preserve bigint y1 $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } # swap to x1<=x2 my $zero = $x1 * 0 * $x2; # columns X<0 are increasing with increasing Y # columns X>0 increase below Y=2*X # return ($self->{'n_start'}, max ( # left column $self->xy_to_n($x1, ($y2 >= 2*$x1 ? $y2 : $y1)), # right column $self->xy_to_n($x2, ($y2 >= 2*$x2 ? $y2 : $y1)), # top row centre X=0, if it's covered by x1,x2 ($x1 < 0 && $x2 > 0 ? $self->xy_to_n($zero,$y2) : ()))); } # No, because N decreases in right hand columns # return (1, # max ($self->xy_to_n($x1,$y2), # $self->xy_to_n($x2,$y2), # # and at X=0 if it's covered by x1,x2 # ($x1 < 0 && $x2 > 0 ? $self->xy_to_n($zero,$y2) : ())); # my @n; # if ($y1 <= 2*$x2) { # # right vertical # push @n, (3*$x2+2)*$x2 - $y1; # } # if (($x1 > 0) != ($x2 > 0)) { # # centre vertical # return (3*$y2+5)*$y2 + 3; # } 1; __END__ =for stopwords Ryde Math-PlanePath ie OEIS =head1 NAME Math::PlanePath::MPeaks -- points in expanding M shape =head1 SYNOPSIS use Math::PlanePath::MPeaks; my $path = Math::PlanePath::MPeaks->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path puts points in layers of an "M" shape =cut # math-image --path=MPeaks --expression='i<=56?i:0' --output=numbers --size=50x10 =pod 41 49 7 40 42 48 50 6 39 22 43 47 28 51 5 38 21 23 44 46 27 29 52 4 37 20 9 24 45 26 13 30 53 3 36 19 8 10 25 12 14 31 54 2 35 18 7 2 11 4 15 32 55 1 34 17 6 1 3 5 16 33 56 <- Y=0 ^ -4 -3 -2 -1 X=0 1 2 3 4 N=1 to N=5 is the first "M" shape, then N=6 to N=16 on top of that, etc. The centre goes half way down. Reckoning the N=1 to N=5 as layer d=1 then Xleft = -d Xright = d Ypeak = 2*d - 1 Ycentre = d - 1 Each "M" is 6 points longer than the preceding. The verticals are each 2 longer, and the centre diagonals each 1 longer. This step 6 is similar to the C. The octagonal numbers N=1,8,21,40,65,etc k*(3k-2) are a straight line of slope 2 going up to the left. The octagonal numbers of the second kind N=5,16,33,56,etc k*(3k+2) are along the X axis to the right. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same pattern. For example to start at 0, =cut # math-image --path=MPeaks,n_start=0 --expression='i<=55?i:0' --output=numbers --size=50x10 =pod n_start => 0 40 48 39 41 47 49 38 21 42 46 27 50 37 20 22 43 45 26 28 51 36 19 8 23 44 25 12 29 52 35 18 7 9 24 11 13 30 53 34 17 6 1 10 3 14 31 54 33 16 5 0 2 4 15 32 55 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::MPeaks-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n < 0.5> the return is an empty list, it being considered there are no negative points. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer which has the effect of treating points as a squares of side 1, so the half-plane y>=-0.5 is entirely covered. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back n_start=1 (the default) A045944 N on X axis >= 1, extra initial 0 being octagonal numbers second kind A056106 N on Y axis, extra initial 1 A056109 N on X negative axis <= -1 n_start=0 A049450 N on Y axis, extra initial 0, 2*pentagonal n_start=2 A027599 N on Y axis, extra initial 6,2 =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut # Local variables: # compile-command: "math-image --path=MPeaks --lines --scale=20" # End: # # math-image --path=MPeaks --all --output=numbers Math-PlanePath-129/lib/Math/PlanePath/SquareArms.pm0000644000175000017500000002323013734026656017656 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=SquareArms --lines --scale=10 # math-image --path=SquareArms --all --output=numbers_dash # math-image --path=SquareArms --values=Polygonal,polygonal=8 # # RepdigitsAnyBase fall on 14 or 15 lines ... # package Math::PlanePath::SquareArms; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; *_sqrtint = \&Math::PlanePath::_sqrtint; # uncomment this to run the ### lines #use Smart::Comments '###'; use constant arms_count => 4; use constant xy_is_visited => 1; use constant x_negative_at_n => 4; use constant y_negative_at_n => 5; use constant turn_any_right => 0; # only left or straight #------------------------------------------------------------------------------ # 28 # 172 +144 # 444 +272 +128 # 844 +400 +128 # [ 0, 1, 2, 3,], # [ 0, 2, 6, 12 ], # N = (d^2 + d) # d = -1/2 + sqrt(1 * $n + 1/4) # = (-1 + 2*sqrt($n + 1/4)) / 2 # = (-1 + sqrt(4*$n + 1)) / 2 sub n_to_xy { my ($self, $n) = @_; ### SquareArms n_to_xy(): $n if ($n < 2) { if ($n < 1) { return; } ### centre return (0, 1-$n); # from n=1 towards n=5 at x=0,y=-1 } $n -= 2; my $frac; { my $int = int($n); $frac = $n - $int; $n = $int; # BigFloat int() gives BigInt, use that } # arm as initial rotation my $rot = _divrem_mutate($n,4); ### $n my $d = int( (-1 + _sqrtint(4*$n+1)) / 2 ); ### d frac: ((-1 + sqrt(4*$n + 1)) / 2) ### $d ### base: $d*($d+1) $n -= $d*($d+1); ### remainder: $n $rot += ($d % 4); my $x = $d + 1; my $y = $frac + $n - $d; $rot %= 4; if ($rot & 2) { $x = -$x; # rotate 180 $y = -$y; } if ($rot & 1) { return (-$y,$x); # rotate +90 } else { return ($x,$y); } } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### SquareArms xy_to_n: "$x,$y" if ($x == 0 && $y == 0) { return 1; } my $rot = 0; # eg. y=2 have (0<=>$y)-$y == -1-2 == -3 if ($y <= ($x <=> 0) - $x) { ### below diagonal, rot 180 ... $rot = 2; $x = -$x; # rotate 180 $y = -$y; } if ($x < $y) { ### left of diagonal, rot -90 ... $rot++; ($x,$y) = ($y,-$x); # rotate -90 } # diagonal down from N=2 # x=1 d=0 n=2 # x=5 d=4 n=82 # x=9 d=8 n=290 # x=13 d=12 n=626 # N = (4 d^2 + 4 d + 2) # = (4 x^2 - 4 x + 2) # offset = y + x-1 upwards from diagonal # N + 4*offset # = (4*x^2 - 4*x + 2) + 4*(y + x-1) # = 4*x^2 - 4*x + 2 + 4*y + 4*x - 4 # = 4*x^2 + 4*y - 2 # cf N=4*x^2 is on the X or Y axis, which is X axis after rotation # ### xy: "$x,$y" ### $rot ### x offset: $x-1 + $y ### d mod: $d % 4 ### rot d mod: (($rot-$d) % 4) return ($x*$x + $y)*4 - 2 + (($rot-$x+1) % 4); } # d = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ], # Nmax = [ 9, 25, 49, 81, 121, 169, 225, 289, 361 ] # being the N=5 arm one spot before the corner of each run # N = (4 d^2 + 4 d + 1) # = (2d+1)^2 # = ((4*$d + 4)*$d + 1) # or for d-1 # N = (4 d^2 - 4 d + 1) # = (2d-1)^2 # = ((4*$d - 4)*$d + 1) # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; my ($d_lo, $d_hi) = _rect_square_range ($x1,$y1, $x2,$y2); return (((4*$d_lo - 4)*$d_lo + 1), max ($self->xy_to_n($x1,$y1), $self->xy_to_n($x1,$y2), $self->xy_to_n($x2,$y1), $self->xy_to_n($x2,$y2))); } sub _rect_square_range { my ($x1,$y1, $x2,$y2) = @_; ### _rect_square_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); # if x1,x2 opposite signs then origin x=0 covered, similarly y my $x_zero_uncovered = ($x1<0) == ($x2<0); my $y_zero_uncovered = ($y1<0) == ($y2<0); foreach ($x1,$y1, $x2,$y2) { $_ = abs($_); } ### abs rect: "x=$x1 to $x2, y=$y1 to $y2" if ($x2 < $x1) { ($x1,$x2) = ($x2,$x1) } # swap to x1 $y2 ? $x2 : $y2)); } 1; __END__ =for stopwords Math-PlanePath Ryde repdigit dlo dlo-1 Nlo Nhi =head1 NAME Math::PlanePath::SquareArms -- four spiral arms =head1 SYNOPSIS use Math::PlanePath::SquareArms; my $path = Math::PlanePath::SquareArms->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path follows four spiral arms, each advancing successively, ...--33--29 3 | 26--22--18--14--10 25 2 | | | 30 11-- 7-- 3 6 21 1 | | | | ... 15 4 1 2 17 ... <- Y=0 | | | | | 19 8 5-- 9--13 32 -1 | | | 23 12--16--20--24--28 -2 | 27--31--... -3 ^ ^ ^ ^ ^ ^ ^ -3 -2 -1 X=0 1 2 3 ... Each arm is quadratic, with each loop 128 longer than the preceding. XThe perfect squares fall in eight straight lines 4, with the even squares on the X and Y axes and the odd squares on the diagonals X=Y and X=-Y. Some novel straight lines arise from numbers which are a repdigit in one or more bases (Sloane's A167782). "111" in various bases falls on straight lines. Numbers "[16][16][16]" in bases 17,19,21,etc are a horizontal at Y=3 because they're perfect squares, and "[64][64][64]" in base 65,66,etc go a vertically downwards from X=12,Y=-266 similarly because they're squares. Each arm is N=4*k+rem for a remainder rem=0,1,2,3, so sequences related to multiples of 4 or with a modulo 4 pattern may fall on particular arms. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::SquareArms-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n E 1> the return is an empty list, as the path starts at 1. Fractional C<$n> gives a point on the line between C<$n> and C<$n+4>, that C<$n+4> being the next point on the same spiralling arm. This is probably of limited use, but arises fairly naturally from the calculation. =back =head2 Descriptive Methods =over =item C<$arms = $path-Earms_count()> Return 4. =back =head1 FORMULAS =head2 Rectangle N Range Within a square X=-d...+d, and Y=-d...+d the biggest N is the end of the N=5 arm in that square, which is N=9, 25, 49, 81, etc, (2d+1)^2, in successive corners of the square. So for a rectangle find a surrounding d square, d = max(abs(x1),abs(y1),abs(x2),abs(y2)) from which Nmax = (2*d+1)^2 = (4*d + 4)*d + 1 This can be used for a minimum too by finding the smallest d covered by the rectangle. dlo = max (0, min(abs(y1),abs(y2)) if x=0 not covered min(abs(x1),abs(x2)) if y=0 not covered ) from which the maximum of the preceding dlo-1 square, Nlo = / 1 if dlo=0 \ (2*(dlo-1)+1)^2 +1 if dlo!=0 = (2*dlo - 1)^2 = (4*dlo - 4)*dlo + 1 For a tighter maximum, horizontally N increases to the left or right of the diagonal X=Y line (or X=Y+/-1 line), which means one end or the other is the maximum. Similar vertically N increases above or below the off-diagonal X=-Y so the top or bottom is the maximum. This means for a rectangle the biggest N is at one of the four corners, Nhi = max (xy_to_n (x1,y1), xy_to_n (x1,y2), xy_to_n (x2,y1), xy_to_n (x2,y2)) The current code uses a dlo for Nlo and the corners for Nhi, which means the high is exact but the low is not. =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/CfracDigits.pm0000644000175000017500000004371713734026673017770 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::CfracDigits; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use Math::PlanePath::RationalsTree; *_xy_to_quotients = \&Math::PlanePath::RationalsTree::_xy_to_quotients; use Math::PlanePath::CoprimeColumns; *_coprime = \&Math::PlanePath::CoprimeColumns::_coprime; # uncomment this to run the ### lines #use Smart::Comments; use constant parameter_info_array => [ { name => 'radix', share_key => 'radix2_min1', display => 'Radix', type => 'integer', minimum => 1, default => 2, width => 3, }, ]; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant x_minimum => 1; use constant y_minimum => 2; use constant diffxy_maximum => -1; # upper octant X<=Y-1 so X-Y<=-1 use constant gcdxy_maximum => 1; # no common factor # FIXME: believe this is right, but check N+1 always changes Y sub absdy_minimum { my ($self) = @_; return ($self->{'radix'} < 3 ? 0 : 1); } # radix=1 N=1 has dir4=0 # radix=2 N=5628 has dir4=0 dx=9,dy=0 # radix=3 N=1189140 has dir4=0 dx=1,dy=0 # radix=4 N=169405 has dir4=0 dx=2,dy=0 # always eventually 0 ? # use constant dir_minimum_dxdy => (1,0); # the default # radix=1 N=4 dX=1,dY=-1 for dir4=3.5 # radix=2 N=4413 dX=9,dY=-1 # radix=3 N=9492 dX=3,dY=-1 # ENHANCE-ME: suspect believe approaches 360 degrees, eventually, but proof? # use constant dir_maximum_dxdy => (0,0); # the default sub turn_any_straight { my ($self) = @_; return ($self->{'radix'} != 1); # radix=1 never straight } sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; return $self->{'radix'} + 1; } sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; return $self->{'radix'}; } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); unless ($self->{'radix'} && $self->{'radix'} >= 1) { $self->{'radix'} = 2; } return $self; } sub n_to_xy { my ($self, $n) = @_; ### CfracDigits n_to_xy(): "$n" if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); if ($n != $int) { ### frac ... my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; ### x1,y1: "$x1, $y1" ### x2,y2: "$x2, $y2" ### dx,dy: "$dx, $dy" ### result: ($frac*$dx + $x1).', '.($frac*$dy + $y1) return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } my $radix = $self->{'radix'}; my $zero = ($n * 0); # inherit bignum 0 my $x = $zero; my $y = 1 + $zero; # inherit bignum 1 foreach my $q (_n_to_quotients_bottomtotop($n,$radix,$zero)) { # bottom to top ### at: "$x,$y q=$q" # 1/(q + X/Y) = 1/((qY+X)/Y) # = Y/(qY+X) ($x,$y) = ($y, $q*$y + $x); } ### return: "$x,$y" return ($x,$y); } # Return a list of quotients bottom to top. The base3 digits of N are split # by "3" delimiters and the parts adjusted so the first bottom-most q>=2 and # the rest q>=1. The values are ready to be used as continued fraction # terms. # sub _n_to_quotients_bottomtotop { my ($n, $radix, $zero) = @_; ### _n_to_quotients_bottomtotop(): $n my $radix_plus_1 = $radix + 1; my @ret; my @group; foreach my $digit (_digit_split_1toR_lowtohigh($n,$radix_plus_1)) { if ($digit == $radix_plus_1) { ### @group push @ret, _digit_join_1toR_destructive(\@group, $radix, $zero) + 1; @group = (); } else { push @group, $digit; } } ### final group: @group push @ret, _digit_join_1toR_destructive(\@group, $radix, $zero) + 1; $ret[0] += 1; # bottom-most is +2 rather than +1 ### _n_to_quotients_bottomtotop result: @ret return @ret; } # Return a list of digits 1 <= d <= R which is $n written in $radix, low to # high digits. sub _digit_split_1toR_lowtohigh { my ($n, $radix) = @_; ### assert: $radix >= 1 ### assert: $n >= 0 if ($radix == 1) { return (1) x $n; } my @digits = digit_split_lowtohigh($n,$radix); # mangle 0 -> R my $borrow = 0; foreach my $digit (@digits) { # low to high if ($borrow = (($digit -= $borrow) <= 0)) { # modify array contents $digit += $radix; } } if ($borrow) { ### assert: $digits[-1] == $radix pop @digits; } return @digits; } sub _digit_join_1toR_destructive { my ($aref, $radix, $zero) = @_; ### assert: $radix >= 1 if ($radix == 1) { return scalar(@$aref); } # mangle any digit==$radix down to digit=0 my $carry = 0; foreach my $digit (@$aref) { # low to high if ($carry = (($digit += $carry) >= $radix)) { # modify array contents $digit -= $radix; } } if ($carry) { push @$aref, 1; } ### _digit_join_1toR_destructive() result: digit_join_lowtohigh($aref, $radix, $zero) return digit_join_lowtohigh($aref, $radix, $zero); } sub xy_is_visited { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); return (! ($x < 1 || $y < 2 || $x >= $y) && _coprime($x,$y)); } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### CfracDigits xy_to_n(): "$x,$y" if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } if ($x < 1 || $y < 2 || $x >= $y) { return undef; } my @quotients = _xy_to_quotients($x,$y) or return undef; # $x,$y have a common factor ### @quotients # drop initial 0 integer part ### assert: $quotients[0] == 0 shift @quotients; return _cfrac_join_toptobottom(\@quotients, $self->{'radix'}, $x * 0 * $y); # inherit bignum 0 } # $aref is a list of continued fraction quotients from top-most to # bottom-most. There's no initial integer term in $aref. Each quotient is # q >= 1 except the bottom-most which q-1 and so also >=1. # sub _cfrac_join_toptobottom { my ($aref, $radix, $zero) = @_; ### _cfrac_join_toptobottom(): $aref my @digits; foreach my $q (reverse @$aref) { ### assert: $q >= 1 push @digits, _digit_split_1toR_lowtohigh($q-1, $radix), $radix+1; } pop @digits; # no high delimiter ### groups digits 1toR: @digits return _digit_join_1toR_destructive(\@digits, $radix+1, $zero); } # X/Y = F[k]/F[k+1] quotients all 1 # N = all delimiter digits R,R,...,R # = 1222...2221 # = R^k + 2*(R^k+1)/(R-1) - 1 # = (RR^k - R^k + 2R^k + 2 - R + 1) / (R-1) # = (RR^k + R^k - R + 3) / (R-1) # = ((R+1)R^k - R + 3) / (R-1) # take high as "12" = R+2 # k = log(Y)/log(phi) # N = (R+2) * R ** k # N = Y ** (log(R)/log(phi)) # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### rect_to_n_range() $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### $x2 ### $y2 # | / # | / x1 # | / +-----y2 # | / | # |/ +----- # if ($x2 < 1 || $y2 < 2 || $x1 >= $y2) { ### no values, rect outside upper octant ... return (1,0); } my $zero = ($x1 * 0 * $y1 * $x2 * $y2); # inherit bignum my $radix = $self->{'radix'}; return (0, ($radix+3) * ($radix+1 + $zero) ** ($radix == 1 ? $y2 : _log_phi_estimate($y2,$radix))); } # Return an estimate of log base phi of $x, that being log($x)/log(phi), # where phi=(1+sqrt(5))/2 the golden ratio. # sub _log_phi_estimate { my ($x) = @_; my ($pow,$exp) = round_down_pow ($x, 2); return int ($exp * (log(2) / log((1+sqrt(5))/2))); } 1; __END__ =for stopwords eg Ryde OEIS ie Math-PlanePath coprime octant onwards decrement Shallit radix-1 Radix radix HCS 10www w's =head1 NAME Math::PlanePath::CfracDigits -- continued fraction terms encoded by digits =head1 SYNOPSIS use Math::PlanePath::CfracDigits; my $path = Math::PlanePath::CfracDigits->new (tree_type => 'Kepler'); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis path enumerates reduced fractions S<0 E X/Y E 1> with X,Y no common factor using a method by Jeffrey Shallit encoding continued fraction terms in digit strings, as per =over Jeffrey Shallit, "Number Theory and Formal Languages", part 3, L =back Fractions up to a given denominator are covered by roughly N=den^2.28. This is a much smaller N range than the run-length encoding in C and C (but is more than C). =cut # math-image --path=CfracDigits --output=numbers_xy --all --size=78x17 =pod 15 | 25 27 91 61 115 307 105 104 14 | 23 48 65 119 111 103 13 | 22 24 46 29 66 59 113 120 101 109 99 98 12 | 17 60 114 97 11 | 16 18 30 64 58 112 118 102 96 95 10 | 14 28 100 94 9 | 13 15 20 38 36 35 8 | 8 21 39 34 7 | 7 9 19 37 33 32 6 | 5 31 5 | 4 6 12 11 4 | 2 10 3 | 1 3 2 | 0 1 | Y=0 | ---------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 A fraction S<0 E X/Y E 1> has a finite continued fraction of the form 1 X/Y = 0 + --------------------- 1 q[1] + ----------------- 1 q[2] + ------------ .... 1 q[k-1] + ---- q[k] where each q[i] >= 1 except last q[k] >= 2 The terms are collected up as a sequence of integers E=0 by subtracting 1 from each and 2 from the last. # each >= 0 q[1]-1, q[2]-1, ..., q[k-2]-1, q[k-1]-1, q[k]-2 These integers are written in base-2 using digits 1,2. A digit 3 is written between each term as a separator. base2(q[1]-1), 3, base2(q[2]-1), 3, ..., 3, base2(q[k]-2) If a term q[i]-1 is zero then its base-2 form is empty and there's adjacent 3s in that case. If the high q[1]-1 is zero then a bare high 3, and if the last q[k]-2 is zero then a bare final 3. If there's just a single term q[1] and q[1]-2=0 then the string is completely empty. This occurs for X/Y=1/2. The resulting string of 1s,2s,3s is reckoned as a base-3 value with digits 1,2,3 and the result is N. All possible strings of 1s,2s,3s occur (including the empty string) and so all integers NE=0 correspond one-to-one with an X/Y fraction with no common factor. Digits 1,2 in base-2 means writing an integer in the form d[k]*2^k + d[k-1]*2^(k-1) + ... + d[2]*2^2 + d[1]*2 + d[0] where each digit d[i]=1 or 2 Similarly digits 1,2,3 in base-3 which is used for N, d[k]*3^k + d[k-1]*3^(k-1) + ... + d[2]*3^2 + d[1]*3 + d[0] where each digit d[i]=1, 2 or 3 This is not the same as the conventional binary and ternary radix representations by digits 0,1 or 0,1,2 (ie. 0 to radix-1). The effect of digits 1 to R is to change any 0 digit to instead R and decrement the value above that position to compensate. =head2 Axis Values N=0,1,2,4,5,7,etc in the X=1 column is integers with no digit 0s in ternary. N=0 is considered no digits at all and so no digit 0. These points are fractions 1/Y which are a single term q[1]=Y-1 and hence no "3" separators, only a run of digits 1,2. These N values are also those which are the same when written in digits 0,1,2 as when written in digits 1,2,3, since there's no 0s or 3s. N=0,3,10,11,31,etc along the diagonal Y=X+1 are integers which are ternary "10www..." where the w's are digits 1 or 2, so no digit 0s except the initial "10". These points Y=X+1 points are X/(X+1) with continued fraction 1 X/(X+1) = 0 + ------- 1 1 + --- X so q0=1 and q1=X, giving N="3,X-1" in digits 1,2,3, which is N="1,0,X-1" in normal ternary. For example N=34 is ternary "1021" which is leading "10" and then X-1=7 ternary "21". =head2 Radix The optional C parameter can select another base for the continued fraction terms, and corresponding radix+1 for the resulting N. The default is radix=2 as described above. Any integer radixE=1 can be selected. For example, =cut # math-image --path=CfracDigits,radix=5 --output=numbers_xy --all --size=78x17 =pod radix => 5 11 | 10 30 114 469 75 255 1549 1374 240 225 10 | 9 109 1369 224 9 | 8 24 74 254 234 223 8 | 7 78 258 41 7 | 5 18 73 253 228 40 6 | 4 39 5 | 3 12 42 38 4 | 2 37 3 | 1 6 2 | 0 1 | Y=0 | ---------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 The X=1 column is integers with no digit 0 in base radix+1, so in radix=5 means no 0 digit in base-6. =head2 Radix 1 The radix=1 case encodes continued fraction terms using only digit 1, which means runs of q many "1"s to add up to q, and then digit "2" as separator. N = 11111 2 1111 2 ... 2 1111 2 11111 base2 digits 1,2 \---/ \--/ \--/ \---/ q[1]-1 q[2]-1 q[k-1]-1 q[k]-2 which becomes in plain binary N = 100000 10000 ... 10000 011111 base2 digits 0,1 \----/ \---/ \---/ \----/ q[1] q[2] q[k-1] q[k]-1 Each "2" becomes "0" in plain binary and carry +1 into the run of 1s above it. That carry propagates through those 1s, turning them into 0s, and stops at the "0" above them (which had been a "2"). The low run of 1s from q[k]-2 has no "2" below it and is therefore unchanged. =cut # math-image --path=CfracDigits,radix=1 --output=numbers_xy --all --size=60x12 =pod radix => 1 11 | 511 32 18 21 39 55 29 26 48 767 10 | 255 17 25 383 9 | 127 16 19 27 24 191 8 | 63 10 14 95 7 | 31 8 9 13 12 47 6 | 15 23 5 | 7 4 6 11 4 | 3 5 3 | 1 2 2 | 0 1 | Y=0 | ------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 The result is similar to L. But the lowest run is "0111" here, instead of "1000" as it is in the HCS. So N-1 here, and a flip (Y-X)/X to map from X/YE1 here to instead all rationals for the HCS tree. For example CfracDigits radix=1 RationalsTree tree_type=HCS X/Y = 5/6 (Y-X)/X = 1/5 is at is at N = 23 = 0b10111 N = 24 = 0b11000 ^^^^ ^^^^ =head1 FUNCTIONS See L for behaviour common to all path classes. =over =item C<$path = Math::PlanePath::CfracDigits-Enew ()> =item C<$path = Math::PlanePath::CfracDigits-Enew (radix =E $radix)> Create and return a new path object. =item C<$n = $path-En_start()> Return 0, the first N in the path. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back radix=1 A071766 X coordinate (numerator), except extra initial 1 radix=2 (the default) A032924 N in X=1 column, ternary no digit 0 (but lacking N=0) radix=3 A023705 N in X=1 column, base-4 no digit 0 (but lacking N=0) radix=4 A023721 N in X=1 column, base-5 no digit 0 (but lacking N=0) radix=10 A052382 N in X=1 column, decimal no digit 0 (but lacking N=0) =head1 SEE ALSO L, L, L L, L, L L =head1 HOME PAGE L =head1 LICENSE Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/GosperReplicate.pm0000644000175000017500000010070413734026670020661 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=GosperReplicate --lines --scale=10 # math-image --path=GosperReplicate --all --output=numbers_dash # math-image --path=GosperReplicate,numbering_type=rotate --all --output=numbers_dash # package Math::PlanePath::GosperReplicate; use 5.004; use strict; use List::Util qw(max); use POSIX 'ceil'; use Math::Libm 'hypot'; use Math::PlanePath::SacksSpiral; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_up_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant parameter_info_array => [ { name => 'numbering_type', display => 'Numbering', share_key => 'numbering_type_rotate', type => 'enum', default => 'fixed', choices => ['fixed','rotate'], choices_display => ['Fixed','Rotate'], description => 'Fixed or rotating sub-part numbering.', }, ]; use constant n_start => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_even; use constant x_negative_at_n => 3; use constant y_negative_at_n => 5; use constant absdx_minimum => 1; use constant dir_maximum_dxdy => (3,-1); #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); $self->{'numbering_type'} ||= 'fixed'; # default return $self; } sub _digits_rotate_lowtohigh { my ($aref) = @_; my $rot = 0; foreach my $digit (reverse @$aref) { if ($digit) { $rot += $digit-1; $digit = ($rot % 6) + 1; # mutate $aref } } } sub _digits_unrotate_lowtohigh { my ($aref) = @_; my $rot = 0; foreach my $digit (reverse @$aref) { if ($digit) { $digit = ($digit-1-$rot) % 6; # mutate $aref $rot += $digit; $digit++; } } } sub n_to_xy { my ($self, $n) = @_; ### GosperReplicate n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $x = my $y = $n*0; # inherit bigint from $n my $sx = $x + 2; # 2 my $sy = $x; # 0 # digit # 3 2 # \ / # 4---0---1 # / \ # 5 6 my @digits = digit_split_lowtohigh($n,7); if ($self->{'numbering_type'} eq 'rotate') { _digits_rotate_lowtohigh(\@digits); } foreach my $digit (@digits) { ### digit: "$digit $x,$y side $sx,$sy" if ($digit == 1) { ### right ... # $x = -$x; # rotate 180 # $y = -$y; $x += $sx; $y += $sy; } elsif ($digit == 2) { ### up right ... # ($x,$y) = ((3*$y-$x)/2, # rotate -120 # ($x+$y)/-2); $x += ($sx - 3*$sy)/2; # at +60 $y += ($sx + $sy)/2; } elsif ($digit == 3) { ### up left ... # ($x,$y) = (($x+3*$y)/2, # -60 # ($y-$x)/2); $x += ($sx + 3*$sy)/-2; # at +120 $y += ($sx - $sy)/2; } elsif ($digit == 4) { ### left $x -= $sx; # at -180 $y -= $sy; } elsif ($digit == 5) { ### down left # ($x,$y) = (($x-3*$y)/2, # rotate +60 # ($x+$y)/2); $x += (3*$sy - $sx)/2; # at -120 $y += ($sx + $sy)/-2; } elsif ($digit == 6) { ### down right # ($x,$y) = (($x+3*$y)/-2, # rotate +120 # ($x-$y)/2); $x += ($sx + 3*$sy)/2; # at -60 $y += ($sy - $sx)/2; } # 2*(sx,sy) + rot+60(sx,sy) ($sx,$sy) = ((5*$sx - 3*$sy) / 2, ($sx + 5*$sy) / 2); } return ($x,$y); } # modulus # 1 3 # \ / # 5---0---2 # / \ # 4 6 # 0 1 2 3 4 5 6 my @modulus_to_x = (0,-1, 2, 1,-1,-2, 1); my @modulus_to_y = (0, 1, 0, 1,-1, 0,-1); my @modulus_to_digit = (0, 3, 1, 2, 5, 4, 6); sub xy_to_n { my ($self, $x, $y) = @_; ### GosperReplicate xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); if (($x + $y) % 2) { return undef; } my $level = _xy_to_level_ceil($x,$y); if (is_infinite($level)) { return $level; } my $zero = ($x * 0 * $y); # inherit bignum 0 my @n; # digits low to high while ($level-- >= 0 && ($x || $y)) { ### at: "$x,$y m=".(($x + 2*$y) % 7) my $m = ($x + 2*$y) % 7; push @n, $modulus_to_digit[$m]; $x -= $modulus_to_x[$m]; $y -= $modulus_to_y[$m]; ### digit: "to $x,$y" ### assert: (3 * $y + 5 * $x) % 14 == 0 ### assert: (5 * $y - $x) % 14 == 0 # shrink ($x,$y) = ((3*$y + 5*$x) / 14, (5*$y - $x) / 14); } if ($self->{'numbering_type'} eq 'rotate') { _digits_unrotate_lowtohigh(\@n); } return digit_join_lowtohigh (\@n, 7, $zero); } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $y1 *= sqrt(3); $y2 *= sqrt(3); my ($r_lo, $r_hi) = Math::PlanePath::SacksSpiral::_rect_to_radius_range ($x1,$y1, $x2,$y2); $r_hi *= 2; my $level_plus_1 = ceil( log(max(1,$r_hi/4)) / log(sqrt(7)) ) + 2; return (0, 7**$level_plus_1 - 1); } sub _xy_to_level_ceil { my ($x,$y) = @_; my $r = hypot($x,$y); $r *= 2; return ceil( log(max(1,$r/4)) / log(sqrt(7)) ) + 1; } #------------------------------------------------------------------------------ # levels sub level_to_n_range { my ($self, $level) = @_; return (0, 7**$level - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($pow, $exp) = round_up_pow ($n+1, 7); return $exp; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords PlanePath eg Ryde Gosper Math-PlanePath =head1 NAME Math::PlanePath::GosperReplicate -- self-similar hexagon replications =head1 SYNOPSIS use Math::PlanePath::GosperReplicate; my $path = Math::PlanePath::GosperReplicate->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is a self-similar hexagonal tiling of the plane. At each level the shape is the Gosper island. 17----16 4 / \ 24----23 18 14----15 3 / \ \ 25 21----22 19----20 10---- 9 2 \ / \ 26----27 3---- 2 11 7---- 8 1 / \ \ 31----30 4 0---- 1 12----13 <- Y=0 / \ \ 32 28----29 5---- 6 45----44 -1 \ / \ 33----34 38----37 46 42----43 -2 / \ \ 39 35----36 47----48 -3 \ 40----41 -4 ^ -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 Points are spread out on every second X coordinate to make a triangular lattice in integer coordinates (see L). The base pattern is the inner N=0 to N=6, then six copies of that shape are arranged around as the blocks N=7,14,21,28,35,42. Then six copies of the resulting N=0 to N=48 shape are replicated around, etc. Each point can be taken as a little hexagon, so that all points tile the plane with hexagons. The innermost N=0 to N=6 are for instance, * * / \ / \ / \ / \ * * * | 3 | 2 | * * * / \ / \ / \ / \ / \ / \ * * * * | 4 | 0 | 1 | * * * * \ / \ / \ / \ / \ / \ / * * * | 5 | 6 | * * * \ / \ / \ / \ / * * The further replications are the same arrangement, but the sides become ever wigglier and the centres rotate around. The rotation can be seen N=7 at X=5,Y=1 which is up from the X axis. The C path is this same replicating shape, but starting from a side instead of the middle and traversing in such as way as to make each N adjacent. The C curve itself is this replication too, but segments across hexagons. =head2 Complex Base The path corresponds to expressing complex integers X+i*Y in a base b = 5/2 + i*sqrt(3)/2 =cut # GP-DEFINE sqrt3 = quadgen(12); # GP-DEFINE sqrt3i = quadgen(-12); # GP-Test sqrt3^2 == 3 # GP-Test sqrt3i^2 == -3 # GP-DEFINE b = 5/2 + sqrt3i/2; =pod with coordinates scaled to put equilateral triangles on a square grid. So for integer X,Y on the triangular grid (X,Y either both odd or both even), X/2 + i*Y*sqrt(3)/2 = a[n]*b^n + ... + a[2]*b^2 + a[1]*b + a[0] where each digit a[i] is either 0 or a sixth root of unity encoded into base-7 digits of N, w6 = e^(i*pi/3) sixth root of unity, b = 2 + w6 = 1/2 + i*sqrt(3)/2 N digit a[i] complex number ------- ------------------- 0 0 1 w6^0 = 1 2 w6^1 = 1/2 + i*sqrt(3)/2 3 w6^2 = -1/2 + i*sqrt(3)/2 4 w6^3 = -1 5 w6^4 = -1/2 - i*sqrt(3)/2 6 w6^5 = 1/2 - i*sqrt(3)/2 =cut # GP-DEFINE w6 = 1/2 + sqrt3i/2; # GP-Test w6^6 == 1 # GP-Test w6^0 == 1 # GP-Test w6^1 == 1/2 + sqrt3i/2 # GP-Test w6^2 == -1/2 + sqrt3i/2 # GP-Test w6^3 == -1 # GP-Test w6^4 == -1/2 - sqrt3i/2 # GP-Test w6^5 == 1/2 - sqrt3i/2 # GP-Test (5/2)^2 + (sqrt3/2)^2 == 7 # GP-DEFINE z_digit(d) = [0, 1,w6,w6^2, -1,w6^4,w6^5][d+1]; # GP-DEFINE z_point(n) = \ # GP-DEFINE subst(Pol(apply(z_digit,digits(n,7))),'x,b); # GP-Test z_point(0) == 0 # GP-Test z_point(1) == 1 # GP-Test z_point(2) == w6 # GP-Test z_point(7) == w6+2 # GP-DEFINE nearly_equal_epsilon = 1e-15; # GP-DEFINE nearly_equal(x,y, epsilon=nearly_equal_epsilon) = \ # GP-DEFINE abs(x-y) < epsilon; # GP-DEFINE to_base7(n) = fromdigits(digits(n,7)); # GP-DEFINE from_base7(n) = fromdigits(digits(n),7); =pod 7 digits suffice because norm(b) = (5/2)^2 + (sqrt(3)/2)^2 = 7 =cut # GP-Test norm(b) == 7 # GP-Test (5/2)^2 + (sqrt3/2)^2 == 7 =pod =head2 Rotate Numbering Parameter C 'rotate'> applies a rotation in each sub-part according to its location around the preceding level. The effect can be illustrated by writing N in base-7. Part 10-16 is the same as the middle 0-6. Part 20-26 has a rotation by +60 degrees. Part 30-36 has rotation by +120 degrees, and so on. =cut # start from this, then mangled by hand # math-image --path=GosperReplicate,numbering_type=rotate --all --output=numbers_dash =pod 22----21 / / numbering_type => 'rotate' 31 36 23 20 26 N shown in base-7 / \ \ \ / 32 30 35 24----25 13----12 \ / / \ 33----34 3---- 2 14 10----11 / \ \ 46----45 4 0---- 1 15----16 \ \ 41----40 44 5---- 6 64----63 \ / / \ 42----43 55----54 65 60 62 / \ \ \ / 56 50 53 66 61 / / 51----52 Notice this means in each part the 11, 21, 31, etc, points are directed away from the middle in the same way, relative to the sub-part locations. Working through the expansions gives the following rule for when an N is on the boundary of level k, write N in k many base-7 digits (empty string if k=0) if any 0 digit then non-boundary ignore high digit and all 1 digits if any 4 or 5 digit then non-boundary if any 32, 33, 66 pair then non-boundary A 0 digit is the middle of a block, or 4 or 5 digit the inner side of a block, for kE=1, hence non-boundary. After that the 6,1,2,3 parts variously expand with rotations so that a 66 is enclosed on the clockwise side and 32 and 33 on the anti-clockwise side. =cut # in decimal # 16----15 # / / # 22 27 17 14 20 # / \ \ \ / # 23 21 26 18----19 10---- 9 # \ / / \ # 24----25 3---- 2 11 7---- 8 # / \ \ # 34----33 4 0---- 1 12----13 # \ \ # 29----28 32 5---- 6 46----45 # \ / / \ # 30----31 40----39 47 42 44 # / \ \ \ / # 41 35 38 48 43 # / / # 36----37 =pod =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::GosperReplicate-Enew ()> =item C<$path = Math::PlanePath::GosperReplicate-Enew (numbering_type =E $str)> Create and return a new path object. The C parameter can be "fixed" (default) "rotate" =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 7**$level - 1)>. =back =head1 FORMULAS =head2 Axis Rotations In the fixed numbering, digit positions 1,2,3,4,5,6 go around +60deg each, so the N for rotation of X,Y by +60 degrees is each digit +1. N = 0, 1, 2, 3, 4, 5, 6, 10, 11, 12 rot+60(N) = 0, 2, 3, 4, 5, 6, 1, 14, 16, 17, ... decimal = 0, 2, 3, 4, 5, 6, 1, 20, 22, 23, ... base7 rot+120(N) = 0, 3, 4, 5, 6, 1, 2, 21, 24, 25, ... decimal = 0, 3, 4, 5, 6, 1, 2, 30, 33, 34, ... base7 etc =cut # rot180(N) = 0, 4, 5, 6, 1, 2, 3, 28, 32, 33, ... decimal # = 0, 4, 5, 6, 1, 2, 3, 40, 44, 45, ... base7 # # rot-120(N) = 0, 5, 6, 1, 2, 3, 4, 35, 40, 41, ... decimal # = 0, 5, 6, 1, 2, 3, 4, 50, 55, 56, ... base7 # # rot-60(N) = 0, 6, 1, 2, 3, 4, 5, 42, 48, 43, ... decimal # = 0, 6, 1, 2, 3, 4, 5, 60, 66, 61, ... base7 # GP-DEFINE digit_plus1(d) = [0,2,3,4,5,6,1][d+1]; # GP-DEFINE digit_plus2(d) = [0,3,4,5,6,1,2][d+1]; # GP-DEFINE digit_plus3(d) = [0,4,5,6,1,2,3][d+1]; # GP-DEFINE digit_minus2(d) = [0,5,6,1,2,3,4][d+1]; # GP-DEFINE digit_minus1(d) = [0,6,1,2,3,4,5][d+1]; # GP-DEFINE N_rotate_plus60(n) = fromdigits(apply(digit_plus1, digits(n,7)),7); # GP-DEFINE N_rotate_plus120(n)= fromdigits(apply(digit_plus2, digits(n,7)),7); # GP-DEFINE N_rotate_180(n) = fromdigits(apply(digit_plus3, digits(n,7)),7); # GP-DEFINE N_rotate_minus120(n)=fromdigits(apply(digit_minus2,digits(n,7)),7); # GP-DEFINE N_rotate_minus60(n)= fromdigits(apply(digit_minus1,digits(n,7)),7); # GP-Test my(v=[0, 2, 3, 4, 5, 6, 1, 14, 16, 17]); /* samples shown */ \ # GP-Test vector(#v,n,n--; N_rotate_plus60(n)) == v # GP-Test my(v=[0, 2, 3, 4, 5, 6, 1, 20, 22, 23]); /* samples shown */ \ # GP-Test vector(#v,n,n--; to_base7(N_rotate_plus60(n))) == v # GP-Test my(v=[0, 3, 4, 5, 6, 1, 2, 21, 24, 25]); /* samples shown */ \ # GP-Test vector(#v,n,n--; N_rotate_plus120(n)) == v # GP-Test my(v=[0, 3, 4, 5, 6, 1, 2, 30, 33, 34]); /* samples shown */ \ # GP-Test vector(#v,n,n--; to_base7(N_rotate_plus120(n))) == v # GP-Test my(v=[0, 4, 5, 6, 1, 2, 3, 28, 32, 33]); /* samples shown */ \ # GP-Test vector(#v,n,n--; N_rotate_180(n)) == v # GP-Test my(v=[0, 4, 5, 6, 1, 2, 3, 40, 44, 45]); /* samples shown */ \ # GP-Test vector(#v,n,n--; to_base7(N_rotate_180(n))) == v # GP-Test my(v=[0, 5, 6, 1, 2, 3, 4, 35, 40, 41]); /* samples shown */ \ # GP-Test vector(#v,n,n--; N_rotate_minus120(n)) == v # GP-Test my(v=[0, 5, 6, 1, 2, 3, 4, 50, 55, 56]); /* samples shown */ \ # GP-Test vector(#v,n,n--; to_base7(N_rotate_minus120(n))) == v # GP-Test my(v=[0, 6, 1, 2, 3, 4, 5, 42, 48, 43]); /* samples shown */ \ # GP-Test vector(#v,n,n--; N_rotate_minus60(n)) == v # GP-Test my(v=[0, 6, 1, 2, 3, 4, 5, 60, 66, 61]); /* samples shown */ \ # GP-Test vector(#v,n,n--; to_base7(N_rotate_minus60(n))) == v # GP-Test vector(500,n,n--; z_point(N_rotate_plus60(n))) == \ # GP-Test vector(500,n,n--; w6*z_point(n)) # GP-Test vector(500,n,n--; z_point(N_rotate_plus120(n))) == \ # GP-Test vector(500,n,n--; w6^2*z_point(n)) # GP-Test vector(500,n,n--; z_point(N_rotate_180(n))) == \ # GP-Test vector(500,n,n--; -z_point(n)) # GP-Test vector(500,n,n--; z_point(N_rotate_minus120(n))) == \ # GP-Test vector(500,n,n--; conj(w6)^2*z_point(n)) # GP-Test vector(500,n,n--; z_point(N_rotate_minus60(n))) == \ # GP-Test vector(500,n,n--; conj(w6)*z_point(n)) # not in OEIS: 2, 3, 4, 5, 6, 1, 14, 16, 17 # not in OEIS: 2, 3, 4, 5, 6, 1, 20, 22, 23 # not in OEIS: 3, 4, 5, 6, 1, 2, 21, 24, 25 # not in OEIS: 3, 4, 5, 6, 1, 2, 30, 33, 34 # not in OEIS: 4, 5, 6, 1, 2, 3, 28, 32, 33 # not in OEIS: 4, 5, 6, 1, 2, 3, 40, 44, 45 # not in OEIS: 5, 6, 1, 2, 3, 4, 35, 40, 41 # not in OEIS: 5, 6, 1, 2, 3, 4, 50, 55, 56 # not in OEIS: 6, 1, 2, 3, 4, 5, 42, 48, 43 # not in OEIS: 6, 1, 2, 3, 4, 5, 60, 66, 61 =pod In the rotate numbering, just adding +1 (etc) at the high digit alone is rotation. =cut # GP-DEFINE n_rotate_highdigit(n,offset) = { # GP-DEFINE my(v=digits(n)); # GP-DEFINE v[1] = ((v[1]-1+offset)%6) + 1; # GP-DEFINE fromdigits(v,7); # GP-DEFINE } # for(offset=1,6,print(vector(18,n, n_rotate_highdigit(n,offset)))) # not in OEIS: 2, 3, 4, 5, 6, 1, 2, 3, 4, 14, 15, 16, 17, 18, 19, 20, 21, 22 # not in OEIS: 3, 4, 5, 6, 1, 2, 3, 4, 5, 21, 22, 23, 24, 25, 26, 27, 28, 29 # not in OEIS: 4, 5, 6, 1, 2, 3, 4, 5, 6, 28, 29, 30, 31, 32, 33, 34, 35, 36 # not in OEIS: 5, 6, 1, 2, 3, 4, 5, 6, 1, 35, 36, 37, 38, 39, 40, 41, 42, 43 # not in OEIS: 6, 1, 2, 3, 4, 5, 6, 1, 2, 42, 43, 44, 45, 46, 47, 48, 49, 50 # not in OEIS: 1, 2, 3, 4, 5, 6, 1, 2, 3, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 =pod =head2 X,Y Extents The maximum X in a given level N=0 to 7^k-1 can be calculated from the replications. A given high digit 1 to 6 has sub-parts located at b^k*w6^(d-1). Those sub-parts are all the same, so the one with maximum real(b^k*w6^(d-1)) contains the maximum X. N_xmax_digit(j) = d=1to6 where real(w6^(d-1) * b^j) is maximum = 1,1,6,6,6,5,5,5,4,4,4,3,3,3,3,2,2, ... k-1 N_xmax(k) = digits N_xmax_digit(j) low digit j=0 j=0 = 0, 1, 8, 302, 2360, 16766, 100801, ... decimal = 0, 1, 11, 611, 6611, 66611, 566611, ... base7 k-1 z_xmax(k) = sum w6^d[j] * b^j j=0 each d[j] with real(w6^d[j] * b^j) maximum = 0, 1, 7/2+1/2*sqrt3*i, 10-sqrt3*i, 57/2-3/2*sqrt3*i,... xmax(k) = 2*real(z_xmax(k)) = 0, 2, 7, 20, 57, 151, 387, 1070, 2833, 7106, ... =cut # GP-DEFINE N_xmax_digit(j) = \ # GP-DEFINE my(p=b^j,d); vecmax(vector(6,d,real(w6^(d-1)*p)),&d); d; # GP-Test my(v=[1,1,6,6,6,5,5,5,4,4,4,3,3,3,3,2,2]); /* samples shown */ \ # GP-Test vector(#v,j,j--; N_xmax_digit(j)) == v # GP-DEFINE N_xmax(k) = fromdigits(Vecrev(vector(k,j,j--; N_xmax_digit(j))),7); # GP-Test my(v=[0, 1, 8, 302, 2360, 16766, 100801]); /* samples shown */ \ # GP-Test vector(#v,j,j--; N_xmax(j)) == v # GP-Test my(v=[0, 1, 11, 611, 6611, 66611, 566611]); /* samples shown */ \ # GP-Test vector(#v,j,j--; to_base7(N_xmax(j))) == v # GP-Test to_base7(N_xmax(51)) \ # GP-Test == 334445556661112222333444555666111222333344455566611 # GP-DEFINE z_xmax(k) = { # GP-DEFINE sum(j=0,k-1, # GP-DEFINE my(p=b^j, v=vector(6,d,(w6^(d-1)*p)), i); # GP-DEFINE vecmax(real(v),&i); # GP-DEFINE v[i]); # GP-DEFINE } # GP-Test my(v=[0, 1, 7/2+1/2*sqrt3i, 10-sqrt3i, 57/2-3/2*sqrt3i]); \ # GP-Test vector(#v,k,k--; z_xmax(k)) == v # GP-Test z_xmax(0) == 0 # GP-Test z_xmax(1) == 1 # GP-Test z_point(7) == 5/2 + 1/2*sqrt3i # GP-Test z_point(8) == 7/2 + 1/2*sqrt3i # GP-DEFINE xmax(k) = real(z_xmax(k)); # GP-Test my(v=[0, 2, 7, 20, 57, 151, 387, 1070, 2833, 7106]); \ # GP-Test vector(#v,k,k--; 2*xmax(k)) == v # GP-Test 2*xmax(45) == 12321054172600214702 # GP-Test 2*xmax(2) == 7 /* X of N=8 shown in sample numbers */ # vector(15,k,k--; N_xmax_digit(k)) # not in OEIS: 1, 1, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 3 # vector(8,k,k++; N_xmax(k)) # vector(8,k,k++; to_base7(N_xmax(k))) # not in OEIS: 8, 57, 400, 10004, 77232, 547828, 3018457, 20312860 # not in OEIS: 11, 111, 1111, 41111, 441111, 4441111, 34441111, 334441111 # vector(6,k,k--; z_xmax(k)) # vector(8,k, norm(z_xmax(k))) # vector(10,k,k++; 2*real(z_xmax(k))) # vector(10,k,k++; 2*imag(z_xmax(k))) # vector(10,k,k++; real(z_xmax(k))+imag(z_xmax(k))) # not in OEIS: 1, 13, 103, 819, 5827, 39243, 291772, 2026399 \\ norm # not in OEIS: 7, 20, 57, 151, 387, 1070, 2833, 7106, 19686, 52675 \\ real # not in OEIS: 1, -2, -3, 13, -49, -86, 163, -1102, -2128, 1597 \\ imag # not in OEIS: 4, 9, 27, 82, 169, 492, 1498, 3002, 8779, 27136 \\ real+imag # GP-DEFINE z_points(k) = vector(7^k,n,n--; z_point(n)); # GP-DEFINE N_xmax_by_points(k) = my(n); vecmax(real(z_points(k)),&n); n-1; # GP-Test vector(5,k,k--; N_xmax_by_points(k)) == \ # GP-Test vector(5,k,k--; N_xmax(k)) # GP-Test z_point(302) == 10 - sqrt3i # GP-Test z_point(57) == 9 + 3*sqrt3i # GP-Test to_base7(302) == 611 # GP-Test to_base7(57) == 111 =pod For computer calculation these maximums can be calculated from the powers. The parts resulting can also be written in terms of the angle arg(b) = atan(sqrt(3)/5) = 19.106... degrees =cut # GP-DEFINE b_angle = arg(b); # GP-DEFINE b_angle_degrees = b_angle * 180/Pi; # GP-Test nearly_equal( b_angle, atan(sqrt3/5) ) # GP-Test b_angle_degrees > 19.106 # GP-Test b_angle_degrees < 19.106+1/10^3 # not in OEIS: 0.333473172251832115336090 \\ radians # not in OEIS: 19.1066053508690943945174 \\ degrees =pod For successive k, if adding this pushes the b^k angle past +30deg then the preceding digit goes past -30deg and becomes the new maximum X. Write the angle as a fraction of 60deg (pi/3), F = atan(sqrt(3)/5) / (pi/3) = 0.318443 ... =cut # GP-DEFINE angle_F = atan(sqrt3/5) / (Pi/3); # GP-Test angle_F > 0.318443 # GP-Test angle_F < 0.318443 + 1/10^6 # not in OEIS: 0.318443422514484906575291 =pod This is irrational since b^k is never on the X or Y axes. That can be seen since 2/sqrt3*imag(b^k) mod 7 goes in a repeating pattern 1,5,4,6,2,3. Similarly 2*real(b^k) mod 7 so not on the Y axis, and also anything on the Y axis would have 3*k fall on the X axis. =cut # GP-DEFINE is_integer(x) = (x==floor(x)); # GP-Test vector(100,k,k--; is_integer(imag(2*b^k))) == vector(100,k,1) # GP-Test vector(100,k,k--; imag(2*b^k)%7) == \ # GP-Test vector(100,k,k--; if(k==0,0, [1,5,4,6,2,3][(k-1)%6+1])) # # GP-Test vector(100,k,k--; is_integer(real(2*b^k))) == vector(100,k,1) # GP-Test vector(100,k,k--; real(2*b^k)%7) == \ # GP-Test vector(100,k,k--; if(k==0,2, [5, 4, 6, 2, 3, 1][(k-1)%6+1])) =pod Digits low to high are successive steps back cyclically 6,5,4,3,2,1 so that (with mod giving 0 to 5), N_xmax_digit(j) = (-floor(F*j+1/2) mod 6) + 1 =cut # GP-DEFINE N_xmax_digit_by_floor(j) = (-floor(angle_F*j+1/2) % 6) + 1; # GP-Test vector(1000,j,j--; N_xmax_digit_by_floor(j)) == \ # GP-Test vector(1000,j,j--; N_xmax_digit(j)) =pod The +1/2 is since initial direction b^0=1 is angle 0 which is half way between -30 and +30 deg. Similarly for the location, using conj(w6) for rotation back z_xmax_exp(j) = floor(F*j+1/2) = 0,0,1,1,1,2,2,2,3,3,3,4,4,4,4,5,5,5, ... z_xmax(k) = sum(j=0,k-1, conj(w6)^z_xmax_exp(j) * b^j) =cut # GP-DEFINE z_xmax_exp(j) = floor(angle_F*j+1/2); # GP-Test my(v=[0,0,1,1,1,2,2,2,3,3,3,4,4,4,4,5,5,5]); /* samples shown */ \ # GP-Test vector(#v,j,j--; z_xmax_exp(j)) == v # GP-DEFINE z_xmax_by_floor(k) = sum(j=0,k-1, conj(w6)^z_xmax_exp(j) * b^j); # GP-Test vector(200,j,j--; z_xmax_by_floor(j)) == \ # GP-Test vector(200,j,j--; z_xmax(j)) # # # vector(35,k,k++; z_xmax_exp(k)) \\ floor(angle_F*j+1/2)) # not in OEIS: 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 11 # not A082964 a(n) = m given by arctan(tan(n)) = n - m*Pi. # GP-DEFINE A082964(n) = round((n-atan(tan(n)))/Pi); # # atan(tan(n)) gives fractional part -pi/2 to +pi/2, so how many revolutions # angle n makes around a circle, up to -pi/2, so factor 1/Pi # 1/Pi \\ 0.318309886183790671537767 is close to F # # GP-DEFINE A082964_by_floor(n) = floor(1/Pi*n+1/2); # GP-Test vector(10000,n,A082964(n)) == \ # GP-Test vector(10000,n,A082964_by_floor(n)) # GP-Test vector(1000,n,A082964(n)) != \ # GP-Test vector(1000,j, floor(angle_F*j+1/2)) =pod By symmetry the maximum extent is the same in 60deg, 120deg, etc directions, suitably rotated. The N in those cases has the digits 1,2,3,4,5,6 cycled around for the rotation. In PlanePath triangular X,Y coordinates direction 60deg means when sum X+3*Y is a maximum, etc. =cut # GP-DEFINE w12_times_sqrt3 = 1+w6; /* w12 * sqrt(3) */ # (x/2+y*sqrt3i/2) * conj(w6) == (x/4 + 3*y/4) + (-x/4 + y*1/4)*sqrt3i # (x/2+y*sqrt3i/2) * conj(w12_times_sqrt3) == (x*3/4 + y*3/4) + (-x/4 + y*3/4)*sqrt3i # GP-DEFINE z_to_x(z) = 2*real(z); # GP-DEFINE z_to_y(z) = 2*imag(z); # GP-Test z_to_x(z_point(1)) == 2 # GP-Test z_to_x(z_point(3)) == -1 # GP-Test z_to_y(z_point(3)) == 1 # GP-DEFINE N_s3max_by_points(k) = my(n); vecmax(real(z_points(k)/w6),&n); n-1; # GP-Test to_base7(N_s3max_by_points(3)) == 122 # GP-Test to_base7(N_s3max_by_points(4)) == 1122 =pod If the +1/2 in the floor is omitted then the effect is to find the maximum point in direction +30deg. In the PlanePath coordinates this means maximum sum S = X+Y. N_smax_digit(j) = (-floor(F*j) mod 6) + 1 = 1,1,1,1,6,6,6,5,5,5,4,4,4,3,3, ... k-1 N_smax(k) = digits N_smax_digit(j) low digit j=0 j=0 = 0, 1, 8, 57, 400, 14806, 115648, ... decimal = 0, 1, 11, 111, 1111, 61111, 661111, ... base7 and also N_smax() + 1 z_smax_exp(j) = floor(F*j) = 0,0,0,0,1,1,1,2,2,2,3,3,3,4,4,4,5,5,5,6, ... z_smax(k) = sum(j=0,k-1, conj(w6)^z_smax_exp(j) * b^j) = 0, 1, 7/2+1/2*sqrt3*i, 9+3*sqrt3*i, 19+12*sqrt3*i, ... and also z_smax() + w6^2 smax(k) = 2*real(z_smax(k)) + imag(z_smax(k))*2/sqrt3 = 0, 2, 8, 24, 62, 172, 470, 1190, 3202, 8740, ... coordinate sum X+Y max In the base figure, points 1 and 2 have the same X+Y=2 and this remains so in subsequent levels, so that for kE=1 N_smax(k) and N_smax(k)+1 are equal maximums. =cut # GP-DEFINE N_smax_digit(j) = (-floor(angle_F*j) % 6) + 1; # GP-Test my(v=[1,1,1,1,6,6,6,5,5,5,4,4,4,3,3]); /* samples shown */ \ # GP-Test vector(#v,j,j--; N_smax_digit(j)) == v # GP-DEFINE N_smax(k) = fromdigits(Vecrev(vector(k,j,j--; N_smax_digit(j))),7); # GP-Test N_smax(0) == 0 # GP-Test N_smax(1) == 1 # GP-Test N_smax(6) == 115648 # GP-Test to_base7(N_smax(51)) \ # GP-Test == 444555566611122233344455566661112223334445556661111 # GP-Test my(v=[0, 1, 8, 57, 400, 14806, 115648]); /* samples shown */ \ # GP-Test vector(#v,j,j--; N_smax(j)) == v # GP-Test my(v=[0, 1, 11, 111, 1111, 61111, 661111]); /* samples shown */ \ # GP-Test vector(#v,j,j--; to_base7(N_smax(j))) == v # vector(25,k,k--; N_smax_digit(k)) # vector(8,k, N_smax(k)) # vector(8,k, to_base7(N_smax(k))) # not in OEIS: 1,1,1,1,6,6,6,5,5,5,4,4,4,3,3,3,2,2,2,1,1,1,6,6,6 \\ digits # not in OEIS: 1, 8, 57, 400, 14806, 115648 \\ decimal # not in OEIS: 1, 11, 111, 1111, 61111, 661111 \\ base7 # vector(8,k, N_smax(k)+1) # vector(8,k, to_base7(N_smax(k))+1) # not in OEIS: 2, 9, 58, 401, 14807, 115649, 821543, 4939258 \\ decimal # not in OEIS: 2, 12, 112, 1112, 61112, 661112, 6661112, 56661112 \\ base7 # GP-DEFINE z_smax_exp(j) = floor(angle_F*j); # GP-Test my(v=[0,0,0,0,1,1,1,2,2,2,3,3,3,4,4,4,5,5,5,6]); /*samples shown*/ \ # GP-Test vector(#v,j,j--; z_smax_exp(j)) == v # GP-DEFINE z_smax(k) = sum(j=0,k-1, conj(w6)^z_smax_exp(j) * b^j); # GP-Test my(v=[0, 1, 7/2+1/2*sqrt3i, 9+3*sqrt3i, 19+12*sqrt3i]); /*samples*/ \ # GP-Test vector(#v,j,j--; z_smax(j)) == v # GP-Test vector(50,j,j--; real( z_smax(j) / w12_times_sqrt3 )) == \ # GP-Test vector(50,j,j--; real( (z_smax(j)+w6^2) / w12_times_sqrt3 )) # GP-DEFINE smax(k) = my(z=z_smax(k)); z_to_x(z)+z_to_y(z); # GP-Test my(v=[0, 2, 8, 24, 62, 172, 470, 1190, 3202, 8740]); /*samples*/ \ # GP-Test vector(#v,j,j--; smax(j)) == v # vector(50,k,k++; z_smax_exp(k)) \\ floor(angle_F*j) # not in OEIS: 4,4,4,5,5,5,6,6,6,7,7,7,7,8,8,8,9,9,9,10,10,10,11,11,11,12,12,12,13,13,13,14,14,14,14,15,15,15,16 # not A032615 = floor(n/Pi) # 1/Pi \\ = 0.318309886183790671537767 is close to F # GP-DEFINE A032615(n) = floor(1/Pi*n); # # is not A062300 which is same, almost, maybe, as A032615 after initial terms # A062300 a(n) = floor cosec( pi/(n+1) ) # GP-DEFINE A062300(n) = floor(1/sin(Pi/(n+1))); # GP-Test vector(10000,n,n+=4; A062300(n)) == \ # GP-Test vector(10000,n,n+=4; A032615(n+1)) # GP-Test vector(200,n,n+=4; A062300(n)) != \ # GP-Test vector(200,n,n+=4; z_smax_exp(n+1)) # sin(x)~x when x small so floor(1/sin(Pi/(n+1))) ~ floor((n+1)/Pi) # but with sin(x)n-1, Vec(select(e->e==z,v,1))); # GP-DEFINE } # GP-Test N_smax_list_by_points(0) == [0] # GP-Test N_smax_list_by_points(1) == [1,2] # GP-Test N_smax_list_by_points(2) == [8,9] # GP-Test N_smax_list_by_points(3) == [57,58] # GP-Test N_smax(3) == 57 # tan(n) # atan(tan(n)) # n-atan(tan(n)) # (n-atan(tan(n)))/Pi # n - m*Pi =pod =head1 SEE ALSO L, L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/Hypot.pm0000644000175000017500000005022613734026670016677 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # A000328 Number of points of norm <= n^2 in square lattice. # 1, 5, 13, 29, 49, 81, 113, 149, 197, 253, 317, 377, 441, 529, 613, 709, 797 # a(n) = 1 + 4 * sum(j=0, n^2 / 4, n^2 / (4*j+1) - n^2 / (4*j+3) ) # A014200 num points norm <= n^2, excluding 0, divided by 4 # # A046109 num points norm == n^2 # # A057655 num points x^2+y^2 <= n # A014198 = A057655 - 1 # # A004018 num points x^2+y^2 == n # # A057962 hypot count x-1/2,y-1/2 <= n # is last point of each hypot in points=odd # # A057961 hypot count as radius increases # # points="square_horiz" # points="square_vert" # points="square_centre" # A199015 square_centred partial sums # package Math::PlanePath::Hypot; use 5.004; use strict; use Carp 'croak'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ { name => 'points', share_key => 'points_aeo', display => 'Points', type => 'enum', default => 'all', choices => ['all','even','odd'], choices_display => ['All','Even','Odd'], description => 'Which X,Y points visit, either all of them or just X+Y=even or odd.', }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; { my %x_negative_at_n = (all => 3, even => 2, odd => 2); sub x_negative_at_n { my ($self) = @_; return $self->n_start + $x_negative_at_n{$self->{'points'}}; } } { my %y_negative_at_n = (all => 4, even => 3, odd => 3); sub y_negative_at_n { my ($self) = @_; return $self->n_start + $y_negative_at_n{$self->{'points'}}; } } sub rsquared_minimum { my ($self) = @_; return ($self->{'points'} eq 'odd' ? 1 # odd at X=1,Y=0 : 0); # even,all at X=0,Y=0 } # points=even includes X=Y so abs(X-Y)>=0 # points=odd doesn't include X=Y so abs(X-Y)>=1 *absdiffxy_minimum = \&rsquared_minimum; *sumabsxy_minimum = \&rsquared_minimum; use constant turn_any_right => 0; # always left or straight sub turn_any_straight { my ($self) = @_; return ($self->{'points'} ne 'all'); # points=all is left always } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } my $points = ($self->{'points'} ||= 'all'); if ($points eq 'all') { $self->{'n_to_x'} = [0]; $self->{'n_to_y'} = [0]; $self->{'hypot_to_n'} = [0]; $self->{'y_next_x'} = [1, 1]; $self->{'y_next_hypot'} = [1, 2]; $self->{'x_inc'} = 1; $self->{'x_inc_factor'} = 2; $self->{'x_inc_squared'} = 1; $self->{'y_factor'} = 2; $self->{'opposite_parity'} = -1; } elsif ($points eq 'even') { $self->{'n_to_x'} = [0]; $self->{'n_to_y'} = [0]; $self->{'hypot_to_n'} = [0]; $self->{'y_next_x'} = [2, 1]; $self->{'y_next_hypot'} = [4, 2]; $self->{'x_inc'} = 2; $self->{'x_inc_factor'} = 4; $self->{'x_inc_squared'} = 4; $self->{'y_factor'} = 2; $self->{'opposite_parity'} = 1; } elsif ($points eq 'odd') { $self->{'n_to_x'} = []; $self->{'n_to_y'} = []; $self->{'hypot_to_n'} = []; $self->{'y_next_x'} = [1]; $self->{'y_next_hypot'} = [1]; $self->{'x_inc'} = 2; $self->{'x_inc_factor'} = 4; $self->{'x_inc_squared'} = 4; $self->{'y_factor'} = 2; $self->{'opposite_parity'} = 0; } elsif ($points eq 'square_centred') { $self->{'n_to_x'} = []; $self->{'n_to_y'} = []; $self->{'hypot_to_n'} = []; $self->{'y_next_x'} = [undef,1]; $self->{'y_next_hypot'} = [undef,2]; $self->{'x_inc'} = 2; $self->{'x_inc_factor'} = 4; # ((x+2)^2 - x^2) = 4*x+4 $self->{'x_inc_squared'} = 4; $self->{'y_start'} = 1; $self->{'y_inc'} = 2; $self->{'opposite_parity'} = -1; } else { croak "Unrecognised points option: ", $points; } return $self; } sub _extend { my ($self) = @_; ### _extend() n: scalar(@{$self->{'n_to_x'}}) ### y_next_x: $self->{'y_next_x'} my $n_to_x = $self->{'n_to_x'}; my $n_to_y = $self->{'n_to_y'}; my $hypot_to_n = $self->{'hypot_to_n'}; my $y_next_x = $self->{'y_next_x'}; my $y_next_hypot = $self->{'y_next_hypot'}; my $y_start = $self->{'y_start'} || 0; my $y_inc = $self->{'y_inc'} || 1; # set @y to the Y with the smallest $y_next_hypot[$y], and if there's some # Y's with equal smallest hypot then all those Y's my @y = ($y_start); my $hypot = $y_next_hypot->[$y_start] || 99; for (my $y = $y_start+$y_inc; $y < @$y_next_x; $y += $y_inc) { if ($hypot == $y_next_hypot->[$y]) { push @y, $y; } elsif ($hypot > $y_next_hypot->[$y]) { @y = ($y); $hypot = $y_next_hypot->[$y]; } } ### chosen y list: @y # if the endmost of the @$y_next_x, @$y_next_hypot arrays are used then # extend them by one if ($y[-1] == $#$y_next_x) { ### grow y_next_x ... my $y = $#$y_next_x + $y_inc; my $x = $y + ($self->{'points'} eq 'odd'); $y_next_x->[$y] = $x; $y_next_hypot->[$y] = $x*$x+$y*$y; ### $y_next_x ### $y_next_hypot ### assert: $y_next_hypot->[$y] == $y**2 + $x*$x } # @x is the $y_next_x[$y] for each of the @y smallests, and step those # selected elements next X and hypot for that new X,Y my @x = map { my $y = $_; my $x = $y_next_x->[$y]; $y_next_x->[$y] += $self->{'x_inc'}; $y_next_hypot->[$y] += $self->{'x_inc_factor'} * $x + $self->{'x_inc_squared'}; ### assert: $y_next_hypot->[$y] == ($x+$self->{'x_inc'})**2 + $y**2 $x } @y; ### $hypot ### base octant: join(' ',map{"$x[$_],$y[$_]"} 0 .. $#x) # transpose X,Y to Y,X { my @base_x = @x; my @base_y = @y; unless ($y[0]) { # no transpose of x,0 shift @base_x; shift @base_y; } if ($x[-1] == $y[-1]) { # no transpose of x,x pop @base_x; pop @base_y; } push @x, reverse @base_y; push @y, reverse @base_x; } ### with transpose q1: join(' ',map{"$x[$_],$y[$_]"} 0 .. $#x) # rotate +90 quadrant 1 into quadrant 2 { my @base_y = @y; push @y, @x; push @x, map {-$_} @base_y; } ### with rotate q2: join(' ',map{"$x[$_],$y[$_]"} 0 .. $#x) # rotate +180 quadrants 1+2 into quadrants 2+3 push @x, map {-$_} @x; push @y, map {-$_} @y; ### store: join(' ',map{"$x[$_],$y[$_]"} 0 .. $#x) ### at n: scalar(@$n_to_x) ### hypot_to_n: "h=$hypot n=".scalar(@$n_to_x) $hypot_to_n->[$hypot] = scalar(@$n_to_x); push @$n_to_x, @x; push @$n_to_y, @y; # ### hypot_to_n now: join(' ',map {defined($hypot_to_n->[$_]) && "h=$_,n=$hypot_to_n->[$_]"} 0 .. $#$hypot_to_n) # my $x = $y_next_x->[0]; # # $x = $y_next_x->[$y]; # $n_to_x->[$next_n] = $x; # $n_to_y->[$next_n] = $y; # $xy_to_n{"$x,$y"} = $next_n++; # # $y_next_x->[$y]++; # $y_next_hypot->[$y] = $y*$y + $y_next_x->[$y]**2; } sub n_to_xy { my ($self, $n) = @_; ### Hypot n_to_xy(): $n $n = $n - $self->{'n_start'}; # starting $n==0, warn if $n==undef if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; # fraction part my $n_to_x = $self->{'n_to_x'}; my $n_to_y = $self->{'n_to_y'}; while ($int >= $#$n_to_x) { _extend($self); } my $x = $n_to_x->[$int]; my $y = $n_to_y->[$int]; return ($x + $n * ($n_to_x->[$int+1] - $x), $y + $n * ($n_to_y->[$int+1] - $y)); } sub xy_is_visited { my ($self, $x, $y) = @_; if ($self->{'opposite_parity'} >= 0) { $x = round_nearest ($x); $y = round_nearest ($y); if ((($x%2) ^ ($y%2)) == $self->{'opposite_parity'}) { return 0; } } if ($self->{'points'} eq 'square_centred') { unless (($y%2) && ($x%2)) { return 0; } } return 1; } sub xy_to_n { my ($self, $x, $y) = @_; ### Hypot xy_to_n(): "$x, $y" ### hypot_to_n last: $#{$self->{'hypot_to_n'}} $x = round_nearest ($x); $y = round_nearest ($y); if ((($x%2) ^ ($y%2)) == $self->{'opposite_parity'}) { return undef; } if ($self->{'points'} eq 'square_centred') { unless (($y%2) && ($x%2)) { return undef; } } my $hypot = $x*$x + $y*$y; if (is_infinite($hypot)) { ### infinity return undef; } my $n_to_x = $self->{'n_to_x'}; my $n_to_y = $self->{'n_to_y'}; my $hypot_to_n = $self->{'hypot_to_n'}; while ($hypot > $#$hypot_to_n) { _extend($self); } my $n = $hypot_to_n->[$hypot]; for (;;) { if ($x == $n_to_x->[$n] && $y == $n_to_y->[$n]) { return $n + $self->{'n_start'}; } $n += 1; if ($n_to_x->[$n]**2 + $n_to_y->[$n]**2 != $hypot) { ### oops, hypot_to_n no good ... return undef; } } # if ($x < 0 || $y < 0) { # return undef; # } # my $h = $x*$x + $y*$y; # # while ($y_next_x[$y] <= $x) { # _extend($self); # } # return $xy_to_n{"$x,$y"}; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = abs (round_nearest ($x1)); $y1 = abs (round_nearest ($y1)); $x2 = abs (round_nearest ($x2)); $y2 = abs (round_nearest ($y2)); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # circle area pi*r^2, with r^2 = $x2**2 + $y2**2 return ($self->{'n_start'}, $self->{'n_start'} + int (3.2 * (($x2+1)**2 + ($y2+1)**2))); } 1; __END__ # Quadrant style ... # # 9 73 75 79 83 85 # 8 58 62 64 67 71 81 ... # 7 45 48 52 54 61 69 78 86 # 6 35 37 39 43 50 56 65 77 88 # 5 26 28 30 33 41 47 55 68 80 # 4 17 19 22 25 31 40 49 60 70 84 # 3 11 13 15 20 24 32 42 53 66 82 # 2 6 8 9 14 21 29 38 51 63 76 # 1 3 4 7 12 18 27 36 46 59 74 # Y=0 1 2 5 10 16 23 34 44 57 72 # # X=0 1 2 3 4 5 6 7 8 9 ... # # For example N=37 is at X=1,Y=6 which is sqrt(1*1+6*6) = sqrt(37) from the # origin. The next closest to the origin is X=6,Y=2 at sqrt(40). In general # it's the sums of two squares X^2+Y^2 taken in order from smallest to biggest. # # Points X,Y and swapped Y,X are the same distance from the origin. The one # with bigger X is taken first, then the swapped Y,X (as long as X!=Y). For # example N=21 is X=4,Y=2 and N=22 is X=2,Y=4. =for stopwords Ryde Math-PlanePath ie hypot octant onwards OEIS hypots =head1 NAME Math::PlanePath::Hypot -- points in order of hypotenuse distance =head1 SYNOPSIS use Math::PlanePath::Hypot; my $path = Math::PlanePath::Hypot->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path visits integer points X,Y in order of their distance from the origin 0,0, or anti-clockwise from the X axis among those of equal distance, =cut # math-image --expression='i<=89?i:0' --path=Hypot --output=numbers --size=79 =pod 84 73 83 5 74 64 52 47 51 63 72 4 75 59 40 32 27 31 39 58 71 3 65 41 23 16 11 15 22 38 62 2 85 53 33 17 7 3 6 14 30 50 82 1 76 48 28 12 4 1 2 10 26 46 70 <- Y=0 86 54 34 18 8 5 9 21 37 57 89 -1 66 42 24 19 13 20 25 45 69 -2 77 60 43 35 29 36 44 61 81 -3 78 67 55 49 56 68 80 -4 87 79 88 -5 ^ -5 -4 -3 -2 -1 X=0 1 2 3 4 5 For example N=58 is at X=4,Y=-1 is sqrt(4*4+1*1) = sqrt(17) from the origin. The next furthest from the origin is X=3,Y=3 at sqrt(18). See C for points in order of X^2+3*Y^2, or C and C in order of plain sum X+Y. =head2 Equal Distances Points with the same distance are taken in anti-clockwise order around from the X axis. For example X=3,Y=1 is sqrt(10) from the origin, as are the swapped X=1,Y=3, and X=-1,Y=3 etc in other quadrants, for a total 8 points N=30 to N=37 all the same distance. When one of X or Y is 0 there's no negative, so just four negations like N=10 to 13 points X=2,Y=0 through X=0,Y=-2. Or on the diagonal X==Y there's no swap, so just four like N=22 to N=25 points X=3,Y=3 through X=3,Y=-3. There can be more than one way for the same distance to arise. A Pythagorean triple like 3^2 + 4^2 == 5^2 has 8 points from the 3,4, then 4 points from the 5,0 giving a total 12 points N=70 to N=81. Other combinations like 20^2 + 15^2 == 24^2 + 7^2 occur too, and also with more than two different ways to have the same sum. =head2 Multiples of 4 The first point of a given distance from the origin is either on the X axis or somewhere in the first octant. The row Y=1 just above the axis is the first of its equals from XE=2 onwards, and similarly further rows for big enough X. There's always a multiple of 4 many points with the same distance so the first point has N=4*k+2, and similarly on the negative X side N=4*j, for some k or j. If you plot the prime numbers on the path then those even N's (composites) are gaps just above the positive X axis, and on or just below the negative X axis. =head2 Circle Lattice Gauss's circle lattice problem asks how many integer X,Y points there are within a circle of radius R. The points on the X axis N=2,10,26,46, etc are the first for which X^2+Y^2==R^2 (integer X==R). Adding option C0> to make them each 1 less gives the number of points strictly inside, ie. X^2+Y^2 E R^2. The last point satisfying X^2+Y^2==R^2 is either in the octant below the X axis, or is on the negative Y axis. Those N's are the number of points X^2+Y^2E=R^2, Sloane's A000328. When that A000328 sequence is plotted on the path a straight line can be seen in the fourth quadrant extending down just above the diagonal. It arises from multiples of the Pythagorean 3^2 + 4^2, first X=4,Y=-3, then X=8,Y=-6, etc X=4*k,Y=-3*k. But sometimes the multiple is not the last among those of that 5*k radius, so there's gaps in the line. For example 20,-15 is not the last since because 24,-7 is also 25 away from the origin. =head2 Even Points Option C "even"> visits just the even points, meaning the sum X+Y even, so X,Y both even or both odd. =cut # math-image --expression='i<70?i:0' --path=Hypot,points=even --output=numbers --size=79 =pod points => "even" 52 40 39 51 5 47 32 23 31 46 4 53 27 16 15 26 50 3 33 11 7 10 30 2 41 17 3 2 14 38 1 24 8 1 6 22 <- Y=0 42 18 4 5 21 45 -1 34 12 9 13 37 -2 54 28 19 20 29 57 -3 48 35 25 36 49 -4 55 43 44 56 -5 ^ -5 -4 -3 -2 -1 X=0 1 2 3 4 5 Even points can be mapped to all points by a 45 degree rotate and flip. N=1,6,22,etc on the X axis here is on the X=Y diagonal of all-points. And conversely N=1,2,10,26,etc on the X=Y diagonal here is the X axis of all-points. The sets of points with equal hypotenuse are the same in the even and all, but the flip takes them in a reversed order. =head2 Odd Points Option C "odd"> visits just the odd points, meaning sum X+Y odd, so X,Y one odd the other even. =cut # math-image --expression='i<=76?i:0' --path=Hypot,points=odd --output=numbers --size=78x30 =pod points => "odd" 71 55 54 70 6 63 47 36 46 62 5 64 37 27 26 35 61 4 72 38 19 14 18 34 69 3 48 20 7 6 17 45 2 56 28 8 2 5 25 53 1 39 15 3 + 1 13 33 <- Y=0 57 29 9 4 12 32 60 -1 49 21 10 11 24 52 -2 73 40 22 16 23 44 76 -3 65 41 30 31 43 68 -4 66 50 42 51 67 -5 74 58 59 75 -6 ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 Odd points can be mapped to all points by a 45 degree rotate and a shift X-1,Y+1 to put N=1 at the origin. The effect of that shift is as if the hypot measure in "all" points was (X-1/2)^2+(Y-1/2)^2 and for that reason the sets of points with equal hypots are not the same in odd and all. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::Hypot-Enew ()> =item C<$path = Math::PlanePath::Hypot-Enew (points =E $str), n_start =E $n> Create and return a new hypot path object. The C option can be "all" all integer X,Y (the default) "even" only points with X+Y even "odd" only points with X+Y odd =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n E 1> the return is an empty list, it being considered the first point at X=0,Y=0 is N=1. Currently it's unspecified what happens if C<$n> is not an integer. Successive points are a fair way apart, so it may not make much sense to say give an X,Y position in between the integer C<$n>. =item C<$n = $path-Exy_to_n ($x,$y)> Return an integer point number for coordinates C<$x,$y>. Each integer N is considered the centre of a unit square and an C<$x,$y> within that square returns N. For "even" and "odd" options only every second square in the plane has an N and if C<$x,$y> is a position not covered then the return is C. =back =head1 FORMULAS The calculations are not particularly efficient currently. Private arrays are built similar to what's described for C, but with replication for negative and swapped X,Y. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back points="all", n_start=0 A051132 N on X axis, being count points norm < X^2 points="odd" A005883 count of points with norm==4*n+1 =head1 SEE ALSO L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/DigitGroups.pm0000644000175000017500000002670013734026672020036 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=DigitGroups --output=numbers_dash # math-image --path=DigitGroups,radix=2 --all --output=numbers # # increment N+1 changes low 01111 to 10000 # X bits change 01111 to 000, no carry, decreasing by number of low 1s # Y bits change 011 to 100, plain +1 # # cf A084473 binary 0->0000 # A088698 binary 1->11 # A175047 binary 0000run->0 # # G. Cantor, "Ein Beitrag zur Mannigfaltigkeitslehre", Journal für die reine # und angewandte Mathematik (Crelle's Journal), Vol. 84, 242-258, 1878. # http://www.digizeitschriften.de/dms/img/?PPN=PPN243919689_0084&DMDID=dmdlog15 package Math::PlanePath::DigitGroups; use 5.004; use strict; #use List::Util 'max','min'; *max = \&Math::PlanePath::_max; *min = \&Math::PlanePath::_min; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'parameter_info_array', # "radix" parameter 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; use constant absdx_minimum => 1; sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; return $self->{'radix'} - 1; } sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; return $self->{'radix'}; } sub _UNDOCUMENTED__turn_any_straight_at_n { my ($self) = @_; if ($self->{'radix'} == 2) { return 274; } return 1; } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); my $radix = $self->{'radix'}; if (! defined $radix || $radix <= 2) { $radix = 2; } $self->{'radix'} = $radix; return $self; } sub n_to_xy { my ($self, $n) = @_; ### DigitGroups n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } # what to do for fractions ? { my $int = int($n); ### $int if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat ### $frac my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $radix = $self->{'radix'}; my (@x,@y); # digits low to high my @digits = digit_split_lowtohigh($n,$radix) or return (0,0); # if $n==0 DIGITS: for (;;) { my $digit; # from @digits to @x do { ### digit to x: $digits[0] $digit = shift @digits; # $n digits low to high push @x, $digit; @digits || last DIGITS; } while ($digit); # $digit==0 is separator # from @digits to @y do { $digit = shift @digits; # low to high ### digit to y: $digit push @y, $digit; @digits || last DIGITS; } while ($digit); # $digit==0 is separator } my $zero = $n * 0; # inherit bignum 0 return (digit_join_lowtohigh (\@x, $radix, $zero), digit_join_lowtohigh (\@y, $radix, $zero)); } sub xy_to_n { my ($self, $x, $y) = @_; ### DigitGroups xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } if ($x < 0 || $y < 0) { return undef; } if ($x == 0 && $y == 0) { return 0; } my $radix = $self->{'radix'}; my $zero = ($x * 0 * $y); # inherit bignum 0 my @n; # digits low to high my @x = digit_split_lowtohigh($x,$radix); my @y = digit_split_lowtohigh($y,$radix); while (@x || @y) { my $digit; do { $digit = shift @x || 0; # low to high ### digit from x: $digit push @n, $digit; } while ($digit); do { $digit = shift @y || 0; # low to high ### digit from y: $digit push @n, $digit; } while ($digit); } return digit_join_lowtohigh (\@n, $radix, $zero); } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### DigitGroups rect_to_n_range() ... if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } # x1 smaller if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # y1 smaller if ($y2 < 0 || $x2 < 0) { return (1, 0); # rect all negative, no N } my $radix = $self->{'radix'}; my ($power, $lo_level) = round_down_pow (min($x1,$y1), $radix); if (is_infinite($lo_level)) { return (0,$lo_level); } ($power, my $hi_level) = round_down_pow (max($x2,$y2), $radix); if (is_infinite($hi_level)) { return (0,$hi_level); } return ($lo_level == 0 ? 0 : ($radix*$radix + 1) * $radix ** (2*$lo_level), ($radix-1)*$radix**(3*$hi_level+2) + $radix**($hi_level+1) - 1); } 1; __END__ =for stopwords Ryde Math-PlanePath undrawn Radix cardinality bijection radix OEIS KE<246>nig KE<246>nig's nig =head1 NAME Math::PlanePath::DigitGroups -- X,Y digits grouped by zeros =head1 SYNOPSIS use Math::PlanePath::DigitGroups; my $path = Math::PlanePath::DigitGroups->new (radix => 2); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path splits an N into X,Y by digit groups separated by a 0. The default is binary so for example N = 110111001011 is split into groups with a leading high 0 bit, and those groups then go to X and Y alternately, N = 11 0111 0 01 011 X Y X Y X X = 11 0 011 = 110011 Y = 0111 01 = 11101 The result is a one-to-one mapping between numbers NE=0 and pairs XE=0,YE=0. The default binary is 11 | 38 77 86 155 166 173 182 311 550 333 342 347 10 | 72 145 148 291 168 297 300 583 328 337 340 595 9 | 66 133 138 267 162 277 282 535 322 325 330 555 8 | 128 257 260 515 272 521 524 1031 320 545 548 1043 7 | 14 29 46 59 142 93 110 119 526 285 302 187 6 | 24 49 52 99 88 105 108 199 280 177 180 211 5 | 18 37 42 75 82 85 90 151 274 165 170 171 4 | 32 65 68 131 80 137 140 263 160 161 164 275 3 | 6 13 22 27 70 45 54 55 262 141 150 91 2 | 8 17 20 35 40 41 44 71 136 81 84 83 1 | 2 5 10 11 34 21 26 23 130 69 74 43 Y=0 | 0 1 4 3 16 9 12 7 64 33 36 19 +------------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 N=0,1,4,3,16,9,etc along the X axis is X with zero bits doubled. For example X=9 is binary 1001, double up the zero bits to 100001 for N=33 at X=9,Y=0. This is because in the digit groups Y=0 so when X is grouped by its zero bits there's an extra 0 from Y in between each group. Similarly N=0,2,8,6,32,etc along the Y axis is Y with zero bits doubled, plus an extra zero bit at the low end coming from the first X=0 group. For example Y=9 is again binary 1001, doubled zeros to 100001, and an extra zero at the low end 1000010 is N=66 at X=0,Y=9. =head2 Radix The C $r> option selects a different base for the digit split. For example radix 5 gives radix => 5 12 | 60 301 302 303 304 685 1506 1507 1508 1509 1310 1511 11 | 55 276 277 278 279 680 1381 1382 1383 1384 1305 1386 10 | 250 1251 1252 1253 1254 1275 6256 6257 6258 6259 1300 6261 9 | 45 226 227 228 229 670 1131 1132 1133 1134 1295 1136 8 | 40 201 202 203 204 665 1006 1007 1008 1009 1290 1011 7 | 35 176 177 178 179 660 881 882 883 884 1285 886 6 | 30 151 152 153 154 655 756 757 758 759 1280 761 5 | 125 626 627 628 629 650 3131 3132 3133 3134 675 3136 4 | 20 101 102 103 104 145 506 507 508 509 270 511 3 | 15 76 77 78 79 140 381 382 383 384 265 386 2 | 10 51 52 53 54 135 256 257 258 259 260 261 1 | 5 26 27 28 29 130 131 132 133 134 255 136 Y=0 | 0 1 2 3 4 25 6 7 8 9 50 11 +----------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 =head2 Real Line and Plane Xnig, Julius>This split is inspired by the digit grouping in the proof by Julius KE<246>nig that the real line is the same cardinality as the plane. (Cantor's original proof was a C style digit interleaving.) In KE<246>nig's proof a bijection between interval n=(0,1) and pairs x=(0,1),y=(0,1) is made by taking groups of digits stopping at a non-zero. Non-terminating fractions like 0.49999... are chosen over terminating 0.5000... so there's always infinitely many non-zero digits going downwards. For the integer form here the groupings are digit going upwards and there's infinitely many zero digits above the top, hence the grouping by zeros instead of non-zeros. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::DigitGroups-Enew ()> =item C<$path = Math::PlanePath::DigitGroups-Enew (radix =E $r)> Create and return a new path object. The optional C parameter gives the base for digit splitting (the default is binary, radix 2). =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back radix=2 (the default) A084471 N on X axis, bit 0->00 A084472 N on X axis, in binary A060142 N on X axis, sorted into ascending order =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/DekkingCurve.pm0000644000175000017500000005567113734026672020170 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::DekkingCurve; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); *_divrem = \&Math::PlanePath::_divrem; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_up_pow', 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 1; use constant class_y_negative => 1; use constant parameter_info_array => [ { name => 'arms', share_key => 'arms_4', display => 'Arms', type => 'integer', minimum => 1, maximum => 4, default => 1, width => 1, description => 'Arms', } ]; #------------------------------------------------------------------------------ sub x_negative { my ($self) = @_; return $self->{'arms'} > 1; } { my @x_negative_at_n = (undef, undef, 5, 2, 2); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } sub y_negative { my ($self) = @_; return $self->{'arms'} > 2; } { my @y_negative_at_n = (undef, undef, undef, 8, 3); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } #------------------------------------------------------------------------------ use Math::PlanePath::DekkingCentres; use vars '@_next_state','@_digit_to_x','@_digit_to_y','@_yx_to_digit'; BEGIN { *_next_state = \@Math::PlanePath::DekkingCentres::_next_state; *_digit_to_x = \@Math::PlanePath::DekkingCentres::_digit_to_x; *_digit_to_y = \@Math::PlanePath::DekkingCentres::_digit_to_y; *_yx_to_digit = \@Math::PlanePath::DekkingCentres::_yx_to_digit; } sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} ||= 1; return $self; } # tables generated by tools/dekking-curve-table.pl # my @edge_dx = (0,0,0,1,1, 0,0,1,1,0, 0,0,0,1,0, 0,0,1,0,1, 0,1,0,1,1, 1,1,1,1,1, 1,1,1,0,1, 1,1,0,1,0, 0,0,1,0,0, 0,1,1,0,0, 1,1,1,0,0, 1,1,0,0,1, 1,1,1,0,1, 1,1,0,1,0, 1,0,1,0,0, 0,0,0,0,0, 0,0,0,1,0, 0,0,1,0,1, 1,1,0,1,1, 1,0,0,1,1, 1,1,1,1,1, 1,0,0,0,0, 1,1,1,1,1, 0,0,0,0,1, 1,0,0,1,1, 1,1,1,0,0, 1,1,1,1,1, 0,0,0,1,1, 0,0,1,0,1, 0,1,0,1,1, 0,0,0,0,0, 0,1,1,1,1, 0,0,0,0,0, 1,1,1,1,0, 0,1,1,0,0, 0,0,0,1,1, 0,0,0,0,0, 1,1,1,0,0, 1,1,0,1,0, 1,0,1,0,0); my @edge_dy = (0,0,0,0,0, 0,0,0,1,0, 0,0,1,0,1, 1,1,0,1,1, 1,0,0,1,1, 0,0,0,1,1, 0,0,1,1,0, 0,0,0,1,0, 0,0,1,0,1, 0,1,0,1,1, 1,1,1,1,1, 1,1,1,0,1, 1,1,0,1,0, 0,0,1,0,0, 0,1,1,0,0, 1,1,1,0,0, 1,1,0,0,1, 1,1,1,0,1, 1,1,0,1,0, 1,0,1,0,0, 0,0,0,1,1, 0,0,0,0,0, 1,1,1,0,0, 1,1,0,1,0, 1,0,1,0,0, 1,1,1,1,1, 1,0,0,0,0, 1,1,1,1,1, 0,0,0,0,1, 1,0,0,1,1, 1,1,1,0,0, 1,1,1,1,1, 0,0,0,1,1, 0,0,1,0,1, 0,1,0,1,1, 0,0,0,0,0, 0,1,1,1,1, 0,0,0,0,0, 1,1,1,1,0, 0,1,1,0,0); sub n_to_xy { my ($self, $n) = @_; ### DekkingCurve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; # fraction part my $arms = $self->{'arms'}; my $arm = _divrem_mutate ($int, $arms); if ($arm) { $int += 1; } my @digits = digit_split_lowtohigh($int,25); my $state = 0; my @x; my @y; foreach my $i (reverse 0 .. $#digits) { $state += $digits[$i]; $x[$i] = $_digit_to_x[$state]; $y[$i] = $_digit_to_y[$state]; $state = $_next_state[$state]; } ### @x ### @y ### $state ### dx: $_digit_to_x[$state+24] - $_digit_to_x[$state] ### dy: $_digit_to_y[$state+24] - $_digit_to_y[$state] my $zero = $int * 0; my $x = ($n * (($_digit_to_x[$state+24] - $_digit_to_x[$state])/4) + digit_join_lowtohigh(\@x, 5, $zero) + $edge_dx[$state]); my $y = ($n * (($_digit_to_y[$state+24] - $_digit_to_y[$state])/4) + digit_join_lowtohigh(\@y, 5, $zero) + $edge_dy[$state]); if ($arm < 2) { if ($arm < 1) { return ($x,$y); } # arm==0 return (-$y,$x); # arm==1 rotate +90 } if ($arm < 3) { return (-$x,-$y); } # arm==2 return ($y,-$x); # arm==3 rotate -90 } sub xy_to_n { my ($self, $x, $y) = @_; ### DekkingCurve xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my $arms = $self->{'arms'}; if (($arms < 2 && $x < 0) || ($arms < 3 && $y < 0)) { ### X or Y negative, no N value ... return undef; } foreach my $arm (0 .. $arms-1) { foreach my $xoffset (0,-1) { foreach my $yoffset (0,-1) { my @x = digit_split_lowtohigh($x+$xoffset,5); my @y = digit_split_lowtohigh($y+$yoffset,5); my $state = 0; my @n; foreach my $i (reverse 0 .. max($#x,$#y)) { my $digit = $n[$i] = $_yx_to_digit[$state + 5*($y[$i]||0) + ($x[$i]||0)]; $state = $_next_state[$state+$digit]; } my $zero = $x*0*$y; my $n = digit_join_lowtohigh(\@n, 25, $zero); $n = $n*$arms; if (my ($nx,$ny) = $self->n_to_xy($n)) { if ($nx == $x && $ny == $y) { return $n + ($arm ? $arm-$arms : $arm); } } } } ($x,$y) = ($y,-$x); # rotate -90 ### rotate to: "$x, $y" } return undef; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### DekkingCurve rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } my $arms = $self->{'arms'}; if (($arms < 2 && $x2 < 0) || ($arms < 3 && $y2 < 0)) { ### rectangle all negative, no N values ... return (1, 0); } my ($pow) = round_down_pow (max(abs($x1),abs($y1),$x2,$y2) + 1, 5); ### $pow return (0, 25*$pow*$pow*$arms - 1); } #------------------------------------------------------------------------------ sub level_to_n_range { my ($self, $level) = @_; return (0, 25**$level * $self->{'arms'}); } sub n_to_level { my ($self, $n) = @_; ### n_to_level(): $n if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); $n += $self->{'arms'}-1; # division rounding up _divrem_mutate ($n, $self->{'arms'}); my ($pow, $exp) = round_up_pow ($n, 25); return $exp; } #------------------------------------------------------------------------------ # Not taking into account multiple arms ... # Return true if X axis segment $x to $x+1 is traversed sub _UNDOCUMENTED__xseg_is_traversed { my ($self, $x) = @_; if ($x < 0 || is_infinite($x)) { return 0; } if ($x == 0) { return 1; } my $digit = _divrem_mutate($x, 5); if ($digit) { return ($digit == 1); } # find lowest non-zero while ($x && ! ($digit = _divrem_mutate($x, 5))) { } return ($digit == 1 || $digit == 2); } # Return true if Y axis segment $y to $y+1 is traversed sub _UNDOCUMENTED__yseg_is_traversed { my ($self, $y) = @_; if ($y < 0 || is_infinite($y)) { return 0; } my $digit = _divrem_mutate($y, 5); if ($digit != 4) { return ($digit == 3); } # find lowest non-4 while ($y && ($digit = _divrem_mutate($y, 5)) == 4) { } return ($digit == 2 || $digit == 3); } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde ie Math-PlanePath Dekking Gosper McKenna unrotated DFA =head1 NAME Math::PlanePath::DekkingCurve -- 5x5 self-similar edge curve =head1 SYNOPSIS use Math::PlanePath::DekkingCurve; my $path = Math::PlanePath::DekkingCurve->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is an integer version of a 5x5 self-similar curve from =over F. M. Dekking, "Recurrent Sets", Advances in Mathematics, volume 44, 1982, pages 79-104, section 4.9 "Gosper-Type Curves" =back This is also a horizontal mirror image of the E-curve from =over Douglas M. McKenna, "SquaRecurves, E-Tours, Eddies, and Frenzies: Basic Families of Peano Curves on the Square Grid", in "The Lighter Side of Mathematics: Proceedings of the Eugene Strens Memorial Conference on Recreational Mathematics and its History", Mathematical Association of America, 1994, pages 49-73, ISBN 0-88385-516-X =back The base pattern is N=0 to N=25. It repeats with rotations or reversals which make the ends join. For example N=75 to N=100 is the base pattern in reverse, ie. from N=25 down to N=0. Or N=50 to N=75 is reverse and also rotate by -90. =cut # math-image --path=DekkingCurve --all --output=numbers_dash --size=78x30 =pod 10 | 123-124-125-... 86--85 | | | | 9 | 115-116-117 122-121 90--89--88--87 84 | | | | | | 8 | 114-113 118-119-120 91--92--93 82--83 | | | | 7 | 112 107-106 103-102 95--94 81 78--77 | | | | | | | | | | 6 | 111 108 105-104 101 96--97 80--79 76 | | | | | | 5 | 110-109 14--15 100--99--98 39--40 75 66--65 | | | | | | | | 4 | 10--11--12--13 16 35--36--37--38 41 74 71--70 67 64 | | | | | | | | | | 3 | 9---8---7 18--17 34--33--32 43--42 73--72 69--68 63 | | | | | | 2 | 5---6 19 22--23 30--31 44 47--48 55--56--57 62--61 | | | | | | | | | | | | 1 | 4---3 20--21 24 29--28 45--46 49 54--53 58--59--60 | | | | | | Y=0| 0---1---2 25--26--27 50--51--52 +---------------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 The curve segments correspond to edges of squares in a 5x5 arrangement. + + + 14----15 + | v |> ^ ^ <| | 10----11----12----13 16 + | v |> |> ^ ^ | 9-----8-----7 18----17 + v | | |> ^ |> | ^ + 5-----6 19 22----23 | <| | <| <| ^ | <| | + 4-----3 20----21 -- 24 | v <| ^ ^ |> | 0-----1-----2 + + 25 The little notch marks show which square each edge represents and which it expands into at the next level. For example N=1 to N=2 has its notch on the left so the next level N=25 to N=50 expands on the left. All the directions are made by rotating the base pattern. When the expansion is on the right the segments go in reverse. For example N=2 to N=3 expands on the right and is made by rotating the base pattern clockwise 90 degrees. This means that N=2 becomes the 25 end, and following the curve to the 0 start at N=3. Dekking writes these directions as a sequence of 25 symbols s(i,j) where i=0 for left plain or i=1 for right reversal and j=0,1,2,3 direction j*90 degrees anti-clockwise so E,N,W,S. =head2 Arms The optional C parameter can give up to four copies of the curve, each advancing successively. Each copy is in a successive quadrant. =cut # math-image --path=DekkingCurve,arms=3 --expression='i<75?i:0' --output=numbers_dash --size=78x24 =pod arms => 3 | 67-70-73 42-45 5 | | | 43-46-49 64-61 30-33-36-39 48 4 | | | | | 40-37 52-55-58 27-24-21 54-51 3 | | | 34 19-16 7--4 15-18 57 66-69 2 | | | | | | | | | 31 22 13-10 1 12--9 60-63 72 1 | | | | ...--74 28-25 5--2 0--3--6 75-... <-- Y=0 | | 71 62-59 8-11 -1 | | | | 68-65 56 17-14 -2 | | 50-53 20-23-26 -3 | | 47 38-35-32-29 -4 | | 44-41 -5 ^ ... -5 -4 -3 -2 -1 X=0 1 2 3 4 5 ... The origin is N=0 and is on the first arm only. The second and subsequent arms begin 1,2,etc. The curves interleave perfectly on the axes where the arms meet. The result is that arms=4 fills the plane visiting each integer X,Y exactly once and not touching or crossing. =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::DekkingCurve-Enew ()> =item C<$path = Math::PlanePath::DekkingCurve-Enew (arms =E $a)> Create and return a new path object. The optional C parameter gives between 1 and 4 copies of the curve successively advancing. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 25**$level)>, or for multiple arms return C<(0, $arms * 25**$level)>. There are 25^level + 1 points in a level, numbered starting from 0. On the second and third arms the origin is omitted (so as not to repeat that point) and so just 25^level for them, giving 25^level+1 + (arms-1)*25^level = arms*25^level + 1 many points starting from 0. =back =head1 FORMULAS =head2 X Axis Segments In the sample points above there are some line segments on the X axis. A segment X to X+1 is traversed or not according to X digits in base 5 traversed if X==0 traversed if low digit 1 not-traversed if low digit 2 or 3 or 4 when low digit == 0 traversed if lowest non-zero 1 or 2 not-traversed if lowest non-zero 3 or 4 XsegPred(X) = 1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,0,1,0,... =1 at 0,1,5,6,10,11,16,21,25,26,30,31,35,36,41,... =cut # not in OEIS: 1,1,0,0,0,1,1,0,0,0,1,1,0,0,0,0,1,0,0,0,0 # not in OEIS: 0,1,5,6,10,11,16,21,25,26,30,31,35,36,41,46,50 =pod In the samples the segments at X=1, X=6 and X=11 segments traversed are low digit 1. Their preceding X=5 and X=10 segments are low digit==0 and the lowest non-zero 1 or 2 (respectively). At X=15 however the lowest non-zero is 3 and so not-traversed there. In general in groups of 5 there is always X==1 mod 5 traversed but its preceding X==0 mod 5 is traversed or not according to lowest non-zero 1,2 or 3,4. This pattern is found by considering how the base pattern expands. The plain base pattern has its south edge on the X axis. The first two sub-parts of that south edge are the base pattern unrotated, so the south edge again, but the other parts rotated. In general the sides are 0 1 2 3 4 S -> S,S,E,N,W E -> S,S,E,N,N N -> W,S,E,N,N W -> W,S,E,N,W Starting in S and taking digits high to low a segment is traversed when the final state is S again. Any digit 1,2,3 goes to S,E,N respectively. If no 1,2,3 at all then S start. At the lowest 1,2,3 there are only digits 0,4 below. If no such digits then only digit 1 which is S, or no digits at all for N=0, is traversed. If one or more 0s below then E goes to S so a lowest non-zero 2 means traversed too. If there is any 4 then it goes to N or W and in those states both 0,4 stay in N or W so not-traversed. The transitions from the lowest 1,2,3 can be drawn in a state diagram, +--+ v |4 base 5 digits of X North <---+ <-------+ high to low / | | /0 |4 | / | |3 +-> v | 2 | | West East <--- start lowest 1,2,3 +-- ^ | | 0,4 \ | |1 \4 |0 |or no 1,2,3 at all \ | | South <---+ <-------+ ^ |0 +--+ The full diagram, starting from the top digit, is less clear +--+ v |3,4 +---> North <---+ 3| / | ^ \ |3,4 | /0 1 | 2\ | base 5 digits of X | / | | \ | high to low +-> | v | | v | <-+ | West 2---------> East | start in South, +-- | ^ | | ^ | --+ segment traversed 0,4 | \ | | / | 2 if end in South | \4 | 3 2/ | 1| \ v | / |0,1 +---> South <---+ ^ |0,1 +--+ but allows usual DFA state machine manipulations to reverse to go low to high. +---------- start ----------+ | 1 0| 2,3,4 | base 5 digits of X | | | low to high v 1,2 v 3,4 v traversed <------- m1 -------> not-traversed 0| ^ +-+ In state m1 a 0 digit loops back to m1 so finds the lowest non-zero. States start and m1 are the same except for the behaviour of digit 2 and so in the rules above the result for digit 2 differs according to whether there are any low 0s. =head2 Y Axis Segments The Y axis can be treated similarly Y digits in base 5 (with a single 0 digit if Y==0) traversed if lowest digit 3 not-traversed if lowest digit 0 or 1 or 2 when lowest digit == 4 traversed if lowest non-4 is 2 or 3 not-traversed if lowest non-4 is 0 or 1 YsegPred(X) = 0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,0,0,... =1 at 3,8,13,14,18,19,23,28,33,38,39,43,44,48,... =cut # not in OEIS: 0,0,0,1,0,0,0,0,1,0,0,0,0,1,1,0,0,0,1,1,0 # not in OEIS: 3,8,13,14,18,19,23,28,33,38,39,43,44,48 =pod The Y axis goes around the base square clockwise, so the digits are reversed 0E-E4 from the X axis for the state transitions. The initial state is W. 0 1 2 3 4 S -> W,N,E,S,S E -> N,N,E,S,S N -> N,N,E,S,W W -> W,N,E,S,W N and W can be merged as equivalent. Their only difference is digit 0 going to N or W and both of those are final result not-traversed. Final state S is reached if the lowest digit is 3, or if state S or E are reached by digit 2 or 3 and then only 4s below. =head2 X,Y Axis Interleaving For arms=2 the second copy of the curve is rotated +90 degrees, and similarly a third or fourth copy in arms=3 or 4. This means each axis is a Y axis of the quadrant before and an X axis of the quadrant after. When this happens the segments do not overlap nor does the curve touch. This is seen from the digit rules above. The 1 mod 5 segment is always traversed by X and never by Y. The 2 mod 5 segment is never traversed by either. The 3 mod 5 segment is always traversed by Y and never by X. The 0 mod 5 segment is sometimes traversed by X, and never by Y. The 4 mod 5 segment is sometimes traversed by Y, and never by Y. 0 1 2 3 4 *-------*-------*-------*-------*-------* X X neither Y Y maybe maybe A 4 mod 5 segment has one or more trailing 4s and at +1 for the next segment they become 0s and increment the lowest non-4. +--------+-----+-------+ | ... | d | 4...4 | N == 4 mod 5 X never +--------+-----+-------+ Y maybe +--------+-----+-------+ | ... | d+1 | 0...0 | N+1 == 0 mod 5 X maybe +--------+-----+-------+ Y never Per the Y rule, a 4 mod 5 segment is traversed when d=2,3. The following segment is then d+1=3,4 as lowest non-zero which in the X rule is not-traversed. Conversely in the Y rule not-traversed when d=0,1 which becomes d+1=1,2 which in the X rule is traversed. So exactly one of two consecutive 4 mod 5 and 0 mod 5 segments are traversed. XsegPred(X) or YsegPred = 1,1,0,1,0,1,1,0,1,0,1,1,0,1,1,... =1 at 0,1,3,5,6,8,10,11,13,14,16,18,19,21,23,25,... =cut # union # not in OEIS: 1,1,0,1,0,1,1,0,1,0,1,1,0,1,1,0,1,0,1,1,0,1,0,1,0,1,1,0,1,0,1,1,0,1,0,1,1 # not in OEIS: 0,1,3,5,6,8,10,11,13,14,16,18,19,21,23,25,26,28,30 =pod =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/MultipleRings.pm0000644000175000017500000014023513734026667020400 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=MultipleRings --lines # # math-image --wx --path=MultipleRings,ring_shape=polygon,step=5 --scale=50 --figure=ring --all # # FIXME: $y equal across bottom side centre ? package Math::PlanePath::MultipleRings; use 5.004; use strict; use Carp 'croak'; #use List::Util 'min','max'; *min = \&Math::PlanePath::_min; *max = \&Math::PlanePath::_max; # Math::Trig has asin_real() too, but it just runs the blob of code in # Math::Complex -- prefer libm use Math::Libm 'asin', 'hypot'; use vars '$VERSION', '@ISA'; @ISA = ('Math::PlanePath'); use Math::PlanePath; *_sqrtint = \&Math::PlanePath::_sqrtint; $VERSION = 129; use Math::PlanePath::Base::Generic 'is_infinite'; use Math::PlanePath::SacksSpiral; # uncomment this to run the ### lines # use Smart::Comments; use constant 1.02; # for leading underscore use constant _PI => 2*atan2(1,0); use constant figure => 'circle'; use constant n_frac_discontinuity => 0; use constant gcdxy_minimum => 0; use constant parameter_info_array => [{ name => 'step', display => 'Step', share_key => 'step_6_min3', type => 'integer', minimum => 0, default => 6, width => 3, description => 'How much longer each ring is than the preceding.', }, { name => 'ring_shape', display => 'Ring Shape', type => 'enum', default => 'circle', choices => ['circle','polygon'], choices_display => ['Circle','Polygon'], description => 'The shape of each ring, either a circle or a polygon of "step" many sides.', }, ]; sub turn_any_left { my ($self) = @_; # step == 0 is always straight ahead return ($self->{'step'} != 0); } sub turn_any_right { my ($self) = @_; # step=0 is always straight ahead # step=1 is never right return ($self->{'step'} >= 2); } { my @_UNDOCUMENTED__turn_any_right_at_n = (undef, # 0 undef, # 1 131, # 2 44, # 3 23, # 4 29, # 5 17, # 6 20, # 7 23); # 8 sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; $self->turn_any_right or return undef; if ($self->{'ring_shape'} eq 'polygon') { # step=8 24, 9, 10, 11 return $self->n_start - 1 + ($self->{'step'} < 9 ? 3*$self->{'step'} : $self->{'step'}); } return $self->n_start + ($self->{'step'} <= $#_UNDOCUMENTED__turn_any_right_at_n ? $_UNDOCUMENTED__turn_any_right_at_n[$self->{'step'}] : $self->{'step'} - 1); } } sub turn_any_straight { my ($self) = @_; # step=0 straight line # step=1 straight at N=2 # step=2 straight at N=2 return ($self->{'step'} <= 2 ? 1 : $self->{'ring_shape'} eq 'circle' ? 0 # never straight : 1); # ring_shape=polygon sides straight } #------------------------------------------------------------------------------ # Electricity transmission cable in sixes, with one at centre ? # 7 poppy # 19 hyacinth # 37 marigold # 61 cowslip # 127 bluebonnet # An n-gon of points many vertices has each angle # alpha = 2*pi/points # The radius r to a vertex, using a line perpendicular to the line segment # sin(alpha/2) = (1/2)/r # r = 0.5 / sin(pi/points) # And with points = d*step, starting from d=1 # r = 0.5 / sin(pi/(d*step)) # step==0 is a straight line y==0 x=0,1,2,..., anything else whole plane sub x_negative { my ($self) = @_; return ($self->{'step'} > 0); } *y_negative = \&x_negative; sub y_maximum { my ($self) = @_; return ($self->{'step'} == 0 ? 0 # step=0 always Y=0 : undef); } sub x_negative_at_n { my ($self) = @_; return ($self->{'step'} == 0 ? undef # no negatives : $self->{'step'} == 1 ? 3 : $self->n_start + int($self->{'step'}/4) + 1); } sub y_negative_at_n { my ($self) = @_; return ($self->{'step'} == 0 ? undef # no negatives : $self->{'step'} <= 2 ? 6 : $self->n_start + int($self->{'step'}/2) + 1); } sub sumxy_minimum { my ($self) = @_; return ($self->{'step'} == 0 ? 0 : undef); } sub sumabsxy_minimum { my ($self) = @_; # first point N=1 innermost ring my ($x,$y) = $self->n_to_xy($self->n_start); return $x; } *diffxy_minimum = \&sumxy_minimum; # step=0 X=0,Y=0 AbsDiff=0 # step=3 N=88 X=Y=5.3579957587697 ring of 24 is a multiple of 8 sub rsquared_minimum { my ($self) = @_; my $step = $self->{'step'}; if ($step <= 1) { # step=0 along X axis starting X=0,Y=0 # step=1 start at origin return 0; } # step=3 *--___ # circle | --__ o 0.5/r = sin60 = sqrt(3)/2 # | o __* / | \ r = 1/sqrt(3) # | ___-- / | \ r^2 = 1/3 # *-- *---------* # 1/2 # polygon # o 0.5/r = sin60 = sqrt(3)/2 # / | \ r = 1/sqrt(3) # / | \ r^2 = 1/3 # *---------* # 1/2 # if ($step == 3) { return ($self->{'ring_shape'} eq 'polygon' ? 3/4 : 1/3); } if ($step == 4) { # radius = sqrt(2)/2, rsquared=1/2 return 0.5; } # _numsides_to_r() returns 1, no need for a special case here # if ($step == 6) { # # hexagon # return 1; # } my $r; if ($step >= 6 || $self->{'ring_shape'} eq 'polygon') { $r = _numsides_to_r($step,_PI); } else { $r = $self->{'base_r'} + 1; } return $r*$r; } #------------------------------------------------------------------------------ # dx_minimum() etc # step <= 6 # R=base_r+d # theta = 2*$n * $pi / ($d * $step) # = 2pi/(d*step) # dX -> R*sin(theta) # -> R*theta # = (base_r+d)*2pi/(d*step) # -> 2pi/step # # step=5 across first ring # N=6 at X=base_r+2, Y=0 # N=5 at R=base_r+1 theta = 2pi/5 # X=(base_r+1)*cos(theta) # dX = base_r+2 - (base_r+1)*cos(theta) # # step=6 across first ring # base_r = 0.5/sin(_PI/6) - 1 # = 0.5/0.5 - 1 # = 0 # N=7 at X=base_r+2, Y=0 # N=6 at R=base_r+1 theta = 2pi/6 # X=(base_r+1)*cos(theta) # dX = base_r+2 - (base_r+1)*cos(theta) # = base_r+2 - (base_r+1)*0.5 # = 1.5*base_r + 1.5 # = 1.5 # # step > 6 # R = 0.5 / sin($pi / ($d*$step)) # diff = 0.5 / sin($pi / ($d*$step)) - 0.5 / sin($pi / (($d-1)*$step)) # -> 0.5 / ($pi / ($d*$step)) - 0.5 / ($pi / (($d-1)*$step)) # = 0.5 * ($d*$step) / $pi - 0.5 * (($d-1)*$step) / $pi # = step*0.5/pi * ($d - ($d-1)) # = step*0.5/pi # and extra from N=step to N=step+1 # * (1-cos(2pi/step)) # sub dx_minimum { my ($self) = @_; if ($self->{'step'} == 0) { return 1; # horizontal only } if ($self->{'step'} > 6) { return -1; # supremum, unless polygon and step even } if ($self->{'ring_shape'} eq 'polygon') { # step=3,4,5 return (-2*_PI()) / $self->{'step'}; } else { return (-2*_PI()) / $self->{'step'}; } } sub dx_maximum { my ($self) = @_; return ($self->{'step'} == 0 ? 1 # horizontal only : $self->{'step'} == 5 ? $self->{'base_r'}+2 - ($self->{'base_r'}+1)*cos(2*_PI()/5) : $self->{'step'} == 6 ? 1.5 : $self->{'step'} <= 6 ? (2*_PI()) / $self->{'step'} # step > 6, between rings : (0.5/_PI()) * $self->{'step'} * (2-cos(2*_PI()/$self->{'step'}))); } sub dy_minimum { my ($self) = @_; return ($self->{'step'} == 0 ? 0 # horizontal only : $self->{'step'} <= 6 ? (-2*_PI) / $self->{'step'} : -1); # supremum } sub dy_maximum { my ($self) = @_; return ($self->{'step'} == 0 ? 0 # horizontal only : $self->{'step'} <= 6 ? (2*_PI) / $self->{'step'} : 1); # supremum } sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; return ($self->{'step'} == 0 ? (1,0) # E only : ()); # unlimited } sub absdx_minimum { my ($self) = @_; my $step = $self->{'step'}; if ($step == 0) { return 1; # horizontal dX=1 always } if ($self->{'ring_shape'} eq 'polygon') { if ($step % 2) { return 0; # polygons with odd num sides have left vertical dX=0 } else { return sin(_PI/2 /$step); } # if ($self->{'step'} % 2 == 1) { # # return 0; # } else { # return abs($self->dx_minimum); # } } return 0; } sub absdy_minimum { my ($self) = @_; my $step = $self->{'step'}; if ($step == 0) { return 0; # horizontal dX=1 always } if ($self->{'ring_shape'} eq 'polygon') { if ($step == 3) { return 0.5; # sin(30 degrees) innermost polygon } my $frac = ($step+2) % 4; if ($frac == 3) { $frac = 1; } return sin(_PI/2 * $frac/$step); } return 0; } sub dsumxy_minimum { my ($self) = @_; return ($self->{'step'} == 0 ? 1 # horizontal only : -1); # infimum } use constant dsumxy_maximum => 1; # FIXME: for step=1 is there a supremum at 9 or thereabouts? # and for other step<6 too? # 2*dXmax * sqrt(2) ? sub ddiffxy_minimum { my ($self) = @_; return ($self->{'step'} == 0 ? 1 # horizontal only : $self->{'step'} <= 6 ? $self->dx_minimum * sqrt(2) : -1); # infimum } sub ddiffxy_maximum { my ($self) = @_; return ($self->{'step'} == 0 ? 1 # horizontal only : $self->{'step'} <= 6 ? $self->dx_maximum * sqrt(2) : 1); # supremum } #------------------------------------------------------------------------------ # dir_maximum_dxdy() # polygon step many sides # start at vertical angle 1/4 plus 0.5/step, then k*1/step each side # a = 1/4 + (k+1/2)/step # = (1 + 4(k+1/2)/step) / 4 # = ((4*k+2)/step + 1) / 4 # # maximum want 1 > a >= 1-1/step # 1/4 + (k+1/2)/step >= 1-1/step # (k+1/2)/step >= 3/4-1/step # k+1/2 >= 3*step/4-1 # k >= 3*step/4-3/2 # k >= (3*step-6)/4 # k = ceil((3*step-6)/4) # = floor((3*step-6)/4 + 3/4) # = floor((3*step-3)/4) # high side # 1/4 + (k+1/2)/step < 1 # (k+1/2)/step < 3/4 # k+1/2 < 3*step/4 # k < (3*step-2)/4 # k = floor((3*step-2)/4 - 1/4) # = floor((3*step-3)/4) # # so # a = 1/4 + (floor((3*step-3)/4) + 1/2)/step # = (1 + 4*(floor((3*step-3)/4) + 1/2)/step) / 4 # = ((floor((3*step-3)/4)*4 + 2)/step + 1) / 4 # step=4 a = 7/8 # step=5 a = 19/20 # step=6 a = 5/6 # step=7 a = 25/28 # step=8 a = 15/16 # step=10 a = 9/10 # return (int((3*$step-3)/4) * 4 + 2)/$step + 1; # is full circle less 4,3,2,1 as step-2 mod 4 # # sub dir4_maximum { # my ($self) = @_; # if ($self->{'step'} == 0) { # return 0; # horizontal only # } # my $step = $self->{'step'}; # if ($self->{'ring_shape'} eq 'polygon') { # return (($step-2)%4 - 4)/$step + 4; # } # return 4; # supremum, full circle # } # want a >= 1 # 1/4 + (k+1/2)/step >= 1 # (k+1/2)/step >= 3/4 # k+1/2 >= 3*step/4 # k >= 3*step/4 - 1/2 # k >= (3*step-2)/4 # k = ceil((3*step-2)/4) # = floor((3*step-2)/4 + 3/4) # = floor((3*step+1)/4) # min_a = 1/4 + (floor((3*step+1)/4) + 1/2)/step - 1 # = (1 + 4*(floor((3*step+1)/4) + 1/2)/step ) / 4 # = ((4*floor((3*step+1)/4) + 2)/step + 1) / 4 - 1 # = ((floor((3*step+1)/4)*4 + 2)/step - 3) / 4 # return (int((3*$step+1)/4) * 4 + 2)/$step - 3; # is 0,1,2,3 as step-2 mod 4 # return (($step-2) % 4) / $step; # # but last of ring across to first of next may be shallower # # sub dir4_minimum { # my ($self) = @_; # my $step = $self->{'step'}; # if ($self->{'ring_shape'} eq 'polygon') { # if ($step % 4 != 2) { # polygon step=2mod4 includes horizontal ... # my ($dx,$dy) = $self->n_to_dxdy($self->{'step'}); # return min (atan2($dy,$dx) * (2/_PI), # (($step-2) % 4) / $step); # } # # } # return 0; # horizontal # } sub dir_minimum_dxdy { my ($self) = @_; my $step = $self->{'step'}; if ($self->{'ring_shape'} eq 'polygon') { return $self->n_to_dxdy($step == 9 ? 9 : int((3*$step+5)/4)); } return (1,0); # horizontal } sub dir_maximum_dxdy { my ($self) = @_; if ($self->{'step'} == 0) { return (1,0); # step=0 horizontal always } if ($self->{'ring_shape'} eq 'polygon') { my $step = $self->{'step'}; return $self->n_to_dxdy(int((3*$step+1)/4)); # 1 before the minimum # # just before 3/4 way around, then half back .... # # sides side # # ----- ---- # # 3 1 # # 4 2 # # 5 3 # # 6 3 # # 7 4 # # 8 5 # # 9 6 # # 10 6 # return _circlefrac_to_xy (1, int((3*$step-3)/4), $step, _PI); } return (0,0); # supremum, full circle } #------------------------------------------------------------------------------ sub new { ### MultipleRings new() ... my $self = shift->SUPER::new(@_); my $step = $self->{'step'}; $step = $self->{'step'} = (! defined $step ? 6 # default : $step < 0 ? 0 # minimum : $step); ### $step my $ring_shape = ($self->{'ring_shape'} ||= 'circle'); if (! ($ring_shape eq 'circle' || $ring_shape eq 'polygon')) { croak "Unrecognised ring_shape option: ", $ring_shape; } if ($step < 3) { # polygon shape only for step >= 3 $ring_shape = $self->{'ring_shape'} = 'circle'; } if ($ring_shape eq 'polygon') { ### polygon ... if ($step == 6) { ### 0.5/sin(PI/6)=1 exactly ... $self->{'base_r'} = 1; } elsif ($step == 3) { ### 0.5/sin(PI/3)=sqrt(3)/3 ... $self->{'base_r'} = sqrt(3)/3; } else { $self->{'base_r'} = 0.5/sin(_PI/$step); } } elsif ($step == 6) { ### 0.5/sin(PI/6) = 1 exactly ... $self->{'base_r'} = 0; } elsif ($step == 4) { ### 0.5/sin(PI/4) = sqrt(2)/2 ... $self->{'base_r'} = sqrt(2)/2 - 1; } elsif ($step == 3) { ### 0.5/sin(PI/3) = sqrt(3)/3 ... $self->{'base_r'} = sqrt(3)/3 - 1; } elsif ($step < 6) { ### sin: $step>1 && sin(_PI/$step) $self->{'base_r'} = ($step > 1 && 0.5/sin(_PI/$step)) - 1; } ### base r: $self->{'base_r'} return $self; } # with N decremented # d = [ 1, 2, 3, 4, 5 ] # N = [ 0, 1, 3, 6, 10 ] # # N = (1/2 d^2 - 1/2 d) # = (1/2*$d**2 - 1/2*$d) # = ((0.5*$d - 0.5)*$d) # = 0.5*$d*($d-1) # # d = 1/2 + sqrt(2 * $n + 1/4) # = 0.5 + sqrt(2*$n + 0.25) # = [ 1 + 2*sqrt(2n + 1/4) ] / 2 # = [ 1 + sqrt(8n + 1) ] / 2 # # (d+1)d/2 - d(d-1)/2 # = [ (d^2 + d) - (d^2-d) ] / 2 # = [ d^2 + d - d^2 + d ] / 2 # = 2d/2 = d # # radius # step > 6 1 / (2 * sin(pi / ($d*$step)) # step <= 6 Rbase + d # # usual polygon formula R = a / 2*sin(pi/n) # cf inner radius r = a / 2*tan(pi/n) # along chord # # polygon horizontal when a=1 # 1/4 + (k+1/2)/step = 1 # (k+1/2)/step = 3/4 # k+1/2 = 3*step/4 # k = 3*step/4 - 1/2 # k = ()/4 # 4*k = 3*step-2 # and when a=1/2 # 1/4 + (k+1/2)/step = 1/2 # (k+1/2)/step = 1/4 # k+1/2 = step/4 # 4*k+2 = step # 1/2 / R = sin(2pi/sides) # 1/2 / (R^2 - 1/4) = tan(2pi/sides) # f(x) = 1/2 / R - sin(2pi/sides) = $f # f'(x) = -1/2 / R^2 - cos(2pi/sides) = $slope # $r-$f/$slope better approx # (1/2 / R - sin(2pi/sides)) / (-1/2 / R^2 - cos(2pi/sides)) # = (R/2 - R^2 sin(2pi/sides)) / (-1/2 - R^2 * cos(2pi/sides)) sub n_to_xy { my ($self, $n) = @_; ### MultipleRings n_to_xy(): "n=$n step=$self->{'step'} shape=$self->{'ring_shape'}" # "$n<1" separate test from decrement so as to warn on undef # don't have anything sensible for infinity, and _PI / infinity would # throw a div by zero if ($n < 1) { return; } if (is_infinite($n)) { return ($n,$n); } $n -= 1; ### decremented n: $n my $step = $self->{'step'}; if (! $step) { ### step==0 goes along X axis ... return ($n, 0); } my $d = int((_sqrtint(8*$n/$step + 1) + 1) / 2); ### d frac: (sqrt(int(8*$n) + 1) + 1) / 2 ### d int: "$d" ### base: ($d*($d-1)/2).'' ### next base: (($d+1)*$d/2).'' ### assert: $n >= ($d*($d-1)/2) ### assert: $n < ($step * ($d+1) * $d / 2) $n -= $d*($d-1)/2 * $step; ### n remainder: "$n" ### assert: $n >= 0 ### assert: $n < $d*$step my $zero = $n * 0; if (ref $n) { if ($n->isa('Math::BigInt')) { $n = Math::PlanePath::SacksSpiral::_bigfloat()->new($n); } elsif ($n->isa('Math::BigRat')) { $n = $n->as_float; } if ($n->isa('Math::BigFloat')) { ### bigfloat ... $d = Math::BigFloat->new($d); } } my $pi = _pi($n); ### $pi # my $base_r = $self->{'base_r'}; # $base_r = Math::BigFloat->new($base_r); { my $numsides; my $r; if ($self->{'ring_shape'} eq 'circle') { ### circle ... $numsides = $d * $step; if ($step > 6) { $r = 0.5 / sin($pi / $numsides); } else { my $base_r; if ($step == 6) { $base_r = 0; # exactly } elsif ($step == 4) { ### 0.5/sin(PI/4)=sqrt(2)/2 ... $base_r = sqrt(0.5 + $zero) - 1; # sqrt() instead of sin() } elsif ($step == 3) { ### 0.5/sin(PI/3)=sqrt(3)/3 ... $base_r = sqrt(3 + $zero)/3 - 1; # sqrt() instead of sin() } elsif ($step == 1) { $base_r = -1; # so initial d=1 at $r=0 } else { $base_r = 0.5/sin($pi/$step) - 1; } $r = $base_r + $d; } } else { ### polygon ... $numsides = $step; my $base_r = _numsides_to_r($step,$pi); if ($step > 6) { $r = $base_r*$d; } else { $r = $base_r + ($d-1)/cos($pi/$step); } $n /= $d; } ### n with frac: $n # numsides even N > numsides/2 # numsides odd N >= (numsides+1)/2 = ceil(numsides/2) my $y_neg; if (2*$n >= $numsides) { $n = $numsides - $n; $y_neg = 1; } my $x_neg; my $xy_transpose; if ($numsides % 2 == 0) { if (4*$n >= $numsides) { $n = $numsides/2 - $n; $x_neg = 1; } if ($numsides % 4 == 0 && 8*$n >= $numsides) { $n = $numsides/4 - $n; $xy_transpose = 1; } } my $side = int($n); $n -= $side; ### $side my ($x, $y) = _circlefrac_to_xy($r, $side, $numsides, $pi); if ($n) { # fractional n offset into side ... my ($to_x, $to_y); $side += 1; if (2*$side == $numsides+1) { # vertical at left, so X unchanged Y negate $to_x = $x; $to_y = - $y; } elsif (4*$side == $numsides+2 || 4*$side == 3*$numsides-2) { # horizontal at top or bottom, so Y unchanged X negate $to_x = - $x; $to_y = $y; } else { ($to_x, $to_y) = _circlefrac_to_xy($r, $side, $numsides, $pi); } ### $side ### $r ### from: "$x, $y" ### to: "$to_x, $to_y" # If vertical or horizontal then don't apply the proportions since the # two parts $x*$n and $to_x*(1-$n) can round off giving the sum != to # the original $x. if ($to_x != $x) { $x = $x*(1-$n) + $to_x*$n; } if ($to_y != $y) { $y = $y*(1-$n) + $to_y*$n; } } if ($xy_transpose) { ($x,$y) = ($y,$x); } if ($x_neg) { $x = -$x; } if ($y_neg) { $y = -$y; } ### final: "x=$x y=$y" return ($x, $y); } # { # # && $d != 0 # watch out for overflow making d==0 ?? # # # my $d_step = $d*$step; # my $r = ($step > 6 # ? 0.5 / sin($pi / $d_step) # : $base_r + $d); # ### r: "$r" # # my $n2 = 2*$n; # # if ($n2 == int($n2)) { # if (($n2 % $d_step) == 0) { # ### theta=0 or theta=pi, exactly on X axis ... # return ($n ? -$r : $r, # n remainder 0 means +ve X axis, non-zero -ve # 0); # } # if (($d_step % 2) == 0) { # my $n2sub = $n2 - $d_step/2; # if (($n2sub % $d_step) == 0) { # ### theta=pi/2 or theta=3pi/2, exactly on Y axis ... # return (0, # $n2sub ? -$r : $r); # } # } # } # # my $theta = $n2 * $pi / $d_step; # # ### theta frac: (($n - $d*($d-1)/2)/$d).'' # ### theta: "$theta" # # return ($r * cos($theta), # $r * sin($theta)); # } } # $side is 0 to $numsides-1 sub _circlefrac_to_xy { my ($r, $side, $numsides, $pi) = @_; ### _circlefrac_to_xy(): "r=$r side=$side numsides=$numsides pi=$pi" if (2*$side == $numsides) { ### 180-degrees, so X=R, Y=0 ... return (-$r, 0); } if (4*$side == $numsides) { ### 90-degrees, so X=0, Y=R ... return (0, $r); } if (6*$side == $numsides) { ### 60-degrees, so X=R/2, Y=sqrt(3)/2*R ... return ($r / 2, $r * sqrt(3 + $r*0) / 2); } if (8*$side == $numsides) { ### 45-degrees, so X=Y=R/sqrt(2) ... my $x = $r / sqrt(2 + $r*0); return ($x, $x); } # my $two_pi = (ref $r && $r->isa('Math::BigFloat') # ? 2*Math::BigFloat->bpi; # : 2*_PI); # # if (2*$side == $numsides+1) { # ### first below X axis ... # my $theta = 2*$pi * ($side-1)/$numsides; # return ($r * cos($theta), # - $r * sin($theta)); # } # if (4*$side == $numsides+1) { # ### first past Y axis ... # my $theta = 2*$pi * ($side-1)/$numsides; # return (- $r * cos($theta), # $r * sin($theta)); # } ### general case ... my $theta = 2 * $pi * $side/$numsides; return (cos($theta) * $r, sin($theta) * $r); } # my $numsides = $step; # if ($self->{'ring_shape'} eq 'polygon') { # $n /= $d; # my $base_r = _numsides_to_r($step,$pi); # if ($step > 6) { # $r = $base_r*$d; # } else { # $r = $base_r + ($d-1)/cos($pi/$step); # } # } else { # $numsides *= $d; # if ($step > 6) { # $r = _numsides_to_r($numsides,$pi); # } else { # $r = _numsides_to_r($step,$pi) + $d; # } # } # my $side = int($n); # $n -= $side; sub _numsides_to_r { my ($numsides, $pi) = @_; if ($numsides == 3) { return sqrt(0.75 + $pi*0); } if ($numsides == 4) { return sqrt(0.5 + $pi*0); } if ($numsides == 6) { return 1 + $pi*0; } return 0.5 / sin($pi/$numsides); } # for step=4 # R = sqrt(2)/2 + d # R^2 = (sqrt(2)/2 + d)^2 # = 2/4 + 2*sqrt(2)/2*d + d^2 # = 1/2 + d*sqrt(2) + d^2 # not an integer # sub n_to_rsquared { my ($self, $n) = @_; ### MultipleRings n_to_rsquared(): "n=$n" if ($n < 1) { return undef; } if (is_infinite($n)) { return $n; } if (defined (my $r = _n_to_radius_exact($self,$n))) { return $r*$r; } if ($self->{'step'} == 1) { # $n < 4 covered by _n_to_radius_exact() if ($n >= 4 && $n < 7) { # triangle numsides=3 # N=4 at X=2, Y=0 # N=5 at X=-1, Y=sqrt(3) # N=4+f at X=2-3*f Y=f*sqrt(3) # R^2 = (2-3f)^2 + 3*f^2 # = 4-12f+9*f^2 + 3*f^2 # = 4-12f+12*f^2 # = 4*(1 - 3f + 3*f^2) # = 4 - 6*(2*f) + 3*(2*f)^2 # f=1/2 is R^2 = 1 # N=5+f at X=-1 Y = sqrt(3)*(1-2*f) # R^2 = 1 + 3*(1-2*f)^2 # = 1 + 3 - 3*4*f + 3*4*f^2 # = 4 - 12*f + 12*f^2 # = 4 - 12*(f - f^2) # = 4 - 12*f*(1 - f) $n -= int($n); return 4 - 12*$n*(1-$n); } if ($n >= 7 && $n < 11) { ### square numsides=4 ... # X=3-3*f Y=3*f # R^2 = (3-3*f)^2 + (3*f)^2 # = 9*[ (1-f)^2 + f^2) ] # = 9*[ 1 - 2f + f^2 + f^2) ] # = 9*[ 1 - 2f + 2f^2 ] # = 9*[ 1 - 2(f - f^2) ] # = 9 - 18*f*(1 - f) # eg f=1/2 R^2 = (sqrt(2)/2*3)^2 = 2/4*9 = 9/2 $n -= int($n); return 9 - 18*$n*(1-$n); } if ($n >= 16 && $n < 22) { ### hexagon numsides=6 ... # X=5 Y=0 to X=5*1/2 Y=5*sqrt(3)/2 # R^2 = (5 - 5/2*f)^2 + (5*sqrt(3)/2*f)^2 # = 25 - 25*f + 25*f^2 # = 25 - 25*f*(1-f) # eg f=1/2 R^2 = 18.75 # or f=1/5 R^2 = 21 exactly, though 1/5 not exact in binary floats $n -= int($n); return 25 - 25*$n*(1-$n); } # other numsides don't have sin(pi/numsides) an integer or sqrt so # aren't an exact R^2 } # ENHANCE-ME: step=1 various exact values for ring of 4 and ring of 6 return $self->SUPER::n_to_rsquared($n); } sub n_to_radius { my ($self, $n) = @_; ### n_to_radius(): $n if ($n < 1) { return undef; } if (is_infinite($n)) { return $n; } if (defined (my $r = _n_to_radius_exact($self,$n))) { return $r; } return sqrt($self->n_to_rsquared($n)); # return $self->SUPER::n_to_radius($n); } # step=6 shape=polygon exact integer for some of second ring too # sub n_to_trsquared { # my ($self, $n) = @_; # ### MultipleRings n_to_rsquared(): "n=$n" # } sub _n_to_radius_exact { my ($self, $n) = @_; ### _n_to_radius_exact(): "n=$n step=$self->{'step'}" if ($n < 1) { return undef; } if (is_infinite($n)) { return $n; } my $step = $self->{'step'}; if ($step == 0) { return $n - 1; # step=0 goes along X axis starting X=0,Y=0 } if ($step == 1) { if ($n < 4) { if ($n < 2) { return 0; # 0,0 only, no jump across to next ring } $n -= int($n); return abs(1-2*$n); } if ($n == int($n)) { ### step=1 radius=integer steps for integer N ... return _n0_to_d($self,$n-1) - 1; } my $two_n = 2*$n; if ($two_n == 9 || $two_n == 11 || $two_n == 13) { # N=4.5 at X=1/2 Y=sqrt(3)/2 R^2 = 1/4 + 3/4 = 1 exactly # N=5.5 at X=-1, Y=0 so R^2 = 1 exactly # N=6.5 same as N=4.5 return 1; } } elsif ($step == 6) { if ($n == int($n)) { # step=6 circle all integer N has exact integer radius # step=6 polygon only innermost ring N<=6 exact integer radius if ($self->{'ring_shape'} eq 'circle' || $n <= 6) { # ring_shape=polygon return _n0_to_d($self,$n-1); } } } ### no exact radius ... return undef; } sub _n0_to_d { my ($self, $n) = @_; return int((_sqrtint(8*$n/$self->{'step'} + 1) + 1) / 2); } sub _d_to_n0base { my ($self, $d) = @_; return $d*($d-1)/2 * $self->{'step'}; } # From above # r = 0.5 / sin(pi/(d*step)) # # sin(pi/(d*step)) = 0.5/r # pi/(d*step) = asin(1/(2*r)) # 1/d * pi/step = asin(1/(2*r)) # d = pi/(step*asin(1/(2*r))) # # r1 = 0.5 / sin(pi/(d*step)) # r2 = 0.5 / sin(pi/((d+1)*step)) # r2 - r1 = 0.5 / sin(pi/(d*step)) - 0.5 / sin(pi/((d+1)*step)) # r2-r1 >= 1 when step>=7 ? sub _xy_to_d { my ($self, $x, $y) = @_; ### _xy_to_d(): "x=$x y=$y" my $r = hypot ($x, $y); if ($r < 0.5) { ### r smaller than 0.5 ring, treat as d=1 # 1/(2*r) could be div-by-zero # or 1/(2*r) > 1 would be asin()==-nan return 1; } my $two_r = 2*$r; if (is_infinite($two_r)) { ### 1/inf is a divide by zero, avoid that ... return $two_r; } ### $r my $step = $self->{'step'}; if ($self->{'ring_shape'} eq 'polygon') { my $theta_frac = _xy_to_angle_frac($x,$y); $theta_frac -= int($theta_frac*$step) / $step; # modulo 1/step my $r = hypot ($x, $y); my $alpha = 2*_PI/$step; my $theta = 2*_PI * $theta_frac; ### $r ### x=r*cos(theta): $r*cos($theta) ### y=r*sin(theta): $r*sin($theta) my $p = $r*cos($theta) + $r*sin($theta) * sin($alpha/2)/cos($alpha/2); ### $p ### base_r: $self->{'base_r'} ### p - base_r: $p - $self->{'base_r'} if ($step >= 6) { return $p / $self->{'base_r'}; } else { return ($p - $self->{'base_r'}) * cos(_PI/$step) + 1; } } if ($step > 6) { ### d frac by asin: _PI / ($step * asin(1/$two_r)) return _PI / ($step * asin(1/$two_r)); } else { # $step <= 6 ### d frac by base: $r - $self->{'base_r'} return $r - $self->{'base_r'}; } } sub xy_to_n { my ($self, $x, $y) = @_; ### MultipleRings xy_to_n(): "$x, $y step=$self->{'step'} shape=$self->{'ring_shape'}" my $n; my $step = $self->{'step'}; if ($step == 0) { # step==0 $n = int ($x + 1.5); } else { my $theta_frac = _xy_to_angle_frac($x,$y); ### $theta_frac ### assert: (0 <= $theta_frac && $theta_frac < 1) || $theta_frac!=$theta_frac my $d; if ($self->{'ring_shape'} eq 'polygon') { $n = int($theta_frac*$step); $theta_frac -= $n/$step; ### theta modulo 1/step: $theta_frac ### $n my $r = hypot ($x, $y); my $alpha = 2*_PI/$step; my $theta = 2*_PI * $theta_frac; ### $r ### so x=r*cos(theta): $r*cos($theta) ### so y=r*sin(theta): $r*sin($theta) my $pi = _PI; my $p = $r*cos($theta) + $r*sin($theta) * sin($alpha/2)/cos($alpha/2); my $base_r = Math::PlanePath::MultipleRings::_numsides_to_r($step,$pi); ### $p ### $base_r if ($step > 6) { $d = $p / $base_r; } else { $d = ($p - $base_r) * cos($pi/$step) + 1; } ### d frac: $d $d = int($d+0.5); ### $d ### cf _xy_to_d(): _xy_to_d($self,$x,$y) my $f = ($p == 0 ? 0 : $r*sin($theta) / ($p*sin($alpha))); $n = int(($n+$f)*$d + 0.5); ### e: $r*sin($theta) * sin($alpha/2)/cos($alpha/2) ### $f ### $n } else { $d = int(_xy_to_d($self,$x,$y) + 0.5); ### $d $n = int (0.5 + $theta_frac * $d*$step); if ($n >= $d*$step) { $n = 0; } } ### n within ring: $n ### n ring start: _d_to_n0base($self,$d) + 1 $n += _d_to_n0base($self,$d) + 1; ### $d ### d base: 0.5*$d*($d-1) ### d base M: $step * 0.5*$d*($d-1) ### $theta_frac ### theta offset: $theta_frac*$d ### $n } ### trial n: $n if (my ($nx, $ny) = $self->n_to_xy($n)) { ### nxy: "nx=$nx ny=$ny hypot=".hypot($x-$nx,$y-$ny) ### cf orig xy: "x=$x y=$y" if (hypot($x-$nx, $y-$ny) <= 0.5) { return $n; } } return undef; } # ENHANCE-ME: step>=3 small rectangles around 0,0 don't cover any pixels # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### MultipleRings rect_to_n_range(): "$x1,$y1, $x2,$y2 step=$self->{'step'}" my $zero = ($x1<0) != ($x2<0) || ($y1<0) != ($y2<0); my $step = $self->{'step'}; my ($r_lo, $r_hi) = Math::PlanePath::SacksSpiral::_rect_to_radius_range ($x1,$y1, $x2,$y2); ### $r_lo ### $r_hi if (is_infinite($r_hi)) { return (1,$r_hi); } if ($r_hi < 1) { $r_hi = 1; } if ($self->{'ring_shape'} eq 'polygon') { $r_hi /= cos(_PI/$self->{'step'}); ### poly increase r_hi: $r_hi } my ($d_lo, $d_hi); if ($self->{'ring_shape'} eq 'polygon') { if ($step >= 6) { $d_lo = $r_lo / $self->{'base_r'}; $d_hi = $r_hi / $self->{'base_r'}; } else { $d_lo = ($r_lo - $self->{'base_r'}) * cos(_PI/$step) + 1; $d_hi = ($r_hi - $self->{'base_r'}) * cos(_PI/$step) + 1; } } else { if ($step > 6) { $d_lo = ($r_lo > 0 ? _PI / ($step * asin(0.5/$r_lo)) : 0); $d_hi = _PI / ($step * asin(0.5/$r_hi)); } else { $d_lo = $r_lo - $self->{'base_r'}; $d_hi = $r_hi - $self->{'base_r'}; } } ### $d_lo ### $d_hi $d_lo = int($d_lo - 1); $d_hi = int($d_hi + 2); if ($d_lo < 1) { $d_lo = 1; } if ($step) { # start of ring is N= 0.5*$d*($d-1) * $step + 1 ### n_lo: 0.5*$d_lo*($d_lo-1) * $step + 1 ### n_hi: 0.5*$d_hi*($d_hi+1) * $step return ($d_lo*($d_lo-1)/2 * $step + 1, $d_hi*($d_hi+1)/2 * $step); } else { # $step == 0 return ($d_lo, $d_hi); } # # if x1,x2 pos and neg then 0 is covered and it's the minimum # # ENHANCE-ME: might be able to be a little tighter on $d_lo # my $d_lo = ($zero # ? 1 # : max (1, -2 + int (_xy_to_d ($self, # min($x1,$x2), # min($y1,$y2))))); # my $d_hi = 1 + int (_xy_to_d ($self, # max($x1,$x2), # max($y1,$y2))); # ### $d_lo # ### $d_hi # if ((my $step = $self->{'step'})) { # # start of ring is N= 0.5*$d*($d-1) * $step + 1 # ### n_lo: 0.5*$d_lo*($d_lo-1) * $step + 1 # ### n_hi: 0.5*$d_hi*($d_hi+1) * $step # return ($d_lo*($d_lo-1)/2 * $step + 1, # $d_hi*($d_hi+1)/2 * $step); # } else { # # $step == 0 # return ($d_lo, $d_hi); # } } #------------------------------------------------------------------------------ # generic # _xy_to_angle_frac() returns the angle of X,Y as a fraction 0 <= angle < 1 # measured anti-clockwise around from the X axis. # sub _xy_to_angle_frac { my ($x, $y) = @_; # perlfunc.pod warns atan2(0,0) is implementation dependent. The C99 spec # is atan2(+/-0, -0) returns +/-pi, both of which would come out 0.5 here. # Prefer 0 for any +/-0,+/-0. if ($x == 0 && $y == 0) { return 0; } my $frac = atan2($y,$x) * (0.5 / _PI); ### $frac if ($frac < 0) { $frac += 1; } elsif ($frac >= 1) { $frac -= 1; } return $frac; } # return pi=3.14159 etc, inheriting precision etc from $n if it's a BigFloat # or other overload sub _pi { my ($n) = @_; if (ref $n) { if ($n->isa('Math::BigFloat')) { my $digits; if (defined($digits = $n->accuracy)) { ### n accuracy ... } elsif (defined($digits = $n->precision)) { ### n precision ... $digits = -$digits + 1; } elsif (defined($digits = Math::BigFloat->accuracy)) { ### global accuracy ... } elsif (defined($digits = Math::BigFloat->precision)) { ### global precision ... $digits = -$digits + 1; } else { ### div_scale ... $digits = Math::BigFloat->div_scale+1; } ### $digits $digits = max (1, $digits); return Math::BigFloat->bpi($digits); } ### other overload n class: ref $n my $zero = $n * 0; return 2*atan2($zero,1+$zero); } return _PI; } 1; __END__ =for stopwords Ryde Math-PlanePath Pentagonals Nring ie OEIS spacings numsides Nrem pronic pronics RSquared =head1 NAME Math::PlanePath::MultipleRings -- rings of multiples =head1 SYNOPSIS use Math::PlanePath::MultipleRings; my $path = Math::PlanePath::MultipleRings->new (step => 6); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path puts points on concentric rings. Each ring has "step" many points more than the previous and the first is also "step". For example with the default step==6, 24 23 innermost ring 6 25 22 next ring 12 10 next ring 18 26 11 9 21 ... ringnum*step 27 12 3 2 8 20 38 28 13 4 1 7 19 37 <- Y=0 29 14 5 6 18 36 30 15 17 35 16 31 24 32 33 ^ X=0 X,Y positions are not integers, except on the axes. The innermost ring like N=1to6 above has points 1 unit apart. Subsequent rings are a unit chord or unit radial, whichever ensures no overlap. step <= 6 unit spacing radially step >= 6 unit chords around the rings For step=6 the two spacings are the same. Unit radial spacing ensures the X axis points N=1,7,19,37,etc shown above are 1 unit apart. Unit chord spacing ensures adjacent points such as N=7,8,0,etc don't overlap. The layout is similar to the various spiral paths of corresponding step. For example step=6 is like the C, but rounded out to circles instead of a hexagonal grid. Similarly step=4 the C or step=8 the C. The step parameter is also similar to the C with the rows stretched around circles, but C starts from a 1-wide initial row whereas for C here the first is "step" many. =head2 X Axis The starting Nring=1,7,19,37 etc on the X axis for the default step=6 is S<6*d*(d-1)/2 + 1>, counting the innermost ring as d=1. In general Nring is a multiple of the Xtriangular numbers d*(d-1)/2, plus 1, Nring = step*d*(d-1)/2 + 1 XThis is the centred polygonal numbers, being the cumulative count of points making concentric polygons or rings in the style of this path. Straight line radials further around arise from adding multiples of d, so for example in step=6 shown above the line N=3,11,25,etc is S. Multiples k*d with kE=step give lines which are in between the base ones from the innermost ring. =head2 Step 1 For step=1 the first ring is 1 point and each subsequent ring has 1 further point. =cut # math-image --path=MultipleRings,step=1 --expression='i<29?i:0' --output=numbers --size=80x25 =pod 24 23 18 12 17 25 8 13 5 19 9 3 1 2 4 7 11 16 22 <- Y=0 14 6 26 10 20 15 21 28 27 ^ -5 -4 -3 -2-1 X=0 1 2 3 4 5 6 The rings are polygon radius N values ------------ ------ -------- single point 0 1 two points 1 2, 3 triangle 2 4, 5, 6 square 3 7, 8, 9,10 pentagon 4 11,12,13,14,15 hexagon 5 16,17,18,19,20,21 etc The X axis as described above is the triangular numbers plus 1, ie. S. =head2 Step 2 For step=2 the arrangement is roughly =cut # math-image --path=MultipleRings,step=2 --expression='i<43?i:0' --output=numbers --size=80x25 =pod 34 35 33 24 15 23 36 25 22 32 16 9 4 8 14 37 26 17 10 5 2 1 3 7 13 21 31 18 11 6 12 20 38 27 30 42 28 19 29 39 41 40 The pattern is similar to the C (see L). In C each spiral loop is 2 more points than the previous the same as here, but the positioning differs. Here the X axis is the pronic numbers and the squares are to the left, whereas in C rotated around to squares on X axis and pronics to the left. =head2 Ring Shape Option C 'polygon'> puts the points on concentric polygons of "step" many sides, so each concentric polygon has 1 more point on each of its sides than the previous polygon. For example step=4 gives 4-sided polygons, ie. diamonds, 16 / \ ring_shape=>'polygon', step=>4 17 7 15 / / \ \ 18 8 2 6 14 / / / \ \ \ 19 9 3 1 5 13 \ \ \ / / / 20 10 4 12 24 \ \ / / 21 11 23 \ / 22 The polygons are scaled to keep points 1 unit apart. For stepE=6 this means 1 unit apart sideways. step=6 is in fact a honeycomb grid where each points is 1 away from all six of its neighbours. For step=3, 4 and 5 the polygon sides are 1 apart radially, as measured in the centre of each side. This makes points a little more than 1 apart along the sides. Squeezing them up to make the closest points exactly 1 apart is possible, but may require iterating a square root for each ring. step=3 squeezed down would in fact become a variable spacing with successively four close then one wider. For step=2 and step=1 in the current code the default circle shape is used. Should that change? Is there a polygon style with 2 sides or 1 side? The polygon layout is only a little different from a circle, but it lines up points on the sides and that might help show a structure for some sets of points plotted on the path. =head2 Step 3 Pentagonals For step=3 the pentagonal numbers 1,5,12,22,etc, P(k) = (3k-1)*k/2, are a radial going up to the left, and the second pentagonal numbers 2,7,15,26, S(k) = (3k+1)*k/2 are a radial going down to the left, respectively 1/3 and 2/3 the way around the circles. As described in L, those P(k) and preceding P(k)-1, P(k)-2, and S(k) and preceding S(k)-1, S(k)-2 are all composites, so plotting the primes on a step=3 C has two radial gaps where there's no primes. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::MultipleRings-Enew (step =E $integer)> =item C<$path = Math::PlanePath::MultipleRings-Enew (step =E $integer, ring_shape =E $str)> Create and return a new path object. The C parameter controls how many points are added in each circle. It defaults to 6 which is an arbitrary choice and the suggestion is to always pass in a desired count. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. C<$n> can be any value C<$n E= 1> and fractions give positions on the rings in between the integer points. For C<$n < 1> the return is an empty list since points begin at 1. Fractional C<$n> currently ends up on the circle arc between the integer points. Would straight line chords between them be better, reflecting the unit spacing of the points? Neither seems particularly important. =item C<$n = $path-Exy_to_n ($x,$y)> Return an integer point number for coordinates C<$x,$y>. Each integer N is considered the centre of a circle of diameter 1 and an C<$x,$y> within that circle returns N. The unit spacing of the points means those circles don't overlap, but they also don't cover the plane and if C<$x,$y> is not within one then the return is C. =item C<$str = $path-Efigure ()> Return "circle". =back =head1 FORMULAS =head2 N to X,Y - Circle As per above, each ring begins at Nring = step*d*(d-1)/2 + 1 This can be inverted to get the ring number d for a given N, and then subtract Nring for a remainder into the ring. (N-1)/step in the formula effectively converts into triangular number style. d = floor((sqrt(8*(N-1)/step + 1) + 1) / 2) Nrem = N - Nring Rings are sized so that points are spaced 1 unit apart. There are three cases, circle, step<=6 unit radially on X axis polygon, step<=6 unit radially on sides centre step>=7 unit chord between points For the circle shape the integer points are on a circle and fractional N is on a straight line between those integer points. This means it's a polygon too, but one with ever more sides whereas ring_shape=polygon is a fixed "step" many sides. circle numsides = d*step polygon numsides = step The radial distance to a polygon corner is calculated as base varying with d ---------------- --------------------------------------- circle, step<=6 0.5/sin(pi/step) + d-1 polygon, step<=6 0.5/sin(pi/step) + (d-1)/cos(pi/step) circle, step>=7 0 + 0.5/sin(pi/(d*step)) polygon, step>=7 0 + d * 0.5/sin(pi/step) The stepE=6 cases are an initial polygon of "step" many unit sides, then unit spacing d-1 for circle, or for polygon (d-1)/cos(pi/step) which is bigger and ensures the middle of the sides have unit spacing radially. The 0.5/sin(pi/step) for radius of a unit sided polygon arises from r ___---* ___--- | 1/2 = half the polygon side ___--- alpha | o------------------+ alpha = (2pi/numsides) / 2 = pi/numsides sin(alpha) = (1/2) / base_r r = 0.5 / sin(pi/numsides) The angle theta to a polygon vertex is simply a full circle divided by numsides. side = circle Nrem polygon floor(Nrem / step) theta = side * (2pi / numsides) vertex X = r * cos(theta) Y = r * sin(theta) next_theta = (side+1) * (2pi / numsides) next_vertex X = r * cos(next_theta) Y = r * sin(next_theta) frac into side f = circle frac(Nrem) = Nrem modulo 1 polygon Nrem - side*d = Nrem modulo d X = vertex_X + f * (next_vertex_X - vertex_X) Y = vertex_Y + f * (next_vertex_Y - vertex_Y) If Nrem is an integer for circle, or multiple of d for polygon, then the vertex X,Y is the final X,Y, otherwise a fractional distance between the vertex X,Y and next vertex X,Y. For a few cases X or Y are exact integers. Special case code for these cases can ensure floating point rounding of pi doesn't give small offsets from integers. For step=6 the base r is r=1 exactly since the innermost ring is a little hexagon. This means for the circle step=6 case the points on the X axis (positive and negative) are all integers X=1,2,3,etc. P-----P / 1 / \ 1 <-- innermost points 1 apart / / \ P o-----P <-- base_r = 1 \ 1 / \ / P-----P If theta=pi, which is when 2*Nrem==d*step, then the point is on the negative X axis. Returning Y=0 exactly for that avoids sin(pi) giving some small non-zero due to rounding. If theta=pi/2 or theta=3pi/2, which is 4*Nrem==d*step or 4*Nrem==3*d*step, then N is on the positive or negative Y axis (respectively). Returning X=0 exactly avoids cos(pi/2) or cos(3pi/2) giving some small non-zero. Points on the negative X axis points occur when the step is even. Points on the Y axis points occur when the step is a multiple of 4. If theta=pi/4, 3*pi/4, 5*pi/4 or 7*pi/4, which is 8*Nrem==d*step, 3*d*step, 5*d*step or 7*d*step then the points are on the 45-degree lines X=Y or X=-Y. The current code doesn't try to ensure X==Y in these cases. The values are not integers and floating point rounding might mean sin(pi/4)!=cos(pi/4) resulting in X!=Y. =head2 N to RSquared - Step 1 For step=1 the rings are point, line, triangle, square, pentagon, etc, with vertices at radius=numsides-1. For fractional N the triangle, square and hexagon cases are quadratics in the fraction part, allowing exact values from C. Ring R^2 --------------------- -------------- triangle 4 <= N < 7 4 - 12*f*(1-f) square 7 <= N < 11 9 - 18*f*(1-f) hexagon 16 <= N < 22 25 - 25*f*(1-f) f = N - int(N) fractional part of N For example for the square at N=7.5 have f=0.5 and R^2=4.5 exactly. These quadratics arise because sine of 2pi/3, 2pi/4 and 2pi/6 are square roots, which on squaring up in R^2=X^2+Y^2 become integer factors for the fraction f along the polygon side. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A005448 A001844 A005891 A003215 A069099 3 to 7 A016754 A060544 A062786 A069125 A003154 8 to 12 A069126 A069127 A069128 A069129 A069130 13 to 17 A069131 A069132 A069133 18 to 20 N on X axis of step=k, being the centred pentagonals step=1 A002024 Radius+1, runs of n repeated n times step=8 A090915 permutation N at X,-Y, mirror across X axis =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/QuadricCurve.pm0000644000175000017500000003354713734026657020205 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # cf http://mathcurve.com/fractals/minkowski/minkowski.shtml package Math::PlanePath::QuadricCurve; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'round_up_pow', 'digit_split_lowtohigh'; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; # uncomment this to run the ### lines #use Devel::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant y_negative_at_n => 5; use constant sumxy_minimum => 0; # triangular X>=-Y use constant diffxy_minimum => 0; # triangular Y<=X so X-Y>=0 #------------------------------------------------------------------------------ # 2---3 # | | # 0---1 4 7---8 # | | # 5---6 # sub n_to_xy { my ($self, $n) = @_; ### QuadricCurve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $x; { my $int = int($n); $x = $n - $int; # frac $n = $int; # BigFloat/BigRat int() gives BigInt, use that } my $y = $x * 0; # inherit bignum 0 my $len = $y + 1; # inherit bignum 1 foreach my $digit (digit_split_lowtohigh($n,8)) { ### at: "$x,$y digit=$digit" if ($digit == 0) { } elsif ($digit == 1) { ($x,$y) = (-$y + $len, # rotate +90 and offset $x); } elsif ($digit == 2) { $x += $len; # offset $y += $len; } elsif ($digit == 3) { ($x,$y) = ($y + 2*$len, # rotate -90 and offset -$x + $len); } elsif ($digit == 4) { ($x,$y) = ($y + 2*$len, # rotate -90 and offset -$x); } elsif ($digit == 5) { $x += 2*$len; # offset $y -= $len; } elsif ($digit == 6) { ($x,$y) = (-$y + 3*$len, # rotate +90 and offset $x - $len); } elsif ($digit == 7) { ### assert: $digit==7 $x += 3*$len; # offset } $len *= 4; } ### final: "$x,$y" return ($x,$y); } # 8 # | # 7---6 # | # 3---4---5 # | # 2---1 # | # 0 # # | # * 11--12--13 # / \ | # 2---3 10---9 # / | | \ | # 0---1 4 7---8 # \ | | / # 5---6 # \ / # * # sub xy_to_n { my ($self, $x, $y) = @_; ### QuadricCurve xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0) { ### neg x ... return undef; } my ($len,$level) = round_down_pow (($x+abs($y)) || 1, 4); ### $level ### $len if (is_infinite($level)) { return $level; } my $diamond_p = sub { ### diamond_p(): "$x,$y len=$len is ".(($x == 0 && $y == 0) || ($y <= $x && $y > -$x && $y < $len-$x && $y >= $x-$len)) return (($x == 0 && $y == 0) || ($y <= $x && $y > -$x && $y < $len-$x && $y >= $x-$len)); }; my $n = 0; foreach (0 .. $level) { $n *= 8; ### at: "level=$level len=$len x=$x,y=$y n=$n" if (&$diamond_p()) { # digit 0 ... } else { ($x,$y) = ($y, -($x-$len)); # shift and rotate -90 if (&$diamond_p()) { # digit 1 ... $n += 1; } else { ($x,$y) = (-$y, $x-$len); # shift and rotate +90 if (&$diamond_p()) { # digit 2 ... $n += 2; } else { ($x,$y) = (-$y, $x-$len); # shift and rotate +90 if (&$diamond_p()) { # digit 3 ... $n += 3; } else { $x -= $len; if (&$diamond_p()) { # digit 4 ... $n += 4; } else { ($x,$y) = ($y, -($x-$len)); # shift and rotate -90 if (&$diamond_p()) { # digit 5 ... $n += 5; } else { ($x,$y) = ($y, -($x-$len)); # shift and rotate -90 if (&$diamond_p()) { # digit 6 ... $n += 6; } else { ($x,$y) = (-$y, $x-$len); # shift and rotate +90 if (&$diamond_p()) { # digit 7 ... $n += 7; } else { return undef; } } } } } } } } $len /= 4; } ### end at: "x=$x,y=$y n=$n" if ($x != 0 || $y != 0) { return undef; } return $n; } # level extends to x= 4^level # level = log4(x) # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### QuadricCurve rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); if ($x2 < $x1) { $x2 = $x1; # x2 bigger } if ($x2 < 0) { return (1,0); # rect all x negative, no points } $y1 = abs (round_nearest ($y1)); $y2 = abs (round_nearest ($y2)); if ($y2 < $y1) { $y2 = $y1; # y2 bigger abs } my $p4 = $x2+$y2+1; ### $p4 return (0, $p4*$p4); } #------------------------------------------------------------------------------ # levels sub level_to_n_range { my ($self, $level) = @_; return (0, 8**$level); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($pow, $exp) = round_up_pow ($n, 8); return $exp; } #------------------------------------------------------------------------------ { # 0 1 2 3 4 5 6 7 my @_UNDOCUMENTED__n_to_turn_LSR = (undef, 1,-1,-1, 0, 1,1,-1); sub _UNDOCUMENTED__n_to_turn_LSR { my ($self, $n) = @_; if ($n < 1 || is_infinite($n)) { return undef; } while ($n) { if (my $digit = _divrem_mutate($n,8)) { # lowest non-zero digit return $_UNDOCUMENTED__n_to_turn_LSR[$digit]; } } return undef; } } #------------------------------------------------------------------------------ 1; __END__ # 0 1 2 3 4 5 6 7 8 # # 8 @ # | # 7 +---+ # | # 6 +---+---+ # | # 5 +---+ # | # 4 @---+ + +---@ # | # 3 +---+ + # | | # 2 @---+ + +---@ + # | | | # 1 +---+ +---+ +---+ + # | | | # 0 +---+---+ @---+ + +---@---+ + +---@ # | | | | # +---+ +---+ + +---+ # | | | # @---+ + +---@ + # | | # +---+ + # | # @---+ + +---@ # | # + # # + # # + # | # @ =for stopwords eg Ryde Math-PlanePath zig-zag OEIS quadric =head1 NAME Math::PlanePath::QuadricCurve -- eight segment zig-zag =head1 SYNOPSIS use Math::PlanePath::QuadricCurve; my $path = Math::PlanePath::QuadricCurve->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is a self-similar zig-zag of eight segments, 18-19 5 | | 16-17 20 23-24 4 | | | | 15-14 21-22 25-26 3 | | 11-12-13 29-28-27 2 | | 2--3 10--9 30-31 58-59 ... 1 | | | | | | | 0--1 4 7--8 32 56-57 60 63-64 <- Y=0 | | | | | | 5--6 33-34 55-54 61-62 -1 | | 37-36-35 51-52-53 -2 | | 38-39 42-43 50-49 -3 | | | | 40-41 44 47-48 -4 | | 45-46 -5 ^ X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 The base figure is the initial N=0 to N=8, 2---3 | | 0---1 4 7---8 | | 5---6 It then repeats, turned to follow edge directions, so N=8 to N=16 is the same shape going upwards, then N=16 to N=24 across, N=24 to N=32 downwards, etc. The result is the base at ever greater scale extending to the right and with wiggly lines making up the segments. The wiggles don't overlap. The name C here is a slight mistake. Mandelbrot ("Fractal Geometry of Nature" 1982 page 50) calls any islands initiated from a square "quadric", only one of which is with sides by this eight segment expansion. This curve expansion also appears (unnamed) in Mandelbrot's "How Long is the Coast of Britain", 1967. =head2 Level Ranges A given replication extends to Nlevel = 8^level X = 4^level Y = 0 Ymax = 4^0 + 4^1 + ... + 4^level # 11...11 in base 4 = (4^(level+1) - 1) / 3 Ymin = - Ymax =head2 Turn The sequence of turns made by the curve is straightforward. In the base 8 (octal) representation of N, the lowest non-zero digit gives the turn low digit turn (degrees) --------- -------------- 1 +90 L 2 -90 R 3 -90 R 4 0 5 +90 L 6 +90 L 7 -90 R When the least significant digit is non-zero it determines the turn, to make the base N=0 to N=8 shape. When the low digit is zero it's instead the next level up, the N=0,8,16,24,etc shape which is in control, applying a turn for the subsequent base part. So for example at N=16 = 20 octal 20 is a turn -90 degrees. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::QuadricCurve-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 8**$level)>. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A332246 X coordinate A332247 Y coordinate A133851 Y at N=2^k, being successive powers 2^j at k=1mod4 =head1 SEE ALSO L, L, L L -- its F is this curve =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/SquareSpiral.pm0000644000175000017500000007204213776011042020200 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # http://d4maths.lowtech.org/mirage/ulam.htm # http://d4maths.lowtech.org/mirage/img/ulam.gif # sample gif of primes made by APL or something # # http://www.sciencenews.org/view/generic/id/2696/title/Prime_Spirals # Ulam's spiral of primes # # http://web.archive.org/web/20160424015805id_/http://yoyo.cc.monash.edu.au/%7Ebunyip/primes/primeSpiral.htm # http://yoyo.cc.monash.edu.au/%7Ebunyip/primes/triangleUlam.htm # Pulchritudinous Primes of Ulam spiral. # http://mathworld.wolfram.com/PrimeSpiral.html # # Mark C. Chu-Carroll "The Surprises Never Eend: The Ulam Spiral of Primes" # http://scienceblogs.com/goodmath/2010/06/the_surprises_never_eend_the_u.php # # http://yoyo.cc.monash.edu.au/%7Ebunyip/primes/index.html # including image highlighting the lines # S. M. Ellerstein, The Square Spiral, Journal of Recreational # Mathematics 29 (#3, 1998) 188; 30 (#4, 1999-2000), 246-250. # # M. Stein and S. M. Ulam. "An Observation on the # Distribution of Primes." Amer. Math. Monthly 74, 43-44, # 1967. # # M.L. Stein; S. M. Ulam; and M. B. Wells. "A Visual # Display of Some Properties of the Distribution of Primes." # Amer. Math. Monthly 71, 516-520, 1964. # cf sides alternately prime and Fibonacci # A160790 corner N # A160791 side lengths, alternately integer and triangular adding that integer # A160792 corner N # A160793 side lengths, alternately integer and sum primes # A160794 corner N # A160795 side lengths, alternately primes and fibonaccis package Math::PlanePath::SquareSpiral; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines #use Smart::Comments '###'; # Note: this shared by other paths use constant parameter_info_array => [ { name => 'wider', display => 'Wider', type => 'integer', minimum => 0, default => 0, width => 3, description => 'Wider path.', }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; use constant xy_is_visited => 1; # 2w+4 -- 2w+3 ----- w+2 # | | # 2w+5 0------- w+1 # | # 2w+6 --- # ^ # X=0 # sub x_negative_at_n { my ($self) = @_; return $self->n_start + ($self->{'wider'} ? 0 : 4); } sub y_negative_at_n { my ($self) = @_; return $self->n_start + 2*$self->{'wider'} + 6; } sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $self->n_start + 2*$self->{'wider'} + 4; } use constant turn_any_right => 0; # only left or straight sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; return $self->n_start + $self->{'wider'} + 1; } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); # parameters $self->{'wider'} ||= 0; # default if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # wider==0 # base from bottom-right corner # d = [ 1, 2, 3, 4 ] # N = [ 2, 10, 26, 50 ] # N = (4 d^2 - 4 d + 2) # d = 1/2 + sqrt(1/4 * $n + -4/16) # # wider==1 # base from bottom-right corner # d = [ 1, 2, 3, 4 ] # N = [ 3, 13, 31, 57 ] # N = (4 d^2 - 2 d + 1) # d = 1/4 + sqrt(1/4 * $n + -3/16) # # wider==2 # base from bottom-right corner # d = [ 1, 2, 3, 4 ] # N = [ 4, 16, 36, 64 ] # N = (4 d^2) # d = 0 + sqrt(1/4 * $n + 0) # # wider==3 # base from bottom-right corner # d = [ 1, 2, 3 ] # N = [ 5, 19, 41 ] # N = (4 d^2 + 2 d - 1) # d = -1/4 + sqrt(1/4 * $n + 5/16) # # N = 4*d^2 + (-4+2*w)*d + (2-w) # = 4*$d*$d + (-4+2*$w)*$d + (2-$w) # d = 1/2-w/4 + sqrt(1/4*$n + b^2-4ac) # (b^2-4ac)/(2a)^2 = [ (2w-4)^2 - 4*4*(2-w) ] / 64 # = [ 4w^2 - 16w + 16 - 32 + 16w ] / 64 # = [ 4w^2 - 16 ] / 64 # = [ w^2 - 4 ] / 16 # d = 1/2-w/4 + sqrt(1/4*$n + (w^2 - 4) / 16) # = 1/4 * (2-w + sqrt(4*$n + w^2 - 4)) # = 0.25 * (2-$w + sqrt(4*$n + $w*$w - 4)) # # then offset the base by +4*$d+$w-1 for top left corner for +/- remainder # rem = $n - (4*$d*$d + (-4+2*$w)*$d + (2-$w) + 4*$d + $w - 1) # = $n - (4*$d*$d + (-4+2*$w)*$d + 2 - $w + 4*$d + $w - 1) # = $n - (4*$d*$d + (-4+2*$w)*$d + 1 - $w + 4*$d + $w) # = $n - (4*$d*$d + (-4+2*$w)*$d + 1 + 4*$d) # = $n - (4*$d*$d + (2*$w)*$d + 1) # = $n - ((4*$d + 2*$w)*$d + 1) # sub n_to_xy { my ($self, $n) = @_; #### SquareSpiral n_to_xy: $n $n = $n - $self->{'n_start'}; # starting $n==0, warn if $n==undef if ($n < 0) { #### before n_start ... return; } my $w = $self->{'wider'}; my $w_right = int($w/2); my $w_left = $w - $w_right; if ($n <= $w+1) { #### centre horizontal # n=0 at w_left # x = $n - int(($w+1)/2) # = $n - int(($w+1)/2) return ($n - $w_left, # n=0 at w_left 0); } my $d = int ((2-$w + _sqrtint(4*$n + $w*$w)) / 4); #### d frac: ((2-$w + sqrt(int(4*$n) + $w*$w)) / 4) #### $d #### base: 4*$d*$d + (-4+2*$w)*$d + (2-$w) $n -= ((4*$d + 2*$w)*$d); #### remainder: $n if ($n >= 0) { if ($n <= 2*$d) { ### left vertical return (-$d - $w_left, -$n + $d); } else { ### bottom horizontal return ($n - $w_left - 3*$d, -$d); } } else { if ($n >= -2*$d-$w) { ### top horizontal return (-$n - $d - $w_left, $d); } else { ### right vertical return ($d + $w_right, $n + 3*$d + $w); } } } sub xy_to_n { my ($self, $x, $y) = @_; my $w = $self->{'wider'}; my $w_right = int($w/2); my $w_left = $w - $w_right; $x = round_nearest ($x); $y = round_nearest ($y); ### xy_to_n: "x=$x, y=$y" ### $w_left ### $w_right my $d; if (($d = $x - $w_right) > abs($y)) { ### right vertical ### $d # # base bottom right per above ### BR: 4*$d*$d + (-4+2*$w)*$d + (2-$w) # then +$d-1 for the y=0 point # N_Y0 = 4*$d*$d + (-4+2*$w)*$d + (2-$w) + $d-1 # = 4*$d*$d + (-3+2*$w)*$d + (2-$w) + -1 # = 4*$d*$d + (-3+2*$w)*$d + 1-$w ### N_Y0: (4*$d + -3 + 2*$w)*$d + 1-$w # return (4*$d + -3 + 2*$w)*$d - $w + $y + $self->{'n_start'}; } if (($d = -$x - $w_left) > abs($y)) { ### left vertical ### $d # # top left per above ### TL: 4*$d*$d + (2*$w)*$d + 1 # then +$d for the y=0 point # N_Y0 = 4*$d*$d + (2*$w)*$d + 1 + $d # = 4*$d*$d + (1 + 2*$w)*$d + 1 ### N_Y0: (4*$d + 1 + 2*$w)*$d + 1 # return (4*$d + 1 + 2*$w)*$d - $y + $self->{'n_start'}; } $d = abs($y); if ($y > 0) { ### top horizontal ### $d # # top left per above ### TL: 4*$d*$d + (2*$w)*$d + 1 # then -($d+$w_left) for the x=0 point # N_X0 = 4*$d*$d + (2*$w)*$d + 1 + -($d+$w_left) # = 4*$d*$d + (-1 + 2*$w)*$d + 1 - $w_left ### N_Y0: (4*$d - 1 + 2*$w)*$d + 1 - $w_left # return (4*$d - 1 + 2*$w)*$d - $w_left - $x + $self->{'n_start'}; } ### bottom horizontal, and centre y=0 ### $d # # top left per above ### TL: 4*$d*$d + (2*$w)*$d + 1 # then +2*$d to bottom left, +$d+$w_left for the x=0 point # N_X0 = 4*$d*$d + (2*$w)*$d + 1 + 2*$d + $d+$w_left) # = 4*$d*$d + (3 + 2*$w)*$d + 1 + $w_left ### N_Y0: (4*$d + 3 + 2*$w)*$d + 1 + $w_left # return (4*$d + 3 + 2*$w)*$d + $w_left + $x + $self->{'n_start'}; } # hi is exact but lo is not # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); # ENHANCE-ME: find actual minimum if rect doesn't cover 0,0 return ($self->{'n_start'}, max ($self->xy_to_n($x1,$y1), $self->xy_to_n($x2,$y1), $self->xy_to_n($x1,$y2), $self->xy_to_n($x2,$y2))); # my $w = $self->{'wider'}; # my $w_right = int($w/2); # my $w_left = $w - $w_right; # # my $d = 1 + max (abs($y1), # abs($y2), # $x1 - $w_right, -$x1 - $w_left, # $x2 - $w_right, -$x2 - $w_left, # 1); # ### $d # ### is: $d*$d # # # ENHANCE-ME: find actual minimum if rect doesn't cover 0,0 # return (1, # (4*$d - 4 + 2*$w)*$d + 2); # bottom-right } # [ 1, 2, 3, 4, 5 ], # [ 1, 3, 7, 13, 21 ] # N = (d^2 - d + 1) # = ($d**2 - $d + 1) # = (($d - 1)*$d + 1) # d = 1/2 + sqrt(1 * $n + -3/4) # = (1 + sqrt(4*$n - 3)) / 2 # # wider=3 # [ 2, 3, 4, 5 ], # [ 6, 13, 22, 33 ] # N = (d^2 + 2 d - 2) # = ($d**2 + 2*$d - 2) # = (($d + 2)*$d - 2) # d = -1 + sqrt(1 * $n + 3) # # wider=5 # [ 2, 3, 4, 5 ], # [ 8, 17, 28, 41 ] # N = (d^2 + 4 d - 4) # = ($d**2 + 4*$d - 4) # = (($d + 4)*$d - 4) # d = -2 + sqrt(1 * $n + 8) # # wider=7 # [ 2, 3, 4, 5 ], # [ 10, 21, 34, 49 ] # N = (d^2 + 6 d - 6) # = ($d**2 + 6*$d - 6) # = (($d + 6)*$d - 6) # d = -3 + sqrt(1 * $n + 15) # # # N = (d^2 + (w-1)*d + 1-w) # d = (1-w)/2 + sqrt($n + (w^2 + 2w - 3)/4) # = (1-w + sqrt(4*$n + (w-3)(w+1))) / 2 # # extra subtract d+w-1 # Nbase = (d^2 + (w-1)*d + 1-w) + d+w-1 # = d^2 + w*d sub n_to_dxdy { my ($self, $n) = @_; ### n_to_dxdy(): $n $n = $n - $self->{'n_start'}; # starting $n==0, warn if $n==undef if ($n < 0) { #### before n_start ... return; } my $w = $self->{'wider'}; my $d = int((1-$w + _sqrtint(4*$n + ($w+2)*$w+1)) / 2); my $int = int($n); $n -= $int; # fraction 0 <= $n < 1 $int -= ($d+$w)*$d-1; ### $d ### $w ### $n ### $int my ($dx, $dy); if ($int <= 0) { if ($int < 0) { ### horizontal ... $dx = 1; $dy = 0; } else { ### corner horiz to vert ... $dx = 1-$n; $dy = $n; } } else { if ($int < $d) { ### vertical ... $dx = 0; $dy = 1; } else { ### corner vert to horiz ... $dx = -$n; $dy = 1-$n; } } unless ($d % 2) { ### rotate +180 for even d ... $dx = -$dx; $dy = -$dy; } ### result: "$dx, $dy" return ($dx,$dy); } # old bit: # # wider==0 # base from two-way diagonal top-right and bottom-left # s even for top-right diagonal doing top leftwards then left downwards # s odd for bottom-left diagonal doing bottom rightwards then right pupwards # s = [ 0, 1, 2, 3, 4, 5, 6 ] # N = [ 1, 1, 3, 7, 13, 21, 31 ] # +0 +2 +4 +6 +8 +10 # 2 2 2 2 2 # # n = (($d - 1)*$d + 1) # s = 1/2 + sqrt(1 * $n + -3/4) # = .5 + sqrt ($n - .75) # # #------------------------------------------------------------------------------ sub _NOTDOCUMENTED_n_to_figure_boundary { my ($self, $n) = @_; ### _NOTDOCUMENTED_n_to_figure_boundary(): $n # adjust to N=1 at origin X=0,Y=0 $n = $n - $self->{'n_start'} + 1; if ($n < 1) { return undef; } my $wider = $self->{'wider'}; if ($n <= $wider) { # single block row # +---+-----+----+ # | 1 | ... | $n | boundary = 2*N + 2 # +---+-----+----+ return 2*$n + 2; } my $d = int((_sqrtint(4*$n + $wider*$wider - 2) - $wider) / 2); ### $d ### $wider ### cmp: $d*($d+1+$wider) + $wider + 1 if ($n > $d*($d+1+$wider)) { $wider++; ### increment for +2 after turn ... } return 4*$d + 2*$wider + 2; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords Stanislaw Ulam pronic PlanePath Ryde Math-PlanePath Ulam's Honaker's decagonal OEIS Nbase sqrt BigRat Nrem wl wr Nsig incrementing Oren Patashnik =head1 NAME Math::PlanePath::SquareSpiral -- integer points drawn around a square (or rectangle) =head1 SYNOPSIS use Math::PlanePath::SquareSpiral; my $path = Math::PlanePath::SquareSpiral->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes a square spiral, =cut # math-image --path=SquareSpiral --all --output=numbers_dash --size=40x16 =pod 37--36--35--34--33--32--31 3 | | 38 17--16--15--14--13 30 2 | | | | 39 18 5---4---3 12 29 1 | | | | | | 40 19 6 1---2 11 28 ... <- Y=0 | | | | | | 41 20 7---8---9--10 27 52 -1 | | | | 42 21--22--23--24--25--26 51 -2 | | 43--44--45--46--47--48--49--50 -3 ^ -3 -2 -1 X=0 1 2 3 4 See F for a simple program printing these numbers. =head2 Ulam Spiral This path is well known from Stanislaw Ulam finding interesting straight lines when plotting the prime numbers on it. =over Stein, Ulam and Wells, "A Visual Display of Some Properties of the Distribution of Primes", American Mathematical Monthly, volume 71, number 5, May 1964, pages 516-520. L =back =cut # math-image --wx --path=SquareSpiral --primes =pod The cover of Scientific American March 1964 featured this spiral, =over L L =back See F for a standalone program, or see L using this C to draw this pattern and more. Stein, Ulam and Wells above also considered primes on the L path, and on a half-plane like two corners. =head2 Straight Lines XThe perfect squares 1,4,9,16,25 fall on two diagonals with the even perfect squares going to the upper left and the odd squares to the lower right. The Xpronic numbers 2,6,12,20,30,42 etc k^2+k half way between the squares fall on similar diagonals to the upper right and lower left. The decagonal numbers 10,27,52,85 etc 4*k^2-3*k go horizontally to the right at Y=-1. In general, straight lines and diagonals are 4*k^2 + b*k + c. b=0 is the even perfect squares up to the left, then incrementing b is an eighth turn anti-clockwise, or clockwise if negative. So b=1 is horizontal West, b=2 diagonally down South-West, b=3 down South, etc. Honaker's prime-generating polynomial 4*k^2 + 4*k + 59 goes down to the right after the first 30 or so values loop around a bit. =head2 Wider An optional C parameter makes the path wider, becoming a rectangle spiral instead of a square. For example wider => 3 29--28--27--26--25--24--23--22 2 | | 30 11--10-- 9-- 8-- 7-- 6 21 1 | | | | 31 12 1-- 2-- 3-- 4-- 5 20 <- Y=0 | | | 32 13--14--15--16--17--18--19 -1 | 33--34--35--36-... -2 ^ -4 -3 -2 -1 X=0 1 2 3 The centre horizontal 1 to 2 is extended by C many further places, then the path loops around that shape. The starting point 1 is shifted to the left by ceil(wider/2) places to keep the spiral centred on the origin X=0,Y=0. Widening doesn't change the nature of the straight lines which arise, it just rotates them around. For example in this wider=3 example the perfect squares are still on diagonals, but the even squares go towards the bottom left (instead of top left when wider=0) and the odd squares to the top right (instead of the bottom right). Each loop is still 8 longer than the previous, since the widening is a constant amount in each loop. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start with the same shape. For example to start at 0, =cut # math-image --path=SquareSpiral,n_start=0 --all --output=numbers_dash --size=35x16 =pod n_start => 0 16-15-14-13-12 ... | | | 17 4--3--2 11 28 | | | | | 18 5 0--1 10 27 | | | | 19 6--7--8--9 26 | | 20-21-22-23-24-25 The only effect is to push the N values around by a constant amount. It might help match coordinates with something else zero-based. =head2 Corners Other spirals can be formed by cutting the corners of the square so as to go around faster. See the following modules, Corners Cut Class ----------- ----- 1 HeptSpiralSkewed 2 HexSpiralSkewed 3 PentSpiralSkewed 4 DiamondSpiral The C is a re-shaped C looping at the same rate. It shifts corners but doesn't cut them. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::SquareSpiral-Enew ()> =item C<$path = Math::PlanePath::SquareSpiral-Enew (wider =E $integer, n_start =E $n)> Create and return a new square spiral object. An optional C parameter widens the spiral path, it defaults to 0 which is no widening. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n E 1> the return is an empty list, as the path starts at 1. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each N in the path as centred in a square of side 1, so the entire plane is covered. =back =head1 FORMULAS =head2 N to X,Y There's a few ways to break an N into a side and offset into the side. One convenient way is to treat a loop as starting at the bottom right turn, so N=2,10,26,50,etc, If the first loop at N=2 is reckoned loop number d=1 then the loop starts at Nbase = 4*d^2 - 4*d + 2 = 2,10,26,50,... for d=1,2,3,4,... (A069894 but it going from d=0) For example d=3 is Nbase=4*3^2-4*3+2=26 at X=3,Y=-2. The biggest d with Nbase E= N can be found by inverting with the usual quadratic formula d = floor( 1/2 + sqrt(N/4 - 1/4) ) For Perl it's good to keep the sqrt argument an integer (when a UV integer is bigger than an NV float, and for BigRat accuracy), so rearrange to d = floor( (1+sqrt(N-1))/2 ) So Nbase from this d leaves a remainder which is an offset into the loop Nrem = N - Nbase = N - (4*d^2 - 4*d + 2) The loop starts at X=d,Y=d-1 and has sides length 2d, 2d+1, 2d+1 and 2d+2, 2d +------------+ <- Y=d | | 2d | | 2d-1 | . | | | | + X=d,Y=-d+1 | +---------------+ <- Y=-d 2d+1 ^ X=-d The X,Y for an Nrem is then side Nrem range X,Y result ---- ---------- ---------- right Nrem <= 2d-1 X = d Y = -d+1+Nrem top 2d-1 <= Nrem <= 4d-1 X = d-(Nrem-(2d-1)) = 3d-1-Nrem Y = d left 4d-1 <= Nrem <= 6d-1 X = -d Y = d-(Nrem-(4d-1)) = 5d-1-Nrem bottom 6d-1 <= Nrem X = -d+(Nrem-(6d-1)) = -7d+1+Nrem Y = -d The corners Nrem=2d-1, Nrem=4d-1 and Nrem=6d-1 get the same result from the two sides that meet so it doesn't matter if the high comparison is "E" or "E=". The bottom edge runs through to Nrem E 8d, but there's no need to check that since d=floor(sqrt()) above ensures Nrem is within the loop. A small simplification can be had by subtracting an extra 4d-1 from Nrem to make negatives for the right and top sides and positives for the left and bottom. Nsig = N - Nbase - (4d-1) = N - (4*d^2 - 4*d + 2) - (4d-1) = N - (4*d^2 + 1) side Nsig range X,Y result ---- ---------- ---------- right Nsig <= -2d X = d Y = d+(Nsig+2d) = 3d+Nsig top -2d <= Nsig <= 0 X = -d-Nsig Y = d left 0 <= Nsig <= 2d X = -d Y = d-Nsig bottom 2d <= Nsig X = -d+1+(Nsig-(2d+1)) = Nsig-3d Y = -d This calculation can be found in =over Ronald L. Graham, Donald E. Knuth, Oren Patashnik, "Concrete Mathematics", Addison-Wesley, 1989, chapter 3 "Integer Functions", exercise 40 page 99, answer page 498. =back They start the spiral from 0, and first step North so their x is -Y here. Their formula for x(n) tests a floor(2*sqrt(N)) to decide whether on a horizontal and so whether to apply the equivalent of Nrem to the result. =cut # PDF: exercise page 113 answer page 512. =pod =head2 N to X,Y with Wider With the C parameter stretching the spiral loops the formulas above become Nbase = 4*d^2 + (-4+2w)*d + 2-w d = floor ((2-w + sqrt(4N + w^2 - 4)) / 4) Notice for Nbase the w is a term 2*w*d, being an extra 2*w for each loop. The left offset ceil(w/2) described above (L) for the N=1 starting position is written here as wl, and the other half wr arises too, wl = ceil(w/2) wr = floor(w/2) = w - wl The horizontal lengths increase by w, and positions shift by wl or wr, but the verticals are unchanged. 2d+w +------------+ <- Y=d | | 2d | | 2d-1 | . | | | | + X=d+wr,Y=-d+1 | +---------------+ <- Y=-d 2d+1+w ^ X=-d-wl The Nsig formulas then have w, wl or wr variously inserted. In all cases if w=wl=wr=0 then they simplify to the plain versions. Nsig = N - Nbase - (4d-1+w) = N - ((4d + 2w)*d + 1) side Nsig range X,Y result ---- ---------- ---------- right Nsig <= -(2d+w) X = d+wr Y = d+(Nsig+2d+w) = 3d+w+Nsig top -(2d+w) <= Nsig <= 0 X = -d-wl-Nsig Y = d left 0 <= Nsig <= 2d X = -d-wl Y = d-Nsig bottom 2d <= Nsig X = -d+1-wl+(Nsig-(2d+1)) = Nsig-wl-3d Y = -d =head2 Rectangle to N Range Within each row the minimum N is on the X=Y diagonal and N values increases monotonically as X moves away to the left or right. Similarly in each column there's a minimum N on the X=-Y opposite diagonal, or X=-Y+1 diagonal when X negative, and N increases monotonically as Y moves away from there up or down. When widerE0 the location of the minimum changes, but N is still monotonic moving away from the minimum. On that basis the maximum N in a rectangle is at one of the four corners, | x1,y2 M---|----M x2,y2 corner candidates | | | for maximum N -------O--------- | | | | | | x1,y1 M---|----M x1,y1 | =head1 OEIS This path is in Sloane's Online Encyclopedia of Integer Sequences in various forms. Summary at =over L =back And various sequences, =over L (etc), L =back wider=0 (the default) A174344 X coordinate A274923 Y coordinate A268038 negative Y A296030 X,Y pairs A214526 abs(X)+abs(Y) "Manhattan" distance A079813 abs(dY), being k 0s followed by k 1s A055086 direction (total turn) A000267 direction + 1 A063826 direction 1=right,2=up,3=left,4=down A027709 boundary length of N unit squares A078633 grid sticks to make N unit squares A240025 turn 1=left,0=straight (extra initial 1) A033638 N turn positions (extra initial 1, 1) A172979 N turn positions which are primes too A242601 X and Y location of origin then each turn, X=A242601(n+1), Y=A242601(n) A080037 N straight-ahead (except initial 2) A248333 num straight points among the first N A083479 num non-turn points among the first N (straight and the origin) A054552 N values on X axis (East) A054556 N values on Y axis (North) A054567 N values on negative X axis (West) A033951 N values on negative Y axis (South) A054554 N values on X=Y diagonal (NE) A054569 N values on negative X=Y diagonal (SW) A053755 N values on X=-Y opp diagonal X<=0 (NW) A016754 N values on X=-Y opp diagonal X>=0 (SE) A200975 N values on all four diagonals A317186 N on Y axis positive and negative A267682 N on Y axis positive and negative (origin twice) A265400 N inner neighbour A137928 N values on X=-Y+1 opposite diagonal A002061 N values on X=Y diagonal pos and neg A016814 (4k+1)^2, every second N on south-east diagonal A143856 N values on ENE slope dX=2,dY=1 A143861 N values on NNE slope dX=1,dY=2 A215470 N prime and >=4 primes among its 8 neighbours A214664 X coordinate of prime N (Ulam's spiral) A214665 Y coordinate of prime N (Ulam's spiral) A214666 -X \ reckoning spiral starting West A214667 -Y / A053999 prime[N] on X=-Y opp diagonal X>=0 (SE) A054551 prime[N] on the X axis (E) A054553 prime[N] on the X=Y diagonal (NE) A054555 prime[N] on the Y axis (N) A054564 prime[N] on X=-Y opp diagonal X<=0 (NW) A054566 prime[N] on negative X axis (W) A090925 permutation N at rotate +90 A090928 permutation N at rotate +180 A090929 permutation N at rotate +270 A090930 permutation N at clockwise spiralling A020703 permutation N at rotate +90 and go clockwise A090861 permutation N at rotate +180 and go clockwise A090915 permutation N at rotate +270 and go clockwise A185413 permutation N at 1-X,Y being rotate +180, offset X+1, clockwise A068225 permutation N at X+1,Y A121496 run lengths of consecutive N in this permutation A068226 permutation N at X-1,Y A334752 permutation N at X,Y+1 A334751 permutation N at X,Y-1 A020703 permutation N at transpose Y,X (clockwise <-> anti-clockwise) A033952 digits on negative Y axis A033953 digits on negative Y axis, starting 0 A033988 digits on negative X axis, starting 0 A033989 digits on Y axis, starting 0 A033990 digits on X axis, starting 0 A062410 total sum previous row or column wider=1 A069894 N on South-West diagonal The following have "offset 0" S and therefore start from N=0. n_start=0 A180714 X+Y coordinate sum A053615 abs(X-Y), runs n to 0 to n, distance to nearest pronic A001107 N on X axis A033991 N on Y axis A033954 N on negative Y axis, second 10-gonals A002939 N on X=Y diagonal North-East A016742 N on North-West diagonal, 4*k^2 A002943 N on South-West diagonal A156859 N on Y axis positive and negative =head1 SEE ALSO L, L L, L, L, L L L X11 cursor font "box spiral" cursor which is this style (but going clockwise). =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/SierpinskiCurveStair.pm0000644000175000017500000007220113734026656021725 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=SierpinskiCurveStair --lines --scale=10 # # math-image --path=SierpinskiCurveStair,diagonal_length=1 --all --output=numbers_dash --offset=-10,-7 --size=78x30 package Math::PlanePath::SierpinskiCurveStair; use 5.004; use strict; use List::Util 'min','max'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_up_pow', 'round_down_pow'; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; sub x_negative { my ($self) = @_; return ($self->{'arms'} >= 3); } sub y_negative { my ($self) = @_; return ($self->{'arms'} >= 5); } use constant parameter_info_array => [ { name => 'diagonal_length', display => 'Diagonal Length', type => 'integer', minimum => 1, default => 1, width => 1, description => 'Length of the diagonal in the base pattern.', }, { name => 'arms', share_key => 'arms_8', display => 'Arms', type => 'integer', minimum => 1, maximum => 8, default => 1, width => 1, }, ]; use Math::PlanePath::SierpinskiCurve; *x_negative_at_n = \&Math::PlanePath::SierpinskiCurve::x_negative_at_n; *y_negative_at_n = \&Math::PlanePath::SierpinskiCurve::y_negative_at_n; *x_minimum = \&Math::PlanePath::SierpinskiCurve::x_minimum; *sumxy_minimum = \&Math::PlanePath::SierpinskiCurve::sumxy_minimum; use constant sumabsxy_minimum => 1; *diffxy_minimum = \&Math::PlanePath::SierpinskiCurve::diffxy_minimum; use constant absdiffxy_minimum => 1; # X=Y never occurs use constant rsquared_minimum => 1; # minimum X=1,Y=0 use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(8, $self->{'arms'} || 1)); $self->{'diagonal_length'} ||= 1; return $self; } # 20--21 # | | # 18--19 22--23 # | | # 16--17 24--25 # | | # 15--14 27--26 # | | # 4---5 13--12 29--28 36--37 # | | | | | | # 2---3 6---7 10--11 30--31 34--35 38--39 42--43 # | | | | | | | # 0---1 8---9 32--33 40--41 # len=5 # N=0 to 9 is 10 # next N=0 to 41 is 42=4*10+2 # next is 4*42+2=166 # points(level) = 4*points(level-1)+2 # # or side 5 points # points(level) = 4*points(level-1)+1 # = 4*(4*points(level-2)+1)+1 # = 16*points(level-2) + 4 + 1 # = 64*points(level-3) + 16 + 4 + 1 # = 5 * 4^level + 1+...+4^(level-1) # = 5 * 4^level + (4^level - 1) / 3 # = (15 * 4^level + 4^level - 1) / 3 # = (16 * 4^level - 1) / 3 # = (4^(level+2) - 1) / 3 # level=0 (16*1-1)/3=5 # level=1 (16*4-1)/3=21 # level=2 (16*16-1)/3=85 # # n = (16 * 4^level - 1) / 3 # 3n+1 = 16 * 4^level # 4^level = (3n+1)/16 # level = log4 ( (3n+1)/16) # = log4(3n+1) - 2 # N=21 log4(64)-2=3-2=1 # # nlen=4^(level+2) # n = (nlen-1)/3 # next_n = (nlen/4-1)/3 # = (nlen-4)/3 /4 # = ((nlen-1)/3 -1) /4 # # len=2,6,14 # len(k)=2*len(k-1) + 2 # = 2^k + 2*(2^(k-1)-1) # = 2^k + 2^k - 2 # = 2*(2^k - 1) # k=1 len=2*(2-1) = 2 # k=2 len=2*(4-1) = 6 # k=3 len=2*(8-1) = 14 # len(k)-2=2*len(k-1) # (len(k)-2)/2=len(k-1) # len(k-1) = (len(k)-2)/2 # = len(k)/2-1 # # --------- # with P=2*L+1 points per side # points(level) = 64*points(level-3) + 16 + 4 + 1 # = P*4^level + 1+...+4^(level-1) # = P*4^level + (4^level - 1) / 3 # = (3P*4^level + 4^level - 1) / 3 # = ((3P+1)*4^level - 1) / 3 # = ((3*(2L+1)+1)*4^level - 1) / 3 # = ((6L+3+1)*4^level - 1) / 3 # = ((6L+4)*4^level - 1) / 3 # n = ((6L+4)*4^level - 1) / 3 # 3n+1 = (6L+4)*4^level # # len(k) = 2*len(k-1) + 2 # = 2*len(k-2) + 2 + 4 # = 2*len(k-3) + 2 + 4 + 8 # = 2^(k-1)*L + 2^k - 2 # = (L+2)*2^(k-1) - 2 # L=2 k=3 len=(2+2)*2^2-2=14 # # ---------- # Nlevel = ((6L+4)*4^level - 1) / 3 - 1 # = ((6L+4)*4^level - 4) / 3 # Xlevel = (L+2)*2^level - 2 + 1 # = (L+2)*2^level - 1 # # fill = Nlevel / (Xlevel*(Xlevel-1)/2) # = (((6L+4)*4^level - 1) / 3 - 1) / (((L+2)*2^level - 1)*((L+2)*2^level - 2)) # -> (((6L+4)*4^level) / 3) / ((L+2)*2^level)^2 # = ((6L+4)*4^level) / ((L+2)^2*4^level) *2/3 # = ((6L+4)) / ((L+2)^2) * 2/3 # = 2*(3L+2) / ((L+2)^2) * 2/3 # = 4/3 * (3L+2)/(L+2)^2 # = (12L+8) / (3*L^2+12L+12) # L=1 (12+8)/(3+12+12) = 20/27 sub n_to_xy { my ($self, $n) = @_; ### SierpinskiCurveStair n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $frac; { my $int = int($n); $frac = $n - $int; # inherit possible BigFloat if ($frac) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'}); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } ### $frac my $zero = ($n * 0); # inherit bignum 0 my $arm = _divrem_mutate ($n, $self->{'arms'}); my $diagonal_length = $self->{'diagonal_length'}; my $diagonal_div = 6*$diagonal_length + 4; my ($nlen,$level) = round_down_pow ((3*$n+1)/$diagonal_div, 4); ### $nlen ### $level if (is_infinite($level)) { return $level; } my $x = $zero; my $y = $zero; my $dx = 1; my $dy = 0; # (L+2)*2^(level-1) - 2 my $len = ($diagonal_length+2)*2**$level - 2; $nlen = ($diagonal_div*$nlen-1)/3; while ($level-- >= 0) { ### at: "n=$n xy=$x,$y nlen=$nlen len=$len" if ($n < 2*$nlen+1) { if ($n < $nlen) { ### part 0 ... } else { ### part 1 ... $x += ($len+1)*$dx - $len*$dy; $y += ($len+1)*$dy + $len*$dx; ($dx,$dy) = ($dy,-$dx); # rotate -90 $n -= $nlen; } } else { $n -= 2*$nlen+1; if ($n < $nlen) { ### part 2 ... $x += (2*$len+2)*$dx - $dy; $y += (2*$len+2)*$dy + $dx; ($dx,$dy) = (-$dy,$dx); # rotate +90 } else { ### part 3 ... $x += ($len+2)*$dx - ($len+2)*$dy; $y += ($len+2)*$dy + ($len+2)*$dx; $n -= $nlen; } } $nlen = ($nlen-1)/4; $len = $len/2-1; } my $lowdigit_x = int(($n+1)/2); if ($n == 2*$diagonal_length+1) { $lowdigit_x -= 2; } my $lowdigit_y = int($n/2); ### final: "n=$n xy=$x,$y dxdy=$dx,$dy" ### $lowdigit_x ### $lowdigit_y $x += $lowdigit_x*$dx - $lowdigit_y*$dy + 1; # +1 start at x=1,y=0 $y += $lowdigit_x*$dy + $lowdigit_y*$dx; if ($arm & 1) { ($x,$y) = ($y,$x); # mirror 45 } if ($arm & 2) { ($x,$y) = (-1-$y,$x); # rotate +90 } if ($arm & 4) { $x = -1-$x; # rotate 180 $y = -1-$y; } return ($x,$y); } sub xy_to_n { my ($self, $x, $y) = @_; ### SierpinskiCurveStair xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); my $arm = 0; if ($y < 0) { $arm = 4; $x = -1-$x; # rotate -180 $y = -1-$y; } if ($x < 0) { $arm += 2; ($x,$y) = ($y, -1-$x); # rotate -90 } if ($y > $x) { # second octant $arm++; ($x,$y) = ($y,$x); # mirror 45 } my $arms = $self->{'arms'}; if ($arm >= $arms) { return undef; } $x -= 1; if ($x < 0 || $x < $y) { return undef; } ### x adjust to zero: "$x,$y" ### assert: $x >= 0 ### assert: $y >= 0 # len=2*(2^level - 1) # len/2+1 = 2^level # 2^level = len/2+1 # 2^(level+1) = len+2 # len=(L+2)*2^(level-1) - 2 # (len+2)/(L+2) = 2^(level-1) my $diagonal_length = $self->{'diagonal_length'}; my ($len,$level) = round_down_pow (($x+1)/($diagonal_length+2), 2); ### $level ### $len if (is_infinite($level)) { return $level; } my $n = 0; my $nlen = ((6*$diagonal_length+4)*$len*$len-1)/3; $len *= ($self->{'diagonal_length'}+2); ### $len ### $nlen my $n_last_1; foreach (0 .. $level) { ### at: "loop=$_ x=$x,y=$y n=$n nlen=$nlen len=$len diag cmp ".(2*$len-2) ### assert: $x >= 0 ### assert: $y >= 0 if ($x+$y <= 2*$len-2) { ### part 0 or 1... if ($x < $len-1) { ### part 0 ... $n_last_1 = 0; } else { ### part 1 ... ($x,$y) = ($len-2-$y, $x-($len-1)); # shift then rotate +90 $n += $nlen; $n_last_1 = 1; } } else { $n += 2*$nlen + 1; # +1 for middle point ### part 2 or 3 ... if ($y < $len) { ### part 2... ($x,$y) = ($y-1, 2*$len-2-$x); # shift y-1 then rotate -90 $n_last_1 = 0; } else { #### digit 3... $x -= $len; $y -= $len; $n += $nlen; } if ($x < 0) { return undef; } } $len /= 2; $nlen = ($nlen-1)/4; } ### end at: "x=$x,y=$y n=$n last2=$n_last_1" ### assert: $x >= 0 ### assert: $y >= 0 if ($x == $y || $x == $y+1) { $n += $x+$y; } elsif ($n_last_1 && $x == $diagonal_length-1 && $y == $diagonal_length) { # in between diagonals $n += 2*$diagonal_length+1; } else { return undef; } return $n*$arms + $arm; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### SierpinskiCurveStair rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; # x2 # y2 +-------+ * # | | * # y1 +-------+ * # * # * # * # ------------------ # # # * # x1 * x2 * # +-----*-+y2* # | *| * # | * * # | |* * # | | ** # +-------+y1* # ---------------- # my $arms = $self->{'arms'}; if (($arms <= 4 ? ($y2 < 0 # y2 negative, nothing ... || ($arms == 1 && $x2 <= $y1) || ($arms == 2 && $x2 < 0) || ($arms == 3 && $x2 < -$y2)) # arms >= 5 : ($y2 < 0 && (($arms == 5 && $x1 >= $y2) || ($arms == 6 && $x1 >= 0) || ($arms == 7 && $x1 > 3-$y2))))) { ### rect outside octants, for arms: $arms ### $x1 ### $y2 return (1,0); } my $max = $x2; # arms 1,8 using X, starting at X=1 if ($arms >= 2) { # arms 2,3 upper using Y, starting at Y=1 _apply_max ($max, $y2); if ($arms >= 4) { # arms 4,5 right using X, starting at X=-2 _apply_max ($max, -1-$x1); if ($arms >= 6) { # arms 6,7 down using Y, starting at Y=-2 _apply_max ($max, -1-$y1); } } } ### $max # points(level) = (4^(level+2) - 1) / 3 # Nlast(level) = (4^(level+2) - 1) / 3 - 1 # = (4^(level+2) - 4) / 3 # then + arms-1 for last of arms # Nhi = Nlast(level) * arms + arms-1 # = (Nlast(level + 1)) * arms - 1 # = ((4^(level+2) - 4) / 3 + 1) * arms - 1 # = ((4^(level+2) - 1) / 3) * arms - 1 # # len(level) = = (L+2)*2^(level-1) - 2 # points(level) = ((3*P+1)*4^level - 1) / 3 # my ($pow,$level) = round_down_pow ($max/($self->{'diagonal_length'}+2), 2); return (0, ((6*$self->{'diagonal_length'}+4)*4*$pow*$pow - 1) / 3 * $arms - 1); } # set $_[0] to the max of $_[0] and $_[1] sub _apply_max { ### _apply_max(): "$_[0] cf $_[1]" unless ($_[0] > $_[1]) { $_[0] = $_[1]; } } #------------------------------------------------------------------------------ # Nlevel = ((3L+2)*4^level - 5) / 3 # LevelPoints = Nlevel+1 # Nlevel(arms) = (Nlevel+1)*arms - 1 # # Eg. L=1 level=1 (5*4-5)/3 = 5 # arms=8 ((5*4-5)/3+1)*8 - 1 = 47 # sub level_to_n_range { my ($self, $level) = @_; return (0, (4**$level * (3*$self->{'diagonal_length'}+2) - 2) / 3 * $self->{'arms'} - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, $self->{'arms'}); my $diagonal_div = 3*$self->{'diagonal_length'} + 2; my ($pow,$exp) = round_up_pow ((3*$n+3) / (3*$self->{'diagonal_length'}+2), 4); return $exp; } #------------------------------------------------------------------------------ 1; __END__ # 84-85 # | | # 82-83 ... # | # 80-81 # | # 79-78 # | # 68-69 77-76 # | | | # 66-67 70-71 74-75 # | | | # 64-65 72-73 # | # 63-62 55-54 # | | | # 20-21 61-60 57-56 53-52 # | | | | | # 18-19 22-23 59-58 50-51 # | | | # 16-17 24-25 48-49 # | | | # 15-14 27-26 47-46 # | | | # 4--5 13-12 29-28 36-37 45-44 # | | | | | | | # 2--3 6--7 10-11 30-31 34-35 38-39 42-43 # | | | | | | | # 0--1 8--9 32-33 40-41 # ..--90 89--.. 7 # | | # 82-74 73-81 6 # | | # 58-66 65-57 5 # | | # 42-50 49-41 4 # | | # 34-26 25-33 3 # | | # ... 43-35 18-10 9-17 32-40 .. 2 # | | | | | | | | # 91-83 59-51 27-19 2 1 16-24 48-56 80-88 1 # | | | | | | # 75-67 11--3 . 0--8 64-72 <- Y=0 # # 76-68 12--4 7-15 71-79 -1 # | | | | | | # 92-84 60-52 28-20 5 6 23-31 55-63 87-95 -2 # | | | | | | | | # .. 44-36 21-13 14-22 39-47 .. -3 # | | # 37-29 30-38 -4 # | | # 45-53 54-46 -5 # | | # 61-69 70-62 -6 # | | # 85-77 78-86 -7 # | | # ..--93 94--.. -8 # # ^ # -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 =for stopwords eg Ryde Waclaw Sierpinski Sierpinski's Math-PlanePath Nlevel Nend Ntop Xlevel PlanePath SierpinskiCurveStair OEIS =head1 NAME Math::PlanePath::SierpinskiCurveStair -- Sierpinski curve with stair-step diagonals =head1 SYNOPSIS use Math::PlanePath::SierpinskiCurveStair; my $path = Math::PlanePath::SierpinskiCurveStair->new (arms => 2); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is a variation on the C with stair-step diagonal parts. 10 | 52-53 | | | 9 | 50-51 54-55 | | | 8 | 49-48 57-56 | | | 7 | 42-43 46-47 58-59 62-63 | | | | | | | 6 | 40-41 44-45 60-61 64-65 | | | 5 | 39-38 35-34 71-70 67-66 | | | | | | | 4 | 12-13 37-36 33-32 73-72 69-68 92-93 | | | | | | | 3 | 10-11 14-15 30-31 74-75 90-91 94-95 | | | | | | | 2 | 9--8 17-16 29-28 77-76 89-88 97-96 | | | | | | | 1 | 2--3 6--7 18-19 22-23 26-27 78-79 82-83 86-87 98-99 | | | | | | | | | | | | | Y=0 | 0--1 4--5 20-21 24-25 80-81 84-85 ... | +------------------------------------------------------------- ^ X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 The tiling is the same as the C, but each diagonal is a stair step horizontal and vertical. The correspondence is SierpinskiCurve SierpinskiCurveStair 7-- 12-- / | 6 10-11 | | 5 9--8 \ | 1--2 4 2--3 6--7 / \ / | | | 0 3 0--1 4--5 The C N=0 to N=3 corresponds to N=0 to N=5 here. N=7 to N=12 which is a copy of the N=0 to N=5 base. Point N=6 is an extra in between the parts. The next such extra is N=19. =head2 Diagonal Length The C option can make longer diagonals, still in stair-step style. For example diagonal_length => 4 10 | 36-37 | | | 9 | 34-35 38-39 | | | 8 | 32-33 40-41 | | | 7 | 30-31 42-43 | | | 6 | 28-29 44-45 | | | 5 | 27-26 47-46 | | | 4 | 8--9 25-24 49-48 ... | | | | | | 3 | 6--7 10-11 23-22 51-50 62-63 | | | | | | 2 | 4--5 12-13 21-20 53-52 60-61 | | | | | | 1 | 2--3 14-15 18-19 54-55 58-59 | | | | | | Y=0 | 0--1 16-17 56-57 | +------------------------------------------------------ ^ X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 The length is reckoned from N=0 to the end of the first side N=8, which is X=1 to X=5 for length 4 units. =head2 Arms The optional C parameter can give up to eight copies of the curve, each advancing successively. For example arms => 8 98-90 66-58 57-65 89-97 5 | | | | | | 99 82-74 50-42 41-49 73-81 96 4 | | | | 91-83 26-34 33-25 80-88 3 | | | | 67-75 18-10 9-17 72-64 2 | | | | 59-51 27-19 2 1 16-24 48-56 1 | | | | | | 43-35 11--3 . 0--8 32-40 <- Y=0 44-36 12--4 7-15 39-47 -1 | | | | | | 60-52 28-20 5 6 23-31 55-63 -2 | | | | 68-76 21-13 14-22 79-71 -3 | | | | 92-84 29-37 38-30 87-95 -4 | | 85-77 53-45 46-54 78-86 -5 | | | | | | 93 69-61 62-70 94 -6 ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 The multiples of 8 (or however many arms) N=0,8,16,etc is the original curve, and the further mod 8 parts are the copies. The middle "." shown is the origin X=0,Y=0. It would be more symmetrical to have the origin the middle of the eight arms, which would be X=-0.5,Y=-0.5 in the above, but that would give fractional X,Y values. Apply an offset X+0.5,Y+0.5 to centre if desired. =head2 Level Ranges The N=0 point is reckoned as level=0, then N=0 to N=5 inclusive is level=1, etc. Each level is 4 copies of the previous and an extra 2 points between. LevelPoints[k] = 4*LevelPoints[k-1] + 2 starting LevelPoints[0]=1 = 2 + 2*4 + 2*4^2 + ... + 2*4^(k-1) + 1*4^k = (5*4^k - 2)/3 Nlevel[k] = LevelPoints[k] - 1 since starting at N=0 = 5*(4^k - 1)/3 = 0, 5, 25, 105, 425, 1705, 6825, 27305, ... (A146882) =for GP-DEFINE LevelPoints(k) = (5*4^k - 2)/3 =for GP-DEFINE Nlevel(k) = 5*(4^k - 1)/3 =for GP-DEFINE Nlevel_samples = [ 0, 5, 25, 105, 425, 1705, 6825, 27305 ] =for GP-Test vector(20,k,my(k=k-1); Nlevel(k)) == vector(20,k,my(k=k-1); LevelPoints(k) - 1) =for GP-Test vector(length(Nlevel_samples),k,my(k=k-1); Nlevel(k)) == Nlevel_samples The width along the X axis of a level doubles each time, plus an extra distance 3 between. LevelWidth[k] = 2*LevelWidth[k-1] + 3 starting LevelWidth[0]=0 = 3 + 3*2 + 3*2^2 + ... + 3*2^(k-1) + 0*2^k = 3*(2^k - 1) Xlevel[k] = 1 + LevelWidth[k] = 3*2^k - 2 = 1, 4, 10, 22, 46, 94, 190, 382, ... (A033484) =for GP-DEFINE LevelWidth(k) = 3*(2^k - 1) =for GP-DEFINE Xlevel(k) = 3*2^k - 2 =for GP-DEFINE Xlevel_samples = [ 1, 4, 10, 22, 46, 94, 190, 382 ] =for GP-Test vector(20,k,my(k=k-1); Xlevel(k)) == vector(20,k,my(k=k-1); 1 + LevelWidth(k)) =for GP-Test vector(length(Xlevel_samples),k,my(k=k-1); Xlevel(k)) == Xlevel_samples =head2 Level Ranges with Diagonal Length With C = L, level=0 is reckoned as having L many points instead of just 1. =cut # with 4*L+2 in level=1 # LevelPoints[k] = 2 + 2*4 + 2*4^2 + ... + 2*4^(k-2) + (4*L+2)*4^(k-1) # = 2*(4^(k-1) - 1)/3 + (4*L+2)*4^(k-1) # = ( 2*4^(k-1) - 2 + 3*(4*L+2)*4^(k-1) )/3 # = ( 2*4^(k-1) - 2 + (12*L+6)*4^(k-1) )/3 # = ( (12*L+8)*4^(k-1) - 2 )/3 # = ( (3*L+2)*4^k - 2 )/3 # # with L in level=0 # (4*L+2)*4^(k-1) = 2*4^(k-1) + L*4^k # LevelPoints[k] = 2 + 2*4 + 2*4^2 + ... + 2*4^(k-1) + L*4^k # = ( (3*L+2)*4^k - 2 )/3 # =pod LevelPoints[k] = 2 + 2*4 + 2*4^2 + ... + 2*4^(k-1) + L*4^k = ( (3L+2)*4^k - 2 )/3 Nlevel[k] = LevelPoints[k] - 1 = ( (3L+2)*4^k - 5 )/3 =for GP-DEFINE LevelPoints(k,L) = ( (3*L+2)*4^k - 2 )/3 =for GP-DEFINE Nlevel(k,L) = ( (3*L+2)*4^k - 5 )/3 =for GP-Test LevelPoints(0,4) == 4 =for GP-Test Nlevel(0,4) == 3 =for GP-Test Nlevel(1,4) == 17 =for GP-Test Nlevel(2,4) == 73 =for GP-Test vector(5,L, vector(20,k,my(k=k-1); Nlevel(k))) == vector(5,L, vector(20,k,my(k=k-1); LevelPoints(k) - 1)) =for GP-Test vector(length(Nlevel_samples),k,my(k=k-1); Nlevel(k,1)) == Nlevel_samples The width of level=0 becomes L-1 instead of 0. =cut # LevelWidth[k] = 2*LevelWidth[k-1] + 3 starting LevelWidth[0]=L-1 # = 3 + 3*2 + 3*2^2 + ... + 3*2^(k-1) + (L-1)*2^k # = 3*(2^k - 1) + (L-1)*2^k # = 3*2^k - 3 + (L-1)*2^k # = (L+2)*2^k - 3 =pod LevelWidth[k] = 2*LevelWidth[k-1] + 3 starting LevelWidth[0]=L-1 = 3 + 3*2 + 3*2^2 + ... + 3*2^(k-1) + (L-1)*2^k = (L+2)*2^k - 3 Xlevel[k] = 1 + LevelWidth[k] = (L+2)*2^k - 2 =for GP-DEFINE LevelWidth(k,L) = (L+2)*2^k - 3 =for GP-DEFINE Xlevel(k,L) = (L+2)*2^k - 2 =for GP-Test vector(5,L, vector(20,k,my(k=k-1); Xlevel(k))) == vector(5,L, vector(20,k,my(k=k-1); 1 + LevelWidth(k))) =for GP-Test vector(length(Xlevel_samples),k,my(k=k-1); Xlevel(k,1)) == Xlevel_samples Level=0 as L many points can be thought of as a little block which is replicated in mirror image to make level=1. For example the diagonal 4 example above becomes 8 9 diagonal_length => 4 | | 6--7 10-11 | | . 5 12 . 2--3 14-15 | | 0--1 16-17 The spacing between the parts is had in the tiling by taking a margin of 1/2 at the base and 1 horizontally left and right. =head2 Level Fill =cut # 4/3 * (3L+2) / (L+2)^2 # = 4*(3L+2) / 3*(L+2)^2 # = 4*(3L+2) / 3*(L+2)^2 # =pod The curve doesn't visit all the points in the eighth of the plane below the X=Y diagonal. In general Nlevel+1 many points of the triangular area Xlevel^2/4 are visited, for a filled fraction which approaches a constant Nlevel 4*(3L+2) FillFrac = ------------ -> --------- Xlevel^2 / 4 3*(L+2)^2 For example the default L=1 has FillFrac=20/27=0.74. Or L=2 FillFrac=2/3=0.66. As the diagonal length increases the fraction decreases due to the growing holes in the pattern. =for GP-DEFINE FillFrac(k,L) = Nlevel(k,L) / (Xlevel(k,L)^2 / 4) =for GP-DEFINE FillFracLimit(L) = 4*(3*L+2) / (3* (L+2)^2) =for GP-Test FillFracLimit(1) == 20/27 =for GP-Test FillFracLimit(2) == 2/3 =for GP-Test abs(FillFrac(50,1) - FillFracLimit(1)) < 2^-25 =for GP-Test abs(FillFrac(50,2) - FillFracLimit(2)) < 2^-25 =for GP-Test abs(FillFrac(50,3) - FillFracLimit(3)) < 2^-25 =for GP-Test abs(FillFrac(50,4) - FillFracLimit(4)) < 2^-25 =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::SierpinskiCurveStair-Enew ()> =item C<$path = Math::PlanePath::SierpinskiCurveStair-Enew (diagonal_length =E $L, arms =E $A)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =item C<$n = $path-En_start()> Return 0, the first N in the path. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, ((3*$diagonal_length +2) * 4**$level - 5)/3> as per L
above. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A146882 Nlevel, for level=1 up A033484 Xmax and Ymax in level, being 3*2^n - 2 =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/UlamWarburtonQuarter.pm0000644000175000017500000006155313734026653021750 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::UlamWarburtonQuarter; use 5.004; use strict; use Carp 'croak'; use List::Util 'sum'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'bit_split_lowtohigh', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ { name => 'parts', share_key => 'parts_ulamwarburton_quarter', display => 'Parts', type => 'enum', default => '1', choices => ['1','octant','octant_up' ], choices_display => ['1','Octant','Octant Up' ], description => 'Which parts of the plane to fill.', }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; use constant class_x_negative => 0; use constant class_y_negative => 0; sub diffxy_minimum { my ($self) = @_; return ($self->{'parts'} eq 'octant' ? 0 : undef); } sub diffxy_maximum { my ($self) = @_; return ($self->{'parts'} eq 'octant_up' ? 0 : undef); } # Minimum dir=0 at N=13 dX=2,dY=0. # Maximum dir seems dX=13,dY=-9 at N=149 going top-left part to new bottom # right diagonal. my %dir_maximum_dxdy = (1 => [13,-9], octant => [1,-1], # South-East octant_up => [0,-1], # South ); sub dir_maximum_dxdy { my ($self) = @_; return @{$dir_maximum_dxdy{$self->{'parts'}}}; } sub tree_num_children_list { my ($self) = @_; return ($self->{'parts'} =~ /octant/ ? (0, 1, 2, 3) : (0, 1, 3)); } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } my $parts = ($self->{'parts'} ||= '1'); if (! exists $dir_maximum_dxdy{$parts}) { croak "Unrecognised parts option: ", $parts; } return $self; } # 7 7 7 7 # 6 6 # 7 5 5 7 # 4 # 3 3 5 7 # 2 6 # 1 3 7 7 # # 1+1+3=5 # 5+1+3*5=21 # 1+3 = 4 # 1+3+3+9 = 16 # # 0 # 1 0 +1 # 2 1 +1 <- 1 # 3 2 +3 # 4 5 +1 <- 1 + 4 = 5 # 5 6 +3 # 6 9 +3 # 7 12 +9 # 8 21 <- 1 + 4 + 16 = 21 # 1+3 = 4 power 2 # 1+3+3+9 = 16 power 3 # 1+3+3+9+3+9+9+27 = 64 power 4 # # (1+4+16+...+4^(l-1)) = (4^l-1)/3 # l=1 total=(4-1)/3 = 1 # l=2 total=(16-1)/3 = 5 # l=3 total=(64-1)/3=63/3 = 21 # # n = 1 + (4^l-1)/3 # n-1 = (4^l-1)/3 # 3n-3 = (4^l-1) # 3n-2 = 4^l # # 3^0+3^1+3^1+3^2 = 1+3+3+9=16 # x+3x+3x+9x = 16x = 256 # # 22 # 20 19 18 17 # 12 11 # 21 9 8 16 # 6 # 5 4 7 15 # 2 10 # 1 3 13 14 # sub n_to_xy { my ($self, $n) = @_; ### UlamWarburtonQuarter n_to_xy(): $n if ($n < $self->{'n_start'}) { return; } if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } $n = $n - $self->{'n_start'} + 1; # N=1 basis if ($n == 1) { return (0,0); } my ($depthsum, $nrem, $rowwidth) = _n1_to_depthsum_rem_width($self,$n) or return ($n,$n); # N==nan or N==+inf ### assert: $nrem >= 0 ### assert: $nrem < $width if ($self->{'parts'} eq 'octant_up') { $nrem += ($rowwidth-1)/2; ### assert: $nrem < $width } my @ndigits = digit_split_lowtohigh($nrem,3); my $dhigh = shift(@$depthsum) - 1; # highest term my $x = 0; my $y = 0; foreach my $depthsum (reverse @$depthsum) { # depth terms low to high my $ndigit = shift @ndigits; # N digits low to high ### $depthsum ### $ndigit $x += $depthsum; $y += $depthsum; ### depthsum to xy: "$x,$y" if ($ndigit) { if ($ndigit == 2) { ($x,$y) = (-$y,$x); # rotate +90 } } else { # digit==0 (or undef when run out of @ndigits) ($x,$y) = ($y,-$x); # rotate -90 } ### rotate to: "$x,$y" } ### final: "$x,$y" return ($dhigh + $x, $dhigh + $y); } sub xy_to_n { my ($self, $x, $y) = @_; ### UlamWarburtonQuarter xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); my $parts = $self->{'parts'}; if ($y < 0 || $x < ($parts eq 'octant' ? $y : 0) || ($parts eq 'octant_up' && $x > $y)) { return undef; } if ($x == 0 && $y == 0) { return $self->{'n_start'}; } $x += 1; # pushed away by 1 ... $y += 1; my ($len, $exp) = round_down_pow ($x + $y, 2); if (is_infinite($exp)) { return $exp; } my $depth = my $n = ($x * 0 * $y); # inherit bignum 0 my $rowwidth = $depth + 1; while ($exp-- >= 0) { ### at: "$x,$y n=$n len=$len" # first quadrant square ### assert: $x >= 0 ### assert: $y >= 0 # ### assert: $x < 2*$len # ### assert: $y < 2*$len if ($x >= $len || $y >= $len) { # one of three quarters away from origin # +---+---+ # | 2 | 1 | # +---+---+ # | | 0 | # +---+---+ $x -= $len; $y -= $len; ### shift to: "$x,$y" if ($x) { unless ($y) { return undef; # x==0, y!=0, nothing } } else { if ($y) { return undef; # x!=0, y-=0, nothing } } $depth += $len; if ($x || $y) { $rowwidth *= 3; $n *= 3; if ($y < 0) { ### bottom right, digit 0 ... ($x,$y) = (-$y,$x); # rotate +90 } elsif ($x >= 0) { ### top right, digit 1 ... $n += 1; } else { ### top left, digit 2 ... ($x,$y) = ($y,-$x); # rotate -90 $n += 2; } } } $len /= 2; } ### $n ### $depth if ($self->{'parts'} eq 'octant_up') { $n -= ($rowwidth-1)/2; } return $n + $self->tree_depth_to_n($depth-1); } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### UlamWarburtonQuarter rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 0) { return (1, 0); # all outside first quadrant } if ($x1 < 0) { $x1 *= 0; } if ($y1 < 0) { $y1 *= 0; } # level numbers my $dlo = ($x1 > $y1 ? $x1 : $y1)+1; my $dhi = ($x2 > $y2 ? $x2 : $y2); ### $dlo ### $dhi # round down to level=2^k numbers if ($dlo) { ($dlo) = round_down_pow ($dlo,2); } ($dhi) = round_down_pow ($dhi,2); ### rounded to pow2: "$dlo ".(2*$dhi) return ($self->tree_depth_to_n($dlo-1), $self->tree_depth_to_n(2*$dhi-1)); } #------------------------------------------------------------------------------ use constant tree_num_roots => 1; # ENHANCE-ME: step by the bits, not by X,Y sub tree_n_children { my ($self, $n) = @_; if ($n < $self->{'n_start'}) { return; } my ($x,$y) = $self->n_to_xy($n); my @ret; my $dx = 1; my $dy = 1; foreach (1 .. 4) { if (defined (my $n_child = $self->xy_to_n($x+$dx,$y+$dy))) { if ($n_child > $n) { push @ret, $n_child; } } ($dx,$dy) = (-$dy,$dx); # rotate +90 } return sort {$a<=>$b} @ret; } sub tree_n_parent { my ($self, $n) = @_; if ($n <= $self->{'n_start'}) { return undef; } my ($x,$y) = $self->n_to_xy($n); my $dx = 1; my $dy = 1; foreach (1 .. 4) { if (defined (my $n_parent = $self->xy_to_n($x+$dx,$y+$dy))) { if ($n_parent < $n) { return $n_parent; } } ($dx,$dy) = (-$dy,$dx); # rotate +90 } return undef; } # level = depth+1 = 2^a + 2^b + 2^c + 2^d ... a>b>c>d... # Ndepth = 1 + (-1 # + 4^a # + 3 * 4^b # + 3^2 * 4^c # + 3^3 * 4^d + ...) / 3 sub tree_depth_to_n { my ($self, $depth) = @_; ### tree_depth_to_n(): $depth if (is_infinite($depth)) { return $depth; } unless ($depth >= 0) { return undef; } my $n = $depth*0; # inherit bignum 0 my $pow3 = 1 + $n; # inherit bignum 1 foreach my $bit (reverse bit_split_lowtohigh($depth+1)) { # high to low $n *= 4; if ($bit) { $n += $pow3; $pow3 *= 3; } } if ($self->{'parts'} =~ /octant/) { $n = ($n + (3*$depth-1))/6; } else { $n = ($n-1)/3; } return $n + $self->{'n_start'}; } sub tree_n_to_depth { my ($self, $n) = @_; $n = int($n - $self->{'n_start'} + 1); # N=1 basis if ($n < 1) { return undef; } (my $depthsum, $n) = _n1_to_depthsum_rem_width($self,$n) or return $n; # N==nan or N==+infinity return sum(-1, @$depthsum); } # Return ($aref, $remaining_n). # sum(@$aref) = depth starting depth=1 # # depth+1 = 2^k # Ndepth(depth) = (4^k+2)/3 # 3N-2 = 4^k # NdepthOct(depth) = ((4^k+2)/3 + 2^k)/2 # 6N-2 = 4^k + 3*2^k # sub _n1_to_depthsum_rem_width { my ($self, $n) = @_; ### _n1_to_depthsum_rem_width(): $n my $octant = ($self->{'parts'} =~ /octant/); my ($power, $exp) = round_down_pow (($octant ? 6 : 3)*$n - 2, 4); if (is_infinite($exp)) { return; } ### $power ### $exp ### pow base: ($power - 1)/3 + 1 { my $sub = ($power + 2)/3; # (power-1)/3 + 1 if ($octant) { $sub = ($sub + 2**$exp) / 2; ### prospective sub: $sub ### assert: $sub == ($power + 3 * 2 ** $exp + 2)/6 if ($sub > $n) { $exp -= 1; $power /= 4; $sub = ($power + 3*2**$exp + 2)/6; } } ### assert: $sub <= $n $n -= $sub; } ### n less pow base: $n my @depthsum = (2**$exp); # find the cumulative levelpoints total <= $n, being the start of the # level containing $n # my $factor = 1; while (--$exp >= 0) { $power /= 4; my $sub = $power * $factor; if ($octant) { $sub = ($sub + 2**$exp)/2; } ### $sub my $rem = $n - $sub; ### $n ### $power ### $factor ### consider subtract: $sub ### $rem if ($rem >= 0) { $n = $rem; push @depthsum, 2**$exp; $factor *= 3; } } ### _n1_to_depthsum_rem_width() result ... ### @depthsum ### remaining n: $n ### assert: $n >= 0 ### assert: $n < $factor return (\@depthsum, $n, $factor); } # at 0,2 turn and new height limit # at 1 keep existing depth limit # N=30 rem=1 = 0,1 depth=11=8+2+1=1011 width=9 # sub tree_n_to_subheight { my ($self, $n) = @_; ### tree_n_to_subheight(): $n $n = int($n - $self->{'n_start'} + 1); # N=1 basis if ($n < 1) { return undef; } my ($depthsum, $nrem, $rowwidth) = _n1_to_depthsum_rem_width($self,$n) or return $n; # N==nan or N==+infinity ### $depthsum ### $nrem if ($self->{'parts'} eq 'octant_up') { $nrem += ($rowwidth-1)/2; } my $sub = pop @$depthsum; while (@$depthsum && _divrem_mutate($nrem,3) == 1) { $sub += pop @$depthsum; } if (@$depthsum) { return $depthsum->[-1] - 1 - $sub; } else { return undef; # $nrem all 1-digits } } #------------------------------------------------------------------------------ # levels sub level_to_n_range { my ($self, $level) = @_; return ($self->{'n_start'}, $self->tree_depth_to_n_end(2**($level+1) - 2)); } sub n_to_level { my ($self, $n) = @_; my $depth = $self->tree_n_to_depth($n); if (! defined $depth) { return undef; } my ($pow, $exp) = round_down_pow ($depth+1, 2); return $exp; } #------------------------------------------------------------------------------ 1; __END__ # Octant depth 0 1 1 # 15 1 2 2 # 14 2 3 3,4 # 9 3 4 5 # 7 13 4 5 6,7 # 5 5 6 8,9 # 4 6 12 6 7 10,11,12,13,14 # 2 8 7 8 15 # 1 3 10 11 # # Ndepth 2*oct-depth = quad # oct = (quad+depth)/2 =for stopwords eg Ryde Math-PlanePath Ulam Warburton Ndepth Nend ie OEIS Octant octant =head1 NAME Math::PlanePath::UlamWarburtonQuarter -- growth of a 2-D cellular automaton =head1 SYNOPSIS use Math::PlanePath::UlamWarburtonQuarter; my $path = Math::PlanePath::UlamWarburtonQuarter->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XXThis is the pattern of a cellular automaton studied by Ulam and Warburton, confined to a quarter of the plane and oriented diagonally. Cells are numbered by growth tree row and anti-clockwise within the row. =cut # math-image --path=UlamWarburtonQuarter --all --output=numbers --size=70x15 =pod 14 | 81 80 79 78 75 74 73 72 13 | 57 56 55 54 12 | 82 48 47 77 76 46 45 71 11 | 40 39 10 | 83 49 36 35 34 33 44 70 9 | 58 28 27 53 8 | 84 85 37 25 24 32 68 69 7 | 22 6 | 20 19 18 17 23 31 67 66 5 | 12 11 26 52 4 | 21 9 8 16 29 30 43 65 3 | 6 38 2 | 5 4 7 15 59 41 42 64 1 | 2 10 50 51 Y=0| 1 3 13 14 60 61 62 63 +---------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 The growth rule is a given cell grows diagonally NE, NW, SE and SW, but only if the new cell has no neighbours and is within the first quadrant. So the initial cell "a" is N=1, | | a initial cell, depth=0 +---- It's confined to the first quadrant so can only grow NE as "b", | b | a "b" depth=1 +------ Then the next row "c" cells can go in three directions SE, NE, NW. These cells are numbered anti-clockwise around from the SE as N=3,N=4,N=5. | c c | b | a c "c" depth=2 +--------- The "d" cell is then only a single on the leading diagonal, since the other diagonals all already have neighbours (the existing "c" cells). | d | c c depth=3 | b | a c +--------- | e e | d | c c e depth=4 | b | a c +----------- | f f | e e | d | c c e depth=5 | b f | a c +------------- | g g g g | f f | g e e g | d | c c e g depth=6 | b f | a c g g +------------- In general the pattern always always grows by 1 along the X=Y leading diagonal. The point on that diagonal is the middle of row depth=X. The pattern expands into the sides with a self-similar diamond shaped pattern filling 6 of 16 cells in any 4x4 square block. =head2 Tree Row Ranges Counting depth=0 as the N=1 at the origin, depth=1 as the next N=2, etc, the number of new cells added in the tree row is rowwidth(depth) = 3^(count_1_bits(depth+1) - 1) =for GP-DEFINE rowwidth(depth) = 3^(hammingweight(depth+1) - 1) =for GP-Test rowwidth(0) == 1 /* a */ =for GP-Test rowwidth(1) == 1 /* b */ =for GP-Test rowwidth(2) == 3 /* c */ =for GP-Test rowwidth(3) == 1 /* d */ So depth=0 has 3^(1-1)=1 cells, as does depth=1 which is N=2. Then depth=2 has 3^(2-1)=3 cells N=3,N=4,N=5 because depth+1=3=0b11 has two 1 bits in binary. The N row start and end is the cumulative total of those before it, Ndepth(depth) = 1 + rowwidth(0) + ... + rowwidth(depth-1) Nend(depth) = rowwidth(0) + ... + rowwidth(depth) For example depth=2 ends at N=(1+1+3)=5. =for GP-DEFINE Ndepth(depth) = 1 + sum(i=0,depth-1, rowwidth(i)) =for GP-DEFINE Nend(depth) = sum(i=0,depth, rowwidth(i)) =for GP-Test Nend(2) == 5 depth Ndepth rowwidth Nend 0 1 1 1 1 2 1 2 2 3 3 5 3 6 1 6 4 7 3 9 5 10 3 12 6 13 9 21 7 22 1 22 8 23 3 25 =for GP-Test vector(9,depth,my(depth=depth-1); Ndepth(depth)) == [1,2,3,6,7,10,13,22,23] =for GP-Test vector(9,depth,my(depth=depth-1); rowwidth(depth)) == [1,1,3,1,3,3,9,1,3] =for GP-Test vector(9,depth,my(depth=depth-1); Nend(depth)) == [1,2,5,6,9,12,21,22,25] At row depth+1 = power-of-2 the Ndepth sum is Ndepth(depth) = 1 + (4^a-1)/3 for depth+1 = 2^a For example depth=3 is depth+1=2^2 starts at N=1+(4^2-1)/3=6, or depth=7 is depth+1=2^3 starts N=1+(4^3-1)/3=22. =for GP-Test Ndepth(3) == 6 =for GP-Test Ndepth(7) == 22 Further bits in the depth+1 contribute powers-of-4 with a tripling for each bit above it. So if depth+1 has bits a,b,c,d,etc from high to low then depth+1 = 2^a + 2^b + 2^c + 2^d ... a>b>c>d... Ndepth = 1 + (-1 + 4^a + 3 * 4^b + 3^2 * 4^c + 3^3 * 4^d + ...) / 3 For example depth=5 is depth+1=6 = 2^2+2^1 is Ndepth = 1+(4^2-1)/3 + 4^1 = 10. Or depth=6 is depth+1=7 = 2^2+2^1+2^0 is Ndepth = 1+(4^2-1)/3 + 4^1 + 3*4^0 = 13. =head2 Self-Similar Replication The square shape growth to depth=2^level-2 repeats the pattern to the preceding depth=2^(level-1)-2 three times. For example, | d d c c depth=6 = 2^3-2 | d c triplicates | d d c c depth=2 = 2^2-2 | * | a a b b | a b | a a b b +-------------------- The 3x3 square "a" repeats, pointing SE, NE and NW as "b", "c" and "d". This resulting 7x7 square then likewise repeats. The points in the path here are numbered by tree rows rather than by this sort of replication, but the replication helps to see the structure of the pattern. =head2 Octant Option C 'octant'> confines the pattern to the first eighth of the plane 0E=YE=X. =cut # math-image --path=UlamWarburtonQuarter,parts=octant --all --output=numbers --size=75x15 =pod parts => "octant" 14 | 50 13 | 36 12 | 31 49 11 | 26 10 | 24 30 48 9 | 19 35 8 | 17 23 46 47 7 | 15 6 | 14 16 22 45 44 5 | 9 18 34 4 | 7 13 20 21 29 43 3 | 5 25 2 | 4 6 12 37 27 28 42 1 | 2 8 32 33 Y=0 | 1 3 10 11 38 39 40 41 +------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 In this arrangement N=1,2,4,5,7,etc on the leading diagonal is the last N of each row (C). =head2 Upper Octant Option C 'octant_up'> confines the pattern to the upper octant 0E=XE=Y of the first quadrant. =cut # math-image --path=UlamWarburtonQuarter,parts=octant_up --all --output=numbers --size=75x15 =pod parts => "octant_up" 14 | 46 45 44 43 40 39 38 37 13 | 35 34 33 32 12 | 47 30 29 42 41 28 27 11 | 26 25 10 | 48 31 23 22 21 20 9 | 36 19 18 8 | 49 50 24 17 16 7 | 15 6 | 13 12 11 10 5 | 9 8 4 | 14 7 6 3 | 5 2 | 4 3 1 | 2 Y=0 | 1 +---------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 In this arrangement N=1,2,3,5,6,etc on the leading diagonal is the first N of each row (C). =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same pattern. For example to start at 0, =cut # math-image --path=UlamWarburtonQuarter,n_start=0 --expression='i<22?i:0' --output=numbers =pod n_start => 0 7 | 21 6 | 19 18 17 16 5 | 11 10 4 | 20 8 7 15 3 | 5 2 | 4 3 6 14 1 | 1 9 Y=0| 0 2 12 13 +------------------------- X=0 1 2 3 4 5 6 7 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::UlamWarburtonQuarter-Enew ()> =item C<$path = Math::PlanePath::UlamWarburtonQuarter-Enew (parts =E $str, n_start =E $n)> Create and return a new path object. C can be 1 first quadrant, the default "octant" first eighth "octant_up" upper eighth =back =head2 Tree Methods =over =item C<@n_children = $path-Etree_n_children($n)> Return the children of C<$n>, or an empty list if C<$n> has no children (including when C<$n E 1>, ie. before the start of the path). The children are the cells turned on adjacent to C<$n> at the next row. The way points are numbered means that when there's multiple children they're consecutive N values, for example at N=12 the children 19,20,21. =item C<$n_parent = $path-Etree_n_parent($n)> Return the parent node of C<$n>, or C if C<$n E= 1> (the start of the path). =back =head2 Tree Descriptive Methods =over =item C<@nums = $path-Etree_num_children_list()> Return a list of the possible number of children at the nodes of C<$path>. This is the set of possible return values from C. parts tree_num_children_list() ----- ------------------------ 1 0, 1, 3 octant 0, 1, 2, 3 octant_up 0, 1, 2, 3 The octant forms have 2 children when branching from the leading diagonal, otherwise 0,1,3. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<($n_start, tree_depth_to_n_end(2**($level+1) - 2))>. =back =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path includes =over L (etc) =back parts=1 (the default) A147610 num cells in row, tree_depth_to_width() A151920 total cells to depth, tree_depth_to_n_end() parts=octant,octant_up A079318 num cells in row, tree_depth_to_width() =head1 SEE ALSO L, L, L, L L (a similar binary ones-count related calculation) =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/GcdRationals.pm0000644000175000017500000010306713734026671020151 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # A003989 diagonals from (1,1) # A109004 0,1,1,2,1,2,3,1,1,3,4,1,2,1,4,5,1,1,1,1 # gcd by diagonals (0,0)=0 # (1,0)=1 (0,1)=1 # (2,0)=2 (1,1)=1 (0,2)=2 # A050873 gcd rows n>=1, k=1..n # 1,1,2,1,1,3,1,2,1,4,1,1,1,1,5,1,2,3,2,1,6,1,1,1, # add 0,1,0,1,1,0,1,1,1,0,1,1,1,1,0,1,1,1,1,1,0 A023532 0 at m(m+3)/2 # IntXY 1,0,2,0,0,3,0,1,0,4,0,0,0,0,5,0,1,2,1,0,6, # IntXY+1 2,1,3,1,1,4,1,2,1,5,1,1,1,1,6,1,2,3,2,1,7 # diff 1,0,1,0,0,1,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1 A023531 # A178340 1,2,1,3,1,1,4,1,2,1,5,1,1,1,1,6,1,2,3,2,1,7,1,1 Bernoulli # T(n,m) = A003989(n-m+1,m) m>=1, except when factor cancels # diagonals_down even/odd in wedges, and other modulo # math-image --path=GcdRationals --expression='i<30*31/2?i:0' --text --size=40 # math-image --path=GcdRationals --output=numbers --expression='i<100?i:0' # math-image --path=GcdRationals --all --output=numbers # Y = v = j/g # X = (g-1)*v + u # = (g-1)*j/g + i/g # = ((g-1)*j + i)/g # j=5 11 ... # j=4 7 8 9 10 # j=3 4 5 6 # j=2 2 3 # j=1 1 # # N = (1/2 d^2 - 1/2 d + 1) # = (1/2*$d**2 - 1/2*$d + 1) # = ((1/2*$d - 1/2)*$d + 1) # j = 1/2 + sqrt(2 * $n + -7/4) # = [ 1 + 2*sqrt(2 * $n + -7/4) ] /2 # = [ 1 + sqrt(8*$n -7) ] /2 # # Primes # i=3*a,j=3*b # N=3*a*(3*b-1)/2 package Math::PlanePath::GcdRationals; use 5.004; use strict; use Carp 'croak'; #use List::Util 'min','max'; *min = \&Math::PlanePath::_min; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_sqrtint = \&Math::PlanePath::_sqrtint; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; *_divrem = \&Math::PlanePath::_divrem; use Math::PlanePath::CoprimeColumns; *_coprime = \&Math::PlanePath::CoprimeColumns::_coprime; # uncomment this to run the ### lines #use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant x_minimum => 1; use constant y_minimum => 1; use constant gcdxy_maximum => 1; # no common factor use constant parameter_info_array => [ { name => 'pairs_order', display => 'Pairs Order', type => 'enum', default => 'rows', choices => ['rows','rows_reverse','diagonals_down','diagonals_up'], choices_display => ['Rows', 'Rows Reverse', 'Diagonals Down', 'Diagonals Up'], description => 'Order in the i,j pairs.', } ]; sub absdy_minimum { my ($self) = @_; return ($self->{'pairs_order'} eq 'diagonals_down' ? 1 : 0); } { my %dir_minimum_dxdy = (rows => [1,0], # N=4 to N=5 horiz rows_reverse => [1,0], # N=1 to N=2 horiz diagonals_down => [0,1], # N=1 to N=2 vertical, nothing less diagonals_up => [1,0], # N=4 to N=5 horiz ); sub dir_minimum_dxdy { my ($self) = @_; return @{$dir_minimum_dxdy{$self->{'pairs_order'}}}; } } { my %dir_maximum_dxdy = (rows => [1,-1], # N=2 to N=3 SE diagonal rows_reverse => [2,-1], # N=3 to N=4 dX=2,dY=-1 diagonals_down => [1,-1], # N=5 to N=6 SE diagonal diagonals_up => [2,-1], # N=9 to N=10 dX=2,dY=-1 ); sub dir_maximum_dxdy { my ($self) = @_; return @{$dir_maximum_dxdy{$self->{'pairs_order'}}}; } } #------------------------------------------------------------------------------ # all rationals X,Y >= 1 no common factor use Math::PlanePath::DiagonalRationals; *xy_is_visited = Math::PlanePath::DiagonalRationals->can('xy_is_visited'); sub new { my $self = shift->SUPER::new(@_); my $pairs_order = ($self->{'pairs_order'} ||= 'rows'); (($self->{'pairs_order_n_to_xy'} = $self->can("_pairs_order__${pairs_order}__n_to_xy")) && ($self->{'pairs_order_xygr_to_n'} = $self->can("_pairs_order__${pairs_order}__xygr_to_n"))) or croak "Unrecognised pairs_order: ",$pairs_order; return $self; } sub n_to_xy { my ($self, $n) = @_; ### GcdRationals n_to_xy(): "$n" if ($n < 1) { return; } if (is_infinite($n)) { return ($n,$n); } # what to do for fractional $n? { my $int = int($n); if ($n != $int) { ### frac ... my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } my ($x,$y) = $self->{'pairs_order_n_to_xy'}->($n); # if ($self->{'pairs_order'} eq 'rows' # || $self->{'pairs_order'} eq 'rows_reverse') { # $y = int((sqrt(8*$n-7) + 1) / 2); # $x = $n - ($y - 1)*$y/2; # # if ($self->{'pairs_order'} eq 'rows_reverse') { # $x = $y - ($x-1); # } # # # require Math::PlanePath::PyramidRows; # # my ($x,$y) = Math::PlanePath::PyramidRows->new(step=>1)->n_to_xy($n); # # $x+=1; # # $y+=1; # # } else { # require Math::PlanePath::DiagonalsOctant; # ($x,$y) = Math::PlanePath::DiagonalsOctant->new->n_to_xy($n); # if ($self->{'pairs_order'} eq 'diagonals_up') { # my $d = $x+$y; # top 0,d measure diag down by x # my $e = int($d/2); # end e,d-e # ($x,$y) = ($e-$x, $d - ($e-$x)); # } # $x+=1; # $y+=1; # } ### triangle: "$x,$y" my $gcd = _gcd($x,$y); $x /= $gcd; $y /= $gcd; ### $gcd ### reduced: "$x,$y" ### push out to x: $x + ($gcd-1)*$y return ($x + ($gcd-1)*$y, $y); } sub _pairs_order__rows__n_to_xy { my ($n) = @_; my $y = int( (_sqrtint(8*$n-7) + 1) / 2 ); return ($n - ($y-1)*$y/2, $y); } sub _pairs_order__rows_reverse__n_to_xy { my ($n) = @_; my $y = int( (_sqrtint(8*$n-7) + 1) / 2 ); return ($y*($y+1)/2 + 1 - $n, $y); } sub _pairs_order__diagonals_down__n_to_xy { my ($n) = @_; my $d = _sqrtint($n-1); # eg. N=10 d=3 $n -= $d*($d+1); # eg. d=3 subtract 12 if ($n > 0) { return ($n, 2 - $n + 2*$d); } else { return ($n + $d, 1 - $n + $d); } } sub _pairs_order__diagonals_up__n_to_xy { my ($n) = @_; my $d = _sqrtint($n-1); $n -= $d*($d+1); if ($n > 0) { return (-$n + $d + 2, $n + $d); } else { return (1 - $n, $n + 2*$d); } } # X=(g-1)*v+u # Y=v # u = x % y # i = u*g # = (x % y)*g # = (x % y)*(floor(x/y)+1) # # Better: # g-1 = floor(x/y) # Y = j/g # X = ((g-1)*j + i)/g # j = Y*g # (g-1)*j + i = X*g # i = X*g - (g-1)*j # = X*g - (g-1)*Y*g # N = i + j*(j-1)/2 # = X*g - (g-1)*Y*g + Y*g*(Y*g-1)/2 # = X*g + Y*g * (-(g-1) + (Y*g-1)/2) # but Y*g-1 may be odd # = X*g + Y*g * (Y*g-1 - (2g-2))/2 # = X*g + Y*g * (Y*g-1 - 2g + 2))/2 # = X*g + Y*g * (Y*g - 2g + 1))/2 # = X*g + Y*g * ((Y-2)*g + 1) / 2 # = g * [ X + Y*((Y-2)*g + 1) / 2 ] # # N = X*g - (g-1)*Y*g + Y*g*(Y*g-1)/2 # = [ 2*X*g - 2*(g-1)*Y*g + Y*g*(Y*g-1) ] / 2 # = [ 2*X - 2*(g-1)*Y + Y*(Y*g-1) ] * g / 2 # = [ 2*X + Y*(- 2*(g-1) + (Y*g-1)) ] * g / 2 # = [ 2*X + Y*(-2g + 2 + Y*g - 1) ] * g / 2 # = [ 2*X + Y*((Y-2)*g + 1) ] * g / 2 # = X*g + [(Y-2)*g + 1]*Y*g/2 # # if Y and g both odd then (Y-2)*g+1 is odd+1 so even # q=int(x/y) # x = qy+r qy=x-r # r = x % y # g-1 = q # g = q+1 # g*y = (q+1)*y # = q*y + y # = x-r + y # # N = X*g + Y*g * ((Y-2)*g + 1) / 2 # = X*g + (X+Y-r) * ((Y-2)*g + 1) / 2 # = X*g + (X+Y-r) * ((g*Y-2*g + 1) / 2 # = X*g + (X+Y-r) * (((X+Y-r) - 2*g + 1) / 2 # ... not much better sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### GcdRationals xy_to_n(): "$x,$y" if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } if ($x < 1 || $y < 1 || ! _coprime($x,$y)) { return undef; } my ($p,$r) = _divrem ($x,$y); ### $x ### $y ### $p ### $r return $self->{'pairs_order_xygr_to_n'}->($x,$y,$p+1,$r); # my $g = int($x/$y) + 1; # ### g: "$g" # ### halve: ''.$y*(($y-2)*$g + 1) # return $self->{'pairs_order_xygr_to_n'}->($x,$y,$g); } sub _pairs_order__rows__xygr_to_n { my ($x,$y,$g,$r) = @_; ### j: $x+$y-$r ### i: $g*$r $x += $y; $x -= $r; # j=X+Y-r return $x*($x-1)/2 + $g*$r; # i=g*r } # i = X*g - (g-1)*g*Y # = X*g - (g-1)*(X+Y-r) # = X*g - g*(X+Y-r) + *(X+Y-r) # = X*g - g*X - g*Y + g*r + (X+Y-r) # = X*g - g*X - (X+Y-r) + g*r + (X+Y-r) # = g*r # # N = j-i+1 + j*(j-1)/2 # = [2j-2i + 2 + $j*($j-1)] / 2 # = [-2i + 2 + 2j+ j*(j-1)] / 2 # = [-2i + 2 + j*(j-1+2)] / 2 # = [-2i + 2 + j*(j+1)] / 2 # = 1-i + j*(j+1)/2 # sub _pairs_order__rows_reverse__xygr_to_n { my ($x,$y,$g,$r) = @_; $y += $x; $y -= $r; # j = X+Y-r if ($r == 0) { # Case r=0 which is Y=1 becomes i=0 and that doesn't reverse to the # correct place by j-i+1. Can either set $r=1,$g+=1 or leave $r==0 # alone and adjust $y. $y -= 2; } return $y*($y+1)/2 - $r*$g + 1; } # d = (i-1)+(j-1)+1 # = i+j-1 # = rg + X+Y-r - 1 # = X+Y + r*(g-1) - 1 # if r==0 Y==1 then r=1 g=X-1 # i = r*g = X-1 # j = X+Y-r = X+1-1 = X-1 # d = i+j-1 # = 2X-2 # N = (d*d - (d%2))/4 + X-1 # = ((2X-2)*(2X-2) - 0)/4 + X-1 # = (X-1)^2 + X-1 # sub _pairs_order__diagonals_down__xygr_to_n { my ($x,$y,$g,$r) = @_; $y += $x + $r*($g-1) - 1; # d=X+Y + r*(g-1) - 1 if ($r == 0) { $y *= 2; # d=2*g-2 } return ($y*$y - ($y % 2))/4 + $r*$g; } sub _pairs_order__diagonals_up__xygr_to_n { my ($x,$y,$g,$r) = @_; $y += $x + $r*($g-1); # d=X+Y + r*(g-1) if ($r == 0) { $y = 2*$x - 1; } return ($y*$y - ($y % 2))/4 - $r*$g + 1; } # increase in rows, so right column # in column increase within g wedge, then drop # # int(x2/y2) is slope of top of the wedge containing x2,y2 # g = int(x2/y2)+1 is the slope of the bottom of that wedge # yw = floor(x2 / g) is the Y of that bottom # N at x2,yw,g+1 is the top of the wedge underneath, bigger g smaller y # or x2,y2,g is the top-right corner # # Eg. # x=19 y=2 to 4 # g=int(19/4)+1=5 # yw=int(19/5)=3 # N(19,3,6)= # # at X=Y+1 g=2 # nhi = (y*((y-2)*g + 1) / 2 + x)*g # = (y*((y-2)*2 + 1) / 2 + y+1)*2 # = (y*(2y-4 + 1) / 2 + y+1)*2 # = (y*(2y-3) / 2 + y+1)*2 # = y*(2y-3) + 2y+2 # = 2y^2 - 3y + 2y + 2 # = 2y^2 - y + 2 # = y*(2y-1) + 2 # 11 12 13 14 47 49 51 53 108 111 114 117 194 198 202 206 # 7 9 30 34 69 75 124 132 195 205 # 4 5 17 19 39 42 70 74 110 115 159 165 217 # 2 8 18 32 50 72 98 128 162 200 # 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 # 206=20*19/2+16 i=16,j=20 gcd=4 # 19,5 is slope=floor(19/5)=3 so g=4 # # 205=20*19/2+15 i=15,j=20 gcd=5 # 19,4 is slope=floor(19/4)=4 so g=5 # # 217=21*20/2 + 7, i=21,j=7 gcd=7 # 19,3 is slope=floor(19/3)=6 so g=7 # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### $x2 ### $y2 if ($x2 < 1 || $y2 < 1) { return (1, 0); # outside quadrant } if ($x1 < 1) { $x1 = 1; } if ($y1 < 1) { $y1 = 1; } if ($self->{'pairs_order'} =~ /^diagonals/) { my $d = $x2 + max($x2,$y2); return (1, int($d*($d+($d%2)) / 4)); # N end of diagonal d } my $nhi; { my $c = max($x2,$y2); $nhi = _pairs_order__rows__xygr_to_n($c,$c,2,0); # my $rev = ($self->{'pairs_order'} eq 'rows_reverse'); # my $slope = int($x2/$y2); # my $g = $slope + 1; # # # within top row # { # my $x; # if ($rev) { # if ($slope > 0) { # $x = max ($x1, $y2*$slope); # left-most within this wedge # } else { # $x = $x1; # top-left corner # } # } else { # # pairs_order=rows # $x = $x2; # top-right corner # } # $nhi = $self->{'pairs_order_xygr_to_n'}->($x, $y2, $g, 0); # # ### $slope # ### $g # ### x for hi: $x # ### nhi for x,y2: $nhi # } # # # within x2 column, top of wedge below # # # my $yw = int(($x2+$g-1) / $g); # rounded up # if ($yw >= $y1) { # $nhi = max ($nhi, $self->{'pairs_order_xygr_to_n'}->($x2,$yw,$g+1,0)); # # ### $yw # ### nhi_wedge: $self->{'pairs_order_xygr_to_n'}->($x2,$yw,$g+1,0) # } # my $yw = int($x2 / $g) - ($g==1); # below X=Y diagonal when g==1 # if ($yw >= $y1) { # $g = int($x2/$yw) + 1; # perhaps went across more than one wedge # $nhi = max ($nhi, # ($yw*(($yw-2)*($g+1) + 1) / 2 + $x2)*($g+1)); # ### $yw # ### nhi_wedge: ($yw*(($yw-2)*($g+1) + 1) / 2 + $x2)*($g+1) # } } my $nlo; { $nlo = _pairs_order__rows__xygr_to_n(1,$x1, 1, $x1-1); # my $g = int($x1/$y1) + 1; # $nlo = $self->{'pairs_order_xygr_to_n'}->($x1,$y1,$g,0); # # ### glo: $g # ### $nlo # # if ($g > 1) { # my $yw = max (int($x1 / $g), # 1); # ### $yw # if ($yw <= $y2) { # $g = int($x1/$yw); # no +1, and perhaps up across more than one wedge # $nlo = min ($nlo, $self->{'pairs_order_xygr_to_n'}->($x1,$yw,$g,0)); # ### glo_wedge: $g # ### nlo_wedge: $self->{'pairs_order_xygr_to_n'}->($x1,$yw,$g,0) # } # } # if ($nlo < 1) { # $nlo = 1; # } } ### $nhi ### $nlo return ($nlo, $nhi); } sub _gcd { my ($x, $y) = @_; #### _gcd(): "$x,$y" # bgcd() available in even the earliest Math::BigInt if ((ref $x && $x->isa('Math::BigInt')) || (ref $y && $y->isa('Math::BigInt'))) { return Math::BigInt::bgcd($x,$y); } $x = abs(int($x)); $y = abs(int($y)); unless ($x > 0) { return $y; # gcd(0,y)=y for y>=0, giving gcd(0,0)=0 } if ($y > $x) { $y %= $x; } for (;;) { ### assert: $x >= 1 if ($y <= 1) { return ($y == 0 ? $x # gcd(x,0)=x : 1); # gcd(x,1)=1 } ($x,$y) = ($y, $x % $y); } } # # old code, rows only ... # sub rect_to_n_range { # my ($self, $x1,$y1, $x2,$y2) = @_; # ### rect_to_n_range(): "$x1,$y1 $x2,$y2" # # $x1 = round_nearest ($x1); # $y1 = round_nearest ($y1); # $x2 = round_nearest ($x2); # $y2 = round_nearest ($y2); # # ($x1,$x2) = ($x2,$x1) if $x1 > $x2; # ($y1,$y2) = ($y2,$y1) if $y1 > $y2; # ### $x2 # ### $y2 # # if ($x2 < 1 || $y2 < 1) { # return (1, 0); # outside quadrant # } # # if ($x1 < 1) { $x1 = 1; } # if ($y1 < 1) { $y1 = 1; } # # my $g = int($x2/$y2) + 1; # my $nhi = ($y2*(($y2-2)*$g + 1) / 2 + $x2)*$g; # ### ghi: $g # ### $nhi # # my $yw = int($x2 / $g) - ($g==1); # below X=Y diagonal when g==1 # if ($yw >= $y1) { # $g = int($x2/$yw) + 1; # perhaps went across more than one wedge # $nhi = max ($nhi, # ($yw*(($yw-2)*($g+1) + 1) / 2 + $x2)*($g+1)); # ### $yw # ### nhi_wedge: ($yw*(($yw-2)*($g+1) + 1) / 2 + $x2)*($g+1) # } # # $g = int($x1/$y1) + 1; # my $nlo = ($y1*(($y1-2)*$g + 1) / 2 + $x1)*$g; # # ### glo: $g # ### $nlo # # if ($g > 1) { # $yw = max (int($x1 / $g), # 1); # ### $yw # if ($yw <= $y2) { # $g = int($x1/$yw); # no +1, and perhaps up across more than one wedge # $nlo = min ($nlo, # ($yw*(($yw-2)*$g + 1) / 2 + $x1)*$g); # ### glo_wedge: $g # ### nlo_wedge: ($yw*(($yw-2)*$g + 1) / 2 + $x1)*$g # } # } # # return ($nlo, $nhi); # } 1; __END__ =for stopwords eg Ryde OEIS ie Math-PlanePath GCD gcd gcds gcd/2 gcd-1 j/gcd Fortnow coprime triangulars numberings pronics incrementing =head1 NAME Math::PlanePath::GcdRationals -- rationals by triangular GCD =head1 SYNOPSIS use Math::PlanePath::GcdRationals; my $path = Math::PlanePath::GcdRationals->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis path enumerates X/Y rationals using a method by Lance Fortnow taking a greatest common divisor out of a triangular position. =over L =back The attraction of this approach is that it's both efficient to calculate and visits blocks of X/Y rationals using a modest range of N values, roughly a square N=2*max(num,den)^2 in the default rows style. 13 | 79 80 81 82 83 84 85 86 87 88 89 90 12 | 67 71 73 77 278 11 | 56 57 58 59 60 61 62 63 64 65 233 235 10 | 46 48 52 54 192 196 9 | 37 38 40 41 43 44 155 157 161 8 | 29 31 33 35 122 126 130 7 | 22 23 24 25 26 27 93 95 97 99 101 103 6 | 16 20 68 76 156 5 | 11 12 13 14 47 49 51 53 108 111 114 4 | 7 9 30 34 69 75 124 3 | 4 5 17 19 39 42 70 74 110 2 | 2 8 18 32 50 72 98 1 | 1 3 6 10 15 21 28 36 45 55 66 78 91 Y=0 | -------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 The mapping from N to rational is N = i + j*(j-1)/2 for upper triangle 1 <= i <= j gcd = GCD(i,j) rational = i/j + gcd-1 which means X=numerator Y=denominator are X = (i + j*(gcd-1))/gcd = j + (i-j)/gcd Y = j/gcd The i,j position is a numbering of points above the X=Y diagonal by rows in the style of L with step=1, but starting from i=1,j=1. j=4 | 7 8 9 10 j=3 | 4 5 6 j=2 | 2 3 j=1 | 1 +------------- i=1 2 3 4 If GCD(i,j)=1 then X/Y is simply X=i,Y=j unchanged. This means fractions S 1> are numbered by rows with increasing numerator, but skipping positions where i,j have a common factor. The skipped positions where i,j have a common factor become rationals S1>, ie. below the X=Y diagonal. The integer part is GCD(i,j)-1 so S. For example N=51 is at i=6,j=10 by rows common factor gcd(6,10)=2 so rational R = 2-1 + 6/10 = 1+3/5 = 8/5 ie. X=8,Y=5 If j is prime then gcd(i,j)=1 and so X=i,Y=j. This means that in rows with prime Y are numbered by consecutive N across to the X=Y diagonal. For example in row Y=7 above N=22 to N=27. =head2 Triangular Numbers XN=1,3,6,10,etc along the bottom Y=1 row is the triangular numbers N=k*(k-1)/2. Such an N is at i=k,j=k and has gcd(i,j)=k which divides out to Y=1. N=k*(k-1)/2 i=k,j=k Y = j/gcd = 1 on the bottom row X = (i + j*(gcd-1)) / gcd = (k + k*(k-1)) / k = k-1 successive points on that bottom row N=1,2,4,7,11,etc in the column at X=1 immediately follows each of those bottom row triangulars, ie. N+1. N in X=1 column = Y*(Y-1)/2 + 1 =head2 Primes If N is prime then it's above the sloping line X=2*Y. If N is composite then it might be above or below, but the primes are always above. Here's the table with dots "..." marking the X=2*Y line. primes and composites above | 6 | 16 20 68 | .... X=2*Y 5 | 11 12 13 14 47 49 51 53 .... | .... 4 | 7 9 30 34 .... 69 | .... 3 | 4 5 17 19 .... 39 42 70 only | .... composite 2 | 2 8 .... 18 32 50 below | .... 1 | 1 ..3. 6 10 15 21 28 36 45 55 | .... Y=0 | .... --------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 Values below X=2*Y such as 39 and 42 are always composite. Values above such as 19 and 30 are either prime or composite. Only X=2,Y=1 is exactly on the line, which is prime N=3 as it happens. The rest of the line X=2*k,Y=k is not visited since common factor k would mean X/Y is not a rational in least terms. This pattern of primes and composites occurs because N is a multiple of gcd(i,j) when that gcd is odd, or a multiple of gcd/2 when that gcd is even. N = i + j*(j-1)/2 gcd = gcd(i,j) N = gcd * (i/gcd + j/gcd * (j-1)/2) when gcd odd gcd/2 * (2i/gcd + j/gcd * (j-1)) when gcd even If gcd odd then either j/gcd or j-1 is even, to take the "/2" divisor. If gcd even then only gcd/2 can come out as a factor since taking out the full gcd might leave both j/gcd and j-1 odd and so the "/2" not an integer. That happens for example to N=70 N = 70 i = 4, j = 12 for 4 + 12*11/2 = 70 = N gcd(i,j) = 4 but N is not a multiple of 4, only of 4/2=2 Of course knowing gcd or gcd/2 is a factor of N is only useful when that factor is 2 or more, so odd gcd >= 2 means gcd >= 3 even gcd with gcd/2 >= 2 means gcd >= 4 so N composite when gcd(i,j) >= 3 If gcdE3 then the "factor" coming out is only 1 and says nothing about whether N is prime or composite. There are both prime and composite N with gcdE3, as can be seen among the values above the X=2*Y line in the table above. =head2 Rows Reverse Option C "rows_reverse"> reverses the order of points within the rows of i,j pairs, j=4 | 10 9 8 7 j=3 | 6 5 4 j=2 | 3 2 j=1 | 1 +------------ i=1 2 3 4 The X,Y numbering becomes =cut # math-image --path=GcdRationals,pairs_order=rows_reverse --all --output=numbers =pod pairs_order => "rows_reverse" 11 | 66 65 64 63 62 61 60 59 58 57 10 | 55 53 49 47 209 9 | 45 44 42 41 39 38 170 168 8 | 36 34 32 30 135 131 7 | 28 27 26 25 24 23 104 102 100 98 6 | 21 17 77 69 5 | 15 14 13 12 54 52 50 48 118 4 | 10 8 35 31 76 70 3 | 6 5 20 18 43 40 75 71 2 | 3 9 19 33 51 73 1 | 1 2 4 7 11 16 22 29 37 46 56 Y=0 | ------------------------------------------------ X=0 1 2 3 4 5 6 7 8 9 10 11 The triangular numbers, per L above, are now in the X=1 column, ie. at the left rather than at the Y=1 bottom row. That bottom row is now the next after each triangular, ie. T(X)+1. =head2 Diagonals Option C "diagonals_down"> takes the i,j pairs by diagonals down from the Y axis. C "diagonals_up"> likewise but upwards from the X=Y centre up to the Y axis. (These numberings are in the style of L.) diagonals_down diagonals_up j=7 | 13 j=7 | 16 j=6 | 10 14 j=6 | 12 15 j=5 | 7 11 15 j=5 | 9 11 14 j=4 | 5 8 12 16 j=4 | 6 8 10 13 j=3 | 3 6 9 j=3 | 4 5 7 j=2 | 2 4 j=2 | 2 3 j=1 | 1 j=1 | 1 +------------ +------------ i=1 2 3 4 i=1 2 3 4 The resulting path becomes =cut # math-image --path=GcdRationals,pairs_order=diagonals_down --all --output=numbers --size=40x10 =pod pairs_order => "diagonals_down" 9 | 21 27 40 47 63 72 8 | 17 28 41 56 74 7 | 13 18 23 29 35 42 58 76 6 | 10 30 44 5 | 7 11 15 20 32 46 62 80 4 | 5 12 22 48 52 3 | 3 6 14 24 33 55 2 | 2 8 19 34 54 1 | 1 4 9 16 25 36 49 64 81 Y=0 | -------------------------------- X=0 1 2 3 4 5 6 7 8 9 XThe Y=1 bottom row is the perfect squares which are at i=j in the C and have gcd(i,j)=i thus becoming X=i,Y=1. =cut # math-image --path=GcdRationals,pairs_order=diagonals_up --all --output=numbers --size=40x10 =pod pairs_order => "diagonals_up" 9 | 25 29 39 45 58 65 8 | 20 28 38 50 80 7 | 16 19 23 27 32 37 63 78 6 | 12 26 48 5 | 9 11 14 17 35 46 59 74 4 | 6 10 24 44 54 3 | 4 5 15 22 34 51 2 | 2 8 18 33 52 1 | 1 3 7 13 21 31 43 57 73 Y=0 | -------------------------------- X=0 1 2 3 4 5 6 7 8 9 XXN=1,2,4,6,9 etc in the X=1 column is the perfect squares k*k and the pronics k*(k+1) interleaved, also called the Xquarter-squares. N=2,5,10,17,etc on Y=X+1 above the leading diagonal are the squares+1, and N=3,8,15,24,etc below on Y=X-1 below the diagonal are the squares-1. The GCD division moves points downwards and shears them across horizontally. The effect on diagonal lines of i,j points is as follows | 1 | 1 gcd=1 slope=-1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | . gcd=2 slope=0 | . 2 | . 2 3 gcd=3 slope=1 | . 2 3 gcd=4 slope=2 | . 2 3 4 | . 3 4 5 gcd=5 slope=3 | . 4 5 | . 4 5 | . 5 +------------------------------- The line of "1"s is the diagonal with gcd=1 and thus X,Y=i,j unchanged. The line of "2"s is when gcd=2 so X=(i+j)/2,Y=j/2. Since i+j=d is constant within the diagonal this makes X=d fixed, ie. vertical. Then gcd=3 becomes X=(i+2j)/3 which slopes across by +1 for each i, or gcd=4 has X=(i+3j)/4 slope +2, etc. Of course only some of the points in an i,j diagonal have a given gcd, but those which do are transformed this way. The effect is that for N up to a given diagonal row all the "*" points in the following are traversed, plus extras in wedge shaped arms out to the side. | * | * * up to a given diagonal points "*" | * * * all visited, plus some wedges out | * * * * to the right | * * * * * | * * * * * / | * * * * * / -- | * * * * * -- | * * * * *-- +-------------- In terms of the rationals X/Y the effect is that up to N=d^2 with diagonal d=2j the fractions enumerated are N=d^2 enumerates all num/den where num <= d and num+den <= 2*d =head1 FUNCTIONS See L for behaviour common to all path classes. =over =item C<$path = Math::PlanePath::GcdRationals-Enew ()> =item C<$path = Math::PlanePath::GcdRationals-Enew (pairs_order =E $str)> Create and return a new path object. The C option can be "rows" (default) "rows_reverse" "diagonals_down" "diagonals_up" =back =head1 FORMULAS =head2 X,Y to N -- Rows The defining formula above for X,Y can be inverted to give i,j and N. This calculation doesn't notice if X,Y have a common factor, so a coprime(X,Y) test must be made separately if necessary (for C it is). X/Y = g-1 + (i/g)/(j/g) The g-1 integer part is recovered by a division X divide Y, X = quot*Y + rem division by Y rounded towards 0 where 0 <= rem < Y unless Y=1 in which case use quot=X-1, rem=1 g-1 = quot g = quot+1 The Y=1 special case can instead be left as the usual kind of division quot=X,rem=0, so 0E=remEY. This will give i=0 which is outside the intended 1E=iE=j range, but j is 1 bigger and the combination still gives the correct N. It's as if the i=g,j=g point at the end of a row is moved to i=0,j=g+1 just before the start of the next row. If only N is of interest not the i,j then it can be left rem=0. Equating the denominators in the X/Y formula above gives j by Y = j/g the definition above j = g*Y = (quot+1)*Y j = X+Y-rem per the division X=quot*Y+rem And equating the numerators gives i by X = (g-1)*Y + i/g the definition above i = X*g - (g-1)*Y*g = X*g - quot*Y*g = X*g - (X-rem)*g per the division X=quot*Y+rem i = rem*g i = rem*(quot+1) Then N from i,j by the definition above N = i + j*(j-1)/2 For example X=11,Y=4 divides X/Y as 11=4*2+3 for quot=2,rem=3 so i=3*(2+1)=9 j=11+4-3=12 and so N=9+12*11/2=75 (as shown in the first table above). It's possible to use only the quotient p, not the remainder rem, by taking j=(quot+1)*Y instead of j=X+Y-rem, but usually a division operation gives the remainder at no extra cost, or a cost small enough that it's worth swapping a multiply for an add or two. The gcd g can be recovered by rounding up in the division, instead of rounding down and then incrementing with g=quot+1. g = ceil(X/Y) = cquot for division X=cquot*Y - crem But division in most programming languages is towards 0 or towards -infinity, not upwards towards +infinity. =head2 X,Y to N -- Rows Reverse For pairs_order="rows_reverse", the horizontal i is reversed to j-i+1. This can be worked into the triangular part of the N formula as Nrrev = (j-i+1) + j*(j-1)/2 for 1<=i<=j = j*(j+1)/2 - i + 1 The Y=1 case described above cannot be left to go through with rem=0 giving i=0 and j+1 since the reversal j-i+1 is then not correct. Either use rem=1 as described, or if not then compensate at the end, if r=0 then j -= 2 adjust Nrrev = j*(j+1)/2 - i + 1 same Nrrev as above For example X=5,Y=1 is quot=5,rem=0 gives i=0*(5+1)=0 j=5+1-0=6. Without adjustment it would be Nrrev=6*7/2-0+1=22 which is wrong. But adjusting j-=2 so that j=6-2=4 gives the desired Nrrev=4*5/2-0+1=11 (per the table in L above). =cut # No, not quite # # =head2 Rectangle N Range -- Rows # # An over-estimate of the N range can be calculated just from the X,Y to N # formula above. # # Within a row N increases with increasing X, so for a rectangle the minimum # is in the left column and the maximum in the right column. # # Within a column N values increase until reaching the end of a "g" wedge, # then drop down a bit. So the maximum is either the top-right corner of the # rectangle, or the top of the next lower wedge, ie. smaller Y but bigger g. # Conversely the minimum is either the bottom right of the rectangle, or the # bottom of the next higher wedge, ie. smaller g but bigger Y. (Is that # right?) # # This is an over-estimate because it ignores which X,Y points are coprime and # thus actually should have N values. # # =head2 Rectangle N Range -- Rows Reverse # # When row pairs are taken in reverse order increasing X is not increasing N, # but rather the maximum N of a row is at the left end of the wedge. =pod =head1 OEIS This enumeration of rationals is in Sloane's Online Encyclopedia of Integer Sequences in the following forms =over L (etc) =back pairs_order="rows" (the default) A226314 X coordinate A054531 Y coordinate, being N/GCD(i,j) A000124 N in X=1 column, triangular+1 A050873 ceil(X/Y), gcd by rows A050873-A023532 floor(X/Y) gcd by rows and subtract 1 unless i=j pairs_order="diagonals_down" A033638 N in X=1 column, quartersquares+1 and pronic+1 A000290 N in Y=1 row, perfect squares pairs_order="diagonals_up" A002620 N in X=1 column, squares and pronics A002061 N in Y=1 row, central polygonals (extra initial 1) A002522 N at Y=X+1 above leading diagonal, squares+1 =head1 SEE ALSO L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/QuintetCurve.pm0000644000175000017500000004243213734026657020237 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Boundary of unit squares: # 2*(4*3^n+1) cf A199108 = 4*3^n+1 # # QuintetCurve unit squares boundary # 12,28,76,220,652 # match 12,28,76,220,652 # [HALF] # A079003 a(n) = 4*3^(n-2)+2 package Math::PlanePath::QuintetCurve; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; # inherit: new(), rect_to_n_range(), arms_count(), n_start(), # parameter_info_array(), xy_is_visited() use Math::PlanePath::QuintetCentres; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath::QuintetCentres'); use Math::PlanePath; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'round_up_pow'; # uncomment this to run the ### lines # use Smart::Comments; { my @x_negative_at_n = (undef, 513, 9, 2, 2); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } { my @y_negative_at_n = (undef, 2, 4, 6, 3); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } { my @_UNDOCUMENTED__dxdy_list_at_n = (undef, 8, 5, 5, 4); sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $_UNDOCUMENTED__dxdy_list_at_n[$self->{'arms'}]; } } # N=4 first straight, then for other arms 18,27,36 # must override base Math::PlanePath::QuintetCentres sub _UNDOCUMENTED__turn_any_straight_at_n { my ($self) = @_; # arms=1 4 only first arm has origin 0 # arms=2 7 # arms=3 10 # arms=4 13 return 3*$self->arms_count + 1; } #------------------------------------------------------------------------------ my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); my @digit_reverse = (0,1,0,0,1,0); sub n_to_xy { my ($self, $n) = @_; ### QuintetCurve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $arms = $self->{'arms'}; my $int = int($n); $n -= $int; # fraction part my $rot = _divrem_mutate ($int,$arms); if ($rot) { $int += 1; } my @digits = digit_split_lowtohigh($int,5); my @sx; my @sy; { my $sy = 0 * $int; # inherit bignum 0 my $sx = 1 + $sy; # inherit bignum 1 foreach (@digits) { push @sx, $sx; push @sy, $sy; # 2*(sx,sy) + rot+90(sx,sy) ($sx,$sy) = (2*$sx - $sy, 2*$sy + $sx); } # ### @digits # my $rev = 0; # for (my $i = $#digits; $i >= 0; $i--) { # high to low # ### digit: $digits[$i] # if ($rev) { # ### reverse: "$digits[$i] to ".(5 - $digits[$i]) # $digits[$i] = (5 - $digits[$i]) % 5; # } # # $rev ^= $digit_reverse[$digits[$i]]; # ### now rev: $rev } # ### reversed n: @digits my $x = 0; my $y = 0; my $rev = 0; while (defined (my $digit = pop @digits)) { # high to low my $sx = pop @sx; my $sy = pop @sy; ### at: "$x,$y digit $digit side $sx,$sy" if ($rot & 2) { ($sx,$sy) = (-$sx,-$sy); } if ($rot & 1) { ($sx,$sy) = (-$sy,$sx); } if ($rev) { if ($digit == 0) { $rev = 0; $rot++; } elsif ($digit == 1) { $x -= $sy; $y += $sx; $rot++; } elsif ($digit == 2) { $x += -2*$sy; $y += 2*$sx; } elsif ($digit == 3) { $x += $sx - 2*$sy; # add 2*rot-90(side) + side $y += $sy + 2*$sx; $rot--; $rev = 0; } else { # $digit == 4 $x += $sx - $sy; # add rot-90(side) + side $y += $sy + $sx; } } else { # normal if ($digit == 0) { } elsif ($digit == 1) { $x += $sx; $y += $sy; $rot--; $rev = 1; } elsif ($digit == 2) { $x += $sx + $sy; # add side + rot-90(side) $y += $sy - $sx; } elsif ($digit == 3) { $x += 2*$sx + $sy; $y += 2*$sy - $sx; $rot++; } else { # $digit == 4 $x += 2*$sx; $y += 2*$sy; $rot++; $rev = 1; } } # lowest non-zero digit determines the direction if ($digit != 0) { ### frac_dir at non-zero: $rot } } ### final: "$x,$y" ### $rot $rot &= 3; return ($n * $dir4_to_dx[$rot] + $x, $n * $dir4_to_dy[$rot] + $y); } # up upl left my @attempt_x = (0, 0, -1, -1); my @attempt_y = (0, 1, 1, 0); sub xy_to_n { my ($self, $x, $y) = @_; ### QuintetCurve xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); my ($n, $cx, $cy); foreach my $i (0, 1, 2, 3) { if (defined ($n = $self->SUPER::xy_to_n($x + $attempt_x[$i], $y + $attempt_y[$i])) && (($cx,$cy) = $self->n_to_xy($n)) && $x == $cx && $y == $cy) { return $n; } } return undef; } #------------------------------------------------------------------------------ # levels # arms=1 arms=2 arms=3 arms=4 # level 0 0..1 = 2 0..2 = 2+1=3 0..3 = 2+1+1=4 0..4 = 2+1+1+1=5 # level 1 0..5 = 6 0..10 = 6+5=11 0..15 = 6+5+5=16 0..20 = 6+5+5+5=21 # level 2 0..25 = 26 0..50 = 26+25=51 0..75 = 26+25+25=76 0..100 = 26+25+25+25=101 # 5^k 2*5^k 3*5^k 4*5^k # sub level_to_n_range { my ($self, $level) = @_; return (0, 5**$level * $self->{'arms'}); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); $n += $self->{'arms'}-1; # division rounding up _divrem_mutate ($n, $self->{'arms'}); my ($pow, $exp) = round_up_pow ($n, 5); return $exp; } #------------------------------------------------------------------------------ # R,L,L,S # # forward reverse # 0 forward 0 forward # 1 reverse 1 reverse # 2 forward 2 reverse # 3 forward 3 forward # 4 reverse 4 reverse { # 1 2 3 4 my @_UNDOCUMENTED__n_to_turn_LSR = (-1, 1, 1, 0, # forward no low zeros -1, 1, 0, 0, # forward low zeros 0,-1,-1, 1, # reverse 0, 0,-1, 1); sub _UNDOCUMENTED__n_to_turn_LSR { my ($self, $n) = @_; ### _UNDOCUMENTED__n_to_turn_LSR(): $n $n += $self->{'arms'}-1; # division rounding up _divrem_mutate ($n, $self->{'arms'}); if ($n < 1 || is_infinite($n)) { return undef; } my $any_low_zeros; my $low; while ($n) { last if ($low = _divrem_mutate($n,5)); $any_low_zeros = 1; } ### $low ### $any_low_zeros my $non_two = 0; while (($non_two = _divrem_mutate($n,5)) == 2) {} ### $non_two $low = $low - 1 + ($any_low_zeros ? 4 : 0) # low zeros + ($non_two == 1 || $non_two == 4 ? 8 : 0); # reverse ### lookup: $low return $_UNDOCUMENTED__n_to_turn_LSR[$low]; } } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Mandelbrot Math-PlanePath Nlevel =head1 NAME Math::PlanePath::QuintetCurve -- self-similar "plus" shaped curve =head1 SYNOPSIS use Math::PlanePath::QuintetCurve; my $path = Math::PlanePath::QuintetCurve->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path is Mandelbrot's "quartet" trace of spiralling self-similar "+" shape. =over Benoit B. Mandelbrot, "The Fractal Geometry of Nature", W. H. Freeman and Co., 1983, ISBN 0-7167-1186-9, section 7, "Harnessing the Peano Monster Curves", pages 72-73. =back 125--... 93--92 11 | | | 123-124 94 91--90--89--88 10 | | | 122-121-120 103-102 95 82--83 86--87 9 | | | | | | | 115-116 119 104 101-100--99 96 81 84--85 8 | | | | | | | 113-114 117-118 105 32--33 98--97 80--79--78 7 | | | | | 112-111-110-109 106 31 34--35--36--37 76--77 6 | | | | | 108-107 30 43--42 39--38 75 5 | | | | | 25--26 29 44 41--40 73--74 4 | | | | | 23--24 27--28 45--46--47 72--71--70--69--68 3 | | | 22--21--20--19--18 49--48 55--56--57 66--67 2 | | | | | 5---6---7 16--17 50--51 54 59--58 65 1 | | | | | | | 0---1 4 9---8 15 52--53 60--61 64 <- Y=0 | | | | | | 2---3 10--11 14 62--63 -1 | | 12--13 -2 ^ X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ... Mandelbrot calls this a "quartet", taken as 4 parts around a further middle part (like 4 players around a table). The module name "quintet" here is a mistake, though it does suggest the base-5 nature of the curve. The base figure is the initial N=0 to N=5. 5 | | 0---1 4 base figure | | | | 2---3 It corresponds to a traversal of the following "+" shape, .... 5 . ^ . <| | 0--->1 .. 4 .... v ^ | . . |> |> . . | v . .... 2--->3 .... . v . . . . . . .. . The "v" and ">" notches are the side the figure is directed at the higher replications. The 0, 2 and 3 sub-curves are the right hand side of the line and are a plain repetition of the base figure. The 1 and 4 parts are to the left and are a reversal (rotate the base figure 180 degrees). The first such reversal is seen in the sample above as N=5 to N=10. ..... . . 5---6---7 ... . . | . . | . reversed figure ... 9---8 ... | . | . 10 ... Mandelbrot gives the expansion without designating start and end. The start is chosen here so the expansion has sub-curve 0 forward (not reverse). This ensures the next expansion has the curve the same up to the preceding level, and extending from there. In the base figure it can be seen the N=5 endpoint is rotated up around from the N=0 to N=1 direction. This makes successive higher levels slowly spiral around. base b = 2 + i N = 5^level angle = level * arg(b) = level*atan(1/2) = level * 26.56 degrees In the sample shown above N=125 is level=3 and has spiralled around to angle 3*26.56=79.7 degrees. The next level goes to X negative in the second quadrant. A full circle around the plane is approximately level 14. =head2 Arms The optional C $a> parameter can give 1 to 4 copies of the curve, each advancing successively. For example C4> is as follows. N=4*k points are the plain curve, and N=4*k+1, N=4*k+2 and N=4*k+3 are rotated copies of it. 69--65 ... | | | ..-117-113-109 73 61--57--53--49 120 | | | | 101-105 77 25--29 41--45 100-104 116 | | | | | | | | 97--93 81 21 33--37 92--96 108-112 | | | | 50--46 89--85 17--13-- 9 88--84--80--76--72 | | | | 54 42--38 10-- 6 1-- 5 20--24--28 64--68 | | | | | | | 58 30--34 14 2 0-- 4 16 36--32 60 | | | | | | | 66--62 26--22--18 7-- 3 8--12 40--44 56 | | | | 70--74--78--82--86 11--15--19 87--91 48--52 | | | | 110-106 94--90 39--35 23 83 95--99 | | | | | | | | 114 102--98 47--43 31--27 79 107-103 | | | | 118 51--55--59--63 75 111-115-119-.. | | | ... 67--71 The curve is essentially an ever expanding "+" shape with one corner at the origin. Four such shapes pack as follows. O is the origin and each * is the end of the part on its right. +---+ | | +---*--- +---+ | | B | +---+ +---+ +---* | C | | | +---+ +---O---+ +---+ | | | A | *---+ +---+ +---+ | D | | +---+ +---*---+ | | +---+ At higher replication levels the sides are wiggly and spiralling and the centres of each rotate around, but their sides are symmetric and mesh together perfectly to fill the plane. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::QuintetCurve-Enew ()> =item C<$path = Math::PlanePath::QuintetCurve-Enew (arms =E $a)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =item C<$n = $path-En_start()> Return 0, the first N in the path. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> In the current code the returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle, but don't rely on that yet since finding the exact range is a touch on the slow side. (The advantage of which though is that it helps avoid very big ranges from a simple over-estimate.) =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 5**$level)>, or for multiple arms return C<(0, $arms * 5**$level)>. There are 5^level + 1 points in a level, numbered starting from 0. On the second and subsequent arms the origin is omitted (so as not to repeat that point) and so just 5^level for them, giving 5^level+1 + (arms-1)*5^level = arms*5^level + 1 many points starting from 0. =back =head1 FORMULAS Various properties are in my R5 Dragon mathematical write-up in section "Quartet Curve", =over L =back =head2 X,Y to N The current approach uses the C C. Because the tiling in C and C is the same, the X,Y coordinates for a given N are no more than 1 away in the grid. The way the two lowest shapes are arranged in fact means that for a C N at X,Y then the same N on the C is at one of three locations X, Y same X, Y+1 up X-1, Y+1 up and left X-1, Y left This is so even when the "arms" multiple paths are in use (the same arms in both coordinates). Is there an easy way to know which of the four offsets is right? The current approach is to give each to C to make an N, put that N back through C to see if it's the target C<$n>. =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/PowerArray.pm0000644000175000017500000003743213777755110017700 0ustar gggg# Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # area package Math::PlanePath::PowerArray; use 5.004; use strict; use List::Util 'max'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'parameter_info_array'; # uncomment this to run the ### lines #use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; sub absdx_minimum { my ($self) = @_; return ($self->{'radix'} == 2 ? 1 : 0); # at N=1 dX=0,dY=1 } sub absdy_minimum { my ($self) = @_; return ($self->{'radix'} == 2 ? 0 # at N=1 dX=1,dY=0 : 1); # always different Y } sub dir_minimum_dxdy { my ($self) = @_; return ($self->{'radix'} == 2 ? (1,0) # East : (0,1)); # North } sub dir_maximum_dxdy { my ($self) = @_; my $radix = $self->{'radix'}; return $self->n_to_dxdy($radix==2 ? 3 : $radix-1); } sub turn_any_straight { my ($self) = @_; return ($self->{'radix'} != 3); # radix=3 never straight } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); $self->{'radix'} = max ($self->{'radix'} || 0, 2); # default 2 return $self; } sub n_to_xy { my ($self, $n) = @_; ### PowerArray n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n) || $n == 0) { return ($n,$n); } { # fractions on straight line ? my $int = int($n); if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; } my $x = $n*0; my $radix = $self->{'radix'}; until ($n % $radix) { $x++; $n /= $radix; } ### $x ### $n return ($x, $n - int($n/$radix) - 1); # collapse out multiples of radix } # | 9 # | 8 # | 7 # 4 | 6 30 # 3 | 4 20 # 2 | 3 15 # 1 | 2 10 # 0 | 1 5 25 125 # +------------ # sub xy_to_n { my ($self, $x, $y) = @_; ### PowerArray xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } my $radix = $self->{'radix'}; return ($radix + 0*$y) ** $x # $y*0 to inherit bignum in power * ($y+1 + int($y/($radix-1))); # stretch multiples of radix } # N=..004 X=0 Y=N-floor(N/5) # N=..010 X=1 Y=N/5-floor(N/25) dX=1 dY=... # N=..011 X=0 Y=(N-1)/5-floor((N-1)/25) dX=-1 dY=0 # sub n_to_dxdy { # my ($self, $n) = @_; # } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### PowerArray rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 0) { ### all outside first quadrant ... return (1, 0); } # bottom left into first quadrant if ($x1 < 0) { $x1 *= 0; } # *=0 to preserve bigint if ($y1 < 0) { $y1 *= 0; } return ($self->xy_to_n($x1,$y1), # bottom left $self->xy_to_n($x2,$y2)); # top right } 1; __END__ # Cf # Matthew P. Szudzik, "The Rosenberg-Strong Pairing Function", # arxiv 1706.04129. # Figure 3 drawing binary (transposed x<->y). # Binary used variously Minsky, 1967; Cutland, 1980; Davis and Weyuker, 1983 # Sierpinski, 1912; Hausdorff, 1927; Hrbacek and Jech, 1978. =for stopwords Ryde Math-PlanePath Radix radix ie OEIS DOI =head1 NAME Math::PlanePath::PowerArray -- array by powers =head1 SYNOPSIS use Math::PlanePath::PowerArray; my $path = Math::PlanePath::PowerArray->new (radix => 2); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is a split of N into an odd part and power of 2, =cut # math-image --path=PowerArray --output=numbers --all --size=60x15 =pod 12 | 25 50 100 200 400 800 1600 3200 6400 11 | 23 46 92 184 368 736 1472 2944 5888 10 | 21 42 84 168 336 672 1344 2688 5376 9 | 19 38 76 152 304 608 1216 2432 4864 8 | 17 34 68 136 272 544 1088 2176 4352 7 | 15 30 60 120 240 480 960 1920 3840 6 | 13 26 52 104 208 416 832 1664 3328 5 | 11 22 44 88 176 352 704 1408 2816 4 | 9 18 36 72 144 288 576 1152 2304 3 | 7 14 28 56 112 224 448 896 1792 2 | 5 10 20 40 80 160 320 640 1280 1 | 3 6 12 24 48 96 192 384 768 Y=0 | 1 2 4 8 16 32 64 128 256 +------------------------------------------------------ X=0 1 2 3 4 5 6 7 8 For N=odd*2^k, the coordinates are X=k, Y=(odd-1)/2. The X coordinate is how many factors of 2 can be divided out. The Y coordinate counts odd integers 1,3,5,7,etc as 0,1,2,3,etc. This is clearer by writing N values in binary, N values in binary 6 | 1101 11010 110100 1101000 11010000 110100000 5 | 1011 10110 101100 1011000 10110000 101100000 4 | 1001 10010 100100 1001000 10010000 100100000 3 | 111 1110 11100 111000 1110000 11100000 2 | 101 1010 10100 101000 1010000 10100000 1 | 11 110 1100 11000 110000 1100000 Y=0 | 1 10 100 1000 10000 100000 +--------------------------------------------------------- X=0 1 2 3 4 5 Column X=0 is all the odd numbers, column X=1 is exactly one low 0-bit, and so on. =head2 Radix The C parameter can do the same dividing out in a higher base. For example radix 3 divides out factors of 3, =cut # math-image --path=PowerArray --output=numbers --all --size=50x10 =pod radix => 3 8 | 13 39 117 351 1053 3159 9477 28431 7 | 11 33 99 297 891 2673 8019 24057 6 | 10 30 90 270 810 2430 7290 21870 5 | 8 24 72 216 648 1944 5832 17496 4 | 7 21 63 189 567 1701 5103 15309 3 | 5 15 45 135 405 1215 3645 10935 2 | 4 12 36 108 324 972 2916 8748 1 | 2 6 18 54 162 486 1458 4374 Y=0 | 1 3 9 27 81 243 729 2187 +------------------------------------------------ X=0 1 2 3 4 5 6 7 N=1,3,9,27,etc on the X axis is the powers of 3. N=1,2,4,5,7,etc on the Y axis is the integers N = 1or2 mod 3, ie. those not a multiple of 3. Notice if Y = 1or2 mod 4 then the N values in that row are all even, or if Y = 0or3 mod 4 then the N values are all odd. radix => 3, N values in ternary 6 | 101 1010 10100 101000 1010000 10100000 5 | 22 220 2200 22000 220000 2200000 4 | 21 210 2100 21000 210000 2100000 3 | 12 120 1200 12000 120000 1200000 2 | 11 110 1100 11000 110000 1100000 1 | 2 20 200 2000 20000 200000 Y=0 | 1 10 100 1000 10000 100000 +---------------------------------------------------- X=0 1 2 3 4 5 =head2 Boundary Length The points N=1 to N=2^k-1 inclusive have a boundary length boundary = 2^k + 2k = 4,8,14,24,42,76,... (OEIS A100314) =cut # GP-DEFINE read("my-oeis.gp"); # GP-DEFINE boundary_2ksub1(k) = 2^k + 2*k; # GP-Test my(k=0); 2^k-1 == 0 # GP-Test my(k=1); 2^k-1 == 1 # GP-Test vector(6,k, boundary_2ksub1(k)) == [4, 8, 14, 24, 42, 76] # GP-Test my(v=OEIS_samples("A100314")); /* OFFSET=0 */ \ # GP-Test vector(#v,k,k--; boundary_2ksub1(k)) == v =pod For example N=1 to N=7 is +---+ | 7 | + + | 5 | + +---+ | 3 6 | + +---+ | 1 2 4 | +---+---+---+ The height is the odd numbers, so 2^(k-1). The width is the power k. So total boundary 2*height+2*width = 2^k + 2k. If N=2^k is included then it's on the X axis and so add 2 for boundary = 2^k + 2k + 2 (OEIS 2*A052968). =cut # GP-DEFINE boundary_2k(k) = 2^k + 2*k + 2; # GP-Test my(v=OEIS_samples("A052968")); /* OFFSET=0 */ \ # GP-Test vector(#v,k,k--; if(k==0,4, boundary_2k(k))) == 2*v # GP-Test my(k=0); boundary_2k(k) == 3 # vector(15,k, boundary_2k(k)) =pod For another radix the calculation is similar boundary = 2 * (radix-1) * radix^(k-1) + 2*k For example radix=3, N=1 to N=8 is 8 7 5 4 2 6 1 3 The height is the non-multiples of the radix, so (radix-1)/radix * radix^k. The width is the power k. Total boundary = 2*height+2*width. =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::PowerArray-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 1 and if C<$n E 0> then the return is an empty list. =item C<$n = $path-Exy_to_n ($x,$y)> Return the N point number at coordinates C<$x,$y>. If C<$xE0> or C<$yE0> then there's no N and the return is C. N values grow rapidly with C<$x>. Pass in a number type such as C to preserve precision. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head1 FORMULAS =head2 Rectangle to N Range Within each row, increasing X is increasing N. Within each column, increasing Y is increasing N. So in a rectangle the lower left corner is the minimum N and the upper right is the maximum N. | N max | ----------+ | | ^ | | | | | | | ----> | | +---------- | N min +------------------- =head2 N to Turn Left or Right The turn left or right is given by radix = 2 left at N==0 mod radix and N==1mod4, right otherwise radix >= 3 left at N==0 mod radix right at N=1 or radix-1 mod radix straight otherwise The points N!=0 mod radix are on the Y axis and those N==0 mod radix are off the axis. For that reason the turn at N==0 mod radix is to the left, | C-- --- A--__ -- point B is N=0 mod radix, | --- B turn left A-B-C is left For radix>=3 the turns at A and C are to the right, since the point before A and after C is also on the Y axis. For radix>=4 there's of run of points on the Y axis which are straight. For radix=2 the "B" case N=0 mod 2 applies, but for the A,C points in between the turn alternates left or right. 1-- N=1 mod 4 3-- N=3 mod 4 \ -- turn left \ -- turn right \ -- \ -- 2 -- 2 -- -- -- -- -- 0 4 Points N=2 mod 4 are X=1 and Y=N/2 whereas N=0 mod 4 has 2 or more trailing 0 bits so XE1 and YEN/2. N mod 4 turn ------- ------ 0 left for radix=2 1 left 2 left 3 right =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back radix=2 A007814 X coordinate, count low 0-bits of N A006519 2^X A025480 Y coordinate of N-1, ie. seq starts from N=0 A003602 Y+1, being k for which N=(2k-1)*2^m A153733 2*Y of N-1, strip low 1 bits A000265 2*Y+1, strip low 0 bits A094267 dX, change count low 0-bits A050603 abs(dX) A108715 dY, change in Y coordinate A000079 N on X axis, powers 2^X A057716 N not on X axis, the non-powers-of-2 A005408 N on Y axis (X=0), the odd numbers A003159 N in X=even columns, even trailing 0 bits A036554 N in X=odd columns A014480 N on X=Y diagonal, (2n+1)*2^n A118417 N on X=Y+1 diagonal, (2n-1)*2^n (just below X=Y diagonal) A054582 permutation N by diagonals, upwards A209268 inverse A135764 permutation N by diagonals, downwards A249725 inverse A075300 permutation N-1 by diagonals, upwards A117303 permutation N at transpose X,Y A100314 boundary length for N=1 to N=2^k-1 inclusive being 2^k+2k A131831 same, after initial 1 A052968 half boundary length N=1 to N=2^k inclusive being 2^(k-1)+k+1 radix=3 A007949 X coordinate, power-of-3 dividing N A000244 N on X axis, powers 3^X A001651 N on Y axis (X=0), not divisible by 3 A016051 N on Y column X=1 A051063 N on Y column X=1 A007417 N in X=even columns, even trailing 0 digits A145204 N in X=odd columns (extra initial 0) A141396 permutation, N by diagonals down from Y axis A191449 permutation, N by diagonals up from X axis A135765 odd N by diagonals, deletes the Y=1,2mod4 rows A000975 Y at N=2^k, being binary "10101..101" radix=4 A000302 N on X axis, powers 4^X radix=5 A112765 X coordinate, power-of-5 dividing N A000351 N on X axis, powers 5^X radix=6 A122841 X coordinate, power-of-6 dividing N radix=10 A011557 N on X axis, powers 10^X A067251 N on Y axis, not a multiple of 10 A151754 Y coordinate of N=2^k, being floor(2^k*9/10) =head1 SEE ALSO L, L, L David M. Bradley "Counting Ordered Pairs", Mathematics Magazine, volume 83, number 4, October 2010, page 302, DOI 10.4169/002557010X528032. L =head1 HOME PAGE L =head1 LICENSE Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/DragonMidpoint.pm0000644000175000017500000007423213734026672020517 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=DragonMidpoint --lines --scale=20 # math-image --path=DragonMidpoint --all --output=numbers_dash # A006466 contfrac 2*sum( 1/2^(2^n)), 1 and 2 only # a(5n) recurrence ... # 1,1,1,1, 2, # 1,1,1,1,1,1,1, 2, # 1,1,1,1, 2, # 1,1,1,1, 2, # 1, 2, # 1,1,1,1, 2, # 1,1,1,1,1,1,1, 2, # 1,1,1,1, 2, # 1, 2, # 1,1,1,1,1,1,1, 2, # 1,1,1,1, 2, # 1, 2, # 1,1,1,1, 2, # 1,1,1,1, 2, # 1,1,1,1,1,1,1, 2, # 1,1,1,1, 2, # 1, 2, # 1,1,1,1,1,1,1, 2, # 1,1,1,1, 2, # 1,1,1,1, 2, # 1, 2 # A076214 in decimal # # A073097 number of 4s - 6s - 2s - 1 is -1,0,1 # A081769 positions of 2s # A073088 cumulative total multiples of 4 roughly, hence (4n-3-cum)/2 # # A088435 (contfrac+1)/2 of sum(k>=1,1/3^(2^k)). # A007404 in decimal # package Math::PlanePath::DragonMidpoint; use 5.004; use strict; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'bit_split_lowtohigh', 'digit_join_lowtohigh', 'round_up_pow'; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; # uncomment this to run the ### lines # use Smart::Comments; # whole plane when arms==4 use Math::PlanePath::DragonCurve; use constant n_start => 0; use constant parameter_info_array => [ { name => 'arms', share_key => 'arms_4', display => 'Arms', type => 'integer', minimum => 1, maximum => 4, default => 1, width => 1, description => 'Arms', } ]; { my @x_negative_at_n = (undef, 6,5,2,2); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } { my @y_negative_at_n = (undef, 27,19,11,7); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } { my @_UNDOCUMENTED__dxdy_list_at_n = (undef, 9, 9, 5, 3); sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $_UNDOCUMENTED__dxdy_list_at_n[$self->{'arms'}]; } } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(4, $self->{'arms'} || 1)); return $self; } # sub n_to_xy { # my ($self, $n) = @_; # ### DragonMidpoint n_to_xy(): $n # # if ($n < 0) { return; } # if (is_infinite($n)) { return ($n, $n); } # # { # my $int = int($n); # if ($n != $int) { # my ($x1,$y1) = $self->n_to_xy($int); # my ($x2,$y2) = $self->n_to_xy($int+$self->{'arms'}); # my $frac = $n - $int; # inherit possible BigFloat # my $dx = $x2-$x1; # my $dy = $y2-$y1; # return ($frac*$dx + $x1, $frac*$dy + $y1); # } # $n = $int; # BigFloat int() gives BigInt, use that # } # # my ($x1,$y1) = Math::PlanePath::DragonCurve->n_to_xy($n); # my ($x2,$y2) = Math::PlanePath::DragonCurve->n_to_xy($n+1); # # my $dx = $x2-$x1; # my $dy = $y2-$y1; # return ($x1+$y1 + ($dx+$dy-1)/2, # $y1-$x1 + ($dy-$dx+1)/2); # } sub n_to_xy { my ($self, $n) = @_; ### DragonMidpoint n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } my $frac; { my $int = int($n); $frac = $n - $int; # inherit possible BigFloat $n = $int; # BigFloat int() gives BigInt, use that } my $zero = ($n * 0); # inherit bignum 0 # arm as initial rotation my $rot = _divrem_mutate ($n, $self->{'arms'}); ### $arms ### rot from arm: $rot ### $n # ENHANCE-ME: sx,sy just from len,len my @digits = bit_split_lowtohigh($n); my @sx; my @sy; { my $sx = $zero + 1; my $sy = -$sx; foreach (@digits) { push @sx, $sx; push @sy, $sy; # (sx,sy) + rot+90(sx,sy) ($sx,$sy) = ($sx - $sy, $sy + $sx); } } ### @digits my $rev = 0; my $x = $zero; my $y = $zero; my $above_low_zero = 0; for (my $i = $#digits; $i >= 0; $i--) { # high to low my $digit = $digits[$i]; my $sx = $sx[$i]; my $sy = $sy[$i]; ### at: "$x,$y $digit side $sx,$sy" ### $rot if ($rot & 2) { $sx = -$sx; $sy = -$sy; } if ($rot & 1) { ($sx,$sy) = (-$sy,$sx); } ### rotated side: "$sx,$sy" if ($rev) { if ($digit) { $x -= $sy; $y += $sx; ### rev add to: "$x,$y next is still rev" } else { $above_low_zero = $digits[$i+1]; $rot ++; $rev = 0; ### rev rot, next is no rev ... } } else { if ($digit) { $rot ++; $x += $sx; $y += $sy; $rev = 1; ### plain add to: "$x,$y next is rev" } else { $above_low_zero = $digits[$i+1]; } } } # Digit above the low zero is the direction of the next turn, 0 for left, # 1 for right. # ### final: "$x,$y rot=$rot above_low_zero=".($above_low_zero||0) if ($rot & 2) { $frac = -$frac; # rotate 180 $x -= 1; } if (($rot+1) & 2) { # rot 1 or 2 $y += 1; } if (!($rot & 1) && $above_low_zero) { $frac = -$frac; } $above_low_zero ^= ($rot & 1); if ($above_low_zero) { $y = $frac + $y; } else { $x = $frac + $x; } ### rotated return: "$x,$y" return ($x,$y); } # or tables arithmetically, # # my $ax = ((($x+1) ^ ($y+1)) >> 1) & 1; # my $ay = (($x^$y) >> 1) & 1; # ### assert: $ax == - $yx_adj_x[$y%4]->[$x%4] # ### assert: $ay == - $yx_adj_y[$y%4]->[$x%4] # my @yx_adj_x = ([0,1,1,0], [1,0,0,1], [1,0,0,1], [0,1,1,0]); my @yx_adj_y = ([0,0,1,1], [0,0,1,1], [1,1,0,0], [1,1,0,0]); # arm $x $y 2 | 1 Y=1 # 0 0 0 3 | 0 Y=0 # 1 0 1 ----+---- # 2 -1 1 X=-1 X=0 # 3 -1 0 my @xy_to_arm = ([0, # x=0,y=0 1], # x=0,y=1 [3, # x=-1,y=0 2]); # x=-1,y=1 sub xy_to_n { my ($self, $x, $y) = @_; ### DragonMidpoint xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); { my $overflow = abs($x)+abs($y)+2; if (is_infinite($overflow)) { return $overflow; } } my $zero = ($x * 0 * $y); my @nbits; # low to high while ($x < -1 || $x > 0 || $y < 0 || $y > 1) { my $y4 = $y % 4; my $x4 = $x % 4; my $ax = $yx_adj_x[$y4]->[$x4]; my $ay = $yx_adj_y[$y4]->[$x4]; ### at: "$x,$y n=$n axy=$ax,$ay bit=".($ax^$ay) push @nbits, $ax^$ay; $x -= $ax; $y -= $ay; ### assert: ($x+$y)%2 == 0 ($x,$y) = (($x+$y)/2, # rotate -45 and divide sqrt(2) ($y-$x)/2); } ### final: "xy=$x,$y" my $arm = $xy_to_arm[$x]->[$y]; ### $arm my $arms_count = $self->arms_count; if ($arm >= $arms_count) { return undef; } if ($arm & 1) { ### flip ... @nbits = map {$_^1} @nbits; } return digit_join_lowtohigh(\@nbits, 2, $zero) * $arms_count + $arm; } #------------------------------------------------------------------------------ # xy_is_visited() sub xy_is_visited { my ($self, $x, $y) = @_; return ($self->{'arms'} >= 4 || _xy_to_arm($x,$y) < $self->{'arms'}); } # return arm number 0,1,2,3 sub _xy_to_arm { my ($x, $y) = @_; ### DragonMidpoint _xy_to_arm(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); { my $overflow = abs($x)+abs($y)+2; if (is_infinite($overflow)) { return $overflow; } } while ($x < -1 || $x > 0 || $y < 0 || $y > 1) { my $y4 = $y % 4; my $x4 = $x % 4; $x -= $yx_adj_x[$y4]->[$x4]; $y -= $yx_adj_y[$y4]->[$x4]; ### assert: ($x+$y)%2 == 0 ($x,$y) = (($x+$y)/2, # rotate -45 and divide sqrt(2) ($y-$x)/2); } return $xy_to_arm[$x]->[$y]; } #------------------------------------------------------------------------------ # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### DragonMidpoint rect_to_n_range(): "$x1,$y1 $x2,$y2 arms=$self->{'arms'}" $x1 = abs($x1); $x2 = abs($x2); $y1 = abs($y1); $y2 = abs($y2); my $xmax = int(max($x1,$x2)); my $ymax = int(max($y1,$y2)); return (0, ($xmax*$xmax + $ymax*$ymax + 1) * $self->{'arms'} * 5); } # sub rect_to_n_range { # my ($self, $x1,$y1, $x2,$y2) = @_; # ### DragonMidpoint rect_to_n_range(): "$x1,$y1 $x2,$y2" # # return Math::PlanePath::DragonCurve->rect_to_n_range # (sqrt(2)*$x1, sqrt(2)*$y1, sqrt(2)*$x2, sqrt(2)*$y2); # } #------------------------------------------------------------------------------ sub level_to_n_range { my ($self, $level) = @_; return (0, 2**$level * $self->{'arms'} - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, $self->{'arms'}); my ($pow, $exp) = round_up_pow ($n+1, 2); return $exp; } #------------------------------------------------------------------------------ 1; __END__ # wider drawn arms ... # # # ... 36---32 59---63-... 5 # | | | | # 60 40 28 55 4 # | | | | # 56---52---48---44 24---20---16 51 3 # | | # 17---13----9----5 12 47---43---39 2 # | | | | # 21 6--- 2 1 8 27---31---35 1 # | | | | # 33---29---25 10 3 0--- 4 23 <- Y=0 # | | | | # 37---41---45 14 7---11---15---19 -1 # | | # 49 18---22---26 46---50---54---58 -2 # | | | | # 53 30 42 62 -3 # | | | | # ...--61---57 34---38 ... -4 # # # # ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ # -5 -4 -3 -2 -1 X=0 1 2 3 4 # DragonMidpoint abs(dY) is A073089, but that seq has an extra leading 0 # # --*--+ dy=+/-1 vert and left # | horiz and right # * # | # | # * # | # +--*-- dy=+/-1 # # +--*-- dx=+/-1 vert and right # | horiz and left # * # | # | dx=+/-1 # * # | # --*--+ # # left turn ...01000 # right turn ...11000 # vert ...1 # horiz ...0 # Offset=1 0,0,1,1,1,0,0,1,1,0,1,1,0,0,0,1,1,0,1,1,1,0,0,1,0,0,1,1,0,0,0,1,1,0,1,1,1,0,0,1,1,0,1,1,0,0,0,1, # mod16 # 0 1 # 1 8n+1=4n+1 # 2 0 # 3 1 # 4 1 # 5 1 # 6 0 # 7 0 # 8 1 # 9 8n+1=4n+1 # 10 0 # 11 1 # 12 1 # 13 0 # 14 0 # 15 0 # # a(1) = a(4n+2) = a(8n+7) = a(16n+13) = 0, # a(4n) = a(8n+3) = a(16n+5) = 1 # a(8n+1) = a(4n+1) # N=0 0,1,1,1,0,0,1,1,0,1,1,0,0,0,1,1,0,1,1,1,0,0,1,0,0,1,1,0,0,0,1,1,0,1,1,1,0,0,1,1,0,1,1,0,0,0,1,0,0,1,1, =for stopwords eg Ryde Dragon Math-PlanePath Nlevel Heighway Harter et al bignum Xadj,Yadj lookup OEIS 0b.zz111 0b..zz11 ie tilingsearch Xadj =head1 NAME Math::PlanePath::DragonMidpoint -- dragon curve midpoints =head1 SYNOPSIS use Math::PlanePath::DragonMidpoint; my $path = Math::PlanePath::DragonMidpoint->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is the midpoint of each segment of the dragon curve of Heighway, Harter, et al, per L. 17--16 9---8 5 | | | | 18 15 10 7 4 | | | | 19 14--13--12--11 6---5---4 3 | | 20--21--22 3 2 | | 33--32 25--24--23 2 1 | | | | 34 31 26 0---1 <- Y=0 | | | 35 30--29--28--27 -1 | 36--37--38 43--44--45--46 -2 | | | 39 42 49--48--47 -3 | | | 40--41 50 -4 | 51 -5 | 52--53--54 -6 | ..--64 57--56--55 -7 | | 63 58 -8 | | 62--61--60--59 -9 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -10 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 The dragon curve begins as follows. The midpoints of each segment are numbered starting from 0, +--8--+ +--4--+ | | | | 9 7 5 3 | | | | | +-10--+--6--+ +--2--+ rotate 45 degrees | | | v 11 1 | | +-12--+ *--0--+ * = Origin | ... These midpoints are on fractions X=0.5,Y=0, X=1,Y=0.5, etc. For this C path they're turned clockwise 45 degrees and shrunk by sqrt(2) to be integer X,Y values a unit apart and initial direction to the right. The midpoints are distinct X,Y positions because the dragon curve traverses each edge only once. The dragon curve is self-similar in 2^level sections due to its unfolding. This can be seen in the midpoints too as for example above N=0 to N=16 is the same shape as N=16 to N=32, with the latter rotated 90 degrees and in reverse. For reference, Knuth in "Diamonds and Dragons" has a different numbering for segment midpoints where the dragon orientation is unchanged and instead multiply by 2 to have midpoints as integers. For example the first dragon midpoint at X=1/2,Y=0 is doubled out to X=1,Y=0. That can be obtained from the path here by KnuthX = X - Y + 1 KnuthY = X + Y =for GP-Test 0-0 + 1 == 1 /* N=1 */ =for GP-Test 0+0 == 0 =for GP-Test 1-0 + 1 == 2 /* N=1 */ =for GP-Test 1+0 == 1 =for GP-Test 1-1 + 1 == 1 /* N=2 */ =for GP-Test 1+1 == 2 =for GP-Test 1-2 + 1 == 0 /* N=3 */ =for GP-Test 1+2 == 3 =head2 Arms Like the C the midpoints fill a quarter of the plane and four copies mesh together perfectly when rotated by 90, 180 and 270 degrees. The C parameter can choose 1 to 4 curve arms, successively advancing. For example C 4> begins as follows, with N=0,4,8,12,etc being the first arm (the same as the plain curve above), N=1,5,9,13 the second, N=2,6,10,14 the third and N=3,7,11,15 the fourth. arms => 4 ...-107-103 83--79--75--71 6 | | | 68--64 36--32 99 87 59--63--67 5 | | | | | | | 72 60 40 28 95--91 55 4 | | | | | 76 56--52--48--44 24--20--16 51 3 | | | 80--84--88 17--13---9---5 12 47--43--39 ... 2 | | | | | | 100--96--92 21 6---2 1 8 27--31--35 106 1 | | | | | | 104 33--29--25 10 3 0---4 23 94--98-102 <- Y=0 | | | | | | ... 37--41--45 14 7--11--15--19 90--86--82 -1 | | | 49 18--22--26 46--50--54--58 78 -2 | | | | | 53 89--93 30 42 62 74 -3 | | | | | | | 65--61--57 85 97 34--38 66--70 -4 | | | 69--73--77--81 101-105-... -5 ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 With four arms like this every X,Y point is visited exactly once, because four arms of the C traverse every edge exactly once. =head2 Tiling Taking pairs of adjacent points N=2k and N=2k+1 gives little rectangles with the following tiling of the plane repeating in 4x4 blocks. +---+---+---+-+-+---+-+-+---+ | | | | | | | | | | | +---+ | +---+ | +---+ | +---+ | | | |9 8| | | | | | | +-+-+---+-+-+-+-+-+-+-+-+-+-+ | | | | |7| | | | | | | | | +---+ | +---+ | +---+ | | | | | | |6|5 4| | | | | | +---+-+-+-+-+-+-+-+-+-+-+-+-+ | | | | | |3| | | | | +---+ | +---+ | +---+ | +---+ | | | | | |2| | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | | | |0 1| | | | | | <- Y=0 | | +---+ | +---+ | +---+ | | | | | | | | | | | | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | | | | | | | | | +---+ | +---+ | +---+ | +---+ | | | | | | | | | | | +---+-+-+---+-+-+---+-+-+---+ ^ X=0 The pairs follow this pattern both for the main curve N=0 etc shown, and also for the rotated copies per L above. This tiling is in the tilingsearch database as =over L =back Taking pairs N=2k+1 and N=2k+2, being each odd N and its successor, gives a regular pattern too, but this time repeating in blocks of 16x16. |||--||||||--||--||--||||||--||||||--||||||--||||||--||||||--||| |||--||||||--||--||--||||||--||||||--||||||--||||||--||||||--||| -||------||------||------||------||------||------||------||----- -||------||------||------||------||------||------||------||----- |||--||||||||||||||--||||||||||||||--||||||||||||||--||||||||||| |||--||||||||||||||--||||||||||||||--||||||||||||||--||||||||||| -----||------||------||------||------||------||------||------||- -----||------||------||------||------||------||------||------||- -||--||--||--||--||--||||||--||--||--||--||--||--||--||||||--||- -||--||--||--||--||--||||||--||--||--||--||--||--||--||||||--||- -||------||------||------||------||------||------||------||----- -||------||------||------||------||------||------||------||----- |||||||||||--||||||||||||||--||||||||||||||--||||||||||||||--||| |||||||||||--||||||||||||||--||||||||||||||--||||||||||||||--||| -----||------||------||------||------||------||------||------||- -----||------||------||------||------||------||------||------||- |||--||||||--||--||--||||||--|| ||--||||||--||--||--||||||--||| |||--||||||--||--||--||||||--|| ||--||||||--||--||--||||||--||| -||------||------||------||------||------||------||------||----- -||------||------||------||------||------||------||------||----- |||--||||||||||||||--||||||||||||||--||||||||||||||--||||||||||| |||--||||||||||||||--||||||||||||||--||||||||||||||--||||||||||| -----||------||------||------||------||------||------||------||- -----||------||------||------||------||------||------||------||- -||--||||||--||--||--||--||--||--||--||||||--||--||--||--||--||- -||--||||||--||--||--||--||--||--||--||||||--||--||--||--||--||- -||------||------||------||------||------||------||------||----- -||------||------||------||------||------||------||------||----- |||||||||||--||||||||||||||--||||||||||||||--||||||||||||||--||| |||||||||||--||||||||||||||--||||||||||||||--||||||||||||||--||| -----||------||------||------||------||------||------||------||- -----||------||------||------||------||------||------||------||- =head2 Curve from Midpoints Since the dragon curve always turns left or right, never straight ahead or reverse, its segments are alternately horizontal and vertical. Rotated -45 degrees for the midpoints here this means alternately "opposite diagonal" and "leading diagonal". They fall on X,Y alternately even or odd. So the original dragon curve can be recovered from the midpoints by choosing leading diagonal or opposite diagonal segment according to X,Y even or odd, which is the same as N even or odd. DragonMidpoint dragon segment -------------- ----------------- "even" N==0 mod 2 opposite diagonal which is X+Y==0 mod 2 too "odd" N==1 mod 2 leading diagonal which is X+Y==1 mod 2 too / 3 0 at X=0,Y=0 "even", opposite diagonal / 1 at X=1,Y=0 "odd", leading diagonal \ etc 2 \ \ / 0 1 \ / =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::DragonMidpoint-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =item C<$n = $path-En_start()> Return 0, the first N in the path. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 2**$level - 1)>, or for multiple arms return C<(0, $arms * 2**$level - 1)>. There are 2^level segments comprising the dragon, or arms*2^level when multiple arms, numbered starting from 0. =back =head1 FORMULAS =head2 X,Y to N An X,Y point is turned into N by dividing out digits of a complex base i+1. This base is per the doubling of the C at each level. In midpoint coordinates an adjustment subtracting 0 or 1 must be applied to move an X,Y which is either N=2k or N=2k+1 to the position where dividing out i+1 gives the N=k X,Y. The adjustment is in a repeating pattern of 4x4 blocks. Points N=2k and N=2k+1 both move to the same place corresponding to N=k multiplied by i+1. The adjustment pattern is a little like the pair tiling shown above, but for some pairs both the N=2k and N=2k+1 positions must move, it's not enough just to shift the N=2k+1 to the N=2k. Xadj Yadj Ymod4 Ymod4 3 | 0 1 1 0 3 | 1 1 0 0 2 | 1 0 0 1 2 | 1 1 0 0 1 | 1 0 0 1 1 | 0 0 1 1 0 | 0 1 1 0 0 | 0 0 1 1 +-------- +-------- 0 1 2 3 0 1 2 3 Xmod4 Xmod4 The same tables work for both the main curve and for the rotated copies per L above. until -1<=X<=0 and 0<=Y<=1 Xm = X - Xadj(X mod 4, Y mod 4) Ym = Y - Yadj(X mod 4, Y mod 4) new X,Y = (Xm+i*Ym) / (i+1) = (Xm+i*Ym) * (1-i)/2 = (Xm+Ym)/2, (Ym-Xm)/2 # Xm+Ym and Ym-Xm are both even Nbit = Xadj xor Yadj # bits of N low to high The X,Y reduction stops at one of the start points for the four arms X,Y endpoint Arm +---+---+ ------------ --- | 2 | 1 | Y=1 0, 0 0 +---+---+ 0, 1 1 | 3 | 0 | Y=0 -1, 1 2 +---+---+ -1, 0 3 X=-1 X=0 For arms 1 and 3 the N bits must be flipped 0E-E1. The arm number and hence whether this flip is needed is not known until reaching the endpoint. For bignum calculations there's no need to apply the "/2" shift in newX=(Xm+Ym)/2 and newY=(Ym-Xm)/2. Instead keep a bit position which is the logical low end and pick out two bits from there for the Xadj,Yadj lookup. A whole word can be dropped when the bit position becomes a multiple of 32 or 64 or whatever. =head2 Boundary Taking unit squares at each point, the boundary MB[k] of the resulting shape from 0 to N=2^k-1 inclusive can be had from the boundary B[k] of the plain dragon curve. Taking points N=0 to N=2^k-1 inclusive is the midpoints of the dragon curve line segments N=0 to N=2^k inclusive. MB[k] = B[k] + 2 = 4, 6, 10, 18, 30, 50, 86, 146, 246, 418, 710, 1202, ... 2 + x + 2*x^2 generating function 2 * ------------- 1 - x - 2*x^3 =for GP-DEFINE gB(x)=(2 + 2*x^2) / ((1 - x - 2*x^3) * (1-x)) =for GP-DEFINE gMB(x) = (4 + 2*x + 4*x^2) / (1 - x - 2*x^3) =for GP-Test gMB(x) == 2*(2 + x + 2*x^2)/(1-x-2*x^3) =for GP-DEFINE gOnes(x)=1/(1-x) /* 1,1,1,1,1,1,etc */ =for GP-Test gMB(x) == gB(x) + 2*gOnes(x) =for GP-Test Vec(gMB(x) - O(x^12)) == [4, 6, 10, 18, 30, 50, 86, 146, 246, 418, 710, 1202] A unit square at the midpoint is a diamond on a dragon line segment / \ / \ midpoint m *--m--* diamond on dragon curve line segment \ / \ / A boundary segment of the dragon curve has two sides of the diamond which are boundary. But when the boundary makes a right hand turn two such sides touch and are therefore not midpoint boundary. /^\ / | \ right turn \ | //\ two diamond sides touch \|// \ *<----* \ / \ / The dragon curve at N=0 points East and the last segment N=2^k-1 to N=2^k is North. Since the curve never overlaps itself this means that when going around the right side of the curve there is 1 more left turn than right turn, lefts - rights = 1 The total line segments on the right is the dragon curve R[k] and there are R[k]-1 turns, so the total turns lefts+rights is lefts + rights + 1 = R[k] So the lefts and rights are obtained separately 2*lefts = R[k] adding the two equations 2*rights = R[k] - 2 subtracting the two equations The result is then MR[k] = 2*R[k] - 2*rights = 2*R[k] - 2*(R[k]-2)/2 = R[k] + 2 A similar calculation is made on the left side of the curve. The net turn is the same and so the same lefts-rights=1 and thus from the dragon curve L[k] left boundary ML[k] = 2*L[k] - 2*lefts = 2*L[k] - 2*(L[k]/2) = L[k] The total is then MB[k] = MR[k] + ML[k] = R[k]+2 + L[k] = B[k] + 2 since B[k]=R[k]+L[k] The generating function can be had from the partial fractions form of the dragon curve boundary. B[k]+2 means adding 2/(1-x) which cancels out the -2/(1-x) in gB(x). =cut # /\ B[0]=2 # *--* MB[0]=4 # \/ # # * # /|\ # /\|/ B[0]=4 # *--* MB[0]=6 # \/ # # * # /|\ B[0] = 8 # \|/\ MR[2] = 6 # *--* ML[2] = 4 # \/|\ MB[2] = 10 # /\|/ # *--* # \/ # # 4,6,10,18,30,50,86,146,246,418,710,1202, # 6,8,12,20,32,52,88,148,248,420,712,1204, =pod =head1 OEIS The C is in Sloane's Online Encyclopedia of Integer Sequences as =over L (etc) =back A073089 abs(dY) of n-1 to n, so 0=horizontal,1=vertical (extra initial 0) A077860 Y at N=2^k, being Re(-(i+1)^k + i-1) A090678 0=straight, 1=not straight (extra initial 1,1) A203175 boundary of unit squares N=0 to N=2^k-1, value 4 onwards =head2 A073089 For A073089=abs(dY), the midpoint curve is vertical when the C has a vertical followed by a left turn, or horizontal followed by a right turn. C verticals are whenever N is odd, and the turn is the bit above the lowest 0 in N (per L). So abs(dY) = lowbit(N) XOR bit-above-lowest-zero(N) The n in A073089 is offset by 2 from the N numbering of the path here, so n=N+2. The initial value at n=1 in A073089 has no corresponding N (it would be N=-1). The mod-16 definitions in A073089 express combinations of N odd/even and bit-above-low-0 which are the vertical midpoint segments. The recurrence a(8n+1)=a(4n+1) acts to strip zeros above a low 1 bit, n = 0b..uu0001 -> 0b...uu001 In terms of N=n-2 this reduces N = 0b..vv1111 -> 0b...vv111 which has the effect of seeking a lowest 0 in the range of the mod-16 conditions. =head1 SEE ALSO L, L, L L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/DragonRounded.pm0000644000175000017500000004043413734026672020331 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=DragonRounded --lines --scale=10 # math-image --path=DragonRounded,arms=4 --all --output=numbers_dash --size=132x60 # package Math::PlanePath::DragonRounded; use 5.004; use strict; #use List::Util 'min','max'; *min = \&Math::PlanePath::_min; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest', 'floor'; use Math::PlanePath::Base::Digits 'round_up_pow'; use Math::PlanePath::DragonMidpoint; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant parameter_info_array => [ { name => 'arms', share_key => 'arms_4', display => 'Arms', type => 'integer', minimum => 1, maximum => 4, default => 1, width => 1, description => 'Arms', } ]; { my @x_negative_at_n = (undef, 8,5,2,2); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } { my @y_negative_at_n = (undef, 26,17,8,3); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } use constant sumabsxy_minimum => 1; use constant absdiffxy_minimum => 1; # X=Y doesn't occur use constant rsquared_minimum => 1; # minimum X=1,Y=0 use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; *_UNDOCUMENTED__dxdy_list = \&Math::PlanePath::_UNDOCUMENTED__dxdy_list_eight; use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(4, $self->{'arms'} || 1)); return $self; } sub n_to_xy { my ($self, $n) = @_; ### DragonRounded n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } my $frac; { my $int = int($n); $frac = $n - $int; $n = $int; # BigFloat int() gives BigInt, use that } ### $frac my $zero = ($n * 0); # inherit bignum 0 # arm as initial rotation my $rot = _divrem_mutate ($n, $self->{'arms'}); # two points per edge my $x_offset = _divrem_mutate ($n, 2); # ENHANCE-ME: sx,sy just from len=3*2**level my @digits; my @sx; my @sy; { my $sx = $zero + 3; my $sy = $zero; while ($n) { push @digits, ($n % 2); push @sx, $sx; push @sy, $sy; $n = int($n/2); # (sx,sy) + rot+90(sx,sy) ($sx,$sy) = ($sx - $sy, $sy + $sx); } } ### @digits my $rev = 0; my $x = $zero; my $y = $zero; my $above_low_zero = 0; for (my $i = $#digits; $i >= 0; $i--) { # high to low my $digit = $digits[$i]; my $sx = $sx[$i]; my $sy = $sy[$i]; ### at: "$x,$y $digit side $sx,$sy" ### $rot if ($rot & 2) { ($sx,$sy) = (-$sx,-$sy); } if ($rot & 1) { ($sx,$sy) = (-$sy,$sx); } ### rotated side: "$sx,$sy" if ($rev) { if ($digit) { $x += -$sy; $y += $sx; ### rev add to: "$x,$y next is still rev" } else { $above_low_zero = $digits[$i+1]; $rot ++; $rev = 0; ### rev rot, next is no rev ... } } else { if ($digit) { $rot ++; $x += $sx; $y += $sy; $rev = 1; ### plain add to: "$x,$y next is rev" } else { $above_low_zero = $digits[$i+1]; } } } # Digit above the low zero is the direction of the next turn, 0 for left, # 1 for right, and that determines the y_offset to apply to go across # towards the next edge. When original input $n is odd, which means # $x_offset 0 at this point, there's no y_offset as going along the edge # not across the vertex. # my $y_offset = ($x_offset ? ($above_low_zero ? -$frac : $frac) : 0); $x_offset = $frac + 1 + $x_offset; ### final: "$x,$y rot=$rot above_low_zero=$above_low_zero offset=$x_offset,$y_offset" if ($rot & 2) { ($x_offset,$y_offset) = (-$x_offset,-$y_offset); # rotate 180 } if ($rot & 1) { ($x_offset,$y_offset) = (-$y_offset,$x_offset); # rotate +90 } $x = $x_offset + $x; $y = $y_offset + $y; ### rotated offset: "$x_offset,$y_offset return $x,$y" return ($x,$y); } my @yx_rtom_dx = ([undef, 1, 1, undef, 1, 1], [ 0, undef, undef, 1, undef, undef], [ 0, undef, undef, 1, undef, undef], [undef, 1, 1, undef, 1, 1], [ 1, undef, undef, 0, undef, undef], [ 1, undef, undef, 0, undef, undef]); my @yx_rtom_dy = ([undef, 0, 0, undef, -1, -1], [ 0, undef, undef, 0, undef, undef], [ 0, undef, undef, 0, undef, undef], [undef, -1, -1, undef, 0, 0], [ 0, undef, undef, 0, undef, undef], [ 0, undef, undef, 0, undef, undef]); sub xy_to_n { my ($self, $x, $y) = @_; ### DragonRounded xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); my $x6 = $x % 6; my $y6 = $y % 6; my $dx = $yx_rtom_dx[$y6][$x6]; defined $dx or return undef; my $dy = $yx_rtom_dy[$y6][$x6]; defined $dy or return undef; # my $n = $self->Math::PlanePath::DragonMidpoint::xy_to_n # ($x - floor($x/3) - $dx, # $y - floor($y/3) - $dy); # ### dxy: "$dx, $dy" # ### to: ($x - floor($x/3) - $dx).", ".($y - floor($y/3) - $dy) # ### $n return $self->Math::PlanePath::DragonMidpoint::xy_to_n ($x - floor($x/3) - $dx, $y - floor($y/3) - $dy); } # level 21 n=1048576 .. 2097152 # min 1052677 0b100000001000000000101 at -1026,1 factor 1.99610706057474 # n=2^20 min r^2=2^20 plus a bit # maybe ... # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### DragonRounded rect_to_n_range(): "$x1,$y1 $x2,$y2 arms=$self->{'arms'}" $x1 = abs($x1); $x2 = abs($x2); $y1 = abs($y1); $y2 = abs($y2); my $xmax = int(max($x1,$x2) / 3); my $ymax = int(max($y1,$y2) / 3); return (0, ($xmax*$xmax + $ymax*$ymax + 1) * $self->{'arms'} * 16); } #------------------------------------------------------------------------------ # each 2 points is a line segment, so 2*DragonMidpoint # level 0 0--1 # level 1 0--1 2--3 # level 2 0--1 2--3 4--5 6--7 # # arms=4 # level 0 0--3 / 1--4 / 2--5 / 3--7 # level 1 # # 2^level segments # 2*2^level rounded points # arms*2^level when multi-arm # numbered starting 0 # sub level_to_n_range { my ($self, $level) = @_; return (0, 2**($level+1) * $self->{'arms'} - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, 2*$self->{'arms'}); my ($pow, $exp) = round_up_pow ($n+1, 2); return $exp; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Dragon Math-PlanePath Nlevel Heighway Harter et al vertices multi-arm Xadj,Yadj OEIS Xadj =head1 NAME Math::PlanePath::DragonRounded -- dragon curve, with rounded corners =head1 SYNOPSIS use Math::PlanePath::DragonRounded; my $path = Math::PlanePath::DragonRounded->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is a version of the dragon curve by Harter, Heighway, et al, done with two points per edge and skipping vertices so as to make rounded-off corners, 17-16 9--8 6 / \ / \ 18 15 10 7 5 | | | | 19 14 11 6 4 \ \ / \ 20-21 13-12 5--4 3 \ \ 22 3 2 | | 23 2 1 / / 33-32 25-24 . 0--1 Y=0 / \ / 34 31 26 -1 | | | 35 30 27 -2 \ \ / 36-37 29-28 44-45 -3 \ / \ 38 43 46 -4 | | | 39 42 47 -5 \ / / 40-41 49-48 -6 / 50 -7 | ... ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -15-14-13-12-11-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 ... The two points on an edge have one of X or Y a multiple of 3 and the other Y or X at 1 mod 3 or 2 mod 3. For example N=19 and N=20 are on the X=-9 edge (a multiple of 3), and at Y=4 and Y=5 (1 and 2 mod 3). The "rounding" of the corners ensures that for example N=13 and N=21 don't touch as they approach X=-6,Y=3. The curve always approaches vertices like this and never crosses itself. =head2 Arms The dragon curve fills a quarter of the plane and four copies mesh together rotated by 90, 180 and 270 degrees. The C parameter can choose 1 to 4 curve arms, successively advancing. For example C 4> gives 36-32 59-... 6 / \ / ... 40 28 55 5 | | | | 56 44 24 51 4 \ / \ \ 52-48 13--9 20-16 47-43 3 / \ \ \ 17 5 12 39 2 | | | | 21 1 8 35 1 / / / 29-25 6--2 0--4 27-31 <- Y=0 / / / 33 10 3 23 -1 | | | | 37 14 7 19 -2 \ \ \ / 41-45 18-22 11-15 50-54 -3 \ \ / \ 49 26 46 58 -4 | | | | 53 30 42 ... -5 / \ / ...-57 34-38 -6 ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 With 4 arms like this all 3x3 blocks are visited, using 4 out of 9 points in each. =head2 Midpoint The points of this rounded curve correspond to the C with a little squish to turn each 6x6 block into a 4x4 block. For instance in the following N=2,3 are pushed to the left, and N=6 through N=11 shift down and squashes up horizontally. DragonRounded DragonMidpoint 9--8 / \ 10 7 9---8 | | | | 11 6 10 7 / \ | | 5--4 <=> -11 6---5---4 \ | 3 3 | | 2 2 / | . 0--1 0---1 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::DragonRounded-Enew ()> =item C<$path = Math::PlanePath::DragonRounded-Enew (arms =E $aa)> Create and return a new path object. The optional C parameter makes a multi-arm curve. The default is 1 for just one arm. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =item C<$n = $path-En_start()> Return 0, the first N in the path. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 2 * 2**$level - 1)>, or for multiple arms return C<(0, $arms * 2 * 2**$level - 1)>. There are 2^level segments comprising the dragon, or arms*2^level when multiple arms. Each has 2 points in this rounded curve, numbered starting from 0. =back =head1 FORMULAS =head2 X,Y to N The correspondence with the C noted above allows the method from that module to be used for the rounded C. The correspondence essentially reckons each point on the rounded curve as the midpoint of a dragon curve of one greater level of detail, and segments on 45-degree angles. The coordinate conversion turns each 6x6 block of C to a 4x4 block of C. There's no rotations or anything. Xmid = X - floor(X/3) - Xadj[X%6][Y%6] Ymid = Y - floor(Y/3) - Yadj[X%6][Y%6] N = DragonMidpoint n_to_xy of Xmid,Ymid Xadj[][] is a 6x6 table of 0 or 1 or undef Yadj[][] is a 6x6 table of -1 or 0 or undef The Xadj,Yadj tables are a handy place to notice X,Y points not on the C style 4 of 9 points. Or 16 of 36 points since the tables are 6x6. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include the various C sequences at even N, and in addition =over L (etc) =back A152822 abs(dX), so 0=vertical,1=not, being 1,1,0,1 repeating A166486 abs(dY), so 0=horizontal,1=not, being 0,1,1,1 repeating =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/CellularRule57.pm0000644000175000017500000003633413734026673020352 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::CellularRule57; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::PlanePath::Base::Generic 'round_nearest'; use Math::PlanePath::CellularRule54; *_rect_for_V = \&Math::PlanePath::CellularRule54::_rect_for_V; # uncomment this to run the ### lines #use Smart::Comments; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; use constant parameter_info_array => [ { name => 'mirror', display => 'Mirror', type => 'boolean', default => 0, description => 'Mirror to "rule 99" instead.', }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; sub x_negative_at_n { my ($self) = @_; return $self->n_start + ($self->{'mirror'} ? 1 : 2); } use constant sumxy_minimum => 0; # triangular X>=-Y so X+Y>=0 use constant diffxy_maximum => 0; # triangular X<=Y so X-Y<=0 use constant dx_maximum => 3; use constant dy_minimum => 0; use constant dy_maximum => 1; sub absdx_minimum { my ($self) = @_; return ($self->{'mirror'} ? 0 : 1); } use constant dsumxy_maximum => 3; # straight East dX=+3 use constant ddiffxy_maximum => 3; # straight East dX=+3 use constant dir_maximum_dxdy => (-1,0); # supremum, West and dY=+1 up #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # left # even y=3 5 # 5 12 # 7 23 # 9 38 # [1,2,3,4], [5,12,23,38] # # N = (2 d^2 + d + 2) # = (2*$d**2 + $d + 2) # = ((2*$d + 1)*$d + 2) # d = -1/4 + sqrt(1/2 * $n + -15/16) # = (-1 + 4*sqrt(1/2 * $n + -15/16)) / 4 # = (sqrt(8*$n-15)-1)/4 # with Y=2*d+1 # row 19, d=9 # N=173 to N=181 is 9 cells rem=0..8 is d-1 # 1/3 section 3 cells rem=0,1,2 floor((d-1)/3) # 2/3 section 6 cells # right solid N=191 to N=200 is 10 of is rem{'n_start'} + 1; # to N=1 basis, and warn if $n undef my $frac; { my $int = int($n); $frac = $n - $int; $n = $int; # BigFloat int() gives BigInt, use that if (2*$frac >= 1) { $frac -= 1; $n += 1; } # -0.5 <= $frac < 0.5 ### assert: 2*$frac >= -1 ### assert: 2*$frac < 1 } if ($n <= 1) { if ($n == 1) { return (0,0); } else { return; } } # d is the two-row group number, y=2*d+1, where n belongs # my $d = int( (_sqrtint(8*$n-15)-1)/4 ); $n -= ((2*$d + 1)*$d + 2); # remainder ### $d ### remainder: $n if ($self->{'mirror'}) { if ($n <= $d) { ### right solid: $n return ($frac + $n - 2*$d - 1, 2*$d+1); } $n -= $d+1; if ($n < int(2*$d/3)) { ### right 2/3: $n return ($frac + int(3*$n/2) - $d + 1, 2*$d+1); } $n -= int(2*$d/3); if ($n < int(($d+2)/3)) { ### left 1/3: $n return ($frac + 3*$n + ((2+$d)%3), 2*$d+1); } $n -= int(($d+2)/3); if ($n < $d) { ### left solid: $n return ($frac + $n + $d+2, 2*$d+1); } $n -= $d; if ($n < int((2*$d+5)/3)) { ### odd 2/3: $n return ($frac + int((3*$n)/2) - $d + - 1, 2*$d+2); } $n -= int((2*$d+5)/3); ### odd 1/3: $n return ($frac + 3*$n + ($d%3) + 1, 2*$d+2); } else { if ($n < $d) { ### left solid: $n return ($frac + $n - 2*$d - 1, 2*$d+1); } $n -= $d; if ($n < int(($d+2)/3)) { ### left 1/3: $n return ($frac + 3*$n - $d + 1, 2*$d+1); } $n -= int(($d+2)/3); if ($n < int(2*$d/3)) { ### right 2/3: $n return ($frac + $n + int(($n+(-$d%3))/2) + 1, 2*$d+1); } $n -= int(2*$d/3); if ($n <= $d) { ### right solid: $n return ($frac + $d + $n + 1, 2*$d+1); } $n -= $d+1; if ($n < int(($d+4)/3)) { ### odd 1/3: $n return ($frac + 3*$n - $d - 1, 2*$d+2); } $n -= int(($d+4)/3); ### odd 2/3: $n return ($frac + $n + int(($n+((1-$d)%3))/2) + 1, 2*$d+2); } } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### CellularRule57 xy_to_n(): "$x,$y" if ($y < 0 || $x < -$y || $x > $y) { ### outside pyramid region ... return undef; } if ($self->{'mirror'}) { # mirrored, rule 99 if ($y % 2) { my $d = ($y+1)/2; ### odd row, solids, d: $d if ($x < -$d) { return ($y+1)*$y/2 + $x + 1 + $self->{'n_start'}; } if ($x < 0) { ### mirror left 2 of 3 ... if (($x += $d+2) % 3) { return ($y+1)*$y/2 + $x-int($x/3) - $d + $self->{'n_start'} - 1; } } elsif ($x > $d) { return ($y+1)*$y/2 + $x - $d + $self->{'n_start'}; } else { ### mirror right 1 of 3 ... $x += 2-$d; unless ($x % 3) { return ($y+1)*$y/2 + $x/3 + $self->{'n_start'}; } } } else { ### even row, sparse ... my $d = $y/2; if ($x >= 0) { ### mirror sparse right 1 of 3 ... if ($x <= $d # only to half way && (($x -= $d) % 3) == 0) { return ($y+1)*$y/2 + $x/3 + $self->{'n_start'}; } } else { # $x < 0 ### mirror sparse left 2 of 3 ... if ($x >= -$d # only to half way && (($x += $d+1) % 3)) { return ($y+1)*$y/2 + $x-int($x/3) - $d + $self->{'n_start'} - 1; } } } } else { # unmirrored, rule 57 if ($y % 2) { my $d = ($y+1)/2; ### odd row, solids, d: $d if ($x <= -$d) { ### solid left ... if ($x < -$d) { # always skip the -$d cell return ($y+1)*$y/2 + $x + 1 + $self->{'n_start'}; } } elsif ($x <= 0) { ### 1 of 3 ... unless (($x += $d+1) % 3) { return ($y+1)*$y/2 + $x/3 - $d + $self->{'n_start'}; } } elsif ($x >= $d) { ### solid right ... return ($y+1)*$y/2 + $x - $d + $self->{'n_start'}; } else { ### 2 of 3 ... $x += 1-$d; if ($x % 3) { return ($y+1)*$y/2 + $x-int($x/3) + $self->{'n_start'}; } } } else { ### even row, sparse ... my $d = $y/2; if ($x > 0) { ### right 2 of 3 ... if ($x <= $d # goes to half way only && (($x -= $d+1) % 3)) { return ($y+1)*$y/2 + $x-int($x/3) + 1 + $self->{'n_start'}; } } else { # $x <= 0 ### left 1 of 3 ... if (($x += $d) >= 0 # goes to half way only && ! ($x % 3)) { return ($y+1)*$y/2 + $x/3 - $d + $self->{'n_start'}; } } } } return undef; } # left edge ((2*$d + 1)*$d + 2) # where y=2*d+1 # d=floor((y-1)/2) # left N = (2*floor((y-1)/2) + 1)*floor((y-1)/2) + 2 # = (yodd + 1)*yodd/2 + 2 # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### CellularRule57 rect_to_n_range(): "$x1,$y1, $x2,$y2" ($x1,$y1, $x2,$y2) = _rect_for_V ($x1,$y1, $x2,$y2) or return (1,0); # rect outside pyramid my $zero = ($x1 * 0 * $y1 * $x2 * $y2); # inherit bignum $y1 -= ! ($y1 % 2); $y2 -= ! ($y2 % 2); return ($zero + ($y1 < 1 ? $self->{'n_start'} : ($y1-1)*$y1/2 + 1 + $self->{'n_start'}), $zero + ($y2+2)*($y2+1)/2 + $self->{'n_start'}); } 1; __END__ =for stopwords straight-ish Ryde Math-PlanePath ie hexagonals 18-gonal Xmax-Xmin Nleft Nright OEIS =head1 NAME Math::PlanePath::CellularRule57 -- cellular automaton 57 and 99 points =head1 SYNOPSIS use Math::PlanePath::CellularRule57; my $path = Math::PlanePath::CellularRule57->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis is the pattern of Stephen Wolfram's "rule 57" cellular automaton =over L =back arranged as rows =cut # math-image --path=CellularRule57 --all --output=numbers --size=132x50 =pod 51 52 53 54 55 56 10 38 39 40 41 42 43 44 45 46 47 48 49 50 9 33 34 35 36 37 8 23 24 25 26 27 28 29 30 31 32 7 19 20 21 22 6 12 13 14 15 16 17 18 5 9 10 11 4 5 6 7 8 3 3 4 2 2 1 1 <- Y=0 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 XThe triangular numbers N=10,15,21,28,etc, k*(k+1)/2, make a 1/2 sloping diagonal upwards. On rows with odd Y there's a solid block at either end then 1 of 3 cells to the left and 2 of 3 to the right of the centre. On even Y rows there's similar 1 of 3 and 2 of 3 middle parts, but without the solid ends. Those 1 of 3 and 2 of 3 are successively offset so as to make lines going up towards the centre as can be seen in the following plot. =cut # math-image --text --path=CellularRule57 --all =pod *********** * * * * * ** ** ** ************ * * * * ** ** ** ** ********** * * * * ** ** ** *********** * * * * * ** ** ** ********* * * * ** ** ** ********** * * * * ** ** ** ******** * * * * ** ** ********* * * * ** ** ** ******* * * * ** ** ******** * * * * ** ** ****** * * ** ** ******* * * * ** ** ***** * * * ** ****** * * ** ** **** * * ** ***** * * * ** *** * ** **** * * ** ** * * *** * ** * * ** * * * * =head2 Mirror The C 1> option gives the mirror image pattern which is "rule 99". The point numbering shifts but the total points on each row is the same. =cut # math-image --path=CellularRule57,mirror=1 --all --output=numbers --size=132x50 =pod 51 52 53 54 55 56 10 38 39 40 41 42 43 44 45 46 47 48 49 50 9 33 34 35 36 37 8 23 24 25 26 27 28 29 30 31 32 7 19 20 21 22 6 12 13 14 15 16 17 18 5 9 10 11 4 5 6 7 8 3 3 4 2 2 1 1 <- Y=0 -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same pattern. For example to start at 0, =cut # math-image --path=CellularRule57,n_start=0 --all --output=numbers --size=75x8 # math-image --path=CellularRule57,n_start=0,mirror=1 --all --output=numbers --size=75x8 =pod n_start => 0 22 23 24 25 26 27 28 29 30 31 18 19 20 21 11 12 13 14 15 16 17 8 9 10 4 5 6 7 2 3 1 0 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::CellularRule57-Enew ()> =item C<$path = Math::PlanePath::CellularRule57-Enew (mirror =E $bool, n_start =E $n)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each cell as a square of side 1. If C<$x,$y> is outside the pyramid or on a skipped cell the return is C. =back =head1 SEE ALSO L, L, L, L, L L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/UlamWarburton.pm0000644000175000017500000010502613734026654020377 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . #------------------------------------------------------------------------------ # cf # Ulam/Warburton with cells turning off too # A079315 cells OFF -> ON # A079317 cells ON at stage n # A079316 cells ON at stage n, in first quadrant # A151921 net gain ON cells #------------------------------------------------------------------------------ package Math::PlanePath::UlamWarburton; use 5.004; use strict; use Carp 'croak'; use List::Util 'sum'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem = \&Math::PlanePath::_divrem; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_up_pow', 'round_down_pow', 'digit_split_lowtohigh'; use Math::PlanePath::UlamWarburtonQuarter; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ { name => 'parts', share_key => 'parts_ulamwarburton', display => 'Parts', type => 'enum', default => '4', choices => ['4','2','1','octant','octant_up' ], choices_display => ['4','2','1','Octant','Octant Up' ], description => 'Which parts of the plane to fill.', }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; # octant_up goes up the Y axis spine, dX=0 # all others always have dX!=0 sub absdx_minimum { my ($self) = @_; return ($self->{'parts'} eq 'octant_up' ? 0 : 1); } # used also to validate $self->{'parts'} my %x_negative = (4 => 1, 2 => 1, 1 => 0, octant => 0, octant_up => 0, ); sub x_negative { my ($self) = @_; return $x_negative{$self->{'parts'}}; } sub y_negative { my ($self) = @_; return $self->{'parts'} eq '4'; } sub x_negative_at_n { my ($self) = @_; return ($x_negative{$self->{'parts'}} ? $self->n_start + 3 : undef); } sub y_negative_at_n { my ($self) = @_; return ($self->{'parts'} eq '4' ? $self->n_start + 4 : undef); } sub diffxy_minimum { my ($self) = @_; return ($self->{'parts'} eq 'octant' ? 0 : undef); } sub diffxy_maximum { my ($self) = @_; return ($self->{'parts'} eq 'octant_up' ? 0 : undef); } { my %dir_maximum_dxdy = (4 => [1,-1], # N=4 South-East 2 => [1,-1], # N=44 South-East 1 => [2,-1], # N=3 ESE octant => [10,-3], # N=51 octant_up => [2,-1], # N=8 ESE ); sub dir_maximum_dxdy { my ($self) = @_; return @{$dir_maximum_dxdy{$self->{'parts'}}}; } } { my %_UNDOCUMENTED__turn_any_right_at_n = ( 4 => 20, 2 => 35, 1 => 2, octant => 4, octant_up => 2, ); sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; return $self->n_start + $_UNDOCUMENTED__turn_any_right_at_n{$self->{'parts'}}; } } sub tree_num_children_list { my ($self) = @_; return ($self->{'parts'} eq '4' ? (0, 1, 3, 4) : (0, 1, 2, 3 )); } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } my $parts = ($self->{'parts'} ||= '4'); if (! exists $x_negative{$parts}) { croak "Unrecognised parts option: ", $parts; } return $self; } sub n_to_xy { my ($self, $n) = @_; ### UlamWarburton n_to_xy(): "$n parts=$self->{'parts'}" if ($n < $self->{'n_start'}) { return; } if (is_infinite($n)) { return ($n,$n); } { my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } $n = $n - $self->{'n_start'}; # N=0 basis if ($n == 0) { return (0,0); } my $parts = $self->{'parts'}; my ($depthsum, $factor, $nrem) = _n0_to_depthsum_factor_rem($n, $parts) or return $n; # N=nan or +inf ### depthsum: join(',',@$depthsum) ### $factor ### n rem within row: $nrem if ($parts eq '4') { $factor /= 4; } elsif ($parts eq '2') { $factor /= 2; $nrem += ($factor-1)/2; } elsif ($parts eq 'octant_up') { $nrem += $factor; } else { $nrem += ($factor-1)/2; } (my $quad, $nrem) = _divrem ($nrem, $factor); ### factor modulus: $factor ### $quad ### n rem within quad: $nrem ### assert: $quad >= 0 ### assert: $quad <= 3 my $dhigh = shift @$depthsum; # highest term my @ndigits = digit_split_lowtohigh($nrem,3); ### $dhigh ### ndigits low to high: join(',',@ndigits) my $x = 0; my $y = 0; foreach my $depthterm (reverse @$depthsum) { # depth terms low to high my $ndigit = shift @ndigits; # N digits low to high ### $depthterm ### $ndigit $x += $depthterm; ### bit to x: "$x,$y" if ($ndigit) { if ($ndigit == 2) { ($x,$y) = (-$y,$x); # rotate +90 } } else { # $ndigit==0 (or undef when @ndigits shorter than @$depthsum) ($x,$y) = ($y,-$x); # rotate -90 } ### rotate to: "$x,$y" } $x += $dhigh; ### xy before quad: "$x,$y" if ($quad & 2) { $x = -$x; $y = -$y; } if ($quad & 1) { ($x,$y) = (-$y,$x); # rotate +90 } ### final: "$x,$y" return $x,$y; } # no Smart::Comments; sub xy_to_n { my ($self, $x, $y) = @_; ### UlamWarburton xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x == 0 && $y == 0) { return $self->{'n_start'}; } my $parts = $self->{'parts'}; if ($parts ne '4' && ($y < 0 || ($parts ne '2' && $x < ($parts eq 'octant' ? $y : 0)) || ($parts eq 'octant_up' && $x > $y))) { return undef; } my $quad; if ($y > $x) { ### quad above leading diagonal ... # / # above / # / if ($y > -$x) { ### quad above opposite diagonal, top quarter ... # top # \ / # \/ $quad = 1; ($x,$y) = ($y,-$x); # rotate -90 } else { ### quad below opposite diagonal, left quarter ... # \ # left \ # / # / $quad = 2; $x = -$x; # rotate -180 $y = -$y; } } else { ### quad below leading diagonal ... # / # / below # / if ($y > -$x) { ### quad above opposite diagonal, right quarter ... # / # / right # \ # \ $quad = 0; } else { ### quad below opposite diagonal, bottom quarter ... # /\ # / \ # bottom $quad = 3; ($x,$y) = (-$y,$x); # rotate +90 } } ### $quad ### quad rotated xy: "$x,$y" ### assert: ! ($y > $x) ### assert: ! ($y < -$x) my ($len, $exp) = round_down_pow ($x + abs($y), 2); if (is_infinite($exp)) { return ($exp); } my $depth = my $ndigits = my $n = ($x * 0 * $y); # inherit bignum 0 while ($exp-- >= 0) { ### at: "$x,$y n=$n len=$len" my $abs_y = abs($y); if ($x && $x == $abs_y) { return undef; } # right quarter diamond ### assert: $x >= 0 ### assert: $x >= abs($y) ### assert: $x+abs($y) < 2*$len || $x==abs($y) if ($x + $abs_y >= $len) { # one of the three quarter diamonds away from the origin $x -= $len; ### shift to: "$x,$y" $depth += $len; if ($x || $y) { $n *= 3; $ndigits++; if ($y < -$x) { ### bottom, digit 0 ... ($x,$y) = (-$y,$x); # rotate +90 } elsif ($y > $x) { ### top, digit 2 ... ($x,$y) = ($y,-$x); # rotate -90 $n += 2; } else { ### right, digit 1 ... $n += 1; } } } $len /= 2; } ### $n ### $depth ### $ndigits ### npower: 3**$ndigits ### $quad ### quad powered: $quad*3**$ndigits my $npower = 3**$ndigits; if ($parts eq 'octant_up') { $n -= $npower; } elsif ($parts ne '4') { $n -= ($npower-1)/2; } return $n + $quad*$npower + $self->tree_depth_to_n($depth); } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### UlamWarburton rect_to_n_range(): "$x1,$y1 $x2,$y2" my ($dlo, $dhi) = _rect_to_diamond_range (round_nearest($x1), round_nearest($y1), round_nearest($x2), round_nearest($y2)); ### $dlo ### $dhi if ($dlo) { ($dlo) = round_down_pow ($dlo,2); } ($dhi) = round_down_pow ($dhi,2); ### rounded to pow2: "$dlo ".(2*$dhi) return ($self->tree_depth_to_n($dlo), $self->tree_depth_to_n(2*$dhi) - 1); } # x1 | x2 # +--------|-------+ y2 xzero true, yzero false # | | | diamond min is y1 # +--------|-------+ y1 # | # ----------O------------- # # | x1 x2 # | +--------+ y2 xzero false, yzero true # | | | diamond min is x1 # -O-------------------- # | | | # | +--------+ y1 # | # sub _rect_to_diamond_range { my ($x1,$y1, $x2,$y2) = @_; my $xzero = ($x1 < 0) != ($x2 < 0); # x range covers x=0 my $yzero = ($y1 < 0) != ($y2 < 0); # y range covers y=0 $x1 = abs($x1); $y1 = abs($y1); $x2 = abs($x2); $y2 = abs($y2); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1) } if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1) } return (($yzero ? 0 : $y1) + ($xzero ? 0 : $x1), $x2+$y2); } #------------------------------------------------------------------------------ use constant tree_num_roots => 1; # ENHANCE-ME: step by the bits, not by X,Y # ENHANCE-ME: tree_n_to_depth() by probe sub tree_n_children { my ($self, $n) = @_; ### UlamWarburton tree_n_children(): $n if ($n < $self->{'n_start'}) { return; } my ($x,$y) = $self->n_to_xy($n); my @ret; my $dx = 1; my $dy = 0; foreach (1 .. 4) { if (defined (my $n_child = $self->xy_to_n($x+$dx,$y+$dy))) { if ($n_child > $n) { push @ret, $n_child; } } ($dx,$dy) = (-$dy,$dx); # rotate +90 } return sort {$a<=>$b} @ret; } sub tree_n_parent { my ($self, $n) = @_; ### UlamWarburton tree_n_parent(): $n if ($n <= $self->{'n_start'}) { return undef; } my ($x,$y) = $self->n_to_xy($n); my $dx = 1; my $dy = 0; foreach (1 .. 4) { if (defined (my $n_parent = $self->xy_to_n($x+$dx,$y+$dy))) { if ($n_parent < $n) { return $n_parent; } } ($dx,$dy) = (-$dy,$dx); # rotate +90 } return undef; } # sub tree_n_children { # my ($self, $n) = @_; # my ($power, $exp) = _round_down_pow (3*$n-2, 4); # $exp -= 1; # $power /= 4; # # ### $power # ### $exp # ### pow base: 2 + 4*(4**$exp - 1)/3 # # $n -= ($power - 1)/3 * 4 + 2; # ### n less pow base: $n # # my @$depthsum = (2**$exp); # $power = 3**$exp; # # # find the cumulative levelpoints total <= $n, being the start of the # # level containing $n # # # my $factor = 4; # while (--$exp >= 0) { # $power /= 3; # my $sub = 4**$exp * $factor; # ### $sub # # $power*$factor; # my $rem = $n - $sub; # # ### $n # ### $power # ### $factor # ### consider subtract: $sub # ### $rem # # if ($rem >= 0) { # $n = $rem; # push @$depthsum, 2**$exp; # $factor *= 3; # } # } # # $n += $factor; # if (1) { # return ($n,$n+1,$n+2); # } else { # return $n,$n+1,$n+2; # } # } # Converting quarter ... # (N-start)*4+1+start = 4*N-4*start+1+start # = 4*N-3*start+1 # sub tree_depth_to_n { my ($self, $depth) = @_; ### UlamWarburton tree_depth_to_n(): $depth if ($depth == 0) { return $self->{'n_start'}; } my $n = $self->Math::PlanePath::UlamWarburtonQuarter::tree_depth_to_n($depth-1); if (! defined $n) { return undef; } my $parts = $self->{'parts'}; if ($parts eq '2') { return 2*$n - $self->{'n_start'} + $depth; } if ($parts eq '1') { return $n + $depth; } if ($parts eq 'octant' || $parts eq 'octant_up') { return ($n + 1); } ### assert: $parts eq '4' return 4*$n - 3*$self->{'n_start'} + 1; } # sub _NOTWORKING__tree_depth_to_n_range { # my ($self, $depth) = @_; # my ($nstart, $nend) = $self->Math::PlanePath::UlamWarburtonQuarter::tree_depth_to_n_range($self, $depth) # or return; # return (4*$nstart-3 + $self->{'n_start'}-1, # 4*$nend-3 + $self->{'n_start'}-1); # } sub tree_n_to_depth { my ($self, $n) = @_; ### UlamWarburton tree_n_to_depth(): $n $n = $n - $self->{'n_start'}; # N=0 basis if ($n < 0) { return undef; } $n = int($n); if ($n == 0) { return 0; } my ($depthsum) = _n0_to_depthsum_factor_rem($n, $self->{'parts'}) or return $n; # N=nan or +inf return sum(@$depthsum); } # 1+3+3+9=16 # # 0 +1 # 1 +4 <- 0 # 5 +4 <- 1 # 9 +12 # 21 +4 <- 5 + 4+12 = 21 = 5 + 4*(1+3) # 25 +12 # 37 +12 # 49 +36 # 85 +4 <- 21 + 4+12+12+36 = 21 + 4*(1+3+3+9) # 89 +12 <- 8 +64 # 101 +12 # 113 +36 # 149 # 161 # 197 # 233 # 341 # 345 <- 16 +256 # 357 # 369 # 1+3 = 4 power 2 # 1+3+3+9 = 16 power 3 # 1+3+3+9+3+9+9+27 = 64 power 4 # # 4*(1+4+...+4^(l-1)) = 4*(4^l-1)/3 # l=1 total=4*(4-1)/3 = 4 # l=2 total=4*(16-1)/3=4*5 = 20 # l=3 total=4*(64-1)/3=4*63/3 = 4*21 = 84 # # n = 2 + 4*(4^l-1)/3 # (n-2) = 4*(4^l-1)/3 # 3*(n-2) = 4*(4^l-1) # 3n-6 = 4^(l+1)-4 # 3n-2 = 4^(l+1) # # 3^0+3^1+3^1+3^2 = 1+3+3+9=16 # x+3x+3x+9x = 16x = 256 # 4 quads is 4*16=64 # # 1+1+3 = 5 # 1+1+3 +1+1+3 +3+3+9 = 25 # 1+4 = 5 # 1+4+4+12 = 21 = 1 + 4*(1+1+3) # 2 +1 # 3 +3 # 6 +1 # 7 +1 # 10 +3 # 13 # parts=1 # 1+4+...+4^(l-1) + 2^l # = (4^l-1)/3 + 2^l # = (4^l-1 + 3*2^l)/3 # = (2^l*(2^l + 3) - 1)/3 # l=1 total= 3 # l=2 total= 9 # l=3 total= 29 # l=4 total= 101 # # N = (4^l-1)/3 + 2^l # 3*(N-2^l)+1 = 4^l # 12*(N-2^l)+1 = 4 * 4^l # # parts=2 # N = 2*(4^l-1)/3 + 2^l # 3/2*(N-2^l)+1 = 4^l # 6*(N-2^l)+1 = 4 * 4^l # # parts=4 # N = (4^l-1)/3 # 3*N+1 = 4 * 4^l # use Smart::Comments; # Return ($aref, $factor, $remaining_n). # sum(@$aref) = depth starting depth=1 # sub _n0_to_depthsum_factor_rem { my ($n, $parts) = @_; ### _n0_to_depthsum_factor_rem(): "$n parts=$parts" my $factor = ($parts eq '4' ? 4 : $parts eq '2' ? 2 : 1); if ($n == 0) { return ([], $factor, 0); } my $n3 = 3*$n + 1; my $ndepth = 0; my $power = $n3; my $exp; if ($parts eq '4') { $power /= 4; } elsif ($parts eq '2') { $power /= 2; $ndepth = -1; } elsif ($parts =~ /octant/) { $power *= 2; $ndepth = 2; } ($power, $exp) = round_down_pow ($power, 4); ### $n3 ### $power ### $exp if (is_infinite($exp)) { return; } # ### pow base: ($power - 1)/3 * $factor + 1 + ($parts ne '4' && $exp) # $n -= ($power - 1)/3 * $factor + 1; # if ($parts ne '4') { $n -= $exp; } # ### n less pow base: $n my $twopow = 2**$exp; my @depthsum; for (; $exp-- >= 0; $power /= 4, $twopow /= 2) { ### at: "power=$power twopow=$twopow factor=$factor n3=$n3 ndepth=$ndepth depthsum=".join(',',@depthsum) my $nmore = $power * $factor; if ($parts ne '4') { $nmore += 3*$twopow; } if ($parts =~ /octant/) { ### assert: $nmore % 2 == 0 $nmore = $nmore/2; } my $ncmp = $ndepth + $nmore; ### $nmore ### $ncmp if ($n3 >= $ncmp) { ### go to ncmp, remainder: $n3-$ncmp $factor *= 3; $ndepth = $ncmp; push @depthsum, $twopow; } } if ($parts eq '2') { $n3 += 1; } # ### assert: ($n3 - $ndepth)%3 == 0 $n = ($n3 - $ndepth) / 3; $factor /= 3; ### $ndepth ### @depthsum ### remaining n: $n ### assert: $n >= 0 ### assert: $n < $factor + ($parts ne '4') return \@depthsum, $factor, $n; } sub tree_n_to_subheight { my ($self, $n) = @_; ### tree_n_to_subheight(): $n $n = int($n - $self->{'n_start'}); # N=0 basis if ($n < 0) { return undef; } my ($depthsum, $factor, $nrem) = _n0_to_depthsum_factor_rem($n, $self->{'parts'}) or return $n; # N=nan or +inf ### $depthsum ### $factor ### $nrem my $parts = $self->{'parts'}; if ($parts eq '4') { $factor /= 4; } elsif ($parts eq '2') { $factor /= 2; $nrem += ($factor-1)/2; } elsif ($parts eq 'octant_up') { } else { $nrem += ($factor-1)/2; } (my $quad, $nrem) = _divrem ($nrem, $factor); my $sub = pop @$depthsum; while (_divrem_mutate($nrem,3) == 1) { # low "1" ternary digits of Nrem $sub += pop @$depthsum; } if (@$depthsum) { return $depthsum->[-1] - 1 - $sub; } else { return undef; # N all 1-digits, on central infinite spine } } #------------------------------------------------------------------------------ # levels sub level_to_n_range { my ($self, $level) = @_; return ($self->{'n_start'}, $self->tree_depth_to_n_end(2**($level+1)-1)); } sub n_to_level { my ($self, $n) = @_; my $depth = $self->tree_n_to_depth($n); if (! defined $depth) { return undef; } my ($pow, $exp) = round_down_pow ($depth, 2); return $exp; } # parts=4 # Ndepth(2^a) = 2 + 4*(4^a-1)/3 # Nend(2^a-1) = 1 + 4*(4^a-1)/3 = (4^(a+1)-1)/3 # parts=2 # # { # my %factor = (4 => 16, # 2 => 8, # 1 => 4, # octant => 2, # octant_up => 2, # ); # my %constant = (4 => -4, # 2 => -5, # 1 => -4, # octant => 0, # octant_up => 0, # ); # my %spine = (4 => 0, # 2 => 2, # 1 => 2, # octant => 1, # octant_up => 1, # ); # sub level_to_n_range { # my ($self, $level) = @_; # my $parts = $self->{'parts'}; # return ($self->{'n_start'}, # $self->{'n_start'} # + (4**$level * $factor{$parts} + $constant{$parts}) / 3 # + 2**$level * $spine{$parts}); # } # } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Math-PlanePath Ulam Warburton Ndepth OEIS ie Octant octant =head1 NAME Math::PlanePath::UlamWarburton -- growth of a 2-D cellular automaton =head1 SYNOPSIS use Math::PlanePath::UlamWarburton; my $path = Math::PlanePath::UlamWarburton->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XXThis is the pattern of a cellular automaton studied by Ulam and Warburton, numbering cells by growth tree row and anti-clockwise within the rows. =cut # math-image --path=UlamWarburton --expression='i<100?i:0' --output=numbers # and add N=100,N=101 manually =pod 94 9 95 87 93 8 63 7 64 42 62 6 65 30 61 5 66 43 31 23 29 41 60 4 69 67 14 59 57 3 70 44 68 15 7 13 58 40 56 2 96 71 32 16 3 12 28 55 92 1 97 88 72 45 33 24 17 8 4 1 2 6 11 22 27 39 54 86 91 <- Y=0 98 73 34 18 5 10 26 53 90 -1 74 46 76 19 9 21 50 38 52 ... -2 75 77 20 85 51 -3 78 47 35 25 37 49 84 -4 79 36 83 -5 80 48 82 -6 81 -7 99 89 101 -8 100 -9 ^ -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 The growth rule is that a given cell grows up, down, left and right, but only if the new cell has no neighbours (up, down, left or right). So the initial cell "a" is N=1, a initial depth=0 cell The next row "b" cells are numbered N=2 to N=5 anti-clockwise from the right, b b a b depth=1 b Likewise the next row "c" cells N=6 to N=9. The "b" cells only grow outwards as 4 "c"s since the other positions would have neighbours in the existing "b"s. c b c b a b c depth=2 b c The "d" cells are then N=10 to N=21, numbered following the previous row "c" cell order and then anti-clockwise around each. d d c d d b d d c b a b c d depth=3 d b d d c d d There's only 4 "e" cells since among the "d"s only the X,Y axes won't have existing neighbours (the "b"s and "d"s). e d d c d d b d e d c b a b c d e depth=4 d b d d c d d e In general the pattern always grows by 1 outward along the X and Y axes and travels into the quarter planes between with a diamond shaped tree pattern which fills 11 of 16 cells in each 4x4 square block. =head2 Tree Row Ranges Counting depth=0 as the N=1 at the origin and depth=1 as the next N=2,3,4,5 generation, the number of cells in a row is rowwidth(0) = 1 then rowwidth(depth) = 4 * 3^((count_1_bits(depth) - 1) So depth=1 has 4*3^0=4 cells, as does depth=2 at N=6,7,8,9. Then depth=3 has 4*3^1=12 cells N=10 to N=21 because depth=3=0b11 has two 1-bits in binary. The N start and end for a row is the cumulative total of those before it, Ndepth(depth) = 1 + (rowwidth(0) + ... + rowwidth(depth-1)) Nend(depth) = rowwidth(0) + ... + rowwidth(depth) For example depth 3 ends at N=(1+4+4)=9. depth Ndepth rowwidth Nend 0 1 1 1 1 2 4 5 2 6 4 9 3 10 12 21 4 22 4 25 5 26 12 37 6 38 12 49 7 50 36 85 8 86 4 89 9 90 12 101 For a power-of-2 depth the Ndepth is Ndepth(2^a) = 2 + 4*(4^a-1)/3 For example depth=4=2^2 starts at N=2+4*(4^2-1)/3=22, or depth=8=2^3 starts N=2+4*(4^3-1)/3=86. Further bits in the depth value contribute powers-of-4 with a tripling for each bit above. So if the depth number has bits a,b,c,d,etc in descending order, depth = 2^a + 2^b + 2^c + 2^d ... a>b>c>d... Ndepth = 2 + 4*(-1 + 4^a + 3 * 4^b + 3^2 * 4^c + 3^3 * 4^d + ... ) / 3 For example depth=6 = 2^2+2^1 is Ndepth = 2 + (1+4*(4^2-1)/3) + 4^(1+1) = 38. Or depth=7 = 2^2+2^1+2^0 is Ndepth = 1 + (1+4*(4^2-1)/3) + 4^(1+1) + 3*4^(0+1) = 50. =head2 Self-Similar Replication The diamond shape depth=1 to depth=2^level-1 repeats three times. For example an "a" part going to the right of the origin "O", d d d d | a d c --O a a a * c c c ... | a b c b b b b The 2x2 diamond shaped "a" repeats pointing up, down and right as "b", "c" and "d". This resulting 4x4 diamond then likewise repeats up, down and right. The same happens in the other quarters of the plane. The points in the path here are numbered by tree rows rather than in this sort of replication, but the replication helps to see the structure of the pattern. =head2 Half Plane Option C '2'> confines the pattern to the upper half plane C=0>, =cut # math-image --path=UlamWarburton,parts=2 --expression='i<32?i:0' --output=numbers --size=99x16 =pod parts => "2" 28 6 21 5 29 22 16 20 27 4 11 3 30 12 6 10 26 2 23 13 3 9 19 1 31 24 17 14 7 4 1 2 5 8 15 18 25 <- Y=0 -------------------------------------- -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 Points are still numbered anti-clockwise around so X axis N=1,2,5,8,15,etc is the first of row depth=X. X negative axis N=1,4,7,14,etc is the last of row depth=-X. For depth=0 point N=1 is both the first and last of that row. Within a row a line from point N to N+1 is always a 45-degree angle. This is true of each 3 direct children, but also across groups of children by symmetry. For this parts=2 the lines from the last of one row to the first of the next are horizontal, making an attractive pattern of diagonals and then across to the next row horizontally. For parts=4 or parts=1 the last to first lines are at various different slopes and so upsets the pattern. =head2 One Quadrant Option C '1'> confines the pattern to the first quadrant, =cut # math-image --path=UlamWarburton,parts=1 --expression='i<=73?i:0' --output=numbers --size=99x16 =pod parts => "1" to depth=14 14 | 73 13 | 63 12 | 53 62 72 11 | 49 10 | 39 48 71 9 | 35 47 61 8 | 31 34 38 46 52 60 70 7 | 29 45 59 6 | 19 28 69 67 5 | 15 27 57 4 | 11 14 18 26 68 58 51 56 66 3 | 9 25 23 43 2 | 5 8 24 17 22 44 37 42 65 1 | 3 7 13 21 33 41 55 Y=0 | 1 2 4 6 10 12 16 20 30 32 36 40 50 54 64 +----------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 X axis N=1,2,4,6,10,etc is the first of each row X=depth. Y axis N=1,3,5,9,11,etc is the last similarly Y=depth. In this arrangement, horizontal arms have even, N and vertical arms have odd N. For example the vertical at X=8 N=30,33,37,etc has N odd from N=33 up and when it turns to horizontal at N=42 or N=56 it switches to N even. The children of N=66 are not shown but the verticals from there are N=79 below and N=81 above and so switch to odd again. This odd/even pattern is true of N=2 horizontal and N=3 vertical and thereafter is true due to each row having an even number of points and the self-similar replications in the pattern, |\ replication | \ block 0 to 1 and 3 |3 \ and block 0 block 2 less sides |---- |\ 2|\ | \ | \ |0 \|1 \ --------- Block 0 is the base and is replicated as block 1 and in reverse as block 3. Block 2 is a further copy of block 0, but the two halves of block 0 rotated inward 90 degrees, so the X axis of block 0 becomes the vertical of block 2, and the Y axis of block 0 the horizontal of block 2. Those axis parts are dropped since they're already covered by block 1 and 3 and dropping them flips the odd/even parity to match the vertical/horizontal flip due to the 90-degree rotation. =head2 Octant Option C 'octant'> confines the pattern to the first eighth of the plane 0E=YE=X. =cut # math-image --path=UlamWarburton,parts=octant --expression='i<=51?i:0' --output=numbers --size=75x15 =pod parts => "octant" 7 | 47 ... 6 | 48 36 46 5 | 49 31 45 4 | 50 37 32 27 30 35 44 3 | 14 51 24 43 41 2 | 15 10 13 25 20 23 42 34 40 1 | 5 8 12 18 22 29 39 Y=0 | 1 2 3 4 6 7 9 11 16 17 19 21 26 28 33 38 +------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 In this arrangement, N=1,2,3,4,6,7,etc on the X axis is the first N of each row (C). =head2 Upper Octant Option C 'octant_up'> confines the pattern to the upper octant 0E=XE=Y of the first quadrant. =cut # math-image --path=UlamWarburton,parts=octant_up --expression='i<=51?i:0' --output=numbers --size=75x15 =pod parts => "octant_up" 8 | 16 17 19 22 26 29 34 42 7 | 15 21 28 41 6 | 10 14 38 33 40 5 | 8 13 39 4 | 6 7 9 12 3 | 5 11 2 | 3 4 1 | 2 Y=0 | 1 +-------------------------- X=0 1 2 3 4 5 6 7 In this arrangement, N=1,2,3,5,6,8,etc on the Y axis the last N of each row (C). =head2 N Start The default is to number points starting N=1 as shown above. Option C can give a different start, in the same pattern. For example to start at 0, =cut # math-image --path=UlamWarburton,n_start=0 --expression='i<38?i:0' --output=numbers =pod n_start => 0 29 5 30 22 28 4 13 3 14 6 12 2 31 15 2 11 27 1 32 23 16 7 3 0 1 5 10 21 26 <- Y=0 33 17 4 9 25 -1 18 8 20 37 -2 19 -3 34 24 36 -4 35 -5 ^ -5 -4 -3 -2 -1 X=0 1 2 3 4 5 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::UlamWarburton-Enew ()> =item C<$path = Math::PlanePath::UlamWarburton-Enew (parts =E $str, n_start =E $n)> Create and return a new path object. The C option (a string) can be "4" the default "2" "1" =back =head2 Tree Methods =over =item C<@n_children = $path-Etree_n_children($n)> Return the children of C<$n>, or an empty list if C<$n> has no children (including when C<$n E 1>, ie. before the start of the path). The children are the cells turned on adjacent to C<$n> at the next row. The way points are numbered means that when there's multiple children they're consecutive N values, for example at N=6 the children are 10,11,12. =back =head2 Tree Descriptive Methods =over =item C<@nums = $path-Etree_num_children_list()> Return a list of the possible number of children in C<$path>. This is the set of possible return values from C. The possible children varies with the C, parts tree_num_children_list() ----- ------------------------ 4 0, 1, 3, 4 (the default) 2 0, 1, 2, 3 1 0, 1, 2, 3 parts=4 has 4 children at the origin N=0 and thereafter either 0, 1 or 3. parts=2 and parts=1 can have 2 children on the boundaries where the 3rd child is chopped off, otherwise 0, 1 or 3. =item C<$n_parent = $path-Etree_n_parent($n)> Return the parent node of C<$n>, or C if C<$n E= 1> (the start of the path). =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<$n_lo = $n_start> and parts $n_hi ----- ----- 4 $n_start + (16*4**$level - 4) / 3 2 $n_start + ( 8*4**$level - 5) / 3 + 2*2**$level 1 $n_start + ( 4*4**$level - 4) / 3 + 2*2**$level C<$n_hi> is C. =back =head1 OEIS This cellular automaton is in Sloane's Online Encyclopedia of Integer Sequences as =over L (etc) =back parts=4 A147562 total cells to depth, being tree_depth_to_n() n_start=0 A147582 added cells at depth A264039 off cells >=2 neighbours ("poisoned") A260490 increment A264768 off cells, 4 neighbours ("surrounded") A264769 increment parts=2 A183060 total cells to depth=n in half plane A183061 added cells at depth=n parts=1 A151922 total cells to depth=n in quadrant A079314 added cells at depth=n The A147582 new cells sequence starts from n=1, so takes the innermost N=1 single cell as row n=1, then N=2,3,4,5 as row n=2 with 5 cells, etc. This makes the formula a binary 1-bits count on n-1 rather than on N the way rowwidth() above is expressed. The 1-bits-count power 3^(count_1_bits(depth)) part of the rowwidth() is also separately in A048883, and as n-1 in A147610. =head1 SEE ALSO L, L, L, L L (a similar binary 1s-count related calculation) =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/AztecDiamondRings.pm0000644000175000017500000003363313734026674021150 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # cute groupings # AztecDiamondRings, FibonacciWord fibonacci_word_type plain, 10x10 scale 15 package Math::PlanePath::AztecDiamondRings; use 5.004; use strict; #use List::Util 'min', 'max'; *min = \&Math::PlanePath::_min; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_sqrtint = \&Math::PlanePath::_sqrtint; use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; use constant n_frac_discontinuity => 0; use constant xy_is_visited => 1; sub x_negative_at_n { my ($self) = @_; return $self->n_start + 1; } sub y_negative_at_n { my ($self) = @_; return $self->n_start + 2; } use constant dx_minimum => -1; use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant 1.02 _UNDOCUMENTED__dxdy_list => (1,0, # E 1,1, # NE # not North -1,1, # NW -1,0, # W -1,-1, # SW 0,-1, # S 1,-1); # SE; use constant dsumxy_minimum => -2; # diagonals use constant dsumxy_maximum => 2; use constant ddiffxy_minimum => -2; use constant ddiffxy_maximum => 2; use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_right => 0; # only left or straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } return $self; } # starting from X axis and n_start=0 # d = [ 1, 2, 3, 4, 5 ] # n = [ 0,4,12,24,40 ] # N = (2 d^2 - 2 d) # = (2*$d**2 - 2*$d) # = ((2*$d - 2)*$d) # d = 1/2 + sqrt(1/2 * $n + 1/4) # = (sqrt(2*$n+1) + 1)/2 # # X negative axis # d = [ 1, 2, 3, 4,5 ] # n = [ 2, 8, 18, 32, 50 ] # N = (2 d^2) sub n_to_xy { my ($self, $n) = @_; #### n_to_xy: $n # adjust to N=0 at origin X=0,Y=0 $n = $n - $self->{'n_start'}; if ($n < 0) { return; } my $d = int( (_sqrtint(2*$n+1) + 1)/2 ); $n -= 2*$d*$d; # to $n=0 half way around at horiz Y=-1 X<-1 if ($n < 0) { my $x = -$d-$n-1; if ($n < -$d) { # top-right return ($x, min($n+2*$d, $d-1)); } else { # top-left return (max($x, -$d), -1-$n); } } else { my $x = $n-$d; if ($n < $d) { # bottom-left my $y = -1-$n; return ($x, max($y, -$d)); } else { # bottom-right return (min($x, $d-1), $n-2*$d); } } } sub xy_to_n { my ($self, $x, $y) = @_; ### AztecDiamondRings xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x >= 0) { my $d = $x + abs($y); return (2*$d + 2)*$d + $y + $self->{'n_start'}; } if ($y >= 0) { my $d = $y - $x; return 2*$d*$d - 1 - $y + $self->{'n_start'}; } else { my $d = $y + $x; return (2*$d + 4)*$d + 1 - $y + $self->{'n_start'}; } } # | | x2>=-x1 | # M---+ | M-------M | +---M # | | | | | | | | | # +---m | +----m--+ | m---+ # | | | # -----+------ -------+------- -----+-------- # | | | # # | | | # M---+ | M-------M y2>=-y1 | +---M # | | | | | | | | | # | m | | | | | m | # -------+------ -------m------- -----+-------- # | | | | | | | | | # M---+ | M-------M | +---M # | | | # # | | | # -----+------ -------+------- -----+-------- # | | | # +---m | +--m----+ | m---+ # | | | | | | | | | # M---+ | M-------M | +---M # | | | # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### AztecDiamondRings rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; my $min_x = 0; my $min_y = ($y2 < 0 ? ($min_x = -1, $y2) : $y1 > 0 ? $y1 : 0); if ($x2 < $min_x) { $min_x = $x2 } # right edge if 0/-1 not covered elsif ($x1 > $min_x) { $min_x = $x1 } # left edge if 0/-1 not covered my $max_y = ($y2 >= -$y1 ? $y2 : $y1); my $max_x = ($x2 >= -$x1-($max_y<0) ? $x2 : $x1); ### min at: "$min_x, $min_y" ### max at: "$max_x, $max_y" return ($self->xy_to_n($min_x,$min_y), $self->xy_to_n($max_x,$max_y)); } 1; __END__ =for stopwords eg Ryde Math-PlanePath ie xbase OEIS =head1 NAME Math::PlanePath::AztecDiamondRings -- rings around an Aztec diamond shape =head1 SYNOPSIS use Math::PlanePath::AztecDiamondRings; my $path = Math::PlanePath::AztecDiamondRings->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path makes rings around an Aztec diamond shape, =cut # math-image --path=AztecDiamondRings --all --output=numbers --size=60x14 =pod 46-45 4 / \ 47 29-28 44 3 / / \ \ 48 30 16-15 27 43 ... 2 / / / \ \ \ \ 49 31 17 7--6 14 26 42 62 1 / / / / \ \ \ \ \ 50 32 18 8 2--1 5 13 25 41 61 <- Y=0 | | | | | | | | | | 51 33 19 9 3--4 12 24 40 60 -1 \ \ \ \ / / / / 52 34 20 10-11 23 39 59 -2 \ \ \ / / / 53 35 21-22 38 58 -3 \ \ / / 54 36-37 57 -4 \ / 55-56 -5 ^ -5 -4 -3 -2 -1 X=0 1 2 3 4 5 This is similar to the C, but has all four corners flattened to 2 vertical or horizontal, instead of just one in the C. This is only a small change to the alignment of numbers in the sides, but is more symmetric. XY axis N=1,6,15,28,45,66,etc are the hexagonal numbers k*(2k-1). The hexagonal numbers of the "second kind" 3,10,21,36,55,78, etc k*(2k+1), are the vertical at X=-1 going downwards. Combining those two is the triangular numbers 3,6,10,15,21,etc, k*(k+1)/2, alternately on one line and the other. Those are the positions of all the horizontal steps, ie. where dY=0. XX axis N=1,5,13,25,etc is the "centred square numbers". Those numbers are made by drawing concentric squares with an extra point on each side each time. The path here grows the same way, adding one extra point to each of the four sides. *---*---*---* | | | *---*---* | count total "*"s for | | | | centred square numbers * | *---* | * | | | | | | | * | * | * | | | | | | | | | *---* | | * | | * | *---*---* | | | *---*---*---* =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start, in the same pattern. For example to start at 0, =cut # math-image --path=AztecDiamondRings,n_start=0 --expression='i<=59?i:0' --output=numbers --size=50x10 =pod n_start => 0 45 44 46 28 27 43 47 29 15 14 26 42 48 30 16 6 5 13 25 41 49 31 17 7 1 0 4 12 24 40 50 32 18 8 2 3 11 23 39 59 51 33 19 9 10 22 38 58 52 34 20 21 37 57 53 35 36 56 54 55 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::AztecDiamondRings-Enew ()> =item C<$path = Math::PlanePath::AztecDiamondRings-Enew (n_start =E $n)> Create and return a new Aztec diamond spiral object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n < 1> the return is an empty list, it being considered the path starts at 1. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each point in the path as a square of side 1, so the entire plane is covered. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head1 FORMULAS =head2 X,Y to N The path makes lines in each quadrant. The quadrant is determined by the signs of X and Y, then the line in that quadrant is either d=X+Y or d=X-Y. A quadratic in d gives a starting N for the line and Y (or X if desired) is an offset from there, Y>=0 X>=0 d=X+Y N=(2d+2)*d+1 + Y Y>=0 X<0 d=Y-X N=2d^2 - Y Y<0 X>=0 d=X-Y N=(2d+2)*d+1 + Y Y<0 X<0 d=X+Y N=(2d+4)*d+2 - Y For example Y=2 X=3 d=2+3=5 N=(2*5+2)*5+1 + 2 = 63 Y=2 X=-1 d=2-(-1)=3 N=2*3*3 - 2 = 16 Y=-1 X=4 d=4-(-1)=5 N=(2*5+2)*5+1 + -1 = 60 Y=-2 X=-3 d=-3+(-2)=-5 N=(2*-5+4)*-5+2 - (-2) = 34 The two XE=0 cases are the same N formula and can be combined with an abs, X>=0 d=X+abs(Y) N=(2d+2)*d+1 + Y This works because at Y=0 the last line of one ring joins up to the start of the next. For example N=11 to N=15, 15 2 \ 14 1 \ 13 <- Y=0 12 -1 / 11 -2 ^ X=0 1 2 =head2 Rectangle to N Range Within each row N increases as X increases away from the Y axis, and within each column similarly N increases as Y increases away from the X axis. So in a rectangle the maximum N is at one of the four corners of the rectangle. | x1,y2 M---|----M x2,y2 | | | -------O--------- | | | | | | x1,y1 M---|----M x1,y1 | For any two rows y1 and y2, the values in row y2 are all bigger than in y1 if y2E=-y1. This is so even when y1 and y2 are on the same side of the origin, ie. both positive or both negative. For any two columns x1 and x2, the values in the part with YE=0 are all bigger if x2E=-x1, or in the part of the columns with YE0 it's x2E=-x1-1. So the biggest corner is at max_y = (y2 >= -y1 ? y2 ? y1) max_x = (x2 >= -x1 - (max_y<0) ? x2 : x1) The difference in the X handling for Y positive or negative is due to the quadrant ordering. When YE=0, at X and -X the bigger N is the X negative side, but when YE0 it's the X positive side. A similar approach gives the minimum N in a rectangle. min_y = / y2 if y2 < 0, and set xbase=-1 | y1 if y1 > 0, and set xbase=0 \ 0 otherwise, and set xbase=0 min_x = / x2 if x2 < xbase | x1 if x1 > xbase \ xbase otherwise The minimum row is Y=0, but if that's not in the rectangle then the y2 or y1 top or bottom edge is the minimum. Then within any row the minimum N is at xbase=0 if YE0 or xbase=-1 if YE=0. If that xbase is not in range then the x2 or x1 left or right edge is the minimum. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back n_start=1 (the default) A001844 N on X axis, the centred squares 2k(k+1)+1 n_start=0 A046092 N on X axis, 4*triangular A139277 N on diagonal X=Y A023532 abs(dY), being 0 if N=k*(k+3)/2 =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/Corner.pm0000644000175000017500000004643513775041413017030 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # cf Matthew Szudzik http://www.szudzik.com/ElegantPairing.pdf going inwardly: # # 5 | 25--... # | # 4 | 16--17--18--19 24 # | | # 3 | 9--10--11 15 23 # | | | # 2 | 4-- 5 8 14 22 # | | | | # 1 | 1 3 7 13 21 # | | | | | # Y=0 | 0 2 6 12 20 # +--------------------- # X=0 1 2 3 4 # not in OEIS: 0, 1,2, 4,3,6, 9,5,7,12, 16,10,8,13,20 # not in OEIS: 1, 2,3, 5,4,7, 10,6,8,13, 17,11,9,14,21 # one-based: 1, 3,2, 7,4,5, 13,8,6,10 = A108644 # # cf A185728 where diagonal is last in each gnomon # A185725 gnomon sides alternately starting from ends # A185726 gnomon sides alternately starting from diagonal # # cf A004120 ?? # #---------- # corners alternating "shell" # # A319514 interleaved x,y # x=OEIS_bfile_func("A319289"); # y=OEIS_bfile_func("A319290"); # plothraw(vector(3^3,n,n--; x(n)), \ # vector(3^3,n,n--; y(n)), 1+8+16+32) package Math::PlanePath::Corner; use 5.004; use strict; use List::Util 'min'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; *_sqrtint = \&Math::PlanePath::_sqrtint; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; # uncomment this to run the ### lines # use Smart::Comments; use constant class_x_negative => 0; use constant class_y_negative => 0; use constant n_frac_discontinuity => .5; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; use Math::PlanePath::SquareSpiral; *parameter_info_array = \&Math::PlanePath::SquareSpiral::parameter_info_array; use constant dx_maximum => 1; use constant dy_minimum => -1; # dSum east dX=1,dY=0 for dSum=+1 # south dX=0,dY=-1 for dSum=-1 # gnomon up to start of next gnomon is # X=wider+k,Y=0 to X=0,Y=k+1 # dSum = 0-(wider+k) + (k+1)-0 # = -wider-k + k + 1 # = 1-wider sub dsumxy_minimum { my ($self) = @_; return min(-1, 1-$self->{'wider'}); } use constant dsumxy_maximum => 1; # East along top # dDiffXY east dX=1,dY=0 for dDiffXY=1-0 = 1 # south dX=0,dY=-1 for dDiffXY=0-(-1) = 1 # gnomon up to start of next gnomon is # X=wider+k,Y=0 to X=0,Y=k+1 # dDiffXY = 0-(wider+k) - ((k+1)-0) # = -wider - 2*k - 1 unbounded negative use constant ddiffxy_maximum => 1; # East along top # use constant dir_minimum_dxdy => (1,0); # East at N=2 use constant dir_maximum_dxdy => (0,-1); # South at N=3 sub turn_any_left { my ($self) = @_; return ($self->{'wider'} != 0); # wider=0 no left, right or straight always } sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; return ($self->{'wider'} ? $self->n_start + $self->{'wider'} : undef); # wider=0 no left } sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; return $self->n_start + $self->{'wider'} + 1; } #------------------------------------------------------------------------------ # same as PyramidSides, just 45 degress around sub new { my $self = shift->SUPER::new (@_); if (! defined $self->{'n_start'}) { $self->{'n_start'} = $self->default_n_start; } $self->{'wider'} ||= 0; # default return $self; } sub n_to_xy { my ($self, $n) = @_; ### Corner n_to_xy: $n # adjust to N=1 at origin X=0,Y=0 $n = $n - $self->{'n_start'} + 1; # comparing $n<0.5, but done in integers for the benefit of Math::BigInt if (2*$n < 1) { return; } my $wider = $self->{'wider'}; # wider==0 # vertical at X=0 has N=1, 2, 5, 10, 17, 26 # but start 0.5 back so at X=-0.5 have N=0.5, 1.5, 4.5, 9.5, 16.5, 25.5 # N = (Y^2 + 1/2) # Y = floor sqrt(N - 1/2) # = floor sqrt(4*N - 2)/2 staying in integers for the sqrt() # # wider==1 # 0.5 back so at X=-0.5 have N=0.5, 2.5, 6.5, 12.5 # N = (Y^2 + Y + 1/2) # Y = floor -1/2 + sqrt(N - 1/4) # = floor (-1 + sqrt(4*N - 1))/2 # # wider==2 # 0.5 back so at X=-0.5 have N=0.5, 3.5, 8.5, 15.5 # N = (Y^2 + 2 Y + 1/2) # Y = floor -1 + sqrt(N + 1/2) # = floor (-2 + sqrt(4*N + 2))/2 # # wider==3 # 0.5 back so at X=-0.5 have N=0.5, 4.5, 10.5, 18.5 # N = (Y^2 + 3 Y + 1/2) # Y = floor -3/2 + sqrt(N + 7/4) # = floor (-3 + sqrt(4*N + 7))/2 # # 0,1,4,9 # my $y = int((sqrt(4*$n + -1) - $wider) / 2); # ### y frac: (sqrt(4*$n + -1) - $wider) / 2 my $y = int((_sqrtint(4*$n + $wider*$wider - 2) - $wider) / 2); ### y frac: (sqrt(int(4*$n) + $wider*$wider - 2) - $wider) / 2 ### $y # diagonal at X=Y has N=1, 3, 7, 13, 21 # N = ((Y + 1)*Y + (Y+1)*wider + 1) # = ((Y + 1 + wider)*Y + wider + 1) # so subtract that leaving N negative on the horizontal part, or positive # for the downward vertical part # $n -= $y*($y+1+$wider) + $wider + 1; ### corner n: $y*($y+1+$wider) + $wider + 1 ### rem: $n ### assert: $n!=$n || $n >= -($y+$wider+0.5) # ### assert: $n <= ($y+0.5) if ($n < 0) { # top horizontal return ($n + $y+$wider, $y); } else { # right vertical return ($y+$wider, -$n + $y); } } sub xy_to_n { my ($self, $x, $y) = @_; ### Corner xy_to_n(): "$x,$y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } my $wider = $self->{'wider'}; my $xw = $x - $wider; if ($y >= $xw) { ### top edge, N left is: $y*$y + $wider*$y + 1 return ($y*$y + $wider*$y # Y axis N value + $x # plus X offset across + $self->{'n_start'}); } else { ### right vertical, N diag is: $xw*$xw + $xw*$wider ### $xw # Ndiag = Nleft + Y+w # N = xw*xw + w*xw + 1 + xw+w + (xw - y) # = xw*xw + w*xw + 1 + xw+w + xw - y # = xw*xw + xw*(w+2) + 1 + w - y # = xw*(xw + w+2) + w+1 - y return ($xw*($xw+$wider+2) + $wider - $y + $self->{'n_start'}); } } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### Corner rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } if ($y2 < 0 || $x2 < 0) { return (1, 0); # rect all negative, no N } if ($x1 < 0) { $x1 *= 0; } # "*=" to preserve bigint x1 or y1 if ($y1 < 0) { $y1 *= 0; } my $wider = $self->{'wider'}; my $ylo = $y1; my $xw = $x1 - $wider; if ($y1 <= $xw) { # left column is partly or wholly below X=Y diagonal # # make x1,y1 the min pos $y1 = ($y2 < $xw # wholly below diag, min "*" is at top y2 of the x1 column # # | / # | / # | / *------+ y2 # | / | | # | / +------+ y1 # | / x1 x2 # +------------------ # ^.....^ # wider xw # ? $y2 # only partly below diag, min "*" is the X=Y+wider diagonal at x1 # # / # | +------+ y2 # | | / | # | |/ | # | * | # | /| | # | / +------+ y1 # | / x1 x2 # +------------------ # ^...^xw # wider # : $xw); } if ($y2 <= $x2 - $wider) { # right column entirely at or below X=Y+wider diagonal so max is at the # ylo bottom end of the column # # | / # | --/---+ y2 # | | / | # | |/ | # | / | # | /| | # | / +------+ ylo # | / x2 # +------------------ # ^ # wider # $y2 = $ylo; # x2,y2 now the max pos } ### min xy: "$x1,$y1" ### max xy: "$x2,$y2" return ($self->xy_to_n ($x1,$y1), $self->xy_to_n ($x2,$y2)); } #------------------------------------------------------------------------------ sub _NOTDOCUMENTED_n_to_figure_boundary { my ($self, $n) = @_; ### _NOTDOCUMENTED_n_to_figure_boundary(): $n # adjust to N=1 at origin X=0,Y=0 $n = $n - $self->{'n_start'} + 1; if ($n < 1) { return undef; } my $wider = $self->{'wider'}; if ($n <= $wider) { # single block row # +---+-----+----+ # | 1 | ... | $n | boundary = 2*N + 2 # +---+-----+----+ return 2*$n + 2; } my $d = int((_sqrtint(4*$n + $wider*$wider - 2) - $wider) / 2); ### $d ### $wider if ($n > $d*($d+1+$wider) + $wider) { $wider++; ### increment for +2 after turn ... } return 4*$d + 2*$wider + 2; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords pronic PlanePath Ryde Math-PlanePath ie OEIS gnomon Nstart =head1 NAME Math::PlanePath::Corner -- points shaped around in a corner =head1 SYNOPSIS use Math::PlanePath::Corner; my $path = Math::PlanePath::Corner->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path puts points in layers working outwards from the corner of the first quadrant. =cut # math-image --path=Corner --output=numbers_dash --all --size=30x14 =pod 5 | 26--... | 4 | 17--18--19--20--21 | | 3 | 10--11--12--13 22 | | | 2 | 5-- 6-- 7 14 23 | | | | 1 | 2-- 3 8 15 24 | | | | | | Y=0 | 1 4 9 16 25 +--------------------- X=0 1 2 3 4 XXThe horizontal 1,4,9,16,etc along Y=0 is the perfect squares. This is since each further row/column "gnomon" added to a square makes a one-bigger square, 10 11 12 13 5 6 7 5 6 7 14 2 3 2 3 8 2 3 8 15 1 4 1 4 9 1 4 9 16 2x2 3x3 4x4 N=2,6,12,20,etc on the diagonal X=Y-1 up from X=0,Y=1 is the Xpronic numbers k*(k+1) which are half way between the squares. Each gnomon is 2 longer than the previous. This is similar to the C, C and C paths. The C and the C are the same but C is stretched to two quadrants instead of one for the C here. =head2 Wider An optional C $integer> makes the path wider horizontally, becoming a rectangle. For example =cut # math-image --path=Corner,wider=3 --all --output=numbers_dash --size=38x12 =pod wider => 3 4 | 29--30--31--... | 3 | 19--20--21--22--23--24--25 | | 2 | 11--12--13--14--15--16 26 | | | 1 | 5---6---7---8---9 17 27 | | | | Y=0 | 1---2---3---4 10 18 28 | ----------------------------- ^ X=0 1 2 3 4 5 6 Each gnomon has the horizontal part C many steps longer. Each gnomon is still 2 longer than the previous since this widening is a constant amount in each. =head2 N Start The default is to number points starting N=1 as shown above. An optional C can give a different start with the same shape etc. For example to start at 0, =cut # math-image --path=Corner,n_start=0 --all --output=numbers --size=35x5 =pod n_start => 0 5 | 25 ... 4 | 16 17 18 19 20 3 | 9 10 11 12 21 2 | 4 5 6 13 22 1 | 1 2 7 14 23 Y=0 | 0 3 8 15 24 ----------------- X=0 1 2 3 In Nstart=0, the squares are on the Y axis and the pronic numbers are on the X=Y leading diagonal. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::Corner-Enew ()> =item C<$path = Math::PlanePath::Corner-Enew (wider =E $w, n_start =E $n)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. For C<$n < n_start()-0.5> the return is an empty list. There's an extra 0.5 before Nstart, but nothing further before there. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. C<$x> and C<$y> are each rounded to the nearest integer, which has the effect of treating each point as a square of side 1, so the quadrant x>=-0.5 and y>=-0.5 is entirely covered. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head1 FORMULAS =head2 N to X,Y Counting d=0 for the first L-shaped gnomon at Y=0, then the start of the gnomon is StartN(d) = d^2 + 1 = 1,2,5,10,17,etc The current C code extends to the left by an extra 0.5 for fractional N, so for example N=9.5 is at X=-0.5,Y=3. With this the starting N for each gnomon d is StartNfrac(d) = d^2 + 0.5 Inverting gives the gnomon d number for an N, d = floor(sqrt(N - 0.5)) Subtracting the gnomon start gives an offset into that gnomon OffStart = N - StartNfrac(d) The corner point 1,3,7,13,etc where the gnomon turns down is at d+0.5 into that remainder, and it's convenient to subtract that so negative for the horizontal and positive for the vertical, Off = OffStart - (d+0.5) = N - (d*(d+1) + 1) Then the X,Y coordinates are if (Off < 0) then X=d+Off, Y=d if (Off >= 0) then X=d, Y=d-Off =head2 X,Y to N For a given X,Y the bigger of X or Y determines the d gnomon. If YE=X then X,Y is on the horizontal part. At X=0 have N=StartN(d) per the Start(N) formula above, and any further X is an offset from there. if Y >= X then d=Y N = StartN(d) + X = Y^2 + 1 + X Otherwise if YEX then X,Y is on the vertical part. At Y=0 N is the last point on the gnomon, and one back from the start of the following gnomon, if Y <= X then d=X LastN(d) = StartN(d+1) - 1 = (d+1)^2 N = LastN(d) - Y = (X+1)^2 - Y =head2 Rectangle N Range For C, in each row increasing X is increasing N so the smallest N is in the leftmost column and the biggest N in the rightmost column. | | ------> N increasing | ----------------------- Going up a column, N values are increasing away from the X=Y diagonal up or down, and all N values above X=Y are bigger than the ones below. | ^ N increasing up from X=Y diagonal | | | |/ | / | /| | / | N increasing down from X=Y diagonal | / v |/ ----------------------- This means the biggest N is the top right corner if that corner is YE=X, otherwise the bottom right corner. max N at top right | / | --+ if corner Y>=X | / --+ | | / | / | | |/ | / | | | | / ----v | /| | / max N at bottom right | --+ |/ if corner Y<=X |/ ---------- ------- For the smallest N, if the bottom left corner has YEX then it's in the "increasing" part and that bottom left corner is the smallest N. Otherwise YE=X means some of the "decreasing" part is covered and the smallest N is at Y=min(X,Ymax), ie. either the Y=X diagonal if it's in the rectangle or the top right corner otherwise. | / | | / | | / min N at bottom left | +---- if corner Y>X | / | / |/ ---------- | / | / | | / | / | |/ min N at X=Y | / | * if diagonal crossed | / +-- min N at top left | /| | / | if corner Y= Xmin Xmin,min(Xmin,Ymax) if Ymin <= Xmin =head1 OEIS This path is in Sloane's Online Encyclopedia of Integer Sequences as, =over L (etc) =back wider=0, n_start=1 (the defaults) A213088 X+Y sum A196199 X-Y diff, being runs -n to +n A053615 abs(X-Y), runs n to 0 to n, distance to next pronic A000290 N on X axis, perfect squares starting from 1 A002522 N on Y axis, Y^2+1 A002061 N on X=Y diagonal, extra initial 1 A004201 N on and below X=Y diagonal, so X>=Y A020703 permutation N at transpose Y,X A060734 permutation N by diagonals up from X axis A064790 inverse A060736 permutation N by diagonals down from Y axis A064788 inverse A027709 boundary length of N unit squares A078633 grid sticks of N points n_start=0 A000196 max(X,Y), being floor(sqrt(N)) A005563 N on X axis, n*(n+2) A000290 N on Y axis, perfect squares A002378 N on X=Y diagonal, pronic numbers n_start=2 A059100 N on Y axis, Y^2+2 A014206 N on X=Y diagonal, pronic+2 wider=1 A053188 abs(X-Y), dist to nearest square, extra initial 0 wider=1, n_start=0 A002378 N on Y axis, pronic numbers A005563 N on X=Y diagonal, n*(n+2) wider=1, n_start=2 A014206 N on Y axis, pronic+2 wider=2, n_start=1 A028387 N on X=Y diagonal, k*(k+3) + 1 wider=2, n_start=0 A005563 N on Y axis, (Y+1)^2-1 A028552 N on X=Y diagonal, k*(k+3) wider=3, n_start=0 A028552 N on Y axis, k*(k+3) =head1 SEE ALSO L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/SierpinskiArrowhead.pm0000644000175000017500000006032313734026656021554 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::SierpinskiArrowhead; use 5.004; use strict; use Carp 'croak'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'round_up_pow', 'digit_split_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; # Note: shared by SierpinskiArrowheadCentres use constant parameter_info_array => [ { name => 'align', share_key => 'align_trld', display => 'Align', type => 'enum', default => 'triangular', choices => ['triangular','right','left','diagonal'], choices_display => ['Triangular','Right','Left','Diagonal'], }, ]; use constant n_start => 0; use constant class_y_negative => 0; my %x_negative = (triangular => 1, left => 1, right => 0, diagonal => 0); # Note: shared by SierpinskiArrowheadCentres sub x_negative { my ($self) = @_; return $x_negative{$self->{'align'}}; } { my %x_negative_at_n = (triangular => 3, # right => undef, left => 2, # diagonal => undef, ); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n{$self->{'align'}}; } } use constant sumxy_minimum => 0; # triangular X>=-Y use Math::PlanePath::SierpinskiTriangle; *x_maximum = \&Math::PlanePath::SierpinskiTriangle::x_maximum; *diffxy_maximum = \&Math::PlanePath::SierpinskiTriangle::diffxy_maximum; sub dx_minimum { my ($self) = @_; return ($self->{'align'} eq 'triangular' ? -2 : -1); } sub dx_maximum { my ($self) = @_; return ($self->{'align'} eq 'triangular' ? 2 : 1); } use constant dy_minimum => -1; use constant dy_maximum => 1; { my %_UNDOCUMENTED__dxdy_list = (triangular => [ 2,0, # E N=4 six directions 1,1, # NE N=0 -1,1, # NW N=2 -2,0, # W N=1 -1,-1, # SW N=15 1,-1, # SE N=6 ], right => [ 1,0, # E N=4 1,1, # NE N=0 0,1, # N N=2 -1,0, # W N=1 -1,-1, # SW N=15 0,-1, # S N=6 ], left => [ 1,0, # E N=4 0,1, # N N=0 -1,1, # NW N=2 -1,0, # W N=1 0,-1, # S N=15 1,-1, # SE N=6 ], diagonal => [ 1,0, # E N=0 0,1, # N N=2 -1,1, # NW N=1 -1,0, # W N=15 0,-1, # S N=6 1,-1, # SE N=4 ], ); sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; return @{$_UNDOCUMENTED__dxdy_list{$self->{'align'}}}; } } use constant 1.02 _UNDOCUMENTED__dxdy_list_at_n => 15; sub absdx_minimum { my ($self) = @_; return ($self->{'align'} eq 'triangular' ? 1 : 0); } sub absdx_maximum { my ($self) = @_; return ($self->{'align'} eq 'triangular' ? 2 : 1); } { my %dsumxy_minimum = (triangular => -2, left => -1, right => -2, diagonal => -1, ); sub dsumxy_minimum { my ($self) = @_; return $dsumxy_minimum{$self->{'align'}}; } } { my %dsumxy_maximum = (triangular => 2, left => 1, right => 2, diagonal => 1, ); sub dsumxy_maximum { my ($self) = @_; return $dsumxy_maximum{$self->{'align'}}; } } { my %ddiffxy_minimum = (triangular => -2, left => -2, right => -1, diagonal => -2, ); sub ddiffxy_minimum { my ($self) = @_; return $ddiffxy_minimum{$self->{'align'}}; } } { my %ddiffxy_maximum = (triangular => 2, left => 2, right => 1, diagonal => 2, ); sub ddiffxy_maximum { my ($self) = @_; return $ddiffxy_maximum{$self->{'align'}}; } } sub dir_maximum_dxdy { my ($self) = @_; return ($self->{'align'} eq 'right' ? (0,-1) # South : (1,-1)); # South-East } use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ # Note: shared by SierpinskiArrowheadCentres sub new { my $self = shift->SUPER::new(@_); my $align = ($self->{'align'} ||= 'triangular'); if (! exists $x_negative{$align}) { croak "Unrecognised align option: ", $align; } return $self; } sub n_to_xy { my ($self, $n) = @_; ### SierpinskiArrowhead n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $x = int($n); my $y = $n - $x; # fraction part $n = $x; $x = $y; if (my @digits = digit_split_lowtohigh($n,3)) { my $len = 1; for (;;) { my $digit = shift @digits; # low to high ### odd right: "$x,$y len=$len" ### $digit if ($digit == 0) { } elsif ($digit == 1) { $x = $len - $x; # mirror and offset $y += $len; } else { ($x,$y) = (($x+3*$y)/-2, # rotate +120 ($x-$y)/2 + 2*$len); } @digits || last; $len *= 2; $digit = shift @digits; # low to high ### odd left: "$x,$y len=$len" ### $digit if ($digit == 0) { } elsif ($digit == 1) { $x = - $x - $len; # mirror and offset $y += $len; } else { ($x,$y) = ((3*$y-$x)/2, # rotate -120 ($x+$y)/-2 + 2*$len) } @digits || last; $len *= 2; } } ### final: "$x,$y" if ($self->{'align'} eq 'right') { return (($x+$y)/2, $y); } elsif ($self->{'align'} eq 'left') { return (($x-$y)/2, $y); } elsif ($self->{'align'} eq 'diagonal') { return (($x+$y)/2, ($y-$x)/2); } else { # triangular return ($x,$y); } } sub xy_to_n { my ($self, $x, $y) = @_; $x = round_nearest ($x); $y = round_nearest ($y); ### SierpinskiArrowhead xy_to_n(): "$x, $y" if ($y < 0) { return undef; } if ($self->{'align'} eq 'left') { if ($x > 0) { return undef; } $x = 2*$x + $y; # adjust to triangular style } elsif ($self->{'align'} eq 'triangular') { if (($x%2) != ($y%2)) { return undef; } } else { # right or diagonal if ($x < 0) { return undef; } if ($self->{'align'} eq 'right') { $x = 2*$x - $y; } else { # diagonal ($x,$y) = ($x-$y, $x+$y); } } ### adjusted xy: "$x,$y" # On row Y=2^k the points belong to belong in the level below except for # the endmost X=Y or X=-Y. For example Y=4 has N=6 which is in the level # below, but at the end has N=9 belongs to the level above. So $y-1 puts # Y=2^k into the level below and +($y==abs($x)) pushes the end back up to # the next. # my ($len, $level) = round_down_pow ($y-1 + ($y==abs($x)), 2); ### pow2 round down: $y-1+($y==abs($x)) ### $len ### $level if (is_infinite($level)) { return $level; } my $n = 0; while ($level-- >= 0) { ### at: "$x,$y level=$level len=$len" $n *= 3; if ($y < 0 || $x < -$y || $x > $y) { ### out of range return undef; } if ($y < $len + !($x==$y||$x==-$y)) { ### digit 0, first triangle, no change } else { if ($level & 1) { ### odd level if ($x > 0) { ### digit 1, right triangle $n += 1; $y -= $len; $x = - ($x-$len); ### shift right and mirror to: "$x,$y" } else { ### digit 2, left triangle $n += 2; $y -= 2*$len; ### shift down to: "$x,$y" ($x,$y) = ((3*$y-$x)/2, # rotate -120 ($x+$y)/-2); ### rotate to: "$x,$y" } } else { ### even level if ($x < 0) { ### digit 1, left triangle $n += 1; $y -= $len; $x = - ($x+$len); ### shift right and mirror to: "$x,$y" } else { ### digit 2, right triangle $n += 2; $y -= 2*$len; ### shift down to: "$x,$y" ($x,$y) = (($x+3*$y)/-2, # rotate +120 ($x-$y)/2); ### now: "$x,$y" } } } $len /= 2; } if ($x == 0 && $y == 0) { return $n; } else { return undef; } } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### SierpinskiArrowhead rect_to_n_range() ... $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # swap to y1<=y2 if ($self->{'align'} eq 'diagonal') { $y2 += max (round_nearest ($x1), round_nearest ($x2)); } unless ($y2 >= 0) { ### rect all negative, no N ... return (1, 0); } my ($pow,$exp) = round_down_pow ($y2-1, 2); ### $y2 ### $level return (0, 3**($exp+1)); } #----------------------------------------------------------------------------- # level_to_n_range() sub level_to_n_range { my ($self, $level) = @_; return (0, 3**$level); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($pow, $exp) = round_up_pow ($n, 3); return $exp; } #----------------------------------------------------------------------------- 1; __END__ # sideways ... # # 27 ... 8 # \ # . 26 7 # / # 24----25 . 6 # / # 23 . 20----19 5 # \ / \ # . 22----21 . 18 4 # / # 4---- 5 . . 17 . 3 # / \ \ # 3 . 6 . . 16----15 2 # \ / \ # . 2 7 . 10----11 . 14 1 # / \ / \ / # 0---- 1 . 8---- 9 . 12----13 . <- Y=0 # # X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ... # rows # * 1 \ # * * 2 | # * * 2 | # * * * * 4 / # * * 2 \ # * * * * 4 | 2x prev 4 # * * * * 4 | # * * * * * * * * 8 / # * * 2 \ # * * * * 4 | 2x prev 8 # # cumulative # # 1 # 3 # 5 # 9 # 11 \ # 15 | *2+9 # 19 | # 27 / # 29 \ # 33 | *2+27 # 37 # 45 # 49 # 57 # 65 # 81 =for stopwords eg Ryde Sierpinski Nlevel ie bitwise-AND Math-PlanePath OEIS mirrorings =head1 NAME Math::PlanePath::SierpinskiArrowhead -- self-similar triangular path traversal =head1 SYNOPSIS use Math::PlanePath::SierpinskiArrowhead; my $path = Math::PlanePath::SierpinskiArrowhead->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis path is an integer version of Sierpinski's curve from =over Waclaw Sierpinski, "Sur une Courbe Dont Tout Point est un Point de Ramification", Comptes Rendus Hebdomadaires des SE<233>ances de l'AcadE<233>mie des Sciences, volume 160, January-June 1915, pages 302-305. L =back =cut # PDF download pages 304 to 307 inclusive =pod The path is self-similar triangular parts leaving middle triangle gaps giving the Sierpinski triangle shape. \ 27----26 19----18 15----14 8 \ / \ / \ 25 20 17----16 13 7 / \ / 24 21 11----12 6 \ / / 23----22 10 5 \ 5---- 6 9 4 / \ / 4 7---- 8 3 \ 3---- 2 2 \ 1 1 / 0 <- Y=0 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 The base figure is the N=0 to N=3 shape. It's repeated up in mirror image as N=3 to N=6 then across as N=6 to N=9. At the next level the same is done with the N=0 to N=9 shape, up as N=9 to N=18 and across as N=18 to N=27, etc. The X,Y coordinates are on a triangular lattice done in integers by using every second X, per L. The base pattern is a triangle like 3---------2 - - - - . \ \ C / \ B / \ D \ / \ / . - - - - 1 \ / A / \ / / 0 Higher levels go into the triangles A,B,C but the middle triangle D is not traversed. It's hard to see that omitted middle in the initial N=0 to N=27 above. The following is more of the visited points, making it clearer =cut # generated by devel/sierpinski-arrowhead-stars.pl =pod * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * =head2 Sierpinski Triangle The path is related to the Sierpinski triangle or "gasket" by treating each line segment as the side of a little triangle. The N=0 to N=1 segment has a triangle on the left, N=1 to N=2 on the right, and N=2 to N=3 underneath, which are per the A,B,C parts shown above. Notice there's no middle little triangle "D" in the triplets of line segments. In general a segment N to N+1 has its little triangle to the left if N even or to the right if N odd. This pattern of little triangles is why the N=4 to N=5 looks like it hasn't visited the vertex of the triangular N=0 to N=9 -- the 4 to 5 segment is standing in for a little triangle to the left of that segment. Similarly N=13 to N=14 and each alternate side midway through replication levels. There's easier ways to generate the Sierpinski triangle though. One of the simplest is to take X,Y coordinates which have no 1 bit on common, ie. a bitwise-AND, ($x & $y) == 0 which gives the shape in the first quadrant XE=0,YE=0. The same can be had with the C path by plotting all numbers N which have no digit 3 in their base-4 representation (see L), since digit 3s in that case are X,Y points with a 1 bit in common. The attraction of this Arrowhead path is that it makes a connected traversal through the Sierpinski triangle pattern. =head2 Level Sizes Counting the N=0,1,2,3 part as level 1, each level goes from Nstart = 0 Nlevel = 3^level inclusive of the final triangle corner position. For example level 2 is from N=0 to N=3^2=9. Each level doubles in size, 0 <= Y <= 2^level - 2^level <= X <= 2^level The final Nlevel position is alternately on the right or left, Xlevel = / 2^level if level even \ - 2^level if level odd The Y axis is crossed, ie. X=0, at N=2,6,18,etc which is is 2/3 through the level, ie. after two replications of the previous level, Ncross = 2/3 * 3^level = 2 * 3^(level-1) =head2 Align Parameter An optional C parameter controls how the points are arranged relative to the Y axis. The default shown above is "triangular". The choices are the same as for the C path. "right" means points to the right of the axis, packed next to each other and so using an eighth of the plane. =cut # math-image --path=SierpinskiArrowhead,align=right --all --output=numbers_dash --size=78x22 =pod align => "right" | | 8 | 27-26 19-18 15-14 | | / | / | 7 | 25 20 17-16 13 | / | / 6 | 24 21 11-12 | | / / 5 | 23-22 10 | | 4 | 5--6 9 | / | / 3 | 4 7--8 | | 2 | 3--2 | | 1 | 1 | / Y=0 | 0 +-------------------------- X=0 1 2 3 4 5 6 7 "left" is similar but skewed to the left of the Y axis, ie. into negative X. =cut # math-image --path=SierpinskiArrowhead,align=left --all --output=numbers_dash --size=78x22 =pod align => "left" \ 27-26 19-18 15-14 | 8 \ | \ | \ | 25 20 17-16 13 | 7 | \ | | 24 21 11-12 | 6 \ | | | 23-22 10 | 5 \ | 5--6 9 | 4 | \ | | 4 7--8 | 3 \ | 3--2 | 2 \ | 1 | 1 | | 0 | Y=0 -----------------------------+ -8 -7 -6 -5 -4 -3 -2 -1 X=0 "diagonal" put rows on diagonals down from the Y axis to the X axis. This uses the whole of the first quadrant (with gaps). =cut # math-image --expression='i<=27?i:0' --path=SierpinskiArrowhead,align=diagonal --output=numbers_dash --size=78x22 =pod align => "diagonal" | | 8 | 27 | \ 7 | 26 | | 6 | 24-25 | | 5 | 23 20-19 | \ | \ 4 | 22-21 18 | | 3 | 4--5 17 | | \ \ 2 | 3 6 16-15 | \ | \ 1 | 2 7 10-11 14 | | \ | \ | Y=0 | 0--1 8--9 12-13 +-------------------------- X=0 1 2 3 4 5 6 7 =head2 Sideways Sierpinski presents the curve with a base along the X axis. That can be had here with a -60 degree rotation (see L), (3Y+X)/2, (Y-X)/2 rotate -60 The first point N=1 is then along the X axis at X=2,Y=0. Or to have it diagonally upwards first then apply a mirroring -X before rotating (3Y-X)/2, (Y+X)/2 mirror X and rotate -60 The plain -60 rotate puts the Nlevel=3^level point on the X axis for even number level, and at the top peak for odd level. With the extra mirroring it's the other way around. If drawing successive levels then the two ways can be alternated to have the endpoint on the X axis each time. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::SierpinskiArrowhead-Enew ()> =item C<$path = Math::PlanePath::SierpinskiArrowhead-Enew (align =E $str)> Create and return a new arrowhead path object. C is a string, one of the following as described above. "triangular" the default "right" "left" "diagonal" =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. If C<$n> is not an integer then the return is on a straight line between the integer points. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 3**$level)>. =back =head1 FORMULAS =head2 Turn The turn at N is given by ternary turn(N) N + LowestNonZero(N) + CountLowZeros(N) ------- --------------------------------------- left even right odd In the replications, turns N=1 and N=2 are both left. A low 0 digit expansion is mirror image to maintain initial segment direction. Parts "B" digit=1 above are each mirror images too so turns flip. [flip for each 1 digit] [1 or 2] [flip for each low 0 digit] N is odd or even according as the number of ternary 1 digits is odd or even (all 2 digits being even of course), so N parity accounts for the "B" mirrorings. On a binary computer this is just the low bit rather than examining the high digits of N. In any case if the ternary lowest non-0 is a 1 then it is not such a mirror so adding LowestNonZero cancels that. This turn rule is noted by Alexis Monnerot-Dumaine in OEIS A156595. That sequence is LowestNonZero(N) + CountLowZeros(N) mod 2 and flipping according as N odd or even is the arrowhead turns. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include, =over L (etc) =back A156595 turn 0=left,1=right at even N=2,4,6,etc A189706 turn 0=left,1=right at odd N=1,3,5,etc A189707 (N+1)/2 of the odd N positions of left turns A189708 (N+1)/2 of the odd N positions of right turns align=diagonal A334483 X coordinate A334484 Y coordinate =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/SierpinskiCurve.pm0000644000175000017500000011103513734026656020721 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Sierpinski, "Sur Une Nouvelle Courbe Qui Remplit Toute Une Aire Plaine", # Bull. Acad. Sci. Cracovie Series A, 1912, pages 462-478. package Math::PlanePath::SierpinskiCurve; use 5.004; use strict; use List::Util 'sum','first'; #use List::Util 'min','max'; *min = \&Math::PlanePath::_min; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_up_pow', 'round_down_pow', 'digit_split_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; sub x_negative { my ($self) = @_; return ($self->{'arms'} >= 3); } sub y_negative { my ($self) = @_; return ($self->{'arms'} >= 5); } use constant parameter_info_array => [ { name => 'arms', share_key => 'arms_8', display => 'Arms', type => 'integer', minimum => 1, maximum => 8, default => 1, width => 1, description => 'Arms', }, { name => 'straight_spacing', display => 'Straight Spacing', type => 'integer', minimum => 1, default => 1, width => 1, description => 'Spacing of the straight line points.', }, { name => 'diagonal_spacing', display => 'Diagonal Spacing', type => 'integer', minimum => 1, default => 1, width => 1, description => 'Spacing of the diagonal points.', }, ]; # Ntop = (4^level)/2 - 1 # Xtop = 3*2^(level-1) - 1 # fill = Ntop / (Xtop*(Xtop-1)/2) # -> 2 * ((4^level)/2 - 1) / (3*2^(level-1) - 1)^2 # -> 2 * ((4^level)/2) / (3*2^(level-1))^2 # = 4^level / (9*4^(level-1) # = 4/9 = 0.444 sub x_negative_at_n { my ($self) = @_; return $self->arms_count >= 3 ? 2 : undef; } sub y_negative_at_n { my ($self) = @_; return $self->arms_count >= 5 ? 4 : undef; } { # Note: shared by Math::PlanePath::SierpinskiCurveStair my @x_minimum = (undef, 1, # 1 arm 0, # 2 arms ); # more than 2 arm, X goes negative sub x_minimum { my ($self) = @_; return $x_minimum[$self->arms_count]; } } { # Note: shared by Math::PlanePath::SierpinskiCurveStair my @sumxy_minimum = (undef, 1, # 1 arm, octant and X>=1 so X+Y>=1 1, # 2 arms, X>=1 or Y>=1 so X+Y>=1 0, # 3 arms, Y>=1 and X>=Y, so X+Y>=0 ); # more than 3 arm, Sum goes negative so undef sub sumxy_minimum { my ($self) = @_; return $sumxy_minimum[$self->arms_count]; } } use constant sumabsxy_minimum => 1; # Note: shared by Math::PlanePath::SierpinskiCurveStair # Math::PlanePath::AlternatePaper # Math::PlanePath::AlternatePaperMidpoint sub diffxy_minimum { my ($self) = @_; return ($self->arms_count == 1 ? 1 # octant Y<=X-1 so X-Y>=1 : undef); # more than 1 arm, DiffXY goes negative } use constant absdiffxy_minimum => 1; # X=Y never occurs use constant rsquared_minimum => 1; # minimum X=1,Y=0 sub dx_minimum { my ($self) = @_; return - max($self->{'straight_spacing'}, $self->{'diagonal_spacing'}); } *dy_minimum = \&dx_minimum; sub dx_maximum { my ($self) = @_; return max($self->{'straight_spacing'}, $self->{'diagonal_spacing'}); } *dy_maximum = \&dx_maximum; sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; my $s = $self->{'straight_spacing'}; my $d = $self->{'diagonal_spacing'}; return ($s,0, # E eight scaled ($d ? ( $d, $d) : ()), # NE except s=0 ($s ? ( 0, $s) : ()), # N or d=0 skips ($d ? (-$d, $d) : ()), # NW ($s ? (-$s, 0) : ()), # W ($d ? (-$d,-$d) : ()), # SW ($s ? ( 0,-$s) : ()), # S ($d ? ( $d,-$d) : ())); # SE } { my @_UNDOCUMENTED__dxdy_list_at_n = (undef, 21, 20, 27, 36, 29, 12, 12, 13); sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return $_UNDOCUMENTED__dxdy_list_at_n[$self->{'arms'}]; } } sub dsumxy_minimum { my ($self) = @_; return - max($self->{'straight_spacing'}, 2*$self->{'diagonal_spacing'}); } sub dsumxy_maximum { my ($self) = @_; return max($self->{'straight_spacing'}, 2*$self->{'diagonal_spacing'}); } *ddiffxy_minimum = \&dsumxy_minimum; *ddiffxy_maximum = \&dsumxy_maximum; use constant dir_maximum_dxdy => (1,-1); # South-East use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(8, $self->{'arms'} || 1)); $self->{'straight_spacing'} ||= 1; $self->{'diagonal_spacing'} ||= 1; return $self; } sub n_to_xy { my ($self, $n) = @_; ### SierpinskiCurve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); # BigFloat int() gives BigInt, use that $n -= $int; # preserve possible BigFloat ### $int ### $n my $arm = _divrem_mutate ($int, $self->{'arms'}); my $s = $self->{'straight_spacing'}; my $d = $self->{'diagonal_spacing'}; my $base = 2*$d+$s; my $x = my $y = ($int * 0); # inherit big 0 my $len = $x + $base; # inherit big foreach my $digit (digit_split_lowtohigh($int,4)) { ### at: "$x,$y digit=$digit" if ($digit == 0) { $x = $n*$d + $x; $y = $n*$d + $y; $n = 0; } elsif ($digit == 1) { ($x,$y) = ($n*$s - $y + $len-$d-$s, # rotate +90 $x + $d); $n = 0; } elsif ($digit == 2) { # rotate -90 ($x,$y) = ($n*$d + $y + $len-$d, -$n*$d - $x + $len-$d-$s); $n = 0; } else { # digit==3 $x += $len; } $len *= 2; } # n=0 or n=33..33 $x = $n*$d + $x; $y = $n*$d + $y; $x += 1; if ($arm & 1) { ($x,$y) = ($y,$x); # mirror 45 } if ($arm & 2) { ($x,$y) = (-1-$y,$x); # rotate +90 } if ($arm & 4) { $x = -1-$x; # rotate 180 $y = -1-$y; } # use POSIX 'floor'; # $x += floor($x/3); # $y += floor($y/3); # $x += floor(($x-1)/3) + floor(($x-2)/3); # $y += floor(($y-1)/3) + floor(($y-2)/3); ### final: "$x,$y" return ($x,$y); } my @digit_to_dir = (0, -2, 2, 0); my @dir8_to_dx = (1, 1, 0,-1, -1, -1, 0, 1); my @dir8_to_dy = (0, 1, 1, 1, 0, -1, -1,-1); my @digit_to_nextturn = (-1, # after digit=0 2, # digit=1 -1); # digit=2 sub n_to_dxdy { my ($self, $n) = @_; ### n_to_dxdy(): $n if ($n < 0) { return; # first direction at N=0 } my $int = int($n); $n -= $int; my $arm = _divrem_mutate($int,$self->{'arms'}); my $lowbit = _divrem_mutate($int,2); ### $lowbit ### $int if (is_infinite($int)) { return ($int,$int); } my @ndigits = digit_split_lowtohigh($int,4); ### @ndigits my $dir8 = sum(0, map {$digit_to_dir[$_]} @ndigits); if ($arm & 1) { $dir8 = - $dir8; # mirrored on second,fourth,etc arm } $dir8 += ($arm|1); # NE,NW,SW, or SE my $turn; if ($n || $lowbit) { # next turn # lowest non-3 digit, or zero if all 3s (implicit 0 above high digit) $turn = $digit_to_nextturn[ first {$_!=3} @ndigits, 0 ]; if ($arm & 1) { $turn = - $turn; # mirrored on second,fourth,etc arm } } if ($lowbit) { $dir8 += $turn; } my $s = $self->{'straight_spacing'}; my $d = $self->{'diagonal_spacing'}; $dir8 &= 7; my $spacing = ($dir8 & 1 ? $d : $s); my $dx = $spacing * $dir8_to_dx[$dir8]; my $dy = $spacing * $dir8_to_dy[$dir8]; if ($n) { $dir8 += $turn; $dir8 &= 7; $spacing = ($dir8 & 1 ? $d : $s); $dx += $n*($spacing * $dir8_to_dx[$dir8] - $dx); $dy += $n*($spacing * $dir8_to_dy[$dir8] - $dy); } return ($dx, $dy); } # 2| . 3 . # 1| 1 . 2 # 0| . 0 . # +------ # 0 1 2 # # 4| . . . 3 . # diagonal_spacing == 3 # 3| . . . . 2 4 # mod=2*3+1=7 # 2| . . . . . . . # 1| 1 . . . . . . . # 0| . 0 . . . . . . 6 # +------------------ # 0 1 2 3 4 5 6 7 8 # sub _NOTWORKING__xy_is_visited { my ($self, $x, $y) = @_; $x = round_nearest($x); $y = round_nearest($y); my $mod = 2*$self->{'diagonal_spacing'} + $self->{'straight_spacing'}; return (_rect_within_arms($x,$y, $x,$y, $self->{'arms'}) && ((($x%$mod)+($y%$mod)) & 1)); } # x1 * x2 * # +-----*-+y2* # | *| * # | * * # | |* * # | | ** # +-------+y1* # ---------------- # # arms=5 x1,y2 after X=Y-1 line, so x1 > y2-1, x1 >= y2 # ************ # x1 * x2 # +---*----+y2 # | * | # | * | # |* | # * | # *+--------+y1 # * # # arms=7 x1,y1 after X=-2-Y line, so x1 > -2-y1 # ************ # ** +------+ # * *| | # * * | # * |* | # * | * | # *y1+--*---+ # * x1 * # # _rect_within_arms() returns true if rectangle x1,y1,x2,y2 has some part # within the extent of the $arms set of octants. # sub _rect_within_arms { my ($x1,$y1, $x2,$y2, $arms) = @_; return ($arms <= 4 ? ($y2 >= 0 # y2 top edge must be positive && ($arms <= 2 ? ($arms == 1 ? $x2 > $y1 # arms==1 bottom right : $x2 >= 0) # arms==2 right edge : ($arms == 4 # arms==4 anything || $x2 >= -$y2))) # arms==3 top right # arms >= 5 : ($y2 >= 0 # y2 top edge positive is good, otherwise check || ($arms <= 6 ? ($arms == 5 ? $x1 < $y2 # arms==5 top left : $x1 < 0) # arms==6 left edge : ($arms == 8 # arms==8 anything || $x1 <= -2-$y1)))); # arms==7 bottom left } sub xy_to_n { my ($self, $x, $y) = @_; ### SierpinskiCurve xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); my $arm = 0; if ($y < 0) { $arm = 4; $x = -1-$x; # rotate -180 $y = -1-$y; } if ($x < 0) { $arm += 2; ($x,$y) = ($y, -1-$x); # rotate -90 } if ($y > $x) { # second octant $arm++; ($x,$y) = ($y,$x); # mirror 45 } my $arms = $self->{'arms'}; if ($arm >= $arms) { return undef; } $x -= 1; if ($x < 0 || $x < $y) { return undef; } ### x adjust to zero: "$x,$y" ### assert: $x >= 0 ### assert: $y >= 0 my $s = $self->{'straight_spacing'}; my $d = $self->{'diagonal_spacing'}; my $base = (2*$d+$s); my ($len,$level) = round_down_pow (($x+$y)/$base || 1, 2); ### $level ### $len if (is_infinite($level)) { return $level; } # Xtop = 3*2^(level-1)-1 # $len *= 2*$base; ### initial len: $len my $n = 0; foreach (0 .. $level) { $n *= 4; ### at: "loop=$_ len=$len x=$x,y=$y n=$n" ### assert: $x >= 0 ### assert: $y >= 0 my $len_sub_d = $len - $d; if ($x < $len_sub_d) { ### digit 0 or 1... if ($x+$y+$s < $len) { ### digit 0 ... } else { ### digit 1 ... ($x,$y) = ($y-$d, $len-$s-$d-$x); # shift then rotate -90 $n += 1; } } else { $x -= $len_sub_d; ### digit 2 or 3 to: "x=$x y=$y" if ($x < $y) { # before diagonal ### digit 2... ($x,$y) = ($len-$d-$s-$y, $x); # shift y-len then rotate +90 $n += 2; } else { #### digit 3... $x -= $d; $n += 3; } if ($x < 0) { return undef; } } $len /= 2; } ### end at: "x=$x,y=$y n=$n" ### assert: $x >= 0 ### assert: $y >= 0 $n *= 4; if ($y == 0 && $x == 0) { ### final digit 0 ... } elsif ($x == $d && $y == $d) { ### final digit 1 ... $n += 1; } elsif ($x == $d+$s && $y == $d) { ### final digit 2 ... $n += 2; } elsif ($x == $base && $y == 0) { ### final digit 3 ... $n += 3; } else { return undef; } return $n*$arms + $arm; } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### SierpinskiCurve rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; my $arms = $self->{'arms'}; unless (_rect_within_arms($x1,$y1, $x2,$y2, $arms)) { ### rect outside octants, for arms: $arms return (1,0); } my $max = ($x2 + $y2); if ($arms >= 3) { _apply_max ($max, -1-$x1 + $y2); if ($arms >= 5) { _apply_max ($max, -1-$x1 - $y1-1); if ($arms >= 7) { _apply_max ($max, $x2 - $y1-1); } } } # base=2d+s # level begins at # base*(2^level-1)-s = X+Y ... maybe # base*2^level = X+base # 2^level = (X+base)/base # level = log2((X+base)/base) # then # Nlevel = 4^level-1 my $base = 2 * $self->{'diagonal_spacing'} + $self->{'straight_spacing'}; my ($power) = round_down_pow (int(($max+$base-2)/$base), 2); return (0, 4*$power*$power * $arms - 1); } sub _apply_max { ### _apply_max(): "$_[0] cf $_[1]" unless ($_[0] > $_[1]) { $_[0] = $_[1]; } } #------------------------------------------------------------------------------ sub level_to_n_range { my ($self, $level) = @_; return (0, 4**$level * $self->{'arms'} - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); _divrem_mutate ($n, $self->{'arms'}); my ($pow, $exp) = round_up_pow ($n+1, 4); return $exp; } #------------------------------------------------------------------------------ 1; __END__ # # ...0 ...1 # # ...1 ...2 # # ...2 ...3 # # ..0333 ..1000 any low 3s # # ..02 ..03 # # ..12 ..13 # # ..22 ..23 # # ..03332 ..03333 # # ..13332 ..13333 # # ..23332 ..23333 # # my @lowdigit_to_dir = (1,-2, 1, 0); # my @digit_to_dir = (0, 2,-2, 0); # my @dir8_to_dx = (1, 1, 0,-1, -1, -1, 0, 1); # my @dir8_to_dy = (0, 1, 1, 1, 0, -1, -1,-1); # my @digit_to_nextturn = (-1,-1,2); # my @digit_to_nextturn2 = (2,-1,2); # # sub _WORKING_BUT_HAIRY__n_to_dxdy { # my ($self, $n) = @_; # ### n_to_dxdy(): $n # # if ($n < 0) { # return; # first direction at N=0 # } # if (is_infinite($n)) { # return ($n,$n); # } # # my $int = int($n); # $n -= $int; # my @digits = digit_split_lowtohigh($int,4); # ### @digits # # # strip low 3s # my $any_low3s; # while (($digits[0]||0) == 3) { # shift @digits; # $any_low3s = 1; # } # # my $dir8 = $lowdigit_to_dir[$digits[0] || 0]; # $dir8 += sum(0, map {$digit_to_dir[$_]} @digits); # $dir8 &= 7; # my $dx = $dir8_to_dx[$dir8]; # my $dy = $dir8_to_dy[$dir8]; # # if ($n) { # # fraction part # # if ($any_low3s) { # $dir8 += $digit_to_nextturn2[$digits[0]||0]; # } else { # my $digit = $digits[0] || 0; # if ($digit == 2) { # shift @digits; # # lowest non-3 digit # do { # $digit = shift @digits || 0; # zero if all 3s or no digits at all # } until ($digit != 3); # $dir8 += $digit_to_nextturn2[$digit]; # } else { # $dir8 += $digit_to_nextturn[$digit]; # } # } # $dir8 &= 7; # $dx += $n*($dir8_to_dx[$dir8] - $dx); # $dy += $n*($dir8_to_dy[$dir8] - $dy); # } # return ($dx, $dy); # } # 63-64 14 # | | # 62 65 13 # / \ # 60-61 66-67 12 # | | # 59-58 69-68 11 # \ / # 51-52 57 70 10 # | | | | # 50 53 56 71 ... 9 # / \ / \ / # 48-49 54-55 72-73 8 # | # 47-46 41-40 7 # \ / \ # 15-16 45 42 39 6 # | | | | | # 14 17 44-43 38 5 # / \ / # 12-13 18-19 36-37 4 # | | | # 11-10 21-20 35-34 3 # \ / \ # 3--4 9 22 27-28 33 2 # | | | | | | | # 2 5 8 23 26 29 32 1 # / \ / \ / \ / # 0--1 6--7 24-25 30-31 Y=0 # # ^ # X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ... # The factor of 3 arises because there's a gap between each level, increasing # it by a fixed extra each time, # # length(level) = 2*length(level-1) + 2 # = 2^level + (2^level + 2^(level-1) + ... + 2) # = 2^level + (2^(level+1)-1 - 1) # = 3*2^level - 2 =for stopwords eg Ryde Waclaw Sierpinski Sierpinski's Math-PlanePath Nlevel Nend Ntop Xlevel OEIS dX dY dX,dY nextturn =head1 NAME Math::PlanePath::SierpinskiCurve -- Sierpinski curve =head1 SYNOPSIS use Math::PlanePath::SierpinskiCurve; my $path = Math::PlanePath::SierpinskiCurve->new (arms => 2); my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis is an integer version of the self-similar curve by Waclaw Sierpinski traversing the plane by right triangles. The default is a single arm of the curve in an eighth of the plane. =cut # math-image --path=SierpinskiCurve --all --output=numbers_dash --size=79x26 =pod 10 | 31-32 | / \ 9 | 30 33 | | | 8 | 29 34 | \ / 7 | 25-26 28 35 37-38 | / \ / \ / \ 6 | 24 27 36 39 | | | 5 | 23 20 43 40 | \ / \ / \ / 4 | 7--8 22-21 19 44 42-41 55-... | / \ / \ / 3 | 6 9 18 45 54 | | | | | | 2 | 5 10 17 46 53 | \ / \ / \ 1 | 1--2 4 11 13-14 16 47 49-50 52 | / \ / \ / \ / \ / \ / Y=0 | . 0 3 12 15 48 51 | +----------------------------------------------------------- ^ X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 The tiling it represents is / /|\ / | \ / | \ / 7| 8 \ / \ | / \ / \ | / \ / 6 \|/ 9 \ /-------|-------\ /|\ 5 /|\ 10 /|\ / | \ / | \ / | \ / | \ / | \ / | \ / 1| 2 X 4 |11 X 13|14 \ / \ | / \ | / \ | / \ ... / \ | / \ | / \ | / \ / 0 \|/ 3 \|/ 12 \|/ 15 \ ---------------------------------- The points are on a square grid with integer X,Y. 4 points are used in each 3x3 block. In general a point is used if X%3==1 or Y%3==1 but not both which means ((X%3)+(Y%3)) % 2 == 1 The X axis N=0,3,12,15,48,etc are all the integers which use only digits 0 and 3 in base 4. For example N=51 is 303 base4. Or equivalently the values all have doubled bits in binary, for example N=48 is 110000 binary. (Compare the C which also has these values along the X axis.) =head2 Level Ranges Counting the N=0 point as level=0, and with each level being 4 copies of the previous, the levels end at Nlevel = 4^level - 1 = 0, 3, 15, ... Xlevel = 3*2^level - 2 = 1, 4, 10, ... Ylevel = 0 For example level=2 is Nlevel = 2^(2*2)-1 = 15 at X=3*2^2-2 = 10. =for GP-DEFINE Nlevel(level) = 4^level - 1 =for GP-DEFINE Xlevel(level) = 3*2^level - 2 =for GP-Test Nlevel(0) == 0 =for GP-Test Nlevel(1) == 3 =for GP-Test Nlevel(2) == 15 =for GP-Test Xlevel(0) == 1 =for GP-Test Xlevel(1) == 4 =for GP-Test Xlevel(2) == 10 Doubling a level is the middle of the next level and is the top of the triangle in that next level. Ntop = 2*4^level - 1 = 1, 7, 31, ... Xtop = 3*2^level - 1 = 2, 5, 11, ... Ytop = 3*2^level - 2 = Xlevel = 1, 4, 10, ... For example doubling level=2 is Ntop = 2*4^2-1 = 31 at X=3*2^2-1 = 11 and Y=3*2^2-2 = 10. =for GP-DEFINE Ntop(level) = 2*4^level - 1 =for GP-DEFINE Xtop(level) = 3*2^level - 1 =for GP-DEFINE Ytop(level) = 3*2^level - 2 =for GP-Test 2*4^2-1 == 31 =for GP-Test Ntop(2) == 31 =for GP-Test X=3*2^2-1 == 11 =for GP-Test Xtop(2) == 11 =for GP-Test 3*2^2-2 == 10 =for GP-Test Ytop(2) == 10 The factor of 3 arises from the three steps which make up the N=0,1,2,3 section. The Xlevel width grows as Xlevel(1) = 3 Xlevel(level) = 2*Xwidth(level-1) + 3 which dividing out the factor of 3 is 2*w+1, giving 2^k-1 (in binary a left shift and bring in a new 1 bit). Notice too the Nlevel points as a fraction of the triangular area Xlevel*(Xlevel-1)/2 gives the 4 out of 9 points filled, FillFrac = Nlevel / (Xlevel*(Xlevel-1)/2) -> 4/9 =head2 Arms The optional C parameter can draw multiple curves, each advancing successively. For example 2 arms, arms => 2 ... | 11 | 33 39 57 63 | / \ / \ / \ / 10 | 31 35-37 41 55 59-61 62-... | \ / \ / 9 | 29 43 53 60 | | | | | 8 | 27 45 51 58 | / \ / \ 7 | 25 21-19 47-49 50-52 56 | \ / \ / \ / 6 | 23 17 48 54 | | | 5 | 9 15 46 40 | / \ / \ / \ 4 | 7 11-13 14-16 44-42 38 | \ / \ / 3 | 5 12 18 36 | | | | | 2 | 3 10 20 34 | / \ / \ 1 | 1 2--4 8 22 26-28 32 | / \ / \ / \ / Y=0 | 0 6 24 30 | +----------------------------------------- ^ X=0 1 2 3 4 5 6 7 8 9 10 11 The N=0 point is at X=1,Y=0 (in all arms forms) so that the second arm is within the first quadrant. 1 to 8 arms can be done this way. For example 8 arms are arms => 8 ... ... 6 | | 58 34 33 57 5 \ / \ / \ / ...-59 50-42 26 25 41-49 56-... 4 \ / \ / 51 18 17 48 3 | | | | 43 10 9 40 2 / \ / \ 35 19-11 2 1 8-16 32 1 \ / \ / \ / 27 3 . 0 24 <- Y=0 28 4 7 31 -1 / \ / \ / \ 36 20-12 5 6 15-23 39 -2 \ / \ / 44 13 14 47 -3 | | | | 52 21 22 55 -4 / \ / \ ...-60 53-45 29 30 46-54 63-... -5 / \ / \ / \ 61 37 38 62 -6 | | ... ... -7 ^ -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 The middle "." is the origin X=0,Y=0. It would be more symmetrical to make the origin the middle of the eight arms, at X=-0.5,Y=-0.5 in the above, but that would give fractional X,Y values. Apply an offset X+0.5,Y+0.5 to centre it if desired. =head2 Spacing The optional C and C can increase the space between points diagonally or vertically+horizontally. The default for each is 1. =cut # math-image --path=SierpinskiCurve,straight_spacing=2,diagonal_spacing=1 --all --output=numbers_dash --size=79x26 # math-image --path=SierpinskiCurve,straight_spacing=3,diagonal_spacing=3 --all --output=numbers_dash --size=79x26 =pod straight_spacing => 2 diagonal_spacing => 1 7 ----- 8 / \ 6 9 | | | | | | 5 10 ... \ / \ 1 ----- 2 4 11 13 ---- 14 16 / \ / \ / \ / 0 3 12 15 X=0 1 2 3 4 5 6 7 8 9 10 11 12 13 ... The effect is only to spread the points. The straight lines are both horizontal and vertical so when they're stretched the curve remains on a 45 degree angle in an eighth of the plane. In the level formulas above the "3" factor becomes 2*d+s, effectively being the N=0 to N=3 section sized as d+s+d. d = diagonal_spacing s = straight_spacing Xlevel = (2d+s)*(2^level - 1) + 1 Xtop = (2d+s)*2^(level-1) - d - s + 1 Ytop = (2d+s)*2^(level-1) - d - s =head2 Closed Curve Sierpinski's original conception was a closed curve filling a unit square by ever greater self-similar detail, /\_/\ /\_/\ /\_/\ /\_/\ \ / \ / \ / \ / | | | | | | | | / _ \_/ _ \ / _ \_/ _ \ \/ \ / \/ \/ \ / \/ | | | | /\_/ _ \_/\ /\_/ _ \_/\ \ / \ / \ / \ / | | | | | | | | / _ \ / _ \_/ _ \ / _ \ \/ \/ \/ \ / \/ \/ \/ | | /\_/\ /\_/ _ \_/\ /\_/\ \ / \ / \ / \ / | | | | | | | | / _ \_/ _ \ / _ \_/ _ \ \/ \ / \/ \/ \ / \/ | | | | /\_/ _ \_/\ /\_/ _ \_/\ \ / \ / \ / \ / | | | | | | | | / _ \ / _ \ / _ \ / _ \ \/ \/ \/ \/ \/ \/ \/ \/ The code here might be pressed into use for this by drawing a mirror image of the curve N=0 through Nlevel. Or using the C2> form N=0 to N=4^level - 1, inclusive, and joining up the ends. The curve is also usually conceived as scaling down by quarters. This can be had with C 2> and then an offset to X+1,Y+1 to centre in a 4*2^level square =head2 Koch Curve Midpoints The replicating structure is the same as the Koch curve (L) in that the curve repeats four times to make the next level. The Sierpinski curve points are midpoints of a Koch curve of 90 degree angles with a unit gap between verticals. Koch Curve Koch Curve 90 degree angles, unit gap /\ | | / \ | | / \ | | ----- ----- ------ ------ =cut =pod Sierpinski curve points "*" as midpoints | | 7 8 | | ---6--- ---9--- ---5--- --10--- | | | | | | 1 2 4 11 13 14 | | | | | | ---0--- ---3--- --12--- --15--- =head2 Koch Curve Rounded The Sierpinski curve in mirror image across the X=Y diagonal and rotated -45 degrees is pairs of points on the lines of the Koch curve 90 degree angles unit gap from above. Sierpinski curve mirror image and turn -45 degrees two points on each Koch line segment 15 16 | | 14 17 12--13 . . 18--19 11--10 . . 21--20 3 4 9 22 27 28 | | | | | | 2 5 8 23 26 29 0---1 . . 6---7 . . 24--25 . . 30--31 This is a kind of "rounded" form of the 90-degree Koch, similar what C does for the C. Each 90 turn of the Koch curve is done by two turns of 45 degrees in the Sierpinski curve here, and each 180 degree turn in the Koch is two 90 degree turns here. So the Sierpinski turn sequence is pairs of the Koch turn sequence, as follows. The mirroring means a swap leftE-Eright between the two. N=1 2 3 4 5 6 7 8 Koch L R L L L R L R ... N=1,2 3,4 5,6 7,8 9,10 11,12 13,14 15,16 Sierp R R L L R R R R R R L L R R L L ... =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::SierpinskiCurve-Enew ()> =item C<$path = Math::PlanePath::SierpinskiCurve-Enew (arms =E $integer, diagonal_spacing =E $integer, straight_spacing =E $integer)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =item C<$n = $path-En_start()> Return 0, the first N in the path. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 4**$level - 1)>, or for multiple arms return C<(0, $arms * 4**$level - 1)>. There are 4^level points in a level, or arms*4^level when multiple arms, numbered starting from 0. =back =head1 FORMULAS =head2 N to dX,dY The curve direction at N even can be calculated from the base-4 digits of N/2 in a fashion similar to the Koch curve (L). Counting direction in eighths so 0=East, 1=North-East, 2=North, etc, digit direction ----- --------- 0 0 1 -2 2 2 3 0 direction = 1 + sum direction[base-4 digits of N/2] for N even For example the direction at N=10 has N/2=5 which is "11" in base-4, so direction = 1+(-2)+(-2) = -3 = south-west. The 1 in 1+sum is direction north-east for N=0, then -2 or +2 for the digits follow the curve. For an odd arm the curve is mirrored and the sign of each digit direction is flipped, so a subtract instead of add, direction mirrored = 1 - sum direction[base-4 digits of N/2] for N even For odd N=2k+1 the direction at N=2k is calculated and then also the turn which is made from N=2k to N=2(k+1). This is similar to the Koch curve next turn (L). lowest non-3 next turn digit of N/2 (at N=2k+1,N=2k+2) ------------ ---------------- 0 -1 (right) 1 +2 (left) 2 -1 (right) Again the turn is in eighths, so -1 means -45 degrees (to the right). For example at N=14 has N/2=7 which is "13" in base-4 so lowest non-3 is "1" which is turn +2, so at N=15 and N=16 turn by 90 degrees left. direction = 1 + sum direction[base-4 digits of k] + if N odd then nextturn[low-non-3 of k] for N=2k or 2k+1 dX,dY = direction to 1,0 1,1 0,1 etc For fractional N the same nextturn is applied to calculate the direction of the next segment, and combined with the integer dX,dY as per L. N=2k or 2k+1 + frac direction = 1 + sum direction[base-4 digits of k] if (frac != 0 or N odd) turn = nextturn[low-non-3 of k] if N odd then direction += turn dX,dY = direction to 1,0 1,1 0,1 etc if frac!=0 then direction += turn next_dX,next_dY = direction to 1,0 1,1 0,1 etc dX += frac*(next_dX - dX) dY += frac*(next_dY - dY) For the C and C options the dX,dY values are not units like dX=1,dY=0 but instead are the spacing amount, either straight or diagonal so direction delta with spacing --------- ------------------------- 0 dX=straight_spacing, dY=0 1 dX=diagonal_spacing, dY=diagonal_spacing 2 dX=0, dY=straight_spacing 3 dX=-diagonal_spacing, dY=diagonal_spacing etc As an alternative, it's possible to take just base-4 digits of N, without separate handling for the low-bit of N, but it requires an adjustment on the low base-4 digit, and the next turn calculation for fractional N becomes hairier. A little state table could encode the cumulative and lowest whatever if desired, to take N by base-4 digits high to low, or equivalently by bits high to low with an initial state based on high bit at an odd or even bit position. =head1 OEIS The Sierpinski curve is in Sloane's Online Encyclopedia of Integer Sequences as, =over L (etc) =back A039963 turn 1=right,0=left, doubling the KochCurve turns A081706 N-1 of left turn positions (first values 2,3 whereas N=3,4 here) A127254 abs(dY), so 0=horizontal, 1=vertical or diagonal, except extra initial 1 A081026 X at N=2^k, being successively 3*2^j-1, 3*2^j A039963 is numbered starting n=0 for the first turn, which is at the point N=1 in the path here. =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/PeanoCurve.pm0000644000175000017500000010012213734026667017640 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # cf # # http://www.cut-the-knot.org/Curriculum/Geometry/PeanoComplete.shtml # applet, directions in 9 sub-parts # # math-image --path=PeanoCurve,radix=5 --all --output=numbers # math-image --path=PeanoCurve,radix=5 --lines # # ----------- # Peano: # T = 0.a1 a2 a3 a4 ... # x y x y # # X = 0.b1 b2 ... # a1 a3.k(a2) # # Y = 0.c1 c2 ... # a2.k(a1) a4.k(a1,a3) # # b1=a1 # c1 = a2 comp(a1) # b2 = a3 comp(a2) # c2 = a4 comp(a1+a3) # # bn = a[2n-1] comp a2+a4+...+a[2n-2] # cn = a[2n] comp a1+a3+...+a[2n-1] # # Brouwer(?) no continuous one-to-one between R and RxR, so line and plane # are distinguished. # package Math::PlanePath::PeanoCurve; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use Math::PlanePath::Base::NSEW; # uncomment this to run the ### lines # use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; use constant parameter_info_array => [ { name => 'radix', display => 'Radix', share_key => 'radix_3', type => 'integer', minimum => 2, default => 3, width => 3, } ]; # shared by WunderlichSerpentine sub dx_minimum { my ($self) = @_; return ($self->{'radix'} % 2 ? -1 # odd : undef); # even, unlimited } sub dx_maximum { my ($self) = @_; return ($self->{'radix'} % 2 ? 1 # odd : undef); # even, unlimited } # shared by WunderlichSerpentine sub _UNDOCUMENTED__dxdy_list { my ($self) = @_; return ($self->{'radix'} % 2 ? Math::PlanePath::Base::NSEW->_UNDOCUMENTED__dxdy_list : ()); # even, unlimited } # *--- b^2-1 -- b^2 ---- b^2+b-1 = (b+1)b-1 # | | # *------- # | # 0 ----- b # sub _UNDOCUMENTED__dxdy_list_at_n { my ($self) = @_; return ($self->{'radix'} + 1) * $self->{'radix'} - 1; } # shared by WunderlichSerpentine *dy_minimum = \&dx_minimum; *dy_maximum = \&dx_maximum; *dsumxy_minimum = \&dx_minimum; *dsumxy_maximum = \&dx_maximum; *ddiffxy_minimum = \&dx_minimum; *ddiffxy_maximum = \&dx_maximum; sub dir_maximum_dxdy { my ($self) = @_; return ($self->{'radix'} % 2 ? (0,-1) # odd, South : (0,0)); # even, supremum } sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; return $self->{'radix'} - 1; } sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; return ($self->{'radix'} == 2 ? 5 : 2*$self->{'radix'} - 1); } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); if (! $self->{'radix'} || $self->{'radix'} < 2) { $self->{'radix'} = 3; } return $self; } sub _n_to_xykk { my ($self, $n) = @_; my $radix = $self->{'radix'}; my $radix_minus_1 = $radix - 1; my @ndigits = digit_split_lowtohigh($n,$radix); if (scalar(@ndigits) & 1) { push @ndigits, 0; # so even number of entries } ### @ndigits my $xk = 0; my $yk = 0; my @ydigits; my @xdigits; for (my $i = $#ndigits >> 1; @ndigits; $i--) { # high to low ### $i { my $ndigit = pop @ndigits; # high to low $xk ^= $ndigit; $ydigits[$i] = ($yk & 1 ? $radix_minus_1-$ndigit : $ndigit); } { my $ndigit = pop @ndigits; $yk ^= $ndigit; $xdigits[$i] = ($xk & 1 ? $radix_minus_1-$ndigit : $ndigit); } } my $zero = $n*0; return ((map {digit_join_lowtohigh($_, $radix, $zero)} \@xdigits, \@ydigits), $xk,$yk); } sub n_to_xy { my ($self, $n) = @_; ### PeanoCurve n_to_xy(): $n if ($n < 0) { # negative return; } if (is_infinite($n)) { return ($n,$n); } { # ENHANCE-ME: for odd radix the ends join and the direction can be had # without a full N+1 calculation my $int = int($n); ### $int ### $n if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my ($x,$y) = _n_to_xykk($self,$n); return ($x,$y); } sub _xykk_to_n { my ($self, $x,$y, $offset_xk,$offset_yk) = @_; ### PeanoCurve _xykk_to_n(): "$x, $y offset $offset_xk,$offset_yk" if (($offset_xk && ($x-=$offset_xk) < 0) || ($offset_yk && ($y-=$offset_yk) < 0)) { return; # offset goes negative } my $radix = $self->{'radix'}; my @x = digit_split_lowtohigh ($x, $radix); my @y = digit_split_lowtohigh ($y, $radix); my $radix_minus_1 = $radix - 1; my $xk = 0; my $yk = 0; my @n; # stored low to high, generated from high to low my $i_high = max($#x,$#y); my $npos = 2*$i_high+1; foreach my $i (reverse 0 .. $i_high) { # high to low { my $digit = $y[$i] || 0; if ($yk & 1) { $digit = $radix_minus_1 - $digit; # reverse digit } $n[$npos--] = $digit; $xk ^= $digit; } { my $digit = $x[$i] || 0; if ($xk & 1) { $digit = $radix_minus_1 - $digit; # reverse digit } $n[$npos--] = $digit; $yk ^= $digit; } } ### final n: @n ### final xkyk: ($xk&1).' '.($yk&1) return ((! defined $offset_xk || ($xk&1) == $offset_xk) && (! defined $offset_yk || ($yk&1) == $offset_yk) ? (digit_join_lowtohigh (\@n, $radix, $x*0*$y)) # inherit bignum 0 : ()); } sub xy_to_n { my ($self, $x, $y) = @_; ### PeanoCurve xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } return _xykk_to_n($self, $x,$y); } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### rect_to_n_range(): "$x1,$y1 to $x2,$y2" if ($x2 < 0 || $y2 < 0) { return (1, 0); } my $radix = $self->{'radix'}; my ($power, $level) = round_down_pow (max($x2,$y2), $radix); if (is_infinite($level)) { return (0, $level); } my $n_power = $power * $power * $radix; my $max_x = 0; my $max_y = 0; my $max_n = 0; my $max_xk = 0; my $max_yk = 0; my $min_x = 0; my $min_y = 0; my $min_n = 0; my $min_xk = 0; my $min_yk = 0; # l<=cc2 or h-1c2 or h<=c1 # so does overlap if # l<=c2 and h>c1 # my $radix_minus_1 = $radix - 1; my $overlap = sub { my ($c,$ck,$digit, $c1,$c2) = @_; if ($ck & 1) { $digit = $radix_minus_1 - $digit; } ### overlap consider: "inv".($ck&1)."digit=$digit ".($c+$digit*$power)."<=c<".($c+($digit+1)*$power)." cf $c1 to $c2 incl" return ($c + $digit*$power <= $c2 && $c + ($digit+1)*$power > $c1); }; while ($level-- >= 0) { ### $power ### $n_power ### $max_n ### $min_n { my $digit; for ($digit = $radix_minus_1; $digit > 0; $digit--) { last if &$overlap ($max_y,$max_yk,$digit, $y1,$y2); } $max_n += $n_power * $digit; $max_xk ^= $digit; if ($max_yk&1) { $digit = $radix_minus_1 - $digit; } $max_y += $power * $digit; ### max y digit (complemented): $digit ### $max_y ### $max_n } { my $digit; for ($digit = 0; $digit < $radix_minus_1; $digit++) { last if &$overlap ($min_y,$min_yk,$digit, $y1,$y2); } $min_n += $n_power * $digit; $min_xk ^= $digit; if ($min_yk&1) { $digit = $radix_minus_1 - $digit; } $min_y += $power * $digit; ### min y digit (complemented): $digit ### $min_y ### $min_n } $n_power = int($n_power/$radix); { my $digit; for ($digit = $radix_minus_1; $digit > 0; $digit--) { last if &$overlap ($max_x,$max_xk,$digit, $x1,$x2); } $max_n += $n_power * $digit; $max_yk ^= $digit; if ($max_xk&1) { $digit = $radix_minus_1 - $digit; } $max_x += $power * $digit; ### max x digit (complemented): $digit ### $max_x ### $max_n } { my $digit; for ($digit = 0; $digit < $radix_minus_1; $digit++) { last if &$overlap ($min_x,$min_xk,$digit, $x1,$x2); } $min_n += $n_power * $digit; $min_yk ^= $digit; if ($min_xk&1) { $digit = $radix_minus_1 - $digit; } $min_x += $power * $digit; ### min x digit (complemented): $digit ### $min_x ### $min_n } $power = int($power/$radix); $n_power = int($n_power/$radix); } ### is: "$min_n at $min_x,$min_y to $max_n at $max_x,$max_y" return ($min_n, $max_n); } #------------------------------------------------------------------------------ # levels use Math::PlanePath::ZOrderCurve; *level_to_n_range = \&Math::PlanePath::ZOrderCurve::level_to_n_range; *n_to_level = \&Math::PlanePath::ZOrderCurve::n_to_level; #------------------------------------------------------------------------------ 1; __END__ # +--+ # | | # +--+--+--+ # | | # +--+ # # + # | # +--+--+ # | | | # +--+--+--+--+ # | | | | | # +--+--+--+--+--+--+ # | | | | | # +--+--+--+--+ # | | | # +--+--+ # | # + #----- =for stopwords Giuseppe Peano Peano's there'll eg Sur Une Courbe Qui Remplit Toute Aire Mathematische Annalen Ryde OEIS trit-twiddling ie bignums prepending trit Math-PlanePath versa Online Radix radix Georg representable Mephisto DOI bitwise PeanoDiagonals =head1 NAME Math::PlanePath::PeanoCurve -- 3x3 self-similar quadrant traversal =head1 SYNOPSIS use Math::PlanePath::PeanoCurve; my $path = Math::PlanePath::PeanoCurve->new; my ($x, $y) = $path->n_to_xy (123); # or another radix digits ... my $path5 = Math::PlanePath::PeanoCurve->new (radix => 5); =head1 DESCRIPTION This path is an integer version of the curve described by Peano for filling a unit square, =over Giuseppe Peano, "Sur Une Courbe, Qui Remplit Toute Une Aire Plane", Mathematische Annalen, volume 36, number 1, 1890, pages 157-160. DOI 10.1007/BF01199438. L, L =back It traverses a quadrant of the plane one step at a time in a self-similar 3x3 pattern, 8 60--61--62--63--64--65 78--79--80--... | | | 7 59--58--57 68--67--66 77--76--75 | | | 6 54--55--56 69--70--71--72--73--74 | 5 53--52--51 38--37--36--35--34--33 | | | 4 48--49--50 39--40--41 30--31--32 | | | 3 47--46--45--44--43--42 29--28--27 | 2 6---7---8---9--10--11 24--25--26 | | | 1 5---4---3 14--13--12 23--22--21 | | | Y=0 0---1---2 15--16--17--18--19--20 X=0 1 2 3 4 5 6 7 8 9 ... The start is an S shape of the nine points N=0 to N=8, and then nine of those groups are put together in the same S configuration. The sub-parts are flipped horizontally and/or vertically to make the starts and ends adjacent, so 8 is next to 9, 17 next to 18, etc, 60,61,62 --- 63,64,65 78,79,80 59,58,57 68,67,55 77,76,75 54,55,56 69,70,71 --- 72,73,74 | | 53,52,51 38,37,36 --- 35,34,33 48,49,50 39,40,41 30,31,32 47,46,45 --- 44,43,42 29,28,27 | | 6,7,8 ---- 9,10,11 24,25,26 3,4,5 12,13,14 23,22,21 0,1,2 15,16,17 --- 18,19,20 The process repeats, tripling in size each time. Within a power-of-3 square, 3x3, 9x9, 27x27, 81x81 etc (3^k)x(3^k) at the origin, all the N values 0 to 3^(2*k)-1 are within the square. The top right corner 8, 80, 728, etc is the 3^(2*k)-1 maximum in each. Because each step is by 1, the distance along the curve between two X,Y points is the difference in their N values as given by C. =head2 Radix The C parameter can do the calculation in a base other than 3, using the same kind of direction reversals. For example radix 5 gives 5x5 groups, =cut # math-image --path=PeanoCurve,radix=5 --expression='i<=50?i:0' --output=numbers_dash =pod radix => 5 4 | 20--21--22--23--24--25--26--27--28--29 | | | 3 | 19--18--17--16--15 34--33--32--31--30 | | | 2 | 10--11--12--13--14 35--36--37--38--39 | | | 1 | 9-- 8-- 7-- 6-- 5 44--43--42--41--40 | | | Y=0 | 0-- 1-- 2-- 3-- 4 45--46--47--48--49--50-... | +---------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 10 If the radix is even then the ends of each group don't join up. For example in radix 4 N=15 isn't next to N=16, nor N=31 to N=32, etc. =cut # math-image --path=PeanoCurve,radix=4 --expression='i<=33?i:0' --output=numbers_dash =pod radix => 4 3 | 15--14--13--12 16--17--18--19 | | | 2 | 8-- 9--10--11 23--22--21--20 | | | 1 | 7-- 6-- 5-- 4 24--25--26--27 | | | Y=0 | 0-- 1-- 2-- 3 31--30--29--28 32--33-... | +------------------------------------------ X=0 1 2 4 5 6 7 8 9 10 Even sizes can be made to join using other patterns, but this module is just Peano's digit construction. For joining up in 2x2 groupings see C (which is essentially the only way to join up in 2x2). For bigger groupings there's various ways. =head2 Unit Square Peano's original form was for filling a unit square by mapping a number T in the range 0ETE1 to a pair of X,Y coordinates 0EXE1 and 0EYE1. The curve is continuous and every such X,Y is reached by some T, so it fills the unit square. A unit cube or higher dimension can be filled similarly by developing three or more coordinates X,Y,Z, etc. Cantor had shown a line is equivalent to the plane, Peano's mapping is a continuous way to do that. The code here could be pressed into service for a fractional T to X,Y by multiplying up by a power of 9 to desired precision then dividing X and Y back by the same power of 3 (perhaps swapping X,Y for which one should be the first ternary digit). Note that if T is a binary floating point then a power of 3 division will round off in general since 1/3 is not exactly representable. (See C or C for binary mappings.) Sometimes the curve is drawn with line segments crossing unit squares. See PeanoDiagonals for that sort of path. =head2 Power of 3 Patterns Plotting sequences of values with some connection to ternary digits or powers of 3 will usually give the most interesting patterns on the Peano curve. For example the Mephisto waltz sequence (L) makes diamond shapes, ** * *** * * *** ** *** ** *** ** ** * * * * ** ** *** ** *** * * ** ** *** ** *** *** ** *** ** ** * *** * *** * * *** ** ** *** * * *** * ** ** *** * * *** * ** *** ** *** ** ** * *** * *** * * *** ** * * ** ** *** ** *** * * ** ** *** ** *** *** ** *** ** ** * *** * *** * * *** ** ** *** * * *** * ** ** *** * * *** * ** ** * *** * * *** ** *** ** *** ** ** * * * * ** ** *** ** *** * * ** ** *** ** *** ** * *** * * *** ** *** ** *** ** ** * * ** *** * * *** * ** ** *** * * *** * ** *** ** *** ** ** * *** * *** * * *** ** ** *** * * *** * ** ** *** * * *** * ** ** * *** * * *** ** *** ** *** ** ** * * ** *** * * *** * ** ** *** * * *** * ** *** ** *** ** ** * *** * *** * * *** ** * * ** ** *** ** *** * * ** ** *** ** *** *** ** *** ** ** * *** * *** * * *** ** ** *** * * *** * ** ** *** * * *** * ** ** * *** * * *** ** *** ** *** ** ** * * * * ** ** *** ** *** * * ** ** *** ** *** ** * *** * * *** ** *** ** *** ** ** * * ** *** * * *** * ** ** *** * * *** * ** ** * *** * * *** ** *** ** *** ** ** * * * * ** ** *** ** *** * * ** ** *** ** *** *** ** *** ** ** * *** * *** * * *** ** This arises from each 3x3 block in the Mephisto waltz being one of two shapes which are then flipped by the Peano pattern * * _ _ _ * * _ _ or _ * * (inverse) _ _ * * * _ 0,0,1, 0,0,1, 1,1,0 1,1,0, 1,1,0, 0,0,1 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::PeanoCurve-Enew ()> =item C<$path = Math::PlanePath::PeanoCurve-Enew (radix =E $integer)> Create and return a new path object. The optional C parameter gives the base for digit splitting. The default is ternary C 3>. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional C<$n> give an X,Y position along a straight line between the integer positions. Integer positions are always just 1 apart either horizontally or vertically, so the effect is that the fraction part appears either added to or subtracted from X or Y. =item C<$n = $path-Exy_to_n ($x,$y)> Return the integer point number for coordinates C<$x,$y>. Each integer N is considered the centre of a unit square and an C<$x,$y> within that square returns N. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> Return the range of N values which occur the a rectangle with corners at C<$x1>,C<$y1> and C<$x2>,C<$y2>. If the X,Y values are not integers then the curve is treated as unit squares centred on each integer point and squares which are partly covered by the given rectangle are included. The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, $radix**(2*$level) - 1)>. =back =head1 FORMULAS =head2 N to X,Y Peano's calculation is based on putting base-3 digits of N alternately to X or Y. From the high end of N, a digit goes to Y then the next goes to X. Beginning at an even digit position in N makes the last digit go to X so the first N=0,1,2 is along the X axis. At each stage a "complement" state is maintained for X and for Y. When complemented, the digit is reversed to S<2 - digit>, so 0,1,2 becomes 2,1,0. This reverses the direction so points like N=12,13,14 shown above go leftwards, or groups like 9,10,11 then 12,13,14 then 15,16,17 go downwards. The complement is calculated by adding the digits from N which went to the other one of X or Y. So the X complement is the sum of digits which have gone to Y so far. Conversely the Y complement is the sum of digits put to X. If the complement sum is odd then the reversal is done. A bitwise XOR can be used instead of a sum to accumulate odd/even-ness the same way as a sum. When forming the complement state, the original digits from N are added, before applying any complementing for putting them to X or Y. If the radix is odd, like the default 3, then complementing doesn't change it mod 2 so before or after are the same, but if the radix is even then it's not the same. It also works to take the base-3 digits of N from low to high, generating low to high digits in X and Y. If an odd digit is put to X then the low digits of Y so far must be complemented as S<22..22 - Y> (the 22..22 value being all 2s in base 3, ie. 3^k-1). Conversely if an odd digit is put to Y then X must be complemented. With this approach, the high digit position in N doesn't have to be found, just peel off digits of N from the low end. But the subtract to complement is then more work if using bignums. =head2 X,Y to N The X,Y to N calculation can be done by an inverse of either the high to low or low to high methods above. In both cases digits are put alternately from X and Y into N, with complement as necessary. For the low to high approach, it's not easy to complement just the X digits in the N constructed so far, but it works to build and complement the X and Y digits separately then at the end interleave to make the final N. Complementing is the ternary equivalent of an XOR in binary. On a ternary machine maybe some trit-twiddling would do it. For low to high with even radix, the complementing is also tricky since changing the accumulated X affects the digits of Y below that, and vice versa. What's the rule? Is it alternate digits which end up complemented? In any case the current C code goes high to low which is easier, but means breaking the X,Y inputs into arrays of digits before beginning. =pod =head2 N on Axes N on the X axis is all Y digits 0 in the X,Y to N described above. This means N is the digits of X, and then digit 0 or 2 at each Y position according to odd or even sum of X digits above. The Y digits are at odd positions so the 0 or 2 ternary is 0 or 6 for N in base-9. N on X axis = 0,1,2, 15,16,17, 18,19,20, 141, ... (A163480) ternary 0,1,2, 120,121,122, 200,201,202, 12020 =cut # GP-DEFINE to_ternary(n) = fromdigits(digits(n,3)); # GP-DEFINE to_base9(n) = fromdigits(digits(n,9)); # # GP-DEFINE \\ similar in PeanoCurve-oeis.t # GP-DEFINE N_on_X_axis(x) = { # GP-DEFINE my(v=digits(x,3),s=Mod(0,2)); # GP-DEFINE for(i=1,#v, if(s,v[i]+=6); s+=v[i]); # GP-DEFINE fromdigits(v,9); # GP-DEFINE } # GP-Test vector(10,x,x--; N_on_X_axis(x)) == \ # GP-Test [0,1,2, 15,16,17, 18,19,20, 141] # GP-Test vector(10,x,x--; to_ternary(N_on_X_axis(x))) == \ # GP-Test [0,1,2, 120,121,122, 200,201,202, 12020] # not in OEIS: 1,2, 120,121,122, 200,201,202, 12020 \\ X in base 3 # vector(10,x, to_base9(N_on_X_axis(x))) # not in OEIS: 1, 2, 16, 17, 18, 20, 21, 22, 166, 167 \\ N in base 9 # # my(v=OEIS_samples("A163480")); vector(#v,x,x--; N_on_X_axis(x)) == v # GP-Test vector(3^6,x,x--; N_on_X_axis(3*x)) == \ # GP-Test vector(3^6,x,x--; 9*N_on_X_axis(x) + if(x%2,6)) # GP-Test matrix(3^6,3,x,r,x--;r--; N_on_X_axis(3*x+r)) == \ # GP-Test matrix(3^6,3,x,r,x--;r--; 9*N_on_X_axis(x) + r + if(x%2==1,6)) =pod The Y axis is similar but the X digits are at even positions. N on Y axis = 0,5,6, 47,48,53, 54,59,60, 425, ... (A163481) ternary 0,12,20, 1202,1210,1222, 2000,2012,2020, 120202 =cut # GP-DEFINE \\ similar in PeanoCurve-oeis.t # GP-DEFINE N_on_Y_axis(y) = { # GP-DEFINE my(v=digits(y,3),s=Mod(0,2)); # GP-DEFINE for(i=1,#v, s+=v[i]; v[i] = 3*v[i]+if(s,2)); # GP-DEFINE fromdigits(v,9); # GP-DEFINE } # GP-Test vector(10,y,y--; N_on_Y_axis(y)) == \ # GP-Test [0,5,6, 47,48,53, 54,59,60, 425] # GP-Test vector(10,y,y--; to_ternary(N_on_Y_axis(y))) == \ # GP-Test [0,12,20, 1202,1210,1222, 2000,2012,2020, 120202] # not in OEIS: 12,20, 1202,1210,1222, 2000,2012,2020, 120202 # vector(10,x, to_base9(N_on_Y_axis(x))) # not in OEIS: 5, 6, 52, 53, 58, 60, 65, 66, 522, 523 # my(v=OEIS_samples("A163481")); vector(#v,y,y--; N_on_Y_axis(y)) == v =pod N on the X=Y diagonal has the ternary digits of position d go to both X and Y and so both complemented according to sum of digits of d above. That transformation within d is the ternary reflected Gray code. Gray3(d) = ternary flip 0<->2 when sum of digits above is odd = 0,1,2, 5,4,3, 6,7,8, 17, ... (A128173) ternary 0,1,2, 12,11,10, 20,21,22, 122, ... N on X=Y diag = ternary Gray3(d) and 0,1,2 -> 0,4,8 base9, which is 4*digit = 0,4,8, 44,40,36, 72,76,80, 404, ... (A163343) ternary 0,11,22, 1122,1111,1100, 2200,2211,2222, 112222, =cut # GP-DEFINE Gray3(d) = { # GP-DEFINE my(v=digits(d,3),s=Mod(0,2)); # GP-DEFINE for(i=1,#v, if(s,v[i]=2-v[i]); s+=v[i]); # GP-DEFINE fromdigits(v,3); # GP-DEFINE } # GP-Test vector(10,d,d--; Gray3(d)) == \ # GP-Test [0,1,2, 5,4,3, 6,7,8, 17] # GP-Test vector(10,d,d--; to_ternary(Gray3(d))) == \ # GP-Test [0,1,2, 12,11,10, 20,21,22, 122] # not in OEIS: 1,2, 12,11,10, 20,21,22, 122 # # GP-DEFINE N_on_XY_diagonal(d) = { # GP-DEFINE my(v=digits(Gray3(d),3)); # GP-DEFINE v*=4; # GP-DEFINE fromdigits(v,9); # GP-DEFINE } # GP-Test vector(10,d,d--; N_on_XY_diagonal(d)) == \ # GP-Test [0,4,8, 44,40,36, 72,76,80, 404] # GP-Test vector(10,d,d--; to_ternary(N_on_XY_diagonal(d))) == \ # GP-Test [0,11,22, 1122,1111,1100, 2200,2211,2222, 112222] # not in OEIS: 12,20, 1202,1210,1222, 2000,2012,2020, 120202 # vector(10,d, to_base9(N_on_XY_diagonal(d))) \\ is 4*to_ternary(Gray3) # not in OEIS: 4, 8, 48, 44, 40, 80, 84, 88, 488, 484 =pod =head2 N to abs(dX),abs(dY) The curve goes horizontally or vertically according to the number of trailing "2" digits when N is written in ternary, N trailing 2s direction abs(dX) abs(dY) ------------- --------- ------- ------- even horizontal 1 0 odd vertical 0 1 abs(dX) = 1,1,0, 1,1,0, 1,1,1, 1,1,0, 1,1,0, 1,1,1, ... (A014578) abs(dY) = 0,0,1, 0,0,1, 0,0,0, 0,0,1, 0,0,1, 0,0,0, ... (A182581) For example N=5 is "12" in ternary has 1 trailing "2" which is odd so the step from N=5 to N=6 is vertical. This works because when stepping from N to N+1 a carry propagates through the trailing 2s to increment the digit above. Digits go alternately to X or Y so odd or even trailing 2s put that carry into an X digit or Y digit. X Y X Y X N ... 2 2 2 2 N+1 1 0 0 0 0 carry propagates =cut # GP-DEFINE count_low_2s(n) = valuation(n+1,3); # GP-DEFINE N_is_horizontal(N) = count_low_2s(N) % 2 == 0; # GP-DEFINE N_is_vertical(N) = count_low_2s(N) % 2 == 1; # GP-Test vector(18,N,N--; N_is_horizontal(N)) == \ # GP-Test [1,1,0, 1,1,0, 1,1,1, 1,1,0, 1,1,0, 1,1,1] # GP-Test vector(18,N,N--; N_is_vertical(N)) == \ # GP-Test [0,0,1, 0,0,1, 0,0,0, 0,0,1, 0,0,1, 0,0,0] # vector(100,N, N_is_vertical(N)) =pod =head2 Rectangle to N Range An easy over-estimate of the maximum N in a region can be had by going to the next bigger (3^k)x(3^k) square enclosing the region. This means the biggest X or Y rounded up to the next power of 3 (perhaps using C if you trust its accuracy), so find k with 3^k > max(X,Y) N_hi = 3^(2k) - 1 An exact N range can be found by following the "high to low" N to X,Y procedure above. Start with the easy over-estimate to find a 3^(2k) ternary digit position in N bigger than the desired region, then choose a digit 0,1,2 for X, the biggest which overlaps some of the region. Or if there's an X complement then the smallest digit is the biggest N, again whichever overlaps the region. Then likewise for a digit of Y, etc. Biggest and smallest N must maintain separate complement states as they track down different N digits. A single loop can be used since there's the same "2k" many digits of N to consider for both. The N range of any shape can be done this way, not just a rectangle like C. The procedure only depends on asking whether a one-third sub-part of X or Y overlaps the target region or not. =head1 OEIS This path is in Sloane's Online Encyclopedia of Integer Sequences in several forms, =over L (etc) =back A163528 X coordinate A163529 Y coordinate A163530 X+Y coordinate sum A163531 X^2+Y^2 square of distance from origin A163532 dX, change in X -1,0,1 A163533 dY, change in Y -1,0,1 A014578 abs(dX) from n-1 to n, 1=horiz 0=vertical A182581 abs(dY) from n-1 to n, 0=horiz 1=vertical A163534 direction mod 4 of each step (ENWS) A163535 direction mod 4, transposed X,Y A163536 turn 0=straight,1=right,2=left A163537 turn, transposed X,Y A163342 diagonal sums A163479 diagonal sums divided by 6 A163480 N on X axis A163481 N on Y axis A163343 N on X=Y diagonal, 0,4,8,44,40,36,etc A163344 N on X=Y diagonal divided by 4 A007417 N+1 of positions of horizontals, ternary even trailing 0s A145204 N+1 of positions of verticals, ternary odd trailing 0s A163332 Peano N <-> ZOrder radix=3 N mapping (self-inverse) A163333 with ternary digit swaps before and after And taking X,Y points by the Diagonals sequence, then the value of the following sequences is the N of the Peano curve at those positions. A163334 numbering by diagonals, from same axis as first step A163336 numbering by diagonals, from opposite axis A163338 A163334 + 1, Peano starting from N=1 A163340 A163336 + 1, Peano starting from N=1 C numbers points from the Y axis down, which is the opposite axis to the Peano curve first step along the X axis, so a plain C -> C is the "opposite axis" form A163336. These sequences are permutations of the integers since all X,Y positions of the first quadrant are reached eventually. The inverses are as follows. They can be thought of taking X,Y positions in the Peano curve order and then asking what N the Diagonals would put there. A163335 inverse of A163334 A163337 inverse of A163336 A163339 inverse of A163338 A163341 inverse of A163340 =head1 SEE ALSO L, L, L, L, L, L, L, L, L L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/QuintetReplicate.pm0000644000175000017500000006255413777201747021074 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=QuintetReplicate --lines --scale=10 # math-image --path=QuintetReplicate --output=numbers --all # math-image --path=QuintetReplicate,numbering_type=rotate --output=numbers --all # math-image --path=QuintetReplicate --expression='5**i' package Math::PlanePath::QuintetReplicate; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_up_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ { name => 'numbering_type', display => 'Numbering', share_key => 'numbering_type_rotate', type => 'enum', default => 'fixed', choices => ['fixed','rotate'], choices_display => ['Fixed','Rotate'], description => 'Fixed or rotating sub-part numbering.', }, ]; use constant n_start => 0; use constant xy_is_visited => 1; use constant x_negative_at_n => 3; use constant y_negative_at_n => 4; #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); $self->{'numbering_type'} ||= 'fixed'; # default return $self; } # 10 7 # 2 8 5 6 # 3 0 1 9 # 4 # my @digit_to_xbx = (0,1,0,-1,0); # my @digit_to_xby = (0,0,-1,0,1); # my @digit_to_y = (0,0,1,0,-1); # my @digit_to_yby = (0,0,1,0,-1); # $x += $bx * $digit_to_xbx[$digit] + $by * $digit_to_xby[$digit]; # $y += $bx * $digit_to_ybx[$digit] + $by * $digit_to_yby[$digit]; sub _digits_rotate_lowtohigh { my ($aref) = @_; my $rot = 0; foreach my $digit (reverse @$aref) { # high to low if ($digit) { $rot += $digit-1; $digit = ($rot % 4) + 1; # mutate $aref } } } sub _digits_unrotate_lowtohigh { my ($aref) = @_; my $rot = 0; foreach my $digit (reverse @$aref) { # high to low if ($digit) { $digit = ($digit-1-$rot) % 4; # mutate $aref $rot += $digit; $digit++; } } } sub n_to_xy { my ($self, $n) = @_; ### QuintetReplicate n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } # any value in long frac lines like this? { my $int = int($n); if ($n != $int) { my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $frac = $n - $int; # inherit possible BigFloat my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $x = my $y = my $by = ($n * 0); # inherit bignum 0 my $bx = $x+1; # inherit bignum 1 my @digits = digit_split_lowtohigh($n,5); if ($self->{'numbering_type'} eq 'rotate') { _digits_rotate_lowtohigh(\@digits); } foreach my $digit (@digits) { ### $digit ### $bx ### $by if ($digit == 1) { $x += $bx; $y += $by; } elsif ($digit == 2) { $x -= $by; # i*(bx+i*by) = rotate +90 $y += $bx; } elsif ($digit == 3) { $x -= $bx; # -1*(bx+i*by) = rotate 180 $y -= $by; } elsif ($digit == 4) { $x += $by; # -i*(bx+i*by) = rotate -90 $y -= $bx; } # power (bx,by) = (bx + i*by)*(i+2) # ($bx,$by) = (2*$bx-$by, 2*$by+$bx); } return ($x, $y); } # digit modulus 2Y+X mod 5 # 2 2 # 3 0 1 1 0 4 # 4 3 # my @modulus_to_x = (0,-1,0,0,1); my @modulus_to_y = (0,0,1,-1,0); my @modulus_to_digit = (0,3,2,4,1); sub xy_to_n { my ($self, $x, $y) = @_; ### QuintetReplicate xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); foreach my $overflow (2*$x + 2*$y, 2*$x - 2*$y) { if (is_infinite($overflow)) { return $overflow; } } my $zero = ($x * 0 * $y); # inherit bignum 0 my @n; # digits low to high while ($x || $y) { ### at: "$x,$y" my $m = (2*$y - $x) % 5; ### $m ### digit: $modulus_to_digit[$m] push @n, $modulus_to_digit[$m]; $x -= $modulus_to_x[$m]; $y -= $modulus_to_y[$m]; ### modulus shift to: "$x,$y" # div i+2, # = (i*y + x) * (i-2)/-5 # = (-y -2*y*i + x*i -2*x) / -5 # = (y + 2*y*i - x*i + 2*x) / 5 # = (2x+y + (2*y-x)i) / 5 # # ### assert: ((2*$x + $y) % 5) == 0 # ### assert: ((2*$y - $x) % 5) == 0 ($x,$y) = ((2*$x + $y) / 5, (2*$y - $x) / 5); } if ($self->{'numbering_type'} eq 'rotate') { _digits_unrotate_lowtohigh(\@n); } return digit_join_lowtohigh (\@n, 5, $zero); } # level min x^2+y^2 for N >= 5^k # 0 1 at 1,0 # 1 2 at 1,1 factor 2 # 2 5 at 1,2 factor 2.5 # 3 16 at 0,4 factor 3.2 # 4 65 at -4,7 factor 4.0625 # 5 296 at -14,10 factor 4.55384615384615 # 6 1405 at -37,6 factor 4.74662162162162 # 7 6866 at -79,-25 factor 4.88683274021352 # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = abs($x1); $x2 = abs($x2); $y1 = abs($y1); $y2 = abs($y2); if ($x1 < $x2) { $x1 = $x2; } if ($y1 < $y2) { $y1 = $y2; } my $rsquared = $x1*$x1 + $y1*$y1; if (is_infinite($rsquared)) { return (0, $rsquared); } my $x = 1; my $y = 0; for (my $level = 1; ; $level++) { # (x+iy)*(2+i) ($x,$y) = (2*$x - $y, $x + 2*$y); if (abs($x) >= abs($y)) { $x -= ($x<=>0); } else { $y -= ($y<=>0); } unless ($x*$x + $y*$y <= $rsquared) { return (0, 5**$level - 1); } } } #------------------------------------------------------------------------------ # levels sub level_to_n_range { my ($self, $level) = @_; return (0, 5**$level - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($pow, $exp) = round_up_pow ($n+1, 5); return $exp; } #------------------------------------------------------------------------------ # Return true if $n is on the boundary of $level. # sub _UNDOCUMENTED__n_is_boundary_level { my ($self, $n, $level) = @_; ### _UNDOCUMENTED__n_is_boundary_level(): "n=$n" my @digits = digit_split_lowtohigh($n,5); ### @digits if ($self->{'numbering_type'} eq 'fixed') { _digits_unrotate_lowtohigh(\@digits); ### @digits } # no high 0 digit (and nothing too big) if (@digits != $level) { return 0; } # no 0 digit anywhere else if (grep {$_==0} @digits) { return 0; } # skip high digit and all 1 digits pop @digits; @digits = grep {$_ != 1} @digits; for (my $i = 0; $i < $#digits; $i++) { # low to high if (($digits[$i+1] == 3 && $digits[$i] <= 3) # 33, 32 || ($digits[$i+1] == 4 && $digits[$i] == 4)) { # 44 ### no, pair at: $i return 0; } } return 1; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Math-PlanePath TerdragonCurve GosperSide OEIS =head1 NAME Math::PlanePath::QuintetReplicate -- self-similar "+" tiling =head1 SYNOPSIS use Math::PlanePath::QuintetReplicate; my $path = Math::PlanePath::QuintetReplicate->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is a self-similar tiling of the plane with "+" shapes. It's the same kind of tiling as the C (and C), but with the middle square of the "+" shape centred on the origin. 12 3 13 10 11 7 2 14 2 8 5 6 1 17 3 0 1 9 <- Y=0 18 15 16 4 22 -1 19 23 20 21 -2 24 -3 ^ -4 -3 -2 -1 X=0 1 2 3 4 The base pattern is a "+" shape +---+ | 2 | +---+---+---+ | 3 | 0 | 1 | +---+---+---+ | 4 | +---+ which is then replicated +--+ | | +--+ +--+ +--+ | 10 | | | +--+ +--+--+ +--+ | | | 5 | +--+--+ +--+ +--+ | | 0 | | +--+ +--+ +--+--+ | 15 | | | +--+ +--+--+ +--+ | | | 20 | +--+ +--+ +--+ | | +--+ The effect is to tile the whole plane. Notice the centres 0,5,10,15,20 are the same "+" shape but positioned around at angle atan(1/2)=26.565 degrees. The relative positioning in each of those parts is the same, so at 5 the successive 6,7,8,9 are E,N,W,S like the base shape. =cut # not in OEIS: 26.565051177077989351 # not A242723 which starts 116.565 =pod =head2 Complex Base This tiling corresponds to expressing a complex integer X+i*Y as base b=2+i X+Yi = a[n]*b^n + ... + a[2]*b^2 + a[1]*b + a[0] where each digit position factor a[i] corresponds to N digits N digit a[i] ------- ------ 0 0 1 1 2 i 3 -1 4 -i =cut # GP-DEFINE b = 2+I; # GP-DEFINE QuintetDigit(d) = [0,1,I,-1,-I][d+1]; # GP-DEFINE QuintetPoint(n) = \ # GP-DEFINE subst(Pol(apply(QuintetDigit,digits(n,5))),'x,b); # GP-Test QuintetPoint(0) == 0 # GP-Test QuintetPoint(1) == 1 # GP-Test QuintetPoint(2) == I # GP-Test QuintetPoint(5) == 2+I =pod The base b is at an angle arg(b) = atan(1/2) = 26.56 degrees as seen at N=5 above. Successive powers b^2, b^3, b^4 etc at N=5^level rotate around by that much each time. Npow = 5^level at b^level angle(Npow) = level*26.56 degrees radius(Npow) = sqrt(5) ^ level =cut # GP-DEFINE nearly_equal_epsilon = 1e-15; # GP-DEFINE nearly_equal(x,y, epsilon=nearly_equal_epsilon) = \ # GP-DEFINE abs(x-y) < epsilon; # GP-DEFINE to_base5(n) = fromdigits(digits(n,5)); # GP-DEFINE from_base5(n) = fromdigits(digits(n),5); # GP-DEFINE b_angle = arg(b); # GP-DEFINE b_angle_degrees = b_angle * 180/Pi; # GP-Test nearly_equal( b_angle, atan(1/2) ) # GP-Test b_angle_degrees > 26.56 # GP-Test b_angle_degrees < 26.56+1/10^2 # cf # A242723 decimal degrees 180*(1 - arctan(2)/Pi) = 116.56... # GP-Test nearly_equal( 180*(1-atan(2)/Pi), b_angle_degrees+90 ) # A073000 atan(1/2) radians = 0.463... =pod The path can be reckoned bottom-up as a new low digit of N expanding each unit square to the base "+" shape. +---C D-------C | 2 | | | D---+---+---+ | | => | 3 | 0 | 1 | | | +---+---+---B A-------B | 4 | A---+ Side A-B becomes a 3-segment S. Such an expansion is the same as the TerdragonCurve or GosperSide, but here turns of 90 degrees. Like GosperSide there is no touching or overlap of the sides expansions, so boundary length 4*3^level. =head2 Rotate Numbering Parameter C 'rotate'> applies a rotation to the numbering in each sub-part according to its location around the preceding level. The effect can be illustrated by writing N in base-5. Part 10-14 is the same as the middle 0-4. Part 20-24 has a rotation by +90 degrees. Part 30-34 has rotation by +180 degrees, and part 40-44 by +270 degrees. 21 / | 22 20 24 12 numbering_type => 'rotate' \ / / \ N shown in base-5 23 2 13 10--11 / \ \ 34 3 0-- 1 14 \ \ 31--30 33 4 41 \ / / \ 32 43 40 42 | / 41 =cut # cf # math-image --path=QuintetReplicate,numbering_type=rotate --output=numbers --all =pod Notice this means in each part the 11, 21, 31, etc, points are directed away from the middle in the same way, relative to the sub-part locations. Working through the expansions gives the following rule for when an N is on the boundary of level k, write N in base-5 digits (empty string if k=0) if length < k then non-boundary ignore high digit and all 1 digits if any pair 32, 33, 44 then non-boundary A 0 digit is the middle of a block, so always non-boundary. After that the 4,1,2,3 parts variously expand with rotations so that a 44 is enclosed on the clockwise side and 32 and 33 on the anti-clockwise side. =cut =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::QuintetReplicate-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 5**$level - 1)>. =back =head1 FORMULAS =head2 Axis Rotations The digits positions 1,2,3,4 go around +90deg each, so the N for rotation by +90 is each digit +1, cycling around. rot+90(N) = 0, 2, 3, 4, 1, 10, 12, 13, 14, 11, 15, ... decimal = 0, 2, 3, 4, 1, 20, 22, 23, 24, 21, 30, ... base5 rot-90(N) = 0, 4, 1, 2, 3, 20, 24, 21, 22, 23, 5, ... decimal = 0, 4, 1, 2, 3, 40, 44, 41, 42, 43, 10, ... base5 rot180(N) = 0, 3, 4, 1, 2, 15, 18, 19, 16, 17, 20, ... decimal = 0, 3, 4, 1, 2, 30, 33, 34, 31, 32, 40, ... base5 =cut # GP-DEFINE digit_plus1(d) = [0,2,3,4,1][d+1]; # GP-DEFINE digit_plus2(d) = [0,3,4,1,2][d+1]; # GP-DEFINE digit_minus1(d) = [0,4,1,2,3][d+1]; # GP-DEFINE N_rotate_plus90(n) = fromdigits(apply(digit_plus1, digits(n,5)),5); # GP-DEFINE N_rotate_180(n) = fromdigits(apply(digit_plus2, digits(n,5)),5); # GP-DEFINE N_rotate_minus90(n)= fromdigits(apply(digit_minus1,digits(n,5)),5); # GP-Test my(v=[0,2,3,4,1,10,12,13,14,11,15,17]); /* samples shown */ \ # GP-Test vector(#v,n,n--; N_rotate_plus90(n)) == v # GP-Test my(v=[0,2,3,4,1,20,22,23,24,21,30,32]); /* samples shown */ \ # GP-Test vector(#v,n,n--; to_base5(N_rotate_plus90(n))) == v # GP-Test my(v=[0,4,1,2,3,20,24,21,22,23,5,9]); /* samples shown */ \ # GP-Test vector(#v,n,n--; N_rotate_minus90(n)) == v # GP-Test my(v=[0,4,1,2,3,40,44,41,42,43,10,14]); /* samples shown */ \ # GP-Test vector(#v,n,n--; to_base5(N_rotate_minus90(n))) == v # GP-Test my(v=[0,3,4,1,2,15,18,19,16,17,20,23]); /* samples shown */ \ # GP-Test vector(#v,n,n--; N_rotate_180(n)) == v # GP-Test my(v=[0,3,4,1,2,30,33,34,31,32,40,43]); /* samples shown */ \ # GP-Test vector(#v,n,n--; to_base5(N_rotate_180(n))) == v # GP-Test vector(500,n,n--; QuintetPoint(N_rotate_plus90(n))) == \ # GP-Test vector(500,n,n--; I*QuintetPoint(n)) # GP-Test vector(500,n,n--; QuintetPoint(N_rotate_minus90(n))) == \ # GP-Test vector(500,n,n--; -I*QuintetPoint(n)) # GP-Test vector(500,n,n--; QuintetPoint(N_rotate_180(n))) == \ # GP-Test vector(500,n,n--; -QuintetPoint(n)) # not in OEIS: 2, 3, 4, 1, 10, 12, 13, 14, 11, 15 \\ plus90 # not in OEIS: 2, 3, 4, 1, 20, 22, 23, 24, 21, 30 # not in OEIS: 4, 1, 2, 3, 20, 24, 21, 22, 23, 5 \\ minus90 # not in OEIS: 4, 1, 2, 3, 40, 44, 41, 42, 43, 10 # not in OEIS: 3, 4, 1, 2, 15, 18, 19, 16, 17, 20 \\ 180 # not in OEIS: 3, 4, 1, 2, 30, 33, 34, 31, 32, 40 =pod =head2 X,Y Extents The maximum X in a given level N=0 to 5^k-1 can be calculated from the replications. A given high digit 1 to 4 has sub-parts located at b^k*i^(d-1). Those sub-parts are all the same, so the one with maximum real(b^k*i^(d-1)) contains the maximum X. N_xmax_digit(j) = d=1,2,3,4 where real(i^(d-1) * b^j) is maximum = 1,1,4,4,4,4,3,3,3,2,2,2,1,1, ... k-1 N_xmax(k) = digits N_xmax_digit(j) low digit j=0 j=0 = 0, 1, 6, 106, 606, 3106, 15606, ... decimal = 0, 1, 11, 411, 4411, 44411, 444411, ... base5 k-1 z_xmax(k) = sum i^d[j] * b^j j=0 each d[j] with real(i^d[j] * b^j) maximum = 0, 1, 3+i, 7-2*i, 18-4*i, 42+3*i, 83+41*i, ... xmax(k) = real(z_xmax(k)) = 0, 1, 3, 7, 18, 42, 83, 200, 478, 1005, ... =cut # GP-DEFINE N_xmax_digit(j) = \ # GP-DEFINE my(p=b^j,d); vecmax(vector(4,d,real(I^(d-1)*p)),&d); d; # GP-Test my(v=[1,1,4,4,4,4,3,3,3,2,2,2,1,1]); /* samples shown */ \ # GP-Test vector(#v,j,j--; N_xmax_digit(j)) == v # GP-DEFINE N_xmax(k) = fromdigits(Vecrev(vector(k,j,j--; N_xmax_digit(j))),5); # GP-Test my(v=[0, 1, 6, 106, 606, 3106, 15606]); /* samples shown */ \ # GP-Test vector(#v,j,j--; N_xmax(j)) == v # GP-Test my(v=[0, 1, 11, 411, 4411, 44411, 444411]); /* samples shown */ \ # GP-Test vector(#v,j,j--; to_base5(N_xmax(j))) == v # GP-Test to_base5(N_xmax(51)) \ # GP-Test == 233334441111222333444411122233334441111222333444411 # GP-DEFINE z_xmax(k) = { # GP-DEFINE sum(j=0,k-1, # GP-DEFINE my(p=b^j, v=vector(4,d,(I^(d-1)*p)), i); # GP-DEFINE vecmax(real(v),&i); # GP-DEFINE v[i]); # GP-DEFINE } # GP-Test my(v=[0, 1, 3+I, 7-2*I, 18-4*I, 42+3*I, 83+41*I]); \ # GP-Test vector(#v,k,k--; z_xmax(k)) == v # GP-DEFINE xmax(k) = real(z_xmax(k)); # GP-Test my(v=[0, 1, 3, 7, 18, 42, 83, 200, 478, 1005]); \ # GP-Test vector(#v,k,k--; xmax(k)) == v # GP-Test xmax(51) == 478296859096758296 # vector(15,k,k--; N_xmax_digit(k)) # vector(10,k,k--; N_xmax(k)) # vector(10,k,k--; to_base5(N_xmax(k))) # not in OEIS: 6, 106, 606, 3106, 15606, 62481, 296856, 1468731, 5374981 # not in OEIS: 11, 411, 4411, 44411, 444411, 3444411, 33444411, 333444411 # vector(6,k,k--; z_xmax(k)) # vector(8,k, norm(z_xmax(k))) # vector(10,k, real(z_xmax(k))) # vector(10,k, imag(z_xmax(k))) # not in OEIS: 1, 10, 53, 340, 1773, 8570, 40009, 229160 \\ norm # not in OEIS: 1, 3, 7, 18, 42, 83, 200, 478, 1005, 2204 \\ real # not in OEIS: 0, 1, -2, -4, 3, 41, -3, 26, 362, -356 \\ imag =pod For computer calculation these maximums can be calculated by the powers. The digit parts can also be written in terms of the angle arg(b) = atan(1/2). For successive k, if adding atan(1/2) pushes the b^k angle past +45deg then the preceding digit goes past -45deg and becomes the new maximum X. Write the angle as a fraction of 90deg (pi/2), F = atan(1/2) / (pi/2) = 0.295167 ... =cut # GP-DEFINE quintet_F = atan(1/2) / (Pi/2); # GP-Test quintet_F > 0.295167 # GP-Test quintet_F < 0.295167 + 1/10^6 # not in OEIS: 0.295167235300866548350802 =pod This is irrational since b^k is never on the X or Y axes. That can be seen since imag(b^k) mod 5 == 1 if k odd and == 4 if k even >= 2. Similarly real(b^k) mod 5 == 2,3 so not on the Y axis, or also anything on the Y axis would have 3*k fall on the X axis. =cut # GP-Test vector(100,k,k--; imag(b^k)%5) == \ # GP-Test vector(100,k,k--; if(k==0,0, k%2==1,1,4)) # GP-Test vector(100,k,k--; real(b^k)%5) == \ # GP-Test vector(100,k,k--; if(k==0,1, k%2==1,2,3)) =pod Digits low to high successively step back in a cycle 4,3,2,1 so that (with mod giving 0 to 3), N_xmax_digit(j) = (-floor(F*j+1/2) mod 4) + 1 =cut # GP-DEFINE N_xmax_digit_by_floor(j) = (-floor(quintet_F*j+1/2) % 4) + 1; # GP-Test vector(1000,j,j--; N_xmax_digit_by_floor(j)) == \ # GP-Test vector(1000,j,j--; N_xmax_digit(j)) # vector(35,j,j+=5; floor(quintet_F*j+1/2)) # vector(25,j,j+=3; floor(quintet_F*j)) # not in OEIS: 2,2,2,3,3,3,4,4,4,4,5,5,5,6,6,6,6,7,7,7,8,8,8,9,9,9,9,10,10,10 # not in OEIS: 2,2,2,3,3,3,4,4,4,5,5,5,5,6,6,6,7,7,7,7,8 =pod The +1/2 is since initial direction b^0=1 is angle 0 which is half way between -45 and +45 deg. Similarly the X,Y location, using -i for rotation back z_xmax_exp(j) = floor(F*j+1/2) = 0,0,1,1,1,1,2,2,2,3,3,3,4,4,4,4,5,5, ... z_xmax(k) = sum(j=0,k-1, (-i)^z_xmax_exp(j) * b^j) =cut # GP-DEFINE z_xmax_exp(j) = floor(quintet_F*j+1/2); # GP-Test my(v=[0,0,1,1,1,1,2,2,2,3,3,3,4,4,4,4,5,5]); /* samples shown */ \ # GP-Test vector(#v,j,j--; z_xmax_exp(j)) == v # GP-DEFINE z_xmax_by_floor(k) = sum(j=0,k-1, (-I)^z_xmax_exp(j) * b^j); # GP-Test vector(500,j,j--; z_xmax_by_floor(j)) == \ # GP-Test vector(500,j,j--; z_xmax(j)) # # vector(20,k,k--; z_xmax_exp(k)) # not in OEIS: 0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6 =pod By symmetry the maximum extent is the same for Y vertically and for X or Y negative, suitably rotated. The N in those cases has the digits 1,2,3,4 cycled around as per L above. If the +1/2 in the floor is omitted then the effect is to find the maximum point in direction +45deg, so the point(s) with maximum sum S = X+Y. N_smax_digit(j) = (-floor(F*j) mod 4) + 1 = 1,1,1,1,4,4,4,3,3,3,3,2,2,2,1, ... k-1 N_smax(k) = digits N_smax_digit(j) low digit j=0 j=0 = 0, 1, 6, 31, 156, 2656, 15156, ... decimal = 0, 1, 11, 111, 1111, 41111, 441111, ... base5 and also N_smax() + 1 z_smax_exp(j) = floor(F*j) = 0,0,0,0,1,1,1,2,2,2,2,3,3,3,4,4,4,5,5,5, ... z_smax(k) = sum(j=0,k-1, (-i)^z_smax_exp(j) * b^j) = 0, 1, 3+i, 6+5*i, 8+16*i, 32+23*i, 73+61*i, ... and also z_smax() + 1+i smax(k) = real(z_smax(k)) + imag(z_smax(k)) = 0, 1, 4, 11, 24, 55, 134, 295, 602, 1465, ... In the base figure points 1 and 2 are both on the same 45deg line and this remains so in subsequent levels, so that for kE=1 N_smax(k) and N_smax(k)+1 are equal maximums. =cut # GP-DEFINE N_smax_digit(j) = (-floor(quintet_F*j) % 4) + 1; # GP-Test my(v=[1,1,1,1,4,4,4,3,3,3,3,2,2,2,1]); /* samples shown */ \ # GP-Test vector(#v,j,j--; N_smax_digit(j)) == v # GP-DEFINE N_smax(k) = fromdigits(Vecrev(vector(k,j,j--; N_smax_digit(j))),5); # GP-Test N_smax(0) == 0 # GP-Test N_smax(1) == 1 # GP-Test N_smax(10) == 7343281 # GP-Test to_base5(N_smax(10)) == 3334441111 # GP-Test my(v=[0, 1, 6, 31, 156, 2656, 15156]); /* samples shown */ \ # GP-Test vector(#v,j,j--; N_smax(j)) == v # GP-Test my(v=[0, 1, 11, 111, 1111, 41111, 441111]); /* samples shown */ \ # GP-Test vector(#v,j,j--; to_base5(N_smax(j))) == v # vector(25,k,k--; N_smax_digit(k)) # vector(8,k, N_smax(k)) # vector(8,k, to_base5(N_smax(k))) # not in OEIS: 1,1,1,1,4,4,4,3,3,3,3,2,2,2,1,1,1,4,4,4,4,3,3,3,2 \\ digits # not in OEIS: 1, 6, 31, 156, 2656, 15156, 77656, 312031 \\ decimal # not in OEIS: 1, 11, 111, 1111, 41111, 441111, 4441111, 34441111 \\ base5 # vector(8,k, N_smax(k)+1) # vector(8,k, to_base5(N_smax(k))+1) # not in OEIS: 2, 7, 32, 157, 2657, 15157, 77657, 312032 \\ decimal # not in OEIS: 2, 12, 112, 1112, 41112, 441112, 4441112, 34441112 \\ base5 # GP-DEFINE z_smax_exp(j) = floor(quintet_F*j); # GP-Test my(v=[0,0,0,0,1,1,1,2,2,2,2,3,3,3,4,4,4,5,5,5]); /*samples shown*/ \ # GP-Test vector(#v,j,j--; z_smax_exp(j)) == v # GP-DEFINE z_smax(k) = sum(j=0,k-1, (-I)^z_smax_exp(j) * b^j); # GP-Test my(v=[0,1,3+I,6+5*I,8+16*I,32+23*I,73+61*I]); /*samples shown*/ \ # GP-Test vector(#v,j,j--; z_smax(j)) == v # GP-DEFINE smax(k) = my(z=z_smax(k)); real(z)+imag(z); # GP-Test my(v=[0, 1, 4, 11, 24, 55, 134, 295, 602, 1465]); /*samples shown*/ \ # GP-Test vector(#v,j,j--; smax(j)) == v # vector(16,k,k++; z_smax(k)) # vector(20,k,k++; z_smax_exp(k)) # not in OEIS: 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6 # vector(8,k,k++; norm(z_smax(k))) # vector(10,k,k++; real(z_smax(k))) # vector(10,k,k++; imag(z_smax(k))) # not in OEIS: 10, 61, 320, 1553, 9050, 45373, 198874, 1144933 \\ norm # not in OEIS: 3, 6, 8, 32, 73, 117, 395, 922 \\ real # not in OEIS: 1, 5, 16, 23, 61, 178, 207, 543 \\ imag # vector(10,k,k++; smax(k)) # not in OEIS: 4, 11, 24, 55, 134, 295, 602, 1465 \\ real+imag =pod =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A316657 X coordinate A316658 Y coordinate A316707 norm X^2 + Y^2 =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/QuadricIslands.pm0000644000175000017500000003673213734026657020515 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=QuadricIslands --lines --scale=10 # math-image --path=QuadricIslands --all --output=numbers_dash --size=132x50 package Math::PlanePath::QuadricIslands; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest', 'floor'; use Math::PlanePath::Base::Digits 'round_down_pow'; use Math::PlanePath::QuadricCurve; # uncomment this to run the ### lines #use Smart::Comments; use constant n_frac_discontinuity => 0; use constant x_negative_at_n => 1; use constant y_negative_at_n => 1; use constant sumabsxy_minimum => 1; # minimum X=1/2,Y=1/2 use constant rsquared_minimum => 0.5; # minimum X=1/2,Y=1/2 use constant dx_maximum => 1; use constant dy_maximum => 1; use constant dsumxy_maximum => 1; use constant ddiffxy_minimum => -1; # dDiffXY=+1 or -1 use constant ddiffxy_maximum => 1; use constant dir_maximum_dxdy => (0,-1); # South # N=1,2,3,4 gcd(1/2,1/2) = 1/2 use constant gcdxy_minimum => 1/2; #------------------------------------------------------------------------------ # N=1 to 4 4 of, level=0 # N=5 to 36 12 of, level=1 # N=37 to .. 48 of, level=3 # # each loop = 4*8^level # # n_base = 1 + 4*8^0 + 4*8^1 + ... + 4*8^(level-1) # = 1 + 4*[ 8^0 + 8^1 + ... + 8^(level-1) ] # = 1 + 4*[ (8^level - 1)/7 ] # = 1 + 4*(8^level - 1)/7 # = (4*8^level - 4 + 7)/7 # = (4*8^level + 3)/7 # # n >= (4*8^level + 3)/7 # 7*n = 4*8^level + 3 # (7*n - 3)/4 = 8^level # # nbase(k+1)-nbase(k) # = (4*8^(k+1)+3 - (4*8^k+3)) / 7 # = (4*8*8^k - 4*8^k) / 7 # = (4*8-4) * 8^k / 7 # = 28 * 8^k / 7 # = 4 * 8^k # # nbase(0) = (4*8^0 + 3)/7 = (4+3)/7 = 1 # nbase(1) = (4*8^1 + 3)/7 = (4*8+3)/7 = (32+3)/7 = 35/7 = 5 # nbase(2) = (4*8^2 + 3)/7 = (4*64+3)/7 = (256+3)/7 = 259/7 = 37 # ### loop 1: 4* 8**1 ### loop 2: 4* 8**2 ### loop 3: 4* 8**3 # sub _level_to_base { # my ($level) = @_; # return (4*8**$level + 3) / 7; # } # ### level_to_base(1): _level_to_base(1) # ### level_to_base(2): _level_to_base(2) # ### level_to_base(3): _level_to_base(3) # base = (4 * 8**$level + 3)/7 # = (4 * 8**($level+1) / 8 + 3)/7 # = (8**($level+1) / 2 + 3)/7 sub _n_to_base_and_level { my ($n) = @_; my ($base,$level) = round_down_pow ((7*$n - 3)*2, 8); return (($base/2 + 3)/7, $level - 1); } sub n_to_xy { my ($self, $n) = @_; ### QuadricIslands n_to_xy(): "$n" if ($n < 1) { return; } if (is_infinite($n)) { return ($n,$n); } my ($base, $level) = _n_to_base_and_level($n); ### $level ### $base ### level: "$level" ### next base would be: (4 * 8**($level+1) + 3)/7 my $rem = $n - $base; ### $rem ### assert: $n >= $base ### assert: $n < 8**($level+1) ### assert: $rem>=0 ### assert: $rem < 4 * 8 ** $level my $sidelen = 8**$level; my $side = int($rem / $sidelen); ### $sidelen ### $side ### $rem $rem -= $side*$sidelen; ### assert: $side >= 0 && $side < 4 my ($x, $y) = Math::PlanePath::QuadricCurve::n_to_xy ($self, $rem); my $pos = 4**$level / 2; ### side calc: "$x,$y for pos $pos" ### $x ### $y if ($side < 1) { ### horizontal rightwards return ($x - $pos, $y - $pos); } elsif ($side < 2) { ### right vertical upwards return (-$y + $pos, # rotate +90, offset $x - $pos); } elsif ($side < 3) { ### horizontal leftwards return (-$x + $pos, # rotate 180, offset -$y + $pos); } else { ### left vertical downwards return ($y - $pos, # rotate -90, offset -$x + $pos); } } # +-------+-------+-------+ # |31 | 24 0,1| 23| # | | | | # | +-------+-------+ | # | |4 | |3 | | | # | | | | | | | # +---|--- ---|--- ---|---+ Y=0.5 # |32 | | | | | 16| # | | | | | | | # | +=======+=======+ | Y=0 # | |1 | |2 | | | # | | | | | | | # +---|--- ---|--- ---|---+ Y=-0.5 # | | | | | | | # | | | | | | | # | +-------+-------+ | Y=-1 # | | | | # |7 |8 | 15| # +-------+-------+-------+ # # -2 <= 2*x < 2, round to -2,-1,0,1 # then 4*yround -8,-4,0,4 # total -10 to 5 inclusive my @inner_n_list = ([1,7], [1,8], [2,8], [2,15], # Y=-1 [1,32], [1], [2], [2,16], # Y=-0.5 [4,32], [4], [3], [3,16], # Y=0 [4,31],[4,24],[3,24],[3,23]); # Y=0.5 sub xy_to_n { return scalar((shift->xy_to_n_list(@_))[0]); } sub xy_to_n_list { my ($self, $x, $y) = @_; ### QuadricIslands xy_to_n(): "$x, $y" if ($x >= -1 && $x < 1 && $y >= -1 && $y < 1) { ### round 2x: floor(2*$x) ### round 2y: floor(2*$y) ### index: floor(2*$x) + 4*floor(2*$y) + 10 ### assert: floor(2*$x) + 4*floor(2*$y) + 10 >= 0 ### assert: floor(2*$x) + 4*floor(2*$y) + 10 <= 15 return @{$inner_n_list[ floor(2*$x) + 4*floor(2*$y) + 10 ]}; } $x = round_nearest($x); $y = round_nearest($y); my $high; if ($x >= $y + ($y>0)) { # +($y>0) to exclude the downward bump of the top side ### below leading diagonal ... if ($x < -$y) { ### bottom quarter ... $high = 0; } else { ### right quarter ... $high = 1; ($x,$y) = ($y, -$x); # rotate -90 } } else { ### above leading diagonal if ($y > -$x) { ### top quarter ... $high = 2; $x = -$x; # rotate 180 $y = -$y; } else { ### right quarter ... $high = 3; ($x,$y) = (-$y, $x); # rotate +90 } } ### rotate to: "$x,$y high=$high" # ymax = (10*4^(l-1)-1)/3 # ymax < (10*4^(l-1)-1)/3+1 # (10*4^(l-1)-1)/3+1 > ymax # (10*4^(l-1)-1)/3 > ymax-1 # 10*4^(l-1)-1 > 3*(ymax-1) # 10*4^(l-1) > 3*(ymax-1)+1 # 10*4^(l-1) > 3*(ymax-1)+1 # 10*4^(l-1) > 3*ymax-3+1 # 10*4^(l-1) > 3*ymax-2 # 4^(l-1) > (3*ymax-2)/10 # # (2*4^(l-1) + 1)/3 = ymin # 2*4^(l-1) + 1 = 3*y # 2*4^(l-1) = 3*y-1 # 4^(l-1) = (3*y-1)/2 # # ypos = 4^l/2 = 2*4^(l-1) # z = -2*y+x # (2*4**($level-1) + 1)/3 = z # 2*4**($level-1) + 1 = 3*z # 2*4**($level-1) = 3*z - 1 # 4**($level-1) = (3*z - 1)/2 # = (3*(-2y+x)-1)/2 # = (-6y+3x - 1)/2 # = -3*y + (3x-1)/2 # 2*4**($level-1) = -2*y-x # 4**($level-1) = -y-x/2 # 4**$level = -4y-2x # # line slope y/x = 1/2 as an index my $z = -$y-$x/2; my ($len,$level) = round_down_pow ($z, 4); ### $z ### amin: 2*4**($level-1) ### $level ### $len if (is_infinite($level)) { return $level; } $len *= 2; $x += $len; $y += $len; ### shift to: "$x,$y" my $n = Math::PlanePath::QuadricCurve::xy_to_n($self, $x, $y); # Nmin = (4*8^l+3)/7 # Nmin+high = (4*8^l+3)/7 + h*8^l # = (4*8^l + 3 + 7h*8^l)/7 + # = ((4+7h)*8^l + 3)/7 # ### plain curve on: ($x+2*$len).",".($y+2*$len)." give n=".(defined $n && $n) ### $high ### high: (8**$level)*$high ### base: (4 * 8**($level+1) + 3)/7 ### base with high: ((4+7*$high) * 8**($level+1) + 3)/7 if (defined $n) { return ((4+7*$high) * 8**($level+1) + 3)/7 + $n; } else { return; } } # level width extends # side = 4^level # ypos = 4^l / 2 # width = 1 + 4 + ... + 4^(l-1) # = (4^l - 1)/3 # ymin = ypos(l) - 4^(l-1) - width(l-1) # = 4^l / 2 - 4^(l-1) - (4^(l-1) - 1)/3 # = 4^(l-1) * (2 - 1 - 1/3) + 1/3 # = (2*4^(l-1) + 1) / 3 # # (2*4^(l-1) + 1) / 3 = y # 2*4^(l-1) + 1 = 3*y # 2*4^(l-1) = 3*y-1 # 4^(l-1) = (3*y-1)/2 # # ENHANCE-ME: slope Y=X/2+1 or thereabouts for sides # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### QuadricIslands rect_to_n_range(): "$x1,$y1 $x2,$y2" # $x1 = round_nearest ($x1); # $y1 = round_nearest ($y1); # $x2 = round_nearest ($x2); # $y2 = round_nearest ($y2); my $m = max(abs($x1), abs($x2), abs($y1), abs($y2)); my ($len,$level) = round_down_pow (6*$m-2, 4); ### $len ### $level return (1, (32*8**$level - 4)/7); } # ymax = ypos(l) + 4^(l-1) + width(l-1) # = 4^l / 2 + 4^(l-1) + (4^(l-1) - 1)/3 # = 4^(l-1) * (4/2 + 1 + 1/3) - 1/3 # = 4^(l-1) * (2 + 1 + 1/3) - 1/3 # = 4^(l-1) * 10/3 - 1/3 # = (10*4^(l-1) - 1) / 3 # # (10*4^(l-1) - 1) / 3 = y # 10*4^(l-1) - 1 = 3*y # 10*4^(l-1) = 3*y+1 # 4^(l-1) = (3*y+1)/10 # # based on max ??? ... # # my ($power,$level) = round_down_pow ((3*$m+1-3)/10, 4); # ### $power # ### $level # return (1, # (4*8**($level+3) + 3)/7 - 1); #------------------------------------------------------------------------------ # Nstart(k) = (4*8^k + 3)/7 # Nend(k) = Nstart(k+1) - 1 # = (4*8*8^k + 3)/7 - 1 # = (4*8*8^k + 8*3 - 8*3 + 3)/7 - 1 # = (4*8*8^k + 8*3)/7 + (-8*3 + 3)/7 - 1 # = 8*Nstart(k) + (-8*3 + 3)/7 - 1 # = 8*Nstart(k) - 4 sub level_to_n_range { my ($self, $level) = @_; my $n_lo = (4 * 8**$level + 3)/7; return ($n_lo, 8*$n_lo - 4); } sub n_to_level { my ($self, $n) = @_; if ($n < 1) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($base,$level) = _n_to_base_and_level($n); return $level; } #------------------------------------------------------------------------------ 1; __END__ # 55--56 10--11 -3 # | | # ... 53--54 57 60--61 -4 # | | | | # 52--51 58--59 62--63 -5 # | | # 48--49--50 66--65--64 -6 # | | # 39--40 47--46 67--68 -7 # | | | | # 37--38 41 44--45 69 -8 # | | | # 42--43 70--71 -9 # | # 74--73--72 -10 # | # 75--76 79--80 ... -11 # | | | | # 77--78 81 84--85 -12 # | | # 82--83 -13 # # ^ # -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 =for stopwords eg Ryde ie Math-PlanePath quadric onwards zig zag =head1 NAME Math::PlanePath::QuadricIslands -- quadric curve rings =head1 SYNOPSIS use Math::PlanePath::QuadricIslands; my $path = Math::PlanePath::QuadricIslands->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path is concentric islands made from four sides each an eight segment zig-zag (per the C path). 27--26 3 | | 29--28 25 22--21 2 | | | | 30--31 24--23 20--19 1 | 4--3 | 34--33--32 | 16--17--18 <- Y=0 | 1--2 | 35--36 7---8 15--14 -1 | | | 5---6 9 12--13 -2 | | 10--11 -3 ^ -3 -2 -1 X=0 1 2 3 4 The initial figure is the square N=1,2,3,4 then for the next level each straight side expands to 4x longer and a zigzag like N=5 through N=13 and the further sides to N=36. The individual sides are levels of the C path. *---* | | *---* becomes *---* * *---* | | *---* * <------ * | ^ | | | | v | * ------> * The name C here is a slight mistake. Mandelbrot ("Fractal Geometry of Nature" 1982 page 50) calls any islands initiated from a square "quadric", not just this eight segment expansion. This curve also appears (unnamed) in Mandelbrot's "How Long is the Coast of Britain", 1967. =head2 Level Ranges Counting the innermost square as level 0, each ring is length = 4 * 8^level many points Nlevel = 1 + length[0] + ... + length[level-1] = (4*8^level + 3)/7 Xstart = - 4^level / 2 Ystart = - 4^level / 2 =for GP-DEFINE Nlevel(k) = (4*8^k + 3)/7 =for GP-Test (4*8^2+3)/7 == 37 For example the lower partial ring shown above is level 2 starting N=(4*8^2+3)/7=37 at X=-(4^2)/2=-8,Y=-8. The innermost square N=1,2,3,4 is on 0.5 coordinates, for example N=1 at X=-0.5,Y=-0.5. This is centred on the origin and consistent with the (4^level)/2. Points from N=5 onwards are integer X,Y. 4-------3 Y=+1/2 | | | o | | 1-------2 Y=-1/2 X=-1/2 X=+1/2 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::QuadricIslands-Enew ()> Create and return a new path object. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return per L
above, ( ( 4 * 8**$level + 3) / 7, (32 * 8**$level - 4) / 7 ) =for GP-DEFINE Nend(k) = (32*8^k - 4)/7 =for GP-Test Nlevel(0) == 1 =for GP-Test Nend(0) == 4 =for GP-Test Nlevel(1) == 5 =for GP-Test Nend(1) == 36 =for GP-Test Nlevel(2) == 37 =for GP-Test vector(20,k, Nlevel(k)) == vector(20,k, Nend(k-1)+1) =back =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/CCurve.pm0000644000175000017500000016676413734026674017006 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::CCurve; use 5.004; use strict; use List::Util 'min','max','sum'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_up_pow', 'round_down_pow', 'bit_split_lowtohigh', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; *_divrem = \&Math::PlanePath::_divrem; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; use Math::PlanePath::KochCurve; *_digit_join_hightolow = \&Math::PlanePath::KochCurve::_digit_join_hightolow; # uncomment this to run the ### lines # use Smart::Comments; # Not sure about this yet ... 2 or 4? With mirror images too 8 arms would # fill the plane everywhere 4-visited points double-traversed segments. # use constant parameter_info_array => [ { name => 'arms', # share_key => 'arms_2', # display => 'Arms', # type => 'integer', # minimum => 1, # maximum => 2, # default => 1, # width => 1, # description => 'Arms', # } ]; use constant n_start => 0; use constant x_negative_at_n => 6; use constant y_negative_at_n => 22; use constant 1.02 _UNDOCUMENTED__dxdy_list_at_n => 7; #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(2, $self->{'arms'} || 1)); return $self; } sub n_to_xy { my ($self, $n) = @_; ### CCurve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } my $zero = ($n * 0); # inherit bignum 0 my $x = $zero; my $y = $zero; { my $int = int($n); $x = $n - $int; # inherit possible BigFloat $n = $int; # BigFloat int() gives BigInt, use that } # initial rotation from arm number $n mod $arms my $rot = _divrem_mutate ($n, $self->{'arms'}); my $len = $zero+1; foreach my $digit (digit_split_lowtohigh($n,4)) { ### $digit if ($digit == 0) { ($x,$y) = ($y,-$x); # rotate -90 } elsif ($digit == 1) { $y -= $len; # at Y=-len } elsif ($digit == 2) { $x += $len; # at X=len,Y=-len $y -= $len; } else { ### assert: $digit == 3 ($x,$y) = (2*$len - $y, # at X=2len,Y=-len and rotate +90 $x-$len); } $rot++; # to keep initial direction $len *= 2; } if ($rot & 2) { $x = -$x; $y = -$y; } if ($rot & 1) { ($x,$y) = (-$y,$x); } ### final: "$x,$y" return ($x,$y); } # point N=2^(2k) at XorY=+/-2^k radius 2^k # N=2^(2k-1) at X=Y=+/-2^(k-1) radius sqrt(2)*2^(k-1) # radius = sqrt(2^level) # R(l)-R(l-1) = sqrt(2^level) - sqrt(2^(level-1)) # = sqrt(2^level) * (1 - 1/sqrt(2)) # about 0.29289 # len=1 extent of lower level 0 # len=4 extent of lower level 2 # len=8 extent of lower level 4+1 = 5 # len=16 extent of lower level 8+3 # len/2 + len/4-1 my @digit_to_rot = (-1, 1, 0, 1); my @dir4_to_dsdd = ([1,-1],[1,1],[-1,1],[-1,-1]); sub xy_to_n { return scalar((shift->xy_to_n_list(@_))[0]); } sub xy_to_n_list { my ($self, $x, $y) = @_; ### CCurve xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); my $zero = $x*0*$y; ($x,$y) = ($x + $y, $y - $x); # sum and diff if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my @n_list; foreach my $dsdd (@dir4_to_dsdd) { my ($ds,$dd) = @$dsdd; ### attempt: "ds=$ds dd=$dd" my $s = $x; # sum X+Y my $d = $y; # diff Y-X my @nbits; until ($s >= -1 && $s <= 1 && $d >= -1 && $d <= 1) { ### at: "s=$s, d=$d nbits=".join('',reverse @nbits) my $bit = $s % 2; push @nbits, $bit; if ($bit) { $s -= $ds; $d -= $dd; ($ds,$dd) = ($dd,-$ds); # rotate -90 } # divide 1/(1+i) = (1-i)/(1^2 - i^2) # = (1-i)/2 # so multiply (s + i*d) * (1-i)/2 # s = (s + d)/2 # d = (d - s)/2 # ### assert: (($s+$d)%2)==0 # this form avoids overflow near DBL_MAX my $odd = $s % 2; $s -= $odd; $d -= $odd; $s /= 2; $d /= 2; ($s,$d) = ($s+$d+$odd, $d-$s); } # five final positions # . 0,1 . ds,dd # | # -1,0--0,0--1,0 # | # . 0,-1 . # ### end: "s=$s d=$d ds=$ds dd=$dd" # last step must be East dx=1,dy=0 unless ($ds == 1 && $dd == -1) { next; } if ($s == $ds && $d == $dd) { push @nbits, 1; } elsif ($s != 0 || $d != 0) { next; } # ended s=0,d=0 or s=ds,d=dd, found an N push @n_list, digit_join_lowtohigh(\@nbits, 2, $zero); ### found N: "$n_list[-1]" } ### @n_list return sort {$a<=>$b} @n_list; } # f = (1 - 1/sqrt(2) = .292 # 1/f = 3.41 # N = 2^level # Rend = sqrt(2)^level # Rmin = Rend / 2 maybe # Rmin^2 = (2^level)/4 # N = 4 * Rmin^2 # sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### CCurve rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $x2 = round_nearest ($x2); $y1 = round_nearest ($y1); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; my ($len,$level) = _rect_to_k ($x1,$y1, $x2,$y2); if (is_infinite($level)) { return (0, $level); } return (0, 4*$len*$len*$self->{'arms'} - 1); } # N=16 is Y=4 away k=2 # N=64 is Y=-8+1=-7 away k=3 # N=256=4^4 is X=2^4=16-3=-7 away k=4 # dist = 2^k - (2^(k-2)-1) # = 2^k - 2^(k-2) + 1 # = 4*2^(k-2) - 2^(k-2) + 1 # = 3*2^(k-2) + 1 # k=2 3*2^(2-2)+1=4 len=4^2=16 # k=3 3*2^(3-2)+1=7 len=4^3=64 # k=4 3*2^(4-2)+1=13 # 2^(k-2) = (dist-1)/3 # 2^k = (dist-1)*4/3 # # up = 3*2^(k-2+1) + 1 # 2^(k+1) = (dist-1)*4/3 # 2^k = (dist-1)*2/3 # # left = 3*2^(k-2+1) + 1 # 2^(k+1) = (dist-1)*4/3 # 2^k = (dist-1)*2/3 # # down = 3*2^(k-2+1) + 1 # 2^(k+1) = (dist-1)*4/3 # 2^k = (dist-1)*2/3 # # m=2 4*(2-1)/3=4/3=1 # m=4 4*(4-1)/3=4 sub _rect_to_k { my ($x1,$y1, $x2,$y2) = @_; ### _rect_to_k(): $x1,$y1 { my $m = max(abs($x1),abs($y1),abs($x2),abs($y2)); if ($m < 2) { return (2, 1); } if ($m < 4) { return (4, 2); } ### round_down: 4*($m-1)/3 my ($len, $k) = round_down_pow (4*($m-1)/3, 2); return ($len, $k); } my $len; my $k = 0; my $offset = -1; foreach my $m ($x2, $y2, -$x1, -$y1) { $offset++; ### $offset ### $m next if $m < 0; my ($len1, $k1); # if ($m < 2) { # $len1 = 1; # $k1 = 0; # } else { # } ($len1, $k1) = round_down_pow (($m-1)/3, 2); next if $k1 < $offset; my $sub = ($offset-$k1) % 4; $k1 -= $sub; # round down to k1 == offset mod 4 if ($k1 > $k) { $k = $k1; $len = $len1 / 2**$sub; } } ### result: "k=$k len=$len" return ($len, 2*$k); } my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); # Not yet supporting "arms" parameter ... sub n_to_dxdy { my ($self, $n) = @_; ### n_to_dxdy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; # $n fraction part my @digits = bit_split_lowtohigh($int); my $dir = (sum(@digits)||0) & 3; # count of 1-bits my $dx = $dir4_to_dx[$dir]; my $dy = $dir4_to_dy[$dir]; if ($n) { # apply fraction part $n # count low 1-bits is right turn of N+1, apply as dir-(turn-1) so decr $dir while (shift @digits) { $dir--; } # this with turn=count-1 turn which is dir++ worked into swap and negate # of dir4_to_dy parts $dir &= 3; $dx -= $n*($dir4_to_dy[$dir] + $dx); # with rot-90 instead of $dir+1 $dy += $n*($dir4_to_dx[$dir] - $dy); # this the equivalent with explicit dir++ for turn=count-1 # $dir++; # $dir &= 3; # $dx += $n*($dir4_to_dx[$dir] - $dx); # $dy += $n*($dir4_to_dy[$dir] - $dy); } ### result: "$dx, $dy" return ($dx,$dy); } #------------------------------------------------------------------------------ # k even # S[h] # --------- # / \ Z[h-1] # / \ # | | S[h-1] # \ / Z[h-2] # -- -- # Hb[k] = S[h] + 2*S[h-1] + S[h] + 2*(Z[h-1]/2 - Z[h-2]/2) # + sqrt(2)*(2*Z[h-1]/2 + 2*Z[h-2]/2) # = 2*S[h] + 2*S[h-1] + Z[h-1]-Z[h-2] + sqrt(2) * (Z[h-1] + Z[h-2]) # = 2*2^h + 2*2^(h-1) + 2*2^(h-1)-2 - (2*2^(h-2)-2) + sqrt(2) * (2*2^(h-1)-2 + 2*2^(h-2)-2) # = 3*2^h + 2*2^(h-1)-2 - 2*2^(h-2) + 2 + sqrt(2) * (3*2^(h-1) - 4) # = 3*2^h + 2^(h-1) + sqrt(2) * (3*2^(h-1) - 4) # = 7*2^(h-1) + sqrt(2) * (3*2^(h-1) - 4) # = 7*sqrt(2)^(2h-2) + sqrt(2) * (3*sqrt(2)^(2h-2) - 4) # = 7*sqrt(2)^(k-2) + sqrt(2) * (3*sqrt(2)^(k-2) - 4) # = 7*sqrt(2)^(k-2) + sqrt(2)*3*sqrt(2)^(k-2) - 4*sqrt(2) # = 7*sqrt(2)^(k-2) + 3*sqrt(2)*sqrt(2)^(k-2) - 4*sqrt(2) # = (7 + 3*sqrt(2))*sqrt(2)^(k-2) - 4*sqrt(2) # # S[2]=4 # 11--10--7,9--6---5 Z[1]=2 k=4 h=2 # | | | # 13--12 8 4---3 4 + 2*2 + 4+(2-0) = 14 # | | S[1]=2 (2+0) = 2 # 14 2 # | | # 15---16 0---1 Z[0] = 0 # # k odd # S[h] # ---- # Z[h-1] / \ middle Z[h] # S[h-1] | \ # \ \ # | S[h] # | # \ / Z[h-1] # -- # S[h-1] # # Hb[k] = 2*S[h] + 2*S[h-1] + sqrt(2)*( Z[h]/2 + Z[h-1] + Z[h]/2 + S[h]-S[h-1] ) # = 2*S[h] + 2*S[h-1] + sqrt(2)*( Z[h] + Z[h-1] + S[h]-S[h-1] ) # = 2*2^h + 2*2^(h-1) + sqrt(2)*( 2*2^h-2 + 2*2^(h-1)-2 + 2^h - 2^(h-1) ) # = 3*2^h + sqrt(2)*( 3*2^h + 2^(h-1) - 4 ) # = 3*2^h + sqrt(2)*( 7*2^(h-1) - 4 ) sub _UNDOCUMENTED_level_to_hull_boundary { my ($self, $level) = @_; my ($a, $b) = $self->_UNDOCUMENTED_level_to_hull_boundary_sqrt2($level) or return undef; return $a + $b*sqrt(2); } sub _UNDOCUMENTED_level_to_hull_boundary_sqrt2 { my ($self, $level) = @_; if ($level <= 2) { if ($level < 0) { return; } if ($level == 2) { return (6,0); } return (2, ($level == 0 ? 0 : 1)); } my ($h, $rem) = _divrem($level, 2); my $pow = 2**($h-1); if ($rem) { return (6*$pow, 7*$pow-4); # return (2*S_formula($h) + 2*S_formula($h-1), # Z_formula($h)/2 + Z_formula($h-1) # + Z_formula($h)/2 + (S_formula($h)-S_formula($h-1)) ); } else { return (7*$pow, 3*$pow-4); # return (S_formula($h) + 2*S_formula($h-1) + S_formula($h)+(Z_formula($h-1)-Z_formula($h-2)), # (Z_formula($h-1) + Z_formula($h-2))); } } #------------------------------------------------------------------------------ { my @_UNDOCUMENTED_level_to_hull_area = (0, 1/2, 2); sub _UNDOCUMENTED_level_to_hull_area { my ($self, $level) = @_; if ($level < 3) { if ($level < 0) { return undef; } return $_UNDOCUMENTED_level_to_hull_area[$level]; } my ($h, $rem) = _divrem($level, 2); return 35*2**($level-4) - ($rem ? 13 : 10)*2**($h-1) + 2; # if ($rem) { # return 35*2**($level-4) - 13*$pow + 2; # # my $width = S_formula($h) + Z_formula($h)/2 + Z_formula($h-1)/2; # my $ul = Z_formula($h-1)/2; # my $ur = Z_formula($h)/2; # my $bl = $width - Z_formula($h-1)/2 - S_formula($h-1); # my $br = Z_formula($h-1)/2; # return $width**2 - $ul**2/2 - $ur**2/2 - $bl**2/2 - $br**2/2; # # } else { # return 35*2**($level-4) - 10*$pow + 2; # return 0; # return 35*2**($level-4) - 5*2**$h + 2; # # # my $width = S_formula($h) + Z_formula($h-1); # # my $upper = Z_formula($h-1)/2; # # my $lower = Z_formula($h-2)/2; # # my $height = S_formula($h-1) + $upper + $lower; # # return $width; # * $height - $upper*$upper - $lower*$lower; # } # } } } #------------------------------------------------------------------------------ # levels sub level_to_n_range { my ($self, $level) = @_; return (0, 2**$level); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($pow, $exp) = round_up_pow ($n, 2); return $exp; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Math-PlanePath ie OEIS dX,dY dX combinatorial Ramus th zig zags stairstep Duvall Keesling vy Preprint tilings optimization =head1 NAME Math::PlanePath::CCurve -- Levy C curve =head1 SYNOPSIS use Math::PlanePath::CCurve; my $path = Math::PlanePath::CCurve->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is an integer version of the "C" curve by LE<233>vy. =over "Les Courbes Planes ou Gauches et les Surfaces ComposE<233>e de Parties Semblables au Tout", Journal de l'E<201>cole Polytechnique, July 1938 pages 227-247 and October 1938 pages 249-292 L, L =back It spirals anti-clockwise, variously crossing and overlapping itself. The construction is straightforward but various measurements like how many distinct points are quite complicated. 11-----10-----9,7-----6------5 3 | | | 13-----12 8 4------3 2 | | 19---14,18----17 2 1 | | | | 21-----20 15-----16 0------1 <- Y=0 | 22 -1 | 25,23---24 -2 | 26 35-----34-----33 -3 | | | 27,37--28,36 32 -4 | | | 38 29-----30-----31 -5 | 39,41---40 -6 | 42 ... -7 | | 43-----44 49-----48 64-----63 -8 | | | | 45---46,50----47 62 -9 | | 51-----52 56 60-----61 -10 | | | 53-----54----55,57---58-----59 -11 ^ -7 -6 -5 -4 -3 -2 -1 X=0 1 The initial segment N=0 to N=1 is repeated with a turn +90 degrees left to give N=1 to N=2. Then N=0to2 is repeated likewise turned +90 degrees and placed at N=2 to make N=2to4. And so on doubling each time. 4----3 | N=0to2 2 2 repeated | | as N=2to4 0----1 0----1 0----1 with turn +90 The 90 degree rotation is the same at each repetition, so the segment at N=2^k is always the initial N=0to1 turned +90 degrees. This means at N=1,2,4,8,16,etc the direction is always upwards. The X,Y position can be written in complex numbers as a recurrence with N = 2^k + r high bit 2^k, rest r<2^k C(N) = C(2^k) + i*C(r) = (1+i)^k + i*C(r) The effect is a change from base 2 to base 1+i but with a further power of i on each term. Suppose the 1-bits in N are at positions k0, k1, k2, etc (high to low), then C(N) = b^k0 * i^0 N= 2^k0 + 2^(k1) + 2^(k2) + ... in binary + b^k1 * i^1 k0 > k1 > k2 > ... + b^k2 * i^2 base b=1+i + b^k3 * i^3 + ... Notice the i power is not the bit position k, but rather how many 1-bits are above the position. =head2 Level Ranges 4^k The X,Y extents of the path through to Nlevel=2^k can be expressed as a width and height measured relative to the endpoints. *------------------* <-+ | | | *--* *--* | height h[k] | | | * N=4^k N=0 * <-+ | | | | | below l[k] *--*--* *--*--* <-+ ^-----^ ^-----^ width 2^k width w[k] w[k] Extents to N=4^k <------------------------> total width = 2^k + 2*w[k] N=4^k is on either the X or Y axis and for the extents here it's taken rotated as necessary to be horizontal. k=2 N=4^2=16 shown above is already horizontal. The next level k=3 N=64=4^3 would be rotated -90 degrees to be horizontal. The width w[k] is measured from the N=0 and N=4^k endpoints. It doesn't include the 2^k length between those endpoints. The two ends are symmetric so the extent is the same at each end. h[k] = 2^k - 1 0,1,3,7,15,31,etc w[k] = / 0 for k=0 \ 2^(k-1) - 1 for k>=1 0,0,1,3,7,15,etc l[k] = / 0 for k<=1 \ 2^(k-2) - 1 for k>=2 0,0,0,1,3,7,etc The initial N=0 to N=64 shown above is k=3. h[3]=7 is the X=-7 horizontal. l[3]=1 is the X=1 horizontal. w[3]=3 is the vertical Y=3, and also Y=-11 which is 3 below the endpoint N=64 at Y=8. Expressed as a fraction of the 2^k distance between the endpoints the extents approach total 2 wide by 1.25 high, *------------------* <-+ | | | 1 *--* *--* | total | | | height * N=4^k N=0 * <-+ -> 1+1/4 | | | | | 1/4 *--*--* *--*--* <-+ ^-----^ ^-----^ 1/2 1 1/2 total width -> 2 The extent formulas can be found by considering the self-similar blocks. The initial k=0 is a single line segment and all its extents are 0. h[0] = 0 N=1 ----- N=0 l[0] = 0 w[0] = 0 Thereafter the replication overlap as +-------+---+-------+ | | | | +------+ | | +------+ | | D | | C | | B | | <-+ | +-------+---+-------+ | | 2^(k-1) | | | | | previous | | | | | level ends | E | | A | <-+ +------+ +------+ ^---------------^ 2^k this level ends w[k] = max (h[k-1], w[k-1]) # right of A,B h[k] = 2^(k-1) + max (h[k-1], w[k-1]) # above B,C,D l[k] = max w[k-1], l[k-1]-2^(k-1) # below A,E Since h[k]=2^(k-1)+w[k] have S w[k]> for kE=1 and with the initial h[0]=w[k]=0 have h[k]E=w[k] always. So the max of those two is h. h[k] = 2^(k-1) + h[k-1] giving h[k] = 2^k-1 for k>=1 w[k] = h[k-1] giving w[k] = 2^(k-1)-1 for k>=1 The max for l[k] is always w[k-1] as l[k] is never big enough that the parts B-C and C-D can extend down past their 2^(k-1) vertical position. (l[0]=w[0]=0 and thereafter by induction l[k]E=w[k].) l[k] = w[k-1] giving l[k] = 2^(k-2)-1 for k>=2 =head2 Repeated Points The curve crosses itself and can repeat X,Y positions up to 4 times. The first double, triple and quadruple points are at visits X,Y N ------ ------- ---------------------- 2 -2, 3 7, 9 3 18, -7 189, 279, 281 4 -32, 55 1727, 1813, 2283, 2369 =cut # binary # 2 -10, 11 111, 1001 # 3 2 # 3 10010, -111 10111101, 100010111, 100011001 # 6 5 4 # 4 -100000, 110111 11010111111, 11100010101, # 100011101011, 100101000001 # 9, 6, 7, 4 =pod Each line segment between integer points is traversed at most 2 times, once forward and once backward. There's 4 lines reaching each integer point and this line traversal means the points are visited at most 4 times. As per L below the direction of the curve is given by the count of 1-bits in N. Since no line is repeated each of the N values at a given X,Y have a different count-1-bits mod 4. For example N=7 is 3 1-bits and N=9 is 2 1-bits. The full counts need not be consecutive, as for example N=1727 is 9 1-bits and N=2369 is 4 1-bits. The maximum of 2 line segment traversals can be seen from the way the curve replicates. Suppose the entire plane had all line segments traversed forward and backward. v | v | -- <-------- <- [0,1] [1,1] [X,Y] = integer points -> --------> -- each edge traversed | ^ | ^ forward and backward | | | | | | | | v | v | -- <-------- <-- [0,0] [1,0] -> --------> -- | ^ | ^ Then when each line segment expands on the right the result is the same pattern of traversals -- viewed rotated by 45-degrees and scaled by factor sqrt(2). \ v / v \ v / v [0,1] [1,1] / / ^ \ ^ / ^ \ / / \ \ / / \ \ \ \ / / \ v / v [1/2,1/2] ^ / ^ \ / / \ \ \ \ / / \ \ / / \ v / v \ v / v [0,0] 1,0 ^ / ^ \ ^ / ^ \ The curve is a subset of this pattern. It begins as a single line segment which has this pattern and thereafter the pattern preserves itself. Hence at most 2 segment traversals in the curve. =head2 Tiling The segment traversal argument above can also be made by taking the line segments as triangles which are a quarter of a unit square with peak pointing to the right of the traversal direction. to * ^\ | \ | \ triangle peak | / | / |/ quarter of a unit square from * These triangles in the two directions tile the plane. On expansion each splits into 2 halves in new positions. Those parts don't overlap and the plane is still tiled. See for example =over Larry Riddle L, L =back For the integer version of the curve this kind of tiling can be used to combine copies of the curve so that each every point is visited precisely 4 times. The h[k], w[k] and l[k] extents above are less than the 2^k endpoint length, so a square of side 2^k can be fully tiled with copies of the curve at each corner, | ^ | ^ | | | | 24 copies of the curve | | | | to visit all points of the v | v | inside square ABCD <------- <-------- <-------- precisely 4 times each A B --------> --------> --------> each part points | ^ | ^ N=0 to N=4^k-1 | | | | rotated and shifted | | | | suitably v | v | <-------- <-------- <-------- C D -------- --------> --------> | ^ | ^ | | | | | | | | v | v | The four innermost copies of the curve cover most of the inside square, but the other copies surrounding them loop into the square and fill in the remainder to make 4 visits at every point. =cut # If doing this tiling note that only points N=0 to N=4^k-1 are used. If # N=4^k was included then it would duplicate the N=0 at the "*" endpoints, # resulting in 8 visits there rather than the intended 4. =pod It's interesting to note that a set of 8 curves at the origin only covers the axes with 4-fold visits, | ^ 8 arms at the origin | | cover only X,Y axes v | with 4-visits <-------- <-------- 0,0 away from the axes -------- --------> some points < 4 visits | ^ | | v | This means that if the path had some sort of "arms" of multiple curves extending from the origin then it would visit all points on the axes X=0 Y=0 a full 4 times, but off the axes there would be points without full 4 visits. See F for a wxWidgets program drawing various forms and tilings of the curve. =cut # The S<"_ _ _"> line shown which is part of the 24-pattern above but omitted # here. This line is at Y=2^k. The extents described above mean that it # extends down to Y=2^k - h[k] = 2^k-(2^k-1)=1, so it visits some points in # row Y=1 and higher. Omitting the curve means there are YE=1 not visited # 4 times. Similarly YE=-1 and XE-1 and XE=+1. =pod =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::CCurve-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. =item C<$n = $path-Exy_to_n ($x,$y)> Return the point number for coordinates C<$x,$y>. If there's nothing at C<$x,$y> then return C. If C<$x,$y> is visited more than once then return the smallest C<$n> which visits it. =item C<@n_list = $path-Exy_to_n_list ($x,$y)> Return a list of N point numbers at coordinates C<$x,$y>. If there's nothing at C<$x,$y> then return an empty list. A given C<$x,$y> is visited at most 4 times so the returned list is at most 4 values. =item C<$n = $path-En_start()> Return 0, the first N in the path. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 2**$level)>. =back =head1 FORMULAS Some formulas and results can also be found in the author's mathematical write-up =over L =back =head2 Direction The direction or net turn of the curve is the count of 1 bits in N, direction = count_1_bits(N) * 90degrees For example N=11 is binary 1011 has three 1 bits, so direction 3*90=270 degrees, ie. to the south. This bit count is because at each power-of-2 position the curve is a copy of the lower bits but turned +90 degrees, so +90 for each 1-bit. For powers-of-2 N=2,4,8,16, etc, there's only a single 1-bit so the direction is always +90 degrees there, ie. always upwards. =head2 Turn At each point N the curve can turn in any direction: left, right, straight, or 180 degrees back. The turn is given by the number of low 0-bits of N, turn right = (count_low_0_bits(N) - 1) * 90degrees For example N=8 is binary 0b100 which is 2 low 0-bits for turn=(2-1)*90=90 degrees to the right. When N is odd there's no low zero bits and the turn is always (0-1)*90=-90 to the right, so every second turn is 90 degrees to the left. =head2 Next Turn The turn at the point following N, ie. at N+1, can be calculated by counting the low 1-bits of N, next turn right = (count_low_1_bits(N) - 1) * 90degrees For example N=11 is binary 0b1011 which is 2 low one bits for nextturn=(2-1)*90=90 degrees to the right at the following point, ie. at N=12. This works simply because low 1-bits like ..0111 increment to low 0-bits ..1000 to become N+1. The low 1-bits at N are thus the low 0-bits at N+1. =head2 N to dX,dY C is implemented using the direction described above. For integer N the count mod 4 gives the direction for dX,dY. dir = count_1_bits(N) mod 4 dx = dir_to_dx[dir] # table 0 to 3 dy = dir_to_dy[dir] For fractional N the direction at int(N)+1 can be obtained from the direction at int(N) and the turn at int(N)+1, which is the low 1-bits of N per L above. Those two directions can then be combined as described in L. # apply turn to make direction at Nint+1 turn = count_low_1_bits(N) - 1 # N integer part dir = (dir - turn) mod 4 # direction at N+1 # adjust dx,dy by fractional amount in this direction dx += Nfrac * (dir_to_dx[dir] - dx) dy += Nfrac * (dir_to_dy[dir] - dy) A small optimization can be made by working the "-1" of the turn formula into a +90 degree rotation of the C and C parts by swap and sign change, turn_plus_1 = count_low_1_bits(N) # on N integer part dir = (dir - turn_plus_1) mod 4 # direction-1 at N+1 # adjustment including extra +90 degrees on dir dx -= $n*(dir_to_dy[dir] + dx) dy += $n*(dir_to_dx[dir] - dy) =head2 X,Y to N The N values at a given X,Y can be found by taking terms low to high from the complex number formula (the same as given above) X+iY = b^k N = 2^k + 2^(k1) + 2^(k2) + ... in binary + b^k1 * i base b=1+i + b^k2 * i^2 + ... If the lowest term is b^0 then X+iY has X+Y odd. If the lowest term is not b^0 but instead some power b^n then X+iY has X+Y even. This is because a multiple of b=1+i, X+iY = (x+iy)*(1+i) = (x-y) + (x+y)i so X=x-y Y=x+y sum X+Y = 2x is even if X+iY a multiple of 1+i So the lowest bit of N is found by bit = (X+Y) mod 2 If bit=1 then a power i^p is to be subtracted from X+iY. p is how many 1-bits are above that point, and this is not yet known. It represents a direction to move X,Y to put it on an even position. It's also the direction of the step N-2^l to N, where 2^l is the lowest 1-bit of N. The reduction should be attempted with p commencing as each of the four possible directions N,S,E,W. Some or all will lead to an N. For quadrupled points (such as X=-32, Y=55 described above) all four will lead to an N. for p 0 to 3 dX,dY = i^p # directions [1,0] [0,1] [-1,0] [0,-1] loop until X,Y = [0,0] or [1,0] or [-1,0] or [0,1] or [0,-1] { bit = X+Y mod 2 # bits of N from low to high if bit == 1 { X -= dX # move to "even" X+Y == 0 mod 2 Y -= dY (dX,dY) = (dY,-dX) # rotate -90 as for p-1 } X,Y = (X+Y)/2, (Y-X)/2 # divide (X+iY)/(1+i) } if not (dX=1 and dY=0) wrong final direction, try next p if X=dX and Y=dY further high 1-bit for N found an N if X=0 and Y=0 found an N The "loop until" ends at one of the five points 0,1 | -1,0 -- 0,0 -- 1,0 | 0,-1 It's not possible to wait for X=0,Y=0 to be reached because some dX,dY directions will step infinitely among the four non-zeros. Only the case X=dX,Y=dY is sure to reach 0,0. The successive p decrements which rotate dX,dY by -90 degrees must end at p == 0 mod 4 for highest term in the X+iY formula having i^0=1. This means must end dX=1,dY=0 East. If this doesn't happen then there is no N for that p direction. The number of 1-bits in N is == p mod 4. So the order the N values are obtained follows the order the p directions are attempted. In general the N values will not be smallest to biggest N so a little sort is necessary if that's desired. It can be seen that sum X+Y is used for the bit calculation and then again in the divide by 1+i. It's convenient to write the whole loop in terms of sum S=X+Y and difference D=Y-X. for dS = +1 or -1 # four directions for dD = +1 or -1 # S = X+Y D = Y-X loop until -1 <= S <= 1 and -1 <= D <= 1 { bit = S mod 2 # bits of N from low to high if bit == 1 { S -= dS # move to "even" S+D == 0 mod 2 D -= dD (dS,dD) = (dD,-dS) # rotate -90 } (S,D) = (S+D)/2, (D-S)/2 # divide (S+iD)/(1+i) } if not (dS=1 and dD=-1) wrong final direction, try next dS,dD direction if S=dS and D=dD further high 1-bit for N found an N if S=0 and D=0 found an N The effect of S=X+Y, D=Y-D is to rotate by -45 degrees and use every second point of the plane. D= 2 X=0,Y=2 . rotate -45 D= 1 X=0,Y=1 . X=1,Y=2 . D= 0 X=0,Y=0 . X=1,Y=1 . X=2,Y=2 D=-1 X=1,Y=0 . X=2,Y=1 . D=-2 X=2,Y=0 . S=0 S=1 S=2 S=3 S=4 The final five points described above are then in a 3x3 block at the origin. The four in-between points S=0,D=1 etc don't occur so range tests -1E=SE=1 and -1E=DE=1 can be used. S=-1,D=1 . S=1,D=1 . S=0,D=0 . S=-1,D=-1 . S=1,D=-1 =head2 Segments by Direction In a level N=0 to N=2^k-1 inclusive, the number of segments in each direction 0=East, 1=North, 2=West, 3=South are given by k=0 for k >= 1 --- ---------- M0[k] = 1, 2^(k-2) + d(k+2)*2^(h-1) M1[k] = 0, 2^(k-2) + d(k+0)*2^(h-1) M2[k] = 0, 2^(k-2) + d(k-2)*2^(h-1) M3[k] = 0, 2^(k-2) + d(k-4)*2^(h-1) where h = floor(k/2) and d(m) = 0 1 1 1 0 -1 -1 -1 for m == 0 to 7 mod 8 M0[k] = 1, 1, 1, 1, 2, 6, 16, 36, 72, 136, 256, ... M1[k] = 0, 1, 2, 3, 4, 6, 12, 28, 64, 136, 272, ... M2[k] = 0, 0, 1, 3, 6, 10, 16, 28, 56, 120, 256, ... M3[k] = 0, 0, 0, 1, 4, 10, 20, 36, 64, 120, 240, ... d(n) is a factor +1, -1 or 0 according to n mod 8. Each M goes as a power 2^(k-2), so roughly 1/4 each, but a half power 2^(h-1) possibly added or subtracted in a k mod 8 pattern. In binary this is a 2^(k-2) high 1-bit with another 1-bit in the middle added or subtracted. The total is 2^k since there are a total 2^k points from N=0 to 2^k-1 inclusive. M0[k] + M1[k] + M2[k] + M3[k] = 2^k It can be seen that the d(n) parts sum to 0 so the 2^(h-1) parts cancel out leaving 4*2^(k-2) = 2^k. d(0) + d(2) + d(4) + d(6) = 0 d(1) + d(3) + d(5) + d(7) = 0 =cut # GP-DEFINE Mdir_vec = [0, 1, 1, 1, 0, -1, -1, -1] # GP-DEFINE Mdir(n) = Mdir_vec[(n%8)+1] /* +1 for vector start index 1 */ # GP-DEFINE M0half(k) = my(h=floor(k/2)); if(k==0,1, 2^(k-2) + Mdir(k+2)*2^(h-1)) # GP-DEFINE M1half(k) = my(h=floor(k/2)); if(k==0,0, 2^(k-2) + Mdir(k+0)*2^(h-1)) # GP-DEFINE M2half(k) = my(h=floor(k/2)); if(k==0,0, 2^(k-2) + Mdir(k-2)*2^(h-1)) # GP-DEFINE M3half(k) = my(h=floor(k/2)); if(k==0,0, 2^(k-2) + Mdir(k-4)*2^(h-1)) # GP-DEFINE M0samples = [ 1, 1, 1, 1, 2, 6, 16, 36, 72, 136, 256 ] # GP-DEFINE M1samples = [ 0, 1, 2, 3, 4, 6, 12, 28, 64, 136, 272 ] # GP-DEFINE M2samples = [ 0, 0, 1, 3, 6, 10, 16, 28, 56, 120, 256 ] # GP-DEFINE M3samples = [ 0, 0, 0, 1, 4, 10, 20, 36, 64, 120, 240 ] # GP-Test vector(length(M0samples),k,M0half(k-1)) == M0samples # GP-Test vector(length(M1samples),k,M1half(k-1)) == M1samples # GP-Test vector(length(M2samples),k,M2half(k-1)) == M2samples # GP-Test vector(length(M3samples),k,M3half(k-1)) == M3samples =pod The counts can be calculated in two ways. Firstly they satisfy mutual recurrences. Each adds the preceding rotated M. M0[k+1] = M0[k] + M3[k] initially M0[0] = 1 (N=0 to N=1) M1[k+1] = M1[k] + M0[k] M1[0] = 0 M2[k+1] = M2[k] + M1[k] M2[0] = 0 M3[k+1] = M3[k] + M2[k] M3[0] = 0 Geometrically this can be seen from the way each level extends by a copy of the previous level rotated +90, 7---6---5 Easts in N=0 to 8 | | = Easts in N=0 to 4 8 4---3 + Wests in N=0 to 4 | since N=4 to N=8 is 2 the N=0 to N=4 rotated +90 | 0---1 For the bits in N, level k+1 introduces a new bit either 0 or 1. In M0[k+1] the a 0-bit is count M0[k] the same direction, and when a 1-bit is M3[k] since one less bit mod 4. Similarly the other counts. Some substitutions give 3rd order recurrences for k >= 4 M0[k] = 4*M0[k-1] - 6*M0[k-2] + 4*M0[k-3] initial 1,1,1,1 M1[k] = 4*M1[k-1] - 6*M1[k-2] + 4*M1[k-3] initial 0,1,2,3 M2[k] = 4*M2[k-1] - 6*M2[k-2] + 4*M2[k-3] initial 0,0,1,3 M3[k] = 4*M3[k-1] - 6*M3[k-2] + 4*M3[k-3] initial 0,0,0,1 =cut # GP-DEFINE M0rec(k) = if(k<4,1, 4*M0rec(k-1) - 6*M0rec(k-2) + 4*M0rec(k-3)) # GP-DEFINE M1rec(k) = if(k<4,k, 4*M1rec(k-1) - 6*M1rec(k-2) + 4*M1rec(k-3)) # GP-DEFINE M2rec(k) = if(k<2,0, if(k==2,1, if(k==3,3, 4*M2rec(k-1) - 6*M2rec(k-2) + 4*M2rec(k-3)))) # GP-DEFINE M3rec(k) = if(k<3,0, if(k==3,1, 4*M3rec(k-1) - 6*M3rec(k-2) + 4*M3rec(k-3))) # GP-Test vector(20,k,M0rec(k-1)) == vector(20,k,M0half(k-1)) # GP-Test vector(20,k,M1rec(k-1)) == vector(20,k,M1half(k-1)) # GP-Test vector(20,k,M2rec(k-1)) == vector(20,k,M2half(k-1)) # GP-Test vector(20,k,M3rec(k-1)) == vector(20,k,M3half(k-1)) =pod The characteristic polynomial of these recurrences is x^3 - 4x^2 + 6x - 4 = (x-2) * (x - (1-i)) * (x - (1+i)) =for GP-Test x^3 - 4*x^2 + 6*x - 4 == (x-2)*(x^2 - 2*x + 2) =for GP-Test x^3 - 4*x^2 + 6*x - 4 == (x-2) * (x + (I-1)) * (x - (I+1)) So explicit formulas can be written in powers of the roots 2, 1-i and 1+i, M0[k] = ( 2^k + (1-i)^k + (1+i)^k )/4 for k>=1 M1[k] = ( 2^k + i*(1-i)^k - i*(1+i)^k )/4 M2[k] = ( 2^k - (1-i)^k - (1+i)^k )/4 M3[k] = ( 2^k - i*(1-i)^k + i*(1+i)^k )/4 =cut # GP-DEFINE M0pow(k) = if(k==0,1, (1/4)*(2^k + (1-I)^k + (1+I)^k)) # GP-DEFINE M1pow(k) = if(k==0,0, (1/4)*(2^k + I*(1-I)^k - I*(1+I)^k)) # GP-DEFINE M2pow(k) = if(k==0,0, (1/4)*(2^k - (1-I)^k - (1+I)^k)) # GP-DEFINE M3pow(k) = if(k==0,0, (1/4)*(2^k - I*(1-I)^k + I*(1+I)^k)) # GP-Test vector(50,k,M0pow(k-1)) == vector(50,k,M0half(k-1)) # GP-Test vector(50,k,M1pow(k-1)) == vector(50,k,M1half(k-1)) # GP-Test vector(50,k,M2pow(k-1)) == vector(50,k,M2half(k-1)) # GP-Test vector(50,k,M3pow(k-1)) == vector(50,k,M3half(k-1)) =pod The complex numbers 1-i and 1+i are 45 degree lines clockwise and anti-clockwise respectively. The powers turn them in opposite directions so the imaginary parts always cancel out. The remaining real parts can be had by a half power h=floor(k/2) which is the magnitude abs(1-i)=sqrt(2) projected onto the real axis. The sign selector d(n) above is whether the positive or negative part of the real axis, or zero when at the origin. The second way to calculate is the combinatorial interpretation that per L above the direction is count_1_bits(N) mod 4 so East segments are all N values with count_1_bits(N) == 0 mod 4, ie. N with 0, 4, 8, etc many 1-bits. The number of ways to have those bit counts within total k bits is k choose 0, 4, 8 etc. M0[k] = /k\ + /k\ + ... + / k\ m = floor(k/4) \0/ \4/ \4m/ M1[k] = /k\ + /k\ + ... + / k \ m = floor((k-1)/4) \1/ \5/ \4m+1/ M2[k] = /k\ + /k\ + ... + / k \ m = floor((k-2)/4) \2/ \6/ \4m+2/ M3[k] = /k\ + /k\ + ... + / k \ m = floor((k-3)/4) \3/ \7/ \4m+3/ =cut # GP-DEFINE M0sum(k) = sum(i=0,floor(k/4), binomial(k, 4*i)) # GP-DEFINE M1sum(k) = sum(i=0,floor(k/4), binomial(k, 4*i+1)) # GP-DEFINE M2sum(k) = sum(i=0,floor(k/4), binomial(k, 4*i+2)) # GP-DEFINE M3sum(k) = sum(i=0,floor(k/4), binomial(k, 4*i+3)) # GP-Test vector(length(M0samples),k,M0sum(k-1)) == M0samples # GP-Test vector(length(M1samples),k,M1sum(k-1)) == M1samples # GP-Test vector(length(M2samples),k,M2sum(k-1)) == M2samples # GP-Test vector(length(M3samples),k,M3sum(k-1)) == M3samples =pod The power forms above are cases of the identity by Ramus for sums of binomial coefficients in arithmetic progression like this. (See Knuth volume 1 section 1.2.6 exercise 30 for a form with cosines resulting from w=i+1 as 8th roots of unity.) The total M0+M1+M2+M3=2^k is the total binomials across a row of Pascal's triangle. /k\ + /k\ + ... + /k\ = 2^k \0/ \1/ \k/ It's interesting to note the M counts here are the same in the dragon curve (L). The shapes of the curves are different since the segments are in a different order, but the total puts points N=2^k at the same X,Y position. =cut # cf. # J. Konvalina, Y.-H. Liu, Arithmetic progression sums of binomial # coefficients, Appl. Math. Lett., 10(4), 11-13 (1997). # ((1+I)^k + (1-I)^k)/2^floor(k/2) = [2, 2, 0, -2, -2, -2, 0, 2, ] # M3[k] = M0[k+1] - M0[k] # = 2^(k+1) - 2^k (1-i)^(k+1) - (1-i)^k (1+i)^(k+1) - (1+i)^k # = 2^k (1-i - 1)*(1+i)^k (1+i - 1)*(1+i)^k # = 2^k (-i)*(1+i)^k (i)*(1+i)^k # M2[k] = M3[k+1] - M3[k] # = 2^k (-i)*(-i)*(1+i)^k (i)*(i)*(1+i)^k # = 2^k - (1+i)^k - (1+i)^k # M2[k] = M3[k+1] - M3[k] # = 2^k (-i)*(-i)*(-i)*(1+i)^k (i)*(i)*(i)*(1+i)^k # = 2^k + i*(1+i)^k - i*(1+i)^k # S[k] = a*2^k + (c+di)*(1-i)^k + (e+fi)*(1+i)^k # a*2^0 + (c+di)*(1-i)^0 + (e+fi)*(1+i)^0 = 1 # a + (c+di) + (e+fi) = 1 # a + c + e = 1 # + d + f = 0 # a*2^1 + (c+di)*(1-i)^1 + (e+fi)*(1+i)^1 = 1 # a*2 + (c+di)*(1-i) + (e+fi)*(1+i) = 1 # 2a + d - f = 1 # - c + e = 0 # a*2^2 + (c+di)*(1-i)^2 + (e+fi)*(1+i)^2 = 1 # a*4 + (c+di)*-2i + (e+fi)*2i = 1 # 4a + 2d - 2f = 1 # 4b - 2c + 2e = 0 # matsolve([1,1,1; 2,1,1; 4,2,-2]; [1,1,1]) # a*2 + b*(1-i) + c*(1+i) = 1 # 2a + (1-i)b + (1+i)c = 1 # a*4 + b*-2i + c*2i = 1 4a + -2ib + 2ic = 1 # b=c a=1/4 b=c=3/8 =pod =head2 Right Boundary The length of the right-side boundary of the curve, which is the outside of the "C", from N=0 to N=2^k is R[k] = / 7*2^h - 2k - 6 if k even \ 10*2^h - 2k - 6 if k odd where h = floor(k/2) = 1, 2, 4, 8, 14, 24, 38, 60, 90, 136, 198, 292, 418, ... R[k] = (7/2 + 5/2 * sqrt(2)) * ( sqrt(2))^k + (7/2 - 5/2 * sqrt(2)) * (-sqrt(2))^k - 2*k - 6 R[k] = 2*R[k-1] + R[k-2] - 4*R[k-3] + 2*R[k-4] =cut # GP-DEFINE Rsamples = [1, 2, 4, 8, 14, 24, 38, 60, 90, 136, 198, 292, 418]; # GP-DEFINE Rcases(k)=if(k%2,10,7)*2^floor(k/2) - 2*k - 6; # GP-Test vector(length(Rsamples), k, Rcases(k-1)) == Rsamples # GP-DEFINE Rrec(k) = { # GP-DEFINE if(k<4,Rsamples[k+1], # GP-DEFINE 2*Rrec(k-1) + Rrec(k-2) - 4*Rrec(k-3) + 2*Rrec(k-4)); # GP-DEFINE } # GP-Test vector(length(Rsamples), k, Rrec(k-1)) == Rsamples # GP-DEFINE nearint(x)=if(abs(x-round(x)) < 0.000001, round(x), x) # GP-DEFINE Rpow(k) = { # GP-DEFINE nearint( (7/2 + 5/2 * sqrt(2))*( sqrt(2))^k # GP-DEFINE + (7/2 - 5/2 * sqrt(2))*(-sqrt(2))^k ) # GP-DEFINE - 2*k - 6; # GP-DEFINE } # GP-Test vector(length(Rsamples), k, Rpow(k-1)) == Rsamples # R[2k] = (7/2 + 5/2 * sqrt(2))*( sqrt(2))^(2k) # + (7/2 - 5/2 * sqrt(2))*(-sqrt(2))^(2k) # = (7/2 + 5/2 * sqrt(2))*2^k # + (7/2 - 5/2 * sqrt(2))*2^k # = 2*7/2*2^k # = 7*2^k # # R[2k+1] = (7/2 + 5/2 * sqrt(2))*( sqrt(2))^(2k) # + (7/2 - 5/2 * sqrt(2))*(-sqrt(2))^(2k) # = (7/2 + 5/2 * sqrt(2))*2^k*sqrt(2) # + (7/2 - 5/2 * sqrt(2))*2^k*-sqrt(2) # = (7/2*sqrt(2) + 5/2 * sqrt(2)*sqrt(2))*2^k # + (7/2*-sqrt(2) - 5/2 * sqrt(2)*-sqrt(2))*2^k # = (7/2*sqrt(2) + 5/2 * 2)*2^k # + (7/2*-sqrt(2) + 5/2 * 2)*2^k # = (5/2 * 2)*2^k * 2 # = 10*2^k =pod The length doubles until R[4]=14 which is points N=0 to N=2^4=16. At k=4 the points N=7,8,9 have turned inward and closed off some of the outside of the curve so the boundary less than 2x. 11--10--9,7--6--5 right boundary | | | around "outside" 13--12 8 4--3 N=0 to N=2^4=16 | | 14 2 R[4]=14 | | 15--16 0--1 The floor(k/2) and odd/even cases are eliminated by the +/-sqrt(2) powering shown. Those powers are also per the characteristic equation of the recurrence, x^4 - 2*X^3 - x^2 + 4*x - 2 = (x - 1)^2 * (x + sqrt(2)) * (x - sqrt(2)) roots 1, sqrt(2), -sqrt(2) The right boundary comprises runs of straight lines and zig-zags. When it expands the straight lines become zig-zags and the zig-zags become straight lines. The straight lines all point "forward", which is anti-clockwise. c * a / ^ / ^ / ^ => v \ v \ v \ D<----C<----B<----A D C B A | ^ / ^ v | v \ straight S=3 zig-zag Z[k+1] = 2S[k]-2 = 4 The count Z here is both sides of each "V" shape from points "a" through to "c". So Z counts the boundary length (rather than the number of "V"s). Each S becomes an upward peak. The first and last side of those peaks become part of the following "straight" section (at A and D), hence Z[k+1]=2*S[k]-2. The zigzags all point "forward" too. When they expand they close off the V shape and become 2 straight lines for each V, which means 1 straight line for each Z side. The segment immediately before and after contribute a segment to the resulting straight run too, hence S[k+1]=Z[k]+2. C B A *<---C<---*<---B<---*<---A<---* / ^ / ^ / ^ | | | | v \ v \ v \ => | | | | * * * * <--* * * *<-- / ^ v \ zig-zag Z=4 segments straight S[k+1] = Z[k]+2 = 6 The initial N=0 to N=1 is a single straight segment S[0]=1 and from there the runs grow. N=1 to N=3 is a straight section S[1]=2. Z[0]=0 represents an empty zigzag at N=1. Z[1] is the first non-empty at N=3 to N=5. h S[h] Z[h] Z[h] = 2*S[h]-2 -- ---- ---- S[h+1] = Z[h]+2 0 1 0 1 2 2 S[h+1] = 2*S[h]-2+2 = 2*S[h] 2 4 6 so 3 8 14 S[h] = 2^h 4 16 30 Z[h] = 2*2^h-2 5 32 62 5 64 126 The curve N=0 to N=2^k is symmetric at each end and is made up of runs S[0], Z[0], S[1], Z[1], etc, of straight and zigzag alternately at each end. When k is even there's a single copy of a middle S[k/2]. When k is odd there's a single middle Z[(k-1)/2] (with an S[(k-1)/2] before and after). So / i=h-1 \ # where h = floor(k/2) R[k] = 2 * | sum S[i]+Z[i] | \ i=0 / + S[h] + / S[h]+Z[h] if k odd \ 0 if k even = 2*( 1+2+4+...+2^(h-1) # S[0] to S[h-1] + 2+4+8+...+2^h - 2*h) # Z[0] to Z[h-1] + 2^h # S[h] + if k odd (2^h + 2*2^h - 2) # possible S[h]+Z[h] = 2*(2^h-1 + 2*2^h-2 - 2h) + 2^h + (k odd 3*2^h - 2) = 7*2^h - 4h-6 + (if k odd then + 3*2^h - 2) = 7*2^h - 2k-6 + (if k odd then + 3*2^h) =head2 Convex Hull Boundary A convex hull is the smallest convex polygon which contains a given set of points. For the C curve the boundary length of the convex hull for points N=0 to N=2^k inclusive is hull boundary[k] / 2 if k=0 | 2+sqrt(2) if k=1 = | 6 if k=2 | 6*2^(h-1) + (7*2^(h-1) - 4)*sqrt(2) if k odd >=3 \ 7*2^(h-1) + (3*2^(h-1) - 4)*sqrt(2) if k even >=4 where h = floor(k/2) k hull boundary --- ---------------------------- 0 2 + 0 * sqrt(2) = 2 1 2 + 1 * sqrt(2) = 3.41 2 6 + 0 * sqrt(2) = 6 3 6 + 3 * sqrt(2) = 10.24 4 14 + 2 * sqrt(2) = 16.82 5 12 + 10 * sqrt(2) = 26.14 6 28 + 8 * sqrt(2) = 39.31 7 24 + 24 * sqrt(2) = 57.94 8 56 + 20 * sqrt(2) = 84.28 9 48 + 52 * sqrt(2) = 121.53 The integer part is the straight sides of the hull and the sqrt(2) part is the diagonal sides of the hull. When k is even the hull has the following shape. The sides are as per the right boundary above but after Z[h-2] the curl goes inwards and so parts beyond Z[h-2] are not part of the hull. Each Z stair-step diagonal becomes a sqrt(2) length for the hull. Z counts both vertical and horizontal of each stairstep, hence sqrt(2)*Z/2 for the hull boundary. S[h] *--------* * Z=2 Z[h-1] / \ Z[h-1] | \ diagonal / \ | \ sqrt(2)*Z/2 * * *----* = sqrt(2) S[h-1] | | S[h-1] | | * * Z[h-2] \ / Z[h-2] *-- --* S[h] + Z[h-2]-Z[h-1] k even hull boundary[k] = S[h] + 2*S[h-1] + S[h+Z[h-2]-Z[h-1] + sqrt(2)*(2*Z[h-1] + 2*Z[h-2])/2 When k is odd the shape is similar but Z[h] in the middle. S[h] *----* Z[h-1] / \ middle * \ Z[h] S[h-1] | \ * * \ | S[h] Z[h] | + 2*(S[h]-S[h-1]) * \ / Z[h-1] *---* S[h-1] k odd hull boundary[k] = 2*S[h] + 2*S[h-1] + sqrt(2)*(Z[h]/2 + 2*Z[h-1]/2 + Z[h]/2 + S[h]-S[h-1] =head2 Convex Hull Area The area of the convex hull for points N=0 to N=2^k inclusive is / 0 if k=0 | 1/2 if k=1 HA[k] = | 2 if k=2 | 35*2^(k-4) - 13*2^(h-1) + 2 if k odd >=3 \ 35*2^(k-4) - 10*2^(h-1) + 2 if k even >=4 where h = floor(k/2) = 0, 1/2, 2, 13/2, 17, 46, 102, 230, 482, 1018, 2082, 4274, ... HA[1] and HA[3] are fractions but all others are integers. The area can be calculated from the shapes shown for the hull boundary above. For k odd it can be noted the width and height are equal, then the various corners are cut off. =head2 Line Points The number of points which fall on straight and diagonal lines from the endpoints can be calculated by considering how the previous level duplicates to make the next. d d c \ / c b | + | b \ | / \ | / curve endpoints \ | / \ | / "S" start \|/ \|/ "E" end a------E---e---S------a /|\ /|\ / | \ / | \ / | f f | \ h g g h The curve is rotated to make the endpoints horizontal. Each "a" through "h" is the number of points which fall on the respective line. The curve is symmetric in left to right so the line counts are the same each side in mirror image. "S" start and "E" end points are not included in any of the counts. "e" is the count in between S and E. The two "d" lines meet at point "+" and that point is counted in d. That point is where two previous level curves meet for kE=1. Points are visited up to 4 times (per L above) and all those multiple visits are counted. The following diagram shows how curve level k+1 is made from two level k curves. One is from S to M and another M to E. |\ /| curve level k copies | \ / | S to M and M to E | c+a c+a | making curve k+1 S to E | \|/ | \ | --M-- | / \ | /|\ | c a[k+1] = b[k] c d e+g e+g d / b[k+1] = c[k] \ | / \ | / c[k+1] = d[k] \|/ \|/ d[k+1] = a[k]+c[k] + e[k]+g[k] + 1 b-------E--f---f--S-------b e[k+1] = 2*f[k] /|\ /|\ f[k+1] = g[k] a | g g | a g[k+1] = h[k] / h \ / h \ h[k+1] = a[k] / | \ / | \ / | | \ For example the line S to M is an e[k], but also the M to E contributes a g[k] on that same line so e+g. Similarly c[k] and a[k] on the outer sides of M. Point M itself is visited too so the grand total for d[k+1] is a+c+e+g+1. The other lines are simpler, being just rotations except for the middle line e[k+1] which is made of two f[k]. The successive g[k+1]=h[k]=a[k-1]=b[k-2]=c[k-3]=d[k-4] can be substituted into the d to give a recurrence d[k+1] = d[k-1] + d[k-3] + d[k-5] + 2*d[k-7] + 1 = 0,1,1,2,2,4,4,8,8,17,17,34,34,68,68,136,136,273,273,... x + x^2 (common factor 1+x generating function ------------------- in numerator and (1-2*x^2) * (1-x^8) denominator) d[2h-1] = d[2h] = floor( 8/15 * 2^h ) =cut # GP-DEFINE gd(x)=(x+x^2) / ( (1-2*x^2)*(1-x^8) ) # GP-Test gd(x) == (x+x^2) / ( (1-x^2)*(1+x^2)*(1-2*x^2)*(1+x^4) ) # GP-Test Vec(gd(x) - O(x^19)) == /* sans initial 0s */ \ # GP-Test [1,1,2,2,4,4,8,8,17,17,34,34,68,68,136,136,273,273] # GP-DEFINE vector_modulo(v,i) = v[(i% #v)+1]; # GP-DEFINE d_by_powers(k) = \ # GP-DEFINE 8/15*2^ceil(k/2) - 1/15*vector_modulo([8,1,1,2,2,4,4,8],k); # GP-Test vector(19,k,k--; d_by_powers(k)) == /* sans initial 0s */ \ # GP-Test [0,1,1,2,2,4,4,8,8,17,17,34,34,68,68,136,136,273,273] # GP-DEFINE d_by_powers(k) = floor(8/15*2^ceil(k/2)); # GP-Test vector(19,k,k--; d_by_powers(k)) == /* sans initial 0s */ \ # GP-Test [0,1,1,2,2,4,4,8,8,17,17,34,34,68,68,136,136,273,273] =pod The recurrence begins with the single segment N=0 to N=1 and the two endpoints are not included so initial all zeros a[0]=...=h[0]=0. As an example, the N=0 to N=64 picture above is level k=6 and its "d" line relative to those endpoints is the South-West diagonal down from N=0. The points on that line are N=32,30,40,42 giving d[6]=4. All the measures are relative to the endpoint direction. The points on the fixed X or Y axis or diagonal can be found by taking the appropriate a through h, or sum of two of them for both positive and negative of a direction. =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include =over L (etc) =back A332251 X coordinate A332252 Y coordinate A010059 abs(dX), count1bits(N)+1 mod 2 A010060 abs(dY), count1bits(N) mod 2, being Thue-Morse A000120 direction, being total turn, count 1-bits A179868 direction 0to3, count 1-bits mod 4 A035263 turn 0=straight or 180, 1=left or right, being (count low 0-bits + 1) mod 2 A096268 next turn 1=straight or 180, 0=left or right, being count low 1-bits mod 2 A007814 turn-1 to the right, being count low 0-bits A003159 N positions of left or right turn, ends even num 0 bits A036554 N positions of straight or 180 turn, ends odd num 0 bits A146559 X at N=2^k, being Re((i+1)^k) A009545 Y at N=2^k, being Im((i+1)^k) A131064 right boundary length to odd power N=2^(2k-1), being 5*2^n-4n-4, skip initial 1 A027383 right boundary length differences A038503 number of East segments in N=0 to N=2^k-1 A038504 number of North segments in N=0 to N=2^k-1 A038505 number of West segments in N=0 to N=2^k-1 A000749 number of South segments in N=0 to N=2^k-1 A191689 fractal dimension of the boundary (including holes) A191689 is the fractal dimension which roughly speaking means what power r^k the boundary length grows by when each segment is taken as a little triangle (or similar). There are various holes inside the curling spiralling curve and they are all boundary for this purpose. =over P. Duvall and J. Keesling, "The Dimension of the Boundary of the LE<233>vy Dragon", International Journal Math and Math Sci, volume 20, number 4, 1997, pages 627-632. (Preprint "The Hausdorff Dimension of the Boundary of the LE<233>vy Dragon" L.) =back =head1 HOUSE OF GRAPHS House of Graphs entries for the C curve as a graph include =over L etc =back 19655 level=0 (1-segment path) 32234 level=1 (2-segment path) 286 level=2 (4-segment path) 414 level=3 (8-segment path) 33785 level=4 33787 level=5 33789 level=6 33791 level=7 33793 level=8 =head1 SEE ALSO L, L, L, L L back-end for L which displays the C curve (and various other dragon curve and Koch curves). =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/KochSnowflakes.pm0000644000175000017500000004612613734026667020527 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=KochSnowflakes --lines --scale=10 # # area approaches sqrt(48)/10 # * height=sqrt(1-1/4)=sqrt(3)/2 # /|\ halfbase=1/2 # / | \ trianglearea = sqrt(3)/4 # *-----* # segments = 3*4^level = 3,12,48,192,... # # with initial triangle area=1 # add a new triangle onto each side # x,y scale by 3* so 9*area # # area[level+1] = 9*area[level] + segments # = 9*area[level] + 3*4^level # area[0] = 1 # area[1] = 9*area[0] + 3 = 9 + 3 = 12 # area[2] = 9*area[1] + 3*4 # = 9*(9*1 + 3) + 3*4 # = 9*9 + 3*9 + 3*4 = 120 # area[3] = 9*area[2] + 3*4 # = 9*(9*9 + 3*9 + 3*4) + 3*4^2 # = 9^3 + 3*9^2 + 3*0*4 + 3*4^2 # area[level+1] # = 9^(level+1) + (9^(level+1) - 4^(level+1)) * 3/5 # = (5*9^(level+1) + 3*9^(level+1) - 3*4^(level+1)) / 5 # = (8*9^(level+1) - 3*4^(level+1)) / 5 # # area[level] = (8*9^level - 3*4^level) / 5 # = 1,12,120,1128,10344,93864,847848 # # . # / \ area[0] = 1 # .---. # # . # / \ area=[1] = 12 = 9*area[0] + 3*4^0 # .---.---.---. # \ / \ / \ / # .---.---. # / \ / \ / \ # .---.---.---. # \ / # . # # area[level] / 9^level # = (8*9^level / 9^level - 3*4^level / 9^level) / 5 # = (8 - 3*(4/0)^level)/5 # -> 8/5 as level->infinity # in integer coords # initial triangle area # * 2/3 1*2 / 2 = 1 unit # / \ # *---* -1/3 # -1 +1 # # so area[level] / (sqrt(3)/2) package Math::PlanePath::KochSnowflakes; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow'; use Math::PlanePath::KochCurve; # uncomment this to run the ### lines # use Smart::Comments; use constant n_frac_discontinuity => 0; use constant x_negative_at_n => 1; use constant y_negative_at_n => 1; use constant sumabsxy_minimum => 2/3; # minimum X=0,Y=2/3 use constant rsquared_minimum => 4/9; # minimum X=0,Y=2/3 # maybe: use constant radius_minimum => 2/3; # minimum X=0,Y=2/3 # jump across rings is WSW slope 2, so following maximums use constant dx_maximum => 2; use constant dy_maximum => 1; use constant dsumxy_maximum => 2; use constant ddiffxy_maximum => 2; use constant absdx_minimum => 1; # never vertical use constant dir_maximum_dxdy => (1,-1); # South-East # N=1 gcd(-1, -1/3) = 1/3 # N=2 gcd( 1, -1/3) = 1/3 # N=3 gcd( 0, 2/3) = 2/3 use constant gcdxy_minimum => 1/3; use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new (@_); $self->{'sides'} ||= 3; # default return $self; } # N=1 to 3 3 of, level=1 # N=4 to 15 12 of, level=2 # N=16 to .. 48 of, level=3 # # each loop = 3*4^level # # n_base = 1 + 3*4^0 + 3*4^1 + ... + 3*4^(level-1) # = 1 + 3*[ 4^0 + 4^1 + ... + 4^(level-1) ] # = 1 + 3*[ (4^level - 1)/3 ] # = 1 + (4^level - 1) # = 4^level # # each side = loop/3 # = 3*4^level / 3 # = 4^level # # 6 sides # n_base = 1 + 2*3*4^0 + ... # = 2*4^level - 1 # level = log4 (n+1)/2 ### loop 1: 3* 4**1 ### loop 2: 3* 4**2 ### loop 3: 3* 4**3 # sub _level_to_base { # my ($level) = @_; # return -3*$level + 4**($level+1) - 2; # } # ### level_to_base(1): _level_to_base(1) # ### level_to_base(2): _level_to_base(2) # ### level_to_base(3): _level_to_base(3) sub n_to_xy { my ($self, $n) = @_; ### KochSnowflakes n_to_xy(): $n if ($n < 1) { return; } if (is_infinite($n)) { return ($n,$n); } my $sides = $self->{'sides'}; my ($sidelen, $level) = round_down_pow (($sides == 6 ? ($n+1)/2 : $n), 4); my $base = ($sides == 6 ? 2*$sidelen - 1 : $sidelen); my $rem = $n - $base; ### $level ### $base ### $sidelen ### $rem ### assert: $n >= $base ### assert: $rem >= 0 ### assert: $rem < $sidelen * $sides my $side = int($rem / $sidelen); ### $side ### $rem ### subtract: $side*$sidelen $rem -= $side*$sidelen; ### assert: $side >= 0 && $side < $sides my ($x, $y) = Math::PlanePath::KochCurve->n_to_xy ($rem); ### $x ### $y if ($sides == 3) { my $len = 3**($level-1); if ($side < 1) { ### horizontal rightwards return ($x - 3*$len, -$y - $len); } elsif ($side < 2) { ### right slope upwards return (($x-3*$y)/-2 + 3*$len, # flip vert and rotate +120 ($x+$y)/2 - $len); } else { ### left slope downwards return ((-3*$y-$x)/2, # flip vert and rotate -120 ($y-$x)/2 + 2*$len); } } else { # 3 # 5-----4 # 4 / \ # / \ 2 # 6 o 3 # 5 \ . . / # \ . . / 1 # 1-----2 # 0 # 7 # my $len = 3**$level; $x -= $len; # -y flip vert and offset $y = -$y - $len; if ($side >= 3) { ### rotate 180 ... $x = -$x; # rotate 180 $y = -$y; $side -= 3; } if ($side >= 2) { ### upper right slope upwards ... return (($x+3*$y)/-2, # rotate +120 ($x-$y)/2); } if ($side >= 1) { ### lower right slope upwards ... return (($x-3*$y)/2, # rotate +60 ($x+$y)/2); } ### horizontal ... return ($x,$y); } } # N=1 overlaps N=5 # N=2 overlaps N=7 # +---------+ +---------+ Y=1.5 # | | | | # | +---------+ | Y=7/6 = 1.166 # | | | | # | * 13 | | * 11 | Y=1 # | | | | # | | * 3 | | Y=2/3 = 0.666 # | | | | # +---------+ +---------+ Y=0.5 # | | # +---------+---------+---------+ Y=1/6 = 0.166 # | | O | | --Y=0 # | | | | # | | | | # | * 1 | | * 2 | Y=-1/3 = -0.333 # | | | | # +---------+ +---------+ Y=-3/6 = -0.5 # | | | | # +---------+ +---------+ Y=-5/6 = -0.833 # | | | | # | * 5 | | * 7 | Y=-1 # | | | | # | | | | # +---------+ +---------+ Y=-1.5 # sub xy_to_n { return scalar((shift->xy_to_n_list(@_))[0]); } sub xy_to_n_list { my ($self, $x, $y) = @_; ### KochSnowflakes xy_to_n(): "$x, $y" $x = round_nearest ($x); if (abs($x) <= 1) { if ($x == 0) { my $y6 = 6*$y; if ($y6 >= 1 && $y6 < 7) { # Y = 2/3-1/2=1/6 to 2/3+1/2=7/6 return 3; } } else { my $y6 = 6*$y; if ($y6 >= -5 && $y6 < 1) { # Y = -1/3-1/2=-5/6 to -1/3+1/2=+1/6 return (1 + ($x > 0), ($y6 < -3 ? (5+2*($x>0)) : ())); # 5 or 7 up to Y<-1/2 } } } $y = round_nearest ($y); if (($x % 2) != ($y % 2)) { ### diff parity... return; } my $high; if ($x > 0 && $x >= -3*$y) { ### right upper third n=2 ... ($x,$y) = ((3*$y-$x)/2, # rotate -120 and flip vert ($x+$y)/2); $high = 2; } elsif ($x <= 0 && 3*$y > $x) { ### left upper third n=3 ... ($x,$y) = (($x+3*$y)/-2, # rotate +120 and flip vert ($y-$x)/2); $high = 3; } else { ### lower third n=1 ... $y = -$y; # flip vert $high = 1; } ### rotate/flip is: "$x,$y" if ($y <= 0) { return; } my ($len,$level) = round_down_pow($y, 3); $level += 1; ### $level ### $len if (is_infinite($level)) { return $level; } $y -= $len; # shift to Y=0 basis $len *= 3; ### compare for end: ($x+$y)." >= 3*len=".$len if ($x + $y >= $len) { ### past end of this level, no points ... return; } $x += $len; # shift to X=0 basis my $n = Math::PlanePath::KochCurve->xy_to_n($x, $y); ### plain curve on: ($x+3*$len).",".($y-$len)." n=".(defined $n && $n) ### $high ### high: (4**$level)*$high if (defined $n) { return (4**$level)*$high + $n; } else { return; } } # level extends to x= +/- 3^level # y= +/- 2*3^(level-1) # = 2/3 * 3^level # 1.5*y = 3^level # # ENHANCE-ME: share KochCurve segment checker to find actual min/max # # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### KochSnowflakes rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; # # | # +------ . -----+ # |x1,y2 /|\ x2,y2| # / | \ # / | \ # -----/---m---\----- # / | \ # .-----------. # | # y1 # ------- # # -y1 bottom horizontal # (x2+y2)/2 right side # (-x1+y2)/2 left side # each giving a power of 3 of the level # ### right: ($x2+$y2)/2 ### left: (-$x1+$y2)/2 ### bottom: -$y1 my $sides = $self->{'sides'}; my ($pow, $level) = round_down_pow (max ($sides == 6 ? ($x1/-2, $x2/2, -$y1, $y2) : (int(($x2+$y2)/2), int((-$x1+$y2)/2), -$y1)), 3); ### $level # end of $level is 1 before base of $level+1 return (1, 4**($level+2) - 1); } #------------------------------------------------------------------------------ # Nstart = 4^k # length = 3*4^k many points # Nend = Nstart + length-1 # = 4^k + 3*4^k - 1 # = 4*4^k - 1 # = Nstart(k+1) - 1 sub level_to_n_range { my ($self, $level) = @_; my $pow = 4**$level; return ($pow, 4*$pow-1); } sub n_to_level { my ($self, $n) = @_; if ($n < 1) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($sidelen, $level) = round_down_pow (($self->{'sides'} == 6 ? ($n+1)/2 : $n), 4); return $level; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde ie SVG Math-PlanePath Ylo OEIS =head1 NAME Math::PlanePath::KochSnowflakes -- Koch snowflakes as concentric rings =head1 SYNOPSIS use Math::PlanePath::KochSnowflakes; my $path = Math::PlanePath::KochSnowflakes->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path traces out concentric integer versions of the Koch snowflake at successively greater iteration levels. 48 6 / \ 50----49 47----46 5 \ / 54 51 45 42 4 / \ / \ / \ 56----55 53----52 44----43 41----40 3 \ / 57 12 39 2 / / \ \ 58----59 14----13 11----10 37----38 1 \ \ 3 / / 60 15 1----2 9 36 <- Y=0 / \ \ 62----61 4---- 5 7---- 8 35----34 -1 \ \ / / 63 6 33 -2 \ 16----17 19----20 28----29 31----32 -3 \ / \ / \ / 18 21 27 30 -4 / \ 22----23 25----26 -5 \ / 24 -6 ^ -9 -8 -7 -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 7 8 9 The initial figure is the triangle N=1,2,3 then for the next level each straight side expands to 3x longer and a notch like N=4 through N=8, *---* becomes *---* *---* \ / * The angle is maintained in each replacement, for example the segment N=5 to N=6 becomes N=20 to N=24 at the next level. =head2 Triangular Coordinates The X,Y coordinates are arranged as integers on a square grid per L, except the Y coordinates of the innermost triangle which is N=3 X=0, Y=+2/3 * / \ / \ / \ / o \ / \ N=1 *-----------* N=2 X=-1, Y=-1/3 X=1, Y=-1/3 These values are not integers, but they're consistent with the centring and scaling of the higher levels. If all-integer is desired then rounding gives Y=0 or Y=1 and doesn't overlap the subsequent points. =head2 Level Ranges Counting the innermost triangle as level 0, each ring is Nstart = 4^level length = 3*4^level many points For example the outer ring shown above is level 2 starting N=4^2=16 and having length=3*4^2=48 points (through to N=63 inclusive). The X range at a given level is the initial triangle baseline iterated out. Each level expands the sides by a factor of 3 so Xlo = -(3^level) Xhi = +(3^level) For example level 2 above runs from X=-9 to X=+9. The Y range is the points N=6 and N=12 iterated out. Ylo in level 0 since there's no downward notch on that innermost triangle. Ylo = / -(2/3)*3^level if level >= 1 \ -1/3 if level == 0 Yhi = +(2/3)*3^level Notice that for each level the extents grow by a factor of 3 but the notch introduced in each segment is not big enough to go past the corner positions. They can equal the extents horizontally, for example in level 1 N=14 is at X=-3 the same as the corner N=4, and on the right N=10 at X=+3 the same as N=8, but they don't go past. The snowflake is an example of a fractal curve with ever finer structure. The code here can be used for that by going from N=Nstart to N=Nstart+length-1 and scaling X/3^level Y/3^level to give a 2-wide 1-high figure of desired fineness. See F for a complete program doing that as an SVG image file. =head2 Area The area of the snowflake at a given level can be calculated from the area under the Koch curve per L which is the 3 sides, and the central triangle * ^ Yhi / \ | height = 3^level / \ | / \ | *-------* v <-------> width = 3^level - (- 3^level) = 2*3^level Xlo Xhi triangle_area = width*height/2 = 9^level snowflake_area[level] = triangle_area[level] + 3*curve_area[level] = 9^level + 3*(9^level - 4^level)/5 = (8*9^level - 3*4^level) / 5 If the snowflake is conceived as a fractal of fixed initial triangle size and ever-smaller notches then the area is divided by that central triangle area 9^level, unit_snowflake[level] = snowflake_area[level] / 9^level = (8 - 3*(4/9)^level) / 5 -> 8/5 as level -> infinity Which is the well-known 8/5 * initial triangle area for the fractal snowflake. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::KochSnowflakes-Enew ()> Create and return a new path object. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return per L
above, (4**$level, 4**($level+1) - 1) =back =head1 FORMULAS =head2 Rectangle to N Range As noted in L above, for a given level -(3^level) <= X <= 3^level -(2/3)*(3^level) <= Y <= (2/3)*(3^level) So the maximum X,Y in a rectangle gives level = ceil(log3(max(abs(x1), abs(x2), abs(y1)*3/2, abs(y2)*3/2))) and the last point in that level is Nlevel = 4^(level+1) - 1 Using this as an N range is an over-estimate, but an easy calculation. It's not too difficult to trace down for an exact range =head1 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to the Koch snowflake include the following. See L for entries related to a single Koch side. =over L (etc) =back A164346 number of points in ring n, being 3*4^n A178789 number of acute angles in ring n, 4^n + 2 A002446 number of obtuse angles in ring n, 2*4^n - 2 The acute angles are those of +/-120 degrees and the obtuse ones +/-240 degrees. Eg. in the outer ring=2 shown above the acute angles are at N=18, 22, 24, 26, etc. The angles are all either acute or obtuse, so A178789 + A002446 = A164346. =head1 SEE ALSO L, L, L L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/HilbertCurve.pm0000644000175000017500000007416113776704613020204 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # http://www.woollythoughts.com/afghans/peano.html # Knitting # # http://www.geom.uiuc.edu/docs/reference/CRC-formulas/node36.html # Closed path, curved parts # # http://www.wolframalpha.com/entities/calculators/Peano_curve/jh/4o/im/ # Curved corners tilted to a diamond, or is it an 8-step pattern? # # http://www.davidsalomon.name/DC2advertis/AppendC.pdf # # http://www.cut-the-knot.org/do_you_know/hilbert.shtml # Java applet # package Math::PlanePath::HilbertCurve; use 5.004; use strict; #use List::Util 'max','min'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'round_up_pow', 'bit_split_lowtohigh', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; #------------------------------------------------------------------------------ # state=0 3--2 plain # | # 0--1 # # state=4 1--2 transpose # | | # 0 3 # # state=8 # # state=12 3 0 rot180 + transpose # | | # 2--1 # # generated by tools/hilbert-curve-table.pl my @next_state = (4,0,0,12, 0,4,4,8, 12,8,8,4, 8,12,12,0); my @digit_to_x = (0,1,1,0, 0,0,1,1, 1,0,0,1, 1,1,0,0); my @digit_to_y = (0,0,1,1, 0,1,1,0, 1,1,0,0, 1,0,0,1); my @yx_to_digit = (0,1,3,2, 0,3,1,2, 2,3,1,0, 2,1,3,0); my @min_digit = (0,0,1,0, 0,1,3,2, 2,undef,undef,undef, 0,0,3,0, 0,2,1,1, 2,undef,undef,undef, 2,2,3,1, 0,0,1,0, 0,undef,undef,undef, 2,1,1,2, 0,0,3,0, 0); my @max_digit = (0,1,1,3, 3,2,3,3, 2,undef,undef,undef, 0,3,3,1, 3,3,1,2, 2,undef,undef,undef, 2,3,3,2, 3,3,1,1, 0,undef,undef,undef, 2,2,1,3, 3,1,3,3, 0); sub n_to_xy { my ($self, $n) = @_; ### HilbertCurve n_to_xy(): $n ### hex: sprintf "%#X", $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; # fraction part my @ndigits = digit_split_lowtohigh($int,4); my $state = ($#ndigits & 1 ? 4 : 0); my $dirstate = ($#ndigits & 1 ? 0 : 4); # default if all $ndigit==3 my (@xbits, @ybits); foreach my $i (reverse 0 .. $#ndigits) { # digits high to low my $ndigit = $ndigits[$i]; $state += $ndigit; if ($ndigit != 3) { $dirstate = $state; # lowest non-3 digit } $xbits[$i] = $digit_to_x[$state]; $ybits[$i] = $digit_to_y[$state]; $state = $next_state[$state]; } my $zero = ($int * 0); # inherit bigint 0 return ($n * ($digit_to_x[$dirstate+1] - $digit_to_x[$dirstate]) # frac + digit_join_lowtohigh (\@xbits, 2, $zero), $n * ($digit_to_y[$dirstate+1] - $digit_to_y[$dirstate]) # frac + digit_join_lowtohigh (\@ybits, 2, $zero)); } sub xy_to_n { my ($self, $x, $y) = @_; ### HilbertCurve xy_to_n(): "$x, $y" $x = round_nearest ($x); if (is_infinite($x)) { return $x; } $y = round_nearest ($y); if (is_infinite($y)) { return $y; } if ($x < 0 || $y < 0) { return undef; } my @xbits = bit_split_lowtohigh($x); my @ybits = bit_split_lowtohigh($y); my $numbits = max($#xbits,$#ybits); my @ndigits; my $state = ($numbits & 1 ? 4 : 0); foreach my $i (reverse 0 .. $numbits) { # high to low ### at: "state=$state xbit=".($xbits[$i]||0)." ybit=".($ybits[$i]||0) my $ndigit = $yx_to_digit[$state + 2*($ybits[$i]||0) + ($xbits[$i]||0)]; $ndigits[$i] = $ndigit; $state = $next_state[$state+$ndigit]; } ### @ndigits return digit_join_lowtohigh(\@ndigits, 4, $x * 0 * $y); # inherit bignum 0 } # rect_to_n_range() finds the exact minimum/maximum N in the given rectangle. # # The strategy is similar to xy_to_n(), except that at each bit position # instead of taking a bit of x,y from the input instead those bits are # chosen from among the 4 sub-parts according to which has the maximum N and # is within the given target rectangle. The final result is both an $n_max # and a $x_max,$y_max which is its position, but only the $n_max is # returned. # # At a given sub-part the comparisons ask whether x1 is above or below the # midpoint, and likewise x2,y1,y2. Since x2>=x1 and y2>=y1 there's only 3 # combinations of x1>=cmp,x2>=cmp, not 4. # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### HilbertCurve rect_to_n_range(): "$x1,$y1, $x2,$y2" $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 0) { return (1, 0); # rectangle outside first quadrant } my $n_min = my $n_max = my $x_min = my $y_min = my $x_max = my $y_max = ($x1 * 0 * $x2 * $y1 * $y2); # inherit bignum 0 my ($len, $level) = round_down_pow (($x2 > $y2 ? $x2 : $y2), 2); ### $len ### $level if (is_infinite($level)) { return (0, $level); } my $min_state = my $max_state = ($level & 1 ? 4 : 0); while ($level >= 0) { { my $x_cmp = $x_min + $len; my $y_cmp = $y_min + $len; my $digit = $min_digit[3*$min_state + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0)]; $n_min = 4*$n_min + $digit; $min_state += $digit; if ($digit_to_x[$min_state]) { $x_min += $len; } if ($digit_to_y[$min_state]) { $y_min += $len; } $min_state = $next_state[$min_state]; } { my $x_cmp = $x_max + $len; my $y_cmp = $y_max + $len; my $digit = $max_digit[3*$max_state + ($x1 >= $x_cmp ? 2 : $x2 >= $x_cmp ? 1 : 0) + ($y1 >= $y_cmp ? 6 : $y2 >= $y_cmp ? 3 : 0)]; $n_max = 4*$n_max + $digit; $max_state += $digit; if ($digit_to_x[$max_state]) { $x_max += $len; } if ($digit_to_y[$max_state]) { $y_max += $len; } $max_state = $next_state[$max_state]; } $len = int($len/2); $level--; } return ($n_min, $n_max); } #------------------------------------------------------------------------------ # shared by Math::PlanePath::AR2W2Curve and others sub level_to_n_range { my ($self, $level) = @_; return (0, 4**$level - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } if (is_infinite($n)) { return $n; } $n = round_nearest($n); my ($pow, $exp) = round_up_pow ($n+1, 4); return $exp; } #------------------------------------------------------------------------------ 1; __END__ =for :stopwords Ryde Math-PlanePath PlanePaths OEIS ZOrderCurve ZOrder Peano Gosper's HAKMEM Jorg Arndt's bitwise bignums fxtbook Ueber stetige Abbildung einer Linie auf ein FlE<228>chenstE<252>ck Mathematische Annalen DOI ascii lookup Arndt PlanePath ie Sergey Kitaev Toufik Mansour Automata Combinatorics Preprint localized =head1 NAME Math::PlanePath::HilbertCurve -- 2x2 self-similar quadrant traversal =head1 SYNOPSIS use Math::PlanePath::HilbertCurve; my $path = Math::PlanePath::HilbertCurve->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This path is an integer version of the curve described by Hilbert in 1891 for filling a unit square. It traverses a quadrant of the plane one step at a time in a self-similar 2x2 pattern, =over David Hilbert, "Ueber die stetige Abbildung einer Linie auf ein FlE<228>chenstE<252>ck", Mathematische Annalen, volume 38, number 3, 1891, pages 459-460, DOI 10.1007/BF01199431. =back =cut # Someone's copy: # https://www.maths.ed.ac.uk/~v1ranick/papers/hilbert.pdf # # Someone's copy existed in the past: # http://notendur.hi.is/oddur/hilbert/gcs-wrapper-1.pdf =pod ... | | 7 | 63--62 49--48--47 44--43--42 | | | | | | 6 | 60--61 50--51 46--45 40--41 | | | | 5 | 59 56--55 52 33--34 39--38 | | | | | | | | 4 | 58--57 54--53 32 35--36--37 | | 3 | 5---6 9--10 31 28--27--26 | | | | | | | | 2 | 4 7---8 11 30--29 24--25 | | | | 1 | 3---2 13--12 17--18 23--22 | | | | | | Y=0 | 0---1 14--15--16 19--20--21 +---------------------------------- X=0 1 2 3 4 5 6 7 The start is a sideways U shape N=0 to N=3, then four of those are put together in an upside-down U as 5,6 9,10 4,7--- 8,11 | | 3,2 13,12 0,1 14,15-- The orientation of the sub parts ensure the starts and ends are adjacent, so 3 next to 4, 7 next to 8, and 11 next to 12. The process repeats, doubling in size each time and alternately sideways or upside-down U with invert and/or transpose as necessary in the sub-parts. The pattern is sometimes drawn with the first step 0->1 upwards instead of to the right. First step right is used here for consistency with other PlanePaths. Swap X and Y for upwards first instead. See F for a sample program printing the path pattern in ascii. =head2 Level Ranges Within a power-of-2 square 2x2, 4x4, 8x8, 16x16 etc (2^k)x(2^k) at the origin, all the N values 0 to 2^(2*k)-1 are within the square. The maximum 3, 15, 63, 255 etc 2^(2*k)-1 is alternately at the top left or bottom right corner. Because each step is by 1, the distance along the curve between two X,Y points is the difference in their N values (as from C). On the X=Y diagonal N=0,2,8,10,32,etc is the integers using only digits 0 and 2 in base 4, or equivalently have even-numbered bits 0, like x0y0...z0. =head2 Locality The Hilbert curve is fairly well localized in the sense that a small rectangle (or other shape) is usually a small range of N. This property is used in some database systems to store X,Y coordinates using the resulting Hilbert curve N as an index. A search through a 2-D region is then usually a fairly modest linear search through N values. C gives exact N range for a rectangle, or see notes L below for calculating on any shape. The N range can be large when crossing sub-parts. In the sample above it can be seen for instance adjacent points X=0,Y=3 and X=0,Y=4 have rather widely spaced N values 5 and 58. Fractional X,Y values can be indexed by extending the N calculation down into X,Y binary fractions. The code here doesn't do that, but could be pressed into service by moving the binary point in X and Y an even number of places, the same in each. (An odd number of bits would require swapping X,Y to compensate for the alternating transpose in part 0.) The resulting integer N is then divided down by a corresponding multiple-of-4 binary places. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::HilbertCurve-Enew ()> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. Integer positions are always just 1 apart either horizontally or vertically, so the effect is that the fraction part is an offset along either C<$x> or C<$y>. =item C<$n = $path-Exy_to_n ($x,$y)> Return an integer point number for coordinates C<$x,$y>. Each integer N is considered the centre of a unit square and an C<$x,$y> within that square returns N. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 4**$level - 1)>. =back =head1 FORMULAS =head2 N to X,Y Converting N to X,Y coordinates is reasonably straightforward. The top two bits of N is a configuration 3--2 1--2 | or transpose | | 0--1 0 3 according to whether it's an odd or even bit-pair position. Then within each of the "3" sub-parts there's also inverted forms 1--0 3 0 | | | 2--3 2--1 Working N from high to low with a state variable can record whether there's a transpose, an invert, or both, being four states altogether. A bit pair 0,1,2,3 from N then gives a bit each of X,Y according to the configuration and a new state which is the orientation of that sub-part. Bill Gosper's HAKMEM item 115 has this with either bit operations or a table for the state and X,Y bits, =over L, L =back XAnd C++ code based on that in Jorg Arndt's book, =over L (section 1.31.1) =back It also works to process N from low to high, at each stage applying any transpose (swap X,Y) and/or invert (bitwise NOT) to the low X,Y bits generated so far. This works because there's no "reverse" sections, or equivalently the curve is the same forward and reverse. Low to high saves locating the top bits of N, but if using bignums then the bitwise inverts of the full X,Y values will be much more work. =head2 X,Y to N X,Y to N can follow the table approach from high to low taking one bit from X and Y each time. The state table of N-pair -> X-bit,Y-bit is reversible, and a new state is based on the N-pair thus obtained (or could be based on the X,Y bits if that mapping is combined into the state transition table). =head2 Rectangle to N Range An easy over-estimate of the maximum N in a region can be had by finding the next bigger (2^k)x(2^k) square enclosing the region. This means the biggest X or Y rounded up to the next power of 2, so find lowest k with 2^k > max(X,Y) N_max = 2^(2k) - 1 Or equivalently rounding down to the next lower power of 2, find highest k with 2^k <= max(X,Y) N_max = 2^(2*(k+1)) - 1 An exact N range can be found by following the high to low N to X,Y procedure above. Start at the 2^(2k) bit pair position in an N bigger than the desired region and choose 2 bits for N to give a bit each of X and Y. The X,Y bits are based on the state table as above and the bits chosen for N are those for which the resulting X,Y sub-square overlaps some of the target region. The smallest N similarly, choosing the smallest bit pair for N which overlaps. The biggest and smallest N digit for a sub-part can be found with a lookup table. The X range might cover one or both sub-parts, and the Y range similarly, for a total 4 possible configurations. Then a table of state+coverage -E digit gives the minimum and maximum N bit-pair, and state+digit gives a new state the same as X,Y to N. Biggest and smallest N must be calculated with separate state and X,Y values since they track down different N bits and thus different states. But they take the same number of steps from an enclosing level down to level 0 and can thus be done in a single loop. The N range for any shape can be found this way, not just a rectangle like C. At each level the procedure only depends on asking which of the four sub-parts overlaps some of the target area. =head2 Direction Each step between successive N values is always 1 up, down, left or right. The next direction can be calculated from N in the high-to-low procedure above by watching for the lowest non-3 digit and noting the direction from that digit towards digit+1. That can be had from the state+digit -E X,Y table looking up digit and digit+1, or alternatively a further table encoding state+digit -E direction. The reason for taking only the lowest non-3 digit is that in a 3 sub-part the direction it goes is determined by the next higher level. For example at N=11 the direction is down for the inverted-U of the next higher level N=0,4,8,12. This non-3 (or non whatever highest digit) is a general procedure and can be used on any state-based high-to-low procedure of self-similar curves. In the current code, it's used to apply a fractional part of N in the correct direction but is not otherwise made directly available. Because the Hilbert curve has no "reverse" sections it also works to build a direction from low to high N digits. 1 and 2 digits make no change to the orientation, 0 digit is a transpose, and a 3 digit is a rotate and transpose, except that low 3s are transpose-only (no rotate) for the same reason as taking the lowest non-3 above. Jorg Arndt in the fxtbook above notes the direction can be obtained just by counting 3s in n and -n (the twos-complement). This numbers segments starting n=1, unlike PlanePath here starting N=0, so it becomes N+1 count 3s / 0 mod 2 S or E \ 1 mod 2 N or W -(N+1) count 3s / 0 mod 2 N or E \ 1 mod 2 S or W For the twos-complement negation, an even number of base-4 digits of N must be taken. Because -(N+1) = ~N, ie. a ones-complement, the second part is also N count 0s / 0 mod 2 N or E in even num digits \ 1 mod 2 S or W Putting the two together then N count 0s N+1 count 3s direction (0=E,1=N,etc) in base 4 in base 4 0 mod 2 0 mod 2 0 1 mod 2 0 mod 2 3 0 mod 2 1 mod 2 1 1 mod 2 1 mod 2 2 =head2 Segments in Direction The number of segments in each direction is calculated in =over Sergey Kitaev, Toufik Mansour and Patrice SE<233>E<233>bold, "Generating the Peano Curve and Counting Occurrences of Some Patterns", Journal of Automata, Languages and Combinatorics, volume 9, number 4, 2004, pages 439-455. L, L (Preprint as Sergey Kitaev and Toufik Mansour, "The Peano Curve and Counting Occurrences of Some Patterns", October 2002. L.) =cut # http://personal.strath.ac.uk/sergey.kitaev/ # http://personal.strath.ac.uk/sergey.kitaev/publications.html # https://www.researchgate.net/publication/230802620_Generating_the_Peano_curve_and_counting_occurrences_of_some_patterns # full text html # https://www.researchgate.net/profile/Toufik_Mansour/publication/230802620_Generating_the_Peano_curve_and_counting_occurrences_of_some_patterns/links/00b7d5299d5a89bb02000000/Generating-the-Peano-curve-and-counting-occurrences-of-some-patterns.pdf # # https://personal.cis.strath.ac.uk/sergey.kitaev/publications.html # old home # https://personal.cis.strath.ac.uk/sergey.kitaev/index_files/Papers/peano.ps # old postscript # http://web.archive.org/web/1id_/http://personal.cis.strath.ac.uk/sergey.kitaev/index_files/Papers/peano.ps # not in wayback # # http://www.math.chalmers.se/Math/Research/Combinatorics/preprints/kitaev/kitmansel.ps # 216253 # https://hal.archives-ouvertes.fr/hal-01645132 # journal ref only (?) # # http://www.jalc.de/issues/2004/issue_9_4/jalc-2004-439-455.php # http://www.jalc.de/issues/2004/issue_9_4/fulltext/pp-439-455.pdf # https://doi.org/10.25596/jalc-2004-439 # account required # # http://www.ms.uky.edu/~kitaev/ [no such ?] # http://www.ms.uky.edu/~readdy/DM/DM-RESEARCH/kitaev_research.html # # Toufik Mansour, list only no links: # http://math.haifa.ac.il/toufik/manpubff.html =pod =back Their form is based on keeping the top-most U shape fixed and expanding sub-parts. This means the end segments alternate vertical and horizontal in successive expansion levels. direction k=1 2 2 1 to 4 *---* *---* 2 1| 3| |1 |3 1 *---* * *---* * | 1| |3 1| 4 2 4 |3 4--- ---2 * * *---* *---* | 1| |3 k=2 3 *---* *---* 2 2 count segments in direction, for k >= 1 d(1,k) = 4^(k-1) = 1,4,16,64,256,1024,4096,... d(2,k) = 4^(k-1) + 2^(k-1) - 1 = 1,5,19,71,271,1055,4159,... d(3,k) = 4^(k-1) = 1,4,16,64,256,1024,4096,... d(4,k) = 4^(k-1) - 2^(k-1) = 0,2,12,56,240, 992,4032,... (A000302, A099393, A000302, A020522) total segments d(1,k)+d(2,k)+d(3,k)+d(4,k) = 4^k - 1 The path form here keeps the first segment direction fixed. This means a transpose 1E-E2 and 3E-E4 in odd levels. The result is to take the alternate d values as follows. For k=0 there is a single point N=0 so no line segments at all and so c(dir,0)=0. first 4^k-1 segments c(1,k) = / 0 if k=0 North | 4^(k-1) + 2^(k-1) - 1 if k odd >= 1 \ 4^(k-1) if k even >= 2 = 0, 1, 4, 19, 64, 271, 1024, 4159, 16384, ... c(2,k) = / 0 if k=0 East | 4^(k-1) if k odd >= 1 \ 4^(k-1) + 2^(k-1) - 1 if k even >= 2 = 0, 1, 5, 16, 71, 256, 1055, 4096, 16511, ... c(3,k) = / 0 if k=0 South | 4^(k-1) - 2^(k-1) if k odd >= 1 \ 4^(k-1) if k even >= 2 = 0, 0, 4, 12, 64, 240, 1024, 4032, 16384, ... c(4,k) = / 0 if k=0 West | 4^(k-1) if k odd >= 1 \ 4^(k-1) - 2^(k-1) if k even >= 2 = 0, 1, 2, 16, 56, 256, 992, 4096, 16256, ... =cut # not in OEIS: 4, 19, 64, 271, 1024, 4159, 16384 # not in OEIS: 5, 16, 71, 256, 1055, 4096, 16511 # not in OEIS: 4, 12, 64, 240, 1024, 4032, 16384 # not in OEIS: 2, 16, 56, 256, 992, 4096, 16256 =pod The segment N=4^k-1 to N=4^k is North (direction 1) when k odd, or East (direction 2) when k even. That could be added to the respective cases in c(1,k) and c(2,k) if desired. =cut # (d1(k) = 4^(k-1)); for(k=0,8,print1(d1(k),",")) # (d2(k) = 4^(k-1) + 2^(k-1) - 1); for(k=0,8,print1(d2(k),",")) # (d3(k) = 4^(k-1)); for(k=0,8,print1(d3(k),",")) # (d4(k) = 4^(k-1) - 2^(k-1)); for(k=0,8,print1(d4(k),",")) # (c1(k) = if(k==0,0,if(k%2,d2(k),d1(k)))); for(k=0,8,print1(c1(k),", ")) # (c2(k) = if(k==0,1,if(k%2,d1(k),d2(k)))); for(k=0,8,print1(c2(k),", ")) # (c3(k) = if(k==0,0,if(k%2,d3(k),d4(k)))); for(k=0,8,print1(c3(k),", ")) # (c4(k) = if(k==0,0,if(k%2,d4(k),d3(k)))); for(k=0,8,print1(c4(k),", ")) # # N=0 to N=4^k so first 4^k segments # (east4k(k) = c2(k) + if(k>=2&&k%2==0,1,0)); for(k=0,8,print1(east4k(k),", ")) # 1,1,6,16,72,256,1056,4096,16512, # 1,1,6,16,72,256,1056,4096 =pod =head2 Hamming Distance The Hamming distance between integers X and Y is the number of bit positions where the two values differ when written in binary. On the Hilbert curve each bit-pair of N becomes a bit of X and a bit of Y, N X Y ------ --- --- 0 = 00 0 0 1 = 01 1 0 <- difference 1 bit 2 = 10 1 1 3 = 11 0 1 <- difference 1 bit So the Hamming distance for N=0to3 is 1 at N=1 and N=3. At higher levels, these X,Y bits may be transposed (swapped) or rotated by 180 or both. A transpose swapping XE-EY doesn't change the bit difference. A rotate by 180 is a flip 0E-E1 of the bit in both X and Y, so that doesn't change the bit difference either. On that basis, the Hamming distance X,Y is the number of base4 digits of N which are 01 or 11. If bit positions are counted from 0 for the least significant bit then X,Y coordinates of N HammingDist(X,Y) = count 1-bits at even bit positions in N = 0,1,0,1, 1,2,1,2, 0,1,0,1, 1,2,1,2, ... (A139351) See also L which is the same formula, but arising directly from 01 or 11, no transpose or rotate. =cut # (d1(k) = 4^(k-1)); for(k=0,8,print1(d1(k),",")) # (d2(k) = 4^(k-1) + 2^(k-1) - 1); for(k=0,8,print1(d2(k),",")) # (d3(k) = 4^(k-1)); for(k=0,8,print1(d3(k),",")) # (d4(k) = 4^(k-1) - 2^(k-1)); for(k=0,8,print1(d4(k),",")) # (c1(k) = if(k==0,0,if(k%2,d1(k),d2(k)))); for(k=0,8,print1(c1(k),", ")) # (c2(k) = if(k==0,1,if(k%2,d2(k),d1(k)))); for(k=0,8,print1(c2(k),", ")) # (c3(k) = if(k==0,0,if(k%2,d3(k),d4(k)))); for(k=0,8,print1(c3(k),", ")) # (c4(k) = if(k==0,0,if(k%2,d4(k),d3(k)))); for(k=0,8,print1(c4(k),", ")) =pod =head1 OEIS This path is in Sloane's OEIS in several forms, =over L (etc) =back A059253 X coord A059252 Y coord A059261 X+Y A059285 X-Y A163547 X^2+Y^2 = radius squared A139351 HammingDist(X,Y) A059905 X xor Y, being ZOrderCurve X A163365 sum N on diagonal A163477 sum N on diagonal, divided by 4 A163482 N values on X axis A163483 N values on Y axis A062880 N values on diagonal X=Y (digits 0,2 in base 4) A163538 dX -1,0,1 change in X A163539 dY -1,0,1 change in Y A163540 absolute direction of each step (0=E,1=N,2=W,3=S) A163541 absolute direction, swapped X,Y A163542 relative direction (ahead=0,right=1,left=2) A163543 relative direction, swapped X,Y A083885 count East segments N=0 to N=4^k (first 4^k segs) A163900 distance dX^2+dY^2 between Hilbert and ZOrder A165464 distance dX^2+dY^2 between Hilbert and Peano A165466 distance dX^2+dY^2 between Hilbert and transposed Peano A165465 N where Hilbert and Peano have same X,Y A165467 N where Hilbert and Peano have transposed same X,Y The following take points of the plane in various orders, each value in the sequence being the N of the Hilbert curve at those positions. A163355 N by the ZOrderCurve points sequence A163356 inverse, ZOrderCurve by Hilbert points order A166041 N by the PeanoCurve points sequence A166042 inverse, PeanoCurve N by Hilbert points order A163357 N by diagonals like Math::PlanePath::Diagonals with first Hilbert step along same axis the diagonals start A163358 inverse A163359 N by diagonals, transposed start along the opposite axis A163360 inverse A163361 A163357 + 1, numbering the Hilbert N's from N=1 A163362 inverse A163363 A163355 + 1, numbering the Hilbert N's from N=1 A163364 inverse The above sequences are permutations of the integers since all X,Y positions of the first quadrant are covered by each path (Hilbert, ZOrder, Peano). The inverse permutations can be thought of taking X,Y positions in the Hilbert order and asking what N the ZOrder, Peano or Diagonals path would put there. The A163355 permutation by ZOrderCurve can be considered for repeats or cycles, A163905 ZOrderCurve permutation A163355 applied twice A163915 ZOrderCurve permutation A163355 applied three times A163901 fixed points (N where X,Y same in both curves) A163902 2-cycle points A163903 3-cycle points A163890 cycle lengths, points by N A163904 cycle lengths, points by diagonals A163910 count of cycles in 4^k blocks A163911 max cycle length in 4^k blocks A163912 LCM of cycle lengths in 4^k blocks A163914 count of 3-cycles in 4^k blocks A163909 those counts for even k only A163891 N of previously unseen cycle length A163893 first differences of those A163891 A163894 smallest value not an n-cycle A163895 position of new high in A163894 A163896 value of new high in A163894 A163907 ZOrderCurve permutation twice, on points by diagonals A163908 inverse of this See F for a sample program printing the A163359 permutation values. =head1 SEE ALSO L, L, L L, L, L, L L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut # Local variables: # compile-command: "math-image --path=HilbertCurve --lines --scale=20" # End: # math-image --path=HilbertCurve --all --output=numbers_dash --size=70x30 Math-PlanePath-129/lib/Math/PlanePath/AlternatePaper.pm0000644000175000017500000012636713774703373020523 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # ENHANCE-ME: Explanation for this bit ... # 'arms=4' => # { dSum => 'A020985', # GRS # # OEIS-Other: A020985 planepath=AlternatePaper,arms=4 delta_type=dSum # }, package Math::PlanePath::AlternatePaper; use 5.004; use strict; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh', 'bit_split_lowtohigh'; *_divrem = \&Math::PlanePath::_divrem; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_array => [ { name => 'arms', share_key => 'arms_8', display => 'Arms', type => 'integer', minimum => 1, maximum => 8, default => 1, width => 1, description => 'Arms', } ]; use constant n_start => 0; sub x_negative { my ($self) = @_; return ($self->{'arms'} >= 3); } sub y_negative { my ($self) = @_; return ($self->{'arms'} >= 5); } { my @x_negative_at_n = (undef, undef,undef,8,7, 4,4,4,4); sub x_negative_at_n { my ($self) = @_; return $x_negative_at_n[$self->{'arms'}]; } } { my @y_negative_at_n = (undef, undef,undef,undef,undef, 44,23,13,14); sub y_negative_at_n { my ($self) = @_; return $y_negative_at_n[$self->{'arms'}]; } } sub sumxy_minimum { my ($self) = @_; return ($self->arms_count <= 3 ? 0 # 1,2,3 arms above X=-Y diagonal : undef); } sub diffxy_minimum { my ($self) = @_; return ($self->arms_count == 1 ? 0 # 1 arms right of X=Y diagonal : undef); } use constant turn_any_straight => 0; # never straight #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); $self->{'arms'} = max(1, min(8, $self->{'arms'} || 1)); return $self; } { # <------ # state=0 /| +----+----+ # (dir=0) / | |\ 1||<--/ # /2 | |^\ || 0/ # /-->| || \v| / # +----+ ||3 \|/ # /|\ 3|| +----+ # / |^\ || |<--/ state=4 # / 0|| \v| | 2/ (dir=2) # /-->||1 \| | / # +----+----+ |/ # --------> # # |\ state=8 +----+----+ state=12 # ^ |^\ (dir=1) \ 1||<--/| | (dir=3) # | || \ \ || 0/ | | # | ||3 \ \v| /2 | | # | +----+ \|/-->| | # | |<--/|\ +----+ | # | | 2/ |^\ \ 3|| | # | | /0 || \ \ || | # | |/-->||1 \ \v| v # +----+----+ \| my @next_state = (0, 8, 0, 12, # forward 4, 12, 4, 8, # forward NW 0, 8, 4, 8, # reverse 4, 12, 0, 12, # reverse NE ); my @digit_to_x = (0,1,1,1, 1,0,0,0, 0,1,0,0, 1,0,1,1, ); my @digit_to_y = (0,0,1,0, 1,1,0,1, 0,0,0,1, 1,1,1,0, ); # state_to_dx[S] == state_to_x[S+3] - state_to_x[S+0] my @state_to_dx = (1, -1, 0, 0); my @state_to_dy = (0, 0, 1, -1); sub n_to_xy { my ($self, $n) = @_; ### AlternatePaper n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n, $n); } my $int = int($n); # integer part $n -= $int; # fraction part ### $int ### $n my $zero = ($int * 0); # inherit bignum 0 my $arm = _divrem_mutate ($int, $self->{'arms'}); ### $arm ### $int my @digits = digit_split_lowtohigh($int,4); my $state = 0; my (@xbits,@ybits); # bits low to high (like @digits) foreach my $i (reverse 0 .. $#digits) { # high to low $state += $digits[$i]; $xbits[$i] = $digit_to_x[$state]; $ybits[$i] = $digit_to_y[$state]; $state = $next_state[$state]; } my $x = digit_join_lowtohigh(\@xbits,2,$zero); my $y = digit_join_lowtohigh(\@ybits,2,$zero); # X+1,Y+1 for final state=4 or state=12 $x += $digit_to_x[$state]; $y += $digit_to_y[$state]; ### final: "xy=$x,$y state=$state" # apply possible fraction part of $n in direction of $state $x = $n * $state_to_dx[$state >>= 2] + $x; $y = $n * $state_to_dy[$state] + $y; # rotate,transpose for arm number if ($arm & 1) { ($x,$y) = ($y,$x); # transpose } if ($arm & 2) { ($x,$y) = (-$y,$x+1); # rotate +90 and shift origin to X=0,Y=1 } if ($arm & 4) { $x = -1 - $x; # rotate +180 and shift origin to X=-1,Y=1 $y = 1 - $y; } ### rotated return: "$x,$y" return ($x,$y); } } # 8 # # 42 43 7 # # 40 41/45 44 6 # # 34 35/39 38/46 47 5 # # 32-33/53-36/52-37/49---48 4 # | \ # 10 11/31 30/54 51/55 50/58 59 3 # | \ # 8 9/13 12/28 25/29 24/56 57/61 60 2 # | \ # 2 3/7 6/14 15/27 18/26 19/23 22/62 63 1 # | \ # 0 1 4 5 16 17 20 21 ==64 0 # # 0 1 2 3 4 5 6 7 8 sub xy_to_n { return scalar((shift->xy_to_n_list(@_))[0]); } sub xy_to_n_list { my ($self, $x, $y) = @_; ### AlternatePaper xy_to_n(): "$x, $y" $x = round_nearest($x); $y = round_nearest($y); if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my $arms = $self->{'arms'}; my $arm = 0; my @ret; foreach (1 .. 4) { push @ret, map {$_*$arms+$arm} _xy_to_n_list__onearm($self,$x,$y); last if ++$arm >= $arms; ($x,$y) = ($y,$x); # transpose push @ret, map {$_*$arms+$arm} _xy_to_n_list__onearm($self,$x,$y); last if ++$arm >= $arms; # X,Y -> Y,X # -> Y,X-1 # Y-1 shift # -> X-1,-Y # rot -90 # ie. mirror across X axis and shift ($x,$y) = ($x-1,-$y); } return sort {$a<=>$b} @ret; } sub _xy_to_n_list__onearm { my ($self, $x, $y) = @_; ### _xy_to_n_list__onearm(): "$x,$y" if ($y < 0 || $y > $x || $x < 0) { ### outside first octant ... return; } my ($len,$level) = round_down_pow($x, 2); ### $len ### $level if (is_infinite($level)) { return; } my $n = my $big_n = $x * 0 * $y; # inherit bignum 0 my $rev = 0; my $big_x = $x; my $big_y = $y; my $big_rev = 0; while ($level-- >= 0) { ### at: "$x,$y len=$len n=$n" # the smaller N { $n *= 4; if ($rev) { if ($x+$y < 2*$len) { ### rev 0 or 1 ... if ($x < $len) { } else { ### rev 1 ... $rev = 0; $n -= 2; ($x,$y) = ($len-$y, $x-$len); # x-len,y-len then rotate +90 } } else { ### rev 2 or 3 ... if ($y > $len || ($x==$len && $y==$len)) { ### rev 2 ... $n -= 2; $x -= $len; $y -= $len; } else { ### rev 3 ... $n -= 4; $rev = 0; ($x,$y) = ($y, 2*$len-$x); # to origin then rotate -90 } } } else { if ($x+$y <= 2*$len && !($x==$len && $y==$len) && !($x==2*$len && $y==0)) { ### 0 or 1 ... if ($x <= $len) { } else { ### 1 ... $n += 2; $rev = 1; ($x,$y) = ($len-$y, $x-$len); # x-len,y-len then rotate +90 } } else { ### 2 or 3 ... if ($y >= $len && !($x==2*$len && $y==$len)) { $n += 2; $x -= $len; $y -= $len; } else { $n += 4; $rev = 1; ($x,$y) = ($y, 2*$len-$x); # to origin then rotate -90 } } } } # the bigger N { $big_n *= 4; if ($big_rev) { if ($big_x+$big_y <= 2*$len && !($big_x==$len && $big_y==$len) && !($big_x==2*$len && $big_y==0)) { ### rev 0 or 1 ... if ($big_x <= $len) { } else { ### rev 1 ... $big_rev = 0; $big_n -= 2; ($big_x,$big_y) = ($len-$big_y, $big_x-$len); # x-len,y-len then rotate +90 } } else { ### rev 2 or 3 ... if ($big_y >= $len && !($big_x==2*$len && $big_y==$len)) { ### rev 2 ... $big_n -= 2; $big_x -= $len; $big_y -= $len; } else { ### rev 3 ... $big_n -= 4; $big_rev = 0; ($big_x,$big_y) = ($big_y, 2*$len-$big_x); # to origin then rotate -90 } } } else { if ($big_x+$big_y < 2*$len) { ### 0 or 1 ... if ($big_x < $len) { } else { ### 1 ... $big_n += 2; $big_rev = 1; ($big_x,$big_y) = ($len-$big_y, $big_x-$len); # x-len,y-len then rotate +90 } } else { ### 2 or 3 ... if ($big_y > $len || ($big_x==$len && $big_y==$len)) { $big_n += 2; $big_x -= $len; $big_y -= $len; } else { $big_n += 4; $big_rev = 1; ($big_x,$big_y) = ($big_y, 2*$len-$big_x); # to origin then rotate -90 } } } } $len /= 2; } if ($x) { $n += ($rev ? -1 : 1); } if ($big_x) { $big_n += ($big_rev ? -1 : 1); } ### final: "$x,$y n=$n rev=$rev" ### final: "$x,$y big_n=$n big_rev=$rev" return ($n, ($n == $big_n ? () : ($big_n))); } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; ### AlternatePaper rect_to_n_range(): "$x1,$y1 $x2,$y2" $x1 = round_nearest($x1); $x2 = round_nearest($x2); $y1 = round_nearest($y1); $y2 = round_nearest($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; ### rounded: "$x1,$y1 $x2,$y2" my $arms = $self->{'arms'}; if (($arms == 1 && $y1 > $x2) # x2,y1 bottom right corner || ($arms <= 2 && $x2 < 0) || ($arms <= 4 && $y2 < 0)) { ### outside ... return (1,0); } # arm start 0,1 at X=0,Y=0 # 2,3 at X=0,Y=1 # 4,5 at X=-1,Y=1 # 6,7 at X=-1,Y=1 # arms>=6 is arm=5 starting at Y=+1, so 1-$y1 # arms>=8 starts at X=-1 so extra +1 for x2 to the right in that case my ($len, $level) =round_down_pow (max ($x2+($arms>=8), ($arms >= 2 ? $y2 : ()), ($arms >= 4 ? -$x1 : ()), ($arms >= 6 ? 1-$y1 : ())), 2); return (0, 4*$arms*$len*$len-1); } my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); sub n_to_dxdy { my ($self, $n) = @_; ### n_to_dxdy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; # $n fraction part ### $int ### $n my $arm = _divrem_mutate ($int, $self->{'arms'}); ### $arm ### $int # $dir initial direction from the arm. # $inc +/-1 according to the bit position odd or even, but also odd # numbered arms are transposed so flip them. # my @bits = bit_split_lowtohigh($int); my $dir = ($arm+1) >> 1; my $inc = (($#bits ^ $arm) & 1 ? -1 : 1); my $prev = 0; ### @bits ### initial dir: $dir ### initial inc: $inc foreach my $bit (reverse @bits) { if ($bit != $prev) { $dir += $inc; $prev = $bit; } $inc = -$inc; # opposite at each bit } $dir &= 3; my $dx = $dir4_to_dx[$dir]; my $dy = $dir4_to_dy[$dir]; ### $dx ### $dy if ($n) { ### apply fraction part: $n # maybe: # +/- $n as dx or dy # +/- (1-$n) as other dy or dx # strip any low 1-bits, and the 0-bit above them # $inc is +1 at an even bit position or -1 at an odd bit position $inc = my $inc = ($arm & 1 ? -1 : 1); while (shift @bits) { $inc = -$inc; } if ($bits[0]) { # bit above lowest 0-bit, 1=right,0=left $inc = -$inc; } $dir += $inc; # apply turn to give $dir at $n+1 $dir &= 3; $dx += $n*($dir4_to_dx[$dir] - $dx); $dy += $n*($dir4_to_dy[$dir] - $dy); } ### result: "$dx, $dy" return ($dx,$dy); } # { # sub print_table { # my ($name, $aref) = @_; # print "my \@$name = ("; # my $entry_width = max (map {length($_//'')} @$aref); # # foreach my $i (0 .. $#$aref) { # printf "%*s", $entry_width, $aref->[$i]//'undef'; # if ($i == $#$aref) { # print ");\n"; # } else { # print ","; # if (($i % 16) == 15 # || ($entry_width >= 3 && ($i % 4) == 3)) { # print "\n ".(" " x length($name)); # } elsif (($i % 4) == 3) { # print " "; # } # } # } # } # # my @next_state; # my @state_to_dxdy; # # sub make_state { # my %values = @_; # # if ($oddpos) { $rot = ($rot-1)&3; } # my $state = delete $values{'nextturn'}; # $state <<= 2; $state |= delete $values{'rot'}; # $state <<= 1; $state |= delete $values{'oddpos'}; # $state <<= 1; $state |= delete $values{'lowerbit'}; # $state <<= 1; $state |= delete $values{'bit'}; # die if %values; # return $state; # } # sub state_string { # my ($state) = @_; # my $bit = $state & 1; $state >>= 1; # my $lowerbit = $state & 1; $state >>= 1; # my $oddpos = $state & 1; $state >>= 1; # my $rot = $state & 3; $state >>= 2; # my $nextturn = $state; # # if ($oddpos) { $rot = ($rot+1)&3; } # return "rot=$rot,oddpos=$oddpos nextturn=$nextturn lowerbit=$lowerbit (bit=$bit)"; # } # # foreach my $nextturn (0, 1, 2) { # foreach my $rot (0, 1, 2, 3) { # foreach my $oddpos (0, 1) { # foreach my $lowerbit (0, 1) { # foreach my $bit (0, 1) { # my $state = make_state (bit => $bit, # lowerbit => $lowerbit, # rot => $rot, # oddpos => $oddpos, # nextturn => $nextturn); # ### $state # # my $new_nextturn = $nextturn; # my $new_lowerbit = $bit; # my $new_rot = $rot; # my $new_oddpos = $oddpos ^ 1; # # if ($bit != $lowerbit) { # if ($oddpos) { # $new_rot++; # } else { # $new_rot--; # } # $new_rot &= 3; # } # if ($lowerbit == 0 && ! $nextturn) { # $new_nextturn = ($bit ^ $oddpos ? 1 : 2); # bit above lowest 0 # } # # my $dx = 1; # my $dy = 0; # if ($rot & 2) { # $dx = -$dx; # $dy = -$dy; # } # if ($rot & 1) { # ($dx,$dy) = (-$dy,$dx); # rotate +90 # } # ### rot to: "$dx, $dy" # # # if ($oddpos) { # # ($dx,$dy) = (-$dy,$dx); # rotate +90 # # } else { # # ($dx,$dy) = ($dy,-$dx); # rotate -90 # # } # # my $next_dx = $dx; # my $next_dy = $dy; # if ($nextturn == 2) { # ($next_dx,$next_dy) = (-$next_dy,$next_dx); # left, rotate +90 # } else { # ($next_dx,$next_dy) = ($next_dy,-$next_dx); # right, rotate -90 # } # my $frac_dx = $next_dx - $dx; # my $frac_dy = $next_dy - $dy; # # # mask to rot,oddpos only, ignore bit,lowerbit # my $masked_state = $state & ~3; # $state_to_dxdy[$masked_state] = $dx; # $state_to_dxdy[$masked_state + 1] = $dy; # $state_to_dxdy[$masked_state + 2] = $frac_dx; # $state_to_dxdy[$masked_state + 3] = $frac_dy; # # my $next_state = make_state (bit => 0, # lowerbit => $new_lowerbit, # rot => $new_rot, # oddpos => $new_oddpos, # nextturn => $new_nextturn); # $next_state[$state] = $next_state; # } # } # } # } # } # # my @arm_to_state; # foreach my $arm (0 .. 7) { # my $rot = $arm >> 1; # my $oddpos = 0; # if ($arm & 1) { # $rot++; # $oddpos ^= 1; # } # $arm_to_state[$arm] = make_state (bit => 0, # lowerbit => 0, # rot => $rot, # oddpos => $oddpos, # nextturn => 0); # } # # ### @next_state # ### @state_to_dxdy # ### next_state length: 4*(4*2*2 + 4*2) # # print "# next_state length ", scalar(@next_state), "\n"; # print_table ("next_state", \@next_state); # print_table ("state_to_dxdy", \@state_to_dxdy); # print_table ("arm_to_state", \@arm_to_state); # print "\n"; # # foreach my $arm (0 .. 7) { # print "# arm=$arm ",state_string($arm_to_state[$arm]),"\n"; # } # print "\n"; # # # # use Smart::Comments; # # sub n_to_dxdy { # my ($self, $n) = @_; # ### n_to_dxdy(): $n # # my $int = int($n); # $n -= $int; # $n fraction part # ### $int # ### $n # # my $state = _divrem_mutate ($int, $self->{'arms'}) << 2; # ### arm as initial state: $state # # foreach my $bit (bit_split_lowtohigh($int)) { # $state = $next_state[$state + $bit]; # } # $state &= 0x1C; # mask out "prevbit" # # ### final state: $state # ### dx: $state_to_dxdy[$state] # ### dy: $state_to_dxdy[$state+1], # ### frac dx: $state_to_dxdy[$state+2], # ### frac dy: $state_to_dxdy[$state+3], # # return ($state_to_dxdy[$state] + $n * $state_to_dxdy[$state+2], # $state_to_dxdy[$state+1] + $n * $state_to_dxdy[$state+3]); # } # # } #------------------------------------------------------------------------------ # levels use Math::PlanePath::DragonCurve; *level_to_n_range = \&Math::PlanePath::DragonCurve::level_to_n_range; *n_to_level = \&Math::PlanePath::DragonCurve::n_to_level; #------------------------------------------------------------------------------ sub _UNDOCUMENTED_level_to_right_line_boundary { my ($self, $level) = @_; if ($level == 0) { return 1; } my ($h,$odd) = _divrem($level,2); return ($odd ? 6 * 2**$h - 4 : 2 * 2**$h); } sub _UNDOCUMENTED_level_to_left_line_boundary { my ($self, $level) = @_; if ($level == 0) { return 1; } my ($h,$odd) = _divrem($level,2); return ($odd ? 2 * 2**$h : 4 * 2**$h - 4); } sub _UNDOCUMENTED_level_to_line_boundary { my ($self, $level) = @_; my ($h,$odd) = _divrem($level,2); return (($odd?8:6) * 2**$h - 4); } sub _UNDOCUMENTED_level_to_hull_area { my ($self, $level) = @_; return (2**$level - 1)/2; } sub _UNDOCUMENTED__n_is_x_positive { my ($self, $n) = @_; if (! ($n >= 0) || is_infinite($n)) { return 0; } $n = int($n); { my $arm = _divrem_mutate($n, $self->{'arms'}); # arm 1 good only on N=1 which is remaining $n==0 if ($arm == 1) { return ($n == 0); } # arm 0 good # arm 8 good for N>=15 which is remaining $n>=1 unless ($arm == 0 || ($arm == 7 && $n > 0)) { return 0; } } return _is_base4_01($n); } sub _UNDOCUMENTED__n_is_diagonal_NE { my ($self, $n) = @_; if (! ($n >= 0) || is_infinite($n)) { return 0; } $n = int($n); if ($self->{'arms'} >= 8 && $n == 15) { return 1; } if (_divrem_mutate($n, $self->{'arms'}) >= 2) { return 0; } return _is_base4_02($n); } # X axis N is base4 digits 0,1 # and -1 from even is 0,1 low 0333333 # and -2 from even is 0,1 low 0333332 # so $n+2 low digit any then 0,1s above sub _UNDOCUMENTED__n_segment_is_right_boundary { my ($self, $n) = @_; if ($self->{'arms'} >= 8 || ! ($n >= 0) || is_infinite($n)) { return 0; } $n = int($n); if (_divrem_mutate($n, $self->{'arms'}) >= 1) { return 0; } $n += 2; _divrem_mutate($n,4); return _is_base4_01($n); } # diagonal N is base4 digits 0,2, # and -1 from there is 0,2 low 1 # or 0,2 low 13333 # so $n+1 low digit possible 1 or 3 then 0,2s above # which means $n+1 low digit any and 0,2s above #use Smart::Comments; sub _UNDOCUMENTED__n_segment_is_left_boundary { my ($self, $n) = @_; ### _UNDOCUMENTED__n_segment_is_left_boundary(): $n my $arms = $self->{'arms'}; if ($arms >= 8 || ! ($n >= 0) || is_infinite($n)) { return 0; } $n = int($n); if (($n == 1 && $arms >= 4) || ($n == 3 && $arms >= 5) || ($n == 5 && $arms == 7)) { return 1; } if (_divrem_mutate($n, $arms) < $arms-1) { ### no, not last arm ... return 0; } if ($arms % 2) { ### odd arms, stair-step boundary ... $n += 1; _divrem_mutate($n,4); return _is_base4_02($n); } else { # even arms, notched like right boundary $n += 2; _divrem_mutate($n,4); return _is_base4_01($n); } } sub _is_base4_01 { my ($n) = @_; while ($n) { my $digit = _divrem_mutate($n,4); if ($digit >= 2) { return 0; } } return 1; } sub _is_base4_02 { my ($n) = @_; while ($n) { my $digit = _divrem_mutate($n,4); if ($digit == 1 || $digit == 3) { return 0; } } return 1; } 1; __END__ #------------------------------------------------------------------------------ =for :stopwords eg Ryde Math-PlanePath Nlevel et al vertices doublings OEIS Online DragonCurve ZOrderCurve 0xAA..AA Golay-Rudin-Shapiro Rudin-Shapiro dX dY dX,dY GRS dSum undoubled MendE<232>s Tenenbaum des Courbes Papiers de ie ceil paperfolding =head1 NAME Math::PlanePath::AlternatePaper -- alternate paper folding curve =head1 SYNOPSIS use Math::PlanePath::AlternatePaper; my $path = Math::PlanePath::AlternatePaper->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION This is an integer version of the alternate paper folding curve (a variation on the DragonCurve paper folding). =cut # math-image --path=AlternatePaper --expression='i<=128?i:0' --output=numbers --size=60 =pod 8 | 128 | | 7 | 42---43/127 | | | 6 | 40---41/45--44/124 | | | | 5 | 34---35/39--38/46--47/123 | | | | | 4 | 32---33/53--36/52--37/49--48/112 | | | | | | 3 | 10---11/31--30/54--51/55--50/58--59/111 | | | | | | | 2 | 8----9/13--12/28--29/25--24/56--57/61--60/108 | | | | | | | | 1 | 2----3/7---6/14--15/27--26/18--19/23---22/62--63/107 | | | | | | | | | Y=0 | 0-----1 4-----5 16-----17 20-----21 64---.. | +------------------------------------------------------------ X=0 1 2 3 4 5 6 7 8 The curve visits the X axis points and the X=Y diagonal points once each and visits "inside" points between there twice each. The first doubled point is X=2,Y=1 which is N=3 and also N=7. The segments N=2,3,4 and N=6,7,8 have touched, but the curve doesn't cross over itself. The doubled vertices are all like this, touching but not crossing, and no edges repeat. The first step N=1 is to the right along the X axis and the path fills the eighth of the plane up to the X=Y diagonal inclusive. The X axis N=0,1,4,5,16,17,etc is the integers which have only digits 0,1 in base 4, or equivalently those which have a 0 bit at each odd numbered bit position. The X=Y diagonal N=0,2,8,10,32,etc is the integers which have only digits 0,2 in base 4, or equivalently those which have a 0 bit at each even numbered bit position. The X axis values are the same as on the ZOrderCurve X axis, and the X=Y diagonal is the same as the ZOrderCurve Y axis, but in between the two are different. (See L.) =head2 Paper Folding The curve arises from thinking of a strip of paper folded in half alternately one way and the other, and then unfolded so each crease is a 90 degree angle. The effect is that the curve repeats in successive doublings turned 90 degrees and reversed. The first segment N=0 to N=1 unfolds clockwise, pivoting at the endpoint "1", 2 -> | unfold / | ===> | | | 0------1 0-------1 Then that "L" shape unfolds again, pivoting at the end "2", but anti-clockwise, on the opposite side to the first unfold, 2-------3 2 | | | unfold | ^ | | ===> | _/ | | | | 0------1 0-------1 4 In general after each unfold the shape is a triangle as follows. "N" marks the N=2^k endpoint in the shape, either bottom right or top centre. after even number after odd number of unfolds, of unfolds, N=0 to N=2^even N=0 to N=2^odd . N /| / \ / | / \ / | / \ / | / \ / | / \ /_____N /___________\ 0,0 0,0 For an even number of unfolds the triangle consists of 4 sub-parts numbered by the high digit of N in base 4. Those sub-parts are self-similar in the direction "E", "^" etc as follows, and with a reversal for parts 1 and 3. + /| / | / | / 2>| +----+ /|\ 3| / | \ v| / |^ \ | / 0>| 1 \| +----+----+ =head2 Arms The C parameter can choose 1 to 8 curve arms successively advancing. Each fills an eighth of the plane. The second arm is mirrored across the X=Y leading diagonal, so =cut # math-image --path=AlternatePaper,arms=2 --expression='i<=128?i:0' --output=numbers --size=60 =pod arms => 2 | | | | | | 4 | 33---31/55---25/57---23/63---64/65-- | | | | | 3 | 11---13/29---19/27---20/21---22/62-- | | | | | | 2 | 9----7/15---16/17---18/26---24/56-- | | | | | 1 | 3----4/5-----6/14---12/28---30/54-- | | | | | | Y=0 | 0/1----2 8------10 32--- | +------------- ------------------------- X=0 1 2 3 4 Here the even N=0,2,4,6,etc is the plain curve below the X=Y diagonals and odd N=1,3,5,7,9,etc is the mirrored copy. Arms 3 and 4 are the same but rotated +90 degrees and starting from X=0,Y=1. That start point ensures each edge between integer points is traversed just once. =cut # math-image --path=AlternatePaper,arms=4 --expression='i<=256?i:0' --output=numbers --size=60 =pod arms => 4 | | | | | --34/35---14/30---18/21--25/57----37/53-- 3 | | | | | --15/31---10/11----6/17--13/29----32/33-- 2 | | | | | --19 7-----2/3/5---8/9-----12/28-- 1 | | | 0/1-----4 16-- <- Y=0 ----------------------------------------- -1 -2 X=0 1 2 Points N=0,4,8,12,etc is the plain curve, N=1,5,9,13,etc the second mirrored arm, N=2,6,10,14,etc is arm 3 which is the plain curve rotated +90, and N=3,7,11,15,etc the rotated and mirrored. Arms 5 and 6 start at X=-1,Y=1, and arms 7 and 8 start at X=-1,Y=0 so they too traverse each edge once. With a full 8 arms each point is visited twice except for the four start points which are three times. =cut # math-image --path=AlternatePaper,arms=8 --expression='i<=256?i:0' --output=numbers --size=60 =pod arms => 8 | | | | | | --75/107--66/67---26/58---34/41---49/113--73/105-- 3 | | | | | | --51/115---27/59---18/19--10/33---25/57---64/65-- 2 | | | | | | --36/43---12/35---4/5/11---2/3/9--16/17---24/56-- 1 | | | | | | --28/60---20/21---6/7/13--0/1/15---8/39---32/47-- <- Y=0 | | | | | | --68/69---29/61----14/37---22/23--31/63---55/119-- -1 | | | | | | --77/109--53/117---38/45---30/62--70/71---79/111-- -2 | | | | | | ^ -3 -2 -1 X=0 1 2 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::AlternatePaper-Enew ()> =item C<$path = Math::PlanePath::AlternatePaper-Enew (arms =E $integer)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer points. =item C<@n_list = $path-Exy_to_n_list ($x,$y)> Return a list of N point numbers for coordinates C<$x,$y>. For arms=1 there may be none, one or two N's for a given C<$x,$y>. For multiple arms the origin points X=0 or 1 and Y=0 or -1 have up to 3 Ns, being the starting points of the arms. For arms=8 those 4 points have 3 N and every other C<$x,$y> has exactly two Ns. =item C<$n = $path-En_start()> Return 0, the first N in the path. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, 2**$level)>, or for multiple arms return C<(0, $arms * 2**$level + ($arms-1))>. This is the same as L. Each level is an unfold (on alternate sides left or right). =back =head1 FORMULAS Various formulas for coordinates, lengths and area can be found in the author's mathematical write-up =over L =back =head2 Turn At each point N the curve always turns either left or right, it never goes straight ahead. The turn is given by the bit above the lowest 1 bit in N and whether that position is odd or even. N = 0b...z100..00 (including possibly no trailing 0s) ^ pos, counting from 0 for least significant bit (z bit) XOR (pos&1) Turn ------------------- ---- 0 right 1 left For example N=10 binary 0b1010 has lowest 1 bit at 0b__1_ and the bit above that is a 0 at even number pos=2, so turn to the right. =head2 Next Turn The bits also give the turn after next by looking at the bit above the lowest 0. N = 0b...w011..11 (including possibly no trailing 1s) ^ pos, counting from 0 for least significant bit (w bit) XOR (pos&1) Next Turn ------------------- --------- 0 right 1 left For example at N=10 binary 0b1010 the lowest 0 is the least significant bit, and above that is a 1 at odd pos=1, so at N=10+1=11 turn right. This works simply because w011..11 when incremented becomes w100..00 which is the "z" form above. The inversion at odd bit positions can be applied with an xor 0b1010..1010. With that done the turn calculation is then the same as the DragonCurve (see L). =head2 Total Turn The total turn can be calculated from the segment replacements resulting from the bits of N. each bit of N from high to low when plain state 0 -> no change 1 -> turn left if even bit pos or turn right if odd bit pos and go to reversed state when reversed state 1 -> no change 0 -> turn left if even bit pos or turn right if odd bit pos and go to plain state (bit positions numbered from 0 for the least significant bit) This is similar to the DragonCurve (see L) except the turn is either left or right according to an odd or even bit position of the transition, instead of always left for the DragonCurve. =head2 dX,dY Since there's always a turn either left or right, never straight ahead, the X coordinate changes, then Y coordinate changes, alternately. N=0 dX 1 0 1 0 1 0 -1 0 1 0 1 0 -1 0 1 0 ... dY 0 1 0 -1 0 1 0 1 0 1 0 -1 0 -1 0 -1 ... X changes when N is even, Y changes when N is odd. Each change is either +1 or -1. Which it is follows the Golay-Rudin-Shapiro sequence which is parity odd or even of the count of adjacent 11 bit pairs. In the total turn above it can be seen that if the 0-E1 transition is at an odd position and 1-E0 transition at an even position then there's a turn to the left followed by a turn to the right for no net change. Likewise an even and an odd. This means runs of 1 bits with an odd length have no effect on the direction. Runs of even length on the other hand are a left followed by a left, or a right followed by a right, for 180 degrees, which negates the dX change. Thus if N even then dX = (-1)^(count even length runs of 1 bits in N) if N odd then dX = 0 This (-1)^count is related to the Golay-Rudin-Shapiro sequence, GRS = (-1) ^ (count of adjacent 11 bit pairs in N) = (-1) ^ count_1_bits(N & (N>>1)) = / +1 if (N & (N>>1)) even parity \ -1 if (N & (N>>1)) odd parity The GRS is +1 on an odd length run of 1 bits, for example a run 111 is two 11 bit pairs. The GRS is -1 on an even length run, for example 1111 is three 11 bit pairs. So modulo 2 the power in the GRS is the same as the count of even length runs and therefore dX = / GRS(N) if N even \ 0 if N odd For dY the total turn and odd/even runs of 1s is the same 180 degree changes, except N is odd for a Y change so the least significant bit is 1 and there's no return to "plain" state. If this lowest run of 1s starts on an even position (an odd number of 1s) then it's a turn left for +1. Conversely if the run started at an odd position (an even number of 1s) then a turn right for -1. The result for this last run is the same "negate if even length" as the rest of the GRS, just for a slightly different reason. dY = / 0 if N even \ GRS(N) if N odd =head2 dX,dY Pair At a consecutive pair of points N=2k and N=2k+1 the dX and dY can be expressed together in terms of GRS(k) as dX = GRS(2k) = GRS(k) dY = GRS(2k+1) = GRS(k) * (-1)^k = / GRS(k) if k even \ -GRS(k) if k odd For dY reducing 2k+1 to k drops a 1 bit from the low end. If the second lowest bit is also a 1 then they were a "11" bit pair which is lost from GRS(k). The factor (-1)^k adjusts for that, being +1 if k even or -1 if k odd. =head2 dSum From the dX and dY formulas above it can be seen that their sum is simply GRS(N), dSum = dX + dY = GRS(N) The sum X+Y is a numbering of anti-diagonal lines, | \ \ \ |\ \ \ \ | \ \ \ \ |\ \ \ \ \ | \ \ \ \ \ |\ \ \ \ \ \ +------------ 0 1 2 3 4 5 The curve steps each time either up to the next or back to the previous according to dSum=GRS(N). The way the curve visits outside edge X,Y points once each and inner X,Y points twice each means an anti-diagonal s=X+Y is visited a total of s many times. The diagonal has floor(s/2)+1 many points. When s is odd the first is visited once and the rest visited twice. When s is even the X=Y point is only visited once. In each case the total is s many visits. The way the coordinate sum s=X+Y occurs s many times is a geometric interpretation to the way the cumulative GRS sequence has each value k occurring k many times. (See L.) =head1 OEIS The alternate paper folding curve is in Sloane's Online Encyclopedia of Integer Sequences as =over L (etc) =back A020986 X coordinate unduplicated, X+Y coordinate sum being Golay/Rudin/Shapiro cumulative A020990 Y coordinate unduplicated, X-Y diff starting from N=1 being Golay/Rudin/Shapiro * (-1)^n cumulative A068915 Y when N even, X when N odd Since the X and Y coordinates each change alternately, each coordinate appears twice, for instance X=0,1,1,2,2,3,3,2,2,etc. A020986 and A020990 are "undoubled" X and Y in the sense of just one copy of each of those paired values. A209615 turn 1=left,-1=right A292077 turn 0=left,1=right A106665 next turn 1=left,0=right, a(0) is turn at N=1 A020985 dX and dY alternately, dSum change in X+Y being Golay/Rudin/Shapiro sequence +1,-1 A020987 GRS with values 0,1 instead of +1,-1 A077957 Y at N=2^k, being alternately 0 and 2^(k/2) A000695 N on X axis, being base 4 digits 0,1 only A007088 in base-4 A151666 predicate 0,1 for N on X axis A062880 N on diagonal, being base 4 digits 0,2 only A169965 in base-4 A126684 N single-visited points, either X axis or diagonal A176237 N double-visited points A270804 N segments of X=Y diagonal stair-step A270803 0,1 predicate for these segments A022155 N positions of West or South segments, being GRS < 0, ie. dSum < 0 so move to previous anti-diagonal A203463 N positions of East or North segments, being GRS > 0, ie. dSum > 0 so move to next anti-diagonal A212591 N-1 of first time on X+Y=s anti-diagonal A047849 N of first time on X+Y=2^k anti-diagonal A020991 N-1 of last time on X+Y=s anti-diagonal A053644 X of last time on X+Y=s anti-diagonal A053645 Y of last time on X+Y=s anti-diagonal A080079 X-Y of last time on X+Y=s anti-diagonal A093573 N-1 of points on the anti-diagonals d=X+Y, by ascending N-1 value within each diagonal A004277 num visits in column X A020991 etc have values N-1, ie. the numbering differs by 1 from the N here, since they're based on the A020986 cumulative GRS starting at n=0 for value GRS(0). This matches the turn sequence A106665 starting at n=0 for the first turn, whereas for the path here that's N=1. A274230 area to N=2^k = double-visited points to N=2^k A027556 2*area to N=2^k A134057 area to N=4^k A060867 area to N=2*4^k A122746 area increment N=2^k to N=2^(k+1) = num segments West N=0 to 2^k-1 A005418 num segments East N=0 to 2^k-1 A051437 num segments North N=0 to 2^k-1 A007179 num segments South N=0 to 2^k-1 A097038 num runs of 8 consecutive segments within N=0 to 2^k-1 each segment enclosing a new unit square A000225 convex hull area * 2, being 2^k-1 A027383 boundary/2 to N=2^k also boundary verticals or horizontals (boundary is half verticals half horizontals) A131128 boundary to N=4^k A028399 boundary to N=2*4^k A052955 single-visited points to N=2^k A052940 single-visited points to N=4^k, being 3*2^n-1 A181666 n XOR other(n) occurring at double-visited points A086341 graph diameter of level N=0 to 2^k (for k>=3) arms=2 A062880 N on X axis, base 4 digits 0,2 only arms=3 A001196 N on X axis, base 4 digits 0,3 only =head1 HOUSE OF GRAPHS House of Graphs entries for the alternate paperfolding curve as a graph include =over L etc =back 19655 level=0 (1-segment path) 32234 level=1 (2-segment path) 286 level=2 (4-segment path) 27008 level=3 27010 level=4 27012 level=5 33778 level=6 33780 level=7 33782 level=8 =head1 SEE ALSO L, L L, L, L, L L, L Michel MendE<232>s France and G. Tenenbaum, "Dimension des Courbes Planes, Papiers Plies et Suites de Rudin-Shapiro", Bulletin de la S.M.F., volume 109, 1981, pages 207-215. L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/ZOrderCurve.pm0000644000175000017500000004644413751642625020020 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # math-image --path=ZOrderCurve,radix=3 --all --output=numbers # math-image --path=ZOrderCurve --values=Fibbinary --text # # increment N+1 changes low 1111 to 10000 # X bits change 011 to 000, no carry, decreasing by number of low 1s # Y bits change 011 to 100, plain +1 # # cf A105186 replace odd position ternary digits with 0 # package Math::PlanePath::ZOrderCurve; use 5.004; use strict; use List::Util 'max'; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'parameter_info_array', 'round_up_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; use constant dx_maximum => 1; use constant dy_maximum => 1; use constant absdx_minimum => 1; # X coord always changes use constant dsumxy_maximum => 1; # forward straight only sub dir_maximum_dxdy { my ($self) = @_; return (1, 1 - $self->{'radix'}); # SE diagonal } sub turn_any_straight { my ($self) = @_; return ($self->{'radix'} != 2); # radix=2 never straight } sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; return $self->{'radix'} - 1; } sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; return $self->{'radix'}; } #------------------------------------------------------------------------------ sub new { my $self = shift->SUPER::new(@_); my $radix = $self->{'radix'}; if (! defined $radix || $radix <= 2) { $radix = 2; } $self->{'radix'} = $radix; return $self; } sub n_to_xy { my ($self, $n) = @_; ### ZOrderCurve n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } my $int = int($n); $n -= $int; # fraction part my $radix = $self->{'radix'}; my @ndigits = digit_split_lowtohigh ($int, $radix); ### @ndigits unless ($#ndigits & 1) { push @ndigits, 0; # pad @ndigits to an even number of digits } my @xdigits; my @ydigits; while (@ndigits) { push @xdigits, shift @ndigits; # low to high push @ydigits, shift @ndigits; # low to high } ### @xdigits ### @ydigits my $zero = ($int * 0); # inherit bigint 0 my $x = digit_join_lowtohigh (\@xdigits, $radix, $zero); my $y = digit_join_lowtohigh (\@ydigits, $radix, $zero); if ($n) { # fraction part my $dx = 1; my $dy = $zero; my $radix_minus_1 = $radix - 1; foreach my $i (0 .. $#xdigits) { # low to high if ($xdigits[$i] != $radix_minus_1) { ### lowest non-9 is an X digit, so dx=1 dy=0,-R+1,-R^2+1,etc last; } $dy = ($dy * $radix) - $radix_minus_1; # 1-$radix**$i if ($ydigits[$i] != $radix_minus_1) { ### lowest non-9 is a Y digit, so dy=1, dx=-R+1,-R^2+1,etc $dx = $dy; $dy = 1; last; } } ### $dx ### $dy $x = $n*$dx + $x; $y = $n*$dy + $y; } return ($x, $y); } sub n_to_dxdy { my ($self, $n) = @_; ### ZOrderCurve n_to_xy(): $n if ($n < 0) { return; } my $int = int($n); $n -= $int; # fraction part if (is_infinite($int)) { return ($int,$int); } my $radix = $self->{'radix'}; my $digit = _divrem_mutate($int,$radix); # lowest digit of N if ($digit < $radix - 2) { # N an integer at lowdigit{'radix'}; my $zero = ($x * 0 * $y); # inherit bigint 0 my @x = digit_split_lowtohigh($x,$radix); my @y = digit_split_lowtohigh($y,$radix); return digit_join_lowtohigh ([ _digit_interleave (\@x, \@y) ], $radix, $zero); } # return list of @$xaref interleaved with @$yaref # ($xaref->[0], $yaref->[0], $xaref->[1], $yaref->[1], ...) # sub _digit_interleave { my ($xaref, $yaref) = @_; my @ret; foreach my $i (0 .. max($#$xaref,$#$yaref)) { push @ret, $xaref->[$i] || 0; push @ret, $yaref->[$i] || 0; } return @ret; } # exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest ($x1); $y1 = round_nearest ($y1); $x2 = round_nearest ($x2); $y2 = round_nearest ($y2); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } # x1 smaller if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # y1 smaller if ($y2 < 0 || $x2 < 0) { return (1, 0); # rect all negative, no N } if ($x1 < 0) { $x1 *= 0; } # "*=" to preserve bigint x1 or y1 if ($y1 < 0) { $y1 *= 0; } # monotonic increasing in X and Y directions, so this is exact return ($self->xy_to_n ($x1, $y1), $self->xy_to_n ($x2, $y2)); } #------------------------------------------------------------------------------ # levels # arms=1 # level 1 0..0 = 1 # level 1 0..3 = 4 # level 2 0..15 = 16 # 4^k-1 # shared by Math::PlanePath::GrayCode and others sub level_to_n_range { my ($self, $level) = @_; return (0, $self->{'radix'}**(2*$level) - 1); } sub n_to_level { my ($self, $n) = @_; if ($n < 0) { return undef; } $n = round_nearest($n); my ($pow, $exp) = round_up_pow ($n+1, $self->{'radix'}*$self->{'radix'}); return $exp; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords Ryde Math-PlanePath Karatsuba undrawn fibbinary eg Radix radix radix-1 RxR OEIS =head1 NAME Math::PlanePath::ZOrderCurve -- alternate digits to X and Y =head1 SYNOPSIS use Math::PlanePath::ZOrderCurve; my $path = Math::PlanePath::ZOrderCurve->new; my ($x, $y) = $path->n_to_xy (123); # or another radix digits ... my $path3 = Math::PlanePath::ZOrderCurve->new (radix => 3); =head1 DESCRIPTION This path puts points in a self-similar Z pattern described by G.M. Morton, 7 | 42 43 46 47 58 59 62 63 6 | 40 41 44 45 56 57 60 61 5 | 34 35 38 39 50 51 54 55 4 | 32 33 36 37 48 49 52 53 3 | 10 11 14 15 26 27 30 31 2 | 8 9 12 13 24 25 28 29 1 | 2 3 6 7 18 19 22 23 Y=0 | 0 1 4 5 16 17 20 21 64 ... +--------------------------------------- X=0 1 2 3 4 5 6 7 8 The first four points make a "Z" shape if written with Y going downwards (inverted if drawn upwards as above), 0---1 Y=0 / / 2---3 Y=1 Then groups of those are arranged as a further Z, etc, doubling in size each time. 0 1 4 5 Y=0 2 3 --- 6 7 Y=1 / / / 8 9 --- 12 13 Y=2 10 11 14 15 Y=3 Within an power of 2 square 2x2, 4x4, 8x8, 16x16 etc (2^k)x(2^k), all the N values 0 to 2^(2*k)-1 are within the square. The top right corner 3, 15, 63, 255 etc of each is the 2^(2*k)-1 maximum. Along the X axis N=0,1,4,5,16,17,etc is the integers with only digits 0,1 in base 4. Along the Y axis N=0,2,8,10,32,etc is the integers with only digits 0,2 in base 4. And along the X=Y diagonal N=0,3,12,15,etc is digits 0,3 in base 4. In the base Z pattern it can be seen that transposing to Y,X means swapping parts 1 and 2. This applies in the sub-parts too so in general if N is at X,Y then changing base 4 digits 1E-E2 gives the N at the transpose Y,X. For example N=22 at X=6,Y=1 is base-4 "112", change 1E-E2 is "221" for N=41 at X=1,Y=6. =head2 Power of 2 Values Plotting N values related to powers of 2 can come out as interesting patterns. For example displaying the N's which have no digit 3 in their base 4 representation gives * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * The 0,1,2 and not 3 makes a little 2x2 "L" at the bottom left, then repeating at 4x4 with again the whole "3" position undrawn, and so on. This is the Sierpinski triangle (a rotated version of L). The blanks are also a visual representation of 1-in-4 cross-products saved by recursive use of the Karatsuba multiplication algorithm. Plotting the fibbinary numbers (eg. L) which are N values with no adjacent 1 bits in binary makes an attractive tree-like pattern, * ** * **** * ** * * ******** * ** * **** * * ** ** * * * * **************** * * ** ** * * **** **** * * ** ** * * * * ******** ******** * * * * ** ** ** ** * * * * **** **** **** **** * * * * * * * * ** ** ** ** ** ** ** ** * * * * * * * * * * * * * * * * **************************************************************** The horizontals arise from N=...0a0b0c for bits a,b,c so Y=...000 and X=...abc, making those N values adjacent. Similarly N=...a0b0c0 for a vertical. =head2 Radix The C parameter can do the same N E-E X/Y digit splitting in a higher base. For example radix 3 makes 3x3 groupings, radix => 3 5 | 33 34 35 42 43 44 4 | 30 31 32 39 40 41 3 | 27 28 29 36 37 38 45 ... 2 | 6 7 8 15 16 17 24 25 26 1 | 3 4 5 12 13 14 21 22 23 Y=0 | 0 1 2 9 10 11 18 19 20 +-------------------------------------- X=0 1 2 3 4 5 6 7 8 Along the X axis N=0,1,2,9,10,11,etc is integers with only digits 0,1,2 in base 9. Along the Y axis digits 0,3,6, and along the X=Y diagonal digits 0,4,8. In general for a given radix it's base R*R with the R many digits of the first RxR block. =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::ZOrderCurve-Enew ()> =item C<$path = Math::PlanePath::ZOrderCurve-Enew (radix =E $r)> Create and return a new path object. The optional C parameter gives the base for digit splitting (the default is binary, radix 2). =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. Fractional positions give an X,Y position along a straight line between the integer positions. The lines don't overlap, but the lines between bit squares soon become rather long and probably of very limited use. =item C<$n = $path-Exy_to_n ($x,$y)> Return an integer point number for coordinates C<$x,$y>. Each integer N is considered the centre of a unit square and an C<$x,$y> within that square returns N. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> The returned range is exact, meaning C<$n_lo> and C<$n_hi> are the smallest and biggest in the rectangle. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, $radix**(2*$level) - 1)>. =back =head1 FORMULAS =head2 N to X,Y The coordinate calculation is simple. The bits of X and Y are every second bit of N. So if N = binary 101010 then X=000 and Y=111 in binary, which is the N=42 shown above at X=0,Y=7. With the C parameter the digits are treated likewise, in the given radix rather than binary. If N includes a fraction part then it's applied to a straight line towards point N+1. The +1 of N+1 changes X and Y according to how many low radix-1 digits there are in N, and thus in X and Y. In general if the lowest non radix-1 is in X then dX=1 dY = - (R^pos - 1) # pos=0 for lowest digit The simplest case is when the lowest digit of N is not radix-1, so dX=1,dY=0 across. If the lowest non radix-1 is in Y then dX = - (R^(pos+1) - 1) # pos=0 for lowest digit dY = 1 If all digits of X and Y are radix-1 then the implicit 0 above the top of X is considered the lowest non radix-1 and so the first case applies. In the radix=2 above this happens for instance at N=15 binary 1111 so X = binary 11 and Y = binary 11. The 0 above the top of X is at pos=2 so dX=1, dY=-(2^2-1)=-3. =head2 Rectangle to N Range Within each row the N values increase as X increases, and within each column N increases with increasing Y (for all C parameters). So for a given rectangle the smallest N is at the lower left corner (smallest X and smallest Y), and the biggest N is at the upper right (biggest X and biggest Y). =head1 OEIS This path is in Sloane's Online Encyclopedia of Integer Sequences in various forms, =over L (etc) =back radix=2 A059905 X coordinate A059906 Y coordinate A000695 N on X axis (base 4 digits 0,1 only) A062880 N on Y axis (base 4 digits 0,2 only) A001196 N on X=Y diagonal (base 4 digits 0,3 only) A057300 permutation N at transpose Y,X (swap bit pairs) radix=3 A163325 X coordinate A163326 Y coordinate A037314 N on X axis, base 9 digits 0,1,2 A208665 N on Y axis, base 9 digits 0,3,6 A338086 N on X=Y diagonal, base 9 digits 0,4,8 A163327 permutation N at transpose Y,X (swap trit pairs) radix=4 A126006 permutation N at transpose Y,X (swap digit pairs) radix=10 A080463 X+Y of radix=10 (from N=1 onwards) A080464 X*Y of radix=10 (from N=10 onwards) A080465 abs(X-Y), from N=10 onwards A051022 N on X axis (base 100 digits 0 to 9) A338754 N on X=Y diagonal (double-digits 00 to 99) radix=16 A217558 permutation N at transpose Y,X (swap digit pairs) And taking X,Y points in the Diagonals sequence then the value of the following sequences is the N of the C at those positions. radix=2 A054238 numbering by diagonals, from same axis as first step A054239 inverse permutation radix=3 A163328 numbering by diagonals, same axis as first step A163329 inverse permutation A163330 numbering by diagonals, opp axis as first step A163331 inverse permutation C numbers points from the Y axis down, which is the opposite axis to the C first step along the X axis, so a transpose is needed to give A054238. =head1 SEE ALSO L, L, L, L, L, L XXC (section 1.31.2) L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/GrayCode.pm0000644000175000017500000006222413734026670017272 0ustar gggg# Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # cf. A164677 position of Gray bit change, +/- according to 0->1 or 1->0. # (a signed version of A001511) package Math::PlanePath::GrayCode; use 5.004; use strict; use Carp 'croak'; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 129; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; use constant n_start => 0; use constant class_x_negative => 0; use constant class_y_negative => 0; *xy_is_visited = \&Math::PlanePath::Base::Generic::xy_is_visited_quad1; use constant parameter_info_array => [ { name => 'apply_type', share_key => 'apply_type_TsF', display => 'Apply Type', type => 'enum', default => 'TsF', choices => ['TsF','Ts','Fs','FsT','sT','sF'], choices_display => ['TsF','Ts','Fs','FsT','sT','sF'], description => 'How to apply the Gray coding to/from and split.', }, { name => 'gray_type', display => 'Gray Type', type => 'enum', default => 'reflected', choices => ['reflected','modular'], choices_dispaly => ['Reflected','Modular'], description => 'The type of Gray code.', }, { %{Math::PlanePath::Base::Digits::parameter_info_radix2()}, description => 'Radix, for both the Gray code and splitting.', }, ]; sub _is_peano { my ($self) = @_; return ($self->{'radix'} % 2 == 1 && $self->{'gray_type'} eq 'reflected' && ($self->{'apply_type'} eq 'TsF' || $self->{'apply_type'} eq 'FsT')); } sub dx_minimum { my ($self) = @_; return (_is_peano($self) ? -1 : undef); } *dy_minimum = \&dx_minimum; sub dx_maximum { my ($self) = @_; return (_is_peano($self) ? 1 : undef); } *dy_maximum = \&dx_maximum; { # Ror sT and sF the split X coordinate changes from N to N+1 and so does # the to-gray or from-gray transformation, so X always changes. # my %absdx_minimum = ( reflected => { # TsF => 0, # FsT => 0, # Ts => 0, # Fs => 0, sT => 1, sF => 1, }, modular => { # TsF => 0, # Ts => 0, Fs => 1, FsT => 1, sT => 1, sF => 1, }, ); sub absdx_minimum { my ($self) = @_; my $gray_type = ($self->{'radix'} == 2 ? 'reflected' : $self->{'gray_type'}); return ($absdx_minimum{$gray_type}->{$self->{'apply_type'}} || 0); } } *dsumxy_minimum = \&dx_minimum; *dsumxy_maximum = \&dx_maximum; *ddiffxy_minimum = \&dx_minimum; *ddiffxy_maximum = \&dx_maximum; { my %dir_maximum_supremum = ( # # radix==2 always "reflected" # # TsF => 0, # # FsT => 0, # # Ts => 0, # # Fs => 0, # sT => 4, # sF => 4, reflected => { # TsF => 0, # FsT => 0, # Ts => 0, # Fs => 0, sT => 4, sF => 4, }, modular => { # TsF => 0, # Ts => 0, Fs => 4, FsT => 4, sT => 4, sF => 4, }, ); sub dir_maximum_dxdy { my ($self) = @_; my $gray_type = ($self->{'radix'} == 2 ? 'reflected' : $self->{'gray_type'}); return ($dir_maximum_supremum{$gray_type}->{$self->{'apply_type'}} ? (0,0) # supremum : (0,-1)); # South } } # radix=2 TsF==Fs is always straight or left sub turn_any_right { my ($self) = @_; if ($self->{'radix'} == 2 && ($self->{'apply_type'} eq 'TsF' || $self->{'apply_type'} eq 'Fs')) { return 0; # never right } return 1; } sub turn_any_straight { my ($self) = @_; return ($self->{'radix'} == 2 && ($self->{'apply_type'} eq 'sT' || $self->{'apply_type'} eq 'sF') ? 0 # never straight : 1); } sub _UNDOCUMENTED__turn_any_left_at_n { my ($self) = @_; return $self->{'radix'} - 1; } sub _UNDOCUMENTED__turn_any_right_at_n { my ($self) = @_; if ($self->{'apply_type'} eq 'TsF' && $self->{'gray_type'} eq 'reflected' && $self->{'radix'} > 2) { return 2*$self->{'radix'} - 1; } return undef; } #------------------------------------------------------------------------------ my %funcbase = (T => '_digits_to_gray', F => '_digits_from_gray', '' => '_noop'); my %inv = (T => 'F', F => 'T', '' => ''); sub new { my $self = shift->SUPER::new(@_); if (! $self->{'radix'} || $self->{'radix'} < 2) { $self->{'radix'} = 2; } my $apply_type = ($self->{'apply_type'} ||= 'TsF'); my $gray_type = ($self->{'gray_type'} ||= 'reflected'); unless ($apply_type =~ /^([TF]?)s([TF]?)$/) { croak "Unrecognised apply_type \"$apply_type\""; } my $nf = $1; # "T" or "F" or "" my $xyf = $2; $self->{'n_func'} = $self->can("$funcbase{$nf}_$gray_type") || croak "Unrecognised gray_type \"$self->{'gray_type'}\""; $self->{'xy_func'} = $self->can("$funcbase{$xyf}_$gray_type"); $nf = $inv{$nf}; $xyf = $inv{$xyf}; $self->{'inverse_n_func'} = $self->can("$funcbase{$nf}_$gray_type"); $self->{'inverse_xy_func'} = $self->can("$funcbase{$xyf}_$gray_type"); return $self; } sub n_to_xy { my ($self, $n) = @_; ### GrayCode n_to_xy(): $n if ($n < 0) { return; } if (is_infinite($n)) { return ($n,$n); } { # ENHANCE-ME: N and N+1 differ by not much ... my $int = int($n); ### $int if ($n != $int) { my $frac = $n - $int; # inherit possible BigFloat/BigRat ### $frac my ($x1,$y1) = $self->n_to_xy($int); my ($x2,$y2) = $self->n_to_xy($int+1); my $dx = $x2-$x1; my $dy = $y2-$y1; return ($frac*$dx + $x1, $frac*$dy + $y1); } $n = $int; # BigFloat int() gives BigInt, use that } my $radix = $self->{'radix'}; my @digits = digit_split_lowtohigh($n,$radix); $self->{'n_func'}->(\@digits, $radix); my @xdigits; my @ydigits; while (@digits) { push @xdigits, shift @digits; # low to high push @ydigits, shift @digits || 0; } my $xdigits = \@xdigits; my $ydigits = \@ydigits; $self->{'xy_func'}->($xdigits,$radix); $self->{'xy_func'}->($ydigits,$radix); return (digit_join_lowtohigh($xdigits,$radix), digit_join_lowtohigh($ydigits,$radix)); } sub xy_to_n { my ($self, $x, $y) = @_; ### GrayCode xy_to_n(): "$x, $y" $x = round_nearest ($x); $y = round_nearest ($y); if ($x < 0 || $y < 0) { return undef; } if (is_infinite($x)) { return $x; } if (is_infinite($y)) { return $y; } my $radix = $self->{'radix'}; my @xdigits = digit_split_lowtohigh ($x, $radix); my @ydigits = digit_split_lowtohigh ($y, $radix); $self->{'inverse_xy_func'}->(\@xdigits, $radix); $self->{'inverse_xy_func'}->(\@ydigits, $radix); my @digits; for (;;) { (@xdigits || @ydigits) or last; push @digits, shift @xdigits || 0; (@xdigits || @ydigits) or last; push @digits, shift @ydigits || 0; } my $digits = \@digits; $self->{'inverse_n_func'}->($digits,$radix); return digit_join_lowtohigh($digits,$radix); } # not exact sub rect_to_n_range { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest($x1); $y1 = round_nearest($y1); $x2 = round_nearest($x2); $y2 = round_nearest($y2); if ($x1 > $x2) { ($x1,$x2) = ($x2,$x1); } # x1 smaller if ($y1 > $y2) { ($y1,$y2) = ($y2,$y1); } # y1 smaller if ($y2 < 0 || $x2 < 0) { return (1, 0); # rect all negative, no N } my $radix = $self->{'radix'}; my ($pow_max) = round_down_pow (max($x2,$y2), $radix); $pow_max *= $radix; return (0, $pow_max*$pow_max - 1); } #------------------------------------------------------------------------------ use constant 1.02 _noop_reflected => undef; use constant 1.02 _noop_modular => undef; # $aref->[0] low digit sub _digits_to_gray_reflected { my ($aref, $radix) = @_; ### _digits_to_gray(): $aref $radix -= 1; my $reverse = 0; foreach my $digit (reverse @$aref) { # high to low if ($reverse & 1) { $digit = $radix - $digit; # radix-1 - digit } $reverse ^= $digit; } } # $aref->[0] low digit sub _digits_to_gray_modular { my ($aref, $radix) = @_; my $prev = 0; foreach my $digit (reverse @$aref) { # high to low ($digit,$prev) = (($digit - $prev) % $radix, # mutate $aref->[i] $digit); } } # $aref->[0] low digit sub _digits_from_gray_reflected { my ($aref, $radix) = @_; $radix -= 1; # radix-1 my $reverse = 0; foreach my $digit (reverse @$aref) { # high to low if ($reverse & 1) { $reverse ^= $digit; # before this reversal $digit = $radix - $digit; # radix-1 - digit, mutate array } else { $reverse ^= $digit; } } } # $aref->[0] low digit sub _digits_from_gray_modular { my ($aref, $radix) = @_; ### _digits_from_gray_modular(): $aref my $offset = 0; foreach my $digit (reverse @$aref) { # high to low $offset = ($digit = ($digit + $offset) % $radix); # mutate $aref->[i] } } #------------------------------------------------------------------------------ # levels use Math::PlanePath::ZOrderCurve; *level_to_n_range = \&Math::PlanePath::ZOrderCurve::level_to_n_range; *n_to_level = \&Math::PlanePath::ZOrderCurve::n_to_level; #------------------------------------------------------------------------------ 1; __END__ =for stopwords Ryde Math-PlanePath eg Radix radix ie Christos Faloutsos Fs FsT sF pre TsF Peano radices Peano's xk yk OEIS PlanePath undoubled pre-determined TSE DOI =head1 NAME Math::PlanePath::GrayCode -- Gray code coordinates =head1 SYNOPSIS use Math::PlanePath::GrayCode; my $path = Math::PlanePath::GrayCode->new; my ($x, $y) = $path->n_to_xy (123); =head1 DESCRIPTION XThis path is a mapping of N to X,Y using Gray codes. 7 | 63-62 57-56 39-38 33-32 | | | | | 6 | 60-61 58-59 36-37 34-35 | 5 | 51-50 53-52 43-42 45-44 | | | | | 4 | 48-49 54-55 40-41 46-47 | 3 | 15-14 9--8 23-22 17-16 | | | | | 2 | 12-13 10-11 20-21 18-19 | 1 | 3--2 5--4 27-26 29-28 | | | | | Y=0 | 0--1 6--7 24-25 30-31 | +------------------------- X=0 1 2 3 4 5 6 7 XThe default is the form by Faloutsos which is an X,Y split in binary reflected Gray code. =over Christos Faloutsos, "Gray Codes for Partial Match and Range Queries", IEEE Transactions on Software Engineering (TSE), volume 14, number 10, October 1988, pages 1381-1393. DOI 10.1109/32.6184 =back N is converted to a Gray code, then split by bits to X,Y, and those X,Y converted back from Gray to integer indices. Stepping from N to N+1 changes just one bit of the Gray code and therefore changes just one of X or Y each time. Y axis N=0,3,12,15,48,etc are values with only digits 0,3 in base 4. X axis N=0,1,6,7,24,25,etc are values 2k and 2k+1 where k uses only digits 0,3 in base 4. =head2 Radix The default is binary. Option C $r> can select another radix. This radix is used for both the Gray code and the digit splitting. For example C 4>, radix => 4 | 127-126-125-124 99--98--97--96--95--94--93--92 67--66--65--64 | | | | 120-121-122-123 100-101-102-103 88--89--90--91 68--69--70--71 | | | | 119-118-117-116 107-106-105-104 87--86--85--84 75--74--73--72 | | | | 112-113-114-115 108-109-110-111 80--81--82--83 76--77--78--79 15--14--13--12 19--18--17--16 47--46--45--44 51--50--49--48 | | | | 8-- 9--10--11 20--21--22--23 40--41--42--43 52--53--54--55 | | | | 7-- 6-- 5-- 4 27--26--25--24 39--38--37--36 59--58--57--56 | | | | 0-- 1-- 2-- 3 28--29--30--31--32--33--34--35 60--61--62--63 =head2 Apply Type Option C $str> controls how Gray codes are applied to N and X,Y. It can be one of "TsF" to Gray, split, from Gray (default) "Ts" to Gray, split "Fs" from Gray, split "FsT" from Gray, split, to Gray "sT" split, to Gray "sF" split, from Gray "T" means integer-to-Gray, "F" means integer-from-Gray, and omitted means no transformation. For example the following is "Ts" which means N to Gray then split, leaving Gray coded values for X,Y. =cut # math-image --path=GrayCode,apply_type=Ts --all --output=numbers_dash =pod apply_type => "Ts" 7 | 51--50 52--53 44--45 43--42 | | | | | 6 | 48--49 55--54 47--46 40--41 | 5 | 60--61 59--58 35--34 36--37 ...-66 | | | | | | 4 | 63--62 56--57 32--33 39--38 64--65 | 3 | 12--13 11--10 19--18 20--21 | | | | | 2 | 15--14 8-- 9 16--17 23--22 | 1 | 3-- 2 4-- 5 28--29 27--26 | | | | | Y=0 | 0-- 1 7-- 6 31--30 24--25 | +--------------------------------- X=0 1 2 3 4 5 6 7 This "Ts" is quite attractive because a step from N to N+1 changes just one bit in X or Y alternately, giving 2-D single-bit changes. For example N=19 at X=4 followed by N=20 at X=6 is a single bit change in X. N=0,2,8,10,etc on the leading diagonal X=Y is numbers using only digits 0,2 in base 4. N=0,3,15,12,etc on the Y axis is numbers using only digits 0,3 in base 4, but in a Gray code order. The "Fs", "FsT" and "sF" forms effectively treat the input N as a Gray code and convert from it to integers, either before or after split. For "Fs" the effect is little Z parts in various orientations. apply_type => "sF" 7 | 32--33 37--36 52--53 49--48 | / \ / \ 6 | 34--35 39--38 54--55 51--50 | 5 | 42--43 47--46 62--63 59--58 | \ / \ / 4 | 40--41 45--44 60--61 57--56 | 3 | 8-- 9 13--12 28--29 25--24 | / \ / \ 2 | 10--11 15--14 30--31 27--26 | 1 | 2-- 3 7-- 6 22--23 19--18 | \ / \ / Y=0 | 0-- 1 5-- 4 20--21 17--16 | +--------------------------------- X=0 1 2 3 4 5 6 7 =head2 Gray Type The C option selects the type of Gray code. The choices are "reflected" increment to radix-1 then decrement (default) "modular" increment to radix-1 then cycle back to 0 For example in decimal, integer Gray Gray "reflected" "modular" ------- ----------- --------- 0 0 0 1 1 1 2 2 2 ... ... ... 8 8 8 9 9 9 10 19 19 11 18 10 12 17 11 13 16 12 14 15 13 ... ... ... 17 12 16 18 11 17 19 10 18 Notice on reaching "19" the reflected type runs the least significant digit back down from 9 to 0, which is a reverse or reflection of the preceding 0 to 9 upwards. The modular form instead continues to increment that least significant digit, wrapping around from 9 to 0. In binary, the modular and reflected forms are the same (see L below). There's various other systematic ways to make a Gray code changing a single digit successively. But many ways are implicitly based on a pre-determined fixed number of bits or digits, which doesn't suit an unlimited path like here. =head2 Equivalent Combinations Some option combinations are equivalent, condition equivalent --------- ---------- radix=2 modular==reflected and TsF==Fs, Ts==FsT radix>2 odd, reflected TsF==FsT, Ts==Fs, sT==sF because T==F radix>2 even, reflected TsF==Fs, Ts==FsT In radix=2 binary, the "modular" and "reflected" Gray codes are the same because there's only digits 0 and 1 so going forward or backward is the same. For odd radix and reflected Gray code, the "to Gray" and "from Gray" operations are the same. For example the following table is ternary radix=3. Notice how integer value 012 maps to Gray code 010, and in turn integer 010 maps to Gray code 012. All values are either pairs like that or unchanged like 021. integer Gray "reflected" (written in ternary) 000 000 001 001 002 002 010 012 011 011 012 010 020 020 021 021 022 022 For even radix and reflected Gray code, "TsF" is equivalent to "Fs", and also "Ts" equivalent to "FsT". This arises from the way the reversing behaves when split across digits of two X,Y values. (In higher dimensions such as a split to 3-D X,Y,Z it's not the same.) The net effect for distinct paths is condition distinct combinations --------- --------------------- radix=2 four TsF==Fs, Ts==FsT, sT, sF radix>2 odd / three reflected TsF==FsT, Ts==Fs, sT==sF \ six modular TsF, Ts, Fs, FsT, sT, sF radix>2 even / four reflected TsF==Fs, Ts==FsT, sT, sF \ six modular TsF, Ts, Fs, FsT, sT, sF =head2 Peano Curve In C 3> and other odd radices, the "reflected" Gray type gives the Peano curve (see L). This is since the "reflected" encoding is equivalent to Peano's "xk" and "yk" complementing. =cut # math-image --path=GrayCode,radix=3,gray_type=reflected --all --output=numbers_dash =pod radix => 3, gray_type => "reflected" | 53--52--51 38--37--36--35--34--33 | | | 48--49--50 39--40--41 30--31--32 | | | 47--46--45--44--43--42 29--28--27 | 6-- 7-- 8-- 9--10--11 24--25--26 | | | 5-- 4-- 3 14--13--12 23--22--21 | | | 0-- 1-- 2 15--16--17--18--19--20 =head1 FUNCTIONS See L for the behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::GrayCode-Enew ()> =item C<$path = Math::PlanePath::GrayCode-Enew (radix =E $r, apply_type =E $str, gray_type =E $str)> Create and return a new path object. =item C<($x,$y) = $path-En_to_xy ($n)> Return the X,Y coordinates of point number C<$n> on the path. Points begin at 0 and if C<$n E 0> then the return is an empty list. =item C<$n = $path-En_start ()> Return the first N on the path, which is 0. =back =head2 Level Methods =over =item C<($n_lo, $n_hi) = $path-Elevel_to_n_range($level)> Return C<(0, $radix**(2*$level) - 1)>. =back =head1 FORMULAS =head2 Turn The turns in the default binary TsF curve are either to the left +90 or a reverse 180. For example at N=2 the curve turns left, then at N=3 it reverses back 180 to go to N=4. The turn is given by the low zero bits of (N+1)/2, count_low_0_bits(floor((N+1)/2)) if even then turn 90 left if odd then turn 180 reverse Or equivalently floor((N+1)/2) lowest non-zero digit in base 4, 1 or 3 = turn 90 left 2 = turn 180 reverse The 180 degree reversals are all horizontal. They occur because at those N the three N-1,N,N+1 converted to Gray code have the same bits at odd positions and therefore the same Y coordinate. See L for similar turns based on low zero bits (but by +60 and -120 degrees). =head1 OEIS This path is in Sloane's Online Encyclopedia of Integer Sequences in a few forms, =over L (etc) =back apply_type="TsF" (="Fs"), radix=2 (the defaults) A059905 X xor Y A039963 turn sequence, 1=+90 left, 0=180 reverse A035263 turn undoubled, at N=2n and N=2n+1 A065882 base4 lowest non-zero, turn undoubled 1,3=left 2=180rev at N=2n,2n+1 A003159 (N+1)/2 of positions of Left turns, being n with even number of low 0 bits A036554 (N+1)/2 of positions of Right turns being n with odd number of low 0 bits TsF turn sequence goes in pairs, so N=1 and N=2 left then N=3 and N=4 reverse. A039963 includes that repetition, A035263 is just one copy of each and so is the turn at each pair N=2k and N=2k+1. There's many sequences like A065882 which when taken mod2 equal the "count low 0-bits odd/even" which is the same undoubled turn sequence. apply_type="Ts", radix=2 A309952 X coordinate (XOR bit pairs) apply_type="sF", radix=2 A163233 N values by diagonals, same axis start A163234 inverse permutation A163235 N values by diagonals, opp axis start A163236 inverse permutation A163242 N sums along diagonals A163478 those sums divided by 3 A163237 N values by diagonals, same axis, flip digits 2,3 A163238 inverse permutation A163239 N values by diagonals, opp axis, flip digits 2,3 A163240 inverse permutation A099896 N values by PeanoCurve radix=2 order A100280 inverse permutation apply_type="FsT", radix=3, gray_type=modular A208665 N values on X=Y diagonal, base 9 digits 0,3,6 Gray code conversions themselves (not directly offered by the PlanePath code here) are variously A003188 binary A014550 binary written in binary A055975 increments A006068 inverse, Gray->integer A128173 ternary reflected (its own inverse) A105530 ternary modular A105529 inverse, Gray->integer A003100 decimal reflected A174025 inverse, Gray->integer A098488 decimal modular A226134 inverse, Gray->integer =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/Base/0002755000175000017500000000000014001441522016066 5ustar ggggMath-PlanePath-129/lib/Math/PlanePath/Base/NSEW.pm0000644000175000017500000000653113734026652017222 0ustar gggg# Copyright 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::Base::NSEW; use 5.004; use strict; use vars '$VERSION'; $VERSION = 129; use constant dx_minimum => -1; # NSEW straight only use constant dx_maximum => 1; use constant dy_minimum => -1; use constant dy_maximum => 1; use constant dsumxy_minimum => -1; # NSEW straight only use constant dsumxy_maximum => 1; use constant ddiffxy_minimum => -1; use constant ddiffxy_maximum => 1; use constant dir_maximum_dxdy => (0,-1); # South use constant 1.02; use constant _UNDOCUMENTED__dxdy_list => (1,0, # E 0,1, # N -1,0, # W 0,-1); # S 1; __END__ =for stopwords Ryde Math-PlanePath multi-inheritance mixin =head1 NAME Math::PlanePath::Base::NSEW -- multi-inheritance mixin for North, South, East, West unit steps =head1 SYNOPSIS =for test_synopsis my @ISA; # normally a package variable of course, but this satisfies Test::Synopsis package Math::PlanePath::Foo; use Math::PlanePath; use Math::PlanePath::Base::NSEW; @ISA = ('Math::PlanePath::Base::NSEW', 'Math::PlanePath'); =head1 DESCRIPTION This is a multi-inheritance mixin for paths which take only steps North, South, East and West by distance 1 each time. This includes for example the C and also things like the C or C. The following path descriptive methods are provided value dx_minimum() -1 dx_maximum() 1 dy_minimum() -1 dy_maximum() 1 dsumxy_minimum() -1 dsumxy_maximum() 1 ddiffxy_minimum() -1 ddiffxy_maximum() 1 dir_maximum_dxdy() 0,-1 # maximum South =cut # _UNDOCUMENTED__dxdy_list() 1,0, 0,1, -1,0, 0,-1 =pod =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/Base/Generic.pm0000644000175000017500000001541613774222377020032 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath::Base::Generic; use 5.004; use strict; use vars '$VERSION','@ISA','@EXPORT_OK'; $VERSION = 129; use Exporter; @ISA = ('Exporter'); @EXPORT_OK = ('round_nearest', # not documented yet 'is_infinite', 'floor', 'xy_is_even', 'xy_is_visited_quad1', ); # uncomment this to run the ### lines # use Smart::Comments; # with a view to being friendly to BigRat/BigFloat sub round_nearest { my ($x) = @_; ### round_nearest(): "$x", $x # BigRat through to perl 5.12.4 has some dodginess giving a bigint -0 # which is considered !=0. Adding +0 to numify seems to avoid the problem. my $int = int($x) + 0; if ($x == $int) { ### is an integer: "$int", $int return $x; } $x -= $int; ### int: "$int" ### frac: "$x" if ($x >= .5) { ### round up ... return $int + 1; } if ($x < -.5) { ### round down ... return $int - 1; } ### within +/- .5 ... return $int; } use constant parameter_info_nstart0 => { name => 'n_start', share_key => 'n_start_0', display => 'N Start', type => 'integer', default => 0, width => 3, description => 'Starting N.', }; use constant parameter_info_nstart1 => { name => 'n_start', share_key => 'n_start_1', display => 'N Start', type => 'integer', default => 1, width => 3, description => 'Starting N.', }; #------------------------------------------------------------------------------ # these not documented ... sub is_infinite { my ($x) = @_; return ($x != $x # nan || ($x != 0 && $x == 2*$x)); # inf } # With a view to being friendly to BigRat/BigFloat. # # For reference, POSIX::floor() in perl 5.12.4 is a bit bizarre on UV=64bit # and NV=53bit double. UV=2^64-1 rounds up to NV=2^64 which floor() then # returns, so floor() in fact increases the value of what was an integer # already. # # not documented yet sub floor { my ($x) = @_; ### floor(): "$x", $x my $int = int($x); if ($x == $int) { ### is an integer ... return $x; } $x -= $int; ### frac: "$x" if ($x >= 0) { ### frac is non-negative ... return $int; } else { ### frac is negative ... return $int-1; } } # not documented yet sub xy_is_visited_quad1 { # or maybe base class with class_x_negative too my ($self, $x, $y) = @_; return ! (2*$x < -1 || 2*$y < -1); } # not documented yet sub xy_is_visited_quad12 { my ($self, $x, $y) = @_; return (2*$y >= -1); } # not documented yet sub _xy_is_visited_x_positive { my ($self, $x, $y) = @_; return (2*$x >= -1); } # not documented yet sub xy_is_even { my ($self, $x, $y) = @_; return (round_nearest($x)%2 == round_nearest($y)%2); } 1; __END__ =for stopwords Ryde Math-PlanePath PlanePath hashref initializer =head1 NAME Math::PlanePath::Base::Generic -- various path helpers =for test_synopsis my $x =head1 SYNOPSIS use Math::PlanePath::Base::Generic 'round_nearest'; $x = round_nearest($x); =head1 DESCRIPTION This is a few generic helper functions for the PlanePath code. They're designed to work on plain Perl integers and floats and in some cases there's some special support for C. =head1 EXPORTS Nothing is exported by default but each function below can be as in the usual L style, use Math::PlanePath::Base::Generic 'round_nearest'; (But not C and C, for the reason described below.) =head1 FUNCTIONS =head2 Generic =over 4 =item C<$x = round_nearest ($x)> Return C<$x> rounded to the nearest integer. If C<$x> is half way, such as 2.5 then it's round upwards to 3. $x = round_nearest($x); =item C<$href = Math::PlanePath::Base::Generic::parameter_info_nstart0()> =item C<$href = Math::PlanePath::Base::Generic::parameter_info_nstart1()> Return an C parameter hashref suitable for use in a C, with default either 0 or 1. For example, # alone package Math::PlanePath::MySubclass; use constant parameter_info_array => [ Math::PlanePath::Base::Generic::parameter_info_nstart1() ]; # or with other parameters too package Math::PlanePath::MySubclass; use constant parameter_info_array => [ { name => 'something', type => 'integer', default => '123', }, Math::PlanePath::Base::Generic::parameter_info_nstart1(), ]; This function is not exportable since it's meant for a one-off call in an initializer and so no need to import it for repeated use. =back =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath/Base/Digits.pm0000644000175000017500000003037313734026653017673 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # # bit_join_lowtohigh package Math::PlanePath::Base::Digits; use 5.004; use strict; use vars '$VERSION','@ISA','@EXPORT_OK'; $VERSION = 129; use Exporter; @ISA = ('Exporter'); @EXPORT_OK = ('parameter_info_array', 'bit_split_lowtohigh', 'digit_split_lowtohigh', 'digit_join_lowtohigh', 'round_down_pow', 'round_up_pow'); # uncomment this to run the ### lines # use Smart::Comments; use constant parameter_info_radix2 => { name => 'radix', share_key => 'radix_2', display => 'Radix', type => 'integer', minimum => 2, default => 2, width => 3, description => 'Radix (number base).', }; use constant parameter_info_array => [ parameter_info_radix2() ]; #------------------------------------------------------------------------------ # ENHANCE-ME: Sometimes the $pow value is not wanted, # eg. SierpinskiArrowhead, though that tends to be approximation code rather # than exact range calculations etc. # sub round_down_pow { my ($n, $base) = @_; ### round_down_pow(): "$n base $base" # only for integer bases ### assert: $base == int($base) if ($n < $base) { return (1, 0); } # Math::BigInt and Math::BigRat overloaded log() return NaN, use integer # based blog() if (ref $n) { if ($n->isa('Math::BigRat')) { $n = int($n); } if ($n->isa('Math::BigInt') || $n->isa('Math::BigInt::Lite')) { ### use blog() ... my $exp = $n->copy->blog($base); ### exp: "$exp" return (Math::BigInt->new(1)->blsft($exp,$base), $exp); } } my $exp = int(log($n)/log($base)); my $pow = $base**$exp; ### n: ref($n)." $n" ### exp: ref($exp)." $exp" ### pow: ref($pow)." $pow" # check how $pow actually falls against $n, not sure should trust float # rounding in log()/log($base) # Crib: $n as first arg in case $n==BigFloat and $pow==BigInt if ($n < $pow) { ### hmm, int(log) too big, decrease ... $exp -= 1; $pow = $base**$exp; } elsif ($n >= $base*$pow) { ### hmm, int(log) too small, increase ... $exp += 1; $pow *= $base; } ### result ... ### pow: "$pow" ### exp: "$exp" ### $exp return ($pow, $exp); } sub round_up_pow { my ($n, $base) = @_; ### round_up_pow(): "$n base $base" # only for integer bases ### assert: $base == int($base) if ($n < 1) { return (1, 0); } # Math::BigInt and Math::BigRat overloaded log() return NaN, use integer # based blog() if (ref $n) { ### $n if ($n->isa('Math::BigRat')) { $n = int($n); } if ($n->isa('Math::BigInt') || $n->isa('Math::BigInt::Lite')) { ### use blog(): ref $n my $exp = $n->copy->blog($base); ### exp: $exp my $pow = (ref $n)->new(1)->blsft($exp,$base); # Crib: must have $n first to have Math::BigInt::Lite method preferred if ($n > $pow) { ### blog too small, increase ... $pow *= $base; $exp += 1; } return ($pow, $exp); } } my $exp = int(log($n)/log($base) + 1); my $pow = $base**$exp; ### n: ref($n)." $n" ### exp: ref($exp)." $exp" ### pow: ref($pow)." $pow" # check how $pow actually falls against $n, not sure should trust float # rounding in log()/log($base) # Crib: $n as first arg in case $n==BigFloat and $pow==BigInt if ($exp > 0 && $n <= $pow/$base) { ### hmm, int(log) too big, decrease... $exp -= 1; $pow = $base**$exp; } elsif ($n > $pow) { ### hmm, int(log)+1 too small, increase... $exp += 1; $pow *= $base; } return ($pow, $exp); } #------------------------------------------------------------------------------ { my %binary_to_base4 = ('00' => '0', '01' => '1', '10' => '2', '11' => '3'); my @bigint_coderef; $bigint_coderef[4] = sub { (my $str = $_[0]->as_bin) =~ s/^0b//; # strip leading 0b if (length($str) & 1) { $str = "0$str"; } $str =~ s/(..)/$binary_to_base4{$1}/ge; return reverse split //, $str; }; $bigint_coderef[8] = sub { (my $str = $_[0]->as_oct) =~ s/^0//; # strip leading 0 return reverse split //, $str; }; $bigint_coderef[10] = sub { return reverse split //, $_[0]->bstr; }; $bigint_coderef[16] = sub { (my $str = $_[0]->as_hex) =~ s/^0x//; # strip leading 0x return reverse map {hex} split //, $str; }; # In _divrem() and _digit_split_lowtohigh() divide using rem=n%d then # q=(n-rem)/d so that quotient is an exact division. If it's not exact # then goes to float and loses precision if UV=64bit NV=53bit. sub digit_split_lowtohigh { my ($n, $radix) = @_; ### _digit_split_lowtohigh(): $n $n || return; # don't return '0' from BigInt stringize if ($radix == 2) { return bit_split_lowtohigh($n); } my @ret; if (ref $n && $n->isa('Math::BigInt')) { if (my $coderef = $bigint_coderef[$radix]) { return $coderef->($_[0]); } $n = $n->copy; # for bdiv() modification do { (undef, my $digit) = $n->bdiv($radix); push @ret, $digit; } while ($n); if ($radix < 1_000_000) { # plain scalars if fit foreach (@ret) { $_ = $_->numify; # mutate array } } } else { do { my $digit = $n % $radix; push @ret, $digit; $n = int(($n - $digit) / $radix); } while ($n > 0); } return @ret; # array[0] low digit } } # 2**32 on a 32-bit UV, or 2**64 on 64-bit use constant 1.02 _UV_MAX_PLUS_1 => ((~0 >> 1) + 1) * 2.0; sub bit_split_lowtohigh { my ($n) = @_; my @ret; if ($n >= 1) { if (ref $n && $n->isa('Math::BigInt')) { (my $str = $n->as_bin) =~ s/^0b//; # strip leading 0b return reverse split //, $str; } if ($n <= _UV_MAX_PLUS_1) { return reverse split //, sprintf('%b',$n); } do { my $digit = $n % 2; push @ret, $digit; $n = int(($n - $digit) / 2); } while ($n); } return @ret; # array[0] low digit } #------------------------------------------------------------------------------ # $aref->[0] low digit # ENHANCE-ME: BigInt new(), from_bin(), from_oct(), from_hex() sub digit_join_lowtohigh { my ($aref, $radix, $zero) = @_; ### digit_join_lowtohigh() ... ### $aref ### $radix ### $zero my $n = (defined $zero ? $zero : 0); foreach my $digit (reverse @$aref) { # high to low ### $n $n *= $radix; $n += $digit; } ### $n return $n; } 1; __END__ =for stopwords Ryde Math-PlanePath lowtohigh Subclassing arrayref PlanePath hashref radix initializer =head1 NAME Math::PlanePath::Base::Digits -- helpers for digit based paths =for test_synopsis my $n =head1 SYNOPSIS use Math::PlanePath::Base::Digits 'digit_split_lowtohigh'; foreach my $digit (digit_split_lowtohigh ($n, 16)) { } =head1 DESCRIPTION This is a few generic helper functions for paths based on digits or powering. They're designed to work on plain Perl integers and floats and there's some special case support for C. =head1 EXPORTS Nothing is exported by default but each function below can be as in the usual L style, use Math::PlanePath::Base::Digits 'round_down_pow'; (But not C, for the reason described below.) =head1 FUNCTIONS =head2 Generic =over 4 =item C<($power, $exponent) = round_up_pow ($n, $radix)> =item C<($power, $exponent) = round_down_pow ($n, $radix)> Return the power of C<$radix> equal to or either higher or lower than C<$n>. For example ($pow, $exp) = round_down_pow (260, 2); # $pow==512 # the next higher power # $exp==9 # the exponent in that power # 2**9=512 is next above 260 ($pow, $exp) = round_down_pow (260, 2); # $pow==256 # the next lower power # $exp==8 # the exponent in that power # 2**8=256 is next below 260 =item C<@digits = digit_split_lowtohigh ($n, $radix)> =item C<@bits = bit_split_lowtohigh ($n)> Return a list of digits from C<$n> in base C<$radix>, or in binary. For example, @digits = digit_split_lowtohigh (12345, 10); # @digits = (5,4,3,2,1) # decimal digits low to high If C<$n==0> then the return is an empty list. The current code expects C<$n E= 0>. "lowtohigh" in the name tries to make it clear which way the digits are returned. C can be used to get high to low instead (see L). C is the same as C called with radix=2. =item C<$n = digit_join_lowtohigh ($arrayref, $radix)> =item C<$n = digit_join_lowtohigh ($arrayref, $radix, $zero)> Return a value made by joining digits from C<$arrayref> in base C<$radix>. For example, @digits = (5,4,3,2,1) # decimal digits low to high $n = digit_split_lowtohigh (\@digits, 10); # $n == 12345 Optional C<$zero> can be a 0 of an overloaded number type such as C to give a returned C<$n> of that type. =back =head2 Subclassing =over =item C<$aref = parameter_info_array()> Return an arrayref of a C parameter, default 2. This is designed to be imported into a PlanePath subclass as its C method. package Math::PlanePath::MySubclass; use Math::PlanePath::Base::Digits 'parameter_info_array'; The arrayref is [ { name => 'radix', share_key => 'radix_2', display => 'Radix', type => 'integer', minimum => 2, default => 2, width => 3, description => 'Radix (number base).', } ] =item C<$href = Math::PlanePath::Base::Digits::parameter_info_radix2()> Return the single C parameter hashref from the info above. This can be used when a subclass wants the radix parameter and other parameters too, package Math::PlanePath::MySubclass; use constant parameter_info_array => [ { name => 'something_else', type => 'integer', default => '123', }, Math::PlanePath::Base::Digits::parameter_info_radix2(), ]; If the "description" part should be more specific or more detailed then it could be overridden with for example { %{Math::PlanePath::Base::Digits::parameter_info_radix2()}, description => 'Radix, for both something and something.', }, This function is not exportable since it's meant for a one-off call in an initializer and so no need to import it for repeated use. =back =head1 SEE ALSO L, L L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut Math-PlanePath-129/lib/Math/PlanePath.pm0000644000175000017500000023762313774332005015600 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package Math::PlanePath; use 5.004; use strict; use vars '$VERSION'; $VERSION = 129; # uncomment this to run the ### lines # use Smart::Comments; # defaults use constant figure => 'square'; use constant default_n_start => 1; sub n_start { my ($self) = @_; if (ref $self && defined $self->{'n_start'}) { return $self->{'n_start'}; } else { return $self->default_n_start; } } sub arms_count { my ($self) = @_; return $self->{'arms'} || 1; } use constant class_x_negative => 1; use constant class_y_negative => 1; sub x_negative { $_[0]->class_x_negative } sub y_negative { $_[0]->class_y_negative } use constant x_negative_at_n => undef; use constant y_negative_at_n => undef; use constant n_frac_discontinuity => undef; use constant parameter_info_array => []; sub parameter_info_list { return @{$_[0]->parameter_info_array}; } # x_negative(),y_negative() existed before x_minimum(),y_minimum(), so # default x_minimum(),y_minimum() from those. sub x_minimum { my ($self) = @_; return ($self->x_negative ? undef : 0); } sub y_minimum { my ($self) = @_; return ($self->y_negative ? undef : 0); } use constant x_maximum => undef; use constant y_maximum => undef; sub sumxy_minimum { my ($self) = @_; ### PlanePath sumxy_minimum() ... if (defined (my $x_minimum = $self->x_minimum) && defined (my $y_minimum = $self->y_minimum)) { ### $x_minimum ### $y_minimum return $x_minimum + $y_minimum; } return undef; } use constant sumxy_maximum => undef; sub sumabsxy_minimum { my ($self) = @_; my $x_minimum = $self->x_minimum; my $y_minimum = $self->y_minimum; if (defined $x_minimum && $x_minimum >= 0 && defined $y_minimum && $y_minimum >= 0) { # X>=0 and Y>=0 so abs(X)+abs(Y) == X+Y return $self->sumxy_minimum; } return _max($x_minimum||0,0) + _max($y_minimum||0,0); } use constant sumabsxy_maximum => undef; use constant diffxy_minimum => undef; # # If the path is confined to the fourth quadrant, so X>=something and # Y<=something then a minimum X-Y exists. But fourth-quadrant-only path is # unusual, so don't bother with code checking that. # sub diffxy_minimum { # my ($self) = @_; # if (defined (my $y_maximum = $self->y_maximum) # && defined (my $x_minimum = $self->x_minimum)) { # return $x_minimum - $y_maximum; # } else { # return undef; # } # } # If the path is confined to the second quadrant, so X<=something and # Y>=something, then has a maximum X-Y. Presume that the x_maximum() and # y_minimum() occur together. # sub diffxy_maximum { my ($self) = @_; if (defined (my $y_minimum = $self->y_minimum) && defined (my $x_max = $self->x_maximum)) { return $x_max - $y_minimum; } else { return undef; } } # absdiffxy = abs(X-Y) sub absdiffxy_minimum { my ($self) = @_; # if X-Y all one sign, so X-Y>=0 or X-Y<=0, then abs(X-Y) from that my $m; if (defined($m = $self->diffxy_minimum) && $m >= 0) { return $m; } if (defined($m = $self->diffxy_maximum) && $m <= 0) { return - $m; } return 0; } sub absdiffxy_maximum { my ($self) = @_; # if X-Y constrained so min<=X-Y<=max then max abs(X-Y) one of the two ends if (defined (my $min = $self->diffxy_minimum) && defined (my $max = $self->diffxy_maximum)) { return _max(abs($min),abs($max)); } return undef; } # experimental default from x_minimum(),y_minimum() # FIXME: should use absx_minimum, absy_minimum, for paths outside first quadrant sub rsquared_minimum { my ($self) = @_; # The X and Y each closest to the origin. This assumes that point is # actually visited, but is likely to be close. my $x_minimum = $self->x_minimum; my $x_maximum = $self->x_maximum; my $y_minimum = $self->y_minimum; my $y_maximum = $self->y_maximum; my $x = (( defined $x_minimum && $x_minimum) > 0 ? $x_minimum : (defined $x_maximum && $x_maximum) < 0 ? $x_maximum : 0); my $y = (( defined $y_minimum && $y_minimum) > 0 ? $y_minimum : (defined $y_maximum && $y_maximum) < 0 ? $y_maximum : 0); return ($x*$x + $y*$y); # # Maybe initial point $self->n_to_xy($self->n_start)) as the default, # # but that's not the minimum on "wider" paths. # return 0; } use constant rsquared_maximum => undef; sub gcdxy_minimum { my ($self) = @_; ### gcdxy_minimum(): "visited=".($self->xy_is_visited(0,0)||0) return ($self->xy_is_visited(0,0) ? 0 # gcd(0,0)=0 : 1); # any other has gcd>=1 } use constant gcdxy_maximum => undef; use constant turn_any_left => 1; use constant turn_any_right => 1; use constant turn_any_straight => 1; #------------------------------------------------------------------------------ use constant dir_minimum_dxdy => (1,0); # East use constant dir_maximum_dxdy => (0,0); # supremum all angles use constant dx_minimum => undef; use constant dy_minimum => undef; use constant dx_maximum => undef; use constant dy_maximum => undef; # # =item C<$n = $path-E_UNDOCUMENTED__dxdy_list_at_n()> # # Return the N at which all possible dX,dY will have been seen. If there is # not a finite set of possible dX,dY steps then return C. # use constant 1.02; use constant _UNDOCUMENTED__dxdy_list => (); # default empty for not a finite list use constant _UNDOCUMENTED__dxdy_list_at_n => undef; # maybe dxdy_at_n() use constant _UNDOCUMENTED__dxdy_list_three => (2,0, # E -1,1, # NW -1,-1); # SW use constant _UNDOCUMENTED__dxdy_list_six => (2,0, # E 1,1, # NE -1,1, # NW -2,0, # W -1,-1, # SW 1,-1); # SE use constant _UNDOCUMENTED__dxdy_list_eight => (1,0, # E 1,1, # NE 0,1, # N -1,1, # NW -1,0, # W -1,-1, # SW 0,-1, # S 1,-1); # SE sub absdx_minimum { my ($self) = @_; # If dX>=0 then abs(dX)=dX always and absdx_minimum()==dx_minimum(). # This happens for column style paths like CoprimeColumns. # dX>0 is only for line paths so not very interesting. if (defined (my $dx_minimum = $self->dx_minimum)) { if ($dx_minimum >= 0) { return $dx_minimum; } } return 0; } sub absdx_maximum { my ($self) = @_; if (defined (my $dx_minimum = $self->dx_minimum) && defined (my $dx_maximum = $self->dx_maximum)) { return _max(abs($dx_minimum),abs($dx_maximum)); } return undef; } sub absdy_minimum { my ($self) = @_; # if dY>=0 then abs(dY)=dY always and absdy_minimum()==dy_minimum() if (defined (my $dy_minimum = $self->dy_minimum)) { if ($dy_minimum >= 0) { return $dy_minimum; } } return 0; } sub absdy_maximum { my ($self) = @_; if (defined (my $dy_minimum = $self->dy_minimum) && defined (my $dy_maximum = $self->dy_maximum)) { return _max(abs($dy_minimum),abs($dy_maximum)); } else { return undef; } } use constant dsumxy_minimum => undef; use constant dsumxy_maximum => undef; use constant ddiffxy_minimum => undef; use constant ddiffxy_maximum => undef; #------------------------------------------------------------------------------ sub new { my $class = shift; return bless { @_ }, $class; } { my %parameter_info_hash; sub parameter_info_hash { my ($class_or_self) = @_; my $class = (ref $class_or_self || $class_or_self); return ($parameter_info_hash{$class} ||= { map { $_->{'name'} => $_ } $class_or_self->parameter_info_list }); } } sub xy_to_n_list { ### xy_to_n_list() ... if (defined (my $n = shift->xy_to_n(@_))) { ### $n return $n; } ### empty ... return; } sub xy_is_visited { my ($self, $x, $y) = @_; ### xy_is_visited(): "$x,$y is ndefined=".defined($self->xy_to_n($x,$y)) return defined($self->xy_to_n($x,$y)); } sub n_to_n_list { my ($self, $n) = @_; my ($x,$y) = $self->n_to_xy($n) or return; return $self->xy_to_n_list($x,$y); } sub n_to_dxdy { my ($self, $n) = @_; ### n_to_dxdy(): $n my ($x,$y) = $self->n_to_xy ($n) or return; my ($next_x,$next_y) = $self->n_to_xy ($n + $self->arms_count) or return; ### points: "$x,$y $next_x,$next_y" return ($next_x - $x, $next_y - $y); } sub n_to_rsquared { my ($self, $n) = @_; my ($x,$y) = $self->n_to_xy($n) or return undef; return $x*$x + $y*$y; } sub n_to_radius { my ($self, $n) = @_; my $rsquared = $self->n_to_rsquared($n); return (defined $rsquared ? sqrt($rsquared) : undef); } sub xyxy_to_n_list { my ($self, $x1,$y1, $x2,$y2) = @_; my @n1 = $self->xy_to_n_list($x1,$y1) or return; my @n2 = $self->xy_to_n_list($x2,$y2) or return; my $arms = $self->arms_count; return grep { my $want_n2 = $_ + $arms; grep {$_ == $want_n2} @n2 # seek $n2 which is this $n1+$arms } @n1; } sub xyxy_to_n { my $self = shift; my @n_list = $self->xyxy_to_n_list(@_); return $n_list[0]; } sub xyxy_to_n_list_either { my ($self, $x1,$y1, $x2,$y2) = @_; my @n1 = $self->xy_to_n_list($x1,$y1) or return; my @n2 = $self->xy_to_n_list($x2,$y2) or return; my $arms = $self->arms_count; my @n_list; foreach my $n1 (@n1) { foreach my $n2 (@n2) { if (abs($n1 - $n2) == $arms) { push @n_list, _min($n1,$n2); } } } @n_list = sort {$a<=>$b} @n_list; return @n_list; } sub xyxy_to_n_either { my $self = shift; my @n_list = $self->xyxy_to_n_list_either(@_); return $n_list[0]; } #------------------------------------------------------------------------------ # turns sub _UNDOCUMENTED__n_to_turn_LSR { my ($self, $n) = @_; ### _UNDOCUMENTED__n_to_turn_LSR(): $n my ($dx,$dy) = $self->n_to_dxdy($n - $self->arms_count) or return undef; my ($next_dx,$next_dy) = $self->n_to_dxdy($n) or return undef; ### dxdy: "$dx,$dy and $next_dx,$next_dy arms=".$self->arms_count return (($next_dy * $dx <=> $next_dx * $dy) || 0); # 1,0,-1 } #------------------------------------------------------------------------------ # tree sub is_tree { my ($self) = @_; return $self->tree_n_num_children($self->n_start); } use constant tree_n_parent => undef; # default always no parent use constant tree_n_children => (); # default no children sub tree_n_num_children { my ($self, $n) = @_; if ($n >= $self->n_start) { my @n_list = $self->tree_n_children($n); return scalar(@n_list); } else { return undef; } } # For non-trees n_num_children() always returns 0 so that's the single # return here. use constant tree_num_children_list => (0); sub tree_num_children_minimum { my ($self) = @_; return ($self->tree_num_children_list)[0]; } sub tree_num_children_maximum { my ($self) = @_; return ($self->tree_num_children_list)[-1]; } sub tree_any_leaf { my ($self) = @_; return ($self->tree_num_children_minimum == 0); } use constant tree_n_to_subheight => 0; # default all leaf node use constant tree_n_to_depth => undef; use constant tree_depth_to_n => undef; sub tree_depth_to_n_end { my ($self, $depth) = @_; if ($depth >= 0 && defined (my $n = $self->tree_depth_to_n($depth+1))) { ### tree_depth_to_n_end(): $depth, $n return $n-1; } else { return undef; } } sub tree_depth_to_n_range { my ($self, $depth) = @_; if (defined (my $n = $self->tree_depth_to_n($depth)) && defined (my $n_end = $self->tree_depth_to_n_end($depth))) { return ($n, $n_end); } return; } sub tree_depth_to_width { my ($self, $depth) = @_; if (defined (my $n = $self->tree_depth_to_n($depth)) && defined (my $n_end = $self->tree_depth_to_n_end($depth))) { return $n_end - $n + 1; } return undef; } sub tree_num_roots { my ($self) = @_; my @root_n_list = $self->tree_root_n_list; return scalar(@root_n_list); } sub tree_root_n_list { my ($self) = @_; my $n_start = $self->n_start; my @ret; for (my $n = $n_start; ; $n++) { # stop on finding a non-root (has a parent), or a non-tree path has no # children at all if (defined($self->tree_n_parent($n)) || ! $self->tree_n_num_children($n)) { last; } push @ret, $n; } return @ret; } # Generic search upwards. Not fast, but works with past Toothpick or # anything slack which doesn't have own tree_n_root(). When only one root # there's no search. sub tree_n_root { my ($self, $n) = @_; my $num_roots = $self->tree_num_roots; if ($num_roots == 0) { return undef; # not a tree } my $n_start = $self->n_start; unless ($n >= $n_start) { # and warn if $n==undef return undef; # -inf or NaN } if ($num_roots == 1) { return $n_start; # only one root, no search } for (;;) { my $n_parent = $self->tree_n_parent($n); if (! defined $n_parent) { return $n; # found root } unless ($n_parent < $n) { return undef; # +inf or something bad not making progress } $n = $n_parent; } } # Generic search for where no more children. # But must watch out for infinite lets, and might also watch out for # rounding or overflow. # # sub path_tree_n_to_subheight { # my ($path, $n) = @_; # ### path_tree_n_to_subheight(): "$n" # # if (is_infinite($n)) { # return $n; # } # my $max = $path->tree_n_to_depth($n) + 10; # my @n = ($n); # my $height = 0; # do { # @n = map {$path->tree_n_children($_)} @n # or return $height; # $height++; # } while (@n && $height < $max); # # ### height infinite ... # return undef; # } #------------------------------------------------------------------------------ # levels use constant level_to_n_range => (); use constant n_to_level => undef; #------------------------------------------------------------------------------ # shared undocumented internals sub _max { my $max = 0; foreach my $i (1 .. $#_) { if ($_[$i] > $_[$max]) { $max = $i; } } return $_[$max]; } sub _min { my $min = 0; foreach my $i (1 .. $#_) { if ($_[$i] < $_[$min]) { $min = $i; } } return $_[$min]; } # Return square root of $x, rounded towards zero. # Recent BigFloat and BigRat need explicit conversion to BigInt, they no # longer do that in int(). sub _sqrtint { my ($x) = @_; if (ref $x) { if ($x->isa('Math::BigFloat') || $x->isa('Math::BigRat')) { $x = $x->copy->as_int; } } return int(sqrt($x)); } use Math::PlanePath::Base::Generic 'round_nearest'; sub _rect_for_first_quadrant { my ($self, $x1,$y1, $x2,$y2) = @_; $x1 = round_nearest($x1); $y1 = round_nearest($y1); $x2 = round_nearest($x2); $y2 = round_nearest($y2); ($x1,$x2) = ($x2,$x1) if $x1 > $x2; ($y1,$y2) = ($y2,$y1) if $y1 > $y2; if ($x2 < 0 || $y2 < 0) { return; } return ($x1,$y1, $x2,$y2); } # return ($quotient, $remainder) sub _divrem { my ($n, $d) = @_; if (ref $n && $n->isa('Math::BigInt')) { my ($quot,$rem) = $n->copy->bdiv($d); if (! ref $d || $d < 1_000_000) { $rem = $rem->numify; # plain remainder if fits } return ($quot, $rem); } my $rem = $n % $d; return (int(($n-$rem)/$d), # exact division stays in UV $rem); } # return $remainder, modify $n # the scalar $_[0] is modified, but if it's a BigInt then a new BigInt is made # and stored there, the bigint value is not changed sub _divrem_mutate { my $d = $_[1]; my $rem; if (ref $_[0] && $_[0]->isa('Math::BigInt')) { ($_[0], $rem) = $_[0]->copy->bdiv($d); # quot,rem in array context if (! ref $d || $d < 1_000_000) { return $rem->numify; # plain remainder if fits } } else { $rem = $_[0] % $d; $_[0] = int(($_[0]-$rem)/$d); # exact division stays in UV } return $rem; } 1; __END__ =for stopwords PlanePath Ryde Math-PlanePath Math-PlanePath-Toothpick 7-gonals 8-gonal (step+2)-gonal heptagonals octagonals bignum multi-arm eg PerlMagick NaN NaNs subclasses incrementing arrayref hashref filename enum radix ie dX dY dX,dY Rsquared radix SUBCLASSING Ns onwards supremum radix radix-1 octant dSum dDiffXY RSquared Manhattan SumAbs infimum uninitialized factorization characterize characterized =head1 NAME Math::PlanePath -- points on a path through the 2-D plane =head1 SYNOPSIS use Math::PlanePath; # only a base class, see the subclasses for actual operation =head1 DESCRIPTION This is a base class for some mathematical paths which map an integer position C<$n> to and from coordinates C<$x,$y> in the 2D plane. The current classes include the following. The intention is that any C is a PlanePath, and supporting base classes or related things are further down like C. =for my_pod list begin SquareSpiral four-sided spiral PyramidSpiral square base pyramid TriangleSpiral equilateral triangle spiral TriangleSpiralSkewed equilateral skewed for compactness DiamondSpiral four-sided spiral, looping faster PentSpiral five-sided spiral PentSpiralSkewed five-sided spiral, compact HexSpiral six-sided spiral HexSpiralSkewed six-sided spiral skewed for compactness HeptSpiralSkewed seven-sided spiral, compact AnvilSpiral anvil shape OctagramSpiral eight pointed star KnightSpiral an infinite knight's tour CretanLabyrinth 7-circuit extended infinitely SquareArms four-arm square spiral DiamondArms four-arm diamond spiral AztecDiamondRings four-sided rings HexArms six-arm hexagonal spiral GreekKeySpiral square spiral with Greek key motif MPeaks "M" shape layers SacksSpiral quadratic on an Archimedean spiral VogelFloret seeds in a sunflower TheodorusSpiral unit steps at right angles ArchimedeanChords unit chords on an Archimedean spiral MultipleRings concentric circles PixelRings concentric rings of midpoint pixels FilledRings concentric rings of pixels Hypot points by distance HypotOctant first octant points by distance TriangularHypot points by triangular distance PythagoreanTree X^2+Y^2=Z^2 by trees PeanoCurve 3x3 self-similar quadrant PeanoDiagonals across unit squares WunderlichSerpentine transpose parts of PeanoCurve HilbertCurve 2x2 self-similar quadrant HilbertSides along sides of unit squares HilbertSpiral 2x2 self-similar whole-plane ZOrderCurve replicating Z shapes GrayCode Gray code splits WunderlichMeander 3x3 "R" pattern quadrant BetaOmega 2x2 self-similar half-plane AR2W2Curve 2x2 self-similar of four parts KochelCurve 3x3 self-similar of two parts DekkingCurve 5x5 self-similar, edges DekkingCentres 5x5 self-similar, centres CincoCurve 5x5 self-similar ImaginaryBase replicate in four directions ImaginaryHalf half-plane replicate three directions CubicBase replicate in three directions SquareReplicate 3x3 replicating squares CornerReplicate 2x2 replicating "U" LTiling self-similar L shapes DigitGroups digits grouped by zeros FibonacciWordFractal turns by Fibonacci word bits Flowsnake self-similar hexagonal tile traversal FlowsnakeCentres likewise but centres of hexagons GosperReplicate self-similar hexagonal tiling GosperIslands concentric island rings GosperSide single side or radial QuintetCurve self-similar "+" traversal QuintetCentres likewise but centres of squares QuintetReplicate self-similar "+" tiling DragonCurve paper folding DragonRounded paper folding rounded corners DragonMidpoint paper folding segment midpoints AlternatePaper alternating direction folding AlternatePaperMidpoint alternating direction folding, midpoints TerdragonCurve ternary dragon TerdragonRounded ternary dragon rounded corners TerdragonMidpoint ternary dragon segment midpoints AlternateTerdragon alternate ternary dragon R5DragonCurve radix-5 dragon curve R5DragonMidpoint radix-5 dragon curve midpoints CCurve "C" curve ComplexPlus base i+realpart ComplexMinus base i-realpart, including twindragon ComplexRevolving revolving base i+1 SierpinskiCurve self-similar right-triangles SierpinskiCurveStair self-similar right-triangles, stair-step HIndexing self-similar right-triangles, squared up KochCurve replicating triangular notches KochPeaks two replicating notches KochSnowflakes concentric notched 3-sided rings KochSquareflakes concentric notched 4-sided rings QuadricCurve eight segment zig-zag QuadricIslands rings of those zig-zags SierpinskiTriangle self-similar triangle by rows SierpinskiArrowhead self-similar triangle connectedly SierpinskiArrowheadCentres likewise but centres of triangles Rows fixed-width rows Columns fixed-height columns Diagonals diagonals between X and Y axes DiagonalsAlternating diagonals Y to X and back again DiagonalsOctant diagonals between Y axis and X=Y centre Staircase stairs down from the Y to X axes StaircaseAlternating stairs Y to X and back again Corner expanding stripes around a corner CornerAlternating expanding up and down around a corner PyramidRows expanding stacked rows pyramid PyramidSides along the sides of a 45-degree pyramid CellularRule cellular automaton by rule number CellularRule54 cellular automaton rows pattern CellularRule57 cellular automaton (rule 99 mirror too) CellularRule190 cellular automaton (rule 246 mirror too) UlamWarburton cellular automaton diamonds UlamWarburtonQuarter cellular automaton quarter-plane DiagonalRationals rationals X/Y by diagonals FactorRationals rationals X/Y by prime factorization GcdRationals rationals X/Y by rows with GCD integer RationalsTree rationals X/Y by tree FractionsTree fractions 0 in the Math-PlanePath sources for a sample printout of numbers from selected paths or all paths. =head2 Number Types The C<$n> and C<$x,$y> parameters can be either integers or floating point. The paths are meant to do something sensible with fractions but expect round-off for big floating point exponents. Floating point infinities (when available) give NaN or infinite returns of some kind (some unspecified kind as yet). C on negative infinity is an empty return the same as other negative C<$n>. Floating point NaNs (when available) give NaN, infinite, or empty/undef returns, but again of some unspecified kind as yet. Most of the classes can operate on overloaded number types as inputs and give corresponding outputs. Math::BigInt maybe perl 5.8 up for ** operator Math::BigRat Math::BigFloat Number::Fraction 1.14 or higher for abs() A few classes might truncate a bignum or a fraction to a float as yet. In general the intention is to make the calculations generic enough to act on any sensible number type. Recent enough versions of the bignum modules might be required, perhaps C of Perl 5.8 or higher for C<**> exponentiation operator. For reference, an C input as C<$n>, C<$x>, C<$y>, etc, is designed to provoke an uninitialized value warning when warnings are enabled. Perhaps that will change, but the warning at least prevents bad inputs going unnoticed. =head1 FUNCTIONS In the following C is one of the various subclasses, see the list above and under L. =head2 Constructor =over 4 =item C<$path = Math::PlanePath::Foo-Enew (key=Evalue, ...)> Create and return a new path object. Optional key/value parameters may control aspects of the object. =back =head2 Coordinate Methods =over =item C<($x,$y) = $path-En_to_xy ($n)> Return X,Y coordinates of point C<$n> on the path. If there's no point C<$n> then the return is an empty list. For example my ($x,$y) = $path->n_to_xy (-123) or next; # no negatives in $path Paths start from C<$path-En_start()> below, though some will give a position for N=0 or N=-0.5 too. =item C<($dx,$dy) = $path-En_to_dxdy ($n)> Return the change in X and Y going from point C<$n> to point C<$n+1>, or for paths with multiple arms from C<$n> to C<$n+$arms_count> (thus advancing one point along the arm of C<$n>). + $n+1 == $next_x,$next_y ^ | | $dx = $next_x - $x + $n == $x,$y $dy = $next_y - $y C<$n> can be fractional and in that case the dX,dY is from that fractional C<$n> position to C<$n+1> (or C<$n+$arms>). frac $n+1 == $next_x,$next_y v integer *---+---- | / | / |/ $dx = $next_x - $x frac + $n == $x,$y $dy = $next_y - $y | integer * In both cases C is the difference C<$dx=$next_x-$x, $dy=$next_y-$y>. Currently for most paths it's merely two C calls to calculate the two points, but some paths can calculate a dX,dY with a little less work. =item C<$rsquared = $path-En_to_radius ($n)> =item C<$rsquared = $path-En_to_rsquared ($n)> Return the radial distance R=sqrt(X^2+Y^2) of point C<$n>, or the radius squared R^2=X^2+Y^2. If there's no point C<$n> then the return is C. For a few paths, these might be calculated with less work than C. For example the C is simply R^2=N, or the C path with its default step=6 has an integer radius for integer C<$n> whereas C<$x,$y> are fractional (and so inexact). =item C<$n = $path-Exy_to_n ($x,$y)> Return the N point number at coordinates C<$x,$y>. If there's nothing at C<$x,$y> then return C. my $n = $path->xy_to_n(20,20); if (! defined $n) { next; # nothing at this X,Y } C<$x> and C<$y> can be fractional and the path classes will give an integer C<$n> which contains C<$x,$y> within a unit square, circle, or intended figure centred on the integer C<$n>. For paths which completely fill the plane there's always an C<$n> to return, but for the spread-out paths an C<$x,$y> position may fall in between (no C<$n> close enough) and give C. =item C<@n_list = $path-Exy_to_n_list ($x,$y)> Return a list of N point numbers at coordinates C<$x,$y>. If there's nothing at C<$x,$y> then return an empty list. my @n_list = $path->xy_to_n(20,20); Most paths have just a single N for a given X,Y but some such as C and C have multiple N's and this method returns all of them. Currently all paths have a finite number of N at a given location. It's unspecified what might happen for an infinite list, if that ever occurred. =item C<@n_list = $path-En_to_n_list ($n)> Return a list of all N point numbers at the location of C<$n>. This is equivalent to C. The return list includes C<$n> itself. If there is no C<$n> in the path then return an empty list. This function is convenient for paths like C or C with double or triple visited points so an N may have other N at the same location. =item C<$bool = $path-Exy_is_visited ($x,$y)> Return true if C<$x,$y> is visited. This is equivalent to defined($path->xy_to_n($x,$y)) Some paths cover the plane and for them C is always true. For others it might be less work to test a point than to calculate its C<$n>. =item C<$n = $path-Exyxy_to_n($x1,$y1, $x2,$y2)> =item C<$n = $path-Exyxy_to_n_either($x1,$y1, $x2,$y2)> =item C<@n_list = $path-Exyxy_to_n_list($x1,$y1, $x2,$y2)> =item C<@n_list = $path-Exyxy_to_n_list_either($x1,$y1, $x2,$y2)> Return <$n> which goes from C<$x1,$y1> to C<$x2,$y2>. <$n> is at C<$x1,$y1> and C<$n+1> is at C<$x2,$y2>, or for a multi-arm path C<$n+$arms>, so a step along the same arm. If there's no such C<$n> then return C. The C forms allow <$n> in either direction, so C<$x1,$y1> to C<$x2,$y2> or the other way C<$x2,$y2> to C<$x1,$y1>. The C forms return a list of all C<$n> going between C<$x1,$y1> and C<$x2,$y2>. For example in C some segments are traversed twice, once in each direction. The possible N values at each X,Y are determined the same way as for C. =item C<($n_lo, $n_hi) = $path-Erect_to_n_range ($x1,$y1, $x2,$y2)> Return a range of N values covering the rectangle with corners at C<$x1>,C<$y1> and C<$x2>,C<$y2>. The range is inclusive. For example, my ($n_lo, $n_hi) = $path->rect_to_n_range (-5,-5, 5,5); foreach my $n ($n_lo .. $n_hi) { my ($x, $y) = $path->n_to_xy($n) or next; print "$n $x,$y"; } The return might be an over-estimate of the N range required to cover the rectangle. Even if the range is exact, the nature of the path may mean many points between C<$n_lo> and C<$n_hi> are outside the rectangle. But the range is at least a lower and upper bound on the N values which occur in the rectangle. Classes which guarantee an exact lo/hi say so in their docs. C<$n_hi> is usually no more than an extra partial row, revolution, or self-similar level. C<$n_lo> might be merely the starting C<$path-En_start()>, which is fine if the origin is in the desired rectangle but away from the origin could actually start higher. C<$x1>,C<$y1> and C<$x2>,C<$y2> can be fractional. If they partly overlap some N figures then those N's are included in the return. If there's no points in the rectangle then the return can be a "crossed" range like C<$n_lo=1>, C<$n_hi=0> (which makes a C do no loops). But C may not always notice there's no points in the rectangle and might instead return an over-estimate. =back =head2 Descriptive Methods =over =item C<$n = $path-En_start()> Return the first N in the path. The start is usually either 0 or 1 according to what is most natural for the path. Some paths have an C parameter to control the numbering. Some classes have secret dubious undocumented support for N values below this start (zero or negative), but C is the intended starting point. =item C<$f = $path-En_frac_discontinuity()> Return the fraction of N at which there may be discontinuities in the path. For example if there's a jump in the coordinates between N=7.4999 and N=7.5 then the returned C<$f> is 0.5. Or C<$f> is 0 if there's a discontinuity between 6.999 and 7.0. If there's no discontinuities in the path then the return is C. That means for example fractions between N=7 to N=8 give smooth continuous X,Y values (of some kind). This is mainly of interest for drawing line segments between N points. If there's discontinuities then the idea is to draw from say N=7.0 to N=7.499 and then another line from N=7.5 to N=8. =item C<$arms = $path-Earms_count()> Return the number of arms in a "multi-arm" path. For example in C this is 4 and each arm increments in turn, so the first arm is N=1,5,9,13,etc starting from C<$path-En_start()> and incrementing by 4 each time. =item C<$bool = $path-Ex_negative()> =item C<$bool = $path-Ey_negative()> Return true if the path extends into negative X coordinates and/or negative Y coordinates respectively. =item C<$bool = Math::PlanePath::Foo-Eclass_x_negative()> =item C<$bool = Math::PlanePath::Foo-Eclass_y_negative()> =item C<$bool = $path-Eclass_x_negative()> =item C<$bool = $path-Eclass_y_negative()> Return true if any paths made by this class extend into negative X coordinates and/or negative Y coordinates, respectively. For some classes the X or Y extent may depend on parameter values. =item C<$n = $path-Ex_negative_at_n()> =item C<$n = $path-Ey_negative_at_n()> Return the integer N where X or Y respectively first goes negative, or return C if it does not go negative (C or C respectively is false). =item C<$x = $path-Ex_minimum()> =item C<$y = $path-Ey_minimum()> =item C<$x = $path-Ex_maximum()> =item C<$y = $path-Ey_maximum()> Return the minimum or maximum of the X or Y coordinate reached by integer N values in the path. If there's no minimum or maximum then return C. =item C<$dx = $path-Edx_minimum()> =item C<$dx = $path-Edx_maximum()> =item C<$dy = $path-Edy_minimum()> =item C<$dy = $path-Edy_maximum()> Return the minimum or maximum change dX, dY occurring in the path for integer N to N+1. For a multi-arm path the change is N to N+arms so it's the change along the same arm. Various paths which go by rows have non-decreasing Y. For them C is 0. =cut # =item C<@dxdy_list = $path-Edxdy_list()> # # If C<$path> has a finite set of dX,dY steps then return them as a list. # If C<$path> has an infinite set of dX,dY steps then return an empty list. # # $dx1,$dy1, $dx2,$dy2, $dx3,$dy3, ... # # The points are returned in order of angle around starting from East # (dXE0,dY=0), and by increasing length among those of the same angle. If # dX=0,dY=0 occurs (which it doesn't in any current path) then that would be # first in the return list. =pod =item C<$adx = $path-Eabsdx_minimum()> =item C<$adx = $path-Eabsdx_maximum()> =item C<$ady = $path-Eabsdy_minimum()> =item C<$ady = $path-Eabsdy_maximum()> Return the minimum or maximum change abs(dX) or abs(dY) occurring in the path for integer N to N+1. For a multi-arm path, the change is N to N+arms so it's the change along the same arm. C is simply max(dXmax,-dXmin), the biggest change either positive or negative. C similarly. C is 0 if dX=0 occurs anywhere in the path, which means any vertical step. If X always changes then C will be something bigger than 0. C likewise 0 if any horizontal dY=0, or bigger if Y always changes. =item C<$sum = $path-Esumxy_minimum()> =item C<$sum = $path-Esumxy_maximum()> Return the minimum or maximum values taken by coordinate sum X+Y reached by integer N values in the path. If there's no minimum or maximum then return C. S=X+Y is an anti-diagonal. A path which is always right and above some anti-diagonal has a minimum. Some paths might be entirely left and below and so have a maximum, though that's unusual. \ Path always above \ | has minimum S=X+Y \| ---o---- Path always below |\ has maximum S=X+Y | \ \ S=X+Y =item C<$sum = $path-Esumabsxy_minimum()> =item C<$sum = $path-Esumabsxy_maximum()> Return the minimum or maximum values taken by coordinate sum abs(X)+abs(Y) reached by integer N values in the path. A minimum always exists but if there's no maximum then return C. SumAbs=abs(X)+abs(Y) is sometimes called the "taxi-cab" or "Manhattan" distance, being how far to travel through a square-grid city to get to X,Y. C is then how close to the origin the path extends. SumAbs can also be interpreted geometrically as numbering the anti-diagonals of the quadrant containing X,Y, which is equivalent to asking which diamond shape X,Y falls on. C is then the smallest such diamond reached by the path. | /|\ SumAbs = which diamond X,Y falls on / | \ / | \ -----o----- \ | / \ | / \|/ | =item C<$diffxy = $path-Ediffxy_minimum()> =item C<$diffxy = $path-Ediffxy_maximum()> Return the minimum or maximum values taken by coordinate difference X-Y reached by integer N values in the path. If there's no minimum or maximum then return C. D=X-Y is a leading diagonal. A path which is always right and below such a diagonal has a minimum, for example C. A path which is always left and above some diagonal has a maximum D=X-Y. For example various wedge-like paths such as C in its default step=2, and "upper octant" paths have a maximum. / D=X-Y Path always below | / has maximum D=X-Y |/ ---o---- /| / | Path always above / has minimum D=X-Y =item C<$absdiffxy = $path-Eabsdiffxy_minimum()> =item C<$absdiffxy = $path-Eabsdiffxy_maximum()> Return the minimum or maximum values taken by abs(X-Y) for integer N in the path. The minimum is 0 or more. If there's maximum then return C. abs(X-Y) can be interpreted geometrically as the distance away from the X=Y diagonal and measured at right-angles to that line. d=abs(X-Y) X=Y line ^ / \ / \/ /\ / \ / \ o v / d=abs(X-Y) Paths which visit the X=Y line (or approach it as an infimum) have C. Otherwise C is how close they come to the line. If the path is entirely below the X=Y line so XE=Y then X-Y>=0 and C is the same as C. If the path is entirely below the X=Y line then C is S>. =item C<$dsumxy = $path-Edsumxy_minimum()> =item C<$dsumxy = $path-Edsumxy_maximum()> =item C<$ddiffxy = $path-Eddiffxy_minimum()> =item C<$ddiffxy = $path-Eddiffxy_maximum()> Return the minimum or maximum change dSum or dDiffXY occurring in the path for integer N to N+1. For a multi-arm path, the change is N to N+arms so it's the change along the same arm. =item C<$rsquared = $path-Ersquared_minimum()> =item C<$rsquared = $path-Ersquared_maximum()> Return the minimum or maximum Rsquared = X^2+Y^2 reached by integer N values in the path. If there's no minimum or maximum then return C. Rsquared is always E= 0 so it always has a minimum. The minimum will be more than 0 for paths which don't include the origin X=0,Y=0. RSquared generally has no maximum since the paths usually extend infinitely in some direction. C returns C in that case. =cut # =item C<$gcd = $path-Egcdxy_minimum()> # # =item C<$gcd = $path-Egcdxy_maximum()> # # Return the minimum or maximum GCD(X,Y) reached by integer N values in the # path. If there's no minimum or maximum then return C. # # C is always 0 or more since the sign of X and Y is ignored # for taking the GCD. GCD(0,0)=0 is the only GCD=0. X!=0 or Y!=0 gives # GCD(X,Y)E0. So the minimum is 0 if X=0,Y=0 is visited and E0 if # not. # # C is usually C since there's no limit to the GCD. # Paths such as C where X,Y have no common factor have # C returning 1. =pod =item C<($dx,$dy) = $path-Edir_minimum_dxdy()> =item C<($dx,$dy) = $path-Edir_maximum_dxdy()> Return a vector which is the minimum or maximum angle taken by a step integer N to N+1, or for a multi-arm path N to N+arms, so it's the change along the same arm. Directions are reckoned anti-clockwise around from the X axis. | * dX=2,dY=2 dX=-1,dY=1 * | / \|/ ------+----* dX=1,dY=0 | | * dX=0,dY=-1 A path which is always goes N,S,E,W such as the C has minimum East dX=1,dY=0 and maximum South dX=0,dY=-1. Paths which go diagonally may have different limits. For example the C goes in 2x1 steps and so has minimum East-North-East dX=2,dY=1 and maximum East-South-East dX=2,dY=-1. If the path has directions approaching 360 degrees then C is 0,0 which should be taken to mean a full circle as a supremum. For example C. If the path only ever goes East then the maximum is East dX=1,dY=0, and the minimum the same. This isn't particularly interesting, but arises for example in the C path height=0. =item C<$bool = $path-Eturn_any_left()> =item C<$bool = $path-Eturn_any_right()> =item C<$bool = $path-Eturn_any_straight()> Return true if the path turns left, right, or straight (which includes 180deg reverse) at any integer N. N+1 left N-1 -------- N --> N+1 straight N+1 right A line from N-1 to N is a current direction and the turn at N is then whether point N+1 is to the left or right of that line. Directly along the line is straight, and so is anything directly behind as a reverse. This is the turn style of L. =item C<$str = $path-Efigure()> Return a string name of the figure (shape) intended to be drawn at each C<$n> position. This is currently either "square" side 1 centred on $x,$y "circle" diameter 1 centred on $x,$y Of course this is only a suggestion since PlanePath doesn't draw anything itself. A figure like a diamond for instance can look good too. =back =head2 Tree Methods Some paths are structured like a tree where each N has a parent and possibly some children. 123 / | \ 456 999 458 / / \ 1000 1001 1005 The N numbering and any relation to X,Y positions varies among the paths. Some are numbered by rows in breadth-first style and some have children with X,Y positions adjacent to their parent, but that shouldn't be assumed, only that there's a parent-child relation down from some set of root nodes. =over =item C<$bool = $path-Eis_tree()> Return true if C<$path> is a tree. The various tree methods have empty or C returns on non-tree paths. Often it's enough to check for that from a desired method rather than a separate C check. =item C<@n_children = $path-Etree_n_children($n)> Return a list of N values which are the child nodes of C<$n>, or return an empty list if C<$n> has no children. There could be no children either because C<$path> is not a tree or because there's no children at a particular C<$n>. =item C<$num = $path-Etree_n_num_children($n)> Return the number of children of C<$n>, or 0 if C<$n> has no children, or C if S n_start()>> (ie. before the start of the path). If the tree is considered as a directed graph then this is the "out-degree" of C<$n>. =item C<$n_parent = $path-Etree_n_parent($n)> Return the parent node of C<$n>, or C if it has no parent. There is no parent at the root node of the tree, or one of multiple roots, or if C<$path> is not a tree. =item C<$n_root = $path-Etree_n_root ($n)> Return the N which is the root node of C<$n>. This is the top of the tree as would be found by following C repeatedly. The return is C if there's no C<$n> point or if C<$path> is not a tree. =item C<$depth = $path-Etree_n_to_depth($n)> Return the depth of node C<$n>, or C if there's no point C<$n>. The top of the tree is depth=0, then its children are depth=1, etc. The depth is a count of how many parent, grandparent, etc, levels are above C<$n>, ie. until reaching C returning C. For non-tree paths C is always C and C is always 0. =item C<$n_lo = $path-Etree_depth_to_n($depth)> =item C<$n_hi = $path-Etree_depth_to_n_end($depth)> =item C<($n_lo, $n_hi) = $path-Etree_depth_to_n_range ($depth)> Return the first or last N, or both those N, for tree level C<$depth> in the path. If there's no such C<$depth> or if C<$path> is not a tree then return C, or for C return an empty list. The points C<$n_lo> through C<$n_hi> might not necessarily all be at C<$depth>. It's possible for depths to be interleaved or intermixed in the point numbering. But many paths are breadth-wise successive rows and for them C<$n_lo> to C<$n_hi> inclusive is all C<$depth>. C<$n_hi> can only exist if the row has a finite number of points. That's true of all current paths, but perhaps allowance ought to be made for C<$n_hi> as C or some such if there is no maximum N for some row. =item C<$num = $path-Etree_depth_to_width ($depth)> Return the number of points at C<$depth> in the tree. If there's no such C<$depth> or C<$path> is not a tree then return C. =item C<$height = $path-Etree_n_to_subheight($n)> Return the height of the sub-tree starting at C<$n>, or C if infinite. The height of a tree is the longest distance down to a leaf node. For example, ... N subheight \ --- --------- 6 7 8 0 undef \ \ / 1 undef 3 4 5 2 2 \ \ / 3 undef 1 2 4 1 \ / 5 0 0 ... At N=0 and all of the left side the tree continues infinitely so the sub-height there is C for infinite. For N=2 the sub-height is 2 because the longest path down is 2 levels (to N=7 or N=8). For a leaf node such as N,=5 the sub-height is 0. =back =head2 Tree Descriptive Methods =over =item C<$num = $path-Etree_num_roots()> Return the number of root nodes in C<$path>. If C<$path> is not a tree then return 0. Many tree paths have a single root and for them the return is 1. =item C<@n_list = $path-Etree_root_n_list()> Return a list of the N values which are the root nodes in C<$path>. If C<$path> is not a tree then this is an empty list. There are C many return values. =item C<$num = $path-Etree_num_children_minimum()> =item C<$num = $path-Etree_num_children_maximum()> =item C<@nums = $path-Etree_num_children_list()> Return the possible number of children of the nodes of C<$path>, either the minimum, the maximum, or a list of all possible numbers of children. For C the list of values is in increasing order, so the first value is C and the last is C. =item C<$bool = $path-Etree_any_leaf()> Return true if there are any leaf nodes in the tree, meaning any N for which C is 0. This is the same as C since if NumChildren=0 occurs then there are leaf nodes. Some trees may have no leaf nodes, for example in the complete binary tree of C every node always has 2 children. =back =head2 Level Methods =over =item Cn_to_level($n)> Return the replication level containing C<$n>. The first level is 0. =item C<($n_lo,$n_hi) = $path-Elevel_to_n_range($level)> Return the range of N values, inclusive, which comprise a self-similar replication level in C<$path>. If C<$path> has no notion of such levels then return an empty list. my ($n_lo, $n_hi) = $path->level_to_n_range(6) or print "no levels in this path"; For example the C has levels running C<0> to C<2**$level>, or the C is C<0> to C<4**$level - 1>. Most levels are powers like this. A power C<2**$level> is a "vertex" style whereas C<2**$level - 1> is a "centre" style. The difference is generally whether the X,Y points represent vertices of the object's segments as opposed to centres or midpoints. =back =head2 Parameter Methods =over =item C<$aref = Math::PlanePath::Foo-Eparameter_info_array()> =item C<@list = Math::PlanePath::Foo-Eparameter_info_list()> Return an arrayref of list describing the parameters taken by a given class. This meant to help making widgets etc for user interaction in a GUI. Each element is a hashref { name => parameter key arg for new() share_key => string, or undef description => human readable string type => string "integer","boolean","enum" etc default => value minimum => number, or undef maximum => number, or undef width => integer, suggested display size choices => for enum, an arrayref } C is a string, one of "integer" "enum" "boolean" "string" "filename" "filename" is separate from "string" since it might require subtly different handling to reach Perl as a byte string, whereas a "string" type might in principle take Perl wide chars. For "enum" the C field is the possible values, such as { name => "flavour", type => "enum", choices => ["strawberry","chocolate"], } C and/or C are omitted if there's no hard limit on the parameter. C is designed to indicate when parameters from different C classes can done by a single control widget in a GUI etc. Normally the C is enough, but when the same name has slightly different meanings in different classes a C allows the same meanings to be matched up. =item C<$hashref = Math::PlanePath::Foo-Eparameter_info_hash()> Return a hashref mapping parameter names C<$info-E{'name'}> to their C<$info> records. { wider => { name => "wider", type => "integer", ... }, } =back =head1 GENERAL CHARACTERISTICS The classes are mostly based on integer C<$n> positions and those designed for a square grid turn an integer C<$n> into integer C<$x,$y>. Usually they give in-between positions for fractional C<$n> too. Classes not on a square grid but instead giving fractional X,Y such as C and C are designed for a unit circle at each C<$n> but they too can give in-between positions on request. All X,Y positions are calculated by separate C calls. To follow a path use successive C<$n> values starting from C<$path-En_start()>. foreach my $n ($path->n_start .. 100) { my ($x,$y) = $path->n_to_xy($n); print "$n $x,$y\n"; } The separate C calls were motivated by plotting just some N points of a path, such as just the primes or the perfect squares. Successive positions in paths could perhaps be done more efficiently in an iterator style. Paths with a quadratic "step" are not much worse than a C to break N into a segment and offset, but the self-similar paths which chop N into digits of some radix could increment instead of recalculate. If interested only in a particular rectangle or similar region then iterating has the disadvantage that it may stray outside the target region for a long time, making an iterator much less useful than it seems. For wild paths it can be better to apply C by rows or similar across the desired region. L etc offer the PlanePath coordinates, directions, turns, etc as sequences. The iterator forms there simply make repeated calls to C etc. =head2 Scaling and Orientation The paths generally make a first move to the right and go anti-clockwise around from the X axis, unless there's some more natural orientation. Anti-clockwise is the usual direction for mathematical spirals. There's no parameters for scaling, offset or reflection as those things are thought better left to a general coordinate transformer, for example to expand or invert for display. Some easy transformations can be had just from the X,Y with -X,Y flip horizontally (mirror image) X,-Y flip vertically (across the X axis) -Y,X rotate +90 degrees (anti-clockwise) Y,-X rotate -90 degrees (clockwise) -X,-Y rotate 180 degrees Flip vertically makes spirals go clockwise instead of anti-clockwise, or a flip horizontally the same but starting on the left at the negative X axis. See L below for 60 degree rotations of the triangular grid paths too. The Rows and Columns paths are exceptions to the rule of not having rotated versions of paths. They began as ways to pass in width and height as generic parameters and let the path use the one or the other. For scaling and shifting see for example L, and to rotate as well see L. =head2 Loop Step The paths can be characterized by how much longer each loop or repetition is than the preceding one. For example each cycle around the C is 8 more N points than the preceding. =for my_pod step begin Step Path ---- ---- 0 Rows, Columns (fixed widths) 1 Diagonals 2/2 DiagonalsOctant (2 rows for +2) 2 SacksSpiral, Corner, CornerAlternating, PyramidSides, PyramidRows (default) 4 DiamondSpiral, AztecDiamondRings, Staircase 4/2 CellularRule54, CellularRule57, DiagonalsAlternating (2 rows for +4) 5 PentSpiral, PentSpiralSkewed 5.65 PixelRings (average about 4*sqrt(2)) 6 HexSpiral, HexSpiralSkewed, MPeaks, MultipleRings (default) 6/2 CellularRule190 (2 rows for +6) 6.28 ArchimedeanChords (approaching 2*pi), FilledRings (average 2*pi) 7 HeptSpiralSkewed 8 SquareSpiral, PyramidSpiral 16/2 StaircaseAlternating (up and back for +16) 9 TriangleSpiral, TriangleSpiralSkewed 12 AnvilSpiral 16 OctagramSpiral, ToothpickSpiral 19.74 TheodorusSpiral (approaching 2*pi^2) 32/4 KnightSpiral (4 loops 2-wide for +32) 64 DiamondArms (each arm) 72 GreekKeySpiral 128 SquareArms (each arm) 128/4 CretanLabyrinth (4 loops for +128) 216 HexArms (each arm) totient CoprimeColumns, DiagonalRationals numdivisors DivisibleColumns various CellularRule parameter MultipleRings, PyramidRows =for my_pod step end The step determines which quadratic number sequences make straight lines. For example the gap between successive perfect squares increases by 2 each time (4 to 9 is +5, 9 to 16 is +7, 16 to 25 is +9, etc), so the perfect squares make a straight line in the paths of step 2. In general straight lines on stepped paths are quadratics N = a*k^2 + b*k + c where a=step/2 The polygonal numbers are like this, with the (step+2)-gonal numbers making a straight line on a "step" path. For example the 7-gonals (heptagonals) are 5/2*k^2-3/2*k and make a straight line on the step=5 C. Or the 8-gonal octagonal numbers 6/2*k^2-4/2*k on the step=6 C. There are various interesting properties of primes in quadratic progressions. Some quadratics seem to have more primes than others. For example see L. Many quadratics have no primes at all, or none above a certain point, either trivially if always a multiple of 2 etc, or by a more sophisticated reasoning. See L for a factorization on the roots making a no-primes gap. A 4*step path splits a straight line in two, so for example the perfect squares are a straight line on the step=2 "Corner" path, and then on the step=8 C they instead fall on two lines (lower left and upper right). In the bigger step there's one line of the even squares (2k)^2 == 4*k^2 and another of the odd squares (2k+1)^2. The gap between successive even squares increases by 8 each time and likewise between odd squares. =head2 Self-Similar Powers The self-similar patterns such as C generally have a base pattern which repeats at powers N=base^level or squares N=(base*base)^level. Or some multiple or relationship to such a power for things like C and C. =for my_pod base begin Base Path ---- ---- 2 HilbertCurve, HilbertSides, HilbertSpiral, ZOrderCurve (default), GrayCode (default), BetaOmega, AR2W2Curve, HIndexing, ImaginaryBase (default), ImaginaryHalf (default), SierpinskiCurve, SierpinskiCurveStair, CubicBase (default) CornerReplicate, ComplexMinus (default), ComplexPlus (default), ComplexRevolving, DragonCurve, DragonRounded, DragonMidpoint, AlternatePaper, AlternatePaperMidpoint, CCurve, DigitGroups (default), PowerArray (default) 3 PeanoCurve (default), PeanoDiagonals (default), WunderlichSerpentine (default),WunderlichMeander, KochelCurve, GosperIslands, GosperSide SierpinskiTriangle, SierpinskiArrowhead, SierpinskiArrowheadCentres, TerdragonCurve, TerdragonRounded, TerdragonMidpoint, AlternateTerdragon, UlamWarburton, UlamWarburtonQuarter (each level) 4 KochCurve, KochPeaks, KochSnowflakes, KochSquareflakes, LTiling, 5 QuintetCurve, QuintetCentres, QuintetReplicate, DekkingCurve, DekkingCentres, CincoCurve, R5DragonCurve, R5DragonMidpoint 7 Flowsnake, FlowsnakeCentres, GosperReplicate 8 QuadricCurve, QuadricIslands 9 SquareReplicate Fibonacci FibonacciWordFractal, WythoffArray parameter PeanoCurve, PeanoDiagonals, WunderlichSerpentine, ZOrderCurve, GrayCode, ImaginaryBase, ImaginaryHalf, CubicBase, ComplexPlus, ComplexMinus, DigitGroups, PowerArray =for my_pod base end Many number sequences plotted on these self-similar paths tend to be fairly random, or merely show the tiling or path layout rather than much about the number sequence. Sequences related to the base can make holes or patterns picking out parts of the path. For example numbers without a particular digit (or digits) in the relevant base show up as holes. See for example L. =head2 Triangular Lattice Some paths are on triangular or "A2" lattice points like *---*---*---*---*---* / \ / \ / \ / \ / \ / *---*---*---*---*---* \ / \ / \ / \ / \ / \ *---*---*---*---*---* / \ / \ / \ / \ / \ / *---*---*---*---*---* \ / \ / \ / \ / \ / \ *---*---*---*---*---* / \ / \ / \ / \ / \ / *---*---*---*---*---* This is done in integer X,Y on a square grid by using every second square and offsetting alternate rows. This means sum X+Y even, ie. X,Y either both even or both odd, not of opposite parity. . * . * . * . * . * . * * . * . * . * . * . * . . * . * . * . * . * . * * . * . * . * . * . * . . * . * . * . * . * . * * . * . * . * . * . * . The X axis the and diagonals X=Y and X=-Y divide the plane into six equal parts in this grid. X=-Y X=Y \ / \ / \ / ----------------- X=0 / \ / \ / \ The diagonal X=3*Y is the middle of the first sixth, representing a twelfth of the plane. The resulting triangles are flatter than they should be. The triangle base is width=2 and top is height=1, whereas it would be height=sqrt(3) for an equilateral triangle. That sqrt(3) factor can be applied if desired, X, Y*sqrt(3) side length 2 X/2, Y*sqrt(3)/2 side length 1 Integer Y values have the advantage of fitting pixels on the usual kind of raster computer screen, and not losing precision in floating point results. If doing a general-purpose coordinate rotation then be sure to apply the sqrt(3) scale factor before rotating or the result will be skewed. 60 degree rotations can be made within the integer X,Y coordinates directly as follows, all giving integer X,Y results. ( X-3Y)/2, ( X+Y)/2 rotate +60 (anti-clockwise) ( X+3Y)/2, (-X+Y)/2 rotate -60 (clockwise) (-X-3Y)/2, ( X-Y)/2 rotate +120 (-X+3Y)/2, (-X-Y)/2 rotate -120 -X,-Y rotate 180 (X+3Y)/2, (X-Y)/2 mirror across the X=3*Y twelfth (30deg) =cut # GP-DEFINE sqrt3i = quadgen(-12); # GP-Test sqrt3i^2 == -3 # GP-DEFINE w6 = 1/2 + 1/2*sqrt3i; # GP-DEFINE w3 = -1/2 + 1/2*sqrt3i; # GP-DEFINE b12 = 3/2 + 1/2*sqrt3i; # rot +/-60 # GP-Test ('x/2 + 'y*sqrt3i/2)*w6 \ # GP-Test == (('x - 3*'y)/2)*1/2 + (( 'x + 'y)/2)*sqrt3i/2 # GP-Test ('x/2 + 'y*sqrt3i/2)/w6 \ # GP-Test == (('x + 3*'y)/2)*1/2 + ((-'x + 'y)/2)*sqrt3i/2 # rot +/-120 # GP-Test ('x/2 + 'y*sqrt3i/2)*w3 \ # GP-Test == ((-'x - 3*'y)/2)*1/2 + (( 'x - 'y)/2)*sqrt3i/2 # GP-Test ('x/2 + 'y*sqrt3i/2)/w3 \ # GP-Test == ((-'x + 3*'y)/2)*1/2 + ((-'x - 'y)/2)*sqrt3i/2 # mirror across 30deg # GP-Test conj(('x/2 + 'y*sqrt3i/2)/b12)*b12 \ # GP-Test == (('x + 3*'y)/2)*1/2 + (('x - 'y)/2)*sqrt3i/2 =pod The sqrt(3) factor can be worked into a hypotenuse radial distance calculation as follows if comparing distances from the origin. hypot = sqrt(X*X + 3*Y*Y) See for instance C which is triangular points ordered by this radial distance. =head1 FORMULAS The formulas section in the POD of each class describes some of the calculations. This might be of interest even if the code is not. =head2 Triangular Calculations For a triangular lattice, the rotation formulas above allow calculations to be done in the rectangular X,Y coordinates which are the inputs and outputs of the PlanePath functions. Another way is to number vertically on a 60 degree angle with coordinates i,j, ... * * * 2 * * * 1 * * * j=0 i=0 1 2 These coordinates are sometimes used for hexagonal grids in board games etc. Using this internally can simplify rotations a little, -j, i+j rotate +60 (anti-clockwise) i+j, -i rotate -60 (clockwise) -i-j, i rotate +120 j, -i-j rotate -120 -i, -j rotate 180 Conversions between i,j and the rectangular X,Y are X = 2*i + j i = (X-Y)/2 Y = j j = Y A third coordinate k at a +120 degrees angle can be used too, k=0 k=1 k=2 * * * * * * * * * 0 1 2 This is redundant in that it doesn't number anything i,j alone can't already, but it has the advantage of turning rotations into just sign changes and swaps, -k, i, j rotate +60 j, k, -i rotate -60 -j, -k, i rotate +120 k, -i, -j rotate -120 -i, -j, -k rotate 180 The conversions between i,j,k and the rectangular X,Y are like the i,j above but with k worked in too. X = 2i + j - k i = (X-Y)/2 i = (X+Y)/2 Y = j + k j = Y or j = 0 k = 0 k = Y =head2 N to dX,dY -- Fractional C is the change from N to N+1, and is designed both for integer N and fractional N. For fractional N it can be convenient to calculate a dX,dY at floor(N) and at floor(N)+1 and then combine the two in proportion to frac(N). int+2 | | N+1 \ /| | / | | / | | frac / | | / | | / | / int-----N------int+1 this_dX dX,dY next_dX this_dY next_dY |-------|------| frac 1-frac int = int(N) frac = N - int 0 <= frac < 1 this_dX,this_dY at int next_dX,next_dY at int+1 at fractional N dX = this_dX * (1-frac) + next_dX * frac dY = this_dY * (1-frac) + next_dY * frac This is combination of this_dX,this_dY and next_dX,next_dY in proportion to the distances from positions N to int+1 and from int+1 to N+1. The formulas can be rearranged to dX = this_dX + frac*(next_dX - this_dX) dY = this_dY + frac*(next_dY - this_dY) which is like dX,dY at the integer position plus fractional part of a turn or change to the next dX,dY. =head2 N to dX,dY -- Self-Similar For most of the self-similar paths such as C, the change dX,dY is determined by following the state table transitions down through either all digits of N, or to the last non-9 digit, ie. drop any low digits equal to radix-1. Generally paths which are the edges of some tiling use all digits, and those which are the centres of a tiling stop at the lowest non-9. This can be seen for example in the C using all digits, whereas its C variant stops at the lowest non-24. Perhaps this all-digits vs low-non-9 would even characterize path style as edges or centres of a tiling, when a path is specified in some way that a tiling is not quite obvious. =head1 SUBCLASSING The mandatory methods for a PlanePath subclass are n_to_xy() xy_to_n() xy_to_n_list() if multiple N's map to an X,Y rect_to_n_range() It sometimes happens that one of C or C is easier than the other but both should be implemented. C should do something sensible on fractional N. The suggestion is to make it an X,Y proportionally between integer N positions. It could be along a straight line or an arc as best suits the path. A straight line can be done simply by two calculations of the surrounding integer points, until it's clear how to work the fraction into the code directly. C has a base implementation calling plain C to give a single N at X,Y. If a path has multiple Ns at an X,Y (eg. C) then it must implement C to return all those Ns, and must also implement a plain C returning the first of them. C can initially be any convenient over-estimate. It should give N big enough that from there onwards all points are sure to be beyond the given X,Y rectangle. The following descriptive methods have base implementations n_start() 1 class_x_negative() \ 1, so whole plane class_y_negative() / x_negative() calls class_x_negative() y_negative() calls class_x_negative() x_negative_at_n() undef \ as for no negatives y_negative_at_n() undef / The base C starts at N=1. Paths which treat N as digits of some radix or where there's self-similar replication are often best started from N=0 instead since doing so puts nice powers-of-2 etc on the axes or diagonals. use constant n_start => 0; # digit or replication style Paths which use only parts of the plane should define C and/or C to false. For example if only the first quadrant XE=0,YE=0 then use constant class_x_negative => 0; use constant class_y_negative => 0; If negativeness varies with path parameters then C and/or C follow those parameters and the C forms are whether any set of parameters ever gives negative. The following methods have base implementations calling C. A subclass can implement them directly if they can be done more efficiently. n_to_dxdy() calls n_to_xy() twice n_to_rsquared() calls n_to_xy() n_to_radius() sqrt of n_to_rsquared() C is an example of an easy C. C is only slightly trickier. Unless a path has some sort of easy X^2+Y^2 then it might as well let the base implementation call C. The way C supports fractional N can be a little tricky. One way is to calculate dX,dY on the integer N below and above and combine as described in L. For some paths the calculation of turn or direction at ceil(N) can be worked into a calculation of the direction at floor(N) so not much more work. The following methods have base implementations calling C. A subclass might implement them directly if it can be done more efficiently. xy_is_visited() defined(xy_to_n($x,$y)) xyxy_to_n() \ xyxy_to_n_either() | calling xy_to_n_list() xyxy_to_n_list() | xyxy_to_n_list_either() / Paths such as C which fill the plane have C always true, so for them use constant xy_is_visited => 1; For a tree path the following methods are mandatory tree_n_parent() tree_n_children() tree_n_to_depth() tree_depth_to_n() tree_num_children_list() tree_n_to_subheight() The other tree methods have base implementations, =over =item C Checks for C having non-zero C. Usually this suffices, expecting C to be a root node and to have some children. =item C Calls C and counts the number of return values. Many trees can count the children with less work than calculating outright, for example C is simply always 2 for NE=Nstart. =item C Calls C. This assumes that the depth level ends where the next begins. This is true for the various breadth-wise tree traversals, but anything interleaved etc will need its own implementation. =item C Calls C and C. For some paths the row start and end, or start and width, might be calculated together more efficiently. =item C Returns C. This suits breadth-wise style paths where all points at C<$depth> are in a contiguous block. Any path not like that will need its own C. =item C, C Return the first and last values of C as the minimum and maximum. =item C Calls C. If the minimum C is 0 then there's leaf nodes. =back =head1 SEE ALSO =for my_pod see_also begin L, L, L, L, L, L, L, L, L, L, L, L, L, L L, L, L, L, L, L L, L, L, L, L, L, L, L, L, L, L L, L, L, L, L, L, L, L, L, L, L, L, L, L, L L, L, L, L, L, L, L, L L, L, L, L, L L, L, L L, L, L, L L, L L, L, L L, L, L L, L, L, L, L, L, L, L, L, L, L, L L, L, L L, L, L, L, L, L, L, L L L, L, L, L, L, L, L, L L, L, L, L, L, L, L, L, L, L, L, L, L =for my_pod see_also end L, L, L, L, L, L, L, L L, L, L, L L, displaying various sequences on these paths. F, to print all the paths. =head2 Other Ways To Do It L, L, L PerlMagick (module L) demo scripts F and F =head1 HOME PAGE L L =head1 LICENSE Copyright 2010, 2011, 2012, 2013, 2014 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath 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, or (at your option) any later version. Math-PlanePath 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 Math-PlanePath. If not, see . =cut #------------------------------------------------------------------------------ # Maybe: # # ($x,$y) = $path->xy_start() x,y at n_start # ($depth,$offset) = $path->tree_n_to_depth_and_offset # # $bool = $path->rect_to_n_range_is_always_exact() # $bool = $path->tree_n_to_subheight_is_infinite() # identifying the infinite spines only # # tree_n_ordered_children() $n and undefs # SierpinskiTree,ToothpickTree left and right # OneOfEight 3 from horiz, 5 from diag # # gcdxy_minimum # gcdxy_maximum # mulxy_minimum # trsquared_minimum # trsquared_minimum # # ring_to_n_range() 2^(k-1) to 2^k-1 koch peaks # ($x1,$y1, $x2,$y2) = n_to_rect($n) integer points # ($s1,$s1, $d2,$d2) = n_to_diamond($n) integer points # cf fractional part Diagonals outside integer area # n_to_figure_boundary # n_to_hull_boundary # n_to_hull_area # n_to_enclosed_area # n_to_enclosed_boundary # n_to_right_enclosed_boundary # n_to_left_enclosed_boundary # $path->xy_integer() if X,Y both all integer # $path->x_integer() if X all integer # $path->y_integer() if Y all integer # $path->xy_integer_n_start # # xy_all_coprime() xy_coprime() gcd(X,Y)=1 always # xy_all_divisible() X divisible by Y # xy_any_even # xy_any_odd # xy_all_even # xy_all_odd # xy_parity_minimum() X+Y mod 2 # xy_parity_maximum() X+Y mod 2 # xy_parity "even" "odd" "both" # xy_hexlattice_type "centred" "side_horiz" # xy_triangular_lattice "", "even", "odd # # lattice_type square,triangular,triangular_odd,pentagonal,fractional # $path->xy_any_odd() xy_odd() xy_all_odd() # $path->xy_any_even() xy_even() xy_all_even() # # $path->n_to_turn_lsr # $path->n_to_dir4 # $path->n_to_turn4 # $path->n_to_turn6 # $path->n_to_turn8 # $path->n_to_ddist # $path->n_to_drsquared # $path->xy_to_dxdy() or xy_to_dxdy_list() if multiple # $path->xy_to_dir4_list # $path->xy_to_dxdy_list # $path->xy_n_list_maxcount # $path->xy_n_list_maxnum # $path->xy_n_list_maximum # $path->xy_next_in_rect($x,$y, $x1,$y1,$x2,$y2) # return ($x,$y) or empty #------------------------------------------------------------------------------ # xy_unique_n_start # figures_disjoint # figures_disjoint_n_start # separate # unoverlapped #------------------------------------------------------------------------------ # Math::PlanePath::Base::Generic # divrem # divrem_mutate # Math-PlanePath-129/t/0002755000175000017500000000000014001441522012144 5ustar ggggMath-PlanePath-129/t/number-fraction.t0000644000175000017500000004356014001400775015437 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2018, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments '###'; my $test_count = (tests => 459)[1]; plan tests => $test_count; # Number::Fraction version 1.14 wanted for abs() overload. if (! eval 'use Number::Fraction 1.14; 1') { MyTestHelpers::diag ('skip due to Number::Fraction 1.14 not available -- ',$@); foreach (1 .. $test_count) { skip ('due to no Number::Fraction', 1, 1); } exit 0; } MyTestHelpers::diag ('Number::Fraction version ', Number::Fraction->VERSION); { # Number::Fraction version 3.0.3 had a bug where some "plain == blessed" # compares give the wrong answer. But "blessed == plain" ok and writing # that way here is enough. # my $plain = 123; my $nf = Number::Fraction->new($plain); MyTestHelpers::diag ('Number::Fraction plain == nf: ', ($plain == $nf ? "yes, good" : "no, bad")); } #------------------------------------------------------------------------------ # round_nearest() use Math::PlanePath::Base::Generic 'round_nearest'; ok (round_nearest(Number::Fraction->new('-7/4')) == -2, 1); ok (round_nearest(Number::Fraction->new('-3/2')) == -1, 1); ok (round_nearest(Number::Fraction->new('-5/4')) == -1, 1); ok (round_nearest(Number::Fraction->new('-3/4')) == -1, 1); ok (round_nearest(Number::Fraction->new('-1/2')) == 0, 1); ok (round_nearest(Number::Fraction->new('-1/4')) == 0, 1); ok (round_nearest(Number::Fraction->new('1/4')) == 0, 1); ok (round_nearest(Number::Fraction->new('5/4')) == 1, 1); ok (round_nearest(Number::Fraction->new('3/2')) == 2, 1); ok (round_nearest(Number::Fraction->new('7/4')) == 2, 1); ok (round_nearest(Number::Fraction->new('2')) == 2, 1); #------------------------------------------------------------------------------ # floor() use Math::PlanePath::Base::Generic 'floor'; ok (floor(Number::Fraction->new('-7/4')) == -2, 1); ok (floor(Number::Fraction->new('-3/2')) == -2, 1); ok (floor(Number::Fraction->new('-5/4')) == -2, 1); ok (floor(Number::Fraction->new('-3/4')) == -1, 1); ok (floor(Number::Fraction->new('-1/2')) == -1, 1); ok (floor(Number::Fraction->new('-1/4')) == -1, 1); ok (floor(Number::Fraction->new('1/4')) == 0, 1); ok (floor(Number::Fraction->new('3/4')) == 0, 1); ok (floor(Number::Fraction->new('5/4')) == 1, 1); ok (floor(Number::Fraction->new('3/2')) == 1, 1); ok (floor(Number::Fraction->new('7/4')) == 1, 1); ok (floor(Number::Fraction->new('2')) == 2, 1); #------------------------------------------------------------------------------ # Rows { require Math::PlanePath::Rows; my $width = 5; my $path = Math::PlanePath::Rows->new (width => $width); { my $y = Number::Fraction->new(2) ** 20; my $x = 4; my $n = $y*$width + $x + 1; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $x, 1, "got $got_x want $x"); ok ($y == $got_y, 1); my $got_n = $path->xy_to_n($x,$y); ok ($got_n == $n, 1); } { my $n = Number::Fraction->new('4/3'); my ($got_x,$got_y) = $path->n_to_xy($n); ok ("$got_x", '1/3'); ok ($got_y == 0, 1); } { my $n = Number::Fraction->new('4/3') + 15; my ($got_x,$got_y) = $path->n_to_xy($n); ok ("$got_x", '1/3'); ok ($got_y == 3, 1); } { my $n = Number::Fraction->new('4/3') - 15; my ($got_x,$got_y) = $path->n_to_xy($n); ok ("$got_x", '1/3'); ok ($got_y == -3, 1); } } #------------------------------------------------------------------------------ # Diagonals { require Math::PlanePath::Diagonals; my $path = Math::PlanePath::Diagonals->new; { my $x = Number::Fraction->new(2) ** 20 - 1; my $n = ($x+1)*($x+2)/2; # triangular numbers on Y=0 horizontal my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $x, 1, "got $got_x want $x"); ok ($got_y == 0); my $got_n = $path->xy_to_n($x,0); ok ($got_n == $n, 1); } { my $x = Number::Fraction->new(2) ** 20 - 1; my $n = ($x+1)*($x+2)/2; # Y=0 horizontal my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $x, 1); ok ($got_y == 0, 1); my $got_n = $path->xy_to_n($x,0); ok ($got_n == $n, 1); } { my $y = Number::Fraction->new(2) ** 20 - 1; my $n = $y*($y+1)/2 + 1; # X=0 vertical my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == 0, 1); ok ($got_y == $y, 1); my $got_n = $path->xy_to_n(0,$y); ok ($got_n, $n); } { my $n = Number::Fraction->new(-1); my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, undef); ok ($got_y, undef); } { my $n = Number::Fraction->new('1/2'); my ($got_x,$got_y) = $path->n_to_xy($n); ok (!! $got_x->isa('Number::Fraction'), 1); ok (!! $got_y->isa('Number::Fraction'), 1); ok ($got_x == -0.5, 1); ok ($got_y == 0.5, 1); } } #------------------------------------------------------------------------------ ### PeanoCurve ... require Math::PlanePath::PeanoCurve; { my $path = Math::PlanePath::PeanoCurve->new; require Number::Fraction; my $n = Number::Fraction->new(9**5) + Number::Fraction->new('4/3'); my $want_x = Number::Fraction->new(3**5) + Number::Fraction->new('4/3'); my $want_y = Number::Fraction->new(3**5) - 1; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, $want_x); ok ($got_y, $want_y); } #------------------------------------------------------------------------------ ### ZOrderCurve ... require Math::PlanePath::ZOrderCurve; { my $path = Math::PlanePath::ZOrderCurve->new; require Number::Fraction; my $n = Number::Fraction->new(4**5) + Number::Fraction->new('1/3'); $n->isa('Number::Fraction') || die "Oops, n not a BigRat"; my $want_x = Number::Fraction->new(2**5) + Number::Fraction->new('1/3'); my $want_y = 0; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, $want_x); ok ($got_y, $want_y); } #------------------------------------------------------------------------------ ### round_down_pow() ... use Math::PlanePath::Base::Digits 'round_down_pow'; { my $orig = Number::Fraction->new(3) ** 20 + Number::Fraction->new('1/7'); my $n = Number::Fraction->new(3) ** 20 + Number::Fraction->new('1/7'); my ($pow,$exp) = round_down_pow($n,3); ok ($n, $orig); ok ($pow, Number::Fraction->new(3) ** 20); ok ($exp, 20); } { my $orig = Number::Fraction->new(3) ** 20; my $n = Number::Fraction->new(3) ** 20; my ($pow,$exp) = round_down_pow($n,3); ok ($n, $orig); ok ($pow, Number::Fraction->new(3) ** 20); ok ($exp, 20); } #------------------------------------------------------------------------------ ### Modules ... my @modules = ( 'Corner', 'CornerAlternating', 'PyramidSides', 'HilbertSides', 'HilbertCurve', 'HilbertSpiral', 'CfracDigits,radix=1', 'CfracDigits', 'CfracDigits,radix=3', 'CfracDigits,radix=4', 'CfracDigits,radix=10', 'CfracDigits,radix=37', 'ChanTree', 'ChanTree,k=2', 'ChanTree,k=4', 'ChanTree,k=5', 'ChanTree,k=7', 'ChanTree,reduced=1', 'ChanTree,reduced=1,k=2', 'ChanTree,reduced=1,k=4', 'ChanTree,reduced=1,k=5', 'ChanTree,reduced=1,k=7', 'RationalsTree', 'RationalsTree,tree_type=L', 'RationalsTree,tree_type=HCS', 'FractionsTree', 'DekkingCurve', 'DekkingCentres', 'PyramidRows', 'PyramidRows,step=0', 'PyramidRows,step=1', 'PyramidRows,step=3', 'PyramidRows,step=37', 'PyramidRows,align=right', 'PyramidRows,align=right,step=0', 'PyramidRows,align=right,step=1', 'PyramidRows,align=right,step=3', 'PyramidRows,align=right,step=37', 'PyramidRows,align=left', 'PyramidRows,align=left,step=0', 'PyramidRows,align=left,step=1', 'PyramidRows,align=left,step=3', 'PyramidRows,align=left,step=37', 'GreekKeySpiral', 'GreekKeySpiral,turns=0', 'GreekKeySpiral,turns=1', 'GreekKeySpiral,turns=3', 'GreekKeySpiral,turns=4', 'GreekKeySpiral,turns=5', 'GreekKeySpiral,turns=6', 'GreekKeySpiral,turns=7', 'GreekKeySpiral,turns=8', 'GreekKeySpiral,turns=37', 'AlternatePaperMidpoint', 'AlternatePaperMidpoint,arms=2', 'AlternatePaperMidpoint,arms=3', 'AlternatePaperMidpoint,arms=4', 'AlternatePaperMidpoint,arms=5', 'AlternatePaperMidpoint,arms=6', 'AlternatePaperMidpoint,arms=7', 'AlternatePaperMidpoint,arms=8', 'AlternatePaper', 'AlternatePaper,arms=2', 'AlternatePaper,arms=3', 'AlternatePaper,arms=4', 'AlternatePaper,arms=5', 'AlternatePaper,arms=6', 'AlternatePaper,arms=7', 'AlternatePaper,arms=8', 'WythoffPreliminaryTriangle', 'WythoffArray', 'PowerArray', 'PowerArray,radix=3', 'PowerArray,radix=4', 'Diagonals', 'Diagonals,direction=up', 'DiagonalsOctant', 'DiagonalsOctant,direction=up', 'DiagonalsAlternating', 'AlternateTerdragon', 'AlternateTerdragon,arms=1', 'AlternateTerdragon,arms=2', 'AlternateTerdragon,arms=6', 'TerdragonCurve', 'TerdragonCurve,arms=1', 'TerdragonCurve,arms=2', 'TerdragonCurve,arms=6', 'TerdragonMidpoint', 'TerdragonMidpoint,arms=1', 'TerdragonMidpoint,arms=2', 'TerdragonMidpoint,arms=6', 'TerdragonRounded', 'TerdragonRounded,arms=1', 'TerdragonRounded,arms=2', 'TerdragonRounded,arms=6', 'CCurve', 'R5DragonMidpoint', 'R5DragonMidpoint,arms=2', 'R5DragonMidpoint,arms=3', 'R5DragonMidpoint,arms=4', 'R5DragonCurve', 'R5DragonCurve,arms=2', 'R5DragonCurve,arms=3', 'R5DragonCurve,arms=4', 'DragonRounded', 'DragonMidpoint', 'DragonCurve', 'GrayCode', 'WunderlichSerpentine', 'WunderlichSerpentine,serpentine_type=100_000_000', 'WunderlichSerpentine,serpentine_type=000_000_001', 'WunderlichSerpentine,radix=2', 'WunderlichSerpentine,radix=4', 'WunderlichSerpentine,radix=5,serpentine_type=coil', 'CretanLabyrinth', 'OctagramSpiral', 'AnvilSpiral', 'AnvilSpiral,wider=1', 'AnvilSpiral,wider=2', 'AnvilSpiral,wider=9', 'AnvilSpiral,wider=17', 'AR2W2Curve', 'AR2W2Curve,start_shape=D2', 'AR2W2Curve,start_shape=B2', 'AR2W2Curve,start_shape=B1rev', 'AR2W2Curve,start_shape=D1rev', 'AR2W2Curve,start_shape=A2rev', 'BetaOmega', 'KochelCurve', 'CincoCurve', 'LTiling', 'LTiling,L_fill=ends', 'LTiling,L_fill=all', 'MPeaks', # but not across gap 'WunderlichMeander', 'FibonacciWordFractal', # 'CornerReplicate', # not defined yet 'DigitGroups', 'PeanoCurve', 'PeanoDiagonals', 'ZOrderCurve', 'HIndexing', 'SierpinskiCurve', 'SierpinskiCurveStair', 'AztecDiamondRings', # but not across ring end 'DiamondArms', 'SquareArms', 'HexArms', # 'UlamWarburton', # not really defined yet # 'UlamWarburtonQuarter', # not really defined yet 'CellularRule54', # but not across gap # 'CellularRule57', # but not across gap # 'CellularRule57,mirror=1', # but not across gap 'CellularRule190', # but not across gap 'CellularRule190,mirror=1', # but not across gap 'Rows', 'Columns', 'SquareSpiral', 'DiamondSpiral', 'PentSpiral', 'PentSpiralSkewed', 'HexSpiral', 'HexSpiralSkewed', 'HeptSpiralSkewed', 'PyramidSpiral', 'TriangleSpiral', 'TriangleSpiralSkewed', 'TriangleSpiralSkewed,skew=right', 'TriangleSpiralSkewed,skew=up', 'TriangleSpiralSkewed,skew=down', # 'SacksSpiral', # sin/cos # 'TheodorusSpiral', # counting by N # 'ArchimedeanChords', # counting by N # 'VogelFloret', # sin/cos 'KnightSpiral', 'SierpinskiArrowheadCentres', 'SierpinskiArrowheadCentres,align=right', 'SierpinskiArrowheadCentres,align=left', 'SierpinskiArrowheadCentres,align=diagonal', 'SierpinskiArrowhead', 'SierpinskiArrowhead,align=right', 'SierpinskiArrowhead,align=left', 'SierpinskiArrowhead,align=diagonal', # 'SierpinskiTriangle', # not really defined yet 'QuadricCurve', 'QuadricIslands', 'KochSquareflakes', 'KochSnowflakes', 'KochCurve', 'KochPeaks', 'FlowsnakeCentres', 'GosperReplicate', 'GosperSide', 'GosperIslands', 'Flowsnake', # 'DivisibleColumns', # counting by N # 'DivisibleColumns,divisor_type=proper', # 'CoprimeColumns', # counting by N # 'DiagonalRationals',# counting by N # 'GcdRationals', # counting by N # 'GcdRationals,pairs_order=rows_reverse', # 'GcdRationals,pairs_order=diagonals_down', # 'GcdRationals,pairs_order=diagonals_up', # 'FactorRationals', # counting by N # 'TriangularHypot', # counting by N # 'TriangularHypot,points=odd', # 'TriangularHypot,points=all', # 'TriangularHypot,points=hex', # 'TriangularHypot,points=hex_rotated', # 'TriangularHypot,points=hex_centred', 'PythagoreanTree', # 'Hypot', # searching by N # 'HypotOctant', # searching by N # 'PixelRings', # searching by N # 'FilledRings', # searching by N # 'MultipleRings', # sin/cos, maybe 'QuintetCentres', 'QuintetCurve', 'QuintetReplicate', 'SquareReplicate', 'ComplexPlus', 'ComplexPlus,realpart=3', 'ComplexMinus', 'ComplexMinus,realpart=3', 'ComplexRevolving', 'ImaginaryBase', 'ImaginaryHalf', 'CubicBase', # 'File', # not applicable 'Staircase', 'StaircaseAlternating', 'StaircaseAlternating,end_type=square', ); my @classes = map {"Math::PlanePath::$_"} @modules; sub module_parse { my ($mod) = @_; my ($class, @parameters) = split /,/, $mod; return ("Math::PlanePath::$class", map {/(.*?)=(.*)/ or die; ($1 => $2)} @parameters); } foreach my $module (@modules) { ### $module my ($class, %parameters) = module_parse($module); eval "require $class" or die; my $path = $class->new (width => 23, height => 17); my $arms = $path->arms_count; my $n = Number::Fraction->new(2**20) + 5; if ($path->isa('Math::PlanePath::CellularRule190')) { $n += 1; # not across gap } my $orig = Number::Fraction->new('1/3') + $n; my $frac = Number::Fraction->new('1/3'); my $n_frac = $frac + $n; my ($x1,$y1) = $path->n_to_xy($n); ### xy1: "$x1,$y1" my ($x2,$y2) = $path->n_to_xy($n+$arms); ### xy2: "$x2,$y2" my $dx = $x2 - $x1; my $dy = $y2 - $y1; ### dxy: "$dx, $dy" my $want_x = $frac * Number::Fraction->new ($dx) + $x1; my $want_y = $frac * Number::Fraction->new ($dy) + $y1; my ($x_frac,$y_frac) = $path->n_to_xy($n_frac); ### $x_frac ### $y_frac ok ("$x_frac", "$want_x", "$module arms=$arms X frac at n=$n_frac"); ok ("$y_frac", "$want_y", "$module arms=$arms Y frac at n=$n_frac"); } exit 0; Math-PlanePath-129/t/bigint_common.pm0000644000175000017500000004060112523323472015337 0ustar gggg# Copyright 2010, 2011, 2012, 2015 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . package bigint_common; use 5.004; use strict; use Test; # uncomment this to run the ### lines #use Smart::Comments '###'; use lib 't'; use MyTestHelpers; use Math::PlanePath::Base::Digits 'round_down_pow', 'round_up_pow', 'bit_split_lowtohigh', 'digit_split_lowtohigh'; sub isa_bigint { my ($x) = @_; if (ref $x && $x->isa('Math::BigInt')) { return 1; } else { return 0; } } sub isa_bigfloat { my ($x) = @_; if (ref $x && $x->isa('Math::BigFloat')) { return 1; } else { return 0; } } sub bigint_checks { my ($bigclass) = @_; eval "require $bigclass" or die; MyTestHelpers::diag ("$bigclass version ", $bigclass->VERSION); #-------------------------------------------------------------------------- # round_down_pow() { my $orig = $bigclass->new(0); my $n = $bigclass->new(0); my ($pow,$exp) = round_down_pow($n,3); ok ($n, $orig); ok ($pow, $bigclass->new(3) ** 0); ok ($exp, 0); } { my $orig = $bigclass->new(1); my $n = $bigclass->new(1); my ($pow,$exp) = round_down_pow($n,3); ok ($n, $orig); ok ($pow, $bigclass->new(3) ** 0); ok ($exp, 0); } { my $orig = $bigclass->new(2); my $n = $bigclass->new(2); my ($pow,$exp) = round_down_pow($n,3); ok ($n, $orig); ok ($pow, $bigclass->new(3) ** 0); ok ($exp, 0); } { my $orig = $bigclass->new(9); my $n = $bigclass->new(9); my ($pow,$exp) = round_down_pow($n,3); ok ($n, $orig); ok ($pow, $bigclass->new(3) ** 2); ok ($exp, 2); } { my $orig = $bigclass->new(3) ** 128 + 2; my $n = $bigclass->new(3) ** 128 + 2; my ($pow,$exp) = round_down_pow($n,3); ok ($n, $orig); ok ($pow, $bigclass->new(3) ** 128); ok ($exp, 128); } { my $orig = $bigclass->new(3) ** 128; my $n = $bigclass->new(3) ** 128; my ($pow,$exp) = round_down_pow($n,3); ok ($n, $orig); ok ($pow, $bigclass->new(3) ** 128); ok ($exp, 128); } #-------------------------------------------------------------------------- # round_up_pow() { my $orig = $bigclass->new(0); my $n = $bigclass->new(0); my ($pow,$exp) = round_up_pow($n,3); ok ($n, $orig); ok ($pow, $bigclass->new(3) ** 0); ok ($exp, 0); } { my $orig = $bigclass->new(1); my $n = $bigclass->new(1); my ($pow,$exp) = round_up_pow($n,3); ok ($n, $orig); ok ($pow, $bigclass->new(3) ** 0); ok ($exp, 0); } { my $orig = $bigclass->new(2); my $n = $bigclass->new(2); my ($pow,$exp) = round_up_pow($n,3); ok ($n, $orig); ok ($pow, $bigclass->new(3) ** 1); ok ($exp, 1); } { my $orig = $bigclass->new(3) ** 128 - 1; my $n = $bigclass->new(3) ** 128 - 1; my ($pow,$exp) = round_up_pow($n,3); ok ($n, $orig); ok ($pow, $bigclass->new(3) ** 128); ok ($exp, 128); } { my $orig = $bigclass->new(3) ** 128; my $n = $bigclass->new(3) ** 128; my ($pow,$exp) = round_up_pow($n,3); ok ($n, $orig); ok ($pow, $bigclass->new(3) ** 128); ok ($exp, 128); } { my $orig = $bigclass->new(3) ** 128 + 1; my $n = $bigclass->new(3) ** 128 + 1; my ($pow,$exp) = round_up_pow($n,3); ok ($n, $orig); ok ($pow, $bigclass->new(3) ** 129); ok ($exp, 129); } #-------------------------------------------------------------------------- # PythagoreanTree require Math::PlanePath::PythagoreanTree; foreach my $elem ([3,4, 2,1], [6,8, undef,undef], [3,5, undef,undef], [9,10, undef,undef], ){ my ($a,$b, $want_p,$want_q) = @$elem; $a = $bigclass->new($a); $b = $bigclass->new($b); my ($got_p,$got_q) = Math::PlanePath::PythagoreanTree::_ab_to_pq($a,$b); ok ($a, $elem->[0]); # inputs unchanged ok ($b, $elem->[1]); ok ($got_p, $want_p); ok ($got_q, $want_q); } #---------------------------------------------------------------------------- # bit_split_lowtohigh() { require Math::PlanePath; my $zero = $bigclass->new(0); my $thirteen = $bigclass->new(13); ok (join(',',bit_split_lowtohigh($zero)), ''); ok (join(',',bit_split_lowtohigh($thirteen)), '1,0,1,1'); if ($bigclass->isa('Math::BigInt')) { my @bits = bit_split_lowtohigh($thirteen); foreach my $bit (@bits) { ok (! ref $bit, 1, 'bit_split_lowtohigh() return plain bits, not bigints'); } } ok ($thirteen, 13, 'thirteen unchanged'); ok ($zero, 0, 'zero unchanged'); } #---------------------------------------------------------------------------- # digit_split_lowtohigh() { require Math::PlanePath; my $zero = $bigclass->new(0); my $thirteen = $bigclass->new(13); ok (join(',',digit_split_lowtohigh($zero,2)), ''); ok (join(',',digit_split_lowtohigh($zero,3)), ''); ok (join(',',digit_split_lowtohigh($zero,4)), ''); ok (join(',',digit_split_lowtohigh($zero,8)), ''); ok (join(',',digit_split_lowtohigh($zero,10)), ''); ok (join(',',digit_split_lowtohigh($zero,16)), ''); ok (join(',',digit_split_lowtohigh($thirteen,2)), '1,0,1,1'); ok (join(',',digit_split_lowtohigh($thirteen,3)), '1,1,1'); ok (join(',',digit_split_lowtohigh($thirteen,4)), '1,3'); ok (join(',',digit_split_lowtohigh($thirteen,8)), '5,1'); ok (join(',',digit_split_lowtohigh($thirteen,10)), '3,1'); ok (join(',',digit_split_lowtohigh($thirteen,16)), '13'); ok (join(',',digit_split_lowtohigh($bigclass->new(4),4)), '0,1'); ok (join(',',digit_split_lowtohigh($bigclass->new(8),4)), '0,2'); if ($bigclass->isa('Math::BigInt')) { foreach my $radix (2,3,4,5,6,7,8,9,10,11,16,256) { my @digits = digit_split_lowtohigh($thirteen,$radix); foreach my $digit (@digits) { ok (! ref $digit, 1, 'digit_split_lowtohigh() return plain digits, not bigints'); } } } ok ($thirteen, 13, 'thirteen unchanged'); ok ($zero, 0, 'zero unchanged'); } #---------------------------------------------------------------------------- # _divrem() { require Math::PlanePath; my $n = $bigclass->new(123); my ($q,$r) = Math::PlanePath::_divrem($n,5); ok ("$n", 123); ok ("$q", 24); ok ("$r", 3); if ($bigclass->isa('Math::BigInt')) { ok (ref $r, ''); } } #---------------------------------------------------------------------------- # _divrem_mutate() { require Math::PlanePath; my $n = $bigclass->new(123); my $r = Math::PlanePath::_divrem_mutate($n,5); ok ("$n", 24); ok ("$r", 3); if ($bigclass->isa('Math::BigInt')) { ok (ref $r, ''); } } #-------------------------------------------------------------------------- # ImaginaryBase require Math::PlanePath::ImaginaryBase; { my $path = Math::PlanePath::ImaginaryBase->new; { my $x1 = $bigclass->binf; my $y1 = 0; my $x2 = 0; my $y2 = 0; foreach (1 .. 4) { my ($n_lo,$n_hi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); ### $n_lo ### $n_hi ### $x1 ### $y1 ok (!! (ref $n_hi && ($n_hi->is_inf || $n_hi->is_nan)), 1); ($x1,$y1, $x2,$y2) = ($y2, $x1,$y1, $x2); # rotate } } } #--------------------------------------------------------------------------- # VogelFloret { require Math::PlanePath::VogelFloret; { my $path = Math::PlanePath::VogelFloret->new (radius_factor => 1); my $n = $bigclass->new(23); my $rsquared = $path->n_to_rsquared($n); ok ($rsquared == 23, 1); ok (ref $rsquared && $rsquared->isa($bigclass), 1); } if ($bigclass->isa('Math::BigInt')) { my $path = Math::PlanePath::VogelFloret->new (radius_factor => 1.5); my $n = $bigclass->new(40); my $rsquared = $path->n_to_rsquared($n); ok ($rsquared == 90, 1); ok (isa_bigfloat($rsquared), 1, 'non-integer radius_factor promote bigint->bigfloat'); } } #---------------------------------------------------------------------------- # MultipleRings { require Math::PlanePath::MultipleRings; my $path = Math::PlanePath::MultipleRings->new (step => 6); { my $n = $bigclass->new(23); my ($got_x,$got_y) = $path->n_to_xy($n); ok (isa_bigfloat($got_x), 1); ok ($got_x > 0 && $got_x < 1, 1, "MultipleRings n_to_xy($n) got_x $got_x"); ok ($got_y > 2.5 && $got_y < 3.1, 1, "MultipleRings n_to_xy($n) got_y $got_y"); } } #---------------------------------------------------------------------------- # GcdRationals { require Math::PlanePath::GcdRationals; my $path = Math::PlanePath::GcdRationals->new; { foreach my $n ($path->n_start .. 20) { my ($x,$y) = $path->n_to_xy($n); $x = $bigclass->new($x); $y = $bigclass->new($y); my $rev_n = $path->xy_to_n ($x,$y); ok($rev_n,$n); } } { my $x = $bigclass->new(7); my $y = 1; my $n = 28; # 7*8/2 my $got_n = $path->xy_to_n($x,$y); ok ($got_n, $n); my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, $x); ok ($got_y, $y); } { my $x = $bigclass->new(2) ** 128 - 1; my $y = 1; my $n = $x*($x+1)/2; my $got_n = $path->xy_to_n($x,$y); ok ($got_n, $n); my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, $x); ok ($got_y, $y); } { my $x = $bigclass->new(30); my $y = $bigclass->new(105); my $gcd = Math::PlanePath::GcdRationals::_gcd($x,$y); ok ($gcd, 15); ok ($x, 30); ok ($y, 105); } } #-------------------------------------------------------------------------- # CoprimeColumns require Math::PlanePath::CoprimeColumns; { my $path = Math::PlanePath::CoprimeColumns->new; { my $n = $bigclass->new(-1); my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, undef); ok ($got_y, undef); } { my $n = $bigclass->new(-99); my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, undef); ok ($got_y, undef); } { my $n = $bigclass->new(0); my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, 1); ok ($got_y, 1); } } #-------------------------------------------------------------------------- # Corner require Math::PlanePath::Corner; { my $path = Math::PlanePath::Corner->new; { my $y = $bigclass->new(2) ** 128 - 1; { my $n = $y*($y+1) + 1; # on the diagonal my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, $y); ok ($got_y, $y); my $got_n = $path->xy_to_n($y,$y); ok ($got_n, $n); } { my $n = $y*$y+1; # left X=1 vertical my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, 0); ok ($got_y, $y); my $got_n = $path->xy_to_n(0,$y); ok ($got_n, $n); } } { my $n = $bigclass->new(0); my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, undef); ok ($got_y, undef); } } #-------------------------------------------------------------------------- # Diagonals { require Math::PlanePath::Diagonals; my $path = Math::PlanePath::Diagonals->new; { my $x = $bigclass->new(2) ** 128 - 1; my $n = ($x+1)*($x+2)/2; # triangular numbers on Y=0 horizontal my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, $x); ok ($got_y, 0); my $got_n = $path->xy_to_n($x,0); ok ($got_n, $n); } { my $x = $bigclass->new(2) ** 128 - 1; my $n = ($x+1)*($x+2)/2; # Y=0 horizontal my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, $x); ok ($got_y, 0); my $got_n = $path->xy_to_n($x,0); ok ($got_n, $n); } { my $y = $bigclass->new(2) ** 128 - 1; my $n = $y*($y+1)/2 + 1; # X=0 vertical my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, 0); ok ($got_y, $y); my $got_n = $path->xy_to_n(0,$y); ok ($got_n, $n); } { my $n = $bigclass->new(-1); my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, undef); ok ($got_y, undef); } } #-------------------------------------------------------------------------- # PeanoCurve require Math::PlanePath::PeanoCurve; { my $path = Math::PlanePath::PeanoCurve->new; { my $n = $bigclass->new(9) ** 128 + 2; my $want_x = $bigclass->new(3) ** 128 + 2; my $want_y = $bigclass->new(3) ** 128 - 1; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, $want_x); ok ($got_y, $want_y); } # 2020202... # { # my $x = $bigclass->new(3) ** 128 + 1; # my $y = 2; # my $want_n = $bigclass->new(9) ** 127 * 15; # my $got_n = $path->xy_to_n($x,$y); # ok ($got_n, $want_n); # } # { # my $x = 2; # my $y = $bigclass->new(3) ** 128 + 1; # my $want_n = $bigclass->new(9) ** 128 + 6; # my $got_n = $path->xy_to_n($x,$y); # ok ($got_n, $want_n); # } } #-------------------------------------------------------------------------- # ZOrderCurve { require Math::PlanePath::ZOrderCurve; my $path = Math::PlanePath::ZOrderCurve->new; { my $n = $bigclass->new(4) ** 128 + 9; my $want_x = $bigclass->new(2) ** 128 + 1; my $want_y = 2; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, $want_x); ok ($got_y, $want_y); } { my $x = $bigclass->new(2) ** 128 + 1; my $y = 2; my $want_n = $bigclass->new(4) ** 128 + 9; my $got_n = $path->xy_to_n($x,$y); ok ($got_n, $want_n); } { my $x = 2; my $y = $bigclass->new(2) ** 128 + 1; my $want_n = $bigclass->new(4) ** 128 * 2 + 6; my $got_n = $path->xy_to_n($x,$y); ok ($got_n, $want_n); } } #-------------------------------------------------------------------------- # RationalsTree require Math::PlanePath::RationalsTree; { my $path = Math::PlanePath::RationalsTree->new (tree_type => 'CW'); my $n = $bigclass->new(2) ** 256 - 1; my $want_x = 256; my $want_y = 1; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, $want_x); ok ($got_y, $want_y); } { my $path = Math::PlanePath::RationalsTree->new (tree_type => 'SB'); my $n = $bigclass->new(2) ** 256 - 1; my $want_x = 256; my $want_y = 1; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, $want_x); ok ($got_y, $want_y); } { my $path = Math::PlanePath::RationalsTree->new (tree_type => 'AYT'); # cf 2^256 - 1 gives fibonacci F[k]/F[k+1] my $n = $bigclass->new(2) ** 256 + 1; my $want_x = 1; my $want_y = 257; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, $want_x); ok ($got_y, $want_y); } foreach my $tree_type ('SB','CW','AYT','Bird','Drib') { my $path = Math::PlanePath::RationalsTree->new (tree_type => $tree_type); foreach my $i (2 .. 100) { my $x = Math::BigInt->new($i); my $y = Math::BigInt->new($i-1); my $n = $path->xy_to_n ($x,$y) || next; my ($n_lo,$n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n < $n_hi, 1, "rect_to_n_range() $tree_type on near diagonal $x,$y, n=$n nhi=$n_hi"); # MyTestHelpers::diag ("n=$n xy=$x,$y nhi=$n_hi"); } } #-------------------------------------------------------------------------- # SacksSpiral require Math::PlanePath::SacksSpiral; { my $path = Math::PlanePath::SacksSpiral->new; my $x = 0; my $y = $bigclass->new(2) ** 128; my ($nlo, $nhi) = $path->rect_to_n_range($x,$y, 0,0); ok (!! ref $nhi, 1, 'SacksSpiral rect_to_n_range() nhi bignum'); } } 1; Math-PlanePath-129/t/AlternatePaperMidpoint.t0000644000175000017500000001602513734026650016763 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 176; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::AlternatePaperMidpoint; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::AlternatePaperMidpoint::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::AlternatePaperMidpoint->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::AlternatePaperMidpoint->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::AlternatePaperMidpoint->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::AlternatePaperMidpoint->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # first few values { my @data = ([ 0, 0,0 ], [ 0.25, 0.25, 0 ], [ 0.75, 0.75, 0 ], [ 1, 1,0 ], [ 1.25, 1.25, 0 ], [ 1.75, 1.75, 0 ], [ 2, 2,0 ], [ 2.25, 2, 0.25 ], [ 2.75, 2, 0.75 ], [ 3, 2,1 ], ); my $path = Math::PlanePath::AlternatePaperMidpoint->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next unless $want_n == int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::AlternatePaperMidpoint->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative ? 1 : 0, 0, 'x_negative() instance method'); ok ($path->y_negative ? 1 : 0, 0, 'y_negative() instance method'); ok ($path->class_x_negative, 1, 'class_x_negative()'); ok ($path->class_y_negative, 1, 'class_y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::AlternatePaperMidpoint->parameter_info_list; ok (join(',',@pnames), 'arms'); } { my $path = Math::PlanePath::AlternatePaperMidpoint->new (arms => 2); ok ($path->x_negative ? 1 : 0, 0, 'x_negative() instance method'); ok ($path->y_negative ? 1 : 0, 0, 'y_negative() instance method'); } { my $path = Math::PlanePath::AlternatePaperMidpoint->new (arms => 3); ok ($path->x_negative ? 1 : 0, 1, 'x_negative() instance method'); ok ($path->y_negative ? 1 : 0, 0, 'y_negative() instance method'); } { my $path = Math::PlanePath::AlternatePaperMidpoint->new (arms => 4); ok ($path->x_negative ? 1 : 0, 1, 'x_negative() instance method'); ok ($path->y_negative ? 1 : 0, 0, 'y_negative() instance method'); } { my $path = Math::PlanePath::AlternatePaperMidpoint->new (arms => 5); ok ($path->x_negative ? 1 : 0, 1, 'x_negative() instance method'); ok ($path->y_negative ? 1 : 0, 1, 'y_negative() instance method'); } { my $path = Math::PlanePath::AlternatePaperMidpoint->new (arms => 8); ok ($path->x_negative ? 1 : 0, 1, 'x_negative() instance method'); ok ($path->y_negative ? 1 : 0, 1, 'y_negative() instance method'); } #------------------------------------------------------------------------------ # random rect_to_n_range() foreach my $arms (1 .. 8) { my $path = Math::PlanePath::AlternatePaperMidpoint->new (arms => $arms); for (1 .. 5) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)); # 0 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); ok (defined $rev_n, 1, "arms=$arms xy_to_n($x,$y) reverse n, got undef"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() n=$n at xy=$x,$y cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() n=$n at xy=$x,$y cf got n_hi=$n_hi"); } } #------------------------------------------------------------------------------ # matching AlternatePaper require Math::PlanePath::AlternatePaper; foreach my $arms (1 .. 8) { my $bad = 0; my $paper = Math::PlanePath::AlternatePaper->new (arms => $arms); my $midpoint = Math::PlanePath::AlternatePaperMidpoint->new (arms => 8); NN: foreach my $n (0 .. 64) { foreach my $arm (0 .. $arms-1) { my $pn = $n*$arms + $arm; my $mn = $n*8 + (($arm+1)%8); my ($x1,$y1) = $paper->n_to_xy($pn); my ($x2,$y2) = $paper->n_to_xy($pn+$arms); my ($mx,$my) = $midpoint->n_to_xy($mn); my $x = $x1+$x2; # midpoint*2 my $y = $y1+$y2; ($x,$y) = (($x-$y+1)/2, ($x+$y+1)/2); # rotate -45 and shift if ($x != $mx || $y != $my) { MyTestHelpers::diag("arms=$arms n=$n,arm=$arm pn=$pn paper $x1,$y1 to $x2,$y2 is $x,$y cf midpoint mn=$mn $mx,$my"); last NN if $bad++ > 10; } } } ok ($bad, 0); } #------------------------------------------------------------------------------ # matching AlternatePaper, 8arms at arm-1 { require Math::PlanePath::AlternatePaper; my $paper = Math::PlanePath::AlternatePaper->new (arms => 8); my $midpoint = Math::PlanePath::AlternatePaperMidpoint->new (arms => 8); my @arm_map = (7,0,1,2,3,4,5,6); my $bad = 0; foreach my $n (0 .. 256) { my ($x1,$y1) = $paper->n_to_xy($n); my ($x2,$y2) = $paper->n_to_xy($n+8); my $mn = ($n & ~7) | $arm_map[$n&7]; my ($mx,$my) = $midpoint->n_to_xy($mn); my $x = $x1+$x2; # midpoint*2 my $y = $y1+$y2; ($x,$y) = (($x+$y-1)/2, ($y-$x-1)/2); if ($x != $mx || $y != $my) { MyTestHelpers::diag("8arm n=$n paper $x1,$y1 to $x2,$y2 is $x,$y cf midpoint mn=$mn $mx,$my"); last if $bad++ > 10; } } ok ($bad, 0); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/DiamondArms.t0000644000175000017500000001033013734026646014544 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 1568; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::DiamondArms; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::DiamondArms::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::DiamondArms->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::DiamondArms->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::DiamondArms->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::DiamondArms->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative, arms_count { my $path = Math::PlanePath::DiamondArms->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); ok ($path->arms_count, 4, 'arms_count()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::DiamondArms->parameter_info_list; ok (join(',',@pnames), ''); } #------------------------------------------------------------------------------ # first few points { my @data = ( [ 1, 0,0 ], [ 1.25, 0.25,-0.25 ], [ 5, 1,-1 ], [ 2, 1,0 ], [ 2.25, 1.25,0.25 ], [ 6, 2,1 ], [ 3, 1,1 ], [ 3.75, 0.25,1.75 ], [ 7, 0,2 ], [ 4, 0,1 ], [ 4.75, -.75,0.25 ], [ 8, -1,0 ], ); my $path = Math::PlanePath::DiamondArms->new; foreach my $elem (@data) { my ($n, $x, $y) = @$elem; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } #------------------------------------------------------------------------------ # xy_to_n() reverse n_to_xy() and rect_to_n_range() { my $path = Math::PlanePath::DiamondArms->new; for (1 .. 500) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); { my $rev_n = $path->xy_to_n ($x,$y); ok ($rev_n, $n, "xy_to_n() reverse n=$n"); } { my ($n_lo,$n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() on $x,$y = $n n_lo $n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() on $x,$y = $n n_hi $n_hi"); } } } exit 0; Math-PlanePath-129/t/BetaOmeta.t0000644000175000017500000001741113734026650014211 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 344; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::BetaOmega; my $path = Math::PlanePath::BetaOmega->new; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::BetaOmega::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::BetaOmega->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::BetaOmega->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::BetaOmega->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 0, 'x_negative() instance method'); ok ($path->y_negative, 1, 'y_negative() instance method'); ok ($path->class_x_negative, 0, 'class_x_negative() instance method'); ok ($path->class_y_negative, 1, 'class_y_negative() instance method'); my @pnames = map {$_->{'name'}} $path->parameter_info_list; ok (join(',',@pnames), '', 'parameter_info_list() keys'); } #------------------------------------------------------------------------------ # first few points { my @data = ([ 0.25, 0, .25 ], [ 1.25, .25, 1 ], [ 2.25, 1, .75 ], [ 3.25, 1, -.25 ], [ 4.25, .75, -1 ], [ 5.25, 0, -1.25 ], [ 6.25, .25, -2 ], [ 7.25, 1.25, -2 ], [ 0, 0,0 ], [ 1, 0,1 ], [ 2, 1,1 ], [ 3, 1,0 ], [ 4, 1,-1 ], [ 5, 0,-1 ], [ 6, 0,-2 ], [ 7, 1,-2 ], [ 32, 4,4 ], [ 33, 4,5 ], [ 34, 5,5 ], [ 35, 5,4 ], [ 96, 1,-7 ], [ 97, 0,-7 ], [ 98, 0,-8 ], [ 99, 1,-8 ], ); foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "n_to_xy() x at n=$n"); ok ($got_y, $want_y, "n_to_xy() y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next unless $want_n==int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } foreach my $elem (@data) { my ($n, $x, $y) = @$elem; my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); next unless $n==int($n); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } #------------------------------------------------------------------------------ # _y_round_down_len_level() foreach my $elem ([0, 1,0], [1, 1,0], [2, 4,2], [3, 4,2], [5, 4,2], [6, 16,4], [7, 16,4], [21, 16,4], [22, 64,6], [23, 64,6], [-1, 2,1], [-2, 2,1], [-3, 8,3], [-4, 8,3], [-10, 8,3], [-11, 32,5], [-12, 32,5], ) { my ($y, $want_len, $want_level) = @$elem; my ($got_len, $got_level) = Math::PlanePath::BetaOmega::_y_round_down_len_level($y); ok ($got_len, $want_len, "len at y=$y"); ok ($got_level, $want_level, "level at y=$y"); } # No it's not simply from y_min. The alternate up and down means it's a # round towards y_max or y_min at alternate levels ... # # require Math::PlanePath::KochCurve; # my $want_y_min = Y_min_pow($want_level); # my ($based_len, $based_level) # = round_down_pow ($y - $want_y_min, 2); # ok ($based_len, $want_len); # ok ($based_level, $want_level); #------------------------------------------------------------------------------ # Ymin / Ymax per docs { sub floor { my ($x) = @_; return int($x); } sub ceil { my ($x) = @_; if ($x == int($x)) { return $x; } return int($x)+1; } sub Y_min_pow { my ($level) = @_; return - (4**floor($level/2) - 1) * 2 / 3; } sub Y_max_pow { my ($level) = @_; return (4**ceil($level/2) - 1) / 3; } sub Y_min_binary { my ($level) = @_; my $ret = 0; while (($level-=2) >= 0) { $ret <<= 1; $ret |= 1; $ret <<= 1; } return -$ret; } sub Y_max_binary { my ($level) = @_; my $ret = 0; $level++; while (($level-=2) >= 0) { $ret <<= 1; $ret <<= 1; $ret |= 1; } return $ret; } my $want_y_min = 0; my $want_y_max = 0; foreach my $level (1 .. 20) { if ($level & 1) { $want_y_max += 2**($level-1); } else { $want_y_min -= 2**($level-1); } # using "==" so that "-0" works in perl 5.6 ok (Y_min_pow($level) == $want_y_min, 1, "level=$level Y min by pow"); ok (Y_max_pow($level) == $want_y_max, 1, "level=$level Y max by pow"); ok (Y_min_binary($level) == $want_y_min, 1, "level=$level Y min by binary"); ok (Y_max_binary($level) == $want_y_max, 1, "level=$level Y max by binary"); } } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::BetaOmega->new; for (1 .. 20) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($n) frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($n) frac $frac, y"); } } } #------------------------------------------------------------------------------ # many fracs { my $path = Math::PlanePath::BetaOmega->new; my ($x,$y) = $path->n_to_xy (0); my $bad = 0; my $pow = 5; for my $n (0 .. 4**$pow+5) { my ($x2,$y2) = $path->n_to_xy ($n+1); my $frac = 0.25; my $want_xf = $x + ($x2-$x)*$frac; my $want_yf = $y + ($y2-$y)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); if ($got_xf != $want_xf || $got_yf != $want_yf) { MyTestHelpers::diag ("wrong at n=$n got $got_xf,$got_yf want $want_xf,$want_yf"); if ($bad++ > 10) { last; } } ($x,$y) = ($x2,$y2); } ok ($bad, 0); } exit 0; Math-PlanePath-129/t/SierpinskiArrowhead.t0000644000175000017500000002222113734026643016322 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 168; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::SierpinskiArrowhead; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::SierpinskiArrowhead::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::SierpinskiArrowhead->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::SierpinskiArrowhead->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::SierpinskiArrowhead->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::SierpinskiArrowhead->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::SierpinskiArrowhead->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); } #------------------------------------------------------------------------------ # turn claimed in the POD sub dxdy_to_dir6 { my ($dx,$dy) = @_; if ($dy == 0) { if ($dx == 2) { return 0; } if ($dx == -2) { return 3; } } if ($dy == 1) { if ($dx == 1) { return 1; } if ($dx == -1) { return 2; } } if ($dy == -1) { if ($dx == 1) { return 5; } if ($dx == -1) { return 4; } } die "unrecognised $dx,$dy"; } sub path_n_dir { my ($path, $n) = @_; my ($dx,$dy) = $path->n_to_dxdy($n) or die "Oops, no point at ",$n; return dxdy_to_dir6 ($dx, $dy); } # return +/- dir6 sub path_n_turn { my ($path, $n) = @_; my $prev_dir = path_n_dir ($path, $n-1); my $dir = path_n_dir ($path, $n); my $turn = ($dir - $prev_dir + 6) % 6; # "+3" to stay +ve for "use integer" if ($turn >= 4) { $turn -= 6; } return $turn; } # return 1 for left, -1 for right sub calc_n_turn { my ($n) = @_; my $ret = 1; # left while ($n && ($n % 3) == 0) { $ret = -$ret; # flip for low 0s $n = int($n/3); } $n = int($n/3); # skip lowest non-0 while ($n) { if (($n % 3) == 1) { $ret = -$ret; # flip for all 1s } $n = int($n/3); } return $ret; } { my $path = Math::PlanePath::SierpinskiArrowhead->new; my $bad = 0; foreach my $n ($path->n_start + 1 .. 500) { { my $path_turn = path_n_turn ($path, $n); my $calc_turn = calc_n_turn ($n); if ($path_turn != $calc_turn) { MyTestHelpers::diag ("turn n=$n path $path_turn calc $calc_turn"); last if $bad++ > 10; } } } ok ($bad, 0, "turn sequence"); } sub LowestNonTwo { my ($n) = @_; while (($n % 3) == 2) { $n = int($n/3); } return $n % 3; } sub CountLowTwos { my ($n) = @_; my $ret = 0; while (($n % 3) == 2) { $n = int($n/3); $ret++; } return $ret; } # lowest non-2 and its position claimed in the POD sub calc_m_even_is_right { my ($m) = @_; return (LowestNonTwo($m-1) + CountLowTwos($m-1)) % 2; } { my $path = Math::PlanePath::SierpinskiArrowhead->new; my $bad = 0; foreach my $m ($path->n_start + 1 .. 500) { my $n = 2*$m; my $path_turn = (path_n_turn ($path, 2*$m) == -1 ? 1 : 0); my $calc_turn = calc_m_even_is_right ($m); if ($path_turn != $calc_turn) { MyTestHelpers::diag ("turn n=2*$m path $path_turn calc $calc_turn"); last if $bad++ > 10; } } ok ($bad, 0, "turn sequence"); } sub LowestNonOne { my ($n) = @_; while (($n % 3) == 1) { $n = int($n/3); } return $n % 3; } sub CountLowOnes { my ($n) = @_; my $ret = 0; while (($n % 3) == 1) { $n = int($n/3); $ret++; } return $ret; } # lowest non-2 and its position claimed in the POD sub calc_m_odd_is_right { my ($m) = @_; return ((LowestNonOne($m)/2) + CountLowOnes($m)) % 2; } { my $path = Math::PlanePath::SierpinskiArrowhead->new; my $bad = 0; foreach my $m ($path->n_start + 1 .. 500) { my $n = 2*$m; my $path_turn = (path_n_turn ($path, 2*$m+1) == -1 ? 1 : 0); my $calc_turn = calc_m_odd_is_right ($m); if ($path_turn != $calc_turn) { MyTestHelpers::diag ("turn n=2*$m path $path_turn calc $calc_turn"); last if $bad++ > 10; } } ok ($bad, 0, "turn sequence"); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::SierpinskiArrowhead->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 3); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 9); } { my ($n_lo,$n_hi) = $path->level_to_n_range(3); ok ($n_lo, 0); ok ($n_hi, 27); } } #------------------------------------------------------------------------------ # first few points { my @data = ([ 0, 0,0 ], [ 1, 1,1 ], [ 2, 0,2 ], [ 3, -2,2 ], [ .25, .25, .25 ], [ 1.25, .75, 1.25 ], [ 2.5, -1, 2 ], ); my $path = Math::PlanePath::SierpinskiArrowhead->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $want_x, "n_to_xy() x at n=$n"); ok ($got_y, $want_y, "n_to_xy() y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next unless $want_n==int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } foreach my $elem (@data) { my ($n, $x, $y) = @$elem; my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); next unless $n==int($n); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } #------------------------------------------------------------------------------ # xy_to_n() distinct n and all n { my $path = Math::PlanePath::SierpinskiArrowhead->new; my $bad = 0; my @seen; my $xlo = -16; my $xhi = 16; my $ylo = 0; my $yhi = 16; my $count = 0; my ($nlo, $nhi) = $path->rect_to_n_range($xlo,$ylo, $xhi,$yhi); OUTER: for (my $x = $xlo; $x <= $xhi; $x++) { for (my $y = $ylo; $y <= $yhi; $y++) { my $n = $path->xy_to_n ($x,$y); if (! defined $n) { next; } if ($seen[$n]) { MyTestHelpers::diag ("x=$x,y=$y n=$n seen before at $seen[$n]"); last if $bad++ > 10; } my ($rx,$ry) = $path->n_to_xy ($n); if ($rx != $x || $ry != $y) { MyTestHelpers::diag ("x=$x,y=$y n=$n goes back to rx=$rx ry=$ry"); last OUTER if $bad++ > 10; } if ($n < $nlo) { MyTestHelpers::diag ("x=$x,y=$y n=$n below nlo=$nlo"); last OUTER if $bad++ > 10; } if ($n > $nhi) { MyTestHelpers::diag ("x=$x,y=$y n=$n above nhi=$nhi"); last OUTER if $bad++ > 10; } $seen[$n] = "$x,$y"; $count++; } } foreach my $n (0 .. $#seen) { if (! defined $seen[$n]) { MyTestHelpers::diag ("n=$n not seen"); last if $bad++ > 10; } } ok ($bad, 0, "xy_to_n() coverage, $count points"); } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::SierpinskiArrowhead->new; for (1 .. 20) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($n) frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($n) frac $frac, y"); } } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/FlowsnakeCentres.t0000644000175000017500000001412513734026646015631 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 347; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::FlowsnakeCentres; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::FlowsnakeCentres::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::FlowsnakeCentres->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::FlowsnakeCentres->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::FlowsnakeCentres->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::FlowsnakeCentres->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::FlowsnakeCentres->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::FlowsnakeCentres->parameter_info_list; ok (join(',',@pnames), 'arms'); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::FlowsnakeCentres->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 0); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 6); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 48); } # 1*49 numbered 9 onwards so 49-1 = 48 } { my $path = Math::PlanePath::FlowsnakeCentres->new (arms => 3); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 2); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 20); } # 3*7 numbered 0 onwards so 3*7-1 = 20 { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 146); } # 3*49 numbered 0 onwards so 3*49-1 = 146 } #------------------------------------------------------------------------------ # first few points { my @data = ( [ .25, .5, 0 ], [ .5, 1, 0 ], [ 1.75, 1.25, .75 ], [ 0, 0,0 ], [ 1, 2,0 ], [ 2, 1,1 ], [ 3, -1,1 ], [ 4, 0,2 ], [ 5, 2,2 ], [ 6, 3,1 ], [ 7, 5,1 ], ); my $path = Math::PlanePath::FlowsnakeCentres->new; foreach my $elem (@data) { my ($n, $x, $y) = @$elem; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::FlowsnakeCentres->new; my ($n_lo, $n_hi) = $path->rect_to_n_range(0,0, 0,0); ok ($n_lo == 0, 1, "rect_to_n_range() 0,0 n_lo=$n_lo"); ok ($n_hi >= 0, 1, "rect_to_n_range() 0,0 n_hi=$n_hi"); } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::FlowsnakeCentres->new; for (1 .. 20) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($n) frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($n) frac $frac, y"); } } } #------------------------------------------------------------------------------ # random points { my $path = Math::PlanePath::FlowsnakeCentres->new; for (1 .. 50) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } ok ($rev_n, $n, "xy_to_n($x,$y) reverse to expect n=$n, got $rev_n"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() reverse n=$n cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() reverse n=$n cf got n_hi=$n_hi"); } } exit 0; Math-PlanePath-129/t/bigfloat.t0000644000175000017500000002255112710322243014126 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2016 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; use lib 't'; use MyTestHelpers; # uncomment this to run the ### lines # use Smart::Comments '###'; my $test_count = (tests => 59)[1]; plan tests => $test_count; require Math::BigFloat; MyTestHelpers::diag ('Math::BigFloat version ', Math::BigFloat->VERSION); { my $f; if (! eval { $f = Math::BigFloat->new(2) ** 3; 1 }) { MyTestHelpers::diag ('skip due to Math::BigFloat no "**" operator -- ',$@); MyTestHelpers::diag ('value ',$f); foreach (1 .. $test_count) { skip ('due to no Math::BigFloat "**" operator', 1, 1); } exit 0; } } unless (eval { Math::BigFloat->VERSION(1.993); 1 }) { # something fishy for PeanoCurve and ZOrderCurve fraction n_to_xy() MyTestHelpers::diag ('skip due to doubtful oldish Math::BigFloat, maybe'); foreach (1 .. $test_count) { skip ('due to oldish Math::BigFloat', 1, 1); } exit 0; } # unless ($] > 5.008) { # # something fishy for BigFloat on 5.6.2, worry about it later # MyTestHelpers::diag ('skip due to doubtful Math::BigFloat on 5.6.x, maybe'); # foreach (1 .. $test_count) { # skip ('due to Perl 5.6', 1, 1); # } # exit 0; # } require Math::BigInt; MyTestHelpers::diag ('Math::BigInt version ', Math::BigInt->VERSION); { my $n = Math::BigInt->new(2) ** 256; my $int = int($n); if (! ref $int) { MyTestHelpers::diag ('skip due to Math::BigInt no "int" operator'); foreach (1 .. $test_count) { skip ('due to no Math::BigInt int() operator', 1, 1); } exit 0; } } MyTestHelpers::nowarnings(); # after exponentiation test warnings Math::BigFloat->accuracy(150); # significant digits # Something dodgy in BigFloat 1.997 in perl 5.14.3 makes bpi() give NaN if # precision() is set, so don't do that. # Math::BigFloat->precision(-20); # digits right of decimal point # # { # my $pi = Math::BigFloat->bpi; # ### bpi(): $pi # } #------------------------------------------------------------------------------ # round_down_pow() use Math::PlanePath::Base::Digits 'round_down_pow'; { my $orig = Math::BigFloat->new(3) ** 64; my $n = Math::BigFloat->new(3) ** 64; my ($pow,$exp) = round_down_pow($n,3); ok ($n, $orig, "round_down_pow(3) unmodified input"); ok ($pow == Math::BigFloat->new(3.0) ** 64, 1, "round_down_pow(3) 3^64 + 1.25 power"); ok ($exp == 64, 1, "round_down_pow(3) 3^64 + 1.25 exp"); } { my $orig = Math::BigFloat->new(3) ** 64 + 1.25; my $n = Math::BigFloat->new(3) ** 64 + 1.25; my ($pow,$exp) = round_down_pow($n,3); ### pow: "$pow" ### exp: "$exp" ok ($n, $orig, "round_down_pow(3) unmodified input"); ok ($pow == Math::BigFloat->new(3.0) ** 64, 1, "round_down_pow(3) 3^64 + 1.25 power"); ok ($exp == 64, 1, "round_down_pow(3) 3^64 + 1.25 exp"); } #------------------------------------------------------------------------------ # round_nearest() use Math::PlanePath::Base::Generic 'round_nearest'; ok (round_nearest(Math::BigFloat->new('-.75')) == -1, 1); ok (round_nearest(Math::BigFloat->new('-.5')) == 0, 1); ok (round_nearest(Math::BigFloat->new('-.25')) == 0, 1); ok (round_nearest(Math::BigFloat->new('.25')) == 0, 1); ok (round_nearest(Math::BigFloat->new('1.25')) == 1, 1); ok (round_nearest(Math::BigFloat->new('1.5')) == 2, 1); ok (round_nearest(Math::BigFloat->new('1.75')) == 2, 1); ok (round_nearest(Math::BigFloat->new('2')) == 2, 1); #------------------------------------------------------------------------------ # MultipleRings { require Math::PlanePath::MultipleRings; my $width = 5; my $path = Math::PlanePath::MultipleRings->new (step => 6); { my $n = Math::BigFloat->new(4); my ($got_x,$got_y) = $path->n_to_xy($n); ok (!! (ref $got_x && $got_x->isa('Math::BigFloat')), 1); ok ($got_x >= -1.01 && $got_x <= -0.99, 1, "MultipleRings n_to_xy($n) got_x $got_x"); ok ($got_y == 0, 1); } { my $pi = Math::PlanePath::MultipleRings::_pi(0); ok ($pi > 3); ok ($pi < 4); } { my $n = Math::BigFloat->new(4); my $pi = Math::PlanePath::MultipleRings::_pi($n); ok ($pi > 3); ok ($pi < 4); } } #------------------------------------------------------------------------------ # Diagonals { require Math::PlanePath::Diagonals; my $path = Math::PlanePath::Diagonals->new; { my $x = Math::BigFloat->new(2) ** 128 - 1; my $n = ($x+1)*($x+2)/2; # triangular numbers on Y=0 horizontal my ($got_x,$got_y) = $path->n_to_xy($n); $got_x = $x->copy; ok ($got_x == $x, 1, "got x=$got_x want x=$x"); ### $x ### $got_x ### diff: ($x-$got_x)."" ### diff: ($x-$got_x)==0 ok ($got_y == 0); my $got_n = $path->xy_to_n($x,0); ok ($got_n == $n, 1); } { my $x = Math::BigFloat->new(2) ** 128 - 1; my $n = ($x+1)*($x+2)/2; # Y=0 horizontal my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $x, 1); ok ($got_y == 0, 1); my $got_n = $path->xy_to_n($x,0); ok ($got_n == $n, 1); } { my $y = Math::BigFloat->new(2) ** 128 - 1; my $n = $y*($y+1)/2 + 1; # X=0 vertical my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == 0, 1, "Diagonals x of n_to_xy for x=0 y=2^128-1"); ok ($got_y == $y, 1, "Diagonals x of n_to_xy for x=0 y=2^128-1"); my $got_n = $path->xy_to_n(0,$y); ok ($got_n, $n, "Diagonals xy_to_n() at x=0 y=2^128-1"); } { my $n = Math::BigFloat->new(-1); my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x, undef); ok ($got_y, undef); } { my $n = Math::BigFloat->new(0.5); my ($got_x,$got_y) = $path->n_to_xy($n); ### $got_x ### $got_y ok (!! $got_x->isa('Math::BigFloat'), 1); ok (!! $got_y->isa('Math::BigFloat'), 1); ok ($got_x == -0.5, 1); ok ($got_y == 0.5, 1); } } #------------------------------------------------------------------------------ # is_infinite() use Math::PlanePath::Base::Generic 'is_infinite'; { my $x = Math::BigFloat->new(0); $x->binf; MyTestHelpers::diag ("+inf is ",$x); ok (!! is_infinite($x), 1, '_is_infinte() BigFloat +inf'); $x->binf('-'); MyTestHelpers::diag ("-inf is ",$x); ok (!! is_infinite($x), 1, '_is_infinte() BigFloat -inf'); $x->bnan(); MyTestHelpers::diag ("nan is ",$x); ok (!! is_infinite($x), 1, '_is_infinte() BigFloat nan'); } #------------------------------------------------------------------------------ # floor() use Math::PlanePath::Base::Generic 'floor'; ok (floor(Math::BigFloat->new('-.75')) == -1, 1); ok (floor(Math::BigFloat->new('-.5')) == -1, 1); ok (floor(Math::BigFloat->new('-.25')) == -1, 1); ok (floor(Math::BigFloat->new('.25')) == 0, 1); ok (floor(Math::BigFloat->new('.75')) == 0, 1); ok (floor(Math::BigFloat->new('1.25')) == 1, 1); ok (floor(Math::BigFloat->new('1.5')) == 1, 1); ok (floor(Math::BigFloat->new('1.75')) == 1, 1); ok (floor(Math::BigFloat->new('2')) == 2, 1); #------------------------------------------------------------------------------ # Rows { require Math::PlanePath::Rows; my $width = 5; my $path = Math::PlanePath::Rows->new (width => $width); { my $y = Math::BigFloat->new(2) ** 128; my $x = 4; my $n = $y*$width + $x + 1; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $x, 1, "got x=$got_x want x=$x"); ok ($got_y == $y); my $got_n = $path->xy_to_n($x,$y); ok ($got_n == $n, 1); } { my $n = Math::BigFloat->new('1.5'); my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == 0.5, 1); ok ($got_y == 0, 1); } { my $n = Math::BigFloat->new('1.5') + 15; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == 0.5, 1); ok ($got_y == 3, 1); } } #------------------------------------------------------------------------------ # PeanoCurve require Math::PlanePath::PeanoCurve; { my $path = Math::PlanePath::PeanoCurve->new; require Math::BigFloat; my $n = Math::BigFloat->new(9) ** 128 + 1.5; my $want_x = Math::BigFloat->new(3) ** 128 + 1.5; my $want_y = Math::BigFloat->new(3) ** 128 - 1; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $want_x, 1, "PeanoCurve 9^128 + 1.5 X got $got_x want $want_x"); ok ($got_y == $want_y, 1, "PeanoCurve 9^128 + 1.5 Y got $got_y want $want_y"); } #------------------------------------------------------------------------------ # ZOrderCurve require Math::PlanePath::ZOrderCurve; { my $path = Math::PlanePath::ZOrderCurve->new; require Math::BigFloat; my $n = Math::BigFloat->new(4) ** 128 + 0.5; my $want_x = Math::BigFloat->new(2) ** 128 + 0.5; my $want_y = 0; my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $want_x, 1, "ZOrderCurve 4^128 + 0.5 X got $got_x want $want_x"); ok ($got_y == $want_y, 1, "ZOrderCurve 4^128 + 0.5 Y got $got_y want $want_y"); } exit 0; Math-PlanePath-129/t/HilbertSpiral.t0000644000175000017500000001510513734026645015116 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 212; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::HilbertSpiral; my $path = Math::PlanePath::HilbertSpiral->new; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::HilbertSpiral::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::HilbertSpiral->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::HilbertSpiral->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::HilbertSpiral->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative() instance method'); ok ($path->y_negative, 1, 'y_negative() instance method'); my @pnames = map {$_->{'name'}} $path->parameter_info_list; ok (join(',',@pnames), '', 'parameter_info_list() keys'); } #------------------------------------------------------------------------------ # first few points # { # my @data = ([ 0.25, 0, .25 ], # [ 1.25, .25, 1 ], # [ 2.25, 1, .75 ], # [ 3.25, 1, -.25 ], # # [ 4.25, .75, -1 ], # [ 5.25, 0, -1.25 ], # [ 6.25, .25, -2 ], # [ 7.25, 1.25, -2 ], # # # [ 0, 0,0 ], # [ 1, 0,1 ], # [ 2, 1,1 ], # [ 3, 1,0 ], # # [ 4, 1,-1 ], # [ 5, 0,-1 ], # [ 6, 0,-2 ], # [ 7, 1,-2 ], # # [ 32, 4,4 ], # [ 33, 4,5 ], # [ 34, 5,5 ], # [ 35, 5,4 ], # # [ 96, 1,-7 ], # [ 97, 0,-7 ], # [ 98, 0,-8 ], # [ 99, 1,-8 ], # ); # foreach my $elem (@data) { # my ($n, $want_x, $want_y) = @$elem; # my ($got_x, $got_y) = $path->n_to_xy ($n); # ok ($got_x, $want_x, "n_to_xy() x at n=$n"); # ok ($got_y, $want_y, "n_to_xy() y at n=$n"); # } # # foreach my $elem (@data) { # my ($want_n, $x, $y) = @$elem; # next unless $want_n==int($want_n); # my $got_n = $path->xy_to_n ($x, $y); # ok ($got_n, $want_n, "n at x=$x,y=$y"); # } # # foreach my $elem (@data) { # my ($n, $x, $y) = @$elem; # my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); # next unless $n==int($n); # ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); # ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); # } # } #------------------------------------------------------------------------------ # XYmin / XYmax per docs { sub floor { my ($x) = @_; return int($x); } sub ceil { my ($x) = @_; if ($x == int($x)) { return $x; } return int($x)+1; } sub Y_min_pow { my ($level) = @_; return - (4**floor($level/2) - 1) * 2 / 3; } sub Y_max_pow { my ($level) = @_; return (4**ceil($level/2) - 1) / 3; } sub Y_min_binary { my ($level) = @_; my $ret = 0; while (($level-=2) >= 0) { $ret <<= 1; $ret |= 1; $ret <<= 1; } return -$ret; } sub Y_max_binary { my ($level) = @_; my $ret = 0; $level++; while (($level-=2) >= 0) { $ret <<= 1; $ret <<= 1; $ret |= 1; } return $ret; } my $want_y_min = 0; my $want_y_max = 0; foreach my $level (1 .. 20) { if ($level & 1) { $want_y_max += 2**($level-1); } else { $want_y_min -= 2**($level-1); } # using "==" so that "-0" works in perl 5.6 ok (Y_min_pow($level) == $want_y_min, 1, "level=$level Y min by pow"); ok (Y_max_pow($level) == $want_y_max, 1, "level=$level Y max by pow"); ok (Y_min_binary($level) == $want_y_min, 1, "level=$level Y min by binary"); ok (Y_max_binary($level) == $want_y_max, 1, "level=$level Y max by binary"); } } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::HilbertSpiral->new; for (1 .. 20) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($n) frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($n) frac $frac, y"); } } } #------------------------------------------------------------------------------ # many fracs { my $path = Math::PlanePath::HilbertSpiral->new; my ($x,$y) = $path->n_to_xy (0); my $bad = 0; my $pow = 5; for my $n (0 .. 4**$pow+5) { my ($x2,$y2) = $path->n_to_xy ($n+1); my $frac = 0.25; my $want_xf = $x + ($x2-$x)*$frac; my $want_yf = $y + ($y2-$y)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); if ($got_xf != $want_xf || $got_yf != $want_yf) { MyTestHelpers::diag ("wrong at n=$n got $got_xf,$got_yf want $want_xf,$want_yf"); if ($bad++ > 10) { last; } } ($x,$y) = ($x2,$y2); } ok ($bad, 0); } exit 0; Math-PlanePath-129/t/ChanTree.t0000644000175000017500000001373213734026650014043 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 269;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::CoprimeColumns; *_coprime = \&Math::PlanePath::CoprimeColumns::_coprime; use Math::PlanePath::GcdRationals; *_gcd = \&Math::PlanePath::GcdRationals::_gcd; use Math::PlanePath::ChanTree; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::ChanTree::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::ChanTree->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::ChanTree->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::ChanTree->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::ChanTree->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::ChanTree->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); ok ($path->class_x_negative, 0, 'class_x_negative() instance method'); ok ($path->class_y_negative, 0, 'class_y_negative() instance method'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::ChanTree->parameter_info_list; ok (join(',',@pnames), 'k,n_start'); } { my $path = Math::PlanePath::ChanTree->new; ok ($path->tree_num_children_minimum, 3, 'tree_num_children_minimum()'); ok ($path->tree_num_children_maximum, 3, 'tree_num_children_maximum()'); } { my $path = Math::PlanePath::ChanTree->new (k => 123); ok ($path->tree_num_children_minimum, 123, 'tree_num_children_minimum()'); ok ($path->tree_num_children_maximum, 123, 'tree_num_children_maximum()'); } #------------------------------------------------------------------------------ { my @data = ( [ { k => 8, reduced => 1, n_start => 1 }, [ 11, 5,6 ], # 10/12 reduced ], [ { k => 3, reduced => 1, n_start => 1 }, # [ 0, 1,84 ], ], [ { k => 3, reduced => 0, n_start => 1 }, [ 1, 1,2 ], [ 2, 2,1 ], [ 3, 1,4 ], [ 4, 4,5 ], [ 5, 5,2 ], [ 6, 2,5 ], [ 7, 5,4 ], [ 8, 4,1 ], [ 9, 1,6 ], ], [ { k => 4, reduced => 0, n_start => 1 }, [ 1, 1,2 ], [ 2, 2,2 ], [ 3, 2,1 ], [ 4, 1,4 ], # under 1 [ 5, 4,6 ], [ 6, 6,5 ], [ 7, 5,2 ], [ 8, 2,6 ], # under 2 [ 9, 6,8 ], [ 10, 8,6 ], [ 11, 6,2 ], [ 12, 2,5 ], # under 3 [ 13, 5,6 ], [ 14, 6,4 ], [ 15, 4,1 ], [ 16, 1,6 ], [ 17, 6,10 ], # 1,2 -> 1,4 -> 6,10 [ 30, 14,9 ], # 1,2 -> 5,2 -> 14,9 ], [ { k => 4, reduced => 1, n_start => 1 }, [ 1, 1,2 ], [ 2, 1,1 ], # 2,2 [ 3, 2,1 ], [ 4, 1,4 ], [ 5, 2,3 ], # 4,6 [ 6, 6,5 ], [ 7, 5,2 ], [ 8, 1,3 ], # 2,6 ], ); foreach my $group (@data) { my ($options, @elems) = @$group; my $path = Math::PlanePath::ChanTree->new (%$options); my $k = $options->{'k'}; my $n_start = $options->{'n_start'}; my $reduced = $options->{'reduced'} || 0; foreach my $elem (@elems) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "k=$k,r=$reduced x at n=$n"); ok ($got_y, $want_y, "k=$k,r=$reduced y at n=$n"); } foreach my $elem (@elems) { my ($want_n, $x, $y) = @$elem; next unless $want_n==int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "k=$k,r=$reduced xy_to_n($x,$y) sample"); } foreach my $elem (@elems) { my ($n, $x, $y) = @$elem; if ($n == int($n)) { my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo == $n_start, 1, "rect_to_n_range() got_nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range(0,0,$x,$y) got_nhi=$got_nhi at n=$n,x=$x,y=$y"); ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() got_nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() got_nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/HexSpiral-load.t0000644000175000017500000000201011554146764015160 0ustar gggg#!/usr/bin/perl -w # Copyright 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . ## no critic (RequireUseStrict, RequireUseWarnings) use Math::PlanePath::HexSpiral; my $path = Math::PlanePath::HexSpiral->new (width => 1000); $path->n_to_xy(123); $path->xy_to_n(0,0); $path->rect_to_n_range(0,0, 1,1); use Test; plan tests => 1; ok (1,1, 'Math::PlanePath::HexSpiral load as first thing'); exit 0; Math-PlanePath-129/t/KochPeaks.t0000644000175000017500000001346013734026645014224 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 114; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::KochPeaks; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::KochPeaks::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::KochPeaks->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::KochPeaks->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::KochPeaks->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::KochPeaks->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::KochPeaks->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); my @pnames = map {$_->{'name'}} $path->parameter_info_list; ok (join(',',@pnames), ''); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::KochPeaks->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 1); ok ($n_hi, 3); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 4); ok ($n_hi, 12); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 13); ok ($n_hi, 45); } foreach my $level (0 .. 6) { my ($n_lo,$n_hi) = $path->level_to_n_range($level); { my ($x,$y) = $path->n_to_xy($n_lo); ok ($y, 0, 'level at Y=0'); } { my ($x,$y) = $path->n_to_xy($n_hi); ok ($y, 0, 'level at Y=0'); } } } #------------------------------------------------------------------------------ # first few points { my @data = ([ .5, -1.5, -.5 ], [ 3.25, 1.25, -.25 ], [ 3.75, -3.25, -.25 ], [ 1, -1,0 ], [ 2, 0,1 ], [ 3, 1,0 ], [ 4, -3,0 ], [ 5, -2,1 ], [ 6, -3,2 ], [ 7, -1,2 ], [ 8, 0,3 ], [ 9, 1,2 ], [ 10, 3,2 ], [ 11, 2,1 ], [ 12, 3,0 ], [ 13, -9,0 ], ); my $path = Math::PlanePath::KochPeaks->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next unless $want_n==int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } foreach my $elem (@data) { my ($n, $x, $y) = @$elem; $n = int($n+.5); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } #------------------------------------------------------------------------------ # xy_to_n() distinct n { my $path = Math::PlanePath::KochPeaks->new; my $bad = 0; my %seen; my $xlo = -5; my $xhi = 100; my $ylo = -5; my $yhi = 100; my ($nlo, $nhi) = $path->rect_to_n_range($xlo,$ylo, $xhi,$yhi); my $count = 0; OUTER: for (my $x = $xlo; $x <= $xhi; $x++) { for (my $y = $ylo; $y <= $yhi; $y++) { next unless ($x ^ $y) & 1; my $n = $path->xy_to_n ($x,$y); next if ! defined $n; # sparse if ($seen{$n}) { MyTestHelpers::diag ("x=$x,y=$y n=$n seen before at $seen{$n}"); last if $bad++ > 10; } if ($n < $nlo) { MyTestHelpers::diag ("x=$x,y=$y n=$n below nlo=$nlo"); last OUTER if $bad++ > 10; } if ($n > $nhi) { MyTestHelpers::diag ("x=$x,y=$y n=$n above nhi=$nhi"); last OUTER if $bad++ > 10; } $seen{$n} = "$x,$y"; $count++; } } ok ($bad, 0, "xy_to_n() coverage and distinct, $count points"); } #------------------------------------------------------------------------------ # Xlo occurances { my $path = Math::PlanePath::KochPeaks->new; foreach my $level (0 .. 4) { my $n_start = $level + (2*4**$level + 1)/3; my $n_peak = $level + (5*4**$level + 1)/3; my ($x_lo,undef) = $path->n_to_xy($n_start); my $x_lo_count = 0; foreach my $n ($n_start .. $n_peak) { my ($x,undef) = $path->n_to_xy($n); if ($x == $x_lo) { ### found at: "level=$level x=$x n=$n" $x_lo_count++; } } ok ($x_lo_count, 2**$level); } } exit 0; Math-PlanePath-129/t/AlternateTerdragon.t0000644000175000017500000001613413734026650016136 0ustar gggg#!/usr/bin/perl -w # Copyright 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 120; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::AlternateTerdragon; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::AlternateTerdragon::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::AlternateTerdragon->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::AlternateTerdragon->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::AlternateTerdragon->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::AlternateTerdragon->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::AlternateTerdragon->new; ok ($path->n_start, 0, 'n_start()'); ok (! $path->x_negative, 1, 'x_negative()'); ok ( $path->y_negative, 1, 'y_negative()'); ok ($path->class_x_negative, 1, 'class_x_negative()'); ok ($path->class_y_negative, 1, 'class_y_negative()'); } { my $path = Math::PlanePath::AlternateTerdragon->new (arms => 2); ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::AlternateTerdragon->parameter_info_list; ok (join(',',@pnames), 'arms'); } #------------------------------------------------------------------------------ # xy_to_n_list() # at 0,0 foreach my $arms (1 .. 6) { my $path = Math::PlanePath::AlternateTerdragon->new (arms => $arms); my @got_n_list = $path->xy_to_n_list(0,0); my $got_n_list = join(',',@got_n_list); my $want_n_list = join(',', 0 .. $arms-1); ok ($got_n_list, $want_n_list); } { # arms=6 points shown in the POD # # --- 7,8,26 ---------------- 1,12,19 --- # / \ / \ # \ / \ / \ / # \ / \ / \ / # --- 3,14,21 ------------- 0/1/2/3/4/5 -------------- 6,11,24 --- # / \ / \ / \ # / \ / \ / \ # \ / \ / # --- 9,10,28 ---------------- 5,16,23 --- my $path = Math::PlanePath::AlternateTerdragon->new (arms => 6); foreach my $elem ([ 0, 0, '0,1,2,3,4,5'], [ 2, 0, '6,11,24'], [ 1, 1, '1,12,19'], [-1, 1, '7,8,26'], [-2, 0, '3,14,21'], [-1,-1, '9,10,28'], [ 1,-1, '5,16,23'], ) { my ($x,$y, $want_n_list) = @$elem; my @got_n_list = $path->xy_to_n_list($x,$y); my $got_n_list = join(',',@got_n_list); ok ($got_n_list, $want_n_list); } } #------------------------------------------------------------------------------ # n_to_dxdy() { my $path = Math::PlanePath::AlternateTerdragon->new (arms => 2); my $arms_count = $path->arms_count; ok ($arms_count, 2); my $n = 1; my ($x,$y) = $path->n_to_xy($n); ok ($x, 0); ok ($y, 0); my ($x2,$y2) = $path->n_to_xy($n + $arms_count); ok ($x2, 1); ok ($y2, 1); my ($dx,$dy) = $path->n_to_dxdy($n); ok ($dx, 1); ok ($dy, 1); } { my $path = Math::PlanePath::AlternateTerdragon->new; # 1 2 # \ # * # \ # Y=0 0---*---1 # X=0 1 2 my $n = .5; my ($x,$y) = $path->n_to_xy($n); ok ($x, 1); ok ($y, 0); my ($x2,$y2) = $path->n_to_xy($n+1); ok ($x2, 1.5); ok ($y2, .5); my ($dx,$dy) = $path->n_to_dxdy($n); ok ($dx, 1.5 - 1); ok ($dy, .5 - 0); } { my $path = Math::PlanePath::AlternateTerdragon->new; # 1 2------3 # \ # \ # * # Y=0 0-------1 # X=0 1 2 my $n = 1.25; my ($x,$y) = $path->n_to_xy($n); ok ($x, 1.75); ok ($y, .25); my ($x2,$y2) = $path->n_to_xy($n+1); ok ($x2, 1.5); ok ($y2, 1); my ($dx,$dy) = $path->n_to_dxdy($n); ok ($dx, 1.5 - 1.75); ok ($dy, 1 - .25); } { my $path = Math::PlanePath::AlternateTerdragon->new; # 1 2---*---3 # \ / # * * # \ / # Y=0 0---*---1 # X=0 1 2 3 4 my $n = 2.5; my ($x,$y) = $path->n_to_xy($n); ok ($x, 2); ok ($y, 1); my ($x2,$y2) = $path->n_to_xy($n+1); ok ($x2, 2.5); ok ($y2, .5); my ($dx,$dy) = $path->n_to_dxdy($n); ok ($dx, 2.5 - 2); ok ($dy, .5 - 1); } #------------------------------------------------------------------------------ # xy_to_n_list() { # 1 2-------3 # \ / # \ / # \ / # Y=0 0------1,4 # X=0 1 2 { my $path = Math::PlanePath::AlternateTerdragon->new; my @n_list = $path->xy_to_n_list(2,0); ok (join(',',@n_list), '1,4'); } { my $path = Math::PlanePath::AlternateTerdragon->new (arms => 2); my @n_list = $path->xy_to_n_list(2,0); ok (join(',',@n_list), '2,8'); } } #------------------------------------------------------------------------------ # random rect_to_n_range() foreach my $arms (1 .. 4) { my $path = Math::PlanePath::AlternateTerdragon->new (arms => $arms); ok ($path->arms_count, $arms, 'arms_count()'); for (1 .. 5) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)); # 0 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); ok (defined $rev_n, 1, "xy_to_n($x,$y) arms=$arms reverse n, got undef"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() arms=$arms n=$n at xy=$x,$y cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() arms=$arms n=$n at xy=$x,$y cf got n_hi=$n_hi"); } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/HexArms.t0000644000175000017500000001150313734026645013717 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 126; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::HexArms; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::HexArms::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::HexArms->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::HexArms->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::HexArms->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::HexArms->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative, arms_count { my $path = Math::PlanePath::HexArms->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); ok ($path->arms_count, 6, 'arms_count()'); } #------------------------------------------------------------------------------ # first few points { my @data = ([ 1, 0,0 ], [ 2, 2,0 ], [ 8, 3,1 ], [ 14, 2,2 ], [ 3, 1,1 ], [ 9, 0,2 ], [ 15, -2,2 ], [ 4, -1,1 ], [ 10, -3,1 ], [ 16, -4,0 ], [ 5, -2,0 ], [ 11, -3,-1 ], [ 17, -2,-2 ], [ 6, -1,-1 ], [ 12, 0,-2 ], [ 18, 2,-2 ], [ 7, 1,-1 ], [ 13, 3,-1 ], [ 19, 4,0 ], [ 1.75, .75, -.75 ], [ 7.5, 2, -1 ], [ 2.25, 2.25, .25 ], [ 4.25, -1.5, 1 ], [ 5.25, -2.25, -.25 ], ); my $path = Math::PlanePath::HexArms->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid -0 if ($got_y == 0) { $got_y = 0 } ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next unless $want_n==int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } foreach my $elem (@data) { my ($n, $x, $y) = @$elem; my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } #------------------------------------------------------------------------------ # xy_to_n() distinct n # { # my $path = Math::PlanePath::HexArms->new; # my $bad = 0; # my %seen; # my $xlo = -5; # my $xhi = 100; # my $ylo = -5; # my $yhi = 100; # my ($nlo, $nhi) = $path->rect_to_n_range($xlo,$ylo, $xhi,$yhi); # my $count = 0; # OUTER: for (my $x = $xlo; $x <= $xhi; $x++) { # for (my $y = $ylo; $y <= $yhi; $y++) { # next if ($x ^ $y) & 1; # my $n = $path->xy_to_n ($x,$y); # next if ! defined $n; # sparse # # if ($seen{$n}) { # MyTestHelpers::diag ("x=$x,y=$y n=$n seen before at $seen{$n}"); # last if $bad++ > 10; # } # if ($n < $nlo) { # MyTestHelpers::diag ("x=$x,y=$y n=$n below nlo=$nlo"); # last OUTER if $bad++ > 10; # } # if ($n > $nhi) { # MyTestHelpers::diag ("x=$x,y=$y n=$n above nhi=$nhi"); # last OUTER if $bad++ > 10; # } # $seen{$n} = "$x,$y"; # $count++; # } # } # ok ($bad, 0, "xy_to_n() coverage and distinct, $count points"); # } exit 0; Math-PlanePath-129/t/CellularRule190.t0000644000175000017500000001434713734026650015202 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 506; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::CellularRule190; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::CellularRule190::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::CellularRule190->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::CellularRule190->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::CellularRule190->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::CellularRule190->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::CellularRule190->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); ok ($path->class_x_negative, 1, 'class_x_negative() instance method'); ok ($path->class_y_negative, 0, 'class_y_negative() instance method'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::CellularRule190->parameter_info_list; ok (join(',',@pnames), 'mirror,n_start'); } { my $path = Math::PlanePath::CellularRule190->new (n_start => 123); ok ($path->n_start, 123, 'n_start()'); } #------------------------------------------------------------------------------ # first few points { my @data = ( [ 0, [ 1, 0,0 ], [ 2, -1,1 ], [ 3, 0,1 ], [ 4, 1,1 ], [ 5, -2,2 ], [ 6, -1,2 ], [ 7, 0,2 ], # [ 8, 2,2 ], [ 15, -4,4 ], [ 16, -3,4 ], [ 17, -2,4 ], # [ 18, 0,4 ], [ 19, 1,4 ], [ 20, 2,4 ], # [ 21, 4,4 ], ], [ 1, [ 1, 0,0 ], [ 2, -1,1 ], [ 3, 0,1 ], [ 4, 1,1 ], [ 5, -2,2 ], # [ 6, 0,2 ], [ 7, 1,2 ], [ 8, 2,2 ], [ 15, -4,4 ], # [ 16, -2,4 ], [ 17, -1,4 ], [ 18, 0,4 ], # [ 19, 2,4 ], [ 20, 3,4 ], [ 21, 4,4 ], ], ); foreach my $elem (@data) { my ($mirror, @points) = @$elem; my $path = Math::PlanePath::CellularRule190->new (mirror => $mirror); foreach my $point (@points) { my ($n, $x, $y) = @$point; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() mirror=$mirror x at n=$n"); ok ($got_y, $y, "n_to_xy() mirror=$mirror y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() mirror=$mirror n at x=$x,y=$y"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($got_nlo == $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y single"); ok ($got_nhi == $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y single"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::CellularRule190->new; foreach my $i (0 .. 50) { { my $n_left = $path->xy_to_n(-$i,$i); my ($n_lo, $n_hi) = $path->rect_to_n_range(0,$i, -$i,$i); ok ($n_lo, $n_left); } { my $n_right = $path->xy_to_n($i,$i); my ($n_lo, $n_hi) = $path->rect_to_n_range($i,$i, 0,0); ok ($n_hi, $n_right); } } } #------------------------------------------------------------------------------ # random points foreach my $mirror (0, 1) { my $path = Math::PlanePath::CellularRule190->new (mirror => $mirror); for (1 .. 30) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } ok ($rev_n, $n, "xy_to_n($x,$y) mirror=$mirror reverse to expect n=$n, got $rev_n"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() reverse n=$n cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() reverse n=$n cf got n_hi=$n_hi"); } } exit 0; Math-PlanePath-129/t/KnightSpiral.t0000644000175000017500000000537613734026645014762 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 1010;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::KnightSpiral; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::KnightSpiral::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::KnightSpiral->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::KnightSpiral->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::KnightSpiral->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::KnightSpiral->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::KnightSpiral->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::KnightSpiral->parameter_info_list; ok (join(',',@pnames), ''); } #------------------------------------------------------------------------------ # xy_to_n() { my $path = Math::PlanePath::KnightSpiral->new; my ($x, $y) = $path->n_to_xy(1); foreach my $n (2 .. 1000) { my ($nx, $ny) = $path->n_to_xy($n); # diag "n=$n $nx,$ny"; my $dx = abs($nx - $x); my $dy = abs($ny - $y); ok (($dx == 2 && $dy == 1) || ($dx == 1 && $dy == 2), 1, "step n=$n from $x,$y to $nx,$ny D=$dx,$dy"); ($x,$y) = ($nx,$ny); } } exit 0; Math-PlanePath-129/t/DekkingCurve.t0000644000175000017500000002073613734026647014743 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 219;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::DekkingCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::DekkingCurve::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::DekkingCurve->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::DekkingCurve->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::DekkingCurve->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::DekkingCurve->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::DekkingCurve->new; ok ($path->n_start, 0, 'n_start()'); ok (! $path->x_negative, 1, 'x_negative()'); ok (! $path->y_negative, 1, 'y_negative()'); ok ($path->class_x_negative, 1, 'class_x_negative() instance method'); ok ($path->class_y_negative, 1, 'class_y_negative() instance method'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::DekkingCurve->parameter_info_list; ok (join(',',@pnames), 'arms'); } { my $path = Math::PlanePath::DekkingCurve->new (arms=>2); ok ( $path->x_negative, 1, 'arms=2 x_negative()'); ok (! $path->y_negative, 1, 'arms=2 y_negative()'); ok ($path->x_negative_at_n, 5, 'arms=2 x_negative_at_n()'); ok ($path->y_negative_at_n, undef, 'arms=2 x_negative_at_n()'); } { my $path = Math::PlanePath::DekkingCurve->new (arms=>3); ok ($path->x_negative, 1, 'arms=3 x_negative()'); ok ($path->y_negative, 1, 'arms=3 y_negative()'); ok ($path->x_negative_at_n, 2, 'arms=3 x_negative_at_n()'); ok ($path->y_negative_at_n, 8, 'arms=3 x_negative_at_n()'); } { my $path = Math::PlanePath::DekkingCurve->new (arms=>4); ok ($path->x_negative, 1, 'arms=4 x_negative()'); ok ($path->y_negative, 1, 'arms=4 y_negative()'); ok ($path->x_negative_at_n, 2, 'arms=4 x_negative_at_n()'); ok ($path->y_negative_at_n, 3, 'arms=4 x_negative_at_n()'); } #------------------------------------------------------------------------------ # level_to_n_range() { # level 0 0 to 1 # level 1 0 to 25 # level 2 0 to 625 my $path = Math::PlanePath::DekkingCurve->new; my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo,0); ok ($n_hi,625); ok ($path->n_to_level(25), 1); ok ($path->n_to_level(25+1), 2); ok ($path->n_to_level(625), 2); ok ($path->n_to_level(625+1), 3); } { my $path = Math::PlanePath::DekkingCurve->new (arms=>3); my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo,0); ok ($n_hi,3*625); ok ($path->n_to_level(3*25), 1); ok ($path->n_to_level(3*25+1), 2); ok ($path->n_to_level(3*625), 2); ok ($path->n_to_level(3*625+1), 3); } #------------------------------------------------------------------------------ # _UNDOCUMENTED__xseg_is_traversed() { my $path = Math::PlanePath::DekkingCurve->new; my $bad = 0; foreach my $x (0 .. 5**5) { my $want = defined($path->xyxy_to_n_either($x,0, $x+1,0)) ? 1 : 0; my $got = $path->_UNDOCUMENTED__xseg_is_traversed($x) ? 1 : 0; if ($got != $want) { MyTestHelpers::diag ("_UNDOCUMENTED__xseg_is_traversed($x) got $got want $want"); last if $bad++ > 10; } } ok ($bad, 0, "_UNDOCUMENTED__xseg_is_traversed"); } { my $path = Math::PlanePath::DekkingCurve->new; my $bad = 0; foreach my $y (0 .. 5**5) { my $want = defined($path->xyxy_to_n_either(0,$y, 0,$y+1)) ? 1 : 0; my $got = $path->_UNDOCUMENTED__yseg_is_traversed($y) ? 1 : 0; if ($got != $want) { MyTestHelpers::diag ("_UNDOCUMENTED__yseg_is_traversed($y) got $got want $want"); last if $bad++ > 10; } } ok ($bad, 0, "_UNDOCUMENTED__yseg_is_traversed"); } #------------------------------------------------------------------------------ # n_to_xy() first few points { my $path = Math::PlanePath::DekkingCurve->new; my @data = ( [ 0, 0,0 ], [ 1, 1,0 ], [ 2, 2,0 ], [ 3, 2,1 ], [ 0.25, 0.25,0 ], [ 1.25, 1.25,0 ], [ 2.25, 2,0.25 ], [ 24.25, 5, 0.75 ], [ 25.25, 5.25, 0 ], ); foreach my $elem (@data) { my ($n, $x, $y) = @$elem; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } if ($n == int($n)) { { my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range(0,0,$x,$y) for n=$n, got_nlo=$got_nlo"); ok ($got_nhi >= $n, 1, "rect_to_n_range(0,0,$x,$y) for n=$n, got_nhi=$got_nhi"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range($x,$y,$x,$y) for n=$n, got_nlo=$got_nlo"); ok ($got_nhi >= $n, 1, "rect_to_n_range($x,$y,$x,$y) for n=$n, got_nhi=$got_nhi"); } } } } #------------------------------------------------------------------------------ # xy_to_n() sample values { my $path = Math::PlanePath::DekkingCurve->new; my @data = ( [ 0,0, 0 ], [ 1,0, 1 ], [ 2,0, 2 ], [ 3,0, undef ], [ 4,0, undef ], [ 5,0, 25 ], [ 6,0, 26 ], [ 7,0, 27 ], [ 8,0, undef ], [ 9,0, undef ], [ 10,0, 50 ], [ 0,0, 0 ], [ 0,1, undef ], [ 0,2, undef ], [ 0,3, 9 ], [ 0,4, 10 ], [ 0,5, undef ], [ 0,6, undef ], [ 0,7, undef ], [ 0,8, 114 ], [ 0,9, 115 ], [ 0,10, undef ], ); foreach my $elem (@data) { my ($x, $y, $want_n) = @$elem; my $got_n = $path->xy_to_n ($x, $y); ok ((! defined $got_n && ! defined $want_n) || (defined $got_n && defined $want_n && $want_n == $got_n), 1, "xy_to_n($x,$y) want=".(defined $want_n ? $want_n : [undef]). " got=".(defined $got_n ? $got_n : [undef])); } } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::DekkingCurve->new; for (1 .. 20) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; # the end of the ring goes towards the start of the current ring, not # the next if ($y1 == -1 && $x1 >= 0) { $want_xf = $x1; } my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($n) frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($n) frac $frac, y"); } } } exit 0; Math-PlanePath-129/t/ArchimedeanChords.t0000644000175000017500000001230613734026650015711 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 74; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::ArchimedeanChords; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::ArchimedeanChords::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::ArchimedeanChords->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::ArchimedeanChords->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::ArchimedeanChords->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::ArchimedeanChords->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # xy_to_n() { my @data = ([ 0,0, 0 ], [ 0.001,0.001, 0 ], [ -0.001,0.001, 0 ], [ 0.001,-0.001, 0 ], [ -0.001,-0.001, 0 ], ); my $path = Math::PlanePath::ArchimedeanChords->new; $path->n_to_xy(600); # provoke some save table filling foreach my $elem (@data) { my ($x, $y, $want_n) = @$elem; my @got_n = $path->xy_to_n ($x,$y); ok (scalar(@got_n), 1, "xy_to_n x=$x y=$y -- return 1 value"); my $got_n = $got_n[0]; ok ($got_n, $want_n, "xy_to_n x=$x y=$y -- n value"); ok (!! $path->xy_is_visited($x,$y), 1, "xy_is_visited($x,$y)"); } } #------------------------------------------------------------------------------ # n_start(), x_negative(), y_negative(), etc { my $path = Math::PlanePath::ArchimedeanChords->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative() instance method'); ok ($path->y_negative, 1, 'y_negative() instance method'); ok ($path->class_x_negative, 1, 'class_x_negative() instance method'); ok ($path->class_y_negative, 1, 'class_y_negative() instance method'); my @pnames = map {$_->{'name'}} $path->parameter_info_list; ok (join(',',@pnames), ''); ok (!! $path->xy_is_visited(0,0), 1, 'xy_is_visited(0,0)'); ok ($path->gcdxy_minimum, 0, 'gcdxy_minimum() is 0 at X=0,Y=0'); } #------------------------------------------------------------------------------ # _xy_to_nearest_r() { my @data = ( [ 0, 0, 0 ], [ -0.0, -0.0, 0 ], [ -0.0, 0, 0 ], [ 0, -0.0, 0 ], # positive X axis [ .1,0, 0 ], [ .4,0, 0 ], [ .6,0, 1 ], [ .9,0, 1 ], [ 1,0, 1 ], [ 1.1,0, 1 ], [ 1.4,0, 1 ], [ 1.6,0, 2 ], [ 1.9,0, 2 ], [ 2,0, 2 ], [ 2.1,0, 2 ], [ 2.4,0, 2 ], # positive Y axis [ 0,.1, .25 ], [ 0,.2, .25 ], [ 0,.25, .25 ], [ 0,.7, .25 ], [ 0,.8, 1.25 ], [ 0,1.25, 1.25 ], [ 0,1.7, 1.25 ], [ 0,1.8, 2.25 ], [ 0,2.25, 2.25 ], [ 0,2.7, 2.25 ], # negative X axis [ -.1,0, .5 ], [ -.5,0, .5 ], [ -.9,0, .5 ], [ -1.1,0, 1.5 ], [ -1.5,0, 1.5 ], [ -1.9,0, 1.5 ], [ -2.1,0, 2.5 ], [ -2.5,0, 2.5 ], [ -2.9,0, 2.5 ], # negative Y axis [ 0,-.1, .75 ], [ 0,-.75, .75 ], [ 0,-1.2, .75 ], [ 0,-1.3, 1.75 ], [ 0,-1.75, 1.75 ], [ 0,-2.2, 1.75 ], [ 0,-2.3, 2.75 ], [ 0,-2.75, 2.75 ], [ 0,-3.2, 2.75 ], ); foreach my $elem (@data) { my ($x, $y, $want) = @$elem; my $got = Math::PlanePath::ArchimedeanChords::_xy_to_nearest_r($x,$y); ok (abs ($got - $want) < 0.001, 1, "_xy_to_nearest_r() on x=$x,y=$y got $got want $want"); } } #------------------------------------------------------------------------------exit 0; Math-PlanePath-129/t/HexSpiral.t0000644000175000017500000000720513734026645014253 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 35; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::HexSpiral; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::HexSpiral::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::HexSpiral->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::HexSpiral->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::HexSpiral->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::HexSpiral->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::HexSpiral->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::HexSpiral->parameter_info_list; ok (join(',',@pnames), 'wider,n_start'); } #------------------------------------------------------------------------------ # n_to_xy # 28 -- 27 -- 26 -- 25 3 # / \ # 29 13 -- 12 -- 11 24 2 # / / \ \ # 30 14 4 --- 3 10 23 1 # / / / \ \ \ # 31 15 5 1 --- 2 9 22 <- Y=0 # \ \ \ / / # 32 16 6 --- 7 --- 8 21 -1 # \ \ / # 33 17 -- 18 -- 19 -- 20 -2 # \ # 34 -- 35 ... -3 # # ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ # -6 -5 -4 -3 -2 -1 X=0 1 2 3 4 5 6 { my @data = ([ 1, 0,0 ], [ 2, 2,0 ], [ 3, 1,1 ], [ 4, -1,1 ], [ 5, -2,0 ], [ 6, -1,-1 ], [ 7, 1,-1 ], [ 8, 3,-1 ], ); my $path = Math::PlanePath::HexSpiral->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } } exit 0; Math-PlanePath-129/t/QuintetReplicate.t0000644000175000017500000002271013734026643015632 0ustar gggg#!/usr/bin/perl -w # Copyright 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 35; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::QuintetReplicate; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::QuintetReplicate::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::QuintetReplicate->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::QuintetReplicate->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::QuintetReplicate->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::QuintetReplicate->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::QuintetReplicate->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } #------------------------------------------------------------------------------ # _digits_rotate_lowtohigh() # _digits_unrotate_lowtohigh() sub add_to_digit { my ($digit, $add) = @_; if ($digit) { $digit = ((($digit-1) + $add) % 4) + 1; } return $digit; } sub digits_rotate_lowtohigh_by_successive { my ($aref) = @_; my @new = @$aref; foreach my $i (0 .. $#$aref) { if ($aref->[$i]) { foreach my $j (0 .. $i-1) { # below $i $new[$j] = add_to_digit($new[$j], $aref->[$i] - 1); } } } @$aref = @new; } { foreach my $elem ( [ [], [] ], [ [0], [0] ], [ [1], [1] ], [ [4], [4] ], [ [0,1], [0,1] ], [ [4,1], [4,1] ], [ [1,2], [2,2] ], [ [3,3], [1,3] ], [ [1,1,2], [2,2,2] ], [ [1,1,3], [3,3,3] ], [ [2,1,3], [4,3,3] ], ) { my ($digits, $rotated) = @$elem; { my @got = @$digits; Math::PlanePath::QuintetReplicate::_digits_rotate_lowtohigh(\@got); ok(join(',',@got),join(',',@$rotated), "_digits_rotate_lowtohigh of ".join(',',@$digits)); } { my @got = @$rotated; Math::PlanePath::QuintetReplicate::_digits_unrotate_lowtohigh(\@got); ok(join(',',@got),join(',',@$digits), "_digits_unrotate_lowtohigh of ".join(',',@$rotated)); } } } { # rotate / unrotate reversible my $bad = 0; foreach my $n (0 .. 125) { my @digits = Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,5); my $digits = join(',',reverse @digits); my @rot = @digits; Math::PlanePath::QuintetReplicate::_digits_rotate_lowtohigh(\@rot); my $rot = join(',',reverse @rot); my @rot_by_successive = @digits; digits_rotate_lowtohigh_by_successive(\@rot_by_successive); my $rot_by_successive = join(',',reverse @rot_by_successive); unless ($rot eq $rot_by_successive) { MyTestHelpers::diag("n=$n digits=$digits rot=$rot rot_by_successive=$rot_by_successive"); if (++$bad >= 5) { last; } } my @unrot_again = @rot; Math::PlanePath::QuintetReplicate::_digits_unrotate_lowtohigh(\@unrot_again); my $unrot_again = join(',',reverse @unrot_again); if ($digits ne $unrot_again) { MyTestHelpers::diag("n=$n digits=$digits rot=$rot unrot_again=$unrot_again"); if (++$bad >= 1) { last; } } } ok ($bad, 0); } #------------------------------------------------------------------------------ # Boundary Squares when Rotate sub to_base5 { my ($n,$k) = @_; my @digits = Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,5); if (defined $k) { while (@digits < $k) { unshift @digits, 0; } } return join('',@digits); } my @dir4_to_dx = (1,0,-1,0); my @dir4_to_dy = (0,1,0,-1); foreach my $numbering_type ('fixed','rotate') { my $path = Math::PlanePath::QuintetReplicate->new (numbering_type => $numbering_type); my $bad = 0; LEVEL: foreach my $level (3, # 0 .. 5 ) { my ($n_lo, $n_hi) = $path->level_to_n_range($level); my $Bpred_by_path = sub { my ($n) = @_; my ($x,$y) = $path->n_to_xy($n); foreach my $dir4 (0 .. 3) { my $n2 = $path->xy_to_n ($x+$dir4_to_dx[$dir4], $y+$dir4_to_dy[$dir4]); if ($n2 > $n_hi) { return 1; } } return 0; }; foreach my $n ($n_lo .. $n_hi) { my $by_path = $Bpred_by_path->($n) ? 1 : 0; my $by_undoc = $path->_UNDOCUMENTED__n_is_boundary_level($n,$level) ? 1 : 0; if ($by_path != $by_undoc) { my ($x,$y) = $path->n_to_xy($n); my $n5 = to_base5($n,$level); MyTestHelpers::diag("$numbering_type level=$level n=$n [$n5] $x,$y path $by_path undoc $by_undoc ($n_lo to $n_hi)"); if (++$bad >= 1) { last LEVEL; } } } } ok ($bad, 0); } exit; # if ($by_path != $by_digits || $by_path != $by_undoc) { # my ($x,$y) = $path->n_to_xy($n); # my $n5 = to_base5($n,$level); # MyTestHelpers::diag("level=$level n=$n [$n5] $x,$y path $by_path digits $by_digits undoc $by_undoc ($n_lo to $n_hi)"); # if (++$bad >= 10) { last K; } # } # my $by_digits = $Bpred_by_digits->($n) ? 1 : 0; # my $Bpred_by_digits = sub { # my ($n) = @_; # my @digits = reverse # high to low # Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,5); # while (@digits < $level) { unshift @digits, 0; } # foreach my $digit (@digits) { # if ($digit == 0) { return 0; } # } # shift @digits; # skip high # @digits = grep {$_!=1} @digits; # ignore 1s # foreach my $i (0 .. $#digits-1) { # if (($digits[$i] == 3 && $digits[$i+1] == 2) # || ($digits[$i] == 3 && $digits[$i+1] == 3) # || ($digits[$i] == 4 && $digits[$i+1] == 4)) { # return 0; # } # } # return 1; # }; #------------------------------------------------------------------------------ # digit rotation per the POD foreach my $numbering_type ('fixed','rotate') { my $path = Math::PlanePath::QuintetReplicate->new (numbering_type => $numbering_type); my $bad = 0; my $k = 3; my $pow = 5**$k; foreach my $turn (0 .. 3) { foreach my $n (0 .. $pow-1) { my ($x,$y) = $path->n_to_xy($n); my ($tx,$ty) = ($x,$y); foreach (1 .. $turn) { ($tx,$ty) = xy_rotate_plus90($tx,$ty); } my $txy = "$tx,$ty"; my $an = digits_add($n,$turn, $numbering_type); my ($ax,$ay) = $path->n_to_xy($an); my $axy = "$ax,$ay"; if ($txy ne $axy) { my $n5 = join('',reverse Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,5)); my $an5 = join('',reverse Math::PlanePath::Base::Digits::digit_split_lowtohigh($an,5)); print "$numbering_type turn=$turn oops, n=$n [$n5] $x,$y turned $txy cf an=$an [$an5] $axy\n"; $bad++; } } } ok ($bad, 0); } sub xy_rotate_plus90 { my ($x,$y) = @_; return (-$y,$x); # rotate +90 } sub digits_add { my ($n,$offset, $numbering_type) = @_; my @digits = Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,5); foreach my $digit (reverse @digits) { if ($digit) { $digit = (($digit-1 + $offset)%4) + 1; # mutate @digits } if ($numbering_type eq 'rotate') { last; } } return Math::PlanePath::Base::Digits::digit_join_lowtohigh (\@digits, 5); } #------------------------------------------------------------------------------ # numbering_type => 'rotate' sub-parts rotation { my $path = Math::PlanePath::QuintetReplicate->new (numbering_type=>'rotate'); my $bad = 0; my $k = 3; my $pow = 5**$k; foreach my $n ($pow .. 2*$pow-1) { my ($x,$y) = $path->n_to_xy($n); { my ($rx,$ry) = $path->n_to_xy($n+$pow); ($rx,$ry) = xy_rotate_minus90($rx,$ry); my $got = "$rx,$ry"; my $want = "$x,$y"; if ($got ne $want) { print "oops, got $got want $want\n"; $bad++; } } { my ($rx,$ry) = $path->n_to_xy($n+2*$pow); ($rx,$ry) = (-$rx,-$ry); # rotate 180 $bad += ("$rx,$ry" ne "$x,$y"); } } ok ($bad, 0); } sub xy_rotate_minus90 { my ($x,$y) = @_; return ($y,-$x); # rotate -90 } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/ImaginaryBase.t0000644000175000017500000000747613734026645015101 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 311; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::ImaginaryBase; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::ImaginaryBase::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::ImaginaryBase->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::ImaginaryBase->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::ImaginaryBase->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::ImaginaryBase->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::ImaginaryBase->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } #------------------------------------------------------------------------------ # random points { my $radix = 2 + int(rand(20)); my $path = Math::PlanePath::ImaginaryBase->new (radix => $radix); for (1 .. 100) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } ok ($rev_n, $n, "xy_to_n($x,$y) radix=$radix reverse to expect n=$n, got $rev_n"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() radix=$radix reverse n=$n cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() radix=$radix reverse n=$n cf got n_hi=$n_hi"); } } #------------------------------------------------------------------------------ # cf A039724 negabinary # A039723 negadecimal sub index_to_negaradix { my ($n, $radix) = @_; my $power = 1; my $ret = 0; while ($n) { my $digit = $n % $radix; # low to high $n = int($n/$radix); $ret += $power * $digit; $power *= -$radix; } return $ret; } { require Math::PlanePath::ZOrderCurve; my $bad = 0; foreach my $radix (2, 3, 5, 10, 16) { my $zorder = Math::PlanePath::ZOrderCurve->new (radix => $radix); my $imbase = Math::PlanePath::ImaginaryBase->new (radix => $radix); foreach my $n (0 .. 256) { my ($zx,$zy) = $zorder->n_to_xy($n); my $nx = index_to_negaradix($zx,$radix); my $ny = index_to_negaradix($zy,$radix); my $in = $imbase->xy_to_n($nx,$ny); if ($n != $in) { $bad = 1; } } } ok ($bad, 0); } exit 0; Math-PlanePath-129/t/DragonCurve.t0000644000175000017500000002270013734026646014571 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 628; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::DragonCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::DragonCurve::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::DragonCurve->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::DragonCurve->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::DragonCurve->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::DragonCurve->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #---------------------------------------------------------------------------- # n_to_dxdy() { my $path = Math::PlanePath::DragonCurve->new; { my @dxdy = $path->n_to_dxdy(-1); ok (scalar(@dxdy), 0, 'no dxdy at n=-1'); } { my @dxdy = $path->n_to_dxdy(0); ok (scalar(@dxdy), 2); ok ($dxdy[0], 1); ok ($dxdy[1], 0); } } #---------------------------------------------------------------------------- # _UNDOCUMENTED__n_to_turn_LSR() { my $path = Math::PlanePath::DragonCurve->new; ok ($path->_UNDOCUMENTED__n_to_turn_LSR(-1), undef); ok ($path->_UNDOCUMENTED__n_to_turn_LSR(0), undef); ok ($path->_UNDOCUMENTED__n_to_turn_LSR(1), 1); ok ($path->_UNDOCUMENTED__n_to_turn_LSR(2), 1); ok ($path->_UNDOCUMENTED__n_to_turn_LSR(3), -1); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::DragonCurve->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 2); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 4); } { my ($n_lo,$n_hi) = $path->level_to_n_range(3); ok ($n_lo, 0); ok ($n_hi, 8); } } { my $path = Math::PlanePath::DragonCurve->new (arms => 3); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 3*(2**0 + 1) - 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 3*(2**1 + 1) - 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 3*(2**2 + 1) - 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(3); ok ($n_lo, 0); ok ($n_hi, 3*(2**3 + 1) - 1); } } #------------------------------------------------------------------------------ # turn sequence claimed in the pod { # with Y reckoned increasing upwards sub dxdy_to_dir4 { my ($dx, $dy) = @_; if ($dx > 0) { return 0; } # east if ($dx < 0) { return 2; } # west if ($dy > 0) { return 1; } # north if ($dy < 0) { return 3; } # south } sub path_n_dir { my ($path, $n) = @_; my ($dx,$dy) = $path->n_to_dxdy($n) or die "Oops, no point at ",$n; return dxdy_to_dir4 ($dx, $dy); } # return 0 for left, 1 for right sub path_n_turn { my ($path, $n) = @_; my $prev_dir = path_n_dir ($path, $n-1); my $dir = path_n_dir ($path, $n); my $turn = ($dir - $prev_dir) % 4; if ($turn == 1) { return 0; } if ($turn == 3) { return 1; } die "Oops, unrecognised turn dir=$dir"; } # return 0 for left, 1 for right sub calc_n_turn { my ($n) = @_; my ($mask,$z); $mask = $n & -$n; # lowest 1 bit, 000100..00 $z = $n & ($mask << 1); # the bit above it my $turn = ($z == 0 ? 0 : 1); # printf "%b %b %b %d\n", $n,$mask, $z, $turn; return $turn; die if $n == 0; while (($n % 2) == 0) { $n = int($n/2); # skip low 0s } $n = int($n/2); # skip lowest 1 return ($n % 2); # next bit is the turn } # # return 0 for left, 1 for right sub calc_n_next_turn { my ($n) = @_; my ($mask,$w); $mask = $n ^ ($n+1); # low one and below 000111..11 $w = $n & ($mask + 1); # the bit above there my $turn = ($w == 0 ? 0 : 1); return $turn; } my $path = Math::PlanePath::DragonCurve->new; my $bad = 0; foreach my $n ($path->n_start + 1 .. 500) { { my $path_turn = path_n_turn ($path, $n); my $calc_turn = calc_n_turn ($n); if ($path_turn != $calc_turn) { MyTestHelpers::diag ("turn n=$n path $path_turn calc $calc_turn"); last if $bad++ > 10; } } { my $path_turn = path_n_turn ($path, $n+1); my $calc_turn = calc_n_next_turn ($n); if ($path_turn != $calc_turn) { MyTestHelpers::diag ("next turn n=$n path $path_turn calc $calc_turn"); last if $bad++ > 10; } } } ok ($bad, 0, "turn sequence"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::DragonCurve->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); ok ($path->class_x_negative, 1, 'class_x_negative()'); ok ($path->class_y_negative, 1, 'class_y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::DragonCurve->parameter_info_list; ok (join(',',@pnames), 'arms'); } #------------------------------------------------------------------------------ # xyxy_to_n_list() foreach my $arms (1 .. 4) { my $path = Math::PlanePath::DragonCurve->new (arms => $arms); my @got_n_list = $path->xy_to_n_list(0,0); my $got_n_list = join(',',@got_n_list); my $want_n_list = join(',', 0 .. $arms-1); ok ($got_n_list, $want_n_list); } #------------------------------------------------------------------------------ # first few points { my @data = ( [ 0, 0,0 ], [ 1, 1,0 ], [ 2, 1,1 ], [ 3, 0,1 ], [ 4, 0,2 ], [ 0.25, 0.25, 0 ], [ 1.25, 1, 0.25 ], [ 2.25, 0.75, 1 ], [ 3.25, 0, 1.25 ], ); my $path = Math::PlanePath::DragonCurve->new; foreach my $elem (@data) { my ($n, $x, $y) = @$elem; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } #------------------------------------------------------------------------------ # random rect_to_n_range() foreach my $arms (1 .. 4) { my $path = Math::PlanePath::DragonCurve->new (arms => $arms); ok ($path->arms_count, $arms, 'arms_count()'); for (1 .. 5) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)); # 0 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); ok (defined $rev_n, 1, "xy_to_n($x,$y) arms=$arms reverse n, got undef"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() arms=$arms n=$n at xy=$x,$y cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() arms=$arms n=$n at xy=$x,$y cf got n_hi=$n_hi"); } } #------------------------------------------------------------------------------ # random n_to_xy() fracs foreach my $arms (1 .. 4) { my $path = Math::PlanePath::DragonCurve->new (arms => $arms); for (1 .. 20) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+$arms); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($nf) arms=$arms frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($nf) arms=$arms frac $frac, y"); } } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/GcdRationals.t0000644000175000017500000002154113734026646014726 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 625; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::GcdRationals; my @pairs_order_choices = @{Math::PlanePath::GcdRationals->parameter_info_hash ->{'pairs_order'}->{'choices'}}; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::GcdRationals::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::GcdRationals->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::GcdRationals->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::GcdRationals->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::GcdRationals->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # rect_to_n_range() various { my @data = ([ 0,0, 19,5, 1,217 ], [ 7,7, 8,8, 35,93 ], # 35,93 [ 19,2, 19,4, 200,217 ], # 200,217,205 [ 5,3, 5,7, 19,30 ], # 19,30,20,26 ); my $path = Math::PlanePath::GcdRationals->new; foreach my $elem (@data) { my ($x1,$y1, $x2,$y2, $want_nlo, $want_nhi) = @$elem; my ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); ok ($got_nlo <= $want_nlo, 1, "got_nlo=$got_nlo want_nlo=$want_nlo"); ok ($got_nhi >= $want_nhi, 1, "got_nhi=$got_nhi want_nhi=$want_nhi"); } } # exact ones # { # my @data = ([ 3,7, 3,8, 24,31 ], # [ 7,8, 7,13, 35,85 ], # [ 1,1, 1,1, 1,1 ], # [ 1,1, 1,2, 1,2 ], # [ 1,1, 2,1, 1,3 ], # [ 1,1, 8,1, 1,36 ], # [ 6,1, 8,1, 21,36 ], # ); # my $path = Math::PlanePath::GcdRationals->new; # foreach my $elem (@data) { # my ($x1,$y1, $x2,$y2, $want_nlo, $want_nhi) = @$elem; # my ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); # ok ($got_nlo == $want_nlo, # 1, # "got_nlo=$got_nlo want_nlo=$want_nlo"); # ok ($got_nhi == $want_nhi, # 1, # "got_nhi=$got_nhi want_nhi=$want_nhi"); # } # } #------------------------------------------------------------------------------ # pairs_order n_to_xy() { foreach my $group ([ 'rows', [ 1, 1,1 ], [ 2, 1,2 ], [ 3, 2,2 ], [ 4, 1,3 ], [ 5, 2,3 ], [ 6, 3,3 ], [ 7, 1,4 ], [ 8, 2,4 ], [ 9, 3,4 ], [10, 4,4 ], [11, 1,5 ], [12, 2,5 ], [13, 3,5 ], [14, 4,5 ], [15, 5,5 ], ], [ 'rows_reverse', [ 1, 1,1 ], [ 2, 2,2 ], [ 3, 1,2 ], [ 4, 3,3 ], [ 5, 2,3 ], [ 6, 1,3 ], [ 7, 4,4 ], [ 8, 3,4 ], [ 9, 2,4 ], [10, 1,4 ], [11, 5,5 ], [12, 4,5 ], [13, 3,5 ], [14, 2,5 ], [15, 1,5 ], ], [ 'diagonals_down', [ 1, 1,1 ], [ 2, 1,2 ], [ 3, 1,3 ], [ 4, 2,2 ], [ 5, 1,4 ], [ 6, 2,3 ], [ 7, 1,5 ], [ 8, 2,4 ], [ 9, 3,3 ], [10, 1,6 ], [11, 2,5 ], [12, 3,4 ], [13, 1,7 ], [14, 2,6 ], [15, 3,5 ], [16, 4,4 ], [17, 1,8 ], [18, 2,7 ], [19, 3,6 ], [20, 4,5 ], [21, 1,9 ], [22, 2,8 ], [23, 3,7 ], [24, 4,6 ], [25, 5,5 ], [26, 1,10 ], [27, 2,9 ], [28, 3,8 ], [29, 4,7 ], [30, 5,6 ], ], [ 'diagonals_up', [ 1, 1,1 ], [ 2, 1,2 ], [ 3, 2,2 ], [ 4, 1,3 ], [ 5, 2,3 ], [ 6, 1,4 ], [ 7, 3,3 ], [ 8, 2,4 ], [ 9, 1,5 ], [10, 3,4 ], [11, 2,5 ], [12, 1,6 ], [13, 4,4 ], [14, 3,5 ], [15, 2,6 ], [16, 1,7 ], [17, 4,5 ], [18, 3,6 ], [19, 2,7 ], [20, 1,8 ], [21, 5,5 ], [22, 4,6 ], [23, 3,7 ], [24, 2,8 ], [25, 1,9 ], [26, 5,6 ], [27, 4,7 ], [28, 3,8 ], [29, 2,9 ], [30, 1,10], ], ) { my ($pairs_order, @points) = @$group; { my $func = Math::PlanePath::GcdRationals ->can("_pairs_order__${pairs_order}__n_to_xy"); ok (defined $func, 1, $pairs_order); foreach my $point (@points) { my ($n, $want_x,$want_y) = @$point; my ($got_x,$got_y) = &$func($n); ok ($got_x, $want_x, "$pairs_order n=$n"); ok ($got_y, $want_y, "$pairs_order n=$n"); } } { my $func = Math::PlanePath::GcdRationals ->can("_pairs_order__${pairs_order}__xygr_to_n"); ok (defined $func, 1, $pairs_order); foreach my $point (@points) { my ($want_n, $x,$y) = @$point; my $q = 0; # imagining x=q*y+r has q=0,r=x my $r = $x; my $g = $q+1; my $got_n = &$func($x,$y, $g,$r); ok ($got_n, $want_n, "$pairs_order xygr=$x,$y,$g,$r"); } } } } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::GcdRationals->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::GcdRationals->parameter_info_list; ok (join(',',@pnames), 'pairs_order'); } #------------------------------------------------------------------------------ # xy_to_n() reversing n_to_xy() foreach my $pairs_order (@pairs_order_choices) { my $path = Math::PlanePath::GcdRationals->new (pairs_order => $pairs_order); foreach my $n ($path->n_start .. 50) { my ($x,$y) = $path->n_to_xy($n); my $rev_n = $path->xy_to_n ($x,$y); ok($rev_n,$n, "$pairs_order"); } } #------------------------------------------------------------------------------ # Y=1 horizontal triangular numbers { my $path = Math::PlanePath::GcdRationals->new; foreach my $k (1 .. 15) { my $n = $path->xy_to_n ($k, 1); ok ($n, $k*($k+1)/2); my ($x,$y) = $path->n_to_xy($n); ok ($x, $k); ok ($y, 1); } } #------------------------------------------------------------------------------ # rect_to_n_range() random foreach my $pairs_order ('rows') { my $path = Math::PlanePath::GcdRationals->new (pairs_order => $pairs_order); foreach (1 .. 40) { my $x1 = int(rand() * 40) + 1; my $x2 = $x1 + int(rand() * 4); my $y1 = int(rand() * 40) + 1; my $y2 = $y1 + int(rand() * 10); my $nlo = 0; my $nhi = 0; my $nlo_y = 'none'; my $nhi_y = 'none'; foreach my $x ($x1 .. $x2) { foreach my $y ($y1 .. $y2) { my $n = $path->xy_to_n($x,$y); next if ! defined $n; if (! defined $nlo || $n < $nlo) { $nlo = $n; $nlo_y = $y; } if (! defined $nhi || $n > $nhi) { $nhi = $n; $nhi_y = $y; } } } my ($got_nlo,$got_nhi) = $path->rect_to_n_range($x1,$y1, $x2,$y2); ok (! $nlo || $got_nlo <= $nlo, 1, "x=$x1..$x2 y=$y1..$y2 nlo=$nlo (at y=$nlo_y) but got_nlo=$got_nlo"); ok (! $nhi || $got_nhi >= $nhi, 1, "x=$x1..$x2 y=$y1..$y2 nhi=$nhi (at y=$nhi_y) but got_nhi=$got_nhi"); } } #------------------------------------------------------------------------------ # _gcd() on Math::BigInt { require Math::BigInt; my $x = Math::BigInt->new(35); my $y = Math::BigInt->new(15); my $g = Math::PlanePath::GcdRationals::_gcd($x,$y); # not a string compare since "+35" in old Math::BigInt ok ($x == 35, 1, 'x input unchanged'); ok ($y == 15, 1, 'y input unchanged'); ok ($g == 5, 1, 'gcd(35,15) result'); } exit 0; Math-PlanePath-129/t/TriangularHypot.t0000644000175000017500000001643313734026641015507 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 22; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::TriangularHypot; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::TriangularHypot::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::TriangularHypot->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::TriangularHypot->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::TriangularHypot->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::TriangularHypot->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::TriangularHypot->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); my @pnames = map {$_->{'name'}} $path->parameter_info_list; ok (join(',',@pnames), 'points,n_start'); } #------------------------------------------------------------------------------ # all x,y covered and distinct n foreach my $points ('hex_centred','hex','odd','even','all') { my $path = Math::PlanePath::TriangularHypot->new (points => $points); my $bad = 0; my %seen; my $xlo = -10; my $xhi = 10; my $ylo = -10; my $yhi = 10; my ($nlo, $nhi) = $path->rect_to_n_range($xlo,$ylo, $xhi,$yhi); my $count = 0; OUTER: for (my $x = $xlo; $x <= $xhi; $x++) { for (my $y = $ylo; $y <= $yhi; $y++) { my $n = $path->xy_to_n ($x,$y); if ($points eq 'even') { if (($x ^ $y) & 1) { if (defined $n) { MyTestHelpers::diag ("$points: x=$x,y=$y is odd should be n=undef"); last if $bad++ > 10; } next; } } elsif ($points eq 'odd') { if (! (($x ^ $y) & 1)) { if (defined $n) { MyTestHelpers::diag ("$points: x=$x,y=$y is even should be n=undef"); last if $bad++ > 10; } next; } } elsif ($points eq 'hex') { if (! xy_is_hex($x,$y)) { if (defined $n) { MyTestHelpers::diag ("$points: x=$x,y=$y is not hex should be n=undef"); last if $bad++ > 10; } next; } } elsif ($points eq 'hex_rotated') { if (! xy_is_hex_rotated($x,$y)) { if (defined $n) { MyTestHelpers::diag ("$points: x=$x,y=$y is not hex_rotated should be n=undef"); last if $bad++ > 10; } next; } } elsif ($points eq 'hex_centred') { if (! xy_is_hex_centred($x,$y)) { if (defined $n) { my $m = ($x+3*$y) % 6; MyTestHelpers::diag ("$points: x=$x,y=$y is not hex_centred should be n=undef (m=$m)"); last if $bad++ > 10; } next; } } if (! defined $n) { MyTestHelpers::diag ("x=$x,y=$y n=undef"); last OUTER if $bad++ > 10; next; } if ($seen{$n}) { MyTestHelpers::diag ("x=$x,y=$y n=$n seen before at $seen{$n}"); last if $bad++ > 10; } if ($n < $nlo) { MyTestHelpers::diag ("x=$x,y=$y n=$n below nlo=$nlo"); last OUTER if $bad++ > 10; } if ($n > $nhi) { MyTestHelpers::diag ("x=$x,y=$y n=$n above nhi=$nhi"); last OUTER if $bad++ > 10; } $seen{$n} = "$x,$y"; $count++; } } ok ($bad, 0, "$points xy_to_n() coverage and distinct, $count points"); } # "hex" is X+3*Y==0or2 # test against 0 to allow for "%" sign varying under "use integer" sub xy_is_hex { my ($x,$y) = @_; return (($x+3*$y) % 6 == 0 || ($x+3*$y-2) % 6 == 0); } # "hex_rotated" is X+3*Y==0or4 # test against 0 to allow for "%" sign varying under "use integer" sub xy_is_hex_rotated { my ($x,$y) = @_; return (($x+3*$y) % 6 == 0 || ($x+3*$y-4) % 6 == 0); } # "hex_centred" is X+3*Y==2or4 # test against 0 to allow for "%" sign varying under "use integer" sub xy_is_hex_centred { my ($x,$y) = @_; return (($x+3*$y-2) % 6 == 0 || ($x+3*$y-4) % 6 == 0); } #------------------------------------------------------------------------------ # monotonic hypotenuse # (sqrt(3)/2 * y)^2 + (x/2)^2 # = 3/4 * y^2 + 1/4 * x^2 # = 1/4 * (3*y^2 + x^2) sub hex_hypot { my ($x, $y) = @_; return 3*$y*$y + $x*$x; } foreach my $points ('hex_rotated','hex_centred','hex','odd','even','all') { my $path = Math::PlanePath::TriangularHypot->new (points => $points); my $bad = 0; my $n = $path->n_start; my ($x,$y) = $path->n_to_xy($n); my $h = hex_hypot($x,$y); while (++$n < 5000) { my ($x2,$y2) = $path->n_to_xy ($n); if ($points eq 'even') { if (($x2 ^ $y2) & 1) { if (defined $n) { MyTestHelpers::diag ("$points: x2=$x2,y2=$y2 is odd"); last if $bad++ > 10; } next; } } elsif ($points eq 'odd') { if (! (($x2 ^ $y2) & 1)) { if (defined $n) { MyTestHelpers::diag ("$points: x2=$x2,y2=$y2 is even"); last if $bad++ > 10; } next; } } my $h2 = hex_hypot($x2,$y2); ### xy: "$x2,$y2 is $h2" if ($h2 < $h) { MyTestHelpers::diag ("n=$n x=$x2,y=$y2 h=$h2 < prev h=$h x=$x,y=$y"); last if $bad++ > 10; } if ($points eq 'even') { # odd,all don't always turn left if ($n > 2 && ! _turn_func_Left($x,$y, $x2,$y2)) { MyTestHelpers::diag ("$points: not turn left at n=$n x=$x2,y=$y2 prev x=$x,y=$y"); last if $bad++ > 10; } } $h = $h2; $x = $x2; $y = $y2; } ok ($bad, 0, "$points: n_to_xy() hypot non-decreasing"); } sub _turn_func_Left { my ($dx,$dy, $next_dx,$next_dy) = @_; ### _turn_func_Left() ... my $a = $next_dy * $dx; my $b = $next_dx * $dy; return ($a > $b || abs($dx)==abs($next_dx) && abs($dy)==abs($next_dy) # 0 or 180 ? 1 : 0); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/HilbertCurve.t0000644000175000017500000001423413734026645014752 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 296; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments '###'; require Math::PlanePath::HilbertCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::HilbertCurve::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::HilbertCurve->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::HilbertCurve->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::HilbertCurve->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::HilbertCurve->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::HilbertCurve->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 0, 'x_negative() instance method'); ok ($path->y_negative, 0, 'y_negative() instance method'); } #------------------------------------------------------------------------------ # first few points { my @data = ([ 0, 0,0 ], [ 1, 1,0 ], [ 2, 1,1 ], [ 3, 0,1 ], [ .25, .25, 0 ], [ 1.25, 1, .25 ], [ 2.25, 0.75, 1 ], [ 3.25, 0, 1.25 ], [ 4.25, 0, 2.25 ], [ 5.25, .25, 3 ], [ 6.25, 1, 2.75 ], [ 7.25, 1.25, 2 ], [ 8.25, 2, 2.25 ], [ 9.25, 2.25, 3 ], [ 10.25, 3, 2.75 ], [ 11.25, 3, 1.75 ], [ 12.25, 2.75, 1 ], [ 13.25, 2, .75 ], [ 14.25, 2.25, 0 ], [ 15.25, 3.25, 0 ], [ 19.25, 5.25, 0 ], [ 37.25, 7, 4.25 ], [ 31.25, 4, 3.25 ], [ 127.25, 7.25, 8 ], [ 63.25, 0, 7.25 ], [ 255.25, 15.25, 0 ], [ 1023.25, 0, 31.25 ], ); my $path = Math::PlanePath::HilbertCurve->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $want_x, "n_to_xy() x at n=$n"); ok ($got_y, $want_y, "n_to_xy() y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next unless $want_n==int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } foreach my $elem (@data) { my ($n, $x, $y) = @$elem; my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); next unless $n==int($n); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } #------------------------------------------------------------------------------ # rect_to_n_range() random { my $path = Math::PlanePath::HilbertCurve->new; for (1 .. 50) { my $bits = int(rand(14)); # 0 to 14 inclusive (to fit 32-bit N) my $x = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my $y = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my $xcount = int(rand(3)); # 0,1,2 my $ycount = int(rand(3)); # 0,1,2 # $xcount = $ycount = 2; my $n_min = my $n_max = $path->xy_to_n($x,$y); my $n_min_pos = my $n_max_pos = "$x,$y"; foreach my $xc (0 .. $xcount) { foreach my $yc (0 .. $ycount) { my $xp = $x+$xc; my $yp = $y+$yc; ### $xp ### $yp my $n = $path->xy_to_n($xp,$yp); if ($n < $n_min) { $n_min = $n; $n_min_pos = "$xp,$yp"; } if ($n > $n_max) { $n_max = $n; $n_max_pos = "$xp,$yp"; } } } ### $n_min_pos ### $n_max_pos my ($got_n_min,$got_n_max) = $path->rect_to_n_range ($x+$xcount,$y+$ycount, $x,$y); ok ($got_n_min == $n_min, 1, "rect_to_n_range() on $x,$y rect $xcount,$ycount n_min_pos=$n_min_pos"); ok ($got_n_max == $n_max, 1, "rect_to_n_range() on $x,$y rect $xcount,$ycount n_max_pos=$n_max_pos"); } } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::HilbertCurve->new; for (1 .. 20) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($n) frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($n) frac $frac, y"); } } } exit 0; Math-PlanePath-129/t/DiagonalRationals.t0000644000175000017500000001266713734026647015761 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 148; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments; require Math::PlanePath::DiagonalRationals; my $path = Math::PlanePath::DiagonalRationals->new; my $n_start = $path->n_start; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::DiagonalRationals::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::DiagonalRationals->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::DiagonalRationals->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::DiagonalRationals->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { ok ($n_start, 1, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); } { my $path = Math::PlanePath::DiagonalRationals->new (n_start => 37); ok ($path->n_start, 37, 'n_start() 37'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::DiagonalRationals->parameter_info_list; ok (join(',',@pnames), 'direction,n_start'); } #------------------------------------------------------------------------------ # first few points { my @data = ([ undef, 0,0 ], [ undef, 1,0 ], [ undef, 2,0 ], [ undef, 3,0 ], [ undef, 0,1 ], [ undef, 0,2 ], [ undef, 0,3 ], [ 1, 1,1 ], [ 2, 1,2 ], [ 3, 2,1 ], [ 4, 1,3 ], [ 5, 3,1 ], [ 6, 1,4 ], [ 7, 2,3 ], [ 8, 3,2 ], [ 9, 4,1 ], [ 10, 1,5 ], [ 11, 5,1 ], [ 12, 1,6 ], [ 13, 2,5 ], [ 14, 3,4 ], [ 15, 4,3 ], [ 16, 5,2 ], [ 17, 6,1 ], [ 18, 1,7 ], [ 19, 3,5 ], [ 20, 5,3 ], [ 21, 7,1 ], ); foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; next if ! defined $n; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "n_to_xy() x at n=$n"); ok ($got_y, $want_y, "n_to_xy() y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next if defined $want_n && $want_n!=int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "xy_to_n() at x=$x,y=$y"); } foreach my $elem (@data) { my ($n, $x, $y) = @$elem; next unless defined $n && $n==int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo >= $n_start, 1, "rect_to_n_range() nlo=$got_nlo < n_start at n=$n,x=$x,y=$y"); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } #------------------------------------------------------------------------------ # xy_to_n() distinct n { my $bad = 0; my %seen; my $xlo = -5; my $xhi = 100; my $ylo = -5; my $yhi = 100; my ($nlo, $nhi) = $path->rect_to_n_range($xlo,$ylo, $xhi,$yhi); my $count = 0; OUTER: for (my $x = $xlo; $x <= $xhi; $x++) { for (my $y = $ylo; $y <= $yhi; $y++) { next if ($x ^ $y) & 1; my $n = $path->xy_to_n ($x,$y); next if ! defined $n; # sparse if ($seen{$n}) { MyTestHelpers::diag ("x=$x,y=$y n=$n seen before at $seen{$n}"); last if $bad++ > 10; } if ($n < $nlo) { MyTestHelpers::diag ("x=$x,y=$y n=$n below nlo=$nlo"); last OUTER if $bad++ > 10; } if ($n > $nhi) { MyTestHelpers::diag ("x=$x,y=$y n=$n above nhi=$nhi"); last OUTER if $bad++ > 10; } $seen{$n} = "$x,$y"; $count++; } } ok ($bad, 0, "xy_to_n() coverage and distinct, $count points"); } #------------------------------------------------------------------------------ # rect_to_n_range() { my ($nlo, $nhi) = $path->rect_to_n_range(1,1, -2,1); ### $nlo ### $nhi ok ($nlo <= 1, 1, "nlo $nlo"); ok ($nhi >= 1, 1, "nhi $nhi"); } exit 0; Math-PlanePath-129/t/GosperSide.t0000644000175000017500000001556713734026645014432 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 1576; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::GosperSide; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::GosperSide::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::GosperSide->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::GosperSide->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::GosperSide->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::GosperSide->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::GosperSide->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } #------------------------------------------------------------------------------ # first few points { my @data = ( [ .25, .5, 0 ], [ .5, 1, 0 ], [ 1.75, 2.75, .75 ], [ 0, 0,0 ], [ 1, 2,0 ], [ 2, 3,1 ], [ 3, 5,1 ], [ 4, 6,2 ], [ 5, 5,3 ], [ 6, 6,4 ], [ 7, 8,4 ], [ 8, 9,5 ], [ 9, 11,5 ], ); my $path = Math::PlanePath::GosperSide->new; foreach my $elem (@data) { my ($n, $x, $y) = @$elem; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::GosperSide->new; my ($n_lo, $n_hi) = $path->rect_to_n_range(0,0, 0,0); ok ($n_lo == 0, 1, "rect_to_n_range() 0,0 n_lo=$n_lo"); ok ($n_hi >= 0, 1, "rect_to_n_range() 0,0 n_hi=$n_hi"); } #------------------------------------------------------------------------------ # random points { my $path = Math::PlanePath::GosperSide->new; for (1 .. 500) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } ok ($rev_n, $n, "xy_to_n($x,$y) reverse to expect n=$n, got $rev_n"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() reverse n=$n cf n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() reverse n=$n cf n_hi=$n_hi"); } } #------------------------------------------------------------------------------ # turn sequence described in the POD { # 1 for +60 deg, -1 for -60 deg sub n_to_turn_calculated { my ($n) = @_; for (;;) { if ($n == 0) { die "oops n=0"; } my $mod = $n % 3; if ($mod == 1) { return 1; } if ($mod == 2) { return -1; } $n = int($n/3); } } sub dxdy_to_dir6 { my ($dx,$dy) = @_; if ($dy == 0) { if ($dx == 2) { return 0; } if ($dx == -2) { return 3; } } if ($dy == 1) { if ($dx == 1) { return 1; } if ($dx == -1) { return 2; } } if ($dy == -1) { if ($dx == 1) { return 5; } if ($dx == -1) { return 4; } } die "unrecognised $dx,$dy"; } my $path = Math::PlanePath::GosperSide->new; my $n = $path->n_start; my $bad = 0; my ($prev_x, $prev_y) = $path->n_to_xy($n++); my ($x, $y) = $path->n_to_xy($n++); my $dx = $x - $prev_x; my $dy = $y - $prev_y; my $prev_dir = dxdy_to_dir6($dx,$dy); while ($n < 1000) { $prev_x = $x; $prev_y = $y; ($x,$y) = $path->n_to_xy($n); $dx = $x - $prev_x; $dy = $y - $prev_y; my $dir = dxdy_to_dir6($dx,$dy); my $got_turn = ($dir - $prev_dir + 6) % 6; my $want_turn = n_to_turn_calculated($n-1) % 6; if ($want_turn < 0) { $want_turn += 6; } if ($got_turn != $want_turn) { MyTestHelpers::diag ("n=$n turn got=$got_turn want=$want_turn"); MyTestHelpers::diag (" dir=$dir prev_dir=$prev_dir"); last if $bad++ > 10; } $n++; $prev_dir = $dir; } ok ($bad, 0, "turn sequence"); } #------------------------------------------------------------------------------ # total turn described in the POD { # as a count of +60 deg sub n_to_dir_calculated { my ($n) = @_; my $dir = 0; while ($n) { if (($n % 3) == 1) { $dir++; } $n = int($n/3); } return $dir; } my $path = Math::PlanePath::GosperSide->new; my $n = $path->n_start; my $bad = 0; my ($x,$y) = $path->n_to_xy($n); while ($n < 1000) { my ($next_x,$next_y) = $path->n_to_xy($n+1); my $dx = $next_x - $x; my $dy = $next_y - $y; my $path_dir = dxdy_to_dir6($dx,$dy); my $calc_dir = n_to_dir_calculated($n) % 6; if ($path_dir != $calc_dir) { MyTestHelpers::diag ("n=$n dir path=$path_dir calc=$calc_dir"); MyTestHelpers::diag (" xy from $x,$y to $next_x,$next_y"); last if $bad++ > 10; } $x = $next_x; $y = $next_y; $n++; } ok ($bad, 0, "total turn"); } exit 0; Math-PlanePath-129/t/DiamondSpiral.t0000644000175000017500000001023313734026646015076 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 75; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::DiamondSpiral; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::DiamondSpiral::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::DiamondSpiral->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::DiamondSpiral->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::DiamondSpiral->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::DiamondSpiral->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::DiamondSpiral->new; foreach my $elem ( [0,-2, -2,-2, 13,39], # Y=-2, X=-2 to 0 being 39,24,13 [-2,1, 2,1, 3,21], # Y=1, X=-2..2 being 21,10,3,8,17 [1,-2, 1,2, 2,18], # X=1, Y=-2..2 being 14,6,2,8,18 [0,-2, 0,2, 1,13], # X=0, Y=-2..2 being 13,5,1,3,9 [-1,-2, -1,2, 4,24], # X=-1, Y=-2..2 being 24,12,4,10,20 # # [-2,-1, 2,-1, 3,24], # Y=-1, X=-2..2 being 9,3,4,12,24 # [-2,-1, 1,-1, 3,12], # Y=-1, X=-2..1 being 9,3,4,12 ) { my ($x1,$y1,$x2,$y2, $want_lo, $want_hi) = @$elem; my ($got_lo, $got_hi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); ok ($got_lo, $want_lo, "lo on $x1,$y1 $x2,$y2"); ok ($got_hi, $want_hi, "hi on $x1,$y1 $x2,$y2"); } } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::DiamondSpiral->new (height => 123); ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::DiamondSpiral->parameter_info_list; ok (join(',',@pnames), 'n_start'); } #------------------------------------------------------------------------------ # xy_to_n { my @data = ([1, 0,0 ], [2, 1,0 ], [3, 0,1 ], [4, -1,0 ], [5, 0,-1 ], [5.25, 0.25,-1 ], [5.75, 0.75,-1 ], [6, 1,-1 ], [7, 2,0 ], [8, 1,1 ], [9, 0,2 ], [10, -1,1 ], [11, -2,0 ], [12, -1,-1 ], [13, 0,-2 ], [13.25, 0.25,-2 ], [13.75, 0.75,-2 ], [14, 1,-2 ], ); my $path = Math::PlanePath::DiamondSpiral->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; $want_n = int ($want_n + 0.5); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } } exit 0; Math-PlanePath-129/t/Base-Digits.t0000644000175000017500000001712013524205131014426 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2015, 2019 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 80; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::Base::Digits 'parameter_info_array', 'bit_split_lowtohigh', 'digit_split_lowtohigh', 'digit_join_lowtohigh', 'round_down_pow', 'round_up_pow'; my $have_64bits = ((1 << 63) != 0); my $modulo_64bit_dodginess = ($have_64bits && ((~0)%2) != ((~0)&1)); my $skip_64bit = ($modulo_64bit_dodginess ? 'due to 64-bit modulo dodginess ((~0)%2) != ((~0)&1)' : undef); MyTestHelpers::diag ("modulo operator dodginess ((~0)%2) != ((~0)&1): ", $modulo_64bit_dodginess ? "yes (bad)" : "no (good)"); #------------------------------------------------------------------------------ # parameter_info_array() { my $aref = parameter_info_array(); ok (scalar(@$aref), 1); ok ($aref->[0], Math::PlanePath::Base::Digits::parameter_info_radix2()); } #------------------------------------------------------------------------------ # round_down_pow() foreach my $elem ([ 1, 1,0 ], [ 2, 1,0 ], [ 3, 3,1 ], [ 4, 3,1 ], [ 5, 3,1 ], [ 8, 3,1 ], [ 9, 9,2 ], [ 10, 9,2 ], [ 26, 9,2 ], [ 27, 27,3 ], [ 28, 27,3 ], ) { my ($n, $want_pow, $want_exp) = @$elem; my ($got_pow, $got_exp) = round_down_pow($n,3); ok ($got_pow, $want_pow); ok ($got_exp, $want_exp); } # return 3**$k if it is exactly representable, or 0 if not sub pow3_if_exact { my ($k) = @_; my $p = 3**$k; if ($p+1 <= $p || $p-1 >= $p || ($p % 3) != 0 || (($p+1) % 3) != 1 || (($p-1) % 3) != 2) { return 0; } return $p; } { my $bad = 0; foreach my $i (2 .. 200) { my $p = pow3_if_exact($i); if (! $p) { MyTestHelpers::diag ("round_down_pow(3) tests stop for round-off at i=$i"); last; } { my $n = $p-1; my $want_pow = $p/3; my $want_exp = $i-1; my ($got_pow, $got_exp) = round_down_pow($n,3); if ($got_pow != $want_pow || $got_exp != $want_exp) { MyTestHelpers::diag ("round_down_pow($n,3) i=$i prev got $got_pow,$got_exp want $want_pow,$want_exp"); $bad++; } } { my $n = $p; my $want_pow = $p; my $want_exp = $i; my ($got_pow, $got_exp) = round_down_pow($n,3); if ($got_pow != $want_pow || $got_exp != $want_exp) { MyTestHelpers::diag ("round_down_pow($n,3) i=$i exact got $got_pow,$got_exp want $want_pow,$want_exp"); $bad++; } } { my $n = $p+1; my $want_pow = $p; my $want_exp = $i; my ($got_pow, $got_exp) = round_down_pow($n,3); if ($got_pow != $want_pow || $got_exp != $want_exp) { MyTestHelpers::diag ("round_down_pow($n,3) i=$i post got $got_pow,$got_exp want $want_pow,$want_exp"); $bad++; } } } ok ($bad,0); } #------------------------------------------------------------------------------ # round_up_pow() foreach my $elem ([ 1, 1,0 ], [ 2, 3,1 ], [ 3, 3,1 ], [ 4, 9,2 ], [ 5, 9,2 ], [ 8, 9,2 ], [ 9, 9,2 ], [ 10, 27,3 ], [ 26, 27,3 ], [ 27, 27,3 ], [ 28, 81,4 ], ) { my ($n, $want_pow, $want_exp) = @$elem; my ($got_pow, $got_exp) = round_up_pow($n,3); ok ($got_pow, $want_pow, "n=$n"); ok ($got_exp, $want_exp, "n=$n"); } { my $bad = 0; foreach my $i (2 .. 200) { # Not sure if need a bit of head-room on the base-3 calculations. # Had a cpantesters amd64 perl 5.20.1 fail at i=33 for some reason. if (! pow3_if_exact($i+1)) { MyTestHelpers::diag ("round_up_pow(3) tests stop for round-off at i=$i"); last; } my $p = pow3_if_exact($i); { my $n = $p-1; my $want_pow = $p; my $want_exp = $i; my ($got_pow, $got_exp) = round_up_pow($n,3); if ($got_pow != $want_pow || $got_exp != $want_exp) { MyTestHelpers::diag ("round_up_pow($n,3) i=$i prev got $got_pow,$got_exp want $want_pow,$want_exp"); $bad++; } } { my $n = $p; my $want_pow = $p; my $want_exp = $i; my ($got_pow, $got_exp) = round_up_pow($n,3); if ($got_pow != $want_pow || $got_exp != $want_exp) { MyTestHelpers::diag ("round_up_pow($n,3) i=$i exact got $got_pow,$got_exp want $want_pow,$want_exp"); $bad++; } } { my $n = $p+1; my $want_exp = $i+1; my $want_pow = pow3_if_exact($want_exp); if ($want_pow) { my ($got_pow, $got_exp) = round_up_pow($n,3); if ($got_pow != $want_pow || $got_exp != $want_exp) { MyTestHelpers::diag ("round_up_pow($n,3) i=$i post got $got_pow,$got_exp want $want_pow,$want_exp"); $bad++; } } } } ok ($bad,0); } #------------------------------------------------------------------------------ # digit_split_lowtohigh() ok (join(',',digit_split_lowtohigh(0,2)), ''); ok (join(',',digit_split_lowtohigh(13,2)), '1,0,1,1'); { my $n = ~0; foreach my $radix (2,3,4, 5, 6,7,8,9, 10, 16, 37) { my @digits = digit_split_lowtohigh($n,$radix); my $lowtwo = $n % ($radix * $radix); my $lowmod = $lowtwo % $radix; skip ($skip_64bit, $digits[0], $lowmod, "$n radix $radix lowest digit"); my $secondmod = ($lowtwo - ($lowtwo % $radix)) / $radix; skip ($skip_64bit, $digits[1], $secondmod, "$n radix $radix second lowest digit"); } } { my $uv_max = ~0; my $ones = 1; my @bits = digit_split_lowtohigh($uv_max,2); foreach my $bit (@bits) { $ones &&= $bit; } skip ($skip_64bit, $ones, 1, "~0 uv_max $uv_max should be all 1s"); if (! $ones) { MyTestHelpers::diag ("~0 uv_max $uv_max is: ", @bits); } } #------------------------------------------------------------------------------ # bit_split_lowtohigh() ok (join(',',bit_split_lowtohigh(0)), ''); ok (join(',',bit_split_lowtohigh(13)), '1,0,1,1'); { my $uv_max = ~0; my @bits = bit_split_lowtohigh($uv_max); my $ones = 1; foreach my $bit (@bits) { $ones &&= $bit; } skip ($skip_64bit, $ones, 1, "bit_split_lowtohigh(uv_max=$uv_max) ".join(',',@bits)); } #------------------------------------------------------------------------------ # digit_join_lowtohigh() ok (digit_join_lowtohigh([1,2,3],10), 321); # high zeros ok ok (digit_join_lowtohigh([1,1,0],2), 3); ok (digit_join_lowtohigh([1,1,0],8), 9); ok (digit_join_lowtohigh([1,1,0],10), 11); #------------------------------------------------------------------------------ 1; __END__ Math-PlanePath-129/t/PeanoDiagonals.t0000644000175000017500000002470413734026644015242 0ustar gggg#!/usr/bin/perl -w # Copyright 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 253; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::PeanoDiagonals; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::PeanoDiagonals::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::PeanoDiagonals->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::PeanoDiagonals->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::PeanoDiagonals->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::PeanoDiagonals->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::PeanoDiagonals->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); ok ($path->class_x_negative, 0, 'class_x_negative()'); ok ($path->class_y_negative, 0, 'class_y_negative()'); } #---------------------------------------------------------------------------- # Even Radix Offsets # { # foreach my $radix (4) { # my $bad = 0; # my $diag = Math::PlanePath::PeanoDiagonals->new (radix => $radix); # my $plain = Math::PlanePath::PeanoCurve->new (radix => $radix); # foreach my $n (0 .. $radix**6) { # my ($plain_x,$plain_y) = $plain->n_to_xy($n); # my $want_y = $plain_y + ($n % 2); # my $want_x = $plain_x + (int($n/$radix) % 2); # my ($diag_x,$diag_y) = $diag->n_to_xy($n); # unless ($diag_x == $want_x && $diag_y == $want_y) { # print "n=$n plain $plain_x,$plain_y want $want_x,$want_y got diag $diag_x,$diag_y\n"; # last if $bad++ > 10; # } # } # ok ($bad, 0); # } # } # Even has various double-visited points. # { # foreach my $radix (4) { # my $bad = 0; # my $path = Math::PlanePath::PeanoDiagonals->new (radix => $radix); # my %seen; # foreach my $n (0 .. $radix**6) { # my ($x,$y) = $path->n_to_xy($n); # if (defined (my $prev = $seen{"$x,$y"})) { # print "n=$n at $x,$y already seen prev n=$prev\n"; # last if $bad++ > 10; # } else { # $seen{"$x,$y"} = $n; # } # } # ok ($bad, 0, # 'even radix no double-visited points'); # } # } #---------------------------------------------------------------------------- # n_to_dxdy() { my $path = Math::PlanePath::PeanoDiagonals->new; { my @dxdy = $path->n_to_dxdy(-1); ok (scalar(@dxdy), 0, 'no dxdy at n=-1'); } { my @dxdy = $path->n_to_dxdy(0); ok (scalar(@dxdy), 2); ok ($dxdy[0], 1); ok ($dxdy[1], 1); } } #---------------------------------------------------------------------------- # _UNDOCUMENTED__n_to_turn_LSR() { my $path = Math::PlanePath::PeanoDiagonals->new; ok ($path->_UNDOCUMENTED__n_to_turn_LSR(-1), undef); ok ($path->_UNDOCUMENTED__n_to_turn_LSR(0), undef); ok ($path->_UNDOCUMENTED__n_to_turn_LSR(1), -1); ok ($path->_UNDOCUMENTED__n_to_turn_LSR(2), 1); ok ($path->_UNDOCUMENTED__n_to_turn_LSR(3), 1); } sub n_to_turn_LSR_by_digits { my ($self, $n) = @_; require Math::PlanePath::Base::Digits; my @digits = Math::PlanePath::Base::Digits::digit_split_lowtohigh ($n, $self->{'radix'}); my $turn = 1; while (@digits && $digits[0]==0) { # low 0s $turn = -$turn; shift @digits; } foreach my $digit (@digits) { if ($digit % 2) { $turn = -$turn; } } return $turn; } sub CountLowZeros { my ($n, $radix) = @_; $n > 0 || die; my $ret = 0; until ($n % $radix) { $ret++; $n /= $radix; } return $ret; } sub n_to_turn_LSR_by_parity { my ($self, $n) = @_; # per POD return (-1)**($n + CountLowZeros($n, $self->{'radix'})); } { my $bad = 0; foreach my $radix (3,5,7) { my $path = Math::PlanePath::PeanoDiagonals->new (radix => $radix); foreach my $n (1 .. $radix**5) { my $by_path = $path->_UNDOCUMENTED__n_to_turn_LSR($n); { my $by_digits = n_to_turn_LSR_by_digits($path,$n); unless ($by_path == $by_digits) { MyTestHelpers::diag ("radix=$radix"); MyTestHelpers::diag (" $by_path _UNDOCUMENTED__n_to_turn_LSR()"); MyTestHelpers::diag (" $by_digits n_to_turn_LSR_by_digits()"); $bad++; last if $bad > 10; } } { my $by_parity = n_to_turn_LSR_by_parity($path,$n); unless ($by_path == $by_parity) { MyTestHelpers::diag ("radix=$radix"); MyTestHelpers::diag (" $by_path _UNDOCUMENTED__n_to_turn_LSR()"); MyTestHelpers::diag (" $by_parity n_to_turn_LSR_by_parity()"); $bad++; last if $bad > 10; } } } } ok ($bad, 0, '_UNDOCUMENTED__n_to_turn_LSR() vs other ways'); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::PeanoDiagonals->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 9); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 81); } } #------------------------------------------------------------------------------ # xy_to_n() { my $path = Math::PlanePath::PeanoDiagonals->new; { my @n_list = $path->xy_to_n_list(0,0); ok (scalar(@n_list), 1); ok ($n_list[0], 0); } { my @n_list = $path->xy_to_n_list(1,0); ok (scalar(@n_list), 0); ok (join(',',@n_list), '', 'xy_to_n_list(1,0)'); } { my @n_list = $path->xy_to_n_list(-2,-2); ok (scalar(@n_list), 0); } } #------------------------------------------------------------------------------ # first few points { my @data = ( [ 0, 0,0 ], [ 1, 1,1 ], [ 2, 2,0 ], [ 3, 3,1 ], [ 4, 2,2 ], [ 61, 1,9 ], [ 425, 1,9 ], [ .25, .25, .25 ], [ 1.25, 1.25, 1-.25 ], [ 2.25, 2.25, .25 ], [ 3.25, 3-.25, 1+.25 ], [ 4.25, 2-.25, 2-.25 ], # base 4 [ .25, .25, .25, 4 ], [ 1.25, 1.25, 1-.25, 4 ], [ 14, 2,3, 4 ], [ 15, 1,4, 4 ], # described in POD [ 125, 1,4, 4 ], # POD samples, ternary [ 1, 1,1 ], [ 5, 1,1 ], [ 4, 2,2 ], [ 8, 2,2 ], [ 7, 1,3 ], [ 9, 3,3 ], [ 47, 1,3 ], [ 45, 3,3 ], # POD samples, base 4 [ 0, 0,0, 4], [ 0.25, 0.25,0.25, 4], [ 1, 1,1, 4], [ 2, 2,0, 4], [ 3, 3,1, 4], [ 4, 4,1, 4], [ 5, 3,2, 4], [ 6, 2,1, 4], [ 7, 1,2, 4], [ 8, 0,2, 4], [ 9, 1,3, 4], [ 10, 2,2, 4], [ 11, 3,3, 4], [ 12, 4,3, 4], [ 13, 3,4, 4], [ 14, 2,3, 4], [ 15, 1,4, 4], [ 16, 4,4, 4], [ 17, 5,3, 4], [ 18, 6,4, 4], ); foreach my $elem (@data) { my ($n, $x,$y, $radix) = @$elem; $radix ||= 3; my $path = Math::PlanePath::PeanoDiagonals->new (radix => $radix); { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n radix=$radix"); ok ($got_y, $y, "n_to_xy() y at n=$n radix=$radix"); } if ($n==int($n)) { # xy_to_n() my @got_n = $path->xy_to_n_list ($x, $y); my $found = ((grep {$_==$n} @got_n) ? 1 : 0); ok ($found, 1, "xy_to_n_list() n at x=$x,y=$y radix=$radix"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y radix=$radix"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y radix=$radix"); } } } #------------------------------------------------------------------------------ # rect_to_n_range { # with current over-estimates my $path = Math::PlanePath::PeanoDiagonals->new; { # inside 9x9 block my ($nlo, $nhi) = $path->rect_to_n_range (0,0, 1,8); ok ($nhi, 9**2 - 1); } { # on 9x9 block boundary, up to next bigger my ($nlo, $nhi) = $path->rect_to_n_range (0,0, 1,9); ok ($nhi, 9**3 - 1); } } #------------------------------------------------------------------------------ # midpoints are PeanoCurve { require Math::PlanePath::PeanoCurve; my $bad = 0; foreach my $radix (3,5,7) { my $path = Math::PlanePath::PeanoDiagonals->new (radix => $radix); my $mid = Math::PlanePath::PeanoCurve->new (radix => $radix); foreach my $n (0 .. $radix**5) { my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); my $x = ($x1+$x2-1)/2; my $y = ($y1+$y2-1)/2; my ($mx,$my) = $mid->n_to_xy ($n); unless ($x == $mx && $y == $my) { $bad++; } } } ok ($bad, 0); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/HIndexing.t0000644000175000017500000001323413734026645014230 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 466; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::HIndexing; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::HIndexing::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::HIndexing->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::HIndexing->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::HIndexing->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::HIndexing->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::HIndexing->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); } #------------------------------------------------------------------------------ # first few points { my @data = ( [ 0, 0,0 ], [ 1, 0,1 ], [ 2, 0,2 ], [ 3, 0,3 ], [ 4, 1,3 ], [ 5, 1,2 ], [ 6, 2,2 ], [ 7, 2,3 ], ); foreach my $elem (@data) { my ($n, $x, $y) = @$elem; my $path = Math::PlanePath::HIndexing->new; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } if ($n == int($n)) { { my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range(0,0,$x,$y) for n=$n, got_nlo=$got_nlo"); ok ($got_nhi >= $n, 1, "rect_to_n_range(0,0,$x,$y) for n=$n, got_nhi=$got_nhi"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range($x,$y,$x,$y) for n=$n, got_nlo=$got_nlo"); ok ($got_nhi >= $n, 1, "rect_to_n_range($x,$y,$x,$y) for n=$n, got_nhi=$got_nhi"); } } } } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::HIndexing->new; my ($n_lo, $n_hi) = $path->rect_to_n_range(0,0, 0,0); ok ($n_lo == 0, 1, "rect_to_n_range() 0,0 n_lo=$n_lo"); ok ($n_hi >= 0, 1, "rect_to_n_range() 0,0 n_hi=$n_hi"); } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::HIndexing->new; for (1 .. 20) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($n) frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($n) frac $frac, y"); } } } #------------------------------------------------------------------------------ # random points { my $path = Math::PlanePath::HIndexing->new; for (1 .. 50) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } ok ($rev_n, $n, "xy_to_n($x,$y) reverse to expect n=$n, got $rev_n"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() reverse n=$n cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() reverse n=$n cf got n_hi=$n_hi"); } } #------------------------------------------------------------------------------ # diagonal { my $path = Math::PlanePath::HIndexing->new; for (my $y = 0; $y < 256; $y += 2) { my $want_n = $path->xy_to_n ($y, $y); my $got_n = duplicate_bits($y); ok ($got_n, $want_n); } sub duplicate_bits { my ($k) = @_; my $str = sprintf '%b', $k; $str =~ s/(.)/$1$1/g; return oct("0b$str") / 2; } } exit 0; Math-PlanePath-129/t/KochSquareflakes.t0000644000175000017500000001037513734026644015610 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 382; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::KochSquareflakes; # uncomment this to run the ### lines #use Devel::Comments; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::KochSquareflakes::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::KochSquareflakes->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::KochSquareflakes->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::KochSquareflakes->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::KochSquareflakes->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::KochSquareflakes->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::KochSquareflakes->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 1); ok ($n_hi, 4); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 5); ok ($n_hi, 20); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 21); ok ($n_hi, 84); } # 4^6 == 4096 foreach my $level (0 .. 6) { my ($n_lo,$n_hi) = $path->level_to_n_range($level); my ($x_lo,$y_lo) = $path->n_to_xy($n_lo); my ($x_hi,$y_hi) = $path->n_to_xy($n_hi); my $dx = $x_hi - $x_lo; my $dy = $y_hi - $y_lo; ok($dx, 0, "level=$level last to first cycle dx, n_hi=$n_hi"); ok($dy, 1, "level=$level last to first cycle dy, n_hi=$n_hi"); } } #------------------------------------------------------------------------------ # xy_to_n() coverage foreach my $inward (0, 1) { my $path = Math::PlanePath::KochSquareflakes->new (inward => $inward); foreach my $x (-10 .. 10) { foreach my $y (-10 .. 10) { next if $x == 0 && $y == 0; my $n = $path->xy_to_n ($x, $y); next if ! defined $n; ### $n my ($nx,$ny) = $path->n_to_xy ($n); ok ($nx,$x, "x=$x,y=$y n=$n nxy=$nx,$ny"); ok ($ny,$y); } } } #------------------------------------------------------------------------------ # Xstart claimed in the POD foreach my $inward (0, 1) { my $path = Math::PlanePath::KochSquareflakes->new (inward => $inward); foreach my $level (0 .. 7) { my $nstart = (4**($level+1) - 1)/3; my ($xstart,$ystart) = $path->n_to_xy ($nstart); ok ($xstart, $ystart); my $calc_xstart = calc_xstart($level); ok ($calc_xstart, $xstart); } } sub calc_xstart { my ($level) = @_; if ($level == 0) { return -0.5; } if ($level == 1) { return -2; } my $x1 = -0.5; my $x2 = -2; foreach (2 .. $level) { ($x1,$x2) = ($x2, 4*$x2 - 2*$x1); } return $x2; } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/QuintetCurve.t0000644000175000017500000002052213734026643015005 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 360; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::QuintetCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::QuintetCurve::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::QuintetCurve->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::QuintetCurve->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::QuintetCurve->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::QuintetCurve->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::QuintetCurve->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } #------------------------------------------------------------------------------ # turn sequence { sub dxdy_to_dir4 { my ($dx,$dy) = @_; if ($dy == 0) { if ($dx == 1) { return 0; } if ($dx == -1) { return 2; } } if ($dy == 1) { if ($dx == 0) { return 1; } } if ($dy == -1) { if ($dx == 0) { return 3; } } die "unrecognised $dx,$dy"; } my $path = Math::PlanePath::QuintetCurve->new; my $n = $path->n_start; my $bad = 0; my ($prev_x, $prev_y) = $path->n_to_xy($n++); my ($x, $y) = $path->n_to_xy($n++); my $dx = $x - $prev_x; my $dy = $y - $prev_y; my $prev_dir = dxdy_to_dir4($dx,$dy); while ($n < 1000) { $prev_x = $x; $prev_y = $y; ($x,$y) = $path->n_to_xy($n); $dx = $x - $prev_x; $dy = $y - $prev_y; my $dir = dxdy_to_dir4($dx,$dy); my $want_turn = ($dir - $prev_dir) % 4; if ($want_turn == 3) { $want_turn = -1; } my $got_turn = $path->_UNDOCUMENTED__n_to_turn_LSR($n-1); if ($got_turn != $want_turn) { MyTestHelpers::diag ("bad n=$n turn got=$got_turn want=$want_turn"); MyTestHelpers::diag (" dir=$dir prev_dir=$prev_dir"); last if $bad++ > 10; } $n++; $prev_dir = $dir; } ok ($bad, 0, "turn sequence"); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::QuintetCurve->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 5); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 25); } { my ($n_lo,$n_hi) = $path->level_to_n_range(3); ok ($n_lo, 0); ok ($n_hi, 125); } } { my $path = Math::PlanePath::QuintetCurve->new (arms => 4); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 4); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 20); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 100); } } #------------------------------------------------------------------------------ # first few points { my @data = ( # arms=2 # [ 2, 46, -2,4 ], # [ 2, 1, -1,1 ], # arms=1 [ 1, .25, .25, 0 ], [ 1, .5, .5, 0 ], [ 1, 1.75, 1, -.75 ], [ 1, 0, 0,0 ], [ 1, 1, 1,0 ], [ 1, 2, 1,-1 ], [ 1, 3, 2,-1 ], [ 1, 4, 2,0 ], [ 1, 5, 2,1 ], [ 1, 6, 3,1 ], [ 1, 7, 4,1 ], ); foreach my $elem (@data) { my ($arms, $n, $x, $y) = @$elem; my $path = Math::PlanePath::QuintetCurve->new (arms => $arms); { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } if ($n == int($n)) { { my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range(0,0,$x,$y) arms=$arms for n=$n, got_nlo=$got_nlo"); ok ($got_nhi >= $n, 1, "rect_to_n_range(0,0,$x,$y) arms=$arms for n=$n, got_nhi=$got_nhi"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range($x,$y,$x,$y) arms=$arms for n=$n, got_nlo=$got_nlo"); ok ($got_nhi >= $n, 1, "rect_to_n_range($x,$y,$x,$y) arms=$arms for n=$n, got_nhi=$got_nhi"); } } } } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::QuintetCurve->new; my ($n_lo, $n_hi) = $path->rect_to_n_range(0,0, 0,0); ok ($n_lo == 0, 1, "rect_to_n_range() 0,0 n_lo=$n_lo"); ok ($n_hi >= 0, 1, "rect_to_n_range() 0,0 n_hi=$n_hi"); } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::QuintetCurve->new; for (1 .. 20) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($n) frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($n) frac $frac, y"); } } } #------------------------------------------------------------------------------ # random points { my $path = Math::PlanePath::QuintetCurve->new; for (1 .. 50) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } ok ($rev_n, $n, "xy_to_n($x,$y) reverse to expect n=$n, got $rev_n"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo, $n, "rect_to_n_range() reverse n=$n cf got n_lo=$n_lo"); ok ($n_hi, $n, "rect_to_n_range() reverse n=$n cf got n_hi=$n_hi"); } } #------------------------------------------------------------------------------ # reversible # bit slow ... { my $bad = 0; foreach my $arms (1 .. 4) { my $path = Math::PlanePath::QuintetCurve->new (arms => $arms); for my $n (0 .. 5**4) { my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } if (! defined $rev_n || $rev_n != $n) { last if ++$bad > 10; MyTestHelpers::diag ("xy_to_n($x,$y) reverse to expect n=$n, got $rev_n"); } } } ok ($bad, 0); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/CellularRule.t0000644000175000017500000001623313734026650014744 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 1216; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::CellularRule; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::CellularRule::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::CellularRule->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::CellularRule->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::CellularRule->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::CellularRule->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # parameter_info_list() { my @pnames = map {$_->{'name'}} Math::PlanePath::CellularRule->parameter_info_list; ok (join(',',@pnames), 'rule,n_start'); } #------------------------------------------------------------------------------ # first few points foreach my $relem ([ 50, # solid odd [ 1, 0,0 ], [ 2, -1,1 ], [ 3, 1,1 ], [ 4, -2,2 ], [ 5, 0,2 ], [ 6, 2,2 ], [ .75, -0.25, 0 ], [ 1.25, 0.25, 0 ], [ 1.75, -1-.25, 1 ], [ 2.25, -1+.25, 1 ], [ 2.75, 1-.25, 1 ], [ 3.25, 1+.25, 1 ], [ 3.75, -2-.25, 2 ], [ 4.25, -2+.25, 2 ], [ 4.75, 0-.25, 2 ], [ 5.25, 0+.25, 2 ], [ 5.75, 2-.25, 2 ], [ 6.25, 2+.25, 2 ], ], [ 6, # one,two left [ 1, 0,0 ], [ 2, -1,1 ], [ 3, 0,1 ], [ 4, -2,2 ], [ 5, -3,3 ], [ 6, -2,3 ], ], [ 20, # one,two right [ 1, 0,0 ], [ 2, 0,1 ], [ 3, 1,1 ], [ 4, 2,2 ], [ 5, 2,3 ], [ 6, 3,3 ], ], [ 14, # two left [ 1, 0,0 ], [ 2, -1,1 ], [ 3, 0,1 ], [ 4, -2,2 ], [ 5, -1,2 ], [ 6, -3,3 ], [ 7, -2,3 ], ], [ 84, # two right [ 1, 0,0 ], [ 2, 0,1 ], [ 3, 1,1 ], [ 4, 1,2 ], [ 5, 2,2 ], [ 6, 2,3 ], [ 7, 3,3 ], ], ) { my ($rule, @elements) = @$relem; my $path = Math::PlanePath::CellularRule->new (rule => $rule); foreach my $elem (@elements) { my ($n, $x,$y) = @$elem; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "rule=$rule n_to_xy() x at n=$n"); ok ($got_y, $y, "rule=$rule n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "rule=$rule xy_to_n() n at x=$x,y=$y"); } if ($n==int($n)) { my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y ".(ref $path)); } } } #------------------------------------------------------------------------------ # compare CellularRule bit-wise calculation with the specific sub-classes # my $bitwise_count = 0; foreach my $rule (0 .. 255) { my $bad_count = 0; my $path = Math::PlanePath::CellularRule->new (rule => $rule); ok ($path->y_negative, 0, 'y_negative()'); ok ($path->class_x_negative, 1, 'class_x_negative() instance method'); ok ($path->class_y_negative, 0, 'class_y_negative() instance method'); my $got_x_negative = $path->x_negative ? 1 : 0; { my $saw_x_negative = 0; for (my $n = $path->n_start; $n < 50; $n++) { my ($x,$y) = $path->n_to_xy($n) or last; if ($x < 0) { $saw_x_negative = 1; last; } } if ($got_x_negative != $saw_x_negative) { MyTestHelpers::diag ("rule=$rule saw x negative $saw_x_negative vs x_negative() $got_x_negative"); $bad_count++; } } if (ref $path ne 'Math::PlanePath::CellularRule') { MyTestHelpers::diag ("bitwise check rule=$rule"); $bitwise_count++; # copy of CellularRule guts my $bitwise = Math::PlanePath::CellularRule->new (rule => $rule, use_bitwise => 1); foreach my $x (-15 .. 15) { foreach my $y (0 .. 15) { my $path_n = $path->xy_to_n($x,$y); my $bit_n = $bitwise->xy_to_n($x,$y); unless ((! defined $path_n && ! defined $bit_n) || (defined $path_n && defined $bit_n && $path_n == $bit_n)) { MyTestHelpers::diag ("rule=$rule wrong xy_to_n() bitwise at x=$x,y=$y, got bitwise n=",$bit_n," path n=",$path_n); if (++$bad_count > 100) { die "Too much badness" }; ### $bad_count } } } foreach my $n (-2 .. 20) { my @path_xy = $path->n_to_xy($n); my @bit_xy = $bitwise->n_to_xy($n); my $path_xy = join(',',@path_xy); my $bit_xy = join(',',@bit_xy); unless ($path_xy eq $bit_xy) { MyTestHelpers::diag ("rule=$rule wrong n_to_xy() bitwise at n=$n"); if (++$bad_count > 100) { die "Too much badness" }; } } } ok ($bad_count, 0, "no badness in rule=$rule"); } MyTestHelpers::diag ("bitwise checks $bitwise_count"); exit 0; Math-PlanePath-129/t/CornerAlternating.t0000644000175000017500000002365513774220663016004 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 74;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments; require Math::PlanePath::CornerAlternating; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::CornerAlternating::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::CornerAlternating->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::CornerAlternating->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::CornerAlternating->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::CornerAlternating->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::CornerAlternating->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); ok ($path->class_x_negative, 0, 'class_x_negative() instance method'); ok ($path->class_y_negative, 0, 'class_y_negative() instance method'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::CornerAlternating->parameter_info_list; ok (join(',',@pnames), 'wider,n_start'); } #------------------------------------------------------------------------------ # _UNDOCUMENTED__dxdy_list_at_n() foreach my $wider (0,1,2,3,4, 20,21) { foreach my $n_start (0, 1, 37) { my $path = Math::PlanePath::CornerAlternating->new (n_start => $n_start); my $n; for ($n = $path->n_start; ; $n++) { my ($dx,$dy) = $path->n_to_dxdy($n); last if $dx==0 && $dy==-1; } ok ($path->_UNDOCUMENTED__dxdy_list_at_n, $n, "_UNDOCUMENTED__dxdy_list_at_n() first South"); } } #------------------------------------------------------------------------------ # rect_to_n_range() { my @data = ( # 4 | 17-18-19-20-21 ... # | | | | # 3 | 16-15-14-13 22 29 # | | | | # 2 | 5--6--7 12 23 28 # | | | | | | # 1 | 4--3 8 11 24 27 # | | | | | | # Y=0 | 1--2 9-10 25-26 # +--------------------- # X=0 1 2 3 4 5 [0,0, 2,2, 1,9], # bottom [0,0, 3,2, 1,12], [0,0, 4,2, 1,25], # bottom [0,0, 5,2, 1,28], [0,0, 2,3, 1,16], [0,0, 2,4, 1,19], [1,0, 2,3, 2,15], [1,0, 2,4, 2,19], [1,1, 2,3, 3,15], [1,1, 2,4, 3,19], #--------- # 4 | 29-30-31-32-33-34-35-36 ... wider => 3 # | | | | # 3 | 28-27-26-25-24-23-22 37 44 # | | | | # 2 | 11-12-13-14-15-16 21 38 43 # | | | | | | # 1 | 10--9--8--7--6 17 20 39 42 # | | | | | | # Y=0 | 1--2--3--4--5 18-19 40-41 # | # ---------------------------- # X=0 1 2 3 4 5 6 7 8 [0,0, 5,2, 1,18, wider=>3], # bottom [0,0, 6,2, 1,21, wider=>3], [0,0, 7,2, 1,40, wider=>3], # bottom [0,0, 8,2, 1,43, wider=>3], [0,0, 5,3, 1,28, wider=>3], [0,0, 5,4, 1,34, wider=>3], [2,0, 5,3, 3,26, wider=>3], [2,0, 5,4, 3,34, wider=>3], [2,1, 5,4, 6,34, wider=>3], [2,1, 5,3, 6,26, wider=>3], # min at xy=2,4 ); foreach my $elem (@data) { my ($x1,$y1, $x2,$y2, $want_lo,$want_hi, @options) = @$elem; my $path = Math::PlanePath::CornerAlternating->new (@options); my ($n_lo,$n_hi) = $path->rect_to_n_range($x1,$y1, $x2,$y2); ok ($n_lo, $want_lo, "n_lo for $x1,$y1, $x2,$y2"); ok ($n_hi, $want_hi, "n_hi for $x1,$y1, $x2,$y2"); } } exit ; #------------------------------------------------------------------------------ # first few points { my @data = ( [ 0, # wider==0 # [ 1, 0,0, 4 ], # [ 1.25, 0,.25 ], [ 2, 0,1, 6 ], [ 3, 1,1, 8 ], [ 4, 1,0, 8 ], # [ 5, 2,0, 10 ], # [ 6, 2,1, 10 ], # [ 7, 2,2, 12 ], # [ 8, 1,2, 12 ], # [ 9, 0,2, 12 ], # # [ 10, 0,3, 14 ], # [ 11, 1,3, 14 ], # [ 12, 2,3, 14 ], # [ 13, 3,3, 16 ], # [ 14, 3,2, 16 ], # [ 15, 3,1, 16 ], # [ 16, 3,0, 16 ], ], # [ 1, # wider==1 # [ 1, 0,0 ], # [ 2, 1,0 ], # # [ 3, 1,0 ], # [ 4, 1,1 ], # [ 5, 1,2 ], # [ 6, 0,2 ], # # [ 7, 0,2 ], # [ 8, 1,2 ], # [ 9, 2,2 ], # [ 10, 3,2 ], # [ 11, 3,1 ], # [ 12, 3,0 ], # ], # # [ 2, # wider==2 # [ 1, 0,0, 4 ], # [ 2, 1,0, 6 ], # [ 3, 2,0, 8 ], # # [ 4, 1,0, 10 ], # [ 5, 1,1, 10 ], # [ 6, 1,2, 10 ], # [ 7, 1,3, 12 ], # [ 8, 0,3, 12 ], # # [ 9, 0,2, 14 ], # [ 10, 1,2, 14 ], # [ 11, 2,2, 14 ], # [ 12, 3,2, 14 ], # [ 13, 4,2, 16 ], # [ 14, 4,1, 16 ], # [ 15, 4,0, 16 ], # ], # # [ 10, # wider==10 # [ 1, 0,0, 4 ], # [ 10, 9,0, 22 ], # [ 11, 10,0, 24 ], # [ 12, 0,1, 26 ], # [ 13, 1,1, 26 ], # ], ); foreach my $we (@data) { my ($wider, @wdata) = @$we; my $path = Math::PlanePath::CornerAlternating->new (wider => $wider); ### $wider ### @wdata foreach my $elem (@wdata) { my ($n, $x,$y, $boundary) = @$elem; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() wider=$wider x at n=$n"); ok ($got_y, $y, "n_to_xy() wider=$wider y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() wider=$wider n at x=$x,y=$y"); } # if ($n == int($n)) { # { # my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); # ok ($got_nlo <= $n, 1, "rect_to_n_range(0,0,$x,$y) wider=$wider for n=$n, got_nlo=$got_nlo"); # ok ($got_nhi >= $n, 1, "rect_to_n_range(0,0,$x,$y) wider=$wider for n=$n, got_nhi=$got_nhi"); # } # { # $n = int($n); # my ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x,$y, $x,$y); # ok ($got_nlo, $n, "rect_to_n_range($x,$y,$x,$y) wider=$wider for n=$n, got_nlo=$got_nlo"); # ok ($got_nhi, $n, "rect_to_n_range($x,$y,$x,$y) wider=$wider for n=$n, got_nhi=$got_nhi"); # } # } # # if (defined $boundary) { # my $got_boundary = $path->_NOTDOCUMENTED_n_to_figure_boundary($n); # ok ($got_boundary, $boundary, "n=$n xy=$x,$y"); # } } } } exit; #------------------------------------------------------------------------------ # random points { for (1 .. 50) { my $wider = int(rand(50)); # 0 to 50, inclusive my $path = Math::PlanePath::CornerAlternating->new (wider => $wider); my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } ok ($rev_n, $n, "xy_to_n($x,$y) wider=$wider reverse to expect n=$n, got $rev_n"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo, $n, "rect_to_n_range() wider=$wider reverse n=$n cf got n_lo=$n_lo"); ok ($n_hi, $n, "rect_to_n_range() wider=$wider reverse n=$n cf got n_hi=$n_hi"); } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/HexSpiralSkewed-unskew.t0000644000175000017500000000332612136177346016730 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 168; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::HexSpiral; use Math::PlanePath::HexSpiralSkewed; foreach my $n_start (1, 0) { my $plain = Math::PlanePath::HexSpiral->new (n_start => $n_start); my $skewed = Math::PlanePath::HexSpiralSkewed->new (n_start => $n_start); foreach my $n ($n_start .. $n_start+20) { my ($plain_x, $plain_y) = $plain->n_to_xy ($n); my ($skewed_x, $skewed_y) = $skewed->n_to_xy ($n); { my ($conv_x,$conv_y) = (($plain_x-$plain_y)/2, $plain_y); ok ($conv_x == $skewed_x, 1, "plain->skewed x at n=$n plain $plain_x,$plain_y skewed $skewed_x,$skewed_y"); ok ($conv_y == $skewed_y, 1, "plain->skewed y at n=$n"); } { my ($conv_x,$conv_y) = ((2*$skewed_x+$skewed_y), $plain_y); ok ($conv_x == $plain_x, 1, "skewed->plain x at n=$n"); ok ($conv_y == $plain_y, 1, "skewed->plain y at n=$n"); } } } exit 0; Math-PlanePath-129/t/RationalsTree.t0000644000175000017500000003145613734026643015133 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 988; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::RationalsTree; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::RationalsTree::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::RationalsTree->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::RationalsTree->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::RationalsTree->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::RationalsTree->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # depth pythagorean pairs pairs per POD { my $path = Math::PlanePath::RationalsTree->new; foreach my $depth (0 .. 10) { my $count = 0; foreach my $n ($path->tree_depth_to_n($depth) .. $path->tree_depth_to_n_end($depth)) { my ($x,$y) = $path->n_to_xy ($n); if (xy_is_pythagorean($x,$y)) { $count++; } } my $calc_count = depth_to_pythagorean_count($depth); ok ($calc_count, $count, "depth=$depth pythagorean count"); } } sub xy_is_pythagorean { my ($x,$y) = @_; return ($x>$y && ($x%2)!=($y%2)); } sub depth_to_pythagorean_count { my ($depth) = @_; if ($depth % 2 == 0) { return int(2**$depth / 3); } else { return int((2**$depth + 2) / 3); } } # foreach my $depth (0 .. 20) { # printf "%b,", depth_to_pythagorean_count($depth); # } # print "\n"; #------------------------------------------------------------------------------ # SB odd/odd etc pairs per POD { my $path = Math::PlanePath::RationalsTree->new; foreach my $n ($path->n_start .. 50) { my $calc_parity; if ($n % 3 == 0) { my $depth = $path->tree_n_to_depth($n); $calc_parity = ($depth % 2 == 0 ? 'OE' : 'EO'); } elsif ($n % 3 == 1) { $calc_parity = 'OO'; } else { my $depth = $path->tree_n_to_depth($n); $calc_parity = ($depth % 2 == 0 ? 'EO' : 'OE'); } my ($x,$y) = $path->n_to_xy ($n); my $got_parity = ($x % 2 == 0 ? 'E' : 'O') . ($y % 2 == 0 ? 'E' : 'O'); ok ($calc_parity, $got_parity, "parity n=$n"); } } #------------------------------------------------------------------------------ # tree_n_parent() { my @data = ([ 1, undef ], [ 2, 1 ], [ 3, 1 ], [ 4, 2 ], [ 5, 2 ], [ 6, 3 ], [ 7, 3 ], [ 8, 4 ], [ 9, 4 ], [ 10, 5 ], [ 11, 5 ], [ 12, 6 ], [ 13, 6 ], ); my $path = Math::PlanePath::RationalsTree->new; foreach my $elem (@data) { my ($n, $want_n_parent) = @$elem; my $got_n_parent = $path->tree_n_parent ($n); ok ($got_n_parent, $want_n_parent); } } #------------------------------------------------------------------------------ # tree_n_children() { my @data = ([ 1, '2,3' ], [ 2, '4,5' ], [ 3, '6,7' ], [ 4, '8,9' ], [ 5, '10,11' ], [ 6, '12,13' ], [ 7, '14,15' ], ); my $path = Math::PlanePath::RationalsTree->new; foreach my $elem (@data) { my ($n, $want_n_children) = @$elem; my $got_n_children = join(',',$path->tree_n_children($n)); ok ($got_n_children, $want_n_children, "tree_n_children($n)"); } } { my @data = ([ 0, '1,2' ], [ 1, '3,4' ], [ 2, '5,6' ], [ 3, '7,8' ], [ 4, '9,10' ], [ 5, '11,12' ], [ 6, '13,14' ], ); my $path = Math::PlanePath::RationalsTree->new (tree_type => 'L'); foreach my $elem (@data) { my ($n, $want_n_children) = @$elem; my $got_n_children = join(',',$path->tree_n_children($n)); ok ($got_n_children, $want_n_children, "tree_n_children($n)"); } } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::RationalsTree->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::RationalsTree->parameter_info_list; ok (join(',',@pnames), 'tree_type'); } #------------------------------------------------------------------------------ # n_to_xy(), xy_to_n() foreach my $topelem ([ 'L', [ 0, 0,1 ], [ 1, 1,2 ], [ 2, 1,1 ], [ 3, 2,3 ], [ 4, 3,2 ], [ 5, 1,3 ], [ 6, 2,1 ], [ 7, 3,4 ], [ 8, 5,3 ], [ 9, 2,5 ], [ 10, 5,2 ], [ 11, 3,5 ], [ 12, 4,3 ], [ 13, 1,4 ], [ 14, 3,1 ], ], [ 'HCS', [ 1, 1,1 ], [ 2, 2,1 ], [ 3, 1,2 ], [ 4, 3,1 ], [ 5, 3,2 ], [ 6, 1,3 ], [ 7, 2,3 ], [ 8, 4,1 ], [ 9, 5,2 ], [ 10, 4,3 ], [ 11, 5,3 ], [ 12, 1,4 ], [ 13, 2,5 ], [ 14, 3,4 ], [ 15, 3,5 ], [ 581, 49,22 ], # Example in "Numeri" by Valerio Bioglio, Umberto Cerruti, Nadir Murru. # http://www.dm.unito.it/~cerruti/doc-html/tremattine/tre_mattine.pdf [ 194, 3,16 ], ], [ 'SB', [ 1, 1,1 ], [ 2, 1,2 ], [ 3, 2,1 ], [ 4, 1,3 ], [ 5, 2,3 ], [ 6, 3,2 ], [ 7, 3,1 ], [ 8, 1,4 ], [ 9, 2,5 ], [ 10, 3,5 ], [ 11, 3,4 ], [ 12, 4,3 ], [ 13, 5,3 ], [ 14, 5,2 ], [ 15, 4,1 ], [ 16, 1,5 ], [ 17, 2,7 ], [ 18, 3,8 ], [ 19, 3,7 ], [ 20, 4,7 ], [ 21, 5,8 ], [ 22, 5,7 ], [ 23, 4,5 ], [ 24, 5,4 ], [ 25, 7,5 ], [ 26, 8,5 ], [ 27, 7,4 ], [ 28, 7,3 ], [ 29, 8,3 ], [ 30, 7,2 ], [ 31, 5,1 ], [ 95, 6,7 ], ], [ 'CW', [ 1, 1,1 ], [ 2, 1,2 ], [ 3, 2,1 ], [ 4, 1,3 ], [ 5, 3,2 ], [ 6, 2,3 ], [ 7, 3,1 ], [ 8, 1,4 ], [ 9, 4,3 ], [ 10, 3,5 ], [ 11, 5,2 ], [ 12, 2,5 ], [ 13, 5,3 ], [ 14, 3,4 ], [ 15, 4,1 ], ], [ 'Bird', [ 1, 1,1 ], [ 2, 1,2 ], [ 3, 2,1 ], [ 4, 2,3 ], [ 5, 1,3 ], [ 6, 3,1 ], [ 7, 3,2 ], [ 8, 3,5 ], [ 9, 3,4 ], [ 10, 1,4 ], [ 11, 2,5 ], [ 12, 5,2 ], [ 13, 4,1 ], [ 14, 4,3 ], [ 15, 5,3 ], [ 16, 5,8 ], [ 17, 4,7 ], [ 18, 4,5 ], [ 19, 5,7 ], [ 20, 2,7 ], [ 21, 1,5 ], [ 22, 3,7 ], [ 23, 3,8 ], [ 24, 8,3 ], [ 25, 7,3 ], [ 26, 5,1 ], [ 27, 7,2 ], [ 28, 7,5 ], [ 29, 5,4 ], [ 30, 7,4 ], [ 31, 8,5 ], ], [ 'Drib', [ 1, 1,1 ], [ 2, 1,2 ], [ 3, 2,1 ], [ 4, 2,3 ], [ 5, 3,1 ], [ 6, 1,3 ], [ 7, 3,2 ], [ 8, 3,5 ], [ 9, 5,2 ], [ 10, 1,4 ], [ 11, 4,3 ], [ 12, 3,4 ], [ 13, 4,1 ], [ 14, 2,5 ], [ 15, 5,3 ], ], [ 'AYT', [ 1, 1,1 ], [ 2, 2,1 ], [ 3, 1,2 ], [ 4, 3,1 ], [ 5, 1,3 ], [ 6, 3,2 ], [ 7, 2,3 ], [ 8, 4,1 ], [ 9, 1,4 ], [ 10, 4,3 ], [ 11, 3,4 ], [ 12, 5,2 ], [ 13, 2,5 ], [ 14, 5,3 ], [ 15, 3,5 ], [ 16, 5,1 ], [ 17, 1,5 ], [ 18, 5,4 ], [ 19, 4,5 ], [ 20, 7,3 ], [ 21, 3,7 ], [ 22, 7,4 ], [ 23, 4,7 ], [ 24, 7,2 ], [ 25, 2,7 ], [ 26, 7,5 ], [ 27, 5,7 ], [ 28, 8,3 ], [ 29, 3,8 ], [ 30, 8,5 ], [ 31, 5,8 ], # Esempio 13 from Umberto Cerruti "Ordinare i # Razionali: Gli Alberi di Keplero e di Calkin-Wilf" [ 1948, 105,41 ], ], ) { my ($tree_type, @elems) = @$topelem; my $path = Math::PlanePath::RationalsTree->new (tree_type => $tree_type); foreach my $elem (@elems) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "$tree_type x at n=$n"); ok ($got_y, $want_y, "$tree_type y at n=$n"); } foreach my $elem (@elems) { my ($want_n, $x, $y) = @$elem; my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "$tree_type n at x=$x,y=$y"); } foreach my $elem (@elems) { my ($n, $x, $y) = @$elem; my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } #------------------------------------------------------------------------------ # xy_to_n() distinct n foreach my $options ([tree_type => 'SB'], [tree_type => 'CW'], [tree_type => 'AYT'], [tree_type => 'Bird'], [tree_type => 'Drib'], ) { my $path = Math::PlanePath::RationalsTree->new (@$options); my $bad = 0; my %seen; my $xlo = -2; my $xhi = 25; my $ylo = -2; my $yhi = 20; my ($nlo, $nhi) = $path->rect_to_n_range($xlo,$ylo, $xhi,$yhi); my $count = 0; OUTER: for (my $x = $xlo; $x <= $xhi; $x++) { for (my $y = $ylo; $y <= $yhi; $y++) { my $n = $path->xy_to_n ($x,$y); next if ! defined $n; # sparse # avoid overflow when N becomes big if ($n >= 2**32) { MyTestHelpers::diag ("x=$x,y=$y n=$n, oops, meant to keep below 2^32"); last if $bad++ > 10; next; } if ($seen{$n}) { MyTestHelpers::diag ("x=$x,y=$y n=$n seen before at $seen{$n}"); last if $bad++ > 10; } if ($n < $nlo) { MyTestHelpers::diag ("x=$x,y=$y n=$n below nlo=$nlo"); last OUTER if $bad++ > 10; } if ($n > $nhi) { MyTestHelpers::diag ("x=$x,y=$y n=$n above nhi=$nhi"); last OUTER if $bad++ > 10; } $seen{$n} = "$x,$y"; $count++; } } ok ($bad, 0, "xy_to_n() coverage and distinct, $count points"); } #------------------------------------------------------------------------------ # sum of terms in row is numerator 3*2^level-1, per A052940 foreach my $tree_type ('SB', 'CW', 'AYT', 'Bird', 'Drib', ) { my $path = Math::PlanePath::RationalsTree->new (tree_type => $tree_type); for my $level (1 .. 5) { # 7 { my $num = 0; my $den = 1; for my $n (2**$level .. 2**($level+1) - 1) { my ($x,$y) = $path->n_to_xy ($n); ($num, $den) = ($num*$y + $x*$den, $den * $y); foreach my $k (2 .. $y) { while (($num % $k) == 0 && ($den % $k) == 0) { $num /= $k; $den /= $k; } } } # MyTestHelpers::diag ("sum $num/$den"); ok ($num, 3*2**$level - 1, "tree_type $tree_type level $level sum num"); ok ($den, 2, "tree_type $tree_type level $level sum den"); } { my $sum = 0; for my $n (2**$level .. 2**($level+1) - 1) { my ($x,$y) = $path->n_to_xy ($n); $sum += $x; } ### $sum ok ($sum, 3**$level, "tree_type $tree_type level $level numerator sum"); } } } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::RationalsTree->new; foreach my $y (1 .. 3) { foreach my $x (30 .. 40) { my ($n_lo,$n_hi) = $path->rect_to_n_range (0,0, $x,$y); my $n = $path->xy_to_n ($x,$y) || next; ok ($n < $n_hi, 1, "rect_to_n_range() on $x,$y"); } } { my ($n_lo,$n_hi) = $path->rect_to_n_range (9,8, 2,2); ok ($n_hi >= 384, 1); } } exit 0; Math-PlanePath-129/t/GreekKeySpiral.t0000644000175000017500000001414613734026645015237 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 2251; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::GreekKeySpiral; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::GreekKeySpiral::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::GreekKeySpiral->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::GreekKeySpiral->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::GreekKeySpiral->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::GreekKeySpiral->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::GreekKeySpiral->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->default_n_start, 1, 'default_n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); my @pnames = map {$_->{'name'}} $path->parameter_info_list; ok (join(',',@pnames), 'turns'); } #------------------------------------------------------------------------------ # first few points { my @groups = ([ { turns => 0 }, [ 1, 0,0 ], [ 2, 1,0 ], [ 3, 1,1 ], [ 4, 0,1 ], [ 5, -1,1 ], [ 6, -1,0 ], [ 7, -1,-1 ], ], [ { turns => 1 }, [ 1, 0,0 ], [ 2, 0,1 ], [ 3, 1,1 ], [ 4, 1,0 ], [ 5, 2,0 ], [ 6, 3,0 ], [ 7, 3,1 ], [ 8, 2,1 ], ], [ { turns => 2 }, [ 1, 0,0 ], [ 2, 1,0 ], [ 3, 1,1 ], [ 4, 0,1 ], [ 5, 0,2 ], [ 6, 1,2 ], [ 7, 2,2 ], [ 8, 2,1 ], [ 9, 2,0 ], [ 10, 3,0 ], ], ); foreach my $group (@groups) { my ($options, @data) = @$group; my $path = Math::PlanePath::GreekKeySpiral->new (%$options); my $turns = $options->{'turns'}; foreach my $elem (@data) { my ($n, $x,$y) = @$elem; { my ($got_x,$got_y) = $path->n_to_xy($n); ok ($got_x == $x, 1, "turns=$turns n=$n"); ok ($got_y == $y, 1); } { my @got_n_list = $path->xy_to_n_list($x,$y); ok (scalar(@got_n_list), 1); my $got_n_str = join(',', @got_n_list); ok ($got_n_str, $n); } { my $got_n = $path->xy_to_n($x,$y); ok ($got_n, $n); } } } } #------------------------------------------------------------------------------ # n_to_xy() fractions part way between integer points { foreach my $turns (0, 1, 2, 3, 4, 5, 20) { my $path = Math::PlanePath::GreekKeySpiral->new (turns => $turns); my $bad = 0; PATH: foreach my $n ($path->n_start .. $path->n_start + 500) { my ($x,$y) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $nfrac = $n + $frac; my ($got_xfrac,$got_yfrac) = $path->n_to_xy ($nfrac); my $want_xfrac = $x + $frac*($x2-$x); my $want_yfrac = $y + $frac*($y2-$y); if ($got_xfrac != $want_xfrac || $got_yfrac != $want_yfrac) { MyTestHelpers::diag ("xy frac at n=$nfrac"); last PATH if $bad++ > 10; } } } ok ($bad, 0, "n_to_xy() fraction turns=$turns"); } } #------------------------------------------------------------------------------ # xy_to_n() reversals foreach my $turns (0, 1, 2, 3, 4, 5, 20) { my $path = Math::PlanePath::GreekKeySpiral->new (turns => $turns); my $bad = 0; PATH: foreach my $n ($path->n_start .. $path->n_start + $turns*$turns*128) { my ($x,$y) = $path->n_to_xy ($n); my $got_n = $path->xy_to_n ($x, $y); if ($got_n != $n) { MyTestHelpers::diag ("n=$n is $x,$y xy_to_n()=$got_n"); last PATH if $bad++ > 10; } } ok ($bad, 0, "xy_to_n() reversals"); } #------------------------------------------------------------------------------ # rect_to_n_range() first and last foreach my $turns (2, 3, 4, 5, 20, 1, 0) { my $side = $turns+1; my $path = Math::PlanePath::GreekKeySpiral->new (turns => $turns); foreach my $i (1 .. 100) { my $x = $side*$i + $side-1; my $y = -$side*$i; my $n = $path->xy_to_n ($x,$y); my ($n_lo, $n_hi) = $path->rect_to_n_range (0,0, $x,$y); ok ($n_hi, $n, "rect_to_n_range() turns=$turns i=$i hi last x=$x,y=$y"); $x++; $n++; ok ($path->xy_to_n($x,$y), $n); ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo, $n, "rect_to_n_range() turns=$turns i=$i lo first x=$x,y=$y n=$n"); } } exit 0; Math-PlanePath-129/t/TrianguleSpiral.t0000644000175000017500000000465213734026641015460 0ustar gggg#!/usr/bin/perl -w # Copyright 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 13; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::TriangleSpiral; my $path = Math::PlanePath::TriangleSpiral->new; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::TriangleSpiral::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::TriangleSpiral->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::TriangleSpiral->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::TriangleSpiral->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative() instance method'); ok ($path->y_negative, 1, 'y_negative() instance method'); ok ($path->class_x_negative, 1, 'class_x_negative() instance method'); ok ($path->class_y_negative, 1, 'class_y_negative() instance method'); my @pnames = map {$_->{'name'}} $path->parameter_info_list; ok (join(',',@pnames), 'n_start', 'parameter_info_list() keys'); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/MultipleRings.t0000644000175000017500000003275013734026644015154 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 510; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::MultipleRings; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::MultipleRings::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::MultipleRings->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::MultipleRings->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::MultipleRings->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::MultipleRings->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # exact points my $base_r3 = Math::PlanePath::MultipleRings->new(step=>3)->{'base_r'}; my $base_r4 = Math::PlanePath::MultipleRings->new(step=>4)->{'base_r'}; foreach my $elem ( # step=0 horizontal [ [step=>0], 1, 0,0 ], [ [step=>0], 2, 1,0 ], [ [step=>0], 3, 2,0 ], # step=1 [ [step=>1], 1, 0,0 ], [ [step=>1], 1.25, 0,0 ], [ [step=>1], 2, 1,0 ], [ [step=>1], 2.25, 0.5,0 ], [ [step=>1], 2.75, -0.5,0 ], [ [step=>1], 3, -1,0 ], [ [step=>1], 3.25, -0.5,0 ], [ [step=>1], 3.75, 0.5,0 ], [ [step=>1], 4, 2,0 ], [ [step=>1], 7, 3,0 ], [ [step=>1], 8, 0,3 ], [ [step=>1], 9, -3,0 ], [ [step=>1], 10, 0,-3 ], [ [step=>1], 11, 4,0 ], [ [step=>1], 16, 5,0 ], [ [step=>1], 19, -5,0 ], # step=2 [ [step=>2], 1, 0.5, 0 ], [ [step=>2], 2, -0.5, 0 ], [ [step=>2], 3, 1.5, 0 ], [ [step=>2], 4, 0, 1.5 ], [ [step=>2], 5, -1.5, 0 ], [ [step=>2], 6, 0,-1.5 ], [ [step=>2], 7, 2.5, 0 ], [ [step=>2], 10, -2.5, 0 ], [ [step=>2], 13, 3.5, 0 ], [ [step=>2], 17, -3.5, 0 ], [ [step=>2], 21, 4.5, 0 ], [ [step=>2], 26, -4.5, 0 ], # step=3 [ [step=>3], 1, $base_r3+1, 0 ], [ [step=>3], 4, $base_r3+2, 0 ], [ [step=>3], 7, -($base_r3+2), 0 ], [ [step=>3], 10, $base_r3+3, 0 ], [ [step=>3], 19, $base_r3+4, 0 ], [ [step=>3], 25, -($base_r3+4), 0 ], # step=4 [ [step=>4], 1, $base_r4+1, 0 ], [ [step=>4], 2, 0, $base_r4+1 ], [ [step=>4], 3, -($base_r4+1), 0 ], [ [step=>4], 4, 0, -($base_r4+1) ], [ [step=>4], 5, $base_r4+2, 0 ], [ [step=>4], 7, 0, $base_r4+2 ], [ [step=>4], 9, -($base_r4+2), 0 ], [ [step=>4], 11, 0, -($base_r4+2) ], # step=6 [ [step=>6], 1, 1, 0 ], [ [step=>6], 4, -1, 0 ], [ [step=>6], 7, 2, 0 ], [ [step=>6], 13, -2, 0 ], [ [step=>6,ring_shape=>'polygon'], 1, 1, 0 ], [ [step=>6,ring_shape=>'polygon'], 4, -1, 0 ], ) { my ($parameters, $n, $x, $y) = @$elem; my $path = Math::PlanePath::MultipleRings->new (@$parameters); my $name = join(',',@$parameters); { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "$name n_to_xy() x at n=$n"); ok ($got_y, $y, "$name n_to_xy() y at n=$n"); } { # n_to_rsquared() my $rsquared = $x*$x + $y*$y; my @got_rsquared = $path->n_to_rsquared($n); my $got_rsquared = $got_rsquared[0]; ok (scalar(@got_rsquared), 1); ok (defined $got_rsquared, 1); ok ($got_rsquared == $rsquared, 1, "$name n_to_rsquared() at n=$n want $rsquared got $got_rsquared"); } } #------------------------------------------------------------------------------ # n_to_rsquared() foreach my $elem ( # step=0 [ [step=>0], 1, 0*0 ], [ [step=>0], 2, 1*1 ], [ [step=>0], 2.5, 1.5*1.5 ], [ [step=>0], 3, 2*2 ], [ [step=>0], 4, 3*3 ], #------------------------------------------- # step=1 [ [step=>1], 1, 0*0 ], # origin R=0 [ [step=>1], 2, 1*1 ], # horiz, right R=1 [ [step=>1], 2.25, 0.5*0.5 ], [ [step=>1], 2.5, 0 ], [ [step=>1], 2.75, 0.5*0.5 ], [ [step=>1], 3, 1*1 ], # left [ [step=>1], 3.25, 0.5*0.5 ], [ [step=>1], 3.5, 0 ], [ [step=>1], 3.75, 0.5*0.5 ], [ [step=>1], 4, 2*2 ], # triangle, right R=2 [ [step=>1], 4.5, 1*1 ], [ [step=>1], 4.25, 7/4 ], [ [step=>1], 4.75, 7/4 ], [ [step=>1], 5, 2*2 ], # up R=2 at 120deg [ [step=>1], 5.25, 7/4 ], [ [step=>1], 5.75, 7/4 ], [ [step=>1], 6, 2*2 ], # down R=2 at 240deg [ [step=>1], 6.25, 7/4 ], [ [step=>1], 6.75, 7/4 ], [ [step=>1], 7, 3*3 ], # square [ [step=>1], 7.5, 4.5 ], [ [step=>1], 7.25, 45/8 ], [ [step=>1], 10, 3*3 ], [ [step=>1], 10.5, 4.5 ], [ [step=>1], 10.75, 45/8 ], [ [step=>1], 16, 5*5 ], # hexagon [ [step=>1], 16.25, 325/16 ], [ [step=>1], 16.5, 75/4 ], [ [step=>1], 21, 5*5 ], [ [step=>1], 21.75, 325/16 ], #------------------------------------------- # step=6 [ [step=>6], 1, 1*1 ], # 1..6 inclusive [ [step=>6], 6, 1*1 ], # [ [step=>6], 6.75, undef ], [ [step=>6], 7, 2*2 ], # 7..18 inclusive [ [step=>6], 18, 2*2 ], [ [step=>6], 19, 3*3 ], # 19..36 inclusive [ [step=>6], 36, 3*3 ], [ [step=>6,ring_shape=>'polygon'], 1, 1*1 ], [ [step=>6,ring_shape=>'polygon'], 6, 1*1 ], # [ [step=>6,ring_shape=>'polygon'], 7, undef ], ) { my ($parameters, $n, $want_rsquared) = @$elem; my $path = Math::PlanePath::MultipleRings->new (@$parameters); my $name = join(',',@$parameters); { my ($x,$y) = $path->n_to_xy($n); my $xy_rsquared = $x*$x + $y*$y; ok (abs($xy_rsquared-$want_rsquared) < 0.0001); } { my $got_rsquared = $path->n_to_rsquared($n); my $got_rsquared_str = (defined $got_rsquared ? sprintf('%.22f', $got_rsquared) : '[undef]'); my $want_rsquared_str = (defined $want_rsquared ? sprintf('%.22f', $want_rsquared) : '[undef]'); ok (equal($got_rsquared,$want_rsquared), 1, "$name n_to_rsquared() at n=$n got $got_rsquared_str want $want_rsquared_str"); } { my $got_radius = $path->n_to_radius($n); my $got_radius_str = sprintf('%.22f', $got_radius); my $want_radius = sqrt($want_rsquared); my $want_radius_str = sprintf('%.22f', $want_radius); if ($want_radius*$want_radius == $want_rsquared) { ok (equal($got_radius,$want_radius), 1, "$name n_to_radius() at n=$n got $got_radius_str want $want_radius_str"); } else { ok (abs($got_radius-$want_radius) < 0.0001, 1, "$name n_to_radius() at n=$n got $got_radius_str want $want_radius_str"); } } } sub equal { my ($x,$y) = @_; return ((! defined $x && ! defined $y) || (defined $x && defined $y && $x == $y)); } #------------------------------------------------------------------------------ # _xy_to_angle_frac() { my @data = ([ 1, 0, 0 ], [ 0, 1, .25 ], [ -1, 0, .5 ], [ 0, -1, .75 ], [ 0, 0, 0 ], [ -0.0, -0.0, 0 ], [ -0.0, 0, 0 ], [ 0, -0.0, 0 ], ); foreach my $elem (@data) { my ($x, $y, $want) = @$elem; my $got = Math::PlanePath::MultipleRings::_xy_to_angle_frac($x,$y); ok (abs ($got - $want) < 0.001, 1, "_xy_to_angle_frac() on x=$x,y=$y got $got want $want"); } } #------------------------------------------------------------------------------ # n_start, x_negative(), y_negative() { my $path = Math::PlanePath::MultipleRings->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); ok ($path->class_x_negative, 1, 'class_x_negative() instance method'); ok ($path->class_y_negative, 1, 'class_y_negative() instance method'); } { my $path = Math::PlanePath::MultipleRings->new (step => 0); ok ($path->n_start, 1, 'n_start()'); ok (! $path->x_negative, 1, 'x_negative()'); ok (! $path->y_negative, 1, 'y_negative()'); ok ($path->class_x_negative, 1, 'class_x_negative() instance method'); ok ($path->class_y_negative, 1, 'class_y_negative() instance method'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::MultipleRings->parameter_info_list; ok (join(',',@pnames), 'step,ring_shape'); } #------------------------------------------------------------------------------ # xy_to_n() { my $step = 3; my $n = 2; my $path = Math::PlanePath::MultipleRings->new (step => $step); my ($x,$y) = $path->n_to_xy($n); $y -= .1; ### try: "n=$n x=$x,y=$y" my $got_n = $path->xy_to_n($x,$y); ### $got_n ok ($got_n, $n, "xy_to_n() back from n=$n at offset x=$x,y=$y"); } # step=0 and step=1 centred on 0,0 # step=2 two on ring, rounds to the N=1 foreach my $step (0 .. 2) { my $path = Math::PlanePath::MultipleRings->new (step => $step); ok ($path->xy_to_n(0,0), 1, "xy_to_n(0,0) step=$step is 1"); ok ($path->xy_to_n(-0.0, 0), 1, "xy_to_n(-0,0) step=$step is 1"); ok ($path->xy_to_n(0, -0.0), 1, "xy_to_n(0,-0) step=$step is 1"); ok ($path->xy_to_n(-0.0, -0.0), 1, "xy_to_n(-0,-0) step=$step is 1"); } foreach my $step (3 .. 10) { my $path = Math::PlanePath::MultipleRings->new (step => $step); ok ($path->xy_to_n(0,0), undef, "xy_to_n(0,0) step=$step is undef (nothing in centre)"); ok ($path->xy_to_n(-0.0, 0), undef, "xy_to_n(-0,0) step=$step is undef (nothing in centre)"); ok ($path->xy_to_n(0, -0.0), undef, "xy_to_n(0,-0) step=$step is undef (nothing in centre)"); ok ($path->xy_to_n(-0.0, -0.0), undef, "xy_to_n(-0,-0) step=$step is undef (nothing in centre)"); } foreach my $step (0 .. 3) { my $path = Math::PlanePath::MultipleRings->new (step => $step); ok ($path->xy_to_n(0.1,0.1), 1, "xy_to_n(0.1,0.1) step=$step is 1"); } foreach my $step (4 .. 10) { my $path = Math::PlanePath::MultipleRings->new (step => $step); ok ($path->xy_to_n(0.1,0.1), undef, "xy_to_n(0.1,0.1) step=$step is undef (nothing in centre)"); } #------------------------------------------------------------------------------ # rect_to_n_range() foreach my $step (0 .. 10) { my $path = Math::PlanePath::MultipleRings->new (step => $step); my ($got_lo, $got_hi) = $path->rect_to_n_range(0,0,0,0); ok ($got_lo >= 1, 1, "rect_to_n_range(0,0) step=$step is lo=$got_lo"); ok ($got_hi >= $got_lo, 1, "rect_to_n_range(0,0) step=$step want hi=$got_hi >= lo"); } foreach my $step (0 .. 10) { my $path = Math::PlanePath::MultipleRings->new (step => $step); my ($got_lo, $got_hi) = $path->rect_to_n_range(-0.1,-0.1, 0.1,0.1); ok ($got_lo >= 1, 1, "rect_to_n_range(0,0) step=$step is lo=$got_lo"); ok ($got_hi >= $got_lo, 1, "rect_to_n_range(0,0) step=$step want hi=$got_hi >= lo"); } exit 0; Math-PlanePath-129/t/PyramidRows.t0000644000175000017500000001414013734026644014627 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 158; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::PyramidRows; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::PyramidRows::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::PyramidRows->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::PyramidRows->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::PyramidRows->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::PyramidRows->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::PyramidRows->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative() default'); ok ($path->y_negative, 0, 'y_negative() default'); ok ($path->class_x_negative, 1, 'class_x_negative() instance method'); ok ($path->class_y_negative, 0, 'class_y_negative() instance method'); my @pnames = map {$_->{'name'}} $path->parameter_info_list; ok (join(',',@pnames), 'step,align,n_start'); } { my $path = Math::PlanePath::PyramidRows->new (step => 0); ok ($path->n_start, 1, 'n_start()'); ok (! $path->x_negative, 1, 'x_negative() step=0'); ok (! $path->y_negative, 1, 'y_negative() step=0'); ok ($path->class_x_negative, 1, 'class_x_negative() instance method'); ok ($path->class_y_negative, 0, 'class_y_negative() instance method'); } { my $path = Math::PlanePath::PyramidRows->new (step => 1); ok ($path->n_start, 1, 'n_start()'); ok (! $path->x_negative, 1, 'x_negative() step=1'); ok (! $path->y_negative, 1, 'y_negative() step=1'); ok ($path->class_x_negative, 1, 'class_x_negative() instance method'); ok ($path->class_y_negative, 0, 'class_y_negative() instance method'); } { my $path = Math::PlanePath::PyramidRows->new (step => 3); ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative() step=3'); ok ($path->y_negative, 0, 'y_negative() step=3'); ok ($path->class_x_negative, 1, 'class_x_negative() instance method'); ok ($path->class_y_negative, 0, 'class_y_negative() instance method'); } #------------------------------------------------------------------------------ # rect_to_n_range() { foreach my $elem ( # step = 2 # 5 6 7 8 9 y=2 # 2 3 4 y=1 # 1 y=0 # x=0 [undef, 0,1, 0,1, 3,3], [undef, 0,2, 0,2, 7,7], [2, -2,0, -1,2, 2,6], # part left [2, 2,0, 1,2, 4,9], # part right # step = 1 # 4 5 6 y=2 # 2 3 y=1 # 1 y=0 # x=0 [1, 0,1, 0,1, 2,2], [1, 0,2, 0,2, 4,4], [1, -1,1, 0,2, 2,4], # part left [1, 1,0, 2,2, 3,6], # part right # step = 0 # 3 y=2 # 2 y=1 # 1 y=0 # x=0 [0, 0,1, 0,1, 2,2], [0, 0,2, 0,2, 3,3], # step = 4 # 7 8 9 10 11 12 13 14 15 y=2 # 2 3 4 5 6 y=1 # 1 y=0 # x=0 [4, -7,-2, -1,1, 2,3], ) { my ($step, $x1,$y1, $x2,$y2, $want_lo,$want_hi) = @$elem; my $dstep = (defined $step ? $step : 'undef'); my $path = Math::PlanePath::PyramidRows->new (step => $step); my ($got_lo, $got_hi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); ### $got_lo ### $got_hi ok ($got_lo, $want_lo, "lo on $x1,$y1 $x2,$y2 step=$dstep"); ok ($got_hi, $want_hi, "hi on $x1,$y1 $x2,$y2 step=$dstep"); } } { foreach my $elem ( [0,0, 0,0, 1,1], [-1,0, -1,0, 1,0], # off left [1,0, 1,0, 1,0], # off right [-999,-5, -500,3, 1,0], # far off left [ 999,-5, 500,3, 1,0], # far off right [ -10,-1, 10,-6, 1,0], # y negs ) { foreach my $step (undef, 0, 1, 2, 3, 4, 5, 10, 20) { my ($x1,$y1,$x2,$y2, $want_lo, $want_hi) = @$elem; my $dstep = (defined $step ? $step : 'undef'); my $path = Math::PlanePath::PyramidRows->new (step => $step); my ($got_lo, $got_hi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); ok ($got_lo, $want_lo, "lo on $x1,$y1 $x2,$y2 step=$dstep"); ok ($got_hi, $want_hi, "hi on $x1,$y1 $x2,$y2 step=$dstep"); } } } exit 0; Math-PlanePath-129/t/VogelFloret.t0000644000175000017500000000772713734026640014610 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 29; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::VogelFloret; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::VogelFloret::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::VogelFloret->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::VogelFloret->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::VogelFloret->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::VogelFloret->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::VogelFloret->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative() instance method'); ok ($path->y_negative, 1, 'y_negative() instance method'); my @pnames = map {$_->{'name'}} $path->parameter_info_list; ok (join(',',@pnames), 'rotation_type,rotation_factor,radius_factor'); } #------------------------------------------------------------------------------ # parameters { my $pp = Math::PlanePath::VogelFloret->new; ok ($pp->{'rotation_factor'} >= 0, 1); ok ($pp->{'radius_factor'} >= 0, 1); my $ps2 = Math::PlanePath::VogelFloret->new (rotation_type => 'sqrt2'); ok ($ps2->{'rotation_factor'} >= 0, 1,); ok ($ps2->{'radius_factor'} >= 0, 1); ok ($pp->{'rotation_factor'} != $ps2->{'rotation_factor'}, 1); { my $path = Math::PlanePath::VogelFloret->new (rotation_factor => 0.5); ok ($path->{'rotation_factor'} == 0.5, 1); ok ($path->{'radius_factor'} >= 1.0, 1); } { my $path = Math::PlanePath::VogelFloret->new (rotation_type => 'sqrt2', radius_factor => 2.0); ok ($path->{'rotation_factor'}, $ps2->{'rotation_factor'}); ok ($path->{'radius_factor'} >= 2.0, 1); } } #------------------------------------------------------------------------------ # n_to_rsquared() { my $path = Math::PlanePath::VogelFloret->new (radius_factor => 1); ok ($path->n_to_rsquared(0), 0); ok ($path->n_to_rsquared(1), 1); ok ($path->n_to_rsquared(20.5), 20.5); } { my $path = Math::PlanePath::VogelFloret->new (radius_factor => 2); ok ($path->n_to_rsquared(0), 0); ok ($path->n_to_rsquared(1), 1*4); ok ($path->n_to_rsquared(20.5), 20.5*4); } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::VogelFloret->new; my ($n_lo, $n_hi) = $path->rect_to_n_range (-100,-100, 100,100); ok ($n_lo, 1); ok ($n_hi > 1, 1); ok ($n_hi < 10*100*100, 1); } exit 0; Math-PlanePath-129/t/bigint-lite.t0000644000175000017500000000231213246362405014550 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2015, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; use lib 't'; use MyTestHelpers; my $test_count = (tests => 648)[1]; plan tests => $test_count; if (! eval { require Math::BigInt::Lite; 1 }) { MyTestHelpers::diag ('skip due to Math::BigInt::Lite not available -- ',$@); foreach (1 .. $test_count) { skip ('due to no Math::BigInt::Lite', 1, 1); } exit 0; } BEGIN { MyTestHelpers::nowarnings(); } require bigint_common; bigint_common::bigint_checks ('Math::BigInt::Lite'); exit 0; Math-PlanePath-129/t/UlamWarburtonQuarter.t0000644000175000017500000001450713734026640016520 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 317; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::UlamWarburtonQuarter; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::UlamWarburtonQuarter::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::UlamWarburtonQuarter->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::UlamWarburtonQuarter->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::UlamWarburtonQuarter->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::UlamWarburtonQuarter->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::UlamWarburtonQuarter->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 1); ok ($n_hi, 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 1); ok ($n_hi, 5); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 1); ok ($n_hi, 21); } } { my $path = Math::PlanePath::UlamWarburtonQuarter->new (n_start => 10); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 10); ok ($n_hi, 10); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 10); ok ($n_hi, 14); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 10); ok ($n_hi, 30); } } # depth=2-2=0 is level=0 # depth=4-2=2 is level=1 # depth=8-2=6 is level=2 # foreach my $n_start (1, -10, 39) { my $path = Math::PlanePath::UlamWarburtonQuarter->new (n_start => $n_start); foreach my $level (0 .. 6) { my ($n_lo,$n_hi) = $path->level_to_n_range($level); my $depth = 2**($level+1) - 2; my $n_end = $path->tree_depth_to_n_end($depth); ok ($n_hi,$n_end); } } #------------------------------------------------------------------------------ # tree_depth_to_n() { my $path = Math::PlanePath::UlamWarburtonQuarter->new; my @data = ([ 0, 1 ], [ 1, 2 ], [ 2, 3 ], [ 3, 6 ], [ 4, 7 ], [ 5, 10 ], [ 6, 13 ], [ 7, 22 ], [ 8, 23 ], ); foreach my $elem (@data) { my ($depth, $want_n) = @$elem; my $got_n = $path->tree_depth_to_n ($depth); ok ($got_n, $want_n, "tree_depth_to_n() depth=$depth"); } } #------------------------------------------------------------------------------ # tree_n_parent() { my @data = ([ 1, undef ], [ 2, 1 ], [ 3, 2 ], [ 4, 2 ], [ 5, 2 ], [ 6, 4 ], [ 7, 6 ], [ 8, 6 ], [ 9, 6 ], [ 10, 7 ], [ 11, 8 ], [ 12, 9 ], ); my $path = Math::PlanePath::UlamWarburtonQuarter->new; foreach my $elem (@data) { my ($n, $want_n_parent) = @$elem; my $got_n_parent = $path->tree_n_parent ($n); ok ($got_n_parent, $want_n_parent); } } #------------------------------------------------------------------------------ # tree_n_children() { my @data = ([ 1, '2' ], [ 2, '3,4,5' ], [ 3, '' ], [ 4, '6' ], [ 5, '' ], [ 6, '7,8,9' ], [ 7, '10' ], [ 8, '11' ], [ 9, '12' ], ); my $path = Math::PlanePath::UlamWarburtonQuarter->new; foreach my $elem (@data) { my ($n, $want_n_children) = @$elem; my $got_n_children = join(',',$path->tree_n_children($n)); ok ($got_n_children, $want_n_children, "tree_n_children($n)"); } } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::UlamWarburtonQuarter->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); } #------------------------------------------------------------------------------ # random points { my $path = Math::PlanePath::UlamWarburtonQuarter->new; for (1 .. 50) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } ok ($rev_n, $n, "xy_to_n($x,$y) reverse to expect n=$n, got $rev_n"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() reverse n=$n cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() reverse n=$n cf got n_hi=$n_hi"); } } #------------------------------------------------------------------------------ # x,y coverage { my $path = Math::PlanePath::UlamWarburtonQuarter->new; foreach my $x (-10 .. 10) { foreach my $y (-10 .. 10) { my $n = $path->xy_to_n ($x,$y); next if ! defined $n; my ($nx,$ny) = $path->n_to_xy ($n); ok ($nx, $x, "xy_to_n($x,$y)=$n then n_to_xy() reverse"); ok ($ny, $y, "xy_to_n($x,$y)=$n then n_to_xy() reverse"); } } } exit 0; Math-PlanePath-129/t/ComplexMinus.t0000644000175000017500000001363513734026647015005 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 473; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::ComplexMinus; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::ComplexMinus::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::ComplexMinus->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::ComplexMinus->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::ComplexMinus->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::ComplexMinus->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::ComplexMinus->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); ok ($path->class_x_negative, 1, 'class_x_negative() instance method'); ok ($path->class_y_negative, 1, 'class_y_negative() instance method'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::ComplexMinus->parameter_info_list; ok (join(',',@pnames), 'realpart'); } #------------------------------------------------------------------------------ # first few points { my @data = ( # [ 0, 0,0 ], # [ 1, 1,0 ], # [ 2, 1,1 ], # [ 3, 0,1 ], # [ 4, 0,2 ], # # [ 0.25, 0.25, 0 ], # [ 1.25, 1, 0.25 ], # [ 2.25, 0.75, 1 ], # [ 3.25, 0, 1.25 ], # claimed in the POD [ 0x1D0, 4,0 ], [ 0x1D1, 5,0 ], [ 0x1DC, 6,0 ], [ 0x1DD, 7,0 ], [ 0x1C0, 8,0 ], [ 0x1C1, 9,0 ], [ 0x1CC, 10,0 ], [ 0x1CD, 11,0 ], [ 0x110, 12,0 ], [ 0x111, 13,0 ], [ 0x11C, 14,0 ], [ 0x11D, 15,0 ], [ 0x100, 16,0 ], [ 0x101, 17,0 ], [ 0x10C, 18,0 ], [ 0x10D, 19,0 ], [ 0xCD0, 20,0 ], [ 0xCD1, 21,0 ], [ 0xCDC, 22,0 ], [ 0xCDD, 23,0 ], ); my $path = Math::PlanePath::ComplexMinus->new; foreach my $elem (@data) { my ($n, $x, $y) = @$elem; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } #------------------------------------------------------------------------------ # random rect_to_n_range() foreach my $realpart (1 .. 6) { my $path = Math::PlanePath::ComplexMinus->new (realpart => $realpart); for (1 .. 20) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)); # 0 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); ok (defined $rev_n, 1, "xy_to_n($x,$y) realpart=$realpart reverse n, got undef"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() realpart=$realpart n=$n at xy=$x,$y cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() realpart=$realpart n=$n at xy=$x,$y cf got n_hi=$n_hi"); } } #------------------------------------------------------------------------------ # many rect_to_n_range() if (0) { my $bad = 0; foreach my $realpart (1 .. 6) { my $path = Math::PlanePath::ComplexMinus->new (realpart => $realpart); foreach my $n (1 .. 500000) { my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n || $rev_n != $n) { MyTestHelpers::diag ("xy_to_n($x,$y) realpart=$realpart reverse n, got ",$rev_n); $bad = 1; } my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); if ($n_lo > $n) { MyTestHelpers::diag ("rect_to_n_range() realpart=$realpart n=$n at xy=$x,$y cf got n_lo=$n_lo"); $bad = 1; } if ($n_hi < $n) { MyTestHelpers::diag ("rect_to_n_range() realpart=$realpart n=$n at xy=$x,$y cf got n_hi=$n_hi"); $bad = 1; } } } ok ($bad, 0); } exit 0; Math-PlanePath-129/t/AnvilSpiral.t0000644000175000017500000000731013734026650014571 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 31; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::AnvilSpiral; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::AnvilSpiral::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::AnvilSpiral->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::AnvilSpiral->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::AnvilSpiral->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::AnvilSpiral->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::AnvilSpiral->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative() instance method'); ok ($path->y_negative, 1, 'y_negative() instance method'); ok ($path->class_x_negative, 1, 'class_x_negative()'); ok ($path->class_y_negative, 1, 'class_y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::AnvilSpiral->parameter_info_list; ok (join(',',@pnames), 'wider,n_start'); } #------------------------------------------------------------------------------ foreach my $n_start (undef, 0, -37) { foreach my $wider (0, 1, 2, 3, 9, 17) { my $path = Math::PlanePath::AnvilSpiral->new (n_start => $n_start, wider => $wider); my $bad_count = 0; my %seen_xy; foreach my $n ($path->n_start .. 500) { my ($x, $y) = $path->n_to_xy ($n); if ($seen_xy{"$x,$y"}++) { MyTestHelpers::diag ("wider=$wider n_to_xy($n) duplicate xy $x,$y"); last if ++$bad_count > 10; } my $rev_n = $path->xy_to_n ($x, $y); if ($rev_n != $n) { MyTestHelpers::diag ("wider=$wider xy_to_n($x,$y) got $rev_n want $n"); last if ++$bad_count > 10; } { my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, 0,0); if ($n_lo > $n || $n_hi < $n) { MyTestHelpers::diag ("wider=$wider rect_to_n_range($x,$y,0,0) got $n_lo,$n_hi but n=$n"); last if ++$bad_count > 10; } } { my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); if ($n_lo > $n || $n_hi < $n) { MyTestHelpers::diag ("wider=$wider rect_to_n_range($x,$y) got $n_lo,$n_hi but n=$n"); last if ++$bad_count > 10; } } } ok ($bad_count, 0); } } exit 0; Math-PlanePath-129/t/Base-Generic.t0000644000175000017500000000546212136177406014600 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 22; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::Base::Generic 'is_infinite', 'round_nearest', 'floor'; #------------------------------------------------------------------------------ # is_infinite() { my $pos_infinity = 0; my $neg_infinity = 0; my $nan = 0; my $skip_inf; my $skip_nan; if (! eval { require Data::Float; 1 }) { MyTestHelpers::diag ("Data::Float not available"); $skip_inf = 'due to Data::Float not available'; $skip_nan = 'due to Data::Float not available'; } else { if (Data::Float::have_infinite()) { $pos_infinity = Data::Float::pos_infinity(); $neg_infinity = Data::Float::neg_infinity(); } else { $skip_inf = 'due to Data::Float no infinite'; } if (Data::Float::have_nan()) { $nan = Data::Float::nan(); MyTestHelpers::diag ("nan is ",$nan); } else { $skip_nan = 'due to Data::Float no nan'; } } skip ($skip_inf, !! is_infinite($pos_infinity), 1, '_is_infinte() +inf'); skip ($skip_inf, !! is_infinite($neg_infinity), 1, '_is_infinte() -inf'); skip ($skip_nan, !! is_infinite($nan), 1, '_is_infinte() nan'); } { require POSIX; ok (! is_infinite(POSIX::DBL_MAX()), 1, '_is_infinte() DBL_MAX'); ok (! is_infinite(- POSIX::DBL_MAX()), 1, '_is_infinte() neg DBL_MAX'); } #------------------------------------------------------------------------------ # round_nearest() ok (round_nearest(-.75), -1); ok (round_nearest(-.5), 0); ok (round_nearest(-0.25), 0); ok (round_nearest(0.25), 0); ok (round_nearest(1.25), 1); ok (round_nearest(1.5), 2); ok (round_nearest(1.75), 2); ok (round_nearest(2), 2); #------------------------------------------------------------------------------ # floor() ok (floor(-.75), -1); ok (floor(-.5), -1); ok (floor(-0.25), -1); ok (floor(0.25), 0); ok (floor(0.75), 0); ok (floor(1.25), 1); ok (floor(1.5), 1); ok (floor(1.75), 1); ok (floor(2), 2); #------------------------------------------------------------------------------ 1; __END__ Math-PlanePath-129/t/Rows.t0000644000175000017500000000724413734026643013307 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 40; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::Rows; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::Rows::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::Rows->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::Rows->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::Rows->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::Rows->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::Rows->new (height => 123); ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 0, 'x_negative() instance method'); ok ($path->y_negative, 0, 'y_negative() instance method'); } { # width not a parameter as such ... my @pnames = map {$_->{'name'}} Math::PlanePath::Rows->parameter_info_list; ok (join(',',@pnames), 'n_start'); } #------------------------------------------------------------------------------ # n_to_xy { my @data = ([ 1, 0,0 ], [ 2, 1,0 ], [ 2.5, 1.5,0 ], [ 5, 4,0 ], [ 5.5, -0.5,1 ], [ 6, 0,1 ], [ 7, 1,1 ], ); my $path = Math::PlanePath::Rows->new (width => 5); foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next if $want_n != int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } } #------------------------------------------------------------------------------ # rect_to_n_range() { foreach my $elem ([5, 0,0, 0,0, 1,1], [5, 1,0, 1,0, 2,2], [5, 0,-1, 1,-1, -4,-3], [5, 0,0, 9999,0, 1,5 ], [5, 3,0, -9999,0, 1,4 ], ) { my ($width, $x1,$y1,$x2,$y2, $want_lo, $want_hi) = @$elem; my $path = Math::PlanePath::Rows->new (width => $width); my ($got_lo, $got_hi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); ok ($got_lo, $want_lo, "lo on $x1,$y1 $x2,$y2 width=$width"); ok ($got_hi, $want_hi, "hi on $x1,$y1 $x2,$y2 width=$width"); } } exit 0; Math-PlanePath-129/t/SierpinskiArrowheadCentres.t0000644000175000017500000001434513734026643017656 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 336; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::SierpinskiArrowheadCentres; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::SierpinskiArrowheadCentres::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::SierpinskiArrowheadCentres->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::SierpinskiArrowheadCentres->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::SierpinskiArrowheadCentres->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::SierpinskiArrowheadCentres->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::SierpinskiArrowheadCentres->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::SierpinskiArrowheadCentres->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 0); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 2); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 8); } { my ($n_lo,$n_hi) = $path->level_to_n_range(3); ok ($n_lo, 0); ok ($n_hi, 26); } } #------------------------------------------------------------------------------ # first few points { my @table = ([ [ ], # default triangular [ 0, 0,0 ], [ 1, 1,1 ], [ 2, -1,1 ], [ 3, -2,2 ], [ 4, -3,3 ], [ 5, -1,3 ], [ 6, 1,3 ], [ 7, 2,2 ], [ 8, 3,3 ], [ .25, .25, .25 ], [ 1.25, 0.5, 1 ], [ 2.25, -1.25, 1.25 ], [ 3.25, -2.25, 2.25 ], [ 4.25, -2.5, 3 ], [ 5.25, -.5, 3 ], [ 6.25, 1.25, 2.75 ], [ 7.25, 2.25, 2.25 ], [ 8.25, 3.25, 3.25 ], ], [ [ align => 'diagonal' ], [ 0, 0,0 ], [ 1, 1,0 ], [ 2, 0,1 ], [ 3, 0,2 ], [ 4, 0,3 ], [ 5, 1,2 ], [ 6, 2,1 ], [ 7, 2,0 ], [ 8, 3,0 ], ], [ [ align => 'left' ], [ 0, 0,0 ], [ 1, 0,1 ], [ 2, -1,1 ], [ 3, -2,2 ], [ 4, -3,3 ], [ 5, -2,3 ], [ 6, -1,3 ], [ 7, 0,2 ], [ 8, 0,3 ], ], [ [ align => 'right' ], [ 0, 0,0 ], [ 1, 1,1 ], [ 2, 0,1 ], [ 3, 0,2 ], [ 4, 0,3 ], [ 5, 1,3 ], [ 6, 2,3 ], [ 7, 2,2 ], [ 8, 3,3 ], ], ); foreach my $t (@table) { my ($options, @data) = @$t; my $path = Math::PlanePath::SierpinskiArrowheadCentres->new (@$options); foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $want_x, "n_to_xy() x at n=$n @$options"); ok ($got_y, $want_y, "n_to_xy() y at n=$n @$options"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next unless $want_n==int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } foreach my $elem (@data) { my ($n, $x, $y) = @$elem; my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); next unless $n==int($n); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::SierpinskiArrowheadCentres->new; for (1 .. 20) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($n) frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($n) frac $frac, y"); } } } exit 0; Math-PlanePath-129/t/Hypot.t0000644000175000017500000000701713734026645013460 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 13; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::Hypot; my $path = Math::PlanePath::Hypot->new; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::Hypot::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::Hypot->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::Hypot->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::Hypot->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative() instance method'); ok ($path->y_negative, 1, 'y_negative() instance method'); ok ($path->class_x_negative, 1, 'class_x_negative()'); ok ($path->class_y_negative, 1, 'class_y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::Hypot->parameter_info_list; ok (join(',',@pnames), 'points,n_start'); } #------------------------------------------------------------------------------ # increasing R^2 foreach my $points (@{Math::PlanePath::Hypot ->parameter_info_hash->{'points'}->{'choices'}}) { foreach my $n_start (1, 0, 37) { my $path = Math::PlanePath::Hypot->new (points => $points, n_start => $n_start); my $prev_h = -1; my $prev_x = 0; my $prev_y = -1; foreach my $n ($n_start .. $n_start + 1000) { my ($x, $y) = $path->n_to_xy ($n); my $h = $x*$x + $y*$y; if ($h < $prev_h) { die "decreasing h=$h prev=$prev_h"; } if ($n > $n_start+1 && ! _turn_func_Left($prev_x,$prev_y, $x,$y)) { die "not turn left at n=$n xy=$x,$y prev=$prev_x,$prev_y"; } $prev_h = $h; $prev_x = $x; $prev_y = $y; } } } sub _turn_func_Left { my ($dx,$dy, $next_dx,$next_dy) = @_; ### _turn_func_Left() ... my $a = $next_dy * $dx; my $b = $next_dx * $dy; return ($a > $b || $dx==-$next_dx && $dy==-$next_dy # straight opposite 180 ? 1 : 0); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/FibonacciWordFractal.t0000644000175000017500000000530413734026646016361 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 6; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::FibonacciWordFractal; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::FibonacciWordFractal::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::FibonacciWordFractal->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::FibonacciWordFractal->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::FibonacciWordFractal->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); } #------------------------------------------------------------------------------ # xy_to_n() near origin { my $bad = 0; my $path = Math::PlanePath::FibonacciWordFractal->new; OUTER: foreach my $x (-8 .. 16) { foreach my $y (-8 .. 16) { my $n = $path->xy_to_n ($x,$y); next unless defined $n; my ($nx,$ny) = $path->n_to_xy ($n); if ($nx != $x || $ny != $y) { MyTestHelpers::diag("xy_to_n($x,$y) gives n=$n, which is $nx,$ny"); last OUTER if ++$bad > 10; } } } ok ($bad, 0); } #------------------------------------------------------------------------------ # n_to_xy() fracs { my $bad = 0; my $path = Math::PlanePath::FibonacciWordFractal->new; foreach my $n (0 .. 89) { my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); my $want_x = $x1 + ($x2-$x1)*.25; my $want_y = $y1 + ($y2-$y1)*.25; my $n_frac = $n + .25; my ($got_x,$got_y) = $path->n_to_xy ($n_frac); if ($got_x != $want_x || $got_y != $want_y) { MyTestHelpers::diag("n_to_xy($n_frac) got $got_x,$got_y want $want_x,$want_y"); last if ++$bad > 10; } } ok ($bad, 0); } exit 0; Math-PlanePath-129/t/Columns.t0000644000175000017500000001007713734026650013771 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 43;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::Columns; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::Columns::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::Columns->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::Columns->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::Columns->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::Columns->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::Columns->new (height => 123); ok ($path->n_start, 1, 'n_start()'); ok (! $path->x_negative, 1, 'x_negative()'); ok (! $path->y_negative, 1, 'y_negative()'); ok (! $path->class_x_negative, 1, 'class_x_negative() instance method'); ok (! $path->class_y_negative, 1, 'class_y_negative() instance method'); } { # height not a parameter as such ... my @pnames = map {$_->{'name'}} Math::PlanePath::Columns->parameter_info_list; ok (join(',',@pnames), 'n_start'); } #------------------------------------------------------------------------------ # _UNDOCUMENTED__turn_any_right_at_n() { my $path = Math::PlanePath::Columns->new (height => 5); ok ($path->_UNDOCUMENTED__turn_any_right_at_n(), 5); } #------------------------------------------------------------------------------ # n_to_xy { my @data = ([ 1, 0,0 ], [ 2, 0,1 ], [ 2.5, 0,1.5 ], [ 5, 0,4 ], [ 5.5, 1,-0.5 ], [ 6, 1,0 ], [ 7, 1,1 ], ); my $path = Math::PlanePath::Columns->new (height => 5); foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next if $want_n != int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } } #------------------------------------------------------------------------------ # rect_to_n_range() { foreach my $elem ([5, 0,0, 0,0, 1,1], [5, 0,1, 0,1, 2,2], [5, -1,0, -1,1, -4,-3], [5, 0,0, 0,9999, 1,5 ], [5, 0,3, 0,-9999, 1,4 ], ) { my ($height, $x1,$y1,$x2,$y2, $want_lo, $want_hi) = @$elem; my $path = Math::PlanePath::Columns->new (height => $height); my ($got_lo, $got_hi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); ok ($got_lo, $want_lo, "lo on $x1,$y1 $x2,$y2 height=$height"); ok ($got_hi, $want_hi, "hi on $x1,$y1 $x2,$y2 height=$height"); } } exit 0; Math-PlanePath-129/t/CubicBase.t0000644000175000017500000001746413734026647014206 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 191; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::CubicBase; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::CubicBase::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::CubicBase->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::CubicBase->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::CubicBase->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::CubicBase->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::CubicBase->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::CubicBase->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 0); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 3); } { my ($n_lo,$n_hi) = $path->level_to_n_range(3); ok ($n_lo, 0); ok ($n_hi, 7); } } { my $path = Math::PlanePath::CubicBase->new (radix => 4); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 0); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 3); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 15); } { my ($n_lo,$n_hi) = $path->level_to_n_range(3); ok ($n_lo, 0); ok ($n_hi, 63); } } #------------------------------------------------------------------------------ # first few points { my @data = ([ 3, [ 0, 0,0 ], [ 1, 2,0 ], # level=0 [ 2, 4,0 ], [ 3, -1,1 ], # level=1 [ 4, 1,1 ], [ 5, 3,1 ], [ 6, -2,2 ], [ 7, 0,2 ], [ 8, 2,2 ], [ 9, -3,-3 ], # level=2 [ 27, 6,0 ], # level=3 [ 81, -9,9 ], # level=4 [ 243, -9,-9 ], # level=5 ], [ 5, [ 0, 0,0 ], [ 1, 2,0 ], # level=0 [ 2, 4,0 ], [ 3, 6,0 ], [ 4, 8,0 ], [ 5, -1,1 ], # level=1 [ 6, 1,1 ], [ 7, 3,1 ], [ 8, 5,1 ], [ 9, 7,1 ], [ 10, -2,2 ], [ 11, 0,2 ], [ 12, 2,2 ], [ 13, 4,2 ], [ 14, 6,2 ], [ 24, 4,4 ], [ 25, -5,-5 ], # level=2 [ 125, 10,0 ], # level=3 0deg [ 625, -25,25 ], # level=4 120deg [ 3125, -25,-25 ], # level=5 240deg # [ .25, .# 25, 0 ], # [ 1.25, 1, .25 ], # [ 2.25, 0.75, 1 ], # [ 3.25, 0, 1.25 ], # # [ 4.25, 0, 2.25 ], # [ 5.25, .25, 3 ], # [ 6.25, 1, 2.75 ], # [ 7.25, 1.25, 2 ], # # [ 8.25, 2, 2.25 ], # [ 9.25, 2.25, 3 ], # [ 10.25, 3, 2.75 ], # [ 11.25, 3, 1.75 ], # # [ 12.25, 2.75, 1 ], # [ 13.25, 2, .75 ], # [ 14.25, 2.25, 0 ], # [ 15.25, 3.25, 0 ], # # [ 19.25, 5.25, 0 ], # [ 37.25, 7, 4.25 ], # # [ 31.25, 4, 3.25 ], # [ 127.25, 7.25, 8 ], # # [ 63.25, 0, 7.25 ], # [ 255.25, 15.25, 0 ], # [ 1023.25, 0, 31.25 ], ] ); foreach my $elem (@data) { my ($radix, @points) = @$elem; my $path = Math::PlanePath::CubicBase->new (radix => $radix); foreach my $point (@points) { my ($n, $want_x, $want_y) = @$point; my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $want_x, "n_to_xy() radix=$radix x at n=$n"); ok ($got_y, $want_y, "n_to_xy() radix=$radix y at n=$n"); } foreach my $point (@points) { my ($want_n, $x, $y) = @$point; next unless $want_n==int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } foreach my $point (@points) { my ($n, $x, $y) = @$point; my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); next unless $n==int($n); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } #------------------------------------------------------------------------------ # X axis claimed in the POD # wrong # =head2 Axis Values # # In the default radix=2 the N=0,1,8,9,etc on the positive X axis are those # integers with zeros at two of every three bits, starting from zeros in the # second and third least significant bit. # # X axis Ns = binary ..._00_00_00_ with _ either 0 or 1 # in octal, digits 0,1 only # # For a radix other than binary the pattern is the same. Each "_" is any # digit of the given radix, and each 0 must be 0. # foreach my $radix (2, 3, 4) { # my $path = Math::PlanePath::CubicBase->new (radix => $radix); # foreach my $x (0 .. 10) { # my $path_n = $path->xy_to_n (2*$x,0); # my $calc_n = calc_x_to_n($x,$radix); # ok ($calc_n, $path_n, # "calc_x_to_n() radix=$radix at x=$x"); # } # } # # sub calc_x_to_n { # my ($x, $radix) = @_; # my $n = 0; # my $power = 1; # my $grow = 0; # $x = index_to_negaradix($x,$radix*$radix); # foreach my $xdigit (digit_split_lowtohigh($x,$radix)) { # $n += $power*$xdigit; # $power *= $radix*$radix*$radix; # # if ($grow ^= 1) { # # $power *= $radix*$radix; # # } # } # return $n; # } # sub index_to_negaradix { # my ($n, $radix) = @_; # my $power = 1; # my $ret = 0; # while ($n) { # my $digit = $n % $radix; # low to high # $n = int($n/$radix); # $ret += $power * $digit; # $power *= -$radix; # } # return $ret; # } exit 0; Math-PlanePath-129/t/MyTestHelpers.pm0000644000175000017500000002521513244716044015271 0ustar gggg# MyTestHelpers.pm -- my shared test script helpers # Copyright 2008, 2009, 2010, 2011, 2012, 2015, 2017, 2018 Kevin Ryde # MyTestHelpers.pm is shared by several distributions. # # MyTestHelpers.pm 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, or (at your option) any later # version. # # MyTestHelpers.pm 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 file. If not, see . BEGIN { require 5 } package MyTestHelpers; use strict; # Don't want to load Exporter here since that could hide a problem of a # module missing a "use Exporter". Though Test.pm and Test::More (via # Test::Builder::Module) both use it anyway. # # use Exporter; # use vars qw(@ISA @EXPORT_OK %EXPORT_TAGS); # @ISA = ('Exporter'); # @EXPORT_OK = qw(findrefs # main_iterations # warn_suppress_gtk_icon # glib_gtk_versions # any_signal_connections # nowarnings); # %EXPORT_TAGS = (all => \@EXPORT_OK); sub DEBUG { 0 } #----------------------------------------------------------------------------- { my $warning_count; my $stacktraces; my $stacktraces_count = 0; sub nowarnings_handler { my ($msg) = @_; # don't error out for cpan alpha version number warnings unless (defined $msg && $msg =~ /^Argument "[0-9._]+" isn't numeric in numeric gt/) { $warning_count++; if ($stacktraces_count < 3 && eval { require Devel::StackTrace }) { $stacktraces_count++; $stacktraces .= "\n" . Devel::StackTrace->new->as_string() . "\n"; } } warn @_; } sub nowarnings { $SIG{'__WARN__'} = \&nowarnings_handler; } END { if ($warning_count) { MyTestHelpers::diag ("Saw $warning_count warning(s):"); if (defined $stacktraces) { MyTestHelpers::diag ($stacktraces); } else { MyTestHelpers::diag('(Devel::StackTrace not available for backtrace)'); } MyTestHelpers::diag ('Exit code 1 for warnings'); $? = 1; } } } sub diag { if (do { local $@; eval { Test::More->can('diag') }}) { Test::More::diag (@_); } else { my $msg = join('', map {defined($_)?$_:'[undef]'} @_)."\n"; $msg =~ s/^/# /mg; print STDERR $msg; } } sub dump { my ($thing) = @_; if (eval { require Data::Dumper; 1 }) { MyTestHelpers::diag (Data::Dumper::Dumper ($thing)); } else { MyTestHelpers::diag ("Data::Dumper not available"); } } #----------------------------------------------------------------------------- # Test::Weaken and other weaking sub findrefs { my ($obj) = @_; defined $obj or return; require Scalar::Util; if (ref $obj && Scalar::Util::reftype($obj) eq 'HASH') { MyTestHelpers::diag ("Keys: ", join(' ', map {"$_=".(defined $obj->{$_} ? "$obj->{$_}" : '[undef]')} keys %$obj)); } if (eval { require Devel::FindRef }) { MyTestHelpers::diag (Devel::FindRef::track($obj, 8)); } else { MyTestHelpers::diag ("Devel::FindRef not available -- ", $@); } } sub test_weaken_show_leaks { my ($leaks) = @_; $leaks || return; my $unfreed = $leaks->unfreed_proberefs; my $unfreed_count = scalar(@$unfreed); MyTestHelpers::diag ("Test-Weaken leaks $unfreed_count objects"); MyTestHelpers::dump ($leaks); my $proberef; foreach $proberef (@$unfreed) { MyTestHelpers::diag (" unfreed ", $proberef); } foreach $proberef (@$unfreed) { MyTestHelpers::diag ("search ", $proberef); MyTestHelpers::findrefs($proberef); } } #----------------------------------------------------------------------------- # Gtk/Glib helpers # Gtk 2.16 can go into a hard loop on events_pending() / main_iteration_do() # if dbus is not running, or something like that. In any case limiting the # iterations is good for test safety. # sub main_iterations { my $count = 0; if (DEBUG) { MyTestHelpers::diag ("main_iterations() ..."); } while (Gtk2->events_pending) { $count++; Gtk2->main_iteration_do (0); if ($count >= 500) { MyTestHelpers::diag ("main_iterations(): oops, bailed out after $count events/iterations"); return; } } MyTestHelpers::diag ("main_iterations(): ran $count events/iterations"); } # warn_suppress_gtk_icon() is a $SIG{__WARN__} handler which suppresses spam # from Gtk trying to make you buy the hi-colour icon theme. Eg, # # { # local $SIG{'__WARN__'} = \&MyTestHelpers::warn_suppress_gtk_icon; # $something = SomeThing->new; # } # sub warn_suppress_gtk_icon { my ($message) = @_; unless ($message =~ /Gtk-WARNING.*icon/ || $message =~ /\Qrecently-used.xbel/ ) { warn @_; } } sub glib_gtk_versions { my $gtk1_loaded = Gtk->can('init'); my $gtk2_loaded = Gtk2->can('init'); my $glib_loaded = Glib->can('get_home_dir'); if ($gtk1_loaded) { MyTestHelpers::diag ("Perl-Gtk1 version ",Gtk->VERSION); } if ($gtk2_loaded) { MyTestHelpers::diag ("Perl-Gtk2 version ",Gtk2->VERSION); } if ($glib_loaded) { # when loaded MyTestHelpers::diag ("Perl-Glib version ",Glib->VERSION); MyTestHelpers::diag ("Compiled against Glib version ", Glib::MAJOR_VERSION(), ".", Glib::MINOR_VERSION(), ".", Glib::MICRO_VERSION(), "."); MyTestHelpers::diag ("Running on Glib version ", Glib::major_version(), ".", Glib::minor_version(), ".", Glib::micro_version(), "."); } if ($gtk2_loaded) { MyTestHelpers::diag ("Compiled against Gtk version ", Gtk2::MAJOR_VERSION(), ".", Gtk2::MINOR_VERSION(), ".", Gtk2::MICRO_VERSION(), "."); MyTestHelpers::diag ("Running on Gtk version ", Gtk2::major_version(), ".", Gtk2::minor_version(), ".", Gtk2::micro_version(), "."); } if ($gtk1_loaded) { MyTestHelpers::diag ("Running on Gtk version ", Gtk->major_version(), ".", Gtk->minor_version(), ".", Gtk->micro_version(), "."); } } # Return true if there's any signal handlers connected to $obj. # # Signal IDs are from 1 up, don't pass 0 to signal_handler_is_connected() # since in Glib 2.4.1 it spits out a g_log() error. # sub any_signal_connections { my ($obj) = @_; my @connected = grep {$obj->signal_handler_is_connected ($_)} (1 .. 500); if (@connected) { my $connected = join(',',@connected); MyTestHelpers::diag ("$obj signal handlers connected: $connected"); return $connected; } return undef; } # wait for $signame to be emitted on $widget, with a timeout sub wait_for_event { my ($widget, $signame) = @_; if (DEBUG) { MyTestHelpers::diag ("wait_for_event() $signame on ",$widget); } my $done = 0; my $got_event = 0; my $sig_id = $widget->signal_connect ($signame => sub { if (DEBUG) { MyTestHelpers::diag ("wait_for_event() $signame received"); } $done = 1; return 0; # Gtk2::EVENT_PROPAGATE (new in Gtk2 1.220) }); my $timer_id = Glib::Timeout->add (30_000, # 30 seconds sub { $done = 1; MyTestHelpers::diag ("wait_for_event() oops, timeout waiting for $signame on ",$widget); return 1; # Glib::SOURCE_CONTINUE (new in Glib 1.220) }); if ($widget->can('get_display')) { # display new in Gtk 2.2 $widget->get_display->sync; } else { # in Gtk 2.0 gdk_flush() is a sync actually Gtk2::Gdk->flush; } my $count = 0; while (! $done) { if (DEBUG >= 2) { MyTestHelpers::diag ("wait_for_event() iteration $count"); } Gtk2->main_iteration; $count++; } MyTestHelpers::diag ("wait_for_event(): '$signame' ran $count events/iterations\n"); $widget->signal_handler_disconnect ($sig_id); Glib::Source->remove ($timer_id); } #----------------------------------------------------------------------------- # X11::Protocol helpers sub X11_chosen_screen_number { my ($X) = @_; my $i; foreach $i (0 .. $#{$X->{'screens'}}) { if ($X->{'screens'}->[$i]->{'root'} == $X->{'root'}) { return $i; } } die "Oops, current screen not found"; } sub X11_server_info { my ($X) = @_; MyTestHelpers::diag(""); MyTestHelpers::diag("X server info"); MyTestHelpers::diag("vendor: ",$X->{'vendor'}); MyTestHelpers::diag("release_number: ",$X->{'release_number'}); MyTestHelpers::diag("protocol_major_version: ",$X->{'protocol_major_version'}); MyTestHelpers::diag("protocol_minor_version: ",$X->{'protocol_minor_version'}); MyTestHelpers::diag("byte_order: ",$X->{'byte_order'}); MyTestHelpers::diag("num screens: ",scalar(@{$X->{'screens'}})); MyTestHelpers::diag("width_in_pixels: ",$X->{'width_in_pixels'}); MyTestHelpers::diag("height_in_pixels: ",$X->{'height_in_pixels'}); MyTestHelpers::diag("width_in_millimeters: ",$X->{'width_in_millimeters'}); MyTestHelpers::diag("height_in_millimeters: ",$X->{'height_in_millimeters'}); MyTestHelpers::diag("root_visual: ",$X->{'root_visual'}); my $visual_info = $X->{'visuals'}->{$X->{'root_visual'}}; MyTestHelpers::diag(" depth: ",$visual_info->{'depth'}); MyTestHelpers::diag(" class: ",$visual_info->{'class'}, ' ', $X->interp('VisualClass', $visual_info->{'class'})); MyTestHelpers::diag(" colormap_entries: ",$visual_info->{'colormap_entries'}); MyTestHelpers::diag(" bits_per_rgb_value: ",$visual_info->{'bits_per_rgb_value'}); MyTestHelpers::diag(" red_mask: ",sprintf('%#X',$visual_info->{'red_mask'})); MyTestHelpers::diag(" green_mask: ",sprintf('%#X',$visual_info->{'green_mask'})); MyTestHelpers::diag(" blue_mask: ",sprintf('%#X',$visual_info->{'blue_mask'})); MyTestHelpers::diag("ima"."ge_byte_order: ",$X->{'ima'.'ge_byte_order'}, ' ', $X->interp('Significance', $X->{'ima'.'ge_byte_order'})); MyTestHelpers::diag("black_pixel: ",sprintf('%#X',$X->{'black_pixel'})); MyTestHelpers::diag("white_pixel: ",sprintf('%#X',$X->{'white_pixel'})); foreach (0 .. $#{$X->{'screens'}}) { if ($X->{'screens'}->[$_]->{'root'} == $X->{'root'}) { MyTestHelpers::diag("chosen screen: $_"); } } MyTestHelpers::diag(""); } 1; __END__ Math-PlanePath-129/t/ComplexPlus.t0000644000175000017500000000703513734026647014632 0ustar gggg#!/usr/bin/perl -w # Copyright 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 31; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::ComplexPlus; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::ComplexPlus::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::ComplexPlus->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::ComplexPlus->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::ComplexPlus->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::ComplexPlus->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::ComplexPlus->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); ok ($path->class_x_negative, 1, 'class_x_negative() instance method'); ok ($path->class_y_negative, 1, 'class_y_negative() instance method'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::ComplexPlus->parameter_info_list; ok (join(',',@pnames), 'realpart,arms'); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::ComplexPlus->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 0); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 3); } } { my $path = Math::PlanePath::ComplexPlus->new (arms => 2); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 3); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 7); } } { my $path = Math::PlanePath::ComplexPlus->new (realpart => 2); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 0); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 4); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 24); } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/StaircaseAlternating.t0000644000175000017500000001430613734026642016460 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 37; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::StaircaseAlternating; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::StaircaseAlternating::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::StaircaseAlternating->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::StaircaseAlternating->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::StaircaseAlternating->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::StaircaseAlternating->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::StaircaseAlternating->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 0, 'x_negative() instance method'); ok ($path->y_negative, 0, 'y_negative() instance method'); } { # width not a parameter as such ... my @pnames = map {$_->{'name'}} Math::PlanePath::StaircaseAlternating->parameter_info_list; ok (join(',',@pnames), 'end_type,n_start'); } #------------------------------------------------------------------------------ # rect_to_n_range() 2x2 blocks foreach my $end_type ('jump', 'square') { my $path = Math::PlanePath::StaircaseAlternating->new(end_type=>$end_type); my $bad = 0; foreach my $x1 (0 .. 24) { foreach my $xblock (0, 1, 2, 10) { my $x2 = $x1 + $xblock; foreach my $y1 (0 .. 24) { foreach my $yblock (0, 1, 2, 10) { my $y2 = $y1 + $yblock; my @n_list = grep {defined} ($path->xy_to_n($x1,$y1), $path->xy_to_n($x2,$y2), ($xblock >= 1 ? ($path->xy_to_n($x1+1,$y1), $path->xy_to_n($x2-1,$y2)) : ()), ($xblock >= 2 ? ($path->xy_to_n($x1+2,$y1), $path->xy_to_n($x2-2,$y2)) : ()), ($yblock >= 1 ? ($path->xy_to_n($x1,$y1+1), $path->xy_to_n($x2,$y2-1)) : ()), ($yblock >= 2 ? ($path->xy_to_n($x1,$y1+2), $path->xy_to_n($x2,$y2-2)) : ())); my $want_nlo = Math::PlanePath::_min (@n_list); my $want_nhi = Math::PlanePath::_max (@n_list); if (! @n_list) { $want_nlo = 1; # crossed $want_nhi = 0; } my ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); unless ((defined $got_nlo == defined $want_nlo) && ($got_nlo||0) == ($want_nlo||0)) { if (! defined $got_nlo) { $got_nlo = 'undef'; } if (! defined $want_nlo) { $want_nlo = 'undef'; } MyTestHelpers::diag ("$end_type $x1,$y1 to $x2,$y2 want_nlo=$want_nlo got_nlo=$got_nlo"); $bad++; } unless ((defined $got_nhi == defined $want_nhi) && ($got_nhi||0) == ($want_nhi||0)) { if (! defined $got_nhi) { $got_nhi = 'undef'; } if (! defined $want_nhi) { $want_nhi = 'undef'; } MyTestHelpers::diag ("$end_type $x1,$y1 to $x2,$y2 want_nhi=$want_nhi got_nhi=$got_nhi"); $bad++; } } } } } ok ($bad, 0); } #------------------------------------------------------------------------------ # first few values, square { my @data = ([ 1, 0,0 ], [ 2, 0,1 ], [ 3, 1,1 ], [ 4, 1,0 ], [ 5, 2,0 ], [ 6, 3,0 ], ); my $path = Math::PlanePath::StaircaseAlternating->new (end_type=>'square'); foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next unless $want_n == int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } } #------------------------------------------------------------------------------ # xy_to_n() omitted points { my @data = ([ 0,2 ], [ 4,0 ], [ 0,6 ], [ 8,0 ], [ 0,10 ], [ 12,0 ], ); my $path = Math::PlanePath::StaircaseAlternating->new(end_type=>'square'); foreach my $elem (@data) { my ($x,$y) = @$elem; my $got_n = $path->xy_to_n ($x, $y); ok (! defined $got_n, 1, "square omitted $x,$y"); } } exit 0; Math-PlanePath-129/t/SquareSpiral.t0000644000175000017500000002615713734026642014773 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 482; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::SquareSpiral; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::SquareSpiral::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::SquareSpiral->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::SquareSpiral->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::SquareSpiral->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::SquareSpiral->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # cf formula in Graham, Knuth and Patashnik "Concrete Mathematics" page 99 sub Graham_Knuth_patashnik_x { my ($n) = @_; my $m = int(sqrt($n)); return (-1)**$m * ( ($n - $m*($m+1)) * is_even(int(2*sqrt($n))) + ceil(1/2*$m) ); } sub is_even { my ($n) = @_; return ($n % 2 == 0 ? 1 : 0); } sub ceil { my ($n) = @_; return ($n == int($n) ? $n : int($n)+1); } { my $path = Math::PlanePath::SquareSpiral->new (n_start => 0); foreach my $n (0 .. 100) { my $gkp_x = Graham_Knuth_patashnik_x($n); my ($x,$y) = $path->n_to_xy($n); ok (-$y == $gkp_x, 1); } } #---------------------------------------------------------------------------- # _UNDOCUMENTED__n_to_turn_LSR() { my $path = Math::PlanePath::SquareSpiral->new (n_start => 0); ok ($path->_UNDOCUMENTED__n_to_turn_LSR(-1), undef); ok ($path->_UNDOCUMENTED__n_to_turn_LSR(0), undef); ok ($path->_UNDOCUMENTED__n_to_turn_LSR(1), 1); ok ($path->_UNDOCUMENTED__n_to_turn_LSR(2), 1); ok ($path->_UNDOCUMENTED__n_to_turn_LSR(3), 0); } #------------------------------------------------------------------------------ # formulas in pod { my $path = Math::PlanePath::SquareSpiral->new; my $d = 3; my $Nbase = 4*$d**2 - 4*$d + 2; ok ($Nbase, 26); { my $N = $Nbase; my $dd = int (1/2 + sqrt($N/4 - 1/4)); ok ($dd, $d, 'd'); $dd = int ((1+sqrt($N-1)) / 2); ok ($dd, $d, 'd'); } { my $N = $Nbase + 8*$d-1; my $dd = int (1/2 + sqrt($N/4 - 1/4)); ok ($dd, $d, 'd'); $dd = int ((1+sqrt($N-1)) / 2); ok ($dd, $d, 'd'); } { my $N = $Nbase + 8*$d-1 + 1; my $dd = int (1/2 + sqrt($N/4 - 1/4)); ok ($dd, $d+1, 'd'); $dd = int ((1+sqrt($N-1)) / 2); ok ($dd, $d+1, 'd'); } # right upwards { my $Nrem = 0; my ($want_x,$want_y) = $path->n_to_xy($Nbase+$Nrem); ok ($d, $want_x, 'X'); ok (-$d+1+$Nrem, $want_y, 'Y'); } { my $Nrem = 2*$d-1; my ($want_x,$want_y) = $path->n_to_xy($Nbase+$Nrem); ok ($d, $want_x, 'X'); ok (-$d+1+$Nrem, $want_y, 'Y'); } # top { my $Nrem = 2*$d-1; my ($want_x,$want_y) = $path->n_to_xy($Nbase+$Nrem); ok (3*$d-1-$Nrem, $want_x, 'X'); ok ($d, $want_y, 'Y'); } { my $Nrem = 4*$d-1; my ($want_x,$want_y) = $path->n_to_xy($Nbase+$Nrem); ok (3*$d-1-$Nrem, $want_x, 'X'); ok ($d, $want_y, 'Y'); } # left downwards { my $Nrem = 4*$d-1; my ($want_x,$want_y) = $path->n_to_xy($Nbase+$Nrem); ok (-$d, $want_x, 'X'); ok (5*$d-1-$Nrem, $want_y, 'Y'); } { my $Nrem = 6*$d-1; my ($want_x,$want_y) = $path->n_to_xy($Nbase+$Nrem); ok (-$d, $want_x, 'X'); ok (5*$d-1-$Nrem, $want_y, 'Y'); } # bottom { my $Nrem = 6*$d-1; my ($want_x,$want_y) = $path->n_to_xy($Nbase+$Nrem); ok (-7*$d+1+$Nrem, $want_x, 'X'); ok (-$d, $want_y, 'Y'); } { my $Nrem = 8*$d; my ($want_x,$want_y) = $path->n_to_xy($Nbase+$Nrem); ok (-7*$d+1+$Nrem, $want_x, 'X'); ok (-$d, $want_y, 'Y'); } # right upwards my $Nzero = $Nbase + 4*$d-1; { my $Nsig = -(4*$d-1); my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok ($d, $want_x, 'X'); ok (3*$d+$Nsig, $want_y, 'Y'); } { my $Nsig = -2*$d; my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok ($d, $want_x, 'X'); ok (3*$d+$Nsig, $want_y, 'Y'); } # top { my $Nsig = -2*$d; my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok (-$d-$Nsig, $want_x, 'X'); ok ($d, $want_y, 'Y'); } { my $Nsig = 0; my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok (-$d-$Nsig, $want_x, 'X'); ok ($d, $want_y, 'Y'); } # left downwards { my $Nsig = 0; my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok (-$d, $want_x, 'X'); ok ($d-$Nsig, $want_y, 'Y'); } { my $Nsig = 2*$d; my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok (-$d, $want_x, 'X'); ok ($d-$Nsig, $want_y, 'Y'); } # bottom { my $Nsig = 2*$d; my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok ($Nsig-3*$d, $want_x, 'X'); ok (-$d, $want_y, 'Y'); } { my $Nsig = 4*$d+1; my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok ($Nsig-3*$d, $want_x, 'X'); ok (-$d, $want_y, 'Y'); } } #------------------------------------------------------------------------------ # formulas in pod -- wider { my $path = Math::PlanePath::SquareSpiral->new (wider => 7); my $d = 3; my $w = 7; my $Nbase = 4*$d**2 + (-4+2*$w)*$d + 2-$w; ok ($Nbase, 61); my $wl = int(($w+1)/2); # ceil my $wr = int($w/2); # floor ok ($wl, 4); ok ($wr, 3); { my $N = $Nbase; my $dd = int ((2-$w + sqrt(4*$N + $w**2 - 4)) / 4); ok ($dd, $d, 'd'); } { my $N = $Nbase + 8*$d+2*$w-1; my $dd = int ((2-$w + sqrt(4*$N + $w**2 - 4)) / 4); ok ($dd, $d, 'd'); } { my $N = $Nbase + 8*$d+2*$w-1 + 1; my $dd = int ((2-$w + sqrt(4*$N + $w**2 - 4)) / 4); ok ($dd, $d+1, 'd'); } # right upwards my $Nzero = $Nbase + 4*$d-1+$w; { my $Nsig = -(4*$d-1+$w); ok ($Nzero+$Nsig, $Nbase); my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok ($d+$wr, $want_x, 'X'); ok (3*$d+$w+$Nsig, $want_y, 'Y'); } { my $Nsig = -(2*$d+$w); my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok ($d+$wr, $want_x, 'X'); ok (3*$d+$w+$Nsig, $want_y, 'Y'); } # top { my $Nsig = -(2*$d+$w); my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok (-$d-$wl-$Nsig, $want_x, 'X'); ok ($d, $want_y, 'Y'); } { my $Nsig = 0; my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok (-$d-$wl-$Nsig, $want_x, 'X'); ok ($d, $want_y, 'Y'); } # left downwards { my $Nsig = 0; my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok (-$d-$wl, $want_x, 'X'); ok ($d-$Nsig, $want_y, 'Y'); } { my $Nsig = 2*$d; my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok (-$d-$wl, $want_x, 'X'); ok ($d-$Nsig, $want_y, 'Y'); } # bottom { my $Nsig = 2*$d; my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok ($Nsig-$wl-3*$d, $want_x, 'X'); ok (-$d, $want_y, 'Y'); } { my $Nsig = 4*$d+1+$w; my ($want_x,$want_y) = $path->n_to_xy($Nzero+$Nsig); ok ($Nsig-$wl-3*$d, $want_x, 'X'); ok (-$d, $want_y, 'Y'); } } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::SquareSpiral->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative() instance method'); ok ($path->y_negative, 1, 'y_negative() instance method'); ok ($path->class_x_negative, 1, 'class_x_negative()'); ok ($path->class_y_negative, 1, 'class_y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::SquareSpiral->parameter_info_list; ok (join(',',@pnames), 'wider,n_start'); } #------------------------------------------------------------------------------ # n_to_xy # 17 16 15 14 13 # 18 5 4 3 12 # 19 6 1 2 11 # 20 7 8 9 10 # 21 22 23 24 25 26 { my @data = ([ 1, 0,0 ], [ 2, 1,0 ], [ 3, 1,1 ], # top [ 4, 0,1 ], [ 5, -1,1 ], # left [ 6, -1,0 ], [ 7, -1,-1 ], # bottom [ 8, 0,-1 ], [ 9, 1,-1 ], [ 10, 2,-1 ], # right [ 11, 2, 0 ], [ 12, 2, 1 ], [ 13, 2,2 ], # top [ 14, 1,2 ], [ 15, 0,2 ], [ 16, -1,2 ], [ 17, -2, 2 ], # left [ 18, -2, 1 ], [ 19, -2, 0 ], [ 20, -2,-1 ], [ 21, -2,-2 ], # bottom [ 22, -1,-2 ], [ 23, 0,-2 ], [ 24, 1,-2 ], [ 25, 2,-2 ], [ 26, 3,-2 ], # right [ 27, 3,-1 ], ); my $path = Math::PlanePath::SquareSpiral->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } } #------------------------------------------------------------------------------ # random n_to_dxdy() { foreach my $wider (0 .. 10) { my $path = Math::PlanePath::SquareSpiral->new (wider => $wider); # for (my $n = 1.25; $n < 40; $n++) { foreach (1 .. 10) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my ($next_x,$next_y) = $path->n_to_xy ($n+1); my $delta_dx = $next_x - $x; my $delta_dy = $next_y - $y; my ($func_dx,$func_dy) = $path->n_to_dxdy($n); if ($func_dx == 0) { $func_dx = '0'; } # avoid -0 in perl 5.6 if ($func_dy == 0) { $func_dy = '0'; } # avoid -0 in perl 5.6 ok ($func_dx, $delta_dx, "n_to_dxdy($n) wider=$wider dx at xy=$x,$y"); ok ($func_dy, $delta_dy, "n_to_dxdy($n) wider=$wider dy at xy=$x,$y"); } } } exit 0; Math-PlanePath-129/t/KochSnowflakes.t0000644000175000017500000001420013734026644015265 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 158; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments; require Math::PlanePath::KochSnowflakes; my $path = Math::PlanePath::KochSnowflakes->new; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::KochSnowflakes::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::KochSnowflakes->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::KochSnowflakes->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::KochSnowflakes->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); ok ($path->class_x_negative, 1, 'class_x_negative()'); ok ($path->class_y_negative, 1, 'class_y_negative()'); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::KochSnowflakes->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 1); ok ($n_hi, 3); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 4); ok ($n_hi, 15); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 16); ok ($n_hi, 63); } foreach my $level (1 .. 6) { my ($n_lo,$n_hi) = $path->level_to_n_range($level); my ($x_lo,$y_lo) = $path->n_to_xy($n_lo); my ($x_hi,$y_hi) = $path->n_to_xy($n_hi); ### ends: "$x_lo,$y_lo to $x_hi,$y_hi" my $dx = $x_hi - $x_lo; my $dy = $y_hi - $y_lo; ok($dx,1, "snowflake first to last dx $x_lo to $x_hi level=$level"); ok($dy,1, "snowflake first to last dy $y_lo to $y_hi level=$level"); } } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::KochSnowflakes->new; foreach my $elem ( [-1,-5, -1,-5, 1,63 ], # N=23 only [-2,-5, -1,-5, 1,63 ], # N=23 and point beside it [0,0, 0,0, 1,15], # origin only [-9,-3, -9,-3, 1,63], # N=16 only [0,0, -9,-3, 1,63], # taking in N=16 ) { my ($x1,$y1,$x2,$y2, $want_lo, $want_hi) = @$elem; my ($got_lo, $got_hi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); ok ($got_lo, $want_lo, "lo on $x1,$y1 $x2,$y2"); ok ($got_hi, $want_hi, "hi on $x1,$y1 $x2,$y2"); } } #------------------------------------------------------------------------------ # first few points { my @data = ( [ 4.5, -2,-1 ], [ 5.5, -.5,-1.5 ], [ 4, -3,-1 ], [ 5, -1,-1 ], [ 6, 0,-2 ], [ 17, -7,-3 ], ); my $path = Math::PlanePath::KochSnowflakes->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "n_to_xy() x at n=$n"); ok ($got_y, $want_y, "n_to_xy() y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next unless $want_n==int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "xy_to_n($x,$y) N"); } foreach my $elem (@data) { my ($n, $x, $y) = @$elem; $n = int($n+.5); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } #------------------------------------------------------------------------------ # xy_to_n_list() { my @data = ( [ -5, -1, [] ], [ -5, -2, [] ], [ -1, 0, [1] ], [ -1, -.333, [1] ], [ -1, -.5, [1] ], [ -1, -.6, [1,5] ], [ -1, -1, [5] ], [ 1, 0, [2] ], [ 1, -.333, [2] ], [ 1, -.5, [2] ], [ 1, -.6, [2,7] ], [ 1, -1, [7] ], [ 0, .666, [3] ], [ 0, 1, [3] ], [ 0, .5, [3] ], [ 0, -1, [] ], [ 8, 0, [] ], [ 9, 0, [] ], ); foreach my $elem (@data) { my ($x,$y, $want_n_aref) = @$elem; my $want_n_str = join(',', @$want_n_aref); { my @got_n_list = $path->xy_to_n_list($x,$y); ok (scalar(@got_n_list), scalar(@$want_n_aref), "xy_to_n_list($x,$y) count"); my $got_n_str = join(',', @got_n_list); ok ($got_n_str, $want_n_str, "xy_to_n_list($x,$y) values"); } { my $got_n = $path->xy_to_n($x,$y); ok ($got_n, $want_n_aref->[0], "xy_to_n($x,$y) first of list"); } { my @got_n = $path->xy_to_n($x,$y); ok (scalar(@got_n), 1); ok ($got_n[0], $want_n_aref->[0]); } } } exit 0; Math-PlanePath-129/t/TerdragonMidpoint.t0000644000175000017500000000735613734026642016011 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 39; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::TerdragonMidpoint; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::TerdragonMidpoint::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::TerdragonMidpoint->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::TerdragonMidpoint->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::TerdragonMidpoint->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::TerdragonMidpoint->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::TerdragonMidpoint->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 0); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 2); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 8); } { my ($n_lo,$n_hi) = $path->level_to_n_range(3); ok ($n_lo, 0); ok ($n_hi, 26); } } { my $path = Math::PlanePath::TerdragonMidpoint->new (arms => 5); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 4); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 14); } # 5*3 - 1 = 14 { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 44); } # 5*9 - 1 = 44 } #------------------------------------------------------------------------------ # xy_to_n() { my $path = Math::PlanePath::TerdragonMidpoint->new; foreach my $elem ( [ -1,0, undef ], [ 0,0, undef ], [ 1,0, undef ], [ 2,0, 0 ], [ 3,0, undef ], [ -1,1, undef ], [ 0,1, undef ], [ 1,1, undef ], [ 2,1, undef ], [ 3,1, 1 ], [ 4,1, undef ], [ -1,2, undef ], [ 0,2, undef ], [ 1,2, undef ], [ 2,2, undef ], [ 3,2, undef ], [ 4,2, 2 ], [ 5,2, undef ], ) { my ($x,$y, $want_n) = @$elem; my $got_n = $path->xy_to_n ($x,$y); ok ($got_n, $want_n, "xy_to_n($x,$y)"); } } exit 0; Math-PlanePath-129/t/Flowsnake.t0000644000175000017500000001563413734026646014313 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 389; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::Flowsnake; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::Flowsnake::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::Flowsnake->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::Flowsnake->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::Flowsnake->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::Flowsnake->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::Flowsnake->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::Flowsnake->parameter_info_list; ok (join(',',@pnames), 'arms'); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::Flowsnake->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 7); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 49); } } { my $path = Math::PlanePath::Flowsnake->new (arms => 2); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 2); } # 7^0+1 + 7^0 - 1 = 2 { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 14); } # 7^1+1 + 7^1 - 1 = 14 { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 98); } # 7^2+1 + 7^2 - 1 = 98 } { my $path = Math::PlanePath::Flowsnake->new (arms => 3); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 3); } # 7^0+1 + 2*7^0 - 1 = 3 { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 21); } # 7^1+1 + 2*7^1 - 1 = 21 } #------------------------------------------------------------------------------ # first few points { my @data = ( # arms=1 [ 1, 0, 0,0 ], [ 1, 1, 2,0 ], [ 1, 2, 3,1 ], [ 1, 3, 1,1 ], [ 1, 4, 0,2 ], [ 1, 5, 2,2 ], [ 1, 6, 4,2 ], [ 1, 7, 5,1 ], [ 1, 8, 7,1 ], [ 1, 9, 8,2 ], [ 1, .25, .5, 0 ], [ 1, .5, 1, 0 ], [ 1, 1.75, 2.75, .75 ], # arms=2 [ 2, 46, -2,4 ], [ 2, 1, -1,1 ], ); foreach my $elem (@data) { my ($arms, $n, $x, $y) = @$elem; my $path = Math::PlanePath::Flowsnake->new (arms => $arms); { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "arms=$arms n_to_xy() x at n=$n"); ok ($got_y, $y, "arms=$arms n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } if ($n == int($n)) { { my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range(0,0,$x,$y) arms=$arms for n=$n, got_nlo=$got_nlo"); ok ($got_nhi >= $n, 1, "rect_to_n_range(0,0,$x,$y) arms=$arms for n=$n, got_nhi=$got_nhi"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range($x,$y,$x,$y) arms=$arms for n=$n, got_nlo=$got_nlo"); ok ($got_nhi >= $n, 1, "rect_to_n_range($x,$y,$x,$y) arms=$arms for n=$n, got_nhi=$got_nhi"); } } } } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::Flowsnake->new; my ($n_lo, $n_hi) = $path->rect_to_n_range(0,0, 0,0); ok ($n_lo == 0, 1, "rect_to_n_range() 0,0 n_lo=$n_lo"); ok ($n_hi >= 0, 1, "rect_to_n_range() 0,0 n_hi=$n_hi"); } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::Flowsnake->new; for (1 .. 20) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($n) random frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($n) random frac $frac, y"); } } } #------------------------------------------------------------------------------ # random points { my $path = Math::PlanePath::Flowsnake->new; for (1 .. 50) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } ok ($rev_n, $n, "xy_to_n($x,$y) reverse to expect n=$n, got $rev_n"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() reverse n=$n cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() reverse n=$n cf got n_hi=$n_hi"); } } exit 0; Math-PlanePath-129/t/SacksSpiral.t0000644000175000017500000001461413734026643014573 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 153; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::SacksSpiral; sub numeq_array { my ($a1, $a2) = @_; if (! ref $a1 || ! ref $a2) { return 0; } my $i = 0; while ($i < @$a1 && $i < @$a2) { if ($a1->[$i] ne $a2->[$i]) { return 0; } $i++; } return (@$a1 == @$a2); } #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::SacksSpiral::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::SacksSpiral->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::SacksSpiral->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::SacksSpiral->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::SacksSpiral->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::SacksSpiral->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative() instance method'); ok ($path->y_negative, 1, 'y_negative() instance method'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::SacksSpiral->parameter_info_list; ok (join(',',@pnames), ''); } #------------------------------------------------------------------------------ # n_to_rsquared() { my $path = Math::PlanePath::SacksSpiral->new; ok ($path->n_to_rsquared(0), 0); ok ($path->n_to_rsquared(1), 1); ok ($path->n_to_rsquared(20.5), 20.5); } #------------------------------------------------------------------------------ # xy_to_n { my @data = ([ 0,0, [0] ], [ 0.001,0.001, [0] ], [ -0.001,0.001, [0] ], [ 0.001,-0.001, [0] ], [ -0.001,-0.001, [0] ], ); my $path = Math::PlanePath::SacksSpiral->new; foreach my $elem (@data) { my ($x, $y, $want_n_aref) = @$elem; my @got_n = $path->xy_to_n ($x,$y); ### @got_n ok (numeq_array (\@got_n, $want_n_aref), 1, "xy_to_n x=$x y=$y"); } } #------------------------------------------------------------------------------ # _rect_to_radius_range() { foreach my $elem ( # single isolated point [ 0,0, 0,0, 0,0 ], [ 1,0, 1,0, 1,1 ], [ -1,0, -1,0, 1,1 ], [ 0,1, 0,1, 1,1 ], [ 0,-1, 0,-1, 1,1 ], [ 0,0, 1,0, 0,1 ], # strip of x axis [ 1,0, 0,0, 0,1 ], [ 6,0, 3,0, 3,6 ], [ -6,0, -3,0, 3,6 ], [ -6,0, 3,0, 0,6 ], [ 6,0, -3,0, 0,6 ], [ 0,0, 0,1, 0,1 ], # strip of y axis [ 0,1, 0,0, 0,1 ], [ 0,6, 0,3, 3,6 ], [ 0,-6, 0,3, 0,6 ], [ 0,-6, 0,-3, 3,6 ], [ 0,6, 0,-3, 0,6 ], [ 3,1, -3,4, 1,5 ], [ -3,1, 3,4, 1,5 ], [ -3,4, 3,1, 1,5 ], [ 3,4, -3,1, 1,5 ], [ 1,3, 4,-3, 1,5 ], [ 1,-3, 4,3, 1,5 ], [ 4,-3, 1,3, 1,5 ], [ 4,3, 1,-3, 1,5 ], [ -3,-4, 3,4, 0,5 ], [ 3,-4, -3,4, 0,5 ], [ 3,4, -3,-4, 0,5 ], [ -3,4, 3,-4, 0,5 ], [ 0,0, 3,4, 0,5 ], [ 0,0, 3,-4, 0,5 ], [ 0,0, -3,4, 0,5 ], [ 0,0, -3,-4, 0,5 ], [ 6,8, 3,4, 5,10 ], [ 6,8, -3,-4, 0,10 ], [ -6,-8, 3,4, 0,10 ], [ -3,0, 3,4, 0,5 ], [ 0,-3, 4,3, 0,5 ], [ -6,1, 6,8, 1,10 ], # x both, y positive [ -6,-1, 6,-8, 1,10 ], # x both, y negative [ 1,-6, 8,6, 1,10 ], # y both, x positive [ -1,-6, -8,6, 1,10 ], # y both, x negative ) { ## no critic (ProtectPrivateSubs) my ($x1,$y1, $x2,$y2, $want_rlo,$want_rhi) = @$elem; my ($got_rlo,$got_rhi) = Math::PlanePath::SacksSpiral::_rect_to_radius_range ($x1,$y1, $x2,$y2); my $name = "_rect_to_radius_range() $x1,$y1, $x2,$y2"; ok ($got_rlo, $want_rlo, "$name, r lo"); ok ($got_rhi, $want_rhi, "$name, r hi"); } } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::SacksSpiral->new; foreach my $n (1 .. 50) { my ($x, $y) = $path->n_to_xy($n); my $x1 = 0; my $y1 = 0; my ($x2, $y2) = vector_towards_origin($x,$y,0.49); my ($got_n_lo, $got_n_hi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); ### @got_n_lo ### @got_n_hi ok ($got_n_hi >= $n, 1, "hi want=$n got=$got_n_hi, at xy=$x,$y and range $x2,$y2"); } } sub vector_towards_origin { my ($x,$y, $dist) = @_; my $r = sqrt($x*$x+$y*$y); my $frac = ($r-$dist)/$r; return ($x * $frac, $y * $frac); } exit 0; Math-PlanePath-129/t/CornerReplicate.t0000644000175000017500000001341213734026647015434 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 396; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::CornerReplicate; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::CornerReplicate::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::CornerReplicate->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::CornerReplicate->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::CornerReplicate->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::CornerReplicate->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::CornerReplicate->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); ok ($path->class_x_negative, 0, 'class_x_negative() instance method'); ok ($path->class_y_negative, 0, 'class_y_negative() instance method'); } #------------------------------------------------------------------------------ # first few points { my @data = ( [ 0, 0,0 ], [ 1, 1,0 ], [ 2, 1,1 ], [ 3, 0,1 ], [ 4, 2,0 ], [ 5, 3,0 ], [ 6, 3,1 ], [ 7, 2,1 ], [ 8, 2,2 ], [ 9, 3,2 ], [ 10, 3,3 ], [ 11, 2,3 ], [ 12, 0,2 ], [ 13, 1,2 ], [ 14, 1,3 ], [ 15, 0,3 ], # [ .25, .25, 0 ], # [ .5, .5, 0 ], # [ 1.75, 1, -.75 ], ); foreach my $elem (@data) { my ($n, $x, $y) = @$elem; my $path = Math::PlanePath::CornerReplicate->new; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } if ($n == int($n)) { { my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range(0,0,$x,$y) for n=$n, got_nlo=$got_nlo"); ok ($got_nhi >= $n, 1, "rect_to_n_range(0,0,$x,$y) for n=$n, got_nhi=$got_nhi"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($got_nlo, $n, "rect_to_n_range($x,$y,$x,$y) for n=$n, got_nlo=$got_nlo"); ok ($got_nhi, $n, "rect_to_n_range($x,$y,$x,$y) for n=$n, got_nhi=$got_nhi"); } } } } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::CornerReplicate->new; my ($n_lo, $n_hi) = $path->rect_to_n_range(0,0, 0,0); ok ($n_lo == 0, 1, "rect_to_n_range() 0,0 n_lo=$n_lo"); ok ($n_hi >= 0, 1, "rect_to_n_range() 0,0 n_hi=$n_hi"); } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::CornerReplicate->new; for (1 .. 20) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($n) frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($n) frac $frac, y"); } } } #------------------------------------------------------------------------------ # random points { my $path = Math::PlanePath::CornerReplicate->new; for (1 .. 50) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } ok ($rev_n, $n, "xy_to_n($x,$y) reverse to expect n=$n, got $rev_n"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo, $n, "rect_to_n_range() reverse n=$n cf got n_lo=$n_lo"); ok ($n_hi, $n, "rect_to_n_range() reverse n=$n cf got n_hi=$n_hi"); } } exit 0; Math-PlanePath-129/t/PixelRings.t0000644000175000017500000000712513734026644014440 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 15; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::PixelRings; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::PixelRings::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::PixelRings->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::PixelRings->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::PixelRings->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::PixelRings->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::PixelRings->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); ok ($path->class_x_negative, 1, 'class_x_negative()'); ok ($path->class_y_negative, 1, 'class_y_negative()'); ok ($path->n_frac_discontinuity, 0, 'n_frac_discontinuity()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::PixelRings->parameter_info_list; ok (join(',',@pnames), ''); } #------------------------------------------------------------------------------ # xy_to_n() diagonals { my $path = Math::PlanePath::PixelRings->new; my $bad = 0; for (my $i = 0; $i < 3000; $i++) { my $n = $path->xy_to_n ($i, $i) or next; my ($x,$y) = $path->n_to_xy ($n); my $got = "$x,$y"; my $want = "$i,$i"; if ($got ne $want) { MyTestHelpers::diag ("xy_to_n() wrong on diagonal $i,$i n=$n vs $x,$y"); if ($bad++ > 10) { last; } } } ok ($bad, 0); } # { # my $path = Math::PlanePath::PixelRings->new; # my %xy_to_n; # ### n range: $path->rect_to_n_range (-60,-60, 60,60) # my ($n_lo, $n_hi) = $path->rect_to_n_range (-60,-60, 60,60); # foreach my $n ($n_lo .. $n_hi) { # my ($x,$y) = $path->n_to_xy($n); # my $key = "$x,$y"; # if ($xy_to_n{$key}) { # die "Oops, n_to_xy repeat $x,$y: was $xy_to_n{$key} now $n too"; # } # $xy_to_n{$key} = $n; # ### n_to_xy gives: "$x,$y -> $n" # } # ### total: scalar(%xy_to_n) # foreach my $x (0 .. 60, -60 .. -1) { # foreach my $y (0 .. 60, -60 .. -1) { # ok ($path->xy_to_n($x,$y), $xy_to_n{"$x,$y"}, # "xy_to_n($x,$y)"); # } # } # } exit 0; Math-PlanePath-129/t/Staircase.t0000644000175000017500000000713313734026642014267 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 76; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::Staircase; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::Staircase::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::Staircase->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::Staircase->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::Staircase->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::Staircase->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::Staircase->new (height => 123); ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 0, 'x_negative() instance method'); ok ($path->y_negative, 0, 'y_negative() instance method'); } { # width not a parameter as such ... my @pnames = map {$_->{'name'}} Math::PlanePath::Staircase->parameter_info_list; ok (join(',',@pnames), 'n_start'); } #------------------------------------------------------------------------------ # first few values { my @data = ([ 0.75, -0.25,0 ], [ 1, 0,0 ], [ 1.25, 0,-0.25 ], [ 1.75, -0.25, 2 ], [ 2, 0,2 ], [ 2.25, 0, 1.75 ], [ 2.75, 0, 1.25 ], [ 3, 0,1 ], [ 3.25, 0.25, 1 ], [ 4, 1,1 ], [ 4.25, 1,0.75 ], [ 5, 1,0 ], [ 5.25, 1.25, 0 ], [ 6, 2,0 ], [ 6.25, 2, -0.25 ], [ 6.75, -0.25, 4 ], [ 7, 0,4 ], [ 8, 0,3 ], [ 9, 1,3 ], [ 10, 1,2 ], [ 11, 2,2 ], [ 12, 2,1 ], [ 13, 3,1 ], [ 14, 3,0 ], [ 15, 4,0 ], ); my $path = Math::PlanePath::Staircase->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next unless $want_n == int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } } exit 0; Math-PlanePath-129/t/SquareReplicate.t0000644000175000017500000002324513734026643015445 0ustar gggg#!/usr/bin/perl -w # Copyright 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 73; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::SquareReplicate; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::SquareReplicate::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::SquareReplicate->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::SquareReplicate->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::SquareReplicate->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::SquareReplicate->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::SquareReplicate->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } #------------------------------------------------------------------------------ # _digits_rotate_lowtohigh() sub add_to_digit { my ($digit, $add) = @_; if ($digit) { $digit = ((($digit-1) + $add) % 8) + 1; } return $digit; } sub digits_rotate_lowtohigh_by_successive { my ($aref, $numbering_type) = @_; my @new = @$aref; foreach my $i (0 .. $#$aref) { if ($aref->[$i]) { my $add = $aref->[$i] - 1; if ($numbering_type eq 'rotate-4') { $add -= ($add & 1); } foreach my $j (0 .. $i-1) { # below $i $new[$j] = add_to_digit($new[$j], $add); } } } @$aref = @new; } { foreach my $elem ( [ 'rotate-8', [], [] ], [ 'rotate-8', [0], [0] ], [ 'rotate-8', [8], [8] ], [ 'rotate-8', [0,1], [0,1] ], # low to high [ 'rotate-8', [1,1], [1,1] ], [ 'rotate-8', [8,1], [8,1] ], [ 'rotate-8', [1,2], [2,2] ], [ 'rotate-8', [3,2], [4,2] ], [ 'rotate-8', [8,2], [1,2] ], [ 'rotate-8', [1,3], [3,3] ], [ 'rotate-8', [3,3], [5,3] ], [ 'rotate-8', [8,3], [2,3] ], [ 'rotate-4', [], [] ], [ 'rotate-4', [0], [0] ], [ 'rotate-4', [1], [1] ], [ 'rotate-4', [0,1], [0,1] ], [ 'rotate-4', [1,1], [1,1] ], [ 'rotate-4', [8,1], [8,1] ], [ 'rotate-4', [1,2], [1,2] ], [ 'rotate-4', [3,2], [3,2] ], [ 'rotate-4', [8,2], [8,2] ], [ 'rotate-4', [1,3], [3,3] ], [ 'rotate-4', [3,3], [5,3] ], [ 'rotate-4', [8,3], [2,3] ], [ 'rotate-4', [1,4], [3,4] ], [ 'rotate-4', [3,4], [5,4] ], [ 'rotate-4', [8,4], [2,4] ], [ 'rotate-4', [1,5], [5,5] ], [ 'rotate-4', [3,5], [7,5] ], [ 'rotate-4', [8,5], [4,5] ], ) { my ($numbering_type, $digits, $rotated) = @$elem; my $self = Math::PlanePath::SquareReplicate->new (numbering_type => $numbering_type); { my @got = @$digits; Math::PlanePath::SquareReplicate::_digits_rotate_lowtohigh($self, \@got); ok(join(',',@got),join(',',@$rotated), "_digits_rotate_lowtohigh $numbering_type of ".join(',',@$digits)); } ($digits,$rotated) = ($rotated,$digits); { my @got = @$digits; Math::PlanePath::SquareReplicate::_digits_unrotate_lowtohigh($self,\@got); ok(join(',',@got),join(',',@$rotated), "_digits_unrotate_lowtohigh $numbering_type of ".join(',',@$digits)); } } } { # rotate reversible foreach my $numbering_type ('rotate-4','rotate-8') { my $self = Math::PlanePath::SquareReplicate->new (numbering_type => $numbering_type); my $bad = 0; foreach my $n (0 .. 9**4) { my @digits = Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,9); my $digits = join(',',reverse @digits); my @rot = @digits; Math::PlanePath::SquareReplicate::_digits_rotate_lowtohigh($self,\@rot); my $rot = join(',',reverse @rot); my @rot_by_successive = @digits; digits_rotate_lowtohigh_by_successive(\@rot_by_successive, $numbering_type); my $rot_by_successive = join(',',reverse @rot_by_successive); unless ($rot eq $rot_by_successive) { MyTestHelpers::diag("$numbering_type n=$n digits=$digits rot=$rot rot_by_successive=$rot_by_successive"); if (++$bad >= 5) { last; } } my @unrot_again = @rot; Math::PlanePath::SquareReplicate::_digits_unrotate_lowtohigh($self,\@unrot_again); my $unrot_again = join(',',reverse @unrot_again); if ($digits ne $unrot_again) { MyTestHelpers::diag("n=$n digits=$digits rot=$rot unrot_again=$unrot_again"); if (++$bad >= 1) { last; } } } ok($bad,0); } } #------------------------------------------------------------------------------ # Boundary { # 40 39 38 31 30 29 22 21 20 # 41 36 37 32 27 28 23 18 19 # 42 43 44 33 34 35 24 25 26 # 49 48 47 4 3 2 13 12 11 # 50 45 46 5 0 1 14 9 10 # 51 52 53 6 7 8 15 16 17 # 58 57 56 67 66 65 76 75 74 # 59 54 55 68 63 64 77 72 73 # 60 61 62 69 70 71 78 79 80 sub Bpred_by_path { my ($path, $n, $k) = @_; my ($x,$y) = $path->n_to_xy($n); my $m = (3**$k-1)/2; return (abs($x) == $m || abs($y) == $m); } # 0 1 2 3 4 5 6 7 8 my @Bpred_rotate4_state_table = ([4, 1,2,1,2, 1,2,1, 2], # 0=all [4, 1,1,4,4, 4,4,4, 3], # 1=A [4, 1,2,1,1, 4,4,4, 3], # 2=AB [4, 4,3,1,1, 4,4,4, 4], # 3=B [4, 4,4,4,4, 4,4,4, 4]); # 4=non sub Bpred_rotate4_by_states { my ($path, $n, $k) = @_; my @digits = Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,9); while (@digits < $k) { push @digits, 0; } my $state = 0; foreach my $digit (reverse @digits) { # high to low $state = $Bpred_rotate4_state_table[$state]->[$digit]; } return $state != 4; } # non-boundary per POD docs # ignore 2s in 2nd or later # 0 anywhere # 5,6,7 2nd or later # pair 13,33,53,73 or 14,34,54,74 anywhere # pair 43,44 or 81,88 in 2nd or later sub Bpred_rotate4_by_digits { my ($path, $n, $k) = @_; my @digits = reverse # high to low Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,9); while (@digits < $k) { unshift @digits, 0; } # pad to $k { my $i = 1; # delete all 2s in 2nd or later while ($i <= $#digits) { if ($digits[$i] == 2) { splice @digits, $i, 1; } else { $i++; } } } foreach my $i (0 .. $#digits) { # 0 anywhere if ($digits[$i] == 0) { return 0; } } foreach my $i (1 .. $#digits) { # 5,6,7 in 2nd or later if ($digits[$i]==5 || $digits[$i]==6 || $digits[$i]==7) { return 0; } } foreach my $i (0 .. $#digits-1) { # pair 13,33,53,73, 14,34,54,74 anywhere if (($digits[$i]==1 || $digits[$i]==3 || $digits[$i]==5 || $digits[$i]==7) && ($digits[$i+1]==3 || $digits[$i+1]==4)) { return 0; } } foreach my $i (1 .. $#digits-1) { # pair 43,44, 81,88 in 2nd or later digit if (($digits[$i]==4 && ($digits[$i+1]==3 || $digits[$i+1]==4)) || ($digits[$i]==8 && ($digits[$i+1]==1 || $digits[$i+1]==8)) ) { return 0; } } return 1; } my $path = Math::PlanePath::SquareReplicate->new (numbering_type => 'rotate-4'); my $bad = 0; foreach my $k (0 .. 4) { my ($n_lo, $n_hi) = $path->level_to_n_range($k); foreach my $n ($n_lo .. $n_hi) { my $by_states = Bpred_rotate4_by_states($path,$n,$k) ? 1 : 0; my $by_path = Bpred_by_path($path,$n,$k) ? 1 : 0; my $by_digits = Bpred_rotate4_by_digits($path,$n,$k) ? 1 : 0; if ($by_states != $by_path || $by_states != $by_digits) { my @n = Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,9); my $n9 = join(',',reverse @n); MyTestHelpers::diag ("wrong Bpred k=$k n=$n [$n9] by_path $by_path by_states $by_states by_digits $by_digits"); last if ++$bad > 10; } } } ok($bad,0); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/bigint.t0000644000175000017500000000325013246362472013623 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2015, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; use lib 't'; use MyTestHelpers; my $test_count = (tests => 681)[1]; plan tests => $test_count; require Math::BigInt; MyTestHelpers::diag ('Math::BigInt version ', Math::BigInt->VERSION); { my $n = Math::BigInt->new(2) ** 256; my $int = int($n); if (! ref $int) { MyTestHelpers::diag ('skip due to Math::BigInt no "int()" operator'); foreach (1 .. $test_count) { skip ('due to no Math::BigInt int() operator', 1, 1); } exit 0; } } { # as_oct() used by digit_split_lowtohigh(), and code doesn't adapt itself # at runtime my $n = Math::BigInt->new(123); if (! $n->can('as_oct')) { MyTestHelpers::diag ('skip due to Math::BigInt no "as_oct()" method'); foreach (1 .. $test_count) { skip ('due to no Math::BigInt as_oct()', 1, 1); } exit 0; } } BEGIN { MyTestHelpers::nowarnings(); } require bigint_common; bigint_common::bigint_checks ('Math::BigInt'); exit 0; Math-PlanePath-129/t/LTiling.t0000644000175000017500000000677213734026644013725 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 318; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::LTiling; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::LTiling::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::LTiling->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::LTiling->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::LTiling->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::LTiling->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::LTiling->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); } #------------------------------------------------------------------------------ # centre diagonal is 0,2 digits in base 4 { my $path = Math::PlanePath::LTiling->new; for my $i (0 .. 50) { my $n = $path->xy_to_n ($i,$i); my $bits = 0; my $pos = 0; while ($n) { my $digit = $n % 4; $n = int($n/4); ok ($digit == 0 || $digit == 2); $bits |= ($digit==2) << $pos++; } ok ($bits, $i); } } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::LTiling->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 0); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 3); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 15); } } { my $path = Math::PlanePath::LTiling->new (L_fill => 'ends'); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 7); } } { my $path = Math::PlanePath::LTiling->new (L_fill => 'all'); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 2); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 11); } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/DragonRounded.t0000644000175000017500000001573213734026646015114 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 652; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::DragonRounded; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::DragonRounded::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::DragonRounded->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::DragonRounded->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::DragonRounded->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::DragonRounded->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::DragonRounded->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 3); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 7); } } { my $path = Math::PlanePath::DragonRounded->new (arms => 4); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 7); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 15); } } #------------------------------------------------------------------------------ # xy_to_n() reversal foreach my $arms (1 .. 4) { my $path = Math::PlanePath::DragonRounded->new (arms => $arms); for my $x (0 .. 5) { for my $y (0 .. 5) { my $n = $path->xy_to_n ($x,$y); next if ! defined $n; my ($nx,$ny) = $path->n_to_xy ($n); ok ($nx, $x, "arms=$arms xy=$x,$y n=$n cf nxy=$nx,$ny"); ok ($ny, $y, "arms=$arms xy=$x,$y n=$n cf nxy=$nx,$ny"); } } } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::DragonRounded->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::DragonRounded->parameter_info_list; ok (join(',',@pnames), 'arms'); } #------------------------------------------------------------------------------ # first few points { my @data = ( [ 2, [ 0, 1,0 ], ], [ 3, [ 0, 1,0 ], ], [ 4, [ 0, 1,0 ], ], [ 1, [ 0, 1,0 ], [ 1, 2,0 ], [ 2, 3,1 ], [ 3, 3,2 ], [ 4, 2,3 ], [ 5, 1,3 ], [ 6, 0,4 ], [ 7, 0,5 ], [ 8, -1,6 ], [ 9, -2,6 ], [ 10, -3,5 ], [ 11, -3,4 ], [ 0.25, 1.25, 0 ], [ 1.25, 2.25, 0.25 ], [ 2.25, 3, 1.25 ], [ 3.25, 2.75, 2.25 ], [ 4.25, 1.75, 3 ], [ 5.25, 0.75, 3.25 ], [ 6.25, 0, 4.25 ], [ 7.25, -.25, 5.25 ], [ 8.25, -1.25, 6 ], [ 9.25, -2.25, 5.75 ], [ 10.25, -3, 4.75 ], [ 11.25, -3.25, 3.75 ], ], ); foreach my $elem (@data) { my ($arms, @points) = @$elem; my $path = Math::PlanePath::DragonRounded->new (arms => $arms); foreach my $point (@points) { my ($n, $x, $y) = @$point; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() arms=$arms n at x=$x,y=$y"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::DragonRounded->new; for (1 .. 10) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($nf) frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($nf) frac $frac, y"); } } } #------------------------------------------------------------------------------ # random points foreach my $arms (1 .. 4) { my $path = Math::PlanePath::DragonRounded->new (arms => $arms); for (1 .. 30) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive ### random point: "n=$n" my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } ok ($rev_n, $n, "xy_to_n($x,$y) reverse to expect n=$n, got $rev_n"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() reverse n=$n cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() reverse n=$n cf got n_hi=$n_hi"); } } exit 0; Math-PlanePath-129/t/SierpinskiCurve.t0000644000175000017500000002302513734026643015475 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 1254; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::SierpinskiCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::SierpinskiCurve::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::SierpinskiCurve->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::SierpinskiCurve->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::SierpinskiCurve->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::SierpinskiCurve->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start() { my $path = Math::PlanePath::SierpinskiCurve->new(); ok ($path->n_start, 0, 'n_start()'); } #------------------------------------------------------------------------------ # x_negative(), y_negative() { foreach my $elem ([1, 0,0], [2, 0,0], [3, 1,0], [4, 1,0], [5, 1,1], [6, 1,1], [7, 1,1], [8, 1,1]) { my ($arms, $want_x_negative, $want_y_negative) = @$elem; my $path = Math::PlanePath::SierpinskiCurve->new (arms => $arms); ok (!!$path->x_negative, !!$want_x_negative, 'x_negative()'); ok (!!$path->y_negative, !!$want_y_negative, 'y_negative()'); } } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::SierpinskiCurve->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 0); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 3); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 15); } { my ($n_lo,$n_hi) = $path->level_to_n_range(3); ok ($n_lo, 0); ok ($n_hi, 63); } } { my $path = Math::PlanePath::SierpinskiCurve->new (arms => 2); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 7); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 31); } } { my $path = Math::PlanePath::SierpinskiCurve->new (arms => 8); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 7); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 31); } } #------------------------------------------------------------------------------ # rect_to_n_range() samples { foreach my $elem ( [7, 0,-1, 0,-2, 0,27 ], # edges [1, 0,0, 0,0, 1,0], [2, 0,0, 0,0, 0,7], [2, -100,0, -1,0, 1,0], [3, -1,0, -1,0, 1,0], [3, -1,1, -1,1, 0,11], [3, -2,1, -2,1, 1,0], [4, -2,1, -2,1, 0,15], [4, -1,-1, -1,-1, 1,0], [5, -2,-1, -2,-1, 0,19], [5, 0,-2, 0,-2, 1,0], [5, -1,-2, -1,-2, 1,0], [6, -1,-2, -1,-2, 0,23], [6, 0,-2, 0,-2, 1,0], [7, 0,-2, 0,-2, 0,27], [7, 1,-2, 1,-2, 1,0], [8, 1,-2, 1,-2, 0,31], ) { my ($arms, $x1,$y1,$x2,$y2, $want_n_lo,$want_n_hi) = @$elem; my $path = Math::PlanePath::SierpinskiCurve->new (arms => $arms); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); ok ($n_hi, $want_n_hi, "arms=$arms $x1,$y1, $x2,$y2"); ok ($n_lo, $want_n_lo); } } #------------------------------------------------------------------------------ # rect_to_n_range() near origin { my $bad = 0; foreach my $arms (1 .. 8) { my $path = Math::PlanePath::SierpinskiCurve->new (arms => $arms); foreach my $n (0 .. 8*$arms) { my ($x,$y) = $path->n_to_xy ($n); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); unless ($n_lo <= $n) { $bad++; } unless ($n_hi >= $n) { $bad++; } } } ok ($bad, 0); } #------------------------------------------------------------------------------ # first few points { my @data = ( [ 0.25, 1.25, 0.25 ], [ 1.25, 2.25, 1 ], [ 2.25, 3.25, 0.75 ], [ 3.25, 4.25, 0.25 ], [ 4.25, 4.75, 1.25 ], [ 0, 1,0 ], [ 1, 2,1 ], [ 2, 3,1 ], [ 3, 4,0 ], [ 4, 5,1 ], ); my $path = Math::PlanePath::SierpinskiCurve->new; foreach my $elem (@data) { my ($n, $x, $y) = @$elem; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } #------------------------------------------------------------------------------ # random rect_to_n_range() foreach my $arms (1 .. 8) { my $path = Math::PlanePath::SierpinskiCurve->new (arms => $arms); for (1 .. 5) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)); # 0 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); ok (defined $rev_n, 1, "xy_to_n($x,$y) arms=$arms reverse n, got undef"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() arms=$arms n=$n at xy=$x,$y cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() arms=$arms n=$n at xy=$x,$y cf got n_hi=$n_hi"); } } #------------------------------------------------------------------------------ # random n_to_xy() fracs foreach my $arms (1 .. 8) { my $path = Math::PlanePath::SierpinskiCurve->new (arms => $arms); for (1 .. 20) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my $nhex = sprintf '0x%X', $n; my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+$arms); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($nf) arms=$arms frac $frac, X (n hex $nhex)"); ok ($got_yf, $want_yf, "n_to_xy($nf) arms=$arms frac $frac, X (n hex $nhex)"); } } } #------------------------------------------------------------------------------ # xy_to_n() near origin { my $bad = 0; OUTER: foreach my $d (0 .. 4) { foreach my $s (0 .. 4) { foreach my $arms (1 .. 8) { my $path = Math::PlanePath::SierpinskiCurve->new (arms => $arms, straight_spacing => $s, diagonal_spacing => $d); foreach my $x (-8 .. 8) { foreach my $y (-8 .. 8) { my $n = $path->xy_to_n ($x,$y); next unless defined $n; my ($nx,$ny) = $path->n_to_xy ($n); if ($nx != $x || $ny != $y) { MyTestHelpers::diag("xy_to_n($x,$y) arms=$arms gives n=$n, which is $nx,$ny"); last OUTER if ++$bad > 10; } } } } } } ok ($bad, 0); } #------------------------------------------------------------------------------ # X axis base 4 digits 0 and 3 only { my $path = Math::PlanePath::SierpinskiCurve->new; foreach my $i (0 .. 50) { my $x = 3*$i + 1; my $want_n = duplicate_bits($i); my $got_n = $path->xy_to_n ($x,0); ok ($got_n, $want_n, "i=$i N at X=$x,Y=0"); } } sub duplicate_bits { my ($n) = @_; my $bits = sprintf '%b',$n; $bits =~ s/(.)/$1$1/g; return oct("0b$bits"); } exit 0; Math-PlanePath-129/t/NumSeq-PlanePathDelta.t0000644000175000017500000004461612136646214016412 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments '###'; my $test_count = (tests => 94)[1]; plan tests => $test_count; if (! eval { require Math::NumSeq; 1 }) { MyTestHelpers::diag ('skip due to Math::NumSeq not available -- ',$@); foreach (1 .. $test_count) { skip ('due to no Math::NumSeq', 1, 1); } exit 0; } require Math::NumSeq::PlanePathDelta; #------------------------------------------------------------------------------ # _delta_func_Dir4() ok (Math::NumSeq::PlanePathDelta::_delta_func_Dir4(199, 0), 0); ok (Math::NumSeq::PlanePathDelta::_delta_func_Dir4(0, 199), 1); ok (Math::NumSeq::PlanePathDelta::_delta_func_Dir4(-199, 0), 2); ok (Math::NumSeq::PlanePathDelta::_delta_func_Dir4(0, -199), 3); ok (Math::NumSeq::PlanePathDelta::_delta_func_Dir4(1,1), 0.5); ok (Math::NumSeq::PlanePathDelta::_delta_func_Dir4(-3,3), 1.5); ok (Math::NumSeq::PlanePathDelta::_delta_func_Dir4(-3,-3), 2.5); ok (Math::NumSeq::PlanePathDelta::_delta_func_Dir4(2,-2), 3.5); { require Math::Trig; my $two_pi = 2 * Math::Trig::pi(); foreach my $degrees (5, 85, 95, 175, 185, 265, 275, 355) { my $radians = $degrees/360 * $two_pi; my $dx = cos($radians); my $dy = sin($radians); my $want_4 = 4 * $degrees / 360; my $got_4 = Math::NumSeq::PlanePathDelta::_delta_func_Dir4($dx,$dy); ### $dx ### $dy ### $want_4 ### $got_4 ok ($got_4 >= 0); ok ($got_4 < 4); ok ($got_4 + 0.01 > $want_4); ok ($got_4 - 0.01 < $want_4); } } #------------------------------------------------------------------------------ # _delta_func_TDir6() ok (Math::NumSeq::PlanePathDelta::_delta_func_TDir6(199,0), 0); ok (Math::NumSeq::PlanePathDelta::_delta_func_TDir6(5,5), 1); ok (Math::NumSeq::PlanePathDelta::_delta_func_TDir6(-5,5), 2); ok (Math::NumSeq::PlanePathDelta::_delta_func_TDir6(-5,0), 3); ok (Math::NumSeq::PlanePathDelta::_delta_func_TDir6(-2,-2), 4); ok (Math::NumSeq::PlanePathDelta::_delta_func_TDir6(2,-2), 5); # twelfths at dx=3,dy=1 and dx=0,dy=1 ok (Math::NumSeq::PlanePathDelta::_delta_func_TDir6(3,1), 0.5); # +3,+1 ok (Math::NumSeq::PlanePathDelta::_delta_func_TDir6(0,199), 1.5); # 0,+1 ok (Math::NumSeq::PlanePathDelta::_delta_func_TDir6(-6,2), 2.5); # -3,+1 ok (Math::NumSeq::PlanePathDelta::_delta_func_TDir6(-6,-2), 3.5); ok (Math::NumSeq::PlanePathDelta::_delta_func_TDir6(0,-199), 4.5); ok (Math::NumSeq::PlanePathDelta::_delta_func_TDir6(3,-1), 5.5); { my $got_6 = Math::NumSeq::PlanePathDelta::_delta_func_TDir6(1,1.001); ok ($got_6 >= 1); ok ($got_6 <= 1.1); } { my $got_6 = Math::NumSeq::PlanePathDelta::_delta_func_TDir6(-1,0.999); ok ($got_6 >= 2); ok ($got_6 <= 2.1); } { my $got_6 = Math::NumSeq::PlanePathDelta::_delta_func_TDir6(-1,0.0001); ok ($got_6 >= 2.9); ok ($got_6 <= 3); } { my $got_6 = Math::NumSeq::PlanePathDelta::_delta_func_TDir6(-1,-0.0001); ok ($got_6 >= 3); ok ($got_6 <= 3.1); } { require Math::Trig; my $two_pi = 2 * Math::Trig::pi(); foreach my $degrees (5, 85, 95, 175, 185, 265, 275, 355) { my $radians = $degrees/360 * $two_pi; my $dx = cos($radians); my $dy = sin($radians) / sqrt(3); # flattened my $want_6 = 6 * $degrees / 360; my $got_6 = Math::NumSeq::PlanePathDelta::_delta_func_TDir6($dx,$dy); ### $dx ### $dy ### $want_6 ### $got_6 ok ($got_6 >= 0); ok ($got_6 < 6); ok ($got_6 + 0.01 > $want_6); ok ($got_6 - 0.01 < $want_6); } } #------------------------------------------------------------------------------ # characteristic() foreach my $elem (['increasing',undef ], # default SquareSpiral dX not increasing ['non_decreasing', 1, planepath => 'MultipleRings,step=0', delta_type => 'dX' ], ) { my ($key, $want, @parameters) = @$elem; my $seq = Math::NumSeq::PlanePathDelta->new (@parameters); ok ($seq->characteristic($key), $want, join(' ', @parameters)); } # #------------------------------------------------------------------------------ # # values_min(), values_max() # # foreach my $elem # ([undef, undef ], # default undef for SquareSpiral X # [0,undef, coordinate_type => 'Radius' ], # [0,undef, coordinate_type => 'RSquared' ], # # [0,undef, planepath => 'HilbertCurve', coordinate_type => 'X' ], # [0,undef, planepath => 'HilbertCurve', coordinate_type => 'Y' ], # [0,undef, planepath => 'HilbertCurve', coordinate_type => 'Sum' ], # [0,undef, planepath => 'HilbertCurve', coordinate_type => 'Product' ], # # [undef,undef, planepath => 'CellularRule54', coordinate_type => 'X' ], # [0,undef, planepath => 'CellularRule54', coordinate_type => 'Y' ], # [0,undef, planepath => 'CellularRule54', coordinate_type => 'Sum' ], # [undef,undef, planepath => 'CellularRule54', coordinate_type => 'Product' ], # [0,undef, planepath => 'CellularRule54', coordinate_type => 'Radius' ], # [0,undef, planepath => 'CellularRule54', coordinate_type => 'RSquared' ], # [undef,0, planepath => 'CellularRule54', coordinate_type => 'DiffXY' ], # [0,undef, planepath => 'CellularRule54', coordinate_type => 'DiffYX' ], # [0,undef, planepath => 'CellularRule54', coordinate_type => 'AbsDiff' ], # # [undef,undef, planepath => 'CellularRule190', coordinate_type => 'X' ], # [0,undef, planepath => 'CellularRule190', coordinate_type => 'Y' ], # [0,undef, planepath => 'CellularRule190', coordinate_type => 'Sum' ], # [undef,undef, planepath => 'CellularRule190', coordinate_type => 'Product' ], # [0,undef, planepath => 'CellularRule190', coordinate_type => 'Radius' ], # [0,undef, planepath => 'CellularRule190', coordinate_type => 'RSquared' ], # # [undef,undef, planepath => 'UlamWarburton', coordinate_type => 'X' ], # [undef,undef, planepath => 'UlamWarburton', coordinate_type => 'Y' ], # [undef,undef, planepath => 'UlamWarburton', coordinate_type => 'Sum' ], # [undef,undef, planepath => 'UlamWarburton', coordinate_type => 'Product' ], # [0,undef, planepath => 'UlamWarburton', coordinate_type => 'Radius' ], # [0,undef, planepath => 'UlamWarburton', coordinate_type => 'RSquared' ], # # [0,undef, planepath => 'UlamWarburtonQuarter', coordinate_type => 'X' ], # [0,undef, planepath => 'UlamWarburtonQuarter', coordinate_type => 'Y' ], # [0,undef, planepath => 'UlamWarburtonQuarter', coordinate_type => 'Sum' ], # [0,undef, planepath => 'UlamWarburtonQuarter', coordinate_type => 'Product' ], # [0,undef, planepath => 'UlamWarburtonQuarter', coordinate_type => 'Radius' ], # [0,undef, planepath => 'UlamWarburtonQuarter', coordinate_type => 'RSquared' ], # # # [3,undef, planepath => 'PythagoreanTree', coordinate_type => 'X' ], # [4,undef, planepath => 'PythagoreanTree', coordinate_type => 'Y' ], # [7,undef, planepath => 'PythagoreanTree', coordinate_type => 'Sum' ], # [3*4,undef, planepath => 'PythagoreanTree', coordinate_type => 'Product' ], # [5,undef, planepath => 'PythagoreanTree', coordinate_type => 'Radius' ], # [25,undef, planepath => 'PythagoreanTree', coordinate_type => 'RSquared' ], # [undef,undef, planepath => 'PythagoreanTree', coordinate_type => 'DiffXY' ], # [undef,undef, planepath => 'PythagoreanTree', coordinate_type => 'DiffYX' ], # [0,undef, planepath => 'PythagoreanTree', coordinate_type => 'AbsDiff' ], # # [2,undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'X' ], # [1,undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'Y' ], # [3,undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'Sum' ], # [2,undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'Product' ], # #[sqrt(5),undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'Radius' ], # [5,undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'RSquared' ], # [1,undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'DiffXY' ], # [undef,-1, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'DiffYX' ], # [1,undef, planepath => 'PythagoreanTree,coordinates=PQ', coordinate_type => 'AbsDiff' ], # # # [0,undef, planepath => 'HypotOctant', coordinate_type => 'X' ], # [0,undef, planepath => 'HypotOctant', coordinate_type => 'Y' ], # [0,undef, planepath => 'HypotOctant', coordinate_type => 'Sum' ], # [0,undef, planepath => 'HypotOctant', coordinate_type => 'Product' ], # [0,undef, planepath => 'HypotOctant', coordinate_type => 'Radius' ], # [0,undef, planepath => 'HypotOctant', coordinate_type => 'RSquared' ], # [0,undef, planepath => 'HypotOctant', coordinate_type => 'DiffXY' ], # [undef,0, planepath => 'HypotOctant', coordinate_type => 'DiffYX' ], # [0,undef, planepath => 'HypotOctant', coordinate_type => 'AbsDiff' ], # # # [2,undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'X' ], # [1,undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'Y' ], # [3,undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'Sum' ], # [2,undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'Product' ], # # [sqrt(5),undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'Radius' ], # [5,undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'RSquared' ], # [1,undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'DiffXY' ], # [undef,-1, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'DiffYX' ], # [1,undef, planepath => 'DivisibleColumns,divisor_type=proper', coordinate_type => 'AbsDiff' ], # # [1,undef, planepath => 'DivisibleColumns', coordinate_type => 'X' ], # [1,undef, planepath => 'DivisibleColumns', coordinate_type => 'Y' ], # [2,undef, planepath => 'DivisibleColumns', coordinate_type => 'Sum' ], # [1,undef, planepath => 'DivisibleColumns', coordinate_type => 'Product' ], # # [sqrt(2),undef, planepath => 'DivisibleColumns', coordinate_type => 'Radius' ], # [2,undef, planepath => 'DivisibleColumns', coordinate_type => 'RSquared' ], # [0,undef, planepath => 'DivisibleColumns', coordinate_type => 'DiffXY' ], # [undef,0, planepath => 'DivisibleColumns', coordinate_type => 'DiffYX' ], # [0,undef, planepath => 'DivisibleColumns', coordinate_type => 'AbsDiff' ], # # # [1,undef, planepath => 'CoprimeColumns', coordinate_type => 'X' ], # [1,undef, planepath => 'CoprimeColumns', coordinate_type => 'Y' ], # [2,undef, planepath => 'CoprimeColumns', coordinate_type => 'Sum' ], # [1,undef, planepath => 'CoprimeColumns', coordinate_type => 'Product' ], # # [sqrt(2),undef, planepath => 'CoprimeColumns', coordinate_type => 'Radius' ], # [2,undef, planepath => 'CoprimeColumns', coordinate_type => 'RSquared' ], # [0,undef, planepath => 'CoprimeColumns', coordinate_type => 'DiffXY' ], # [undef,0, planepath => 'CoprimeColumns', coordinate_type => 'DiffYX' ], # [0,undef, planepath => 'CoprimeColumns', coordinate_type => 'AbsDiff' ], # # [1,undef, planepath => 'RationalsTree', coordinate_type => 'X' ], # [1,undef, planepath => 'RationalsTree', coordinate_type => 'Y' ], # # X>=1 and Y>=1 always so Sum>=2 # [2,undef, planepath => 'RationalsTree', coordinate_type => 'Sum' ], # [1,undef, planepath => 'RationalsTree', coordinate_type => 'Product' ], # # [sqrt(2),undef, planepath => 'RationalsTree', coordinate_type => 'Radius' ], # [2,undef, planepath => 'RationalsTree', coordinate_type => 'RSquared' ], # # whole first quadrant so diff positive and negative # [undef,undef, planepath => 'RationalsTree', coordinate_type => 'DiffXY' ], # [undef,undef, planepath => 'RationalsTree', coordinate_type => 'DiffYX' ], # [0,undef, planepath => 'RationalsTree', coordinate_type => 'AbsDiff' ], # # [0,undef, planepath => 'QuadricCurve', coordinate_type => 'X' ], # [undef,undef, planepath => 'QuadricCurve', coordinate_type => 'Y' ], # [0,undef, planepath => 'QuadricCurve', coordinate_type => 'Sum' ], # [undef,undef, planepath => 'QuadricCurve', coordinate_type => 'Product' ], # [0,undef, planepath => 'QuadricCurve', coordinate_type => 'Radius' ], # [0,undef, planepath => 'QuadricCurve', coordinate_type => 'RSquared' ], # [0,undef, planepath => 'QuadricCurve', coordinate_type => 'DiffXY' ], # [undef,0, planepath => 'QuadricCurve', coordinate_type => 'DiffYX' ], # [0,undef, planepath => 'QuadricCurve', coordinate_type => 'AbsDiff' ], # # [0,5, planepath => 'Rows,width=6', coordinate_type => 'X' ], # [0,undef, planepath => 'Rows,width=6', coordinate_type => 'Y' ], # [0,undef, planepath => 'Rows,width=6', coordinate_type => 'Sum' ], # [0,undef, planepath => 'Rows,width=6', coordinate_type => 'Product' ], # [0,undef, planepath => 'Rows,width=6', coordinate_type => 'Radius' ], # [0,undef, planepath => 'Rows,width=6', coordinate_type => 'RSquared' ], # [undef,5, planepath => 'Rows,width=6', coordinate_type => 'DiffXY' ], # [-5,undef, planepath => 'Rows,width=6', coordinate_type => 'DiffYX' ], # [0,undef, planepath => 'Rows,width=6', coordinate_type => 'AbsDiff' ], # # [0,undef, planepath => 'Columns,height=6', coordinate_type => 'X' ], # [0,5, planepath => 'Columns,height=6', coordinate_type => 'Y' ], # [0,undef, planepath => 'Columns,height=6', coordinate_type => 'Sum' ], # [0,undef, planepath => 'Columns,height=6', coordinate_type => 'Product' ], # [0,undef, planepath => 'Columns,height=6', coordinate_type => 'Radius' ], # [0,undef, planepath => 'Columns,height=6', coordinate_type => 'RSquared' ], # [-5,undef, planepath => 'Columns,height=6', coordinate_type => 'DiffXY' ], # [undef,5, planepath => 'Columns,height=6', coordinate_type => 'DiffYX' ], # [0,undef, planepath => 'Columns,height=6', coordinate_type => 'AbsDiff' ], # # [0,0, planepath => 'PyramidRows,step=0', coordinate_type => 'X' ], # [0,undef, planepath => 'PyramidRows,step=0', coordinate_type => 'Y' ], # [0,undef, planepath => 'PyramidRows,step=0', coordinate_type => 'Sum' ], # [0,undef, planepath => 'PyramidRows,step=0', coordinate_type => 'Product' ], # [0,undef, planepath => 'PyramidRows,step=0', coordinate_type => 'Radius' ], # [0,undef, planepath => 'PyramidRows,step=0', coordinate_type => 'RSquared' ], # [undef,0, planepath => 'PyramidRows,step=0', coordinate_type => 'DiffXY' ], # [0,undef, planepath => 'PyramidRows,step=0', coordinate_type => 'DiffYX' ], # [0,undef, planepath => 'PyramidRows,step=0', coordinate_type => 'AbsDiff' ], # # [0,undef, planepath => 'PyramidRows,step=1', coordinate_type => 'X' ], # [0,undef, planepath => 'PyramidRows,step=1', coordinate_type => 'Y' ], # [0,undef, planepath => 'PyramidRows,step=1', coordinate_type => 'Sum' ], # [0,undef, planepath => 'PyramidRows,step=1', coordinate_type => 'Product' ], # [0,undef, planepath => 'PyramidRows,step=1', coordinate_type => 'Radius' ], # [0,undef, planepath => 'PyramidRows,step=1', coordinate_type => 'RSquared' ], # [undef,0, planepath => 'PyramidRows,step=1', coordinate_type => 'DiffXY' ], # [0,undef, planepath => 'PyramidRows,step=1', coordinate_type => 'DiffYX' ], # [0,undef, planepath => 'PyramidRows,step=1', coordinate_type => 'AbsDiff' ], # # [undef,undef, planepath => 'PyramidRows,step=2', coordinate_type => 'X' ], # [0,undef, planepath => 'PyramidRows,step=2', coordinate_type => 'Y' ], # [0,undef, planepath => 'PyramidRows,step=2', coordinate_type => 'Sum' ], # [undef,undef, planepath => 'PyramidRows,step=2', coordinate_type => 'Product' ], # [0,undef, planepath => 'PyramidRows,step=2', coordinate_type => 'Radius' ], # [0,undef, planepath => 'PyramidRows,step=2', coordinate_type => 'RSquared' ], # [undef,0, planepath => 'PyramidRows,step=2', coordinate_type => 'DiffXY' ], # [0,undef, planepath => 'PyramidRows,step=2', coordinate_type => 'DiffYX' ], # [0,undef, planepath => 'PyramidRows,step=2', coordinate_type => 'AbsDiff' ], # # [undef,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'X' ], # [0,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'Y' ], # [undef,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'Sum' ], # [undef,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'Product' ], # [0,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'Radius' ], # [0,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'RSquared' ], # [undef,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'DiffXY' ], # [undef,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'DiffYX' ], # [0,undef, planepath => 'PyramidRows,step=3', coordinate_type => 'AbsDiff' ], # # # [0,undef, planepath => 'HIndexing', coordinate_type => 'X' ], # [0,undef, planepath => 'HIndexing', coordinate_type => 'Y' ], # [0,undef, planepath => 'HIndexing', coordinate_type => 'Sum' ], # [0,undef, planepath => 'HIndexing', coordinate_type => 'Product' ], # [0,undef, planepath => 'HIndexing', coordinate_type => 'Radius' ], # [0,undef, planepath => 'HIndexing', coordinate_type => 'RSquared' ], # [undef,0, planepath => 'HIndexing', coordinate_type => 'DiffXY' ], # [0,undef, planepath => 'HIndexing', coordinate_type => 'DiffYX' ], # [0,undef, planepath => 'HIndexing', coordinate_type => 'AbsDiff' ], # ) { # my ($want_min,$want_max, @parameters) = @$elem; # ### @parameters # ### $want_min # ### $want_max # # my $seq = Math::NumSeq::PlanePathDelta->new (@parameters); # ok ($seq->values_min, $want_min, "values_min() ".join(',',@parameters)); # ok ($seq->values_max, $want_max, "values_max() ".join(',',@parameters)); # } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/PlanePath.t0000644000175000017500000000604713246362507014231 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2017, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 36; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath; my $have_64bits = ((1 << 63) != 0); my $modulo_64bit_dodginess = ($have_64bits && ((~0)%2) != ((~0)&1)); #---------------------------------------------------------------------------- # n_to_turn_LSR() { package MyPlanePathZero; use vars '@ISA'; @ISA = ('Math::PlanePath'); use constant n_start => 0; sub n_to_xy { my ($self, $n) = @_; if ($n < 0) { return; } return return (0,0); } } { my $path = MyPlanePathZero->new; { my ($dx,$dy) = $path->n_to_dxdy(0); ok ($dx, 0); ok ($dy, 0); } { my ($dx,$dy) = $path->n_to_dxdy(-1); ok ($dx, undef); ok ($dy, undef); } ok ($path->_UNDOCUMENTED__n_to_turn_LSR(-1), undef); ok ($path->_UNDOCUMENTED__n_to_turn_LSR(0), undef); ok ($path->_UNDOCUMENTED__n_to_turn_LSR(1), 0); } #---------------------------------------------------------------------------- # _divrem() { my $n = 123; my ($q,$r) = Math::PlanePath::_divrem($n,5); ok ("$n", 123); ok ("$q", 24); ok ("$r", 3); } { # perl 5.6 did integer divisions in IV or something, exercise only up to ~0>>1 # perl 5.6.2 has some dodginess in % operator, limit to 31 bits there my $n = ($modulo_64bit_dodginess ? (1 << 32) - 1 : ~0 >> 1); foreach my $d (2,3,4, 5, 6,7,8,9, 10, 16, 37) { my ($q,$r) = Math::PlanePath::_divrem($n,$d); my $m = $q * $d + $r; ok ($n, $m, "_divrem() ~0=$n / $d got q=$q rem=$r"); } } #---------------------------------------------------------------------------- # _divrem_mutate() { my $n = 123; my $r = Math::PlanePath::_divrem_mutate($n,5); ok ("$n", 24); ok ("$r", 3); } { my $n = -123; my $r = Math::PlanePath::_divrem_mutate($n,5); ok ("$n", -25); ok ("$r", 2); } { foreach my $d (2,3,4, 5, 6,7,8,9, 10, 16, 37) { # perl 5.6 did integer divisions in IV or something, exercise only to ~0>>1 my $n = ($modulo_64bit_dodginess ? (1 << 32) - 1 : ~0 >> 1); my $q = $n; my $r = Math::PlanePath::_divrem_mutate($q,$d); my $m = $q * $d + $r; ok ($n, $m, "_divrem_mutate() ~0=$n / $d got q=$q rem=$r"); } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/ImaginaryHalf.t0000644000175000017500000001337613734026645015075 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 349; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::CoprimeColumns; *_coprime = \&Math::PlanePath::CoprimeColumns::_coprime; use Math::PlanePath::GcdRationals; *_gcd = \&Math::PlanePath::GcdRationals::_gcd; use Math::PlanePath::ImaginaryHalf; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::ImaginaryHalf::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::ImaginaryHalf->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::ImaginaryHalf->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::ImaginaryHalf->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::ImaginaryHalf->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::ImaginaryHalf->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); ok ($path->class_x_negative, 1, 'class_x_negative() instance method'); ok ($path->class_y_negative, 0, 'class_y_negative() instance method'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::ImaginaryHalf->parameter_info_list; ok (join(',',@pnames), 'radix,digit_order'); } #------------------------------------------------------------------------------ { my @data = ( [ { digit_order => 'XYX' }, [ 0, 0,0 ], [ 1, 1,0 ], [ 2, 0,1 ], [ 3, 1,1 ], [ 4, -2,0 ], [ 5, -1,0 ], [ 6, -2,1 ], [ 7, -1,1 ], ], [ { digit_order => 'XXY' }, [ 0, 0,0 ], [ 1, 1,0 ], [ 2, -2,0 ], [ 3, -1,0 ], [ 4, 0,1 ], [ 5, 1,1 ], [ 6, -2,1 ], [ 7, -1,1 ], ], [ { digit_order => 'YXX' }, [ 0, 0,0 ], [ 1, 0,1 ], [ 2, 1,0 ], [ 3, 1,1 ], [ 4, -2,0 ], [ 5, -2,1 ], [ 6, -1,0 ], [ 7, -1,1 ], ], [ { digit_order => 'XnYX' }, [ 0, 0,0 ], [ 1, -2,0 ], [ 2, 0,1 ], [ 3, -2,1 ], [ 4, 1,0 ], [ 5, -1,0 ], [ 6, 1,1 ], [ 7, -1,1 ], ], [ { digit_order => 'XnXY' }, [ 0, 0,0 ], [ 1, -2,0 ], [ 2, 1,0 ], [ 3, -1,0 ], [ 4, 0,1 ], [ 5, -2,1 ], [ 6, 1,1 ], [ 7, -1,1 ], ], [ { digit_order => 'YXnX' }, [ 0, 0,0 ], [ 1, 0,1 ], [ 2, -2,0 ], [ 3, -2,1 ], [ 4, 1,0 ], [ 5, 1,1 ], [ 6, -1,0 ], [ 7, -1,1 ], ], ); foreach my $group (@data) { my ($options, @elems) = @$group; my $path = Math::PlanePath::ImaginaryHalf->new (%$options); my $order = $options->{'digit_order'}; my $n_start = $path->n_start; foreach my $elem (@elems) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "order=$order x at n=$n"); ok ($got_y, $want_y, "order=$order y at n=$n"); } foreach my $elem (@elems) { my ($want_n, $x, $y) = @$elem; next unless $want_n==int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "order=$order xy_to_n($x,$y) sample"); } foreach my $elem (@elems) { my ($n, $x, $y) = @$elem; if ($n == int($n)) { my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo == $n_start, 1, "rect_to_n_range() got_nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range(0,0,$x,$y) got_nhi=$got_nhi at n=$n,x=$x,y=$y"); ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() got_nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() got_nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/ZOrderCurve.t0000644000175000017500000000473613734026636014574 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 15; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::ZOrderCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::ZOrderCurve::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::ZOrderCurve->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::ZOrderCurve->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::ZOrderCurve->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::ZOrderCurve->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::ZOrderCurve->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 0, 'x_negative() instance method'); ok ($path->y_negative, 0, 'y_negative() instance method'); } #------------------------------------------------------------------------------ # n_to_rsquared() { my $path = Math::PlanePath::ZOrderCurve->new; ok ($path->n_to_rsquared(0), 0); ok ($path->n_to_rsquared(1), 1); ok ($path->n_to_rsquared(3), 2); ok ($path->n_to_rsquared(4), 4); ok ($path->n_to_rsquared(6), 5); } exit 0; Math-PlanePath-129/t/Diagonals.t0000644000175000017500000000765013734026646014262 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 95; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::Diagonals; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::Diagonals::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::Diagonals->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::Diagonals->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::Diagonals->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::Diagonals->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::Diagonals->new; ok ($path->n_start, 1, 'n_start()'); ok (! $path->x_negative, 1, 'x_negative()'); ok (! $path->y_negative, 1, 'y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::Diagonals->parameter_info_list; ok (join(',',@pnames), 'direction,n_start,x_start,y_start'); } #------------------------------------------------------------------------------ # n_to_xy(), xy_to_n() { my @blocks = ([ [], # default direction=>'down' [0.5, -0.5,0.5 ], [0.75, -0.25,0.25 ], [1, 0,0 ], [1.25, .25,-.25 ], [1.5, -.5,1.5 ], [2, 0,1 ], [3, 1,0 ], [4, 0,2 ], [5, 1,1 ], [6, 2,0 ], [7, 0,3 ], [8, 1,2 ], [9, 2,1 ], [10, 3,0 ], ], [ [ direction => 'up' ], [0.5, 0.5,-0.5 ], [0.75, 0.25,-0.25 ], [1, 0,0 ], [1.25, -.25,.25 ], [1.5, 1.5,-.5 ], [2, 1,0 ], [3, 0,1 ], [4, 2,0 ], [5, 1,1 ], [6, 0,2 ], [7, 3,0 ], [8, 2,1 ], [9, 1,2 ], [10, 0,3 ], ]); foreach my $block (@blocks) { my ($options, @data) = @$block; my $path = Math::PlanePath::Diagonals->new (@$options); foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; $want_n = int ($want_n + 0.5); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "@$options n at x=$x,y=$y"); } } } exit 0; Math-PlanePath-129/t/GosperReplicate.t0000644000175000017500000002755613734026646015460 0ustar gggg#!/usr/bin/perl -w # Copyright 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 59; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments; require Math::PlanePath::GosperReplicate; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::GosperReplicate::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::GosperReplicate->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::GosperReplicate->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::GosperReplicate->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::GosperReplicate->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::GosperReplicate->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } #------------------------------------------------------------------------------ # _digits_rotate_lowtohigh() # _digits_unrotate_lowtohigh() sub add_to_digit { my ($digit, $add) = @_; if ($digit) { $digit = ((($digit-1) + $add) % 6) + 1; } return $digit; } sub digits_rotate_lowtohigh_by_successive { my ($aref) = @_; my @new = @$aref; foreach my $i (0 .. $#$aref) { if ($aref->[$i]) { foreach my $j (0 .. $i-1) { # below $i $new[$j] = add_to_digit($new[$j], $aref->[$i] - 1); } } } @$aref = @new; } { foreach my $elem ( [ [], [] ], [ [0], [0] ], [ [1], [1] ], [ [6], [6] ], [ [0,1], [0,1] ], [ [6,1], [6,1] ], [ [1,2], [2,2] ], [ [3,3], [5,3] ], [ [6,3], [2,3] ], [ [1,1,2], [2,2,2] ], [ [1,1,3], [3,3,3] ], [ [1,1,4], [4,4,4] ], [ [2,1,4], [5,4,4] ], [ [1,2,4], [5,5,4] ], ) { my ($digits, $rotated) = @$elem; { my @got = @$digits; Math::PlanePath::GosperReplicate::_digits_rotate_lowtohigh(\@got); ok(join(',',@got),join(',',@$rotated), "_digits_rotate_lowtohigh of ".join(',',@$digits)); } { my @got = @$rotated; Math::PlanePath::GosperReplicate::_digits_unrotate_lowtohigh(\@got); ok(join(',',@got),join(',',@$digits), "_digits_unrotate_lowtohigh of ".join(',',@$rotated)); } } } { # rotate / unrotate reversible my $bad = 0; foreach my $n (0 .. 7**3) { my @digits = Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,7); my $digits = join(',',reverse @digits); my @rot = @digits; Math::PlanePath::GosperReplicate::_digits_rotate_lowtohigh(\@rot); my $rot = join(',',reverse @rot); my @rot_by_successive = @digits; digits_rotate_lowtohigh_by_successive(\@rot_by_successive); my $rot_by_successive = join(',',reverse @rot_by_successive); unless ($rot eq $rot_by_successive) { MyTestHelpers::diag("n=$n digits=$digits rot=$rot rot_by_successive=$rot_by_successive"); if (++$bad >= 5) { last; } } my @unrot_again = @rot; Math::PlanePath::GosperReplicate::_digits_unrotate_lowtohigh(\@unrot_again); my $unrot_again = join(',',reverse @unrot_again); unless ($digits eq $unrot_again) { MyTestHelpers::diag("n=$n digits=$digits rot=$rot unrot_again=$unrot_again"); if (++$bad >= 1) { last; } } } ok ($bad, 0); } #------------------------------------------------------------------------------ # boundary squares when rotate { my @dir6_to_dx = (2, 1,-1,-2, -1, 1); my @dir6_to_dy = (0, 1, 1, 0, -1,-1); sub to_base7 { my ($n,$k) = @_; my @digits = reverse Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,7); if (defined $k) { while (@digits < $k) { unshift @digits, 0; } } return join('',@digits); } my $path = Math::PlanePath::GosperReplicate->new (numbering_type => 'rotate'); my $bad = 0; K: foreach my $k (0 .. 4) { my ($n_lo, $n_hi) = $path->level_to_n_range($k); ### $k ### $n_hi my $Bpred_by_path = sub { my ($n) = @_; my ($x,$y) = $path->n_to_xy($n); foreach my $dir6 (0 .. 5) { my $n2 = $path->xy_to_n ($x+$dir6_to_dx[$dir6], $y+$dir6_to_dy[$dir6]); ### $n2 if ($n2 > $n_hi) { return 1; } } return 0; }; my $Bpred_by_digits = sub { my ($n) = @_; my @digits = reverse # high to low Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,7); while (@digits < $k) { unshift @digits, 0; } foreach my $digit (@digits) { if ($digit == 0) { return 0; } } shift @digits; # skip high foreach my $digit (@digits) { if ($digit == 4 || $digit == 5) { return 0; } } @digits = grep {$_!=1} @digits; # ignore 1s foreach my $i (0 .. $#digits-1) { if (($digits[$i] == 3 && $digits[$i+1] == 2) || ($digits[$i] == 3 && $digits[$i+1] == 3) || ($digits[$i] == 6 && $digits[$i+1] == 6)) { return 0; } } return 1; }; foreach my $n ($n_lo .. $n_hi) { my $by_path = $Bpred_by_path->($n); my $by_digits = $Bpred_by_digits->($n); if ($by_path != $by_digits) { my ($x,$y) = $path->n_to_xy($n); my $n7 = to_base7($n,$k); MyTestHelpers::diag("k=$k n=$n [$n7] $x,$y path $by_path digits $by_digits ($n_lo to $n_hi)"); if (++$bad >= 10) { last K; } } } } ok ($bad, 0); } #------------------------------------------------------------------------------ # digit rotation per the POD foreach my $numbering_type ('fixed','rotate') { my $path = Math::PlanePath::GosperReplicate->new (numbering_type => $numbering_type); my $bad = 0; my $k = 3; my $pow = 7**$k; foreach my $turn (0,1,2,5,6) { foreach my $n (0 .. $pow-1) { my ($x,$y) = $path->n_to_xy($n); my ($tx,$ty) = ($x,$y); foreach (1 .. $turn) { ($tx,$ty) = xy_rotate_plus60($tx,$ty); } my $txy = "$tx,$ty"; my $an = digits_add($n,$turn, $numbering_type); my ($ax,$ay) = $path->n_to_xy($an); my $axy = "$ax,$ay"; if ($txy ne $axy) { my $n7 = join('',reverse Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,7)); my $an7 = join('',reverse Math::PlanePath::Base::Digits::digit_split_lowtohigh($an,7)); print "$numbering_type turn=$turn oops, n=$n [$n7] $x,$y turned $txy cf an=$an [$an7] $axy\n"; $bad++; } } } ok ($bad, 0); } sub xy_rotate_plus60 { my ($x, $y) = @_; return (($x-3*$y)/2, # rotate +60 ($x+$y)/2); } sub digits_add { my ($n,$offset, $numbering_type) = @_; my @digits = Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,7); foreach my $digit (reverse @digits) { if ($digit) { $digit = (($digit-1 + $offset)%6) + 1; # mutate @digits } if ($numbering_type eq 'rotate') { last; } } return Math::PlanePath::Base::Digits::digit_join_lowtohigh (\@digits, 7); } #------------------------------------------------------------------------------ # numbering_type => 'rotate' sub-parts rotation { my $path = Math::PlanePath::GosperReplicate->new (numbering_type=>'rotate'); my $bad = 0; my $k = 3; my $pow = 7**$k; foreach my $n ($pow .. 2*$pow-1) { my ($x,$y) = $path->n_to_xy($n); { my ($rx,$ry) = $path->n_to_xy($n+$pow); ($rx,$ry) = xy_rotate_minus60($rx,$ry); my $got = "$rx,$ry"; my $want = "$x,$y"; if ($got ne $want) { print "oops, got $got want $want\n"; $bad++; } } { my ($rx,$ry) = $path->n_to_xy($n+2*$pow); ($rx,$ry) = xy_rotate_minus120($rx,$ry); $bad += ("$rx,$ry" ne "$x,$y"); } } ok ($bad, 0); } sub xy_rotate_minus60 { my ($x, $y) = @_; return (($x+3*$y)/2, # rotate -60 ($y-$x)/2); } sub xy_rotate_minus120 { my ($x, $y) = @_; return ((3*$y-$x)/2, # rotate -120 ($x+$y)/-2); } #------------------------------------------------------------------------------ # _digits_rotate_lowtohigh() # _digits_unrotate_lowtohigh() { foreach my $elem ( [ [], [] ], [ [0], [0] ], [ [1], [1] ], [ [6], [6] ], [ [0,1], [0,1] ], [ [6,1], [6,1] ], [ [3,2], [4,2] ], [ [6,3], [2,3] ], ) { my ($digits, $rotated) = @$elem; { my @got = @$digits; Math::PlanePath::GosperReplicate::_digits_rotate_lowtohigh(\@got); ok(join(',',@got),join(',',@$rotated), "_digits_rotate_lowtohigh of ".join(',',@$digits)); } { my @got = @$rotated; Math::PlanePath::GosperReplicate::_digits_unrotate_lowtohigh(\@got); ok(join(',',@got),join(',',@$digits), "_digits_unrotate_lowtohigh of ".join(',',@$rotated)); } } } #------------------------------------------------------------------------------ # No, the replicate shape is each middle of the unit hexagons in GosperIslands. # # Return true if $n is on the boundary of its expansion level (the level # # which is the number of base-7 digits in $n). # sub _rotate_Bpred { # my ($n) = @_; # my @digits = Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,7) # or return 1; # my $prev = $digits[-1] || return 0; # foreach my $digit (reverse @digits[0 .. $#digits-1]) { # high to low # if ($digit == 1) { # } elsif ($digit == 0 || $digit == 4 || $digit == 5 # || ($prev == 6 && $digit == 6) # || ($prev == 3 && ($digit == 2 || $digit == 3))) { # ### _rotate_Bpred(): "not prev=$prev digit=$digit" # return 0; # } # $prev = $digit; # } # return 1; # } # # { # require Math::PlanePath::GosperIslands; # my $islands = Math::PlanePath::GosperIslands->new; # # my $path = Math::PlanePath::GosperReplicate->new (numbering_type=>'rotate'); # my $bad = 0; # foreach my $n (1 .. 7**2) { # my $rotate_Bpred = _rotate_Bpred($n) ? 1 : 0; # my ($x,$y) = $path->n_to_xy($n); # my $island_pred = $islands->xy_is_visited($x,$y) ? 1 : 0; # if ($rotate_Bpred ne $island_pred) { # print $islands->n_to_xy(7),"\n"; # print $islands->xy_to_n(5,1),"\n"; # print $islands->xy_is_visited(5,1),"\n"; # my $n7 = join('',reverse Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,7)); # print "oops, n=$n [$n7] at $x,$y rotate $rotate_Bpred islands $island_pred\n"; # $bad++; # } # } # ok ($bad, 0); # } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/HexSpiralSkewed.t0000644000175000017500000000565413734026645015424 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 23; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::HexSpiralSkewed; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::HexSpiralSkewed::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::HexSpiralSkewed->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::HexSpiralSkewed->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::HexSpiralSkewed->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::HexSpiralSkewed->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::HexSpiralSkewed->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); my @pnames = map {$_->{'name'}} $path->parameter_info_list; ok (join(',',@pnames), 'wider,n_start'); } #------------------------------------------------------------------------------ # xy_to_n { my @data = ([1, 0,0 ], [1.25, .25,0 ], [1.5, .5,0 ], [1.75, .75,0 ], ); my $path = Math::PlanePath::HexSpiralSkewed->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x == $want_x, 1, "x at n=$n"); ok ($got_y == $want_y, 1, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; $want_n = int ($want_n + 0.5); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n == $want_n, 1, "n at x=$x,y=$y"); } } exit 0; Math-PlanePath-129/t/Columns-load.t0000644000175000017500000000201111554145242014670 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . ## no critic (RequireUseStrict, RequireUseWarnings) use Math::PlanePath::Columns; my $path = Math::PlanePath::Columns->new (height => 1000); $path->n_to_xy(123); $path->xy_to_n(0,0); $path->rect_to_n_range(0,0, 1,1); use Test; plan tests => 1; ok (1,1, 'Math::PlanePath::Columns load as first thing'); exit 0; Math-PlanePath-129/t/DivisibleColumns.t0000644000175000017500000001607313734026646015633 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 1426; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::DivisibleColumns; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::DivisibleColumns::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::DivisibleColumns->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::DivisibleColumns->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::DivisibleColumns->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::DivisibleColumns->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start(), x_negative(), y_negative() { my $path = Math::PlanePath::DivisibleColumns->new; ok ($path->n_start, 0, 'n_start() 0'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); } { my $path = Math::PlanePath::DivisibleColumns->new (n_start => 123); ok ($path->n_start, 123, 'n_start() 123'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::DivisibleColumns->parameter_info_list; ok (join(',',@pnames), 'divisor_type,n_start'); } #------------------------------------------------------------------------------ # _count_divisors() foreach my $elem ( [ 1, 1 ], # 1 itself only [ 2, 2 ], # 1,2 only [ 3, 2 ], # 1,3 [ 4, 3 ], # 1,2,4 [ 5, 2 ], # 1,5 [ 6, 4 ], # 1,2,3,6 [ 7, 2 ], # 1,7 ) { my ($x, $want) = @$elem; my $got = Math::PlanePath::DivisibleColumns::_count_divisors($x); ok ($got, $want, "_count_divisors($x)"); } foreach my $x (1 .. 500) { my $got = Math::PlanePath::DivisibleColumns::_count_divisors($x); my $want = divisors_by_mod($x); ok ($got, $want, "_count_divisors($x) vs x%y"); } sub divisors_by_mod { my ($x) = @_; my $count = 0; foreach my $y (1 .. $x) { $count += (($x % $y) == 0); } return $count; } #------------------------------------------------------------------------------ # _count_divisors_cumulative() foreach my $elem ( [ 1, 1 ], # 1 itself only [ 2, 2+1 ], # 1,2 only [ 3, 2+2+1 ], # 1,3 [ 4, 3+2+2+1 ], # 1,2,4 [ 5, 2+3+2+2+1 ], # 1,5 [ 6, 4+2+3+2+2+1 ], # 1,2,3,6 [ 7, 2+4+2+3+2+2+1 ], # 1,7 ) { my ($x, $want) = @$elem; my $got = Math::PlanePath::DivisibleColumns::_count_divisors_cumulative($x); ok ($got, $want, "_count_divisors_cumulative($x)"); } { my $want = 0; foreach my $x (1 .. 500) { my $got = Math::PlanePath::DivisibleColumns::_count_divisors_cumulative($x); $want += divisors_by_mod($x); ok ($got, $want, "_count_divisors($x) vs x%y"); } } #------------------------------------------------------------------------------ # first few points # { # my @data = ([ 0, 1,1 ], # [ 1, 2,1 ], # # [ 2, 3,1 ], # [ 3, 3,2 ], # # [ 4, 4,1 ], # [ 5, 4,3 ], # # [ 6, 5,1 ], # [ 7, 5,2 ], # [ 8, 5,3 ], # [ 9, 5,4 ], # # [ 10, 6,1 ], # [ 11, 6,5 ], # # [ 12, 7,1 ], # # [ -0.5, 1, .5 ], # [ -0.25, 1, .75 ], # [ .25, 1, 1.25 ], # ); # my $path = Math::PlanePath::DivisibleColumns->new; # foreach my $elem (@data) { # my ($n, $want_x, $want_y) = @$elem; # my ($got_x, $got_y) = $path->n_to_xy ($n); # ok ($got_x, $want_x, "n_to_xy() x at n=$n"); # ok ($got_y, $want_y, "n_to_xy() y at n=$n"); # } # # foreach my $elem (@data) { # my ($want_n, $x, $y) = @$elem; # next unless $want_n==int($want_n); # my $got_n = $path->xy_to_n ($x, $y); # ok ($got_n, $want_n, "n at x=$x,y=$y"); # } # # foreach my $elem (@data) { # my ($n, $x, $y) = @$elem; # my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); # next unless $n==int($n); # ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); # ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); # } # } # # # #------------------------------------------------------------------------------ # # xy_to_n() distinct n # # { # my $path = Math::PlanePath::DivisibleColumns->new; # my $bad = 0; # my %seen; # my $xlo = -5; # my $xhi = 100; # my $ylo = -5; # my $yhi = 100; # my ($nlo, $nhi) = $path->rect_to_n_range($xlo,$ylo, $xhi,$yhi); # my $count = 0; # OUTER: for (my $x = $xlo; $x <= $xhi; $x++) { # for (my $y = $ylo; $y <= $yhi; $y++) { # next if ($x ^ $y) & 1; # my $n = $path->xy_to_n ($x,$y); # next if ! defined $n; # sparse # # if ($seen{$n}) { # MyTestHelpers::diag ("x=$x,y=$y n=$n seen before at $seen{$n}"); # last if $bad++ > 10; # } # if ($n < $nlo) { # MyTestHelpers::diag ("x=$x,y=$y n=$n below nlo=$nlo"); # last OUTER if $bad++ > 10; # } # if ($n > $nhi) { # MyTestHelpers::diag ("x=$x,y=$y n=$n above nhi=$nhi"); # last OUTER if $bad++ > 10; # } # $seen{$n} = "$x,$y"; # $count++; # } # } # ok ($bad, 0, "xy_to_n() coverage and distinct, $count points"); # } #------------------------------------------------------------------------------ # rect_to_n_range() exact along X=Y diagonal { my $path = Math::PlanePath::DivisibleColumns->new; foreach my $i (1 .. 200) { my ($nlo, $nhi) = $path->rect_to_n_range($i,0, $i,$i); my $want_n_lo = $path->xy_to_n($i,1); my $want_n_hi = $path->xy_to_n($i,$i); ok ($nlo, $want_n_lo, "nlo exact on X=$i,Y=1"); ok ($nhi, $want_n_hi, "nhi exact on X=Y=$i line"); } } exit 0; Math-PlanePath-129/t/NumSeq-PlanePathTurn.t0000644000175000017500000001520413603033763016277 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments '###'; my $test_count = (tests => 80)[1]; plan tests => $test_count; if (! eval { require Math::NumSeq; 1 }) { MyTestHelpers::diag ('skip due to Math::NumSeq not available -- ',$@); foreach (1 .. $test_count) { skip ('due to no Math::NumSeq', 1, 1); } exit 0; } require Math::NumSeq::PlanePathTurn; #------------------------------------------------------------------------------ # _turn_func_TTurn3() # ok (Math::NumSeq::PlanePathTurn::_turn_func_TTurn3(2,0, 1,1), .5, # 'turn +60'); # ok (Math::NumSeq::PlanePathTurn::_turn_func_TTurn3(2,0, -1,1), 1, # 'turn +120'); # ok (Math::NumSeq::PlanePathTurn::_turn_func_TTurn3(2,0, -1,-1), 2, # 'turn +240'); #------------------------------------------------------------------------------ # _turn_func_Left() ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(1,0, 0,1), 1, 'left 90 from X axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(1,0, -5,5), 1, 'left 135 from X axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(1,0, 1,0), 0, 'straight along X axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(0,1, -1,0), 1, 'left 90 from Y axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(0,1, -2,2), 1, 'left 45 from Y axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(0,1, -4,-4), 1, 'left 135 from Y axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(0,1, 0,1), 0, 'straight along Y axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(-1,0, 0,-1), 1, 'left 90 from X neg axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(-1,0, -1,0), 0, 'straight along X neg axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(0,-1, 1,0), 1, 'left 90 from Y neg axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(0,-1, 1,-1), 1, 'left 45 from Y neg axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(0,-1, 1,1), 1, 'left 135 from Y neg axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(0,-1, -1,-1), 0, 'right 45 from Y neg axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(0,-1, -1,1), 0, 'right 135 from Y neg axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(0,-1, 0,-1), 0, 'straight along Y neg axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(1,0, 0,-1), 0, 'right 90 from X axis'); ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(1,0, -1,0), 0); # straight opposite 180 ok (Math::NumSeq::PlanePathTurn::_turn_func_Left(0,1, 0,-1), 0); # straight opposite 180 #------------------------------------------------------------------------------ # _turn_func_Right() ok (Math::NumSeq::PlanePathTurn::_turn_func_Right(1,0, 0,1), 0); # left 90 ok (Math::NumSeq::PlanePathTurn::_turn_func_Right(1,0, 1,0), 0); # straight ok (Math::NumSeq::PlanePathTurn::_turn_func_Right(1,0, 0,-1), 1); # right 90 ok (Math::NumSeq::PlanePathTurn::_turn_func_Right(1,0, -1,0), 0); # straight opposite 180 ok (Math::NumSeq::PlanePathTurn::_turn_func_Right(0,1, 0,-1), 0); # straight opposite 180 #------------------------------------------------------------------------------ # _turn_func_LSR() ok (Math::NumSeq::PlanePathTurn::_turn_func_LSR(1,0, 1,0), 0); # straight ok (Math::NumSeq::PlanePathTurn::_turn_func_LSR(1,0, 0,1), 1); # left 90 ok (Math::NumSeq::PlanePathTurn::_turn_func_LSR(1,0, 0,-1), -1); # right 90 ok (Math::NumSeq::PlanePathTurn::_turn_func_LSR(1,0, -1,0), 0); # straight opposite 180 ok (Math::NumSeq::PlanePathTurn::_turn_func_LSR(0,1, 0,-1), 0); # straight opposite 180 #------------------------------------------------------------------------------ # values_min(), values_max() foreach my $elem ([0,1, planepath => 'SquareSpiral' ], # default turn_type=>Left [0,1, planepath => 'SquareSpiral', turn_type => 'LSR' ], [0,1, planepath => 'HilbertCurve', turn_type => 'Left' ], [-1,1, planepath => 'HilbertCurve', turn_type => 'LSR' ], [0,1, planepath => 'CellularRule54', turn_type => 'Left' ], [-1,1, planepath => 'CellularRule54', turn_type => 'LSR' ], [0,1, planepath => 'CellularRule190', turn_type => 'Left' ], [-1,1, planepath => 'CellularRule190', turn_type => 'LSR' ], [0,1, planepath => 'Rows,width=6', turn_type => 'Left' ], [-1,1, planepath => 'Rows,width=6', turn_type => 'LSR' ], [0,1, planepath => 'Columns,height=6', turn_type => 'Left' ], [-1,1, planepath => 'Columns,height=6', turn_type => 'LSR' ], # step=0 vertical on Y axis only [0,0, planepath=>'PyramidRows,step=0', turn_type => 'Left' ], [0,0, planepath=>'PyramidRows,step=0', turn_type => 'LSR' ], [0,1, planepath=>'PyramidRows,step=1', turn_type => 'Left' ], [-1,1, planepath=>'PyramidRows,step=1', turn_type => 'LSR' ], # right line [0,0, planepath=>'CellularRule,rule=16', turn_type=>'Left' ], [0,0, planepath=>'CellularRule,rule=16', turn_type=>'LSR' ], # centre line Y axis only [0,0, planepath=>'CellularRule,rule=4', turn_type => 'Left' ], [0,0, planepath=>'CellularRule,rule=4', turn_type => 'LSR' ], # left line [0,0, planepath=>'CellularRule,rule=2', turn_type=>'Left' ], [0,0, planepath=>'CellularRule,rule=2', turn_type=>'LSR' ], # left solid [0,1, planepath=>'CellularRule,rule=206', turn_type=>'Left' ], [-1,1, planepath=>'CellularRule,rule=206', turn_type=>'LSR' ], # odd solid [0,1, planepath=>'CellularRule,rule=50',turn_type=>'Left' ], [-1,1, planepath=>'CellularRule,rule=50',turn_type=>'LSR' ], ) { my ($want_min,$want_max, @parameters) = @$elem; ### @parameters ### $want_min ### $want_max my $seq = Math::NumSeq::PlanePathTurn->new (@parameters); ok ($seq->values_min, $want_min, "values_min() ".join(',',@parameters)); ok ($seq->values_max, $want_max, "values_max() ".join(',',@parameters)); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/CoprimeColumns.t0000644000175000017500000001563113734026647015317 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 236; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::CoprimeColumns; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::CoprimeColumns::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::CoprimeColumns->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::CoprimeColumns->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::CoprimeColumns->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::CoprimeColumns->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::CoprimeColumns->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); ok ($path->class_x_negative, 0, 'class_x_negative() instance method'); ok ($path->class_y_negative, 0, 'class_y_negative() instance method'); } { my $path = Math::PlanePath::CoprimeColumns->new (n_start => 37); ok ($path->n_start, 37, 'n_start() 37'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::CoprimeColumns->parameter_info_list; ok (join(',',@pnames), 'direction,n_start'); } #------------------------------------------------------------------------------ # _coprime() foreach my $elem ([ 1,1, 1 ], [ 6,1, 1 ], [ 6,2, 0 ], [ 6,3, 0 ], [ 6,4, 0 ], [ 6,5, 1 ], ) { my ($x,$y, $want) = @$elem; { my $got = Math::PlanePath::CoprimeColumns::_coprime($x,$y); $got = $got-0; ok ($got, $want, "_coprime($x,$y)"); } { my $got = Math::PlanePath::CoprimeColumns::_coprime($y,$x); $got = $got-0; ok ($got, $want, "_coprime($x,$y)"); } } #------------------------------------------------------------------------------ # _totient() foreach my $elem ( [ 1, 1 ], # 1 itself only [ 2, 1 ], # 1 only [ 3, 2 ], # 1,2 [ 4, 2 ], # 1,3 [ 5, 4 ], # 1,2,3,4 [ 6, 2 ], # 1,5 ) { my ($x, $want) = @$elem; my $got = Math::PlanePath::CoprimeColumns::_totient($x); ok ($got, $want, "_totient($x)"); } foreach my $x (1 .. 100) { my $want = 0; foreach my $y (1 .. $x) { $want += Math::PlanePath::CoprimeColumns::_coprime($x,$y); } my $got = Math::PlanePath::CoprimeColumns::_totient($x); ok ($got, $want, "_totient($x) vs _coprime()"); } #------------------------------------------------------------------------------ # first few points { my @elems = ([ [], [ 0, 1,1 ], [ 1, 2,1 ], [ 2, 3,1 ], [ 3, 3,2 ], [ 4, 4,1 ], [ 5, 4,3 ], [ 6, 5,1 ], [ 7, 5,2 ], [ 8, 5,3 ], [ 9, 5,4 ], [ 10, 6,1 ], [ 11, 6,5 ], [ 12, 7,1 ], [ -0.5, 1, .5 ], [ -0.25, 1, .75 ], [ .25, 1, 1.25 ], ], [ [ direction => 'down' ], [ 2, 3,2 ], [ 3, 3,1 ], [ 6, 5,4 ], [ 7, 5,3 ], [ 8, 5,2 ], [ 9, 5,1 ], ], ); foreach my $elem (@elems) { my ($options_aref, @data) = @$elem; my $path = Math::PlanePath::CoprimeColumns->new (@$options_aref); foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "n_to_xy() x at n=$n"); ok ($got_y, $want_y, "n_to_xy() y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next unless $want_n==int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } foreach my $elem (@data) { my ($n, $x, $y) = @$elem; my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); next unless $n==int($n); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } #------------------------------------------------------------------------------ # xy_to_n() distinct n { my $path = Math::PlanePath::CoprimeColumns->new; my $bad = 0; my %seen; my $xlo = -5; my $xhi = 100; my $ylo = -5; my $yhi = 100; my ($nlo, $nhi) = $path->rect_to_n_range($xlo,$ylo, $xhi,$yhi); my $count = 0; OUTER: for (my $x = $xlo; $x <= $xhi; $x++) { for (my $y = $ylo; $y <= $yhi; $y++) { next if ($x ^ $y) & 1; my $n = $path->xy_to_n ($x,$y); next if ! defined $n; # sparse if ($seen{$n}) { MyTestHelpers::diag ("x=$x,y=$y n=$n seen before at $seen{$n}"); last if $bad++ > 10; } if ($n < $nlo) { MyTestHelpers::diag ("x=$x,y=$y n=$n below nlo=$nlo"); last OUTER if $bad++ > 10; } if ($n > $nhi) { MyTestHelpers::diag ("x=$x,y=$y n=$n above nhi=$nhi"); last OUTER if $bad++ > 10; } $seen{$n} = "$x,$y"; $count++; } } ok ($bad, 0, "xy_to_n() coverage and distinct, $count points"); } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::CoprimeColumns->new; my ($nlo, $nhi) = $path->rect_to_n_range(1,1, -2,1); ok ($nlo <= 0, 1, "nlo $nlo"); ok ($nhi >= 0, 1, "nhi $nhi"); } exit 0; Math-PlanePath-129/t/FactorRationals.t0000644000175000017500000001452013734026646015446 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 291; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::FactorRationals; my $path = Math::PlanePath::FactorRationals->new; my $n_start = $path->n_start; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::FactorRationals::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::FactorRationals->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::FactorRationals->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::FactorRationals->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { ok ($n_start, 1, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::FactorRationals->parameter_info_list; ok (join(',',@pnames), 'factor_coding'); } #------------------------------------------------------------------------------ # first few points { my @data = ( [ 2**31, 1, 2**16 ], [ 2**200, 2**100, 1 ], [ undef, 0,0 ], [ undef, 1,0 ], [ undef, 2,0 ], [ undef, 3,0 ], [ undef, 0,1 ], [ undef, 0,2 ], [ undef, 0,3 ], [ 1, 1,1 ], [ 2, 1,2 ], [ 4, 2,1 ], [ 8, 1,4 ], [ 16, 4,1 ], [ 32, 1,8 ], [ 64, 8,1 ], [ 3, 1,3 ], [ 9, 3,1 ], [ 27, 1,9 ], [ 81, 9,1 ], [ 45, 3,5 ], # 3^2*5 # Listing in Umberto Cerruti "Ordinare i Razionali: Gli Alberi # di Keplero e di Calkin-Wilf" # http://www.dm.unito.it/~cerruti/pdfblog/ordinamenti.pdf # http://www.dm.unito.it/~cerruti/mathblog.html # But inverted since mapping even B(2k)=-k odd B(2k-1)=k. [ 1, 1,1 ], [ 2, 1,2 ], [ 3, 1,3 ], [ 4, 2,1 ], [ 5, 1,5 ], [ 6, 1,6 ], [ 7, 1,7 ], [ 8, 1,4 ], [ 9, 3,1 ], [ 10, 1,10 ], [ 11, 1,11 ], [ 12, 2,3 ], [ 13, 1,13 ], [ 14, 1,14 ], [ 15, 1,15 ], [ 16, 4,1 ], [ 17, 1,17 ], [ 18, 3,2 ], [ 19, 1,19 ], [ 20, 2,5 ], [ 21, 1,21 ], [ 22, 1,22 ], [ 23, 1,23 ], [ 24, 1,12 ], [ 25, 5,1 ], [ 26, 1,26 ], [ 27, 1,9 ], [ 28, 2,7 ], [ 29, 1,29 ], [ 30, 1,30 ], [ 66395120, 44,1805 ], ); foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; next if ! defined $n; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "n_to_xy() x at n=$n"); ok ($got_y, $want_y, "n_to_xy() y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next if defined $want_n && $want_n!=int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "xy_to_n() at x=$x,y=$y"); } foreach my $elem (@data) { my ($n, $x, $y) = @$elem; next unless defined $n && $n==int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo >= $n_start, 1, "rect_to_n_range() nlo=$got_nlo < n_start at n=$n,x=$x,y=$y"); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } #------------------------------------------------------------------------------ # xy_to_n() distinct n { my $bad = 0; my %seen; my $xlo = -5; my $xhi = 100; my $ylo = -5; my $yhi = 100; my ($nlo, $nhi) = $path->rect_to_n_range($xlo,$ylo, $xhi,$yhi); my $count = 0; OUTER: for (my $x = $xlo; $x <= $xhi; $x++) { for (my $y = $ylo; $y <= $yhi; $y++) { next if ($x ^ $y) & 1; my $n = $path->xy_to_n ($x,$y); next if ! defined $n; # sparse if ($seen{$n}) { MyTestHelpers::diag ("x=$x,y=$y n=$n seen before at $seen{$n}"); last if $bad++ > 10; } if ($n < $nlo) { MyTestHelpers::diag ("x=$x,y=$y n=$n below nlo=$nlo"); last OUTER if $bad++ > 10; } if ($n > $nhi) { MyTestHelpers::diag ("x=$x,y=$y n=$n above nhi=$nhi"); last OUTER if $bad++ > 10; } $seen{$n} = "$x,$y"; $count++; } } ok ($bad, 0, "xy_to_n() coverage and distinct, $count points"); } #------------------------------------------------------------------------------ # rect_to_n_range() { my ($nlo, $nhi) = $path->rect_to_n_range(1,1, -2,1); ### $nlo ### $nhi ok ($nlo <= 1, 1, "nlo $nlo"); ok ($nhi >= 1, 1, "nhi $nhi"); } exit 0; Math-PlanePath-129/t/UlamWarburton.t0000644000175000017500000002076213734026641015155 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 1071; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::UlamWarburton; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::UlamWarburton::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::UlamWarburton->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::UlamWarburton->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::UlamWarburton->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::UlamWarburton->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::UlamWarburton->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 1); ok ($n_hi, 5); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 1); ok ($n_hi, 21); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 1); ok ($n_hi, 85); } } { my $path = Math::PlanePath::UlamWarburton->new (parts => '2'); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 1); ok ($n_hi, 4); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 1); ok ($n_hi, 14); } } { my $path = Math::PlanePath::UlamWarburton->new (parts => '1'); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 1); ok ($n_hi, 3); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 1); ok ($n_hi, 9); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 1); ok ($n_hi, 29); } } # depth=2-1=1 is level=0 # depth=4-1=3 is level=1 # depth=8-1=7 is level=2 # foreach my $parts ('4','2','1') { foreach my $n_start (1 # , -10, 39 ) { my $path = Math::PlanePath::UlamWarburton->new (parts => $parts, n_start => $n_start); foreach my $level (0 .. 6) { my ($n_lo,$n_hi) = $path->level_to_n_range($level); my $depth = 2**($level+1) - 1; my $n_end = $path->tree_depth_to_n_end($depth); ok ($n_hi,$n_end, "parts=$parts n_start=$n_start depth=$depth level=$level"); } } } #------------------------------------------------------------------------------ # tree_depth_to_n(), tree_n_to_depth() # 25 # 23 # 19 22 # 15 61 # 11 14 18 60 # 9 59 57 # 5 8 58 17 56 # 3 7 13 19 # 1 2 4 6 10 12 16 18 24 # 1, 3, 5, 9, 11, 15, 19, 29, 31, 35, 39, 49, 53, 63, 73, 101, 103, 107, # 2 4 6 10 12 16 20 30 { my @groups = ([ [ parts => 1 ], [ 8, 30 ], # +1+1 [ 0, 1 ], # [ 1, 2 ], # +1+1 [ 2, 4 ], # +1+1 [ 3, 6 ], # +3+1 [ 4, 10 ], # +1+1 [ 5, 12 ], # +3+1 [ 6, 16 ], # +3+1 [ 7, 20 ], # +9+1 [ 8, 30 ], # +1+1 [ 9, 32 ], [ 15, 74 ], ], [ [ ], # default parts=4 [ 0, 1 ], # +1 [ 1, 2 ], # +4 [ 2, 6 ], # +4 [ 3, 10 ], # +12 [ 4, 22 ], # +4 [ 5, 26 ], # +12 [ 6, 38 ], # +12 [ 7, 50 ], # +36 [ 8, 86 ], # +4 [ 9, 90 ], ], ); foreach my $n_start (1, 0, 37, -3) { foreach my $group (@groups) { my ($options, @data) = @$group; my $path = Math::PlanePath::UlamWarburton->new (@$options, n_start => $n_start); foreach my $elem (@data) { my ($depth, $n) = @$elem; $n = $n - 1 + $n_start; # table is in N=1 basis, adjust { my $want_n = $n; my $got_n = $path->tree_depth_to_n ($depth); ok ($got_n, $want_n, "n_start=$n_start tree_depth_to_n() depth=$depth"); } { my $want_depth = $depth; my $got_depth = $path->tree_n_to_depth ($n); ok ($got_depth, $want_depth, "@$options n_start=$n_start tree_n_to_depth() n=$n"); } if ($depth > 0) { my $want_depth = $depth - 1; my $got_depth = $path->tree_n_to_depth ($n-1); ok ($got_depth, $want_depth, "@$options n_start=$n_start tree_n_to_depth() n=$n"); } } } } } #------------------------------------------------------------------------------ # tree_n_children() { my @data = ([ 1, '2,3,4,5' ], [ 2, '6' ], [ 3, '7' ], [ 4, '8' ], [ 5, '9' ], [ 6, '10,11,12' ], [ 7, '13,14,15' ], [ 8, '16,17,18' ], [ 9, '19,20,21' ], ); my $path = Math::PlanePath::UlamWarburton->new; foreach my $elem (@data) { my ($n, $want_n_children) = @$elem; my $got_n_children = join(',',$path->tree_n_children($n)); ok ($got_n_children, $want_n_children, "tree_n_children($n)"); } } #------------------------------------------------------------------------------ # tree_n_parent() { my @data = ([ 1, undef ], [ 2, 1 ], [ 3, 1 ], [ 4, 1 ], [ 5, 1 ], [ 6, 2 ], [ 7, 3 ], [ 8, 4 ], [ 9, 5 ], [ 10, 6 ], [ 11, 6 ], [ 12, 6 ], [ 13, 7 ], [ 14, 7 ], [ 15, 7 ], ); my $path = Math::PlanePath::UlamWarburton->new; foreach my $elem (@data) { my ($n, $want_n_parent) = @$elem; my $got_n_parent = $path->tree_n_parent ($n); ok ($got_n_parent, $want_n_parent); } } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::UlamWarburton->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } #------------------------------------------------------------------------------ # random points { my $path = Math::PlanePath::UlamWarburton->new; for (1 .. 50) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } ok ($rev_n, $n, "xy_to_n($x,$y) reverse to expect n=$n, got $rev_n"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() reverse n=$n cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() reverse n=$n cf got n_hi=$n_hi"); } } #------------------------------------------------------------------------------ # x,y coverage { my $path = Math::PlanePath::UlamWarburton->new; foreach my $x (-10 .. 10) { foreach my $y (-10 .. 10) { my $n = $path->xy_to_n ($x,$y); next if ! defined $n; my ($nx,$ny) = $path->n_to_xy ($n); ok ($nx, $x, "xy_to_n($x,$y)=$n then n_to_xy() reverse"); ok ($ny, $y, "xy_to_n($x,$y)=$n then n_to_xy() reverse"); } } } exit 0; Math-PlanePath-129/t/KochelCurve.t0000644000175000017500000000712313734026644014564 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 132; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::KochelCurve; my $path = Math::PlanePath::KochelCurve->new; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::KochelCurve::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::KochelCurve->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::KochelCurve->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::KochelCurve->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 0, 'x_negative() instance method'); ok ($path->y_negative, 0, 'y_negative() instance method'); my @pnames = map {$_->{'name'}} $path->parameter_info_list; ok (join(',',@pnames), '', 'parameter_info_list() keys'); } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::KochelCurve->new; for (1 .. 20) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($n) frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($n) frac $frac, y"); } } } #------------------------------------------------------------------------------ # many fracs { my $path = Math::PlanePath::KochelCurve->new; my ($x,$y) = $path->n_to_xy (0); my $bad = 0; my $pow = 5; for my $n (0 .. 4**$pow+5) { my ($x2,$y2) = $path->n_to_xy ($n+1); my $frac = 0.25; my $want_xf = $x + ($x2-$x)*$frac; my $want_yf = $y + ($y2-$y)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); if ($got_xf != $want_xf || $got_yf != $want_yf) { MyTestHelpers::diag ("wrong at n=$n got $got_xf,$got_yf want $want_xf,$want_yf"); if ($bad++ > 10) { last; } } ($x,$y) = ($x2,$y2); } ok ($bad, 0); } exit 0 ; Math-PlanePath-129/t/PeanoCurve-load.t0000644000175000017500000000201311554145271015323 0ustar gggg#!/usr/bin/perl -w # Copyright 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . ## no critic (RequireUseStrict, RequireUseWarnings) use Math::PlanePath::PeanoCurve; my $path = Math::PlanePath::PeanoCurve->new (width => 1000); $path->n_to_xy(123); $path->xy_to_n(0,0); $path->rect_to_n_range(0,0, 1,1); use Test; plan tests => 1; ok (1,1, 'Math::PlanePath::PeanoCurve load as first thing'); exit 0; Math-PlanePath-129/t/Corner.t0000644000175000017500000001515013734026647013604 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 535;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments; require Math::PlanePath::Corner; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::Corner::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::Corner->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::Corner->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::Corner->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::Corner->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::Corner->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); ok ($path->class_x_negative, 0, 'class_x_negative() instance method'); ok ($path->class_y_negative, 0, 'class_y_negative() instance method'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::Corner->parameter_info_list; ok (join(',',@pnames), 'wider,n_start'); } #------------------------------------------------------------------------------ # first few points { my @data = ( [ 0, # wider==0 [ 1, 0,0, 4 ], [ 2, 0,1, 6 ], [ 3, 1,1, 8 ], [ 4, 1,0, 8 ], [ 5, 0,2, 10 ], [ 6, 1,2, 10 ], [ 7, 2,2, 12 ], [ 8, 2,1, 12 ], [ 9, 2,0, 12 ], [ 10, 0,3, 14 ], [ 11, 1,3, 14 ], [ 12, 2,3, 14 ], [ 13, 3,3, 16 ], [ 14, 3,2, 16 ], [ 15, 3,1, 16 ], [ 16, 3,0, 16 ], ], [ 1, # wider==1 [ 1, 0,0 ], [ 2, 1,0 ], [ 3, 0,1 ], [ 4, 1,1 ], [ 5, 2,1 ], [ 6, 2,0 ], [ 7, 0,2 ], [ 8, 1,2 ], [ 9, 2,2 ], [ 10, 3,2 ], [ 11, 3,1 ], [ 12, 3,0 ], ], [ 2, # wider==2 [ 1, 0,0, 4 ], [ 2, 1,0, 6 ], [ 3, 2,0, 8 ], [ 4, 0,1, 10 ], [ 5, 1,1, 10 ], [ 6, 2,1, 10 ], [ 7, 3,1, 12 ], [ 8, 3,0, 12 ], [ 9, 0,2, 14 ], [ 10, 1,2, 14 ], [ 11, 2,2, 14 ], [ 12, 3,2, 14 ], [ 13, 4,2, 16 ], [ 14, 4,1, 16 ], [ 15, 4,0, 16 ], ], [ 10, # wider==10 [ 1, 0,0, 4 ], [ 10, 9,0, 22 ], [ 11, 10,0, 24 ], [ 12, 0,1, 26 ], [ 13, 1,1, 26 ], ], ); foreach my $we (@data) { my ($wider, @wdata) = @$we; my $path = Math::PlanePath::Corner->new (wider => $wider); ### $wider ### @wdata foreach my $elem (@wdata) { my ($n, $x,$y, $boundary) = @$elem; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() wider=$wider x at n=$n"); ok ($got_y, $y, "n_to_xy() wider=$wider y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() wider=$wider n at x=$x,y=$y"); } if ($n == int($n)) { { my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range(0,0,$x,$y) wider=$wider for n=$n, got_nlo=$got_nlo"); ok ($got_nhi >= $n, 1, "rect_to_n_range(0,0,$x,$y) wider=$wider for n=$n, got_nhi=$got_nhi"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($got_nlo, $n, "rect_to_n_range($x,$y,$x,$y) wider=$wider for n=$n, got_nlo=$got_nlo"); ok ($got_nhi, $n, "rect_to_n_range($x,$y,$x,$y) wider=$wider for n=$n, got_nhi=$got_nhi"); } } if (defined $boundary) { my $got_boundary = $path->_NOTDOCUMENTED_n_to_figure_boundary($n); ok ($got_boundary, $boundary, "n=$n xy=$x,$y"); } } } } #------------------------------------------------------------------------------ # random points { for (1 .. 50) { my $wider = int(rand(50)); # 0 to 50, inclusive my $path = Math::PlanePath::Corner->new (wider => $wider); my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } ok ($rev_n, $n, "xy_to_n($x,$y) wider=$wider reverse to expect n=$n, got $rev_n"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo, $n, "rect_to_n_range() wider=$wider reverse n=$n cf got n_lo=$n_lo"); ok ($n_hi, $n, "rect_to_n_range() wider=$wider reverse n=$n cf got n_hi=$n_hi"); } } exit 0; Math-PlanePath-129/t/DragonMidpoint.t0000644000175000017500000001304713734026646015274 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 576; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::DragonMidpoint; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::DragonMidpoint::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::DragonMidpoint->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::DragonMidpoint->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::DragonMidpoint->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::DragonMidpoint->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # xy_to_n() reversal foreach my $arms (1 .. 4) { my $path = Math::PlanePath::DragonMidpoint->new (arms => $arms); for my $x (0 .. 5) { for my $y (0 .. 5) { my $n = $path->xy_to_n ($x,$y); next if ! defined $n; my ($nx,$ny) = $path->n_to_xy ($n); ok ($nx, $x, "arms=$arms xy=$x,$y n=$n cf nxy=$nx,$ny"); ok ($ny, $y, "arms=$arms xy=$x,$y n=$n cf nxy=$nx,$ny"); } } } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::DragonMidpoint->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::DragonMidpoint->parameter_info_list; ok (join(',',@pnames), 'arms'); } #------------------------------------------------------------------------------ # first few points { my @data = ( [ 0, 0,0 ], [ 1, 1,0 ], [ 2, 1,1 ], [ 3, 1,2 ], [ 4, 1,3 ], [ 5, 0,3 ], [ 6, -1,3 ], [ 7, -1,4 ], [ 8, -1,5 ], [ 0.25, 0.25, 0 ], [ 1.25, 1, 0.25 ], [ 2.25, 1, 1.25 ], [ 3.25, 1, 2.25 ], [ 4.25, .75, 3 ], ); my $path = Math::PlanePath::DragonMidpoint->new; foreach my $elem (@data) { my ($n, $x, $y) = @$elem; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } #------------------------------------------------------------------------------ # random fracs foreach my $arms (1 .. 4) { my $path = Math::PlanePath::DragonMidpoint->new (arms => $arms); for (1 .. 5) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+$arms); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($nf) arms=$arms frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($nf) arms=$arms frac $frac, y"); } } } #------------------------------------------------------------------------------ # random points foreach my $arms (1 .. 4) { my $path = Math::PlanePath::DragonMidpoint->new (arms => $arms); for (1 .. 20) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } ok ($rev_n, $n, "xy_to_n($x,$y) reverse to expect n=$n, got $rev_n"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() reverse n=$n cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() reverse n=$n cf got n_hi=$n_hi"); } } exit 0; Math-PlanePath-129/t/AlternatePaper.t0000644000175000017500000002615013734026650015257 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 3485; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::AlternatePaper; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::AlternatePaper::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::AlternatePaper->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::AlternatePaper->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::AlternatePaper->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::AlternatePaper->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # first few values { my @data = ([ 0, 0, 0 ], [ 0.25, 0.25, 0 ], [ 0.75, 0.75, 0 ], [ 1, 1, 0 ], [ 1.25, 1, 0.25 ], [ 1.75, 1, 0.75 ], [ 2, 1, 1 ], [ 2.25, 1.25, 1 ], [ 2.75, 1.75, 1 ], [ 3, 2, 1 ], [ 3.25, 2, 0.75 ], [ 3.75, 2, 0.25 ], [ 4, 2, 0 ], ); my $path = Math::PlanePath::AlternatePaper->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next unless $want_n == int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } } #------------------------------------------------------------------------------ # n_to_xy() fracs foreach my $arms (1 .. 8) { my $path = Math::PlanePath::AlternatePaper->new (arms => $arms); foreach my $n (0 .. 64) { my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+$arms); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf == $want_xf, 1, "n_to_xy($nf) arms=$arms frac $frac, x"); ok ($got_yf == $want_yf, 1, "n_to_xy($nf) arms=$arms frac $frac, y"); } } } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::AlternatePaper->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative ? 1 : 0, 0, 'x_negative() instance method'); ok ($path->y_negative ? 1 : 0, 0, 'y_negative() instance method'); ok ($path->class_x_negative, 1, 'class_x_negative()'); ok ($path->class_y_negative, 1, 'class_y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::AlternatePaper->parameter_info_list; ok (join(',',@pnames), 'arms'); } { my $path = Math::PlanePath::AlternatePaper->new (arms => 2); ok ($path->x_negative ? 1 : 0, 0, 'x_negative() instance method'); ok ($path->y_negative ? 1 : 0, 0, 'y_negative() instance method'); } { my $path = Math::PlanePath::AlternatePaper->new (arms => 3); ok ($path->x_negative ? 1 : 0, 1, 'x_negative() instance method'); ok ($path->y_negative ? 1 : 0, 0, 'y_negative() instance method'); } { my $path = Math::PlanePath::AlternatePaper->new (arms => 4); ok ($path->x_negative ? 1 : 0, 1, 'x_negative() instance method'); ok ($path->y_negative ? 1 : 0, 0, 'y_negative() instance method'); } { my $path = Math::PlanePath::AlternatePaper->new (arms => 5); ok ($path->x_negative ? 1 : 0, 1, 'x_negative() instance method'); ok ($path->y_negative ? 1 : 0, 1, 'y_negative() instance method'); } { my $path = Math::PlanePath::AlternatePaper->new (arms => 8); ok ($path->x_negative ? 1 : 0, 1, 'x_negative() instance method'); ok ($path->y_negative ? 1 : 0, 1, 'y_negative() instance method'); } #------------------------------------------------------------------------------ # turn sequence claimed in the pod { # with Y reckoned increasing upwards sub dxdy_to_dir4 { my ($dx, $dy) = @_; if ($dx > 0) { return 0; } # east if ($dx < 0) { return 2; } # west if ($dy > 0) { return 1; } # north if ($dy < 0) { return 3; } # south } sub path_n_dir { my ($path, $n) = @_; my ($dx,$dy) = $path->n_to_dxdy($n) or die "Oops, no point at ",$n; return dxdy_to_dir4 ($dx, $dy); } # return 1 for left, 0 for right sub path_n_turn { my ($path, $n) = @_; my $prev_dir = path_n_dir ($path, $n-1); my $dir = path_n_dir ($path, $n); my $turn = ($dir - $prev_dir) % 4; if ($turn == 1) { return 1; } # left if ($turn == 3) { return 0; } # right die "Oops, unrecognised turn"; } # return 1 for left, 0 for right sub calc_n_turn { my ($n) = @_; die if $n == 0; my $pos = 0; while (($n % 2) == 0) { $n = int($n/2); # skip low 0s $pos++; } $n = int($n/2); # skip lowest 1 $pos++; return ($n % 2) ^ ($pos % 2); # next bit and its pos are the turn } # return 1 for left, 0 for right sub calc_n_next_turn { my ($n) = @_; die if $n == 0; my $pos = 0; while (($n % 2) == 1) { $n = int($n/2); # skip low 1s $pos++; } $n = int($n/2); # skip lowest 0 $pos++; return ($n % 2) ^ ($pos % 2); # next bit and its pos are the turn } my $bad = 0; my $path = Math::PlanePath::AlternatePaper->new; foreach my $n ($path->n_start + 1 .. 500) { { my $path_turn = path_n_turn ($path, $n); my $calc_turn = calc_n_turn ($n); if ($path_turn != $calc_turn) { MyTestHelpers::diag ("turn n=$n path $path_turn calc $calc_turn"); last if $bad++ > 10; } } { my $path_turn = path_n_turn ($path, $n+1); my $calc_turn = calc_n_next_turn ($n); if ($path_turn != $calc_turn) { MyTestHelpers::diag ("next turn n=$n path $path_turn calc $calc_turn"); last if $bad++ > 10; } } } ok ($bad, 0, "turn sequence"); } #------------------------------------------------------------------------------ # xy_to_n_list() { my @groups = ([ 1, # arms=1 [ 0,1, [] ], [ -1,0, [] ], [ -1,-1, [] ], [ 1,-1, [] ], [ 0,0, [0] ], [ 1,0, [1] ], [ 1,1, [2] ], [ 2,1, [3,7] ], [ 2,0, [4] ], [ 3,0, [5] ], [ 3,1, [6,14] ], # 2,1 7 [ 2,2, [8] ], [ 3,2, [9,13] ], [ 3,3, [10] ], [ 4,3, [11,31] ], [ 4,2, [12,28] ], # 3,2 13 # 3,1 14 [ 4,1, [15,27] ], [ 4,0, [16] ], [ 5,0, [17] ], [ 5,1, [18,26] ], [ 6,1, [19,23] ], [ 6,0, [20] ], [ 7,0, [21] ], [ 7,1, [22,62] ], [ 4,4, [32] ], [ 8,0, [64] ], [ 9,0, [65] ], ], [ 2, # arms=2 [ 0,1, [3] ], [ -1,0, [] ], [ -1,-1, [] ], [ 1,-1, [] ], [ 0,0, [0,1] ], [ 1,0, [2] ], [ 1,1, [4,5] ], # [ 2,1, [3,7] ], # [ 2,0, [4] ], # [ 3,0, [5] ], # [ 3,1, [6,14] ], # # 2,1 7 # [ 2,2, [8] ], # [ 3,2, [9,13] ], # # [ 3,3, [10] ], # # [ 4,3, [11,31] ], # [ 4,2, [12,28] ], # # 3,2 13 # # 3,1 14 # [ 4,1, [15,27] ], # [ 4,0, [16] ], # [ 5,0, [17] ], # [ 5,1, [18,26] ], # [ 6,1, [19,23] ], # [ 6,0, [20] ], # [ 7,0, [21] ], # [ 7,1, [22,62] ], # # [ 4,4, [32] ], # # [ 8,0, [64] ], # [ 9,0, [65] ], ], [ 3, # arms=3 [ 0,0, [0,1] ], [ 0,1, [2,4] ], [ 1,0, [3] ], [ 1,1, [6,7] ], ], ); foreach my $group (@groups) { my ($arms, @data) = @$group; my $path = Math::PlanePath::AlternatePaper->new (arms => $arms); foreach my $elem (@data) { my ($x,$y, $want_n_aref) = @$elem; my $want_n_str = join(',', @$want_n_aref); { my @got_n_list = $path->xy_to_n_list($x,$y); ok (scalar(@got_n_list), scalar(@$want_n_aref), "arms=$arms xy=$x,$y"); my $got_n_str = join(',', @got_n_list); ok ($got_n_str, $want_n_str); } { my $got_n = $path->xy_to_n($x,$y); ok ($got_n, $want_n_aref->[0]); } { my @got_n = $path->xy_to_n($x,$y); ok (scalar(@got_n), 1); ok ($got_n[0], $want_n_aref->[0]); } } } } #------------------------------------------------------------------------------ # random rect_to_n_range() foreach my $arms (1 .. 8) { my $path = Math::PlanePath::AlternatePaper->new (arms => $arms); for (1 .. 5) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)); # 0 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); ok (defined $rev_n, 1, "arms=$arms xy_to_n($x,$y) reverse n, got undef"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() n=$n at xy=$x,$y cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() n=$n at xy=$x,$y cf got n_hi=$n_hi"); } } exit 0; Math-PlanePath-129/t/TheodorusSpiral.t0000644000175000017500000000666213734026641015505 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 28;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::TheodorusSpiral; my $path = Math::PlanePath::TheodorusSpiral->new; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::TheodorusSpiral::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::TheodorusSpiral->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::TheodorusSpiral->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::TheodorusSpiral->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } #------------------------------------------------------------------------------ { my $n_save = Math::PlanePath::TheodorusSpiral::_SAVE(); my $n = 2 * $n_save - 10; my ($x1,$y1) = $path->n_to_xy ($n); $path->n_to_xy ($n + 10); # forward my ($x2,$y2) = $path->n_to_xy ($n); ok ($x1, $x2); ok ($y1, $y2); } { my ($x,$y) = $path->n_to_xy (0); ok ($x, 0); ok ($y, 0); } { my ($x,$y) = $path->n_to_xy (1); ok ($x, 1); ok ($y, 0); } { my ($x,$y) = $path->n_to_xy (2); ok ($x, 1); ok ($y, 1); } #------------------------------------------------------------------------------ # n_to_rsquared() { my $path = Math::PlanePath::TheodorusSpiral->new; ok ($path->n_to_rsquared(0), 0); { ok ($path->n_to_rsquared(0.5), 0.25); # X=0.5, Y=0 my ($x,$y) = $path->n_to_xy(0.5); ok ($path->n_to_rsquared(0.5), $x*$x+$y*$y); } ok ($path->n_to_rsquared(1), 1); { ok ($path->n_to_rsquared(1.5), 1.25); # X=1, Y=0.5 my ($x,$y) = $path->n_to_xy(1.5); ok ($path->n_to_rsquared(1.5), $x*$x+$y*$y); } { ok ($path->n_to_rsquared(2.5), 2.25); # X=1, Y=0.5 my ($x,$y) = $path->n_to_xy(2.5); ok ($path->n_to_rsquared(2.5), $x*$x+$y*$y); } { ok ($path->n_to_rsquared(123.5), 123.25); # X=1, Y=0.5 my ($x,$y) = $path->n_to_xy(123.5); ok ($path->n_to_rsquared(123.5), $x*$x+$y*$y); } } exit 0; Math-PlanePath-129/t/SierpinskiTriangle.t0000644000175000017500000001465613734026643016170 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 206; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::SierpinskiTriangle; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::SierpinskiTriangle::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::SierpinskiTriangle->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::SierpinskiTriangle->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::SierpinskiTriangle->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::SierpinskiTriangle->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::SierpinskiTriangle->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->class_x_negative, 1, 'class_x_negative()'); ok ($path->class_y_negative, 0, 'class_y_negative()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::SierpinskiTriangle->parameter_info_list; ok (join(',',@pnames), 'align,n_start'); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::SierpinskiTriangle->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 0); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 2); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 8); } { my ($n_lo,$n_hi) = $path->level_to_n_range(3); ok ($n_lo, 0); ok ($n_hi, 26); } } #------------------------------------------------------------------------------ # tree_n_parent() { my @data = ([ 0, undef ], [ 1, 0 ], [ 2, 0 ], [ 3, 1 ], [ 4, 2 ], [ 5, 3 ], [ 6, 3 ], [ 7, 4 ], [ 8, 4 ], [ 9, 5 ], [ 10, 8 ], ); foreach my $align ('triangular','left','right','diagonal') { my $path = Math::PlanePath::SierpinskiTriangle->new (align => $align); foreach my $elem (@data) { my ($n, $want_n_parent) = @$elem; my $got_n_parent = $path->tree_n_parent ($n); ok ($got_n_parent, $want_n_parent, "tree_n_parent($n) align=$align"); } } } #------------------------------------------------------------------------------ # tree_n_children() { my @data = ([ 0, '1,2' ], [ 1, '3' ], [ 2, '4' ], [ 3, '5,6' ], [ 4, '7,8' ], [ 5, '9' ], [ 6, '' ], [ 7, '' ], [ 8, '10' ], [ 9, '11,12' ], [ 10, '13,14' ], ); foreach my $align ('triangular','left','right','diagonal') { my $path = Math::PlanePath::SierpinskiTriangle->new (align => $align); foreach my $elem (@data) { my ($n, $want_n_children) = @$elem; my $got_n_children = join(',',$path->tree_n_children($n)); ok ($got_n_children, $want_n_children, "tree_n_children($n)"); } } } #------------------------------------------------------------------------------ # n_to_xy(), xy_to_n() { my @data = ([ 0, 0,0 ], [ 1, -1,1 ], [ 2, 1,1 ], [ 3, -2,2 ], [ 4, 2,2 ], [ 5, -3,3 ], [ 6, -1,3 ], [ 7, 1,3 ], [ 8, 3,3 ], [ 9, -4,4 ], [ 10, 4,4 ], [ 11, -5,5 ], [ 12, -3,5 ], [ 13, 3,5 ], [ 14, 5,5 ], [ 15, -6,6 ], [ 16, -2,6 ], [ 17, 2,6 ], [ 18, 6,6 ], ); my $path = Math::PlanePath::SierpinskiTriangle->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } foreach my $elem (@data) { my ($n, $x, $y) = @$elem; my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } #------------------------------------------------------------------------------ # n_to_xy(), xy_to_n(), reversals { my $bad = 0; my $path = Math::PlanePath::SierpinskiTriangle->new; my $want_n = $path->n_start; OUTER: foreach my $y (0 .. 16) { foreach my $x (-$y .. $y) { my $got_n = $path->xy_to_n($x,$y); if (defined $got_n) { if ($want_n != $got_n) { MyTestHelpers::diag ("reversal xy=$x,$y want_n=$want_n got_n=$got_n"); last OUTER if ++$bad > 10; } $want_n++; } } } ok ($want_n > 10, 1); ok ($bad, 0); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/TerdragonCurve.t0000644000175000017500000002666613734026642015317 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 659; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::TerdragonCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::TerdragonCurve::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::TerdragonCurve->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::TerdragonCurve->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::TerdragonCurve->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::TerdragonCurve->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # digit rotations used by xy_to_n_list() { my @digit_to_x = ([0,2,1], [0,-1,-2], [0,-1, 1]); my @digit_to_y = ([0,0,1], [0, 1, 0], [0,-1,-1]); foreach my $a (0,1,2) { my $x = $digit_to_x[0]->[$a]; my $y = $digit_to_y[0]->[$a]; ($x,$y) = (($x+3*$y)/-2, # rotate +120 ($x-$y)/2); ok ($x == $digit_to_x[1]->[$a], 1); ok ($y == $digit_to_y[1]->[$a], 1); ($x,$y) = (($x+3*$y)/-2, # rotate +120 ($x-$y)/2); ok ($x == $digit_to_x[2]->[$a], 1); ok ($y == $digit_to_y[2]->[$a], 1); } } #------------------------------------------------------------------------------ # xyxy_to_n() { my $path = Math::PlanePath::TerdragonCurve->new; ok ($path->xyxy_to_n(0,0, 2,0), 0); ok ($path->xyxy_to_n(0,0, 1,1), undef); } #------------------------------------------------------------------------------ # xy_to_n_list() { my $path = Math::PlanePath::TerdragonCurve->new (arms => 2); { my @n_list = $path->xy_to_n_list(1,1); ok (join(',',@n_list), '3,4,10'); } { my @n_list = $path->xy_to_n_list(-1,1); ok (join(',',@n_list), '5,11'); } } # at 0,0 foreach my $arms (1 .. 6) { my $path = Math::PlanePath::TerdragonCurve->new (arms => $arms); my @got_n_list = $path->xy_to_n_list(0,0); my $got_n_list = join(',',@got_n_list); my $want_n_list = join(',', 0 .. $arms-1); ok ($got_n_list, $want_n_list); } { # arms=6 points shown in the POD # # --- 8,13,31 ---------------- 7,12,30 --- # / \ / \ # \ / \ / \ / # \ / \ / \ / # --- 9,14,32 ------------- 0,1,2,3,4,5 -------------- 6,17,35 --- # / \ / \ / \ # / \ / \ / \ # \ / \ / # --- 10,15,33 ---------------- 11,16,34 --- my $path = Math::PlanePath::TerdragonCurve->new (arms => 6); foreach my $elem ([-1, 1, '8,13,31'], [ 1, 1, '7,12,30'], [-2, 0, '9,14,32'], [ 0, 0, '0,1,2,3,4,5'], [ 2, 0, '6,17,35'], [-1,-1, '10,15,33'], [ 1,-1, '11,16,34'], ) { my ($x,$y, $want_n_list) = @$elem; my @got_n_list = $path->xy_to_n_list($x,$y); my $got_n_list = join(',',@got_n_list); ok ($got_n_list, $want_n_list); } } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::TerdragonCurve->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 3); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 9); } } { my $path = Math::PlanePath::TerdragonCurve->new (arms => 5); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, (3**0 + 1)*5 - 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, (3**1 + 1)*5 - 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, (3**2 + 1)*5 - 1); } } #------------------------------------------------------------------------------ # xy_to_n_list() { my $path = Math::PlanePath::TerdragonCurve->new; foreach my $elem ([ 1, '1' ], [ 2, '2,5' ], [ 3, '3' ], [ 4, '4,7' ], [ 5, '2,5' ], [ 6, '6,15' ], [ 7, '4,7' ], [ 8, '8,11,14' ], [ 11, '8,11,14' ], [ 14, '8,11,14' ], ) { my ($n, $want_n_list) = @$elem; my ($x,$y) = $path->n_to_xy ($n); my @got_n_list = $path->xy_to_n_list ($x,$y); my $got_n_list = join(',',@got_n_list); ok ($got_n_list, $want_n_list); } } #------------------------------------------------------------------------------ # turn sequence claimed in the pod { # per KochCurve.t, 0=straight, 1=+120 degrees, 2=+240 degrees sub dxdy_to_dir3 { my ($dx,$dy) = @_; if ($dy == 0) { if ($dx == 2) { return 0/2; } # if ($dx == -2) { return 3; } } if ($dy == 1) { # if ($dx == 1) { return 1; } if ($dx == -1) { return 2/2; } } if ($dy == -1) { # if ($dx == 1) { return 5; } if ($dx == -1) { return 4/2; } } die "unrecognised $dx,$dy"; } sub path_n_dir { my ($path, $n) = @_; my ($dx,$dy) = $path->n_to_dxdy($n) or die "Oops, no point at ",$n; return dxdy_to_dir3 ($dx, $dy); } # return 0 for left, 1 for right sub path_n_turn { my ($path, $n) = @_; my $prev_dir = path_n_dir ($path, $n-1); my $dir = path_n_dir ($path, $n); return ($dir - $prev_dir + 3) % 3; # "+3" to stay +ve for "use integer" } # return 1 for left, 2 for right sub calc_n_turn { my ($n) = @_; die if $n == 0; while (($n % 3) == 0) { $n = int($n/3); # skip low 0s } return ($n % 3); # next digit is the turn } # # return 0 for left, 1 for right # sub calc_n_next_turn { # my ($n) = @_; # my $mask = $n ^ ($n+1); # low bits 000111..11 # my $z = $n & ($mask + 1); # the solitary bit above it # my $turn = ($z == 0 ? 0 : 1); # return $turn; # } my $path = Math::PlanePath::TerdragonCurve->new; my $bad = 0; foreach my $n ($path->n_start + 1 .. 500) { { my $path_turn = path_n_turn ($path, $n); my $calc_turn = calc_n_turn ($n); if ($path_turn != $calc_turn) { MyTestHelpers::diag ("turn n=$n path $path_turn calc $calc_turn"); last if $bad++ > 10; } } # { # my $path_turn = path_n_turn ($path, $n+1); # my $calc_turn = calc_n_next_turn ($n); # if ($path_turn != $calc_turn) { # MyTestHelpers::diag ("next turn n=$n path $path_turn calc $calc_turn"); # last if $bad++ > 10; # } # } } ok ($bad, 0, "turn sequence"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::TerdragonCurve->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); ok ($path->class_x_negative, 1, 'class_x_negative()'); ok ($path->class_y_negative, 1, 'class_y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::TerdragonCurve->parameter_info_list; ok (join(',',@pnames), 'arms'); } #------------------------------------------------------------------------------ # first few points { my @data = ( [ 0, 0,0 ], [ 1, 2,0 ], [ 2, 1,1 ], [ 3, 3,1 ], [ 4, 2,2 ], [ 5, 1,1, 'rep' ], [ 6, 0,2 ], [ 0.25, 0.5, 0 ], [ 1.25, 1.75, 0.25 ], [ 2.25, 1.5, 1 ], [ 3.25, 2.75, 1.25 ], ); my $path = Math::PlanePath::TerdragonCurve->new; foreach my $elem (@data) { my ($n, $x,$y, $rep) = @$elem; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n) && ! $rep) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } #------------------------------------------------------------------------------ # random rect_to_n_range() foreach my $arms (1 .. 4) { my $path = Math::PlanePath::TerdragonCurve->new (arms => $arms); ok ($path->arms_count, $arms, 'arms_count()'); for (1 .. 5) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)); # 0 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); ok (defined $rev_n, 1, "xy_to_n($x,$y) arms=$arms reverse n, got undef"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() arms=$arms n=$n at xy=$x,$y cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() arms=$arms n=$n at xy=$x,$y cf got n_hi=$n_hi"); } } #------------------------------------------------------------------------------ # random n_to_xy() fracs foreach my $arms (1 .. 4) { my $path = Math::PlanePath::TerdragonCurve->new (arms => $arms); for (1 .. 20) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+$arms); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($nf) arms=$arms frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($nf) arms=$arms frac $frac, y"); } } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/NumSeq-PlanePathCoord.t0000644000175000017500000001613713246362620016423 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2017, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } my $test_count = (tests => 46)[1]; plan tests => $test_count; if (! eval { require Math::NumSeq; 1 }) { MyTestHelpers::diag ('skip due to Math::NumSeq not available -- ',$@); foreach (1 .. $test_count) { skip ('due to no Math::NumSeq', 1, 1); } exit 0; } require Math::NumSeq::PlanePathCoord; { package MyPlanePath; use vars '@ISA'; @ISA = ('Math::PlanePath'); sub n_to_xy { my ($self, $n) = @_; return ($self->{'x'},$self->{'y'}); } } #------------------------------------------------------------------------------ # _coordinate_func_ExperimentalParity() { my $path = MyPlanePath->new; my $seq = Math::NumSeq::PlanePathCoord->new (planepath_object => $path); $path->{'x'} = 0; $path->{'y'} = 10; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalParity($seq,0), 0); $path->{'x'} = -10; $path->{'y'} = 0; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalParity($seq,0), 0); $path->{'x'} = -11; $path->{'y'} = 0; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalParity($seq,0), 1); $path->{'x'} = 1.5; $path->{'y'} = 11; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalParity($seq,0), 0.5); $path->{'x'} = 1.5; $path->{'y'} = -20; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalParity($seq,0), 1.5); } #------------------------------------------------------------------------------ # _coordinate_func_ExperimentalNumerator() # _coordinate_func_ExperimentalDenominator() { my $path = MyPlanePath->new; my $seq = Math::NumSeq::PlanePathCoord->new (planepath_object => $path); $path->{'x'} = 0; $path->{'y'} = 1; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalNumerator($seq,0), 0); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalDenominator($seq,0), 1); $path->{'x'} = -2; $path->{'y'} = 4; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalNumerator($seq,0), -1); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalDenominator($seq,0), 2); $path->{'x'} = -2; $path->{'y'} = -5; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalNumerator($seq,0), 2); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalDenominator($seq,0), 5); $path->{'x'} = 10; $path->{'y'} = 0; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalNumerator($seq,0), 1); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalDenominator($seq,0), 0); $path->{'x'} = -10; $path->{'y'} = 0; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalNumerator($seq,0), -1); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalDenominator($seq,0), 0); $path->{'x'} = 0; $path->{'y'} = 10; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalNumerator($seq,0), 0); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_ExperimentalDenominator($seq,0), 1); } #------------------------------------------------------------------------------ # _coordinate_func_IntXY() # _coordinate_func_FracXY() { my $path = MyPlanePath->new; my $seq = Math::NumSeq::PlanePathCoord->new (planepath_object => $path); $path->{'x'} = 11; $path->{'y'} = 2; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_IntXY($seq,0), 5); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_FracXY($seq,0), 1/2); $path->{'x'} = -11; $path->{'y'} = 2; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_IntXY($seq,0), -5); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_FracXY($seq,0), -1/2); $path->{'x'} = 11; $path->{'y'} = -2; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_IntXY($seq,0), -5); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_FracXY($seq,0), -1/2); $path->{'x'} = -11; $path->{'y'} = -2; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_IntXY($seq,0), 5); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_FracXY($seq,0), 1/2); } #------------------------------------------------------------------------------ # _coordinate_func_BitAnd() # _coordinate_func_BitOr() # _coordinate_func_BitXor() { my $path = MyPlanePath->new; my $seq = Math::NumSeq::PlanePathCoord->new (planepath_object => $path); $path->{'x'} = 0; $path->{'y'} = 0; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitAnd($seq,0), 0); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitOr($seq,0), 0); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitXor($seq,0), 0); $path->{'x'} = 7; $path->{'y'} = 9; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitAnd($seq,0), 1); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitOr($seq,0), 15); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitXor($seq,0), 14); $path->{'x'} = 7.0 / 16.0; $path->{'y'} = 9.0 / 16.0; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitAnd($seq,0),1.0 /16.0); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitOr($seq,0), 15.0/16.0); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitXor($seq,0),14.0/16.0); $path->{'x'} = 7.0 / 4.0; $path->{'y'} = 9.0 / 4.0; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitAnd($seq,0),1.0 /4.0); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitOr($seq,0), 15.0/4.0); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitXor($seq,0),14.0/4.0); $path->{'x'} = -1.0 / 16.0; $path->{'y'} = -1.0 / 16.0; ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitAnd($seq,0),-1.0/16.0); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitOr($seq,0), -1.0/16.0); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitXor($seq,0),0); $path->{'x'} = -1.0 / 16.0; # ...111.1111 $path->{'y'} = 15.0 / 16.0; # 0.1111 ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitAnd($seq,0),15.0/16.0); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitOr($seq,0), -1.0/16.0); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitXor($seq,0),-1); $path->{'x'} = -1.0 / 16.0; # ...111.1111 $path->{'y'} = 2.0 / 16.0; # 0.0010 ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitAnd($seq,0), 2.0/16.0); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitOr($seq,0), -1.0/16.0); ok (Math::NumSeq::PlanePathCoord::_coordinate_func_BitXor($seq,0),-3.0/16.0); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/SierpinskiCurveStair.t0000644000175000017500000001372013734026643016501 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 53; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::SierpinskiCurveStair; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::SierpinskiCurveStair::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::SierpinskiCurveStair->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::SierpinskiCurveStair->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::SierpinskiCurveStair->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::SierpinskiCurveStair->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::SierpinskiCurveStair->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative ? 1 : 0, 0, 'x_negative()'); ok ($path->y_negative ? 1 : 0, 0, 'y_negative()'); my @pnames = map {$_->{'name'}} Math::PlanePath::SierpinskiCurveStair->parameter_info_list; ok (join(',',@pnames), 'diagonal_length,arms'); } { my $path = Math::PlanePath::SierpinskiCurveStair->new (arms => 2); ok ($path->x_negative ? 1 : 0, 0, 'x_negative()'); ok ($path->y_negative ? 1 : 0, 0, 'y_negative()'); } { my $path = Math::PlanePath::SierpinskiCurveStair->new (arms => 3); ok ($path->x_negative ? 1 : 0, 1, 'x_negative()'); ok ($path->y_negative ? 1 : 0, 0, 'y_negative()'); } { my $path = Math::PlanePath::SierpinskiCurveStair->new (arms => 4); ok ($path->x_negative ? 1 : 0, 1, 'x_negative()'); ok ($path->y_negative ? 1 : 0, 0, 'y_negative()'); } { my $path = Math::PlanePath::SierpinskiCurveStair->new (arms => 5); ok ($path->x_negative ? 1 : 0, 1, 'x_negative()'); ok ($path->y_negative ? 1 : 0, 1, 'y_negative()'); } { my $path = Math::PlanePath::SierpinskiCurveStair->new (arms => 8); ok ($path->x_negative ? 1 : 0, 1, 'x_negative()'); ok ($path->y_negative ? 1 : 0, 1, 'y_negative()'); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::SierpinskiCurveStair->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 0); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 5); my ($x,$y) = $path->n_to_xy($n_hi); ok ($x, 4); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 25); my ($x,$y) = $path->n_to_xy($n_hi); ok ($x, 10); } { my ($n_lo,$n_hi) = $path->level_to_n_range(3); ok ($n_lo, 0); ok ($n_hi, 105); my ($x,$y) = $path->n_to_xy($n_hi); ok ($x, 22); } foreach my $level (0 .. 5) { my ($n_lo,$n_hi) = $path->level_to_n_range($level); { my ($x,$y) = $path->n_to_xy($n_hi); ok ($y, 0, 'level at Y=0'); } if ($n_hi > 0) { { my ($x,$y) = $path->n_to_xy($n_hi - 1); ok ($y, 0, 'before level at Y=0 too'); } { my ($x,$y) = $path->n_to_xy($n_hi + 1); ok ($y, 1, 'after level at Y=1'); } } } } { my $path = Math::PlanePath::SierpinskiCurveStair->new (arms => 8); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 7); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 47); } } #------------------------------------------------------------------------------ # xy_to_n() distinct n { my $bad = 0; my $count = 0; foreach my $arms (1 .. 8) { foreach my $diagonal_length (1 .. 4) { my $xlo = -5; my $xhi = 8*($diagonal_length+1); my $ylo = -5; my $yhi = 8*($diagonal_length+1); my $path = Math::PlanePath::SierpinskiCurveStair->new (arms => $arms, diagonal_length => $diagonal_length); my ($nlo, $nhi) = $path->rect_to_n_range($xlo,$ylo, $xhi,$yhi); my %seen; OUTER: for (my $x = $xlo; $x <= $xhi; $x++) { for (my $y = $ylo; $y <= $yhi; $y++) { next if ($x ^ $y) & 1; my $n = $path->xy_to_n ($x,$y); next if ! defined $n; # sparse if ($seen{$n}) { MyTestHelpers::diag ("dl=$diagonal_length arms=$arms x=$x,y=$y n=$n seen before at $seen{$n}"); last if $bad++ > 10; } if ($n < $nlo) { MyTestHelpers::diag ("x=$x,y=$y n=$n below nlo=$nlo"); last OUTER if $bad++ > 10; } if ($n > $nhi) { MyTestHelpers::diag ("x=$x,y=$y n=$n above nhi=$nhi"); last OUTER if $bad++ > 10; } $seen{$n} = "$x,$y"; $count++; } } } } ok ($bad, 0, "xy_to_n() coverage and distinct, $count points"); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/KochCurve.t0000644000175000017500000002121313734026645014240 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 146; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::KochCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::KochCurve::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::KochCurve->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::KochCurve->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::KochCurve->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::KochCurve->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # random n_to_dxdy() { my $path = Math::PlanePath::KochCurve->new; foreach (1 .. 20) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)); # 0 to 2^bits, inclusive $n += random_quarter(); my ($x,$y) = $path->n_to_xy ($n); my ($next_x,$next_y) = $path->n_to_xy ($n+1); my $delta_dx = $next_x - $x; my $delta_dy = $next_y - $y; my ($func_dx,$func_dy) = $path->n_to_dxdy ($n); ok ($func_dx, $delta_dx, "n_to_dxdy($n) dx at xy=$x,$y"); ok ($func_dy, $delta_dy, "n_to_dxdy($n) dy at xy=$x,$y"); } } # return 0, 0.25, 0.5 or 0.75 sub random_quarter { int(rand(4)) / 4; } #------------------------------------------------------------------------------ # random rect_to_n_range() { require Math::PlanePath::KochCurve; my $path = Math::PlanePath::KochCurve->new; for (1 .. 5) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)); # 0 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); ok (defined $rev_n, 1, "xy_to_n($x,$y) reverse n, got undef"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() n=$n at xy=$x,$y cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() n=$n at xy=$x,$y cf got n_hi=$n_hi"); } } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::KochCurve->new; foreach my $elem ( [4,1, 7,1, 5,5], # Y=1 X=4..7 being N=5 only [0,0, 9,3, 0,8], [9,3, 9,3, 8,8], [10,2, 10,2, 9,9], [11,1, 11,1, 11,11], ) { my ($x1,$y1,$x2,$y2, $want_lo, $want_hi) = @$elem; my ($got_lo, $got_hi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); ok ($got_lo, $want_lo, "lo on $x1,$y1 $x2,$y2"); ok ($got_hi, $want_hi, "hi on $x1,$y1 $x2,$y2"); } } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::KochCurve->new; ok ($path->n_start, 0, 'n_start()'); ok (! $path->x_negative, 1, 'x_negative()'); ok (! $path->y_negative, 1, 'y_negative()'); ok (! $path->class_x_negative, 1, 'class_x_negative()'); ok (! $path->class_y_negative, 1, 'class_y_negative()'); } #------------------------------------------------------------------------------ # first few points { my @data = ([ 0.5, 1,0 ], [ 3.5, 5,0 ], [ 0, 0,0 ], [ 1, 2,0 ], [ 2, 3,1 ], [ 3, 4,0 ], [ 4, 6,0 ], [ 5, 7,1 ], [ 6, 6,2 ], [ 7, 8,2 ], [ 8, 9,3 ], ); my $path = Math::PlanePath::KochCurve->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next unless $want_n==int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } foreach my $elem (@data) { my ($n, $x, $y) = @$elem; if ($n == int($n)) { my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo == 0, 1, "rect_to_n_range() got_nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi == $n, 1, "rect_to_n_range() got_nhi=$got_nhi at n=$n,x=$x,y=$y"); ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($got_nlo == $n, 1, "rect_to_n_range() got_nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi == $n, 1, "rect_to_n_range() got_nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } #------------------------------------------------------------------------------ # xy_to_n() distinct n { my $path = Math::PlanePath::KochCurve->new; my $bad = 0; my %seen; my $xlo = -5; my $xhi = 100; my $ylo = -5; my $yhi = 100; my ($nlo, $nhi) = $path->rect_to_n_range($xlo,$ylo, $xhi,$yhi); my $count = 0; OUTER: for (my $x = $xlo; $x <= $xhi; $x++) { for (my $y = $ylo; $y <= $yhi; $y++) { next if ($x ^ $y) & 1; my $n = $path->xy_to_n ($x,$y); next if ! defined $n; # sparse if ($seen{$n}) { MyTestHelpers::diag ("x=$x,y=$y n=$n seen before at $seen{$n}"); last if $bad++ > 10; } if ($n < $nlo) { MyTestHelpers::diag ("x=$x,y=$y n=$n below nlo=$nlo"); last OUTER if $bad++ > 10; } if ($n > $nhi) { MyTestHelpers::diag ("x=$x,y=$y n=$n above nhi=$nhi"); last OUTER if $bad++ > 10; } $seen{$n} = "$x,$y"; $count++; } } ok ($bad, 0, "xy_to_n() coverage and distinct, $count points"); } #------------------------------------------------------------------------------ # turn sequence { sub dxdy_to_dir6 { my ($dx,$dy) = @_; if ($dy == 0) { if ($dx == 2) { return 0; } if ($dx == -2) { return 3; } } if ($dy == 1) { if ($dx == 1) { return 1; } if ($dx == -1) { return 2; } } if ($dy == -1) { if ($dx == 1) { return 5; } if ($dx == -1) { return 4; } } die "unrecognised $dx,$dy"; } # total 1s left 2s right base 4 digits sub calc_n_to_dir { my ($n) = @_; my $dir = 0; while ($n) { my $digit = $n % 4; if ($digit == 1) { $dir++; } if ($digit == 2) { $dir--; } $n = int($n/4); } return $dir; } # lowest non-zero base 4 digit sub calc_n_to_turn { my ($n) = @_; for (;;) { if ($n == 0) { die "oops n=0"; } my $digit = $n % 4; if ($digit == 1) { return 1; } if ($digit == 2) { return -2; } if ($digit == 3) { return 1; } $n = int($n/4); } } my $path = Math::PlanePath::KochCurve->new; my $n = $path->n_start; my $bad = 0; my ($prev_x, $prev_y) = $path->n_to_xy($n++); my ($x, $y) = $path->n_to_xy($n); my $prev_dx = $x - $prev_x; my $prev_dy = $y - $prev_y; my $prev_dir = dxdy_to_dir6($prev_dx,$prev_dy); while ($n < 1000) { my ($next_x,$next_y) = $path->n_to_xy($n+1); my $dx = $next_x - $x; my $dy = $next_y - $y; my $dir = dxdy_to_dir6($dx,$dy); my $got_turn = ($dir - $prev_dir) % 6; my $want_turn = calc_n_to_turn($n) % 6; if ($got_turn != $want_turn) { MyTestHelpers::diag ("n=$n turn got=$got_turn want=$want_turn"); MyTestHelpers::diag (" dir=$dir prev_dir=$prev_dir"); last if $bad++ > 10; } my $got_dir = $dir % 6; my $want_dir = calc_n_to_dir($n) % 6; if ($got_dir != $want_dir) { MyTestHelpers::diag ("n=$n dir got=$got_dir want=$want_dir"); last if $bad++ > 10; } $n++; $prev_dir = $dir; $x = $next_x; $y = $next_y; } ok ($bad, 0, "turn/dir sequence"); } exit 0; Math-PlanePath-129/t/HypotOctant.t0000644000175000017500000000763413734026645014636 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 21; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::HypotOctant; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::HypotOctant::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::HypotOctant->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::HypotOctant->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::HypotOctant->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::HypotOctant->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::HypotOctant->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 0, 'x_negative() instance method'); ok ($path->y_negative, 0, 'y_negative() instance method'); ok ($path->class_x_negative, 0, 'class_x_negative()'); ok ($path->class_y_negative, 0, 'class_y_negative()'); ok ($path->x_minimum, 0, 'x_minimum()'); ok ($path->y_minimum, 0, 'y_minimum()'); ok ($path->sumxy_minimum, 0, 'sumxy_minimum()'); ok ($path->diffxy_minimum, 0, 'diffxy_minimum()'); } { my $path = Math::PlanePath::HypotOctant->new (points => 'odd'); ok ($path->x_minimum, 1, 'x_minimum()'); ok ($path->y_minimum, 0, 'y_minimum()'); ok ($path->sumxy_minimum, 1, 'sumxy_minimum()'); ok ($path->diffxy_minimum, 1, 'diffxy_minimum()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::HypotOctant->parameter_info_list; ok (join(',',@pnames), 'points'); } #------------------------------------------------------------------------------ # increasing R^2 my @points_choices = @{Math::PlanePath::HypotOctant->parameter_info_hash ->{'points'}->{'choices'}}; foreach my $points (@points_choices) { my $path = Math::PlanePath::HypotOctant->new (points => $points); my $prev_h = -1; my $prev_x = 0; my $prev_y = -1; foreach my $n ($path->n_start .. 1000) { my ($x, $y) = $path->n_to_xy ($n); my $h = $x*$x + $y*$y; if ($h < $prev_h) { die "decreasing h=$h prev=$prev_h"; } $prev_h = $h; $prev_x = $x; $prev_y = $y; } } # if ($n > 2 && ! _turn_func_Left($prev_x,$prev_y, $x,$y)) { # die "not turn left at n=$n xy=$x,$y prev=$prev_x,$prev_y"; # } # sub _turn_func_Left { # my ($dx,$dy, $next_dx,$next_dy) = @_; # ### _turn_func_Left() ... # my $a = $next_dy * $dx; # my $b = $next_dx * $dy; # return ($a > $b # || $dx==-$next_dx && $dy==-$next_dy # straight opposite 180 # ? 1 # : 0); # } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/CincoCurve.t0000644000175000017500000000734013734026650014410 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 134; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::CincoCurve; my $path = Math::PlanePath::CincoCurve->new; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::CincoCurve::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::CincoCurve->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::CincoCurve->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::CincoCurve->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 0, 'x_negative() instance method'); ok ($path->y_negative, 0, 'y_negative() instance method'); ok ($path->class_x_negative, 0, 'class_x_negative() instance method'); ok ($path->class_y_negative, 0, 'class_y_negative() instance method'); my @pnames = map {$_->{'name'}} $path->parameter_info_list; ok (join(',',@pnames), '', 'parameter_info_list() keys'); } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::CincoCurve->new; for (1 .. 20) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($n) frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($n) frac $frac, y"); } } } #------------------------------------------------------------------------------ # many fracs { my $path = Math::PlanePath::CincoCurve->new; my ($x,$y) = $path->n_to_xy (0); my $bad = 0; my $pow = 3; for my $n (0 .. 25**$pow + 5) { my ($x2,$y2) = $path->n_to_xy ($n+1); my $frac = 0.25; my $want_xf = $x + ($x2-$x)*$frac; my $want_yf = $y + ($y2-$y)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); if ($got_xf != $want_xf || $got_yf != $want_yf) { MyTestHelpers::diag ("wrong at n=$n got $got_xf,$got_yf want $want_xf,$want_yf"); if ($bad++ > 10) { last; } } ($x,$y) = ($x2,$y2); } ok ($bad, 0); } exit 0 ; Math-PlanePath-129/t/NumSeq-PlanePathN.t0000644000175000017500000000500413246362654015550 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } my $test_count = (tests => 12)[1]; plan tests => $test_count; if (! eval { require Math::NumSeq; 1 }) { MyTestHelpers::diag ('skip due to Math::NumSeq not available -- ',$@); foreach (1 .. $test_count) { skip ('due to no Math::NumSeq', 1, 1); } exit 0; } require Math::NumSeq::PlanePathN; #------------------------------------------------------------------------------ # characteristic() foreach my $elem (['increasing',1 ], # default SquareSpiral X_axis ['non_decreasing', 1, planepath => 'SquareSpiral,wider=0', line_type => 'X_axis' ], ['increasing', 1, planepath => 'AnvilSpiral,wider=0', line_type => 'X_neg' ], ['increasing_from_i', 0, planepath => 'AnvilSpiral,wider=0', line_type => 'X_neg' ], ['increasing', '', planepath => 'AnvilSpiral,wider=1', line_type => 'X_neg' ], ['increasing_from_i', 1, planepath => 'AnvilSpiral,wider=1', line_type => 'X_neg' ], ['increasing', '', planepath => 'AnvilSpiral,wider=2', line_type => 'X_neg' ], ['increasing_from_i', 1, planepath => 'AnvilSpiral,wider=2', line_type => 'X_neg' ], ['increasing', '', planepath => 'AnvilSpiral,wider=3', line_type => 'X_neg' ], ['increasing_from_i', 2, planepath => 'AnvilSpiral,wider=3', line_type => 'X_neg' ], ['increasing', '', planepath => 'AnvilSpiral,wider=4', line_type => 'X_neg' ], ['increasing_from_i', 2, planepath => 'AnvilSpiral,wider=4', line_type => 'X_neg' ], ) { my ($key, $want, @parameters) = @$elem; my $seq = Math::NumSeq::PlanePathN->new (@parameters); ok ($seq->characteristic($key), $want, join(' ', @parameters)); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/HilbertCurve-load.t0000644000175000017500000000202111554145257015655 0ustar gggg#!/usr/bin/perl -w # Copyright 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . ## no critic (RequireUseStrict, RequireUseWarnings) use Math::PlanePath::HilbertCurve; my $path = Math::PlanePath::HilbertCurve->new (width => 1000); $path->n_to_xy(123); $path->xy_to_n(0,0); $path->rect_to_n_range(0,0, 1,1); use Test; plan tests => 1; ok (1,1, 'Math::PlanePath::HilbertCurve load as first thing'); exit 0; Math-PlanePath-129/t/File.t0000644000175000017500000000445713734026646013242 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 13;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::File; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::File::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::File->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::File->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::File->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::File->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # no filename { my $path = Math::PlanePath::File->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); my ($got_lo, $got_hi) = $path->rect_to_n_range (-1,-1, 1,1); ok ($got_lo, 1, "lo on empty"); ok ($got_hi, 0, "hi on empty"); } { my @pnames = map {$_->{'name'}} Math::PlanePath::File->parameter_info_list; ok (join(',',@pnames), 'filename'); } exit 0; Math-PlanePath-129/t/QuintetCentres.t0000644000175000017500000001571513734026643015334 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 357; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::QuintetCentres; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::QuintetCentres::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::QuintetCentres->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::QuintetCentres->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::QuintetCentres->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::QuintetCentres->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::QuintetCentres->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::QuintetCentres->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 0); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 4); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 24); } { my ($n_lo,$n_hi) = $path->level_to_n_range(3); ok ($n_lo, 0); ok ($n_hi, 124); } } { my $path = Math::PlanePath::QuintetCentres->new (arms => 4); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 3); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 19); } # 4*5 -1 } #------------------------------------------------------------------------------ # first few points { my @data = ( # arms=2 # [ 2, 46, -2,4 ], # [ 2, 1, -1,1 ], # arms=1 [ 1, 0, 0,0 ], [ 1, 1, 1,0 ], [ 1, 2, 1,-1 ], [ 1, 3, 2,0 ], [ 1, 4, 1,1 ], [ 1, 5, 2,1 ], [ 1, 6, 3,2 ], [ 1, 7, 4,1 ], [ 1, .25, .25, 0 ], [ 1, .5, .5, 0 ], [ 1, 1.75, 1, -.75 ], ); foreach my $elem (@data) { my ($arms, $n, $x, $y) = @$elem; my $path = Math::PlanePath::QuintetCentres->new (arms => $arms); { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } if ($n == int($n)) { { my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range(0,0,$x,$y) arms=$arms for n=$n, got_nlo=$got_nlo"); ok ($got_nhi >= $n, 1, "rect_to_n_range(0,0,$x,$y) arms=$arms for n=$n, got_nhi=$got_nhi"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range($x,$y,$x,$y) arms=$arms for n=$n, got_nlo=$got_nlo"); ok ($got_nhi >= $n, 1, "rect_to_n_range($x,$y,$x,$y) arms=$arms for n=$n, got_nhi=$got_nhi"); } } } } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::QuintetCentres->new; my ($n_lo, $n_hi) = $path->rect_to_n_range(0,0, 0,0); ok ($n_lo == 0, 1, "rect_to_n_range() 0,0 n_lo=$n_lo"); ok ($n_hi >= 0, 1, "rect_to_n_range() 0,0 n_hi=$n_hi"); } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::QuintetCentres->new; for (1 .. 20) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($n) frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($n) frac $frac, y"); } } } #------------------------------------------------------------------------------ # random points { my $path = Math::PlanePath::QuintetCentres->new; for (1 .. 50) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n) { $rev_n = 'undef'; } ok ($rev_n, $n, "xy_to_n($x,$y) reverse to expect n=$n, got $rev_n"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo, $n, "rect_to_n_range() reverse n=$n cf got n_lo=$n_lo"); ok ($n_hi, $n, "rect_to_n_range() reverse n=$n cf got n_hi=$n_hi"); } } #------------------------------------------------------------------------------ # reversible 5 digits to N=5**5 { my $good = 1; foreach my $arms (1 .. 4) { my $path = Math::PlanePath::QuintetCentres->new (arms => $arms); for my $n (0 .. 5**5) { my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); if (! defined $rev_n || $rev_n != $n) { $good = 0; if (! defined $rev_n) { $rev_n = 'undef'; } MyTestHelpers::diag ("n=$n arms=$arms xy_to_n($x,$y) reverse got $rev_n"); } } } ok ($good, 1); } exit 0; Math-PlanePath-129/t/FilledRings.t0000644000175000017500000000737413734026646014566 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 99; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::FilledRings; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::FilledRings::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::FilledRings->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::FilledRings->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::FilledRings->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::FilledRings->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); ok ($path->class_x_negative, 1, 'class_x_negative()'); ok ($path->class_y_negative, 1, 'class_y_negative()'); ok ($path->n_frac_discontinuity, 0, 'n_frac_discontinuity()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::FilledRings->parameter_info_list; ok (join(',',@pnames), 'n_start'); } #------------------------------------------------------------------------------ # _cumul_extend() sub cumul_calc { my ($r) = @_; my $sq = ($r+.5)**2; my $count = 0; foreach my $x (-$r-1 .. $r+1) { my $x2 = $x*$x; foreach my $y (-$r-1 .. $r+1) { $count += ($x2 + $y*$y <= $sq); } } return $count; } { my $path = Math::PlanePath::FilledRings->new; foreach my $r (0 .. 50) { my $want = cumul_calc($r); Math::PlanePath::FilledRings::_cumul_extend($path); my $got = $path->{'cumul'}->[$r]; ok ($got, $want, "r=$r"); } } #------------------------------------------------------------------------------ # n_to_xy { my @data = ([ 1, 0,0 ], [ 1.25, 0.25, 0 ], [ 1.75, 0.75, 0 ], [ 2, 1,0 ], [ 2.25, 1, 0.25 ], [ 3, 1,1 ], # top [ 3.25, 0.75, 1 ], [ 9, 1, -1 ], [ 9.25, 1, -0.75 ], [ 10, 2, 0 ], [ 14, -1,2 ], [ 14.25, -1.25, 1.75 ], [ 21.25, 2, -0.75 ], [ 37.25, 3, -0.75 ], [ 38, 4, 0 ], ); my $path = Math::PlanePath::FilledRings->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; if ($want_n == int($want_n)) { my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/QuadricCurve.t0000644000175000017500000001172513734026643014751 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 20; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::QuadricCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::QuadricCurve::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::QuadricCurve->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::QuadricCurve->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::QuadricCurve->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::QuadricCurve->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::QuadricCurve->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); ok ($path->x_minimum, 0, 'x_minimum()'); ok ($path->y_minimum, undef, 'y_minimum()'); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::QuadricCurve->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 1); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 8); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 64); } } #------------------------------------------------------------------------------ # xy_to_n() distinct n { my $path = Math::PlanePath::QuadricCurve->new; my $bad = 0; my %seen; my $xlo = -5; my $xhi = 100; my $ylo = -5; my $yhi = 100; my ($nlo, $nhi) = $path->rect_to_n_range($xlo,$ylo, $xhi,$yhi); my $count = 0; OUTER: for (my $x = $xlo; $x <= $xhi; $x++) { for (my $y = $ylo; $y <= $yhi; $y++) { next if ($x ^ $y) & 1; my $n = $path->xy_to_n ($x,$y); next if ! defined $n; # sparse if ($seen{$n}) { MyTestHelpers::diag ("x=$x,y=$y n=$n seen before at $seen{$n}"); last if $bad++ > 10; } if ($n < $nlo) { MyTestHelpers::diag ("x=$x,y=$y n=$n below nlo=$nlo"); last OUTER if $bad++ > 10; } if ($n > $nhi) { MyTestHelpers::diag ("x=$x,y=$y n=$n above nhi=$nhi"); last OUTER if $bad++ > 10; } $seen{$n} = "$x,$y"; $count++; } } ok ($bad, 0, "xy_to_n() coverage and distinct, $count points"); } #------------------------------------------------------------------------------ # turn sequence { sub dxdy_to_dir4 { my ($dx,$dy) = @_; if ($dy == 0) { if ($dx == 1) { return 0; } if ($dx == -1) { return 2; } } if ($dy == 1) { if ($dx == 0) { return 1; } } if ($dy == -1) { if ($dx == 0) { return 3; } } die "unrecognised $dx,$dy"; } my $path = Math::PlanePath::QuadricCurve->new; my $n = $path->n_start; my $bad = 0; my ($prev_x, $prev_y) = $path->n_to_xy($n++); my ($x, $y) = $path->n_to_xy($n++); my $dx = $x - $prev_x; my $dy = $y - $prev_y; my $prev_dir = dxdy_to_dir4($dx,$dy); while ($n < 1000) { $prev_x = $x; $prev_y = $y; ($x,$y) = $path->n_to_xy($n); $dx = $x - $prev_x; $dy = $y - $prev_y; my $dir = dxdy_to_dir4($dx,$dy); my $want_turn = ($dir - $prev_dir) % 4; if ($want_turn == 3) { $want_turn = -1; } my $got_turn = $path->_UNDOCUMENTED__n_to_turn_LSR($n-1); if ($got_turn != $want_turn) { MyTestHelpers::diag ("n=$n turn got=$got_turn want=$want_turn"); MyTestHelpers::diag (" dir=$dir prev_dir=$prev_dir"); last if $bad++ > 10; } $n++; $prev_dir = $dir; } ok ($bad, 0, "turn sequence"); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/DigitGroups.t0000644000175000017500000000632413734026646014616 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 64; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::DigitGroups; my $path = Math::PlanePath::DigitGroups->new; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::DigitGroups::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::DigitGroups->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::DigitGroups->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::DigitGroups->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 0, 'x_negative() instance method'); ok ($path->y_negative, 0, 'y_negative() instance method'); ok ($path->class_x_negative, 0, 'class_x_negative() instance method'); ok ($path->class_y_negative, 0, 'class_y_negative() instance method'); my @pnames = map {$_->{'name'}} $path->parameter_info_list; ok (join(',',@pnames), 'radix', 'parameter_info_list() keys'); } #------------------------------------------------------------------------------ # diagonal bit runs duplicate sub to_binary { my ($n) = @_; return ($n < 0 ? '-' : '') . sprintf('%b', abs($n)); } sub from_binary { my ($str) = @_; return oct("0b$str"); } # return $n with each run of bits "011...11" duplicated # eg. n=1011 -> 101011011 sub duplicate_bit_runs { my ($n) = @_; ### duplicate_bit_runs(): $n my $str = '0' . to_binary($n); ### $str $str =~ s/(01*)/$1$1/g; ### $str return from_binary($str); } { my $path = Math::PlanePath::DigitGroups->new; foreach my $i (0 .. 50) { my $path_n = $path->xy_to_n($i,$i); my $dup_n = duplicate_bit_runs($i); ok ($dup_n, $path_n); } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/GrayCode.t0000644000175000017500000002230013734026645014042 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min'; use Test; plan tests => 309; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::Base::Digits 'digit_split_lowtohigh'; use Math::PlanePath::GrayCode; use Math::PlanePath::Base::Digits 'digit_join_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments; sub binary_to_decimal { my ($str) = @_; my $ret = 0; foreach my $digit (split //, $str) { $ret = ($ret << 1) + $digit; } return $ret; } #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::GrayCode::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::GrayCode->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::GrayCode->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::GrayCode->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); } #------------------------------------------------------------------------------ # to/from binary Gray sub to_gray_reflected { my ($n, $radix) = @_; my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_to_gray_reflected($digits,$radix); return digit_join_lowtohigh($digits,$radix); } sub from_gray_reflected { my ($n, $radix) = @_; my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_from_gray_reflected($digits,$radix); return digit_join_lowtohigh($digits,$radix); } sub to_gray_modular { my ($n, $radix) = @_; my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_to_gray_modular($digits,$radix); return digit_join_lowtohigh($digits,$radix); } sub from_gray_modular { my ($n, $radix) = @_; my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_from_gray_modular($digits,$radix); return digit_join_lowtohigh($digits,$radix); } { my @gray = (binary_to_decimal('00000'), binary_to_decimal('00001'), binary_to_decimal('00011'), binary_to_decimal('00010'), binary_to_decimal('00110'), binary_to_decimal('00111'), binary_to_decimal('00101'), binary_to_decimal('00100'), binary_to_decimal('01100'), binary_to_decimal('01101'), binary_to_decimal('01111'), binary_to_decimal('01110'), binary_to_decimal('01010'), binary_to_decimal('01011'), binary_to_decimal('01001'), binary_to_decimal('01000'), binary_to_decimal('11000'), binary_to_decimal('11001'), binary_to_decimal('11011'), binary_to_decimal('11010'), binary_to_decimal('11110'), binary_to_decimal('11111'), binary_to_decimal('11101'), binary_to_decimal('11100'), binary_to_decimal('10100'), binary_to_decimal('10101'), binary_to_decimal('10111'), binary_to_decimal('10110'), binary_to_decimal('10010'), binary_to_decimal('10011'), binary_to_decimal('10001'), binary_to_decimal('10000'), ); ### @gray foreach my $i (0 .. $#gray) { my $gray = $gray[$i]; if ($i > 0) { my $prev_gray = $gray[$i-1]; my $xor = $gray ^ $prev_gray; ok (is_pow2($xor), 1, "at i=$i $gray ^ $prev_gray = $xor"); } my $got_gray = to_gray_reflected($i,2); ok ($got_gray, $gray); $got_gray = to_gray_modular($i,2); ok ($got_gray, $gray); my $got_i = from_gray_reflected($gray,2); ok ($got_i, $i); $got_i = from_gray_modular($gray,2); ok ($got_i, $i); } } sub is_pow2 { my ($n) = @_; while (($n & 1) == 0) { if ($n == 0) { return 0; } $n >>= 1; } return ($n == 1); } #------------------------------------------------------------------------------ # to/from modular Gray { my @gray = (000, 001, 002, 003, 004, 005, 006, 007, 017, 010, 011, 012, 013, 014, 015, 016, 026, 027, 020, 021, 022, 023, 024, 025, 035, 036, 037, 030, 031, 032, 033, 034, 044, 045, 046, 047, 040, 041, 042, 043, 053, 054, 055, 056, 057, 050, 051, 052, 062, 063, 064, 065, 066, 067, 060, 061, 071, 072, 073, 074, 075, 076, 077, 070, 0170, 0171, 0172, 0173, 0174, 0175, 0176, 0177, ); ### @gray foreach my $i (0 .. $#gray) { my $gray = $gray[$i]; my $got_gray = to_gray_modular($i,8); ok ($got_gray, $gray); my $got_i = from_gray_modular($gray,8); ok ($got_i, $i); } } { # to/from are inverses my $bad = 0; OUTER: foreach my $funcs ([\&to_gray_modular, \&from_gray_modular], [\&to_gray_reflected, \&from_gray_reflected], ) { my ($to,$from) = @$funcs; foreach my $radix (2 .. 7) { foreach my $i (0 .. min(256,$radix**4)) { my $g = $to->($i,$radix); unless ($from->($g,$radix) == $i) { MyTestHelpers::diag ("bad radix=$radix i=$i"); last OUTER if $bad++ > 10; } } } } ok ($bad, 0); } #------------------------------------------------------------------------------ # turn sequence claimed in the pod -- default BRGC { my $path = Math::PlanePath::GrayCode->new; my $bad = 0; my $n_start = $path->n_start; OUTER: foreach my $n ($n_start+1 .. 500) { { my $path_turn = path_n_turn ($path, $n); my $calc_turn = calc_n_turn_by_low0s ($n); if ($path_turn != $calc_turn) { MyTestHelpers::diag ("turn n=$n path $path_turn calc $calc_turn"); last OUTER if $bad++ > 10; } } { my $path_turn = path_n_turn ($path, $n); my $calc_turn = calc_n_turn_by_base4 ($n); if ($path_turn != $calc_turn) { MyTestHelpers::diag ("turn n=$n path $path_turn calc $calc_turn"); last OUTER if $bad++ > 10; } } } ok ($bad, 0, "turn sequence"); } # with Y reckoned increasing upwards sub dxdy_to_dir4 { my ($dx, $dy) = @_; if ($dx > 0) { return 0; } # east if ($dx < 0) { return 2; } # west if ($dy > 0) { return 1; } # north if ($dy < 0) { return 3; } # south } # return 0=E,1=N,2=W,3=S sub path_n_dir { my ($path, $n) = @_; my ($dx,$dy) = $path->n_to_dxdy($n) or die "Oops, no point at ",$n; return dxdy_to_dir4 ($dx, $dy); } # return 0,1,2,3 to the left sub path_n_turn { my ($path, $n) = @_; my $prev_dir = path_n_dir ($path, $n-1); my $dir = path_n_dir ($path, $n); return ($dir - $prev_dir) & 3; } # return 0,1,2,3 to the left sub calc_n_turn_by_low0s { my ($n) = @_; # in floor (N+1)/2 # even number of 0 bits is turn=1 left # odd number of 0 bits is turn=2 reversal $n = ($n+1)>>1; return (count_low_0_bits($n) % 2 ? 2 : 1); } sub count_low_0_bits { my ($n) = @_; if ($n == 0) { die; } my $count = 0; until ($n % 2) { $count++; $n /= 2; } return $count; } # return 0,1,2,3 to the left sub calc_n_turn_by_base4 { my ($n) = @_; $n = ($n+1)>>1; my $digit = base4_lowest_nonzero_digit($n); return ($digit == 1 || $digit == 3 ? 1 : 2); } sub base4_lowest_nonzero_digit { my ($n) = @_; while (($n & 3) == 0) { $n >>= 2; if ($n == 0) { die "oops, no nonzero digits at all"; } } return $n & 3; } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/WythoffArray.t0000644000175000017500000000522213734026640014771 0ustar gggg#!/usr/bin/perl -w # Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 15;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::WythoffArray; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::WythoffArray::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::WythoffArray->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::WythoffArray->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::WythoffArray->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::WythoffArray->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::WythoffArray->new; ok ($path->n_start, 1, 'n_start()'); ok (! $path->x_negative, 1, 'x_negative()'); ok (! $path->y_negative, 1, 'y_negative()'); ok (! $path->class_x_negative, 1, 'class_x_negative() instance method'); ok (! $path->class_y_negative, 1, 'class_y_negative() instance method'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::WythoffArray->parameter_info_list; ok (join(',',@pnames), 'x_start,y_start'); } { my $path = Math::PlanePath::WythoffArray->new (x_start=>123, y_start=>456); ok ($path->x_minimum, 123, 'x_minimum()'); ok ($path->y_minimum, 456, 'y_minimum()'); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/PeanoCurve.t0000644000175000017500000001006413734026644014417 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 2940; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::PeanoCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::PeanoCurve::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::PeanoCurve->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::PeanoCurve->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::PeanoCurve->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::PeanoCurve->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::PeanoCurve->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 0, 'x_negative() instance method'); ok ($path->y_negative, 0, 'y_negative() instance method'); } #------------------------------------------------------------------------------ # X=Y diagonal is ternary digits duplicated # A163343, and A163344 sans factor 4 # 0 1 2 my @n_on_diagonal = (0,4,8); # 4*digit sub n_on_diagonal { my ($n) = @_; my @n = Math::PlanePath::Base::Digits::digit_split_lowtohigh($n,3); my $rev = 0; foreach my $digit (reverse @n) { # high to low, mutate array $rev ^= ($digit==1); if ($rev) { $digit = 2-$digit; } $digit = $n_on_diagonal[$digit]; } return Math::PlanePath::Base::Digits::digit_join_lowtohigh (\@n,9); } { # d(3n) = 9 d(n) + (8 if n odd) # d(3n+1) = 9 d(n) + 4 # d(3n+2) = 9 d(n) + (8 if n even) my $path = Math::PlanePath::PeanoCurve->new; foreach my $i (0 .. 3**6) { ok (n_on_diagonal($i), $path->xy_to_n($i,$i), 'N on X=Y diagonal'); ok (n_on_diagonal(3*$i), 9*n_on_diagonal($i) + ($i%2==1 ? 8 : 0), 'N on X=Y diagonal, recurrence 3i'); ok (n_on_diagonal(3*$i+1), 9*n_on_diagonal($i) + 4, 'N on X=Y diagonal, recurrence 3i+1'); ok (n_on_diagonal(3*$i+2), 9*n_on_diagonal($i) + ($i%2==0 ? 8 : 0), 'N on X=Y diagonal, recurrence 3i+2'); } } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::PeanoCurve->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 0); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 8); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 0); ok ($n_hi, 80); } } { my $path = Math::PlanePath::PeanoCurve->new (radix => 5); { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 0); ok ($n_hi, 0); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 0); ok ($n_hi, 24); } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/CCurve.t0000644000175000017500000003013113734026650013531 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use List::Util 'min','max'; use Test; plan tests => 294; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments; require Math::PlanePath::CCurve; my $path = Math::PlanePath::CCurve->new; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::CCurve::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::CCurve->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::CCurve->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::CCurve->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 1, 'x_negative() instance method'); ok ($path->y_negative, 1, 'y_negative() instance method'); ok ($path->class_x_negative, 1, 'class_x_negative()'); ok ($path->class_y_negative, 1, 'class_y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::CCurve->parameter_info_list; ok (join(',',@pnames), ''); # no arms yet } #------------------------------------------------------------------------------ # n_to_dxdy() { my @dxdy = $path->n_to_dxdy (-1); ok(scalar(@dxdy), 0); } { # random n_to_dxdy() my $path = Math::PlanePath::CCurve->new; # for (my $n = 1.25; $n < 40; $n++) { foreach (1 .. 10) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive $n += random_quarter(); my ($x,$y) = $path->n_to_xy ($n); my ($next_x,$next_y) = $path->n_to_xy ($n+1); my $delta_dx = $next_x - $x; my $delta_dy = $next_y - $y; my ($func_dx,$func_dy) = $path->n_to_dxdy ($n); if ($func_dx == 0) { $func_dx = '0'; } # avoid -0 in perl 5.6 if ($func_dy == 0) { $func_dy = '0'; } # avoid -0 in perl 5.6 ok ($func_dx, $delta_dx, "n_to_dxdy($n) dx at xy=$x,$y"); ok ($func_dy, $delta_dy, "n_to_dxdy($n) dy at xy=$x,$y"); } } # return 0, 0.25, 0.5 or 0.75 sub random_quarter { int(rand(4)) / 4; } #------------------------------------------------------------------------------ # first few values { my @xy = $path->n_to_xy (-1); ok(scalar(@xy), 0); } ok(! defined($path->_UNDOCUMENTED__n_to_turn_LSR(-1)), 1); ok(! defined($path->_UNDOCUMENTED__n_to_turn_LSR(0)), 1); ok($path->_UNDOCUMENTED__n_to_turn_LSR(1), 1); { my @data = ([ 0, 0,0 ], [ 0.25, 0.25,0 ], [ 0.75, 0.75,0 ], [ 1, 1,0 ], [ 1.25, 1,0.25 ], [ 1.75, 1,0.75 ], [ 2, 1,1 ], [ 3, 1,2 ], [ 4, 0,2 ], [ 5, 0,3 ], [ 6, -1,3 ], [ 7, -2,3 ], [ 8, -2,2 ], [ 9, -2,3 ], ); my $path = Math::PlanePath::CCurve->new; foreach my $elem (@data) { my ($n, $want_x,$want_y) = @$elem; { my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x == $want_x, 1, "x at n=$n"); ok ($got_y == $want_y, 1, "y at n=$n"); } { my ($next_x, $next_y) = $path->n_to_xy ($n+1); my $want_dx = $next_x - $want_x; my $want_dy = $next_y - $want_y; my ($got_dx, $got_dy) = $path->n_to_dxdy ($n); ok ($got_dx == $want_dx, 1, "dx at n=$n"); ok ($got_dy == $want_dy, 1, "dy at n=$n"); } } # foreach my $elem (@data) { # my ($want_n, $x, $y) = @$elem; # next unless $want_n == int($want_n); # my $got_n = $path->xy_to_n ($x, $y); # ok ($got_n, $want_n, "n at x=$x,y=$y"); # } } #------------------------------------------------------------------------------ # xyxy_to_n_list() { my @data = ( [ -2,3, -2,2, [7] ], [ -2,2, -2,3, [8] ], ); foreach my $elem (@data) { my ($x1,$y1, $x2,$y2, $want_n_aref) = @$elem; my $want_n_str = join(',', @$want_n_aref); { my @got_n_list = $path->xyxy_to_n_list($x1,$y1, $x2,$y2); ok (scalar(@got_n_list), scalar(@$want_n_aref), "xyxy_to_n_list($x1,$y1, $x2,$y2) length"); my $got_n_str = join(',', @got_n_list); ok ($got_n_str, $want_n_str, "xyxy_to_n_list($x1,$y1, $x2,$y2) values"); } { my $got_n = $path->xyxy_to_n($x1,$y1, $x2,$y2); ok ($got_n, $want_n_aref->[0]); } { my @got_n = $path->xyxy_to_n($x1,$y1, $x2,$y2); ok (scalar(@got_n), 1); ok ($got_n[0], $want_n_aref->[0]); } } } { my @data = ( [ -2,3, -2,2, [7,8] ], [ -2,2, -2,3, [7,8] ], ); foreach my $elem (@data) { my ($x1,$y1, $x2,$y2, $want_n_aref) = @$elem; my $want_n_str = join(',', @$want_n_aref); { my @got_n_list = $path->xyxy_to_n_list_either($x1,$y1, $x2,$y2); ok (scalar(@got_n_list), scalar(@$want_n_aref), "xyxy_to_n_list_either($x1,$y1, $x2,$y2) length"); my $got_n_str = join(',', @got_n_list); ok ($got_n_str, $want_n_str, "xyxy_to_n_list_either($x1,$y1, $x2,$y2) values"); } { my $got_n = $path->xyxy_to_n_either($x1,$y1, $x2,$y2); ok ($got_n, $want_n_aref->[0]); } { my @got_n = $path->xyxy_to_n_either($x1,$y1, $x2,$y2); ok (scalar(@got_n), 1); ok ($got_n[0], $want_n_aref->[0]); } } } #------------------------------------------------------------------------------ # xy_to_n_list() { my @data = ( # close points [ -2,-2, [] ], [ -2,-1, [] ], [ -2,0, [] ], [ -2,1, [] ], [ -2,2, [8] ], [ -1,-2, [] ], [ -1,-1, [] ], [ -1,0, [] ], [ -1,1, [] ], [ -1,2, [] ], [ 0,-2, [] ], [ 0,-1, [] ], [ 0,0, [0] ], [ 0,1, [] ], [ 0,2, [4] ], [ 1,-2, [] ], [ 1,-1, [] ], [ 1,0, [1] ], [ 1,1, [2] ], [ 1,2, [3] ], [ 2,-2, [] ], [ 2,-1, [] ], [ 2,0, [] ], [ 2,1, [] ], [ 2,2, [] ], # doubled, tripled, quadrupled from the POD [ -2, 3, [7,9] ], [ 18, -7, [189, 279, 281] ], [ -32, 55, [1727, 1813, 2283, 2369] ], ); foreach my $elem (@data) { my ($x,$y, $want_n_aref) = @$elem; my $want_n_str = join(',', @$want_n_aref); { my @got_n_list = $path->xy_to_n_list($x,$y); ok (scalar(@got_n_list), scalar(@$want_n_aref), "xy_to_n_list($x,$y) length"); my $got_n_str = join(',', @got_n_list); ok ($got_n_str, $want_n_str, "xy_to_n_list($x,$y) values"); } { my $got_n = $path->xy_to_n($x,$y); ok ($got_n, $want_n_aref->[0]); } { my @got_n = $path->xy_to_n($x,$y); ok (scalar(@got_n), 1); ok ($got_n[0], $want_n_aref->[0]); } } } #------------------------------------------------------------------------------ # extents claimed in the POD { my @want_h = (0,1,3,7,15,31); my @want_w = (0,0,1,3, 7,15); my @want_l = (0,0,0,1, 3, 7); foreach my $k (0 .. $#want_h) { my $rot = $k % 4; my $len = 2**$k; my $l = 0; my $w1 = 0; my $w2 = 0; my $h = 0; ### $rot ### $len foreach my $n (0 .. 4**$k) { my ($x,$y) = $path->n_to_xy ($n); ### at: "$x,$y n=$n" foreach (1 .. $rot) { ($x,$y) = ($y,-$x); # rotate -90 } ### rotated: "$x,$y" # now endpoints X=0,Y=0 to X=2^k,Y=0 and going into Y negative $l = max($l,$y); $h = max($h,-$y); $w1 = max($w1,-$x); $w2 = max($w2,$x-$len); } ok ($h == $want_h[$k], 1, "h[$k]"); ok ($l == $want_l[$k], 1); ok ($w1 == $want_w[$k], 1); ok ($w2 == $want_w[$k], 1); } } #------------------------------------------------------------------------------ # turn sequence claimed in the pod { my $bad = 0; my $n_start = $path->n_start; OUTER: foreach my $n ($n_start .. 500) { { my $path_dir = path_n_dir ($path, $n); my $calc_dir = calc_n_dir ($n); if ($path_dir != $calc_dir) { MyTestHelpers::diag ("dir n=$n path $path_dir calc $calc_dir"); last OUTER if $bad++ > 10; } } if ($n > $n_start) { # turns from N=1 onwards { my $path_turn = path_n_turn ($path, $n); my $calc_turn = calc_n_turn ($n); if ($path_turn != $calc_turn) { MyTestHelpers::diag ("turn n=$n path $path_turn calc $calc_turn"); last OUTER if $bad++ > 10; } } { my $path_turn = path_n_turn ($path, $n+1); my $calc_turn = calc_n_next_turn ($n); if ($path_turn != $calc_turn) { MyTestHelpers::diag ("next turn n=$n path $path_turn calc $calc_turn"); last OUTER if $bad++ > 10; } } } } ok ($bad, 0, "turn sequence"); } # with Y reckoned increasing upwards sub dxdy_to_dir4 { my ($dx, $dy) = @_; if ($dx > 0) { return 0; } # east if ($dx < 0) { return 2; } # west if ($dy > 0) { return 1; } # north if ($dy < 0) { return 3; } # south } # return 0=E,1=N,2=W,3=S sub path_n_dir { my ($path, $n) = @_; my ($dx,$dy) = $path->n_to_dxdy($n) or die "Oops, no point at ",$n; return dxdy_to_dir4 ($dx, $dy); } # return 0,1,2,3 to the left sub path_n_turn { my ($path, $n) = @_; my $prev_dir = path_n_dir ($path, $n-1); my $dir = path_n_dir ($path, $n); return ($dir - $prev_dir) & 3; } # return 0=E,1=N,2=W,3=S sub calc_n_dir { my ($n) = @_; my $dir = 0; while ($n) { $dir += ($n % 2); # count 1 bits $n = int($n/2); } return ($dir & 3); } # return 0,1,2,3 to the left sub calc_n_turn { my ($n) = @_; return (1-count_low_0_bits($n)) & 3; } sub count_low_0_bits { my ($n) = @_; if ($n == 0) { die; } my $count = 0; until ($n % 2) { $count++; $n /= 2; } return $count; } # return 0,1,2,3 to the left sub calc_n_next_turn { my ($n) = @_; return (1-count_low_1_bits($n)) & 3; } sub count_low_1_bits { my ($n) = @_; my $count = 0; while ($n % 2) { $count++; $n = int($n/2); } return $count; } #------------------------------------------------------------------------------ # random rect_to_n_range() { for (1 .. 5) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)); # 0 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); ok (defined $rev_n, 1, "xy_to_n($x,$y) reverse n=$n, got undef"); my ($n_lo, $n_hi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($n_lo <= $n, 1, "rect_to_n_range() n=$n at xy=$x,$y cf got n_lo=$n_lo"); ok ($n_hi >= $n, 1, "rect_to_n_range() n=$n at xy=$x,$y cf got n_hi=$n_hi"); } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/GosperIslands.t0000644000175000017500000001230013734026646015122 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 596; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::GosperIslands; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::GosperIslands::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::GosperIslands->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::GosperIslands->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::GosperIslands->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::GosperIslands->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::GosperIslands->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); } #------------------------------------------------------------------------------ # first few points { my @data = ( # [ .5, -1.5, -.5 ], # [ 3.25, 1.25, -.25 ], # [ 3.75, -3.25, -.25 ], # [ 1, 2,0 ], [ 2, 1,1 ], [ 3, -1,1 ], [ 4, -2,0 ], [ 5, -1,-1 ], [ 6, 1,-1 ], [ 7, 5,1 ], [ 8, 4,2 ], [ 9, 2,2 ], [ 10, 1,3 ], [ 11, -1,3 ], [ 12, -2,2 ], [ 13, -4,2 ], [ 14, -5,1 ], [ 15, -4,0 ], [ 25, 11,5 ], # [ 9, 1,2 ], # [ 10, 3,2 ], # [ 11, 2,1 ], # [ 12, 3,0 ], # [ 33, -1,7 ], ); my $path = Math::PlanePath::GosperIslands->new; foreach my $elem (@data) { my ($n, $x, $y) = @$elem; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } #------------------------------------------------------------------------------ # xy_to_n() reverse n_to_xy() { my $path = Math::PlanePath::GosperIslands->new; for (1 .. 500) { my $bits = int(rand(25)); # 0 to 25, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x,$y) = $path->n_to_xy ($n); my $rev_n = $path->xy_to_n ($x,$y); ok ($rev_n, $n, "xy_to_n() reverse n=$n"); } } #------------------------------------------------------------------------------ # xy_to_n() distinct n { my $path = Math::PlanePath::GosperIslands->new; my $bad = 0; my %seen; my $xlo = -50; my $xhi = 50; my $ylo = -50; my $yhi = 50; my ($nlo, $nhi) = $path->rect_to_n_range($xlo,$ylo, $xhi,$yhi); my $count = 0; OUTER: for (my $x = $xlo; $x <= $xhi; $x++) { for (my $y = $ylo; $y <= $yhi; $y++) { next if ($x ^ $y) & 1; my $n = $path->xy_to_n ($x,$y); next if ! defined $n; # sparse if ($seen{$n}) { MyTestHelpers::diag ("x=$x,y=$y n=$n seen before at $seen{$n}"); last if $bad++ > 10; } if ($n < $nlo) { MyTestHelpers::diag ("x=$x,y=$y n=$n below nlo=$nlo"); last OUTER if $bad++ > 10; } if ($n > $nhi) { MyTestHelpers::diag ("x=$x,y=$y n=$n above nhi=$nhi"); last OUTER if $bad++ > 10; } $seen{$n} = "$x,$y"; $count++; } } ok ($bad, 0, "xy_to_n() coverage and distinct, $count points"); } exit 0; Math-PlanePath-129/t/AztecDiamondRings.t0000644000175000017500000001357113734026650015720 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 216;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::AztecDiamondRings; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::AztecDiamondRings::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::AztecDiamondRings->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::AztecDiamondRings->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::AztecDiamondRings->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::AztecDiamondRings->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # rect_to_n_range() { my $path = Math::PlanePath::AztecDiamondRings->new; foreach my $elem ( [1,-2, 1,2, 5,27], # X=1, Y=-2..2 being 23,12,5,14,27 [-1,-2, -1,2, 2,16], # X=-1, Y=-2..2 being 10,3,2,7,16 [-2,1, 2,1, 6,26], # Y=1, X=-2..2 being 17,7,6,14,26 [-2,-1, 2,-1, 3,24], # Y=-1, X=-2..2 being 9,3,4,12,24 [-2,-1, 1,-1, 3,12], # Y=-1, X=-2..1 being 9,3,4,12 [0,-2, -2,-2, 10,20], # Y=-2, X=-2 to 0 being 20,10,11 ) { my ($x1,$y1,$x2,$y2, $want_lo, $want_hi) = @$elem; my ($got_lo, $got_hi) = $path->rect_to_n_range ($x1,$y1, $x2,$y2); ok ($got_lo, $want_lo, "lo on $x1,$y1 $x2,$y2"); ok ($got_hi, $want_hi, "hi on $x1,$y1 $x2,$y2"); } } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::AztecDiamondRings->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); ok ($path->class_x_negative, 1, 'class_x_negative() instance method'); ok ($path->class_y_negative, 1, 'class_y_negative() instance method'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::AztecDiamondRings->parameter_info_list; ok (join(',',@pnames), 'n_start'); } { my $path = Math::PlanePath::AztecDiamondRings->new (n_start => 0); ok ($path->n_start, 0, 'n_start()'); } #------------------------------------------------------------------------------ # first few points { my $path = Math::PlanePath::AztecDiamondRings->new; my @data = ( [ 1, 0,0 ], [ 2, -1,0 ], [ 3, -1,-1 ], [ 4, 0,-1 ], [ 5, 1,0 ], [ 6, 0,1 ], [ 7, -1,1 ], [ 8, -2,0 ], [ 1.25, -.25, 0 ], [ 1.75, -.75, 0 ], [ 2.25, -1, -.25 ], [ 3.25, -.75, -1 ], [ 4.25, 0, -.75 ], [ 12.25, 1, -.75 ], [ 24.25, 2, -.75 ], ); foreach my $elem (@data) { my ($n, $x, $y) = @$elem; { # n_to_xy() my ($got_x, $got_y) = $path->n_to_xy ($n); if ($got_x == 0) { $got_x = 0 } # avoid "-0" if ($got_y == 0) { $got_y = 0 } ok ($got_x, $x, "n_to_xy() x at n=$n"); ok ($got_y, $y, "n_to_xy() y at n=$n"); } if ($n==int($n)) { # xy_to_n() my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $n, "xy_to_n() n at x=$x,y=$y"); } if ($n == int($n)) { { my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range(0,0,$x,$y) for n=$n, got_nlo=$got_nlo"); ok ($got_nhi >= $n, 1, "rect_to_n_range(0,0,$x,$y) for n=$n, got_nhi=$got_nhi"); } { $n = int($n); my ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range($x,$y,$x,$y) for n=$n, got_nlo=$got_nlo"); ok ($got_nhi >= $n, 1, "rect_to_n_range($x,$y,$x,$y) for n=$n, got_nhi=$got_nhi"); } } } } #------------------------------------------------------------------------------ # random fracs { my $path = Math::PlanePath::AztecDiamondRings->new; for (1 .. 20) { my $bits = int(rand(20)); # 0 to 20, inclusive my $n = int(rand(2**$bits)) + 1; # 1 to 2^bits, inclusive my ($x1,$y1) = $path->n_to_xy ($n); my ($x2,$y2) = $path->n_to_xy ($n+1); foreach my $frac (0.25, 0.5, 0.75) { my $want_xf = $x1 + ($x2-$x1)*$frac; my $want_yf = $y1 + ($y2-$y1)*$frac; # the end of the ring goes towards the start of the current ring, not # the next if ($y1 == -1 && $x1 >= 0) { $want_xf = $x1; } my $nf = $n + $frac; my ($got_xf,$got_yf) = $path->n_to_xy ($nf); ok ($got_xf, $want_xf, "n_to_xy($n) frac $frac, x"); ok ($got_yf, $want_yf, "n_to_xy($n) frac $frac, y"); } } } exit 0; Math-PlanePath-129/t/Rows-load.t0000644000175000017500000000177711554145213014222 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . ## no critic (RequireUseStrict, RequireUseWarnings) use Math::PlanePath::Rows; my $path = Math::PlanePath::Rows->new (width => 1000); $path->n_to_xy(123); $path->xy_to_n(0,0); $path->rect_to_n_range(0,0, 1,1); use Test; plan tests => 1; ok (1,1, 'Math::PlanePath::Rows load as first thing'); exit 0; Math-PlanePath-129/t/PythagoreanTree.t0000644000175000017500000003261213734026643015453 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 349; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments; require Math::PlanePath::PythagoreanTree; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::PythagoreanTree::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::PythagoreanTree->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::PythagoreanTree->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::PythagoreanTree->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::PythagoreanTree->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # _n_to_digits_lowtohigh() on 53-bits my $have_53bits; { my $bit16 = (1 << 16); my $bit63 = (1 << 15)*$bit16*$bit16*$bit16; my $ffs = $bit63 - 1; ### $ffs ### ffs: sprintf '%b', $ffs my $mod = $ffs % 2; $have_53bits = ($mod == 1 ? 1 : 0); } my $skip_less_than_53bits = ($have_53bits ? undef : 'skip due to no 53-bit integers'); MyTestHelpers::diag ("have_53bits: ", $have_53bits); { # depth=34 # Nrow = (3^depth + 1) / 2 = 8338590849833285 # offset = 2^53-1 - 8338590849833285 = 668608404907706 # ternary "00001101211011001220022202002022022100101" # my $want_str = reverse "0010020200100100011022211200212222"; my $F32 = 0xFFFF_FFFF; my $F21 = (1 << 21) - 1; my $n = ($F21 << 32) | $F32; # 2^53-1 my $digits = Math::PlanePath::PythagoreanTree::_n_to_digits_lowtohigh($n); foreach my $digit (@$digits) { if (! defined $digit) { $digit = '0'; } # mutate array } my $got_str = join('', @$digits); skip ($skip_less_than_53bits, $got_str, $want_str, "n=$n"); } #------------------------------------------------------------------------------ # _n_to_digits_lowtohigh() { my @data = ([ 1, '' ], [ 2, '0' ], [ 3, '1' ], [ 4, '2' ], [ 5, '0,0' ], [ 6, '1,0' ], [ 7, '2,0' ], [ 11, '0,2' ], [ 12, '1,2' ], [ 13, '2,2' ], [ 14, '0,0,0' ], [ 15, '1,0,0' ], [ 16, '2,0,0' ], [ 17, '0,1,0' ], [ 38, '0,2,2' ], [ 39, '1,2,2' ], [ 40, '2,2,2' ], [ 41, '0,0,0,0' ], [ 42, '1,0,0,0' ], ); my $path = Math::PlanePath::PythagoreanTree->new; foreach my $elem (@data) { my ($n, $want_str) = @$elem; my $digits = Math::PlanePath::PythagoreanTree::_n_to_digits_lowtohigh($n); foreach my $digit (@$digits) { if (! defined $digit) { $digit = '0'; } # mutate array } my $got_str = join(',', @$digits); ok ($got_str, $want_str, "n=$n"); } } #------------------------------------------------------------------------------ # _sc_to_pq() { my ($p,$q) = Math::PlanePath::PythagoreanTree::_sc_to_pq(3,5); ok($p,2); ok($q,1); } { my ($p,$q) = Math::PlanePath::PythagoreanTree::_sc_to_pq(4,5); ok($p,undef); ok($q,undef); } #------------------------------------------------------------------------------ # ab_to_pq() # P,Q integers # A = P^2 - Q^2 # B = 2*P*Q B even { require Math::PlanePath::CoprimeColumns; require Math::PlanePath::GcdRationals; my $bad = 0; foreach my $a (-16 .. 50) { foreach my $b (-4 .. 50) { my @pq = Math::PlanePath::PythagoreanTree::_ab_to_pq($a,$b); unless (@pq == 0 || @pq == 2) { MyTestHelpers::diag ("bad, return not 0 or 2 values"); $bad++; } my $have_pq = (scalar(@pq) ? 1 : 0); my ($p,$q) = @pq; if ($have_pq && ! ab_is_triple_with_b_even($a,$b)) { MyTestHelpers::diag ("oops, a=$a,b=$b not b-even triple, gives p=",$p,",q=",$q); $bad++; } # if ($have_pq != ab_is_triple_with_b_even($a,$b)) { # MyTestHelpers::diag ("ahh, a=$a,b=$b gives p=",$p,",q=",$q); # $bad++; # } if ($have_pq) { # unless ($p >= $q) { # MyTestHelpers::diag ("bad, a=$a,b=$b gives p=$p,q=$q not p>=q"); # $bad++; # } unless ($q >= 0) { MyTestHelpers::diag ("bad, a=$a,b=$b gives p=$p,q=$q not q>=0"); $bad++; } unless ($p == int($p)) { MyTestHelpers::diag ("bad, a=$a,b=$b gives non-integer p=$p"); $bad++; } unless ($q == int($q)) { MyTestHelpers::diag ("bad, a=$a,b=$b gives non-integer q=$q"); $bad++; } # unless (Math::PlanePath::CoprimeColumns::_coprime($p,$q)) { # my $gcd = Math::PlanePath::GcdRationals::_gcd($p,$q); # MyTestHelpers::diag ("bad, a=$a,b=$b gives p=$p,q=$q not coprime, gcd=$gcd"); # $bad++; # } } if ($a >= 0 && ab_is_oddeven_primitive_triple($a,$b)) { unless (defined $p && defined $q) { MyTestHelpers::diag ("bad, a=$a,b=$b doesn't give p,q"); $bad++; } } else { # Some non-primitive pass _ab_to_pq(), some do not. # if (defined $p || defined $q) { # my $gcd = Math::PlanePath::GcdRationals::_gcd($p,$q); # MyTestHelpers::diag ("bad, a=$a,b=$b not primitive triple but gives p=$p,q=$q (with gcd=$gcd)"); # $bad++; # } } } } ok ($bad, 0); sub ab_is_oddeven_primitive_triple { my ($a,$b) = @_; unless (($a & 1) && !($b & 1)) { # must have A odd, B even return 0; } unless (ab_is_triple($a,$b)) { return 0; } return Math::PlanePath::CoprimeColumns::_coprime($a,$b); } sub ab_is_triple { my ($a,$b) = @_; if ($b < 0) { return 0; } my $csquared = $a*$a + $b*$b; my $c = int(sqrt($csquared)); return ($c*$c == $csquared); } sub ab_is_triple_with_b_even { my ($a,$b) = @_; return ab_is_triple($a,$b) && (($b & 1) == 0); } } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::PythagoreanTree->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::PythagoreanTree->parameter_info_list; ok (join(',',@pnames), 'tree_type,coordinates,digit_order'); } #------------------------------------------------------------------------------ # tree_n_parent() { my @data = ([ 1, undef ], [ 2, 1 ], [ 3, 1 ], [ 4, 1 ], [ 5, 2 ], [ 6, 2 ], [ 7, 2 ], [ 8, 3 ], [ 9, 3 ], [ 10, 3 ], [ 11, 4 ], [ 12, 4 ], [ 13, 4 ], ); my $path = Math::PlanePath::PythagoreanTree->new; foreach my $elem (@data) { my ($n, $want_n_parent) = @$elem; my $got_n_parent = $path->tree_n_parent ($n); ok ($got_n_parent, $want_n_parent); } } #------------------------------------------------------------------------------ # tree_n_children() { my @data = ([ 1, '2,3,4' ], [ 2, '5,6,7' ], [ 3, '8,9,10' ], [ 4, '11,12,13' ], [ 5, '14,15,16' ], [ 6, '17,18,19' ], [ 7, '20,21,22' ], ); my $path = Math::PlanePath::PythagoreanTree->new; foreach my $elem (@data) { my ($n, $want_n_children) = @$elem; my $got_n_children = join(',',$path->tree_n_children($n)); ok ($got_n_children, $want_n_children, "tree_n_children($n)"); } } #------------------------------------------------------------------------------ # n_to_xy(), xy_to_n() # my $path = Math::PlanePath::PythagoreanTree->new; # print $path->tree_depth_to_n(5); exit; # foreach my $group ([ [], # default tree_type => 'UAD', coordinates => 'AB' [ 1, 3,4 ], [ 2, 5,12 ], [ 3, 21,20 ], [ 4, 15,8 ], [ 5, 7,24 ], [ 6, 55,48 ], [ 7, 45,28 ], [ 8, 39,80 ], [ 9, 119,120 ], [ 10, 77,36 ], [ 11, 33,56 ], [ 12, 65,72 ], [ 13, 35,12 ], [ undef, 27,36 ], [ undef, 45,108 ], [ undef, 63,216 ], [ undef, 75,100 ], [ undef, 81,360 ], ], # example from Jerzy Kocik "Cliffor Algebras and Euclid's # Parameterization of Pythagorean Triples" [ [coordinates => 'AB'], # URLLU in Hall lettering # reverse 10021 = 88, plus row start 122 = 210 [ 122 + (((1*3 + 0)*3 + 0)*3 + 2)*3 + 1, 3115,3348 ], ], [ [coordinates => 'AC'], [ 122 + (((1*3 + 0)*3 + 0)*3 + 2)*3 + 1, 3115,4573 ], ], [ [ tree_type => 'UAD', coordinates => 'PQ' ], [ 1, 2,1 ], [ 2, 3,2 ], [ 3, 5,2 ], [ 4, 4,1 ], [ 5, 4,3 ], [ 6, 8,3 ], [ 7, 7,2 ], [ 8, 8,5 ], [ 9, 12,5 ], [ 10, 9,2 ], [ 11, 7,4 ], [ 12, 9,4 ], [ 13, 6,1 ], ], [ [ tree_type => 'FB' ], [ 1, 3,4 ], [ 2, 5,12 ], [ 3, 15,8 ], [ 4, 7,24 ], [ 5, 9,40 ], [ 6, 35,12 ], [ 7, 11,60 ], [ 8, 21,20 ], [ 9, 55,48 ], [ 10, 39,80 ], [ 11, 13,84 ], [ 12, 63,16 ], [ 13, 15,112 ], ], [ [ tree_type => 'FB', coordinates => 'PQ' ], [ 1, 2,1 ], [ 2, 3,2 ], # K1 [ 3, 4,1 ], # K2 [ 4, 4,3 ], # K3 [ 5, 5,4 ], [ 6, 6,1 ], [ 7, 6,5 ], [ 8, 5,2 ], [ 9, 8,3 ], [ 10, 8,5 ], [ 11, 7,6 ], [ 12, 8,1 ], [ 13, 8,7 ], ], [ [ coordinates => 'AC' ], [ 1, 3,5 ], ], [ [ coordinates => 'BC' ], [ 1, 4,5 ], ], ) { my ($options, @data) = @$group; my $path = Math::PlanePath::PythagoreanTree->new (@$options); foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; next unless defined $n; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n options=@$options"); ok ($got_y, $want_y, "y at n=$n options=@$options"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y options=@$options"); } foreach my $elem (@data) { my ($n, $x, $y) = @$elem; next unless defined $n; my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() nhi=$got_nhi at n=$n,x=$x,y=$y"); } } #------------------------------------------------------------------------------ # xy_to_n() distinct n foreach my $options ([tree_type => 'UAD', coordinates => 'AB'], [tree_type => 'UAD', coordinates => 'AC'], [tree_type => 'UAD', coordinates => 'BC'], [tree_type => 'UAD', coordinates => 'PQ'], [tree_type => 'FB', coordinates => 'AB'], [tree_type => 'FB', coordinates => 'AC'], [tree_type => 'FB', coordinates => 'BC'], [tree_type => 'FB', coordinates => 'PQ']) { my $path = Math::PlanePath::PythagoreanTree->new (@$options); my $bad = 0; my %seen; my $xlo = -2; my $xhi = 25; my $ylo = -2; my $yhi = 20; my ($nlo, $nhi) = $path->rect_to_n_range($xlo,$ylo, $xhi,$yhi); my $count = 0; OUTER: for (my $x = $xlo; $x <= $xhi; $x++) { for (my $y = $ylo; $y <= $yhi; $y++) { my $n = $path->xy_to_n ($x,$y); next if ! defined $n; # sparse # avoid overflow when N becomes big if ($n >= 2**32) { MyTestHelpers::diag ("x=$x,y=$y n=$n, oops, meant to keep below 2^32"); last if $bad++ > 10; next; } if ($seen{$n}) { MyTestHelpers::diag ("x=$x,y=$y n=$n seen before at $seen{$n}"); last if $bad++ > 10; } if ($n < $nlo) { MyTestHelpers::diag ("x=$x,y=$y n=$n below nlo=$nlo"); last OUTER if $bad++ > 10; } if ($n > $nhi) { MyTestHelpers::diag ("x=$x,y=$y n=$n above nhi=$nhi"); last OUTER if $bad++ > 10; } $seen{$n} = "$x,$y"; $count++; } } ok ($bad, 0, "xy_to_n() coverage and distinct, $count points"); } exit 0; Math-PlanePath-129/t/CfracDigits.t0000644000175000017500000001342713734026650014535 0ustar gggg#!/usr/bin/perl -w # Copyright 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 39;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::CfracDigits; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::CfracDigits::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::CfracDigits->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::CfracDigits->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::CfracDigits->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::CfracDigits->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::CfracDigits->new; ok ($path->n_start, 0, 'n_start()'); ok ($path->x_negative, 0, 'x_negative()'); ok ($path->y_negative, 0, 'y_negative()'); ok ($path->class_x_negative, 0, 'class_x_negative() instance method'); ok ($path->class_y_negative, 0, 'class_y_negative() instance method'); } { my @pnames = map {$_->{'name'}} Math::PlanePath::CfracDigits->parameter_info_list; ok (join(',',@pnames), 'radix'); } #------------------------------------------------------------------------------ # first few points { my @data = ([ 12590, 26,269 ], ); my $path = Math::PlanePath::CfracDigits->new; foreach my $elem (@data) { my ($n, $want_x, $want_y) = @$elem; my ($got_x, $got_y) = $path->n_to_xy ($n); ok ($got_x, $want_x, "x at n=$n"); ok ($got_y, $want_y, "y at n=$n"); } foreach my $elem (@data) { my ($want_n, $x, $y) = @$elem; next unless $want_n==int($want_n); my $got_n = $path->xy_to_n ($x, $y); ok ($got_n, $want_n, "n at x=$x,y=$y"); } foreach my $elem (@data) { my ($n, $x, $y) = @$elem; if ($n == int($n)) { my ($got_nlo, $got_nhi) = $path->rect_to_n_range (0,0, $x,$y); ok ($got_nlo == 0, 1, "rect_to_n_range() got_nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range(0,0,$x,$y) got_nhi=$got_nhi at n=$n,x=$x,y=$y"); ($got_nlo, $got_nhi) = $path->rect_to_n_range ($x,$y, $x,$y); ok ($got_nlo <= $n, 1, "rect_to_n_range() got_nlo=$got_nlo at n=$n,x=$x,y=$y"); ok ($got_nhi >= $n, 1, "rect_to_n_range() got_nhi=$got_nhi at n=$n,x=$x,y=$y"); } } } #------------------------------------------------------------------------------ # _digit_split_1toR_lowtohigh() { foreach my $elem ([ 3, 12590, 2,2,3,3,1,3,1,2,1 ], # low to high [ 3, 0, ], # empty [ 3, 1, 1 ], # empty [ 3, 1, 1 ], [ 3, 2, 2 ], [ 3, 3, 3 ], [ 3, 4, 1,1 ], [ 3, 5, 2,1 ], [ 3, 6, 3,1 ], [ 3, 7, 1,2 ], ) { my ($radix, $n, @want_digits) = @$elem; my @got_digits = Math::PlanePath::CfracDigits::_digit_split_1toR_lowtohigh($n,$radix); my $want_digits = join(',',@want_digits); my $got_digits = join(',',@got_digits); ok ($want_digits, $got_digits, "_digit_split_1toR() at n=$n radix=$radix"); } } #------------------------------------------------------------------------------ # _n_to_quotients_bottomtotop() { foreach my $elem ([ 2, 12590, 8,1,2,10 ], # bottom to top [ 2, 0, 2 ], # N=0 one empty [ 2, 1, 3 ], # N=1 [ 2, 2, 4 ], # N=2 [ 2, 3, 2,1 ], # N=3 two empties ) { my ($radix, $n, @want_quotients) = @$elem; { my @got_quotients = Math::PlanePath::CfracDigits::_n_to_quotients_bottomtotop($n,$radix); my $want_quotients = join(',',@want_quotients); my $got_quotients = join(',',@got_quotients); ok ($got_quotients, $want_quotients, "_n_to_quotients() at n=$n radix=$radix"); } } } #------------------------------------------------------------------------------ # _cfrac_join_toptobottom() { foreach my $elem ([ 2, 12590, 10,2,1,7 ], # top to bottom [ 2, 0, 1 ], [ 2, 1, 2 ], [ 2, 3, 1,1 ], ) { my ($radix, $n, @quotients) = @$elem; { my $got_n = Math::PlanePath::CfracDigits::_cfrac_join_toptobottom(\@quotients,$radix); ok ($got_n == $n, 1, "_quotients_join() at n=$n radix=$radix got_n=$got_n"); } } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/t/QuadricIslands.t0000644000175000017500000000574113734026643015263 0ustar gggg#!/usr/bin/perl -w # Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any later # version. # # Math-PlanePath 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 Math-PlanePath. If not, see . use 5.004; use strict; use Test; plan tests => 31; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::QuadricIslands; #------------------------------------------------------------------------------ # VERSION { my $want_version = 129; ok ($Math::PlanePath::QuadricIslands::VERSION, $want_version, 'VERSION variable'); ok (Math::PlanePath::QuadricIslands->VERSION, $want_version, 'VERSION class method'); ok (eval { Math::PlanePath::QuadricIslands->VERSION($want_version); 1 }, 1, "VERSION class check $want_version"); my $check_version = $want_version + 1000; ok (! eval { Math::PlanePath::QuadricIslands->VERSION($check_version); 1 }, 1, "VERSION class check $check_version"); my $path = Math::PlanePath::QuadricIslands->new; ok ($path->VERSION, $want_version, 'VERSION object method'); ok (eval { $path->VERSION($want_version); 1 }, 1, "VERSION object check $want_version"); ok (! eval { $path->VERSION($check_version); 1 }, 1, "VERSION object check $check_version"); } #------------------------------------------------------------------------------ # n_start, x_negative, y_negative { my $path = Math::PlanePath::QuadricIslands->new; ok ($path->n_start, 1, 'n_start()'); ok ($path->x_negative, 1, 'x_negative()'); ok ($path->y_negative, 1, 'y_negative()'); ok ($path->x_minimum, undef, 'x_minimum()'); ok ($path->y_minimum, undef, 'y_minimum()'); } #------------------------------------------------------------------------------ # level_to_n_range() { my $path = Math::PlanePath::QuadricIslands->new; { my ($n_lo,$n_hi) = $path->level_to_n_range(0); ok ($n_lo, 1); ok ($n_hi, 4); } { my ($n_lo,$n_hi) = $path->level_to_n_range(1); ok ($n_lo, 5); ok ($n_hi, 36); } { my ($n_lo,$n_hi) = $path->level_to_n_range(2); ok ($n_lo, 37); } foreach my $level (0 .. 6) { my ($n_lo,$n_hi) = $path->level_to_n_range($level); my ($x_lo,$y_lo) = $path->n_to_xy($n_lo); my ($x_hi,$y_hi) = $path->n_to_xy($n_hi); my $dx = $x_hi - $x_lo; my $dy = $y_hi - $y_lo; ok($dx,0); ok($dy,1); } } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-129/META.json0000644000175000017500000000357714001441522013334 0ustar gggg{ "abstract" : "Mathematical paths through the 2-D plane.", "author" : [ "Kevin Ryde " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010", "license" : [ "gpl_3" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Math-PlanePath", "no_index" : { "directory" : [ "t", "inc", "devel", "xt" ], "package" : [ "Math::PlanePath::ToothpickTree", "Math::PlanePath::ToothpickReplicate", "Math::PlanePath::ToothpickUpist", "Math::PlanePath::LCornerTree", "Math::PlanePath::LCornerReplicate", "Math::PlanePath::OneOfEight" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "List::Util" : "0", "Math::Libm" : "0", "constant" : "1.02", "constant::defer" : "5", "perl" : "5.004" } }, "test" : { "requires" : { "Test" : "0" }, "suggests" : { "Data::Float" : "0", "Math::BigFloat" : "1.993", "Math::BigInt" : "0", "Math::BigInt::Lite" : "0", "Math::BigRat" : "0", "Number::Fraction" : "0" } } }, "release_status" : "stable", "resources" : { "homepage" : "http://user42.tuxfamily.org/math-planepath/index.html", "license" : [ "http://www.gnu.org/licenses/gpl.html" ] }, "version" : 129, "x_serialization_backend" : "JSON::PP version 4.02" } Math-PlanePath-129/META.yml0000644000175000017500000000202614001441522013150 0ustar gggg--- abstract: 'Mathematical paths through the 2-D plane.' author: - 'Kevin Ryde ' build_requires: ExtUtils::MakeMaker: '0' Test: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010' license: gpl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Math-PlanePath no_index: directory: - t - inc - devel - xt package: - Math::PlanePath::ToothpickTree - Math::PlanePath::ToothpickReplicate - Math::PlanePath::ToothpickUpist - Math::PlanePath::LCornerTree - Math::PlanePath::LCornerReplicate - Math::PlanePath::OneOfEight requires: List::Util: '0' Math::Libm: '0' constant: '1.02' constant::defer: '5' perl: '5.004' resources: homepage: http://user42.tuxfamily.org/math-planepath/index.html license: http://www.gnu.org/licenses/gpl.html version: '129' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' Math-PlanePath-129/SIGNATURE0000644000175000017500000016177114001441531013200 0ustar ggggThis file contains message digests of all files listed in MANIFEST, signed via the Module::Signature module, version 0.87. To verify the content in this distribution, first make sure you have Module::Signature installed, then type: % cpansign -v It will check each file's integrity, as well as the signature's validity. If "==> Signature verified OK! <==" is not displayed, the distribution may already have been compromised, and you should not run its Makefile.PL or Build.PL. -----BEGIN PGP SIGNED MESSAGE----- Hash: RIPEMD160 SHA256 fc82ca8b6fdb18d4e3e85cfd8ab58d1bcd3f1b29abe782895abd91d64763f8e7 COPYING SHA256 2e438dc9036ab34c9a123e02017dc0c582867e3e17189b9a7b7c360ba7cd1a29 Changes SHA256 4654ecef69b8cdd446a953fe89011831569991df3e3b245d7c0c8c867728300a MANIFEST SHA256 91eb41c99a794a3fced08c2002ec7a6ed5a8ccb85c20ac26237e6f40dc2f7ba9 MANIFEST.SKIP SHA256 74aa572c75089fd4f3998d086d7e84fa849097615ce9fcd76db72e8965b04b26 META.json SHA256 06b43ef5bdf5d60eb510a84373d0f86879991fcc22401294bf3ae43b5d5137e7 META.yml SHA256 975080120ec8ff7f19e3af4cd05f98b074534450dc79315ed9b7b2214f954db2 Makefile.PL SHA256 48136bbb396c21346df9c3706b4e11f9b5200f11446cd7b7446b3d360ab2e661 debian/changelog SHA256 2e6d31a5983a91251bfae5aefa1c0a19d8ba3cf601d0e8a706b4cfa9661a6b8a debian/compat SHA256 4d071cb3990738ca0454f4911a8b86660f7abff93bbd50e523cf51890ea010d4 debian/control SHA256 8a6574fc77b7829749211607cb8e6c02dfb6adb9e6931dce07ee4df97475dbb1 debian/copyright SHA256 3a475162ee0994750921e80ce4a4291382e2d2bddf7245082c6b50d6a3345a63 debian/rules SHA256 5717e7c840171019a4eeab5b79a7f894a4986eaff93d04ec5b12c9a189f594bf debian/source/format SHA256 1db1d77d669325e60a94bccc86120bf893373d026d6ed0dae3ec3f123b623220 debian/watch SHA256 0372692f3855b988a4e27a891a563d2f71d7b1563d31648a4222b62233133730 devel/Makefile SHA256 74dd87e81a0acd1da3b05f8b43128e25b12f6f7081a64570820dcf46d0a8c86b devel/a240025.l SHA256 2053d9e2b9da1749da107090df45f49f846792a68023f8f1c829043482be23cf devel/alternat.l SHA256 c2fc9a0e57efe3dbd0afbd7f6c02ab20499c5022b7acc8647177f605ae23535e devel/alternate-paper-midpoint.pl SHA256 148faf09a6dd64265d3f611152084ff711a0db20bf29cd9dceb4555d5c479303 devel/alternate-paper.pl SHA256 5804e7ce498c347bd7355468de7cb938fffb3fb6852981640311bdff9be22e64 devel/alternate-terdragon.pl SHA256 be56a17b3870d5e3d190cd1580b175d8228fc86767cb5ab5e8cbbebd14ae1c32 devel/archimedean.pl SHA256 6b22394d442b44eca2eaadf00565726213dc3391cec9521f5a39a9d797770f85 devel/aztec-diamond.pl SHA256 7142f9357726f9ece297e56c990fbe3a8246f20f5bdd61defe7d7e8f329b55b1 devel/beta-omega.pl SHA256 c3a47b0ac5d54bca39bf6122023a8b358703ca2a751a7064c109c5f77df106c7 devel/bigint-lite.pl SHA256 4e2faf50cc839016221f5d69521877452406a01de8c4282c5584e07f96d43336 devel/bignums.pl SHA256 7ebacf0e9aefb24a010f13c2020f041de16619ba54112cd9e27cf091be359cb0 devel/biguv.pl SHA256 265dbc7756baa44ce47d234410b8b107f6fd0d0b641eca64ff7449f67f9edf4f devel/c-curve.pl SHA256 0a3a249abbf602ef6d1c97b9c5a8ead4f6b23403ec67af55f5249b2cbcdcdc04 devel/cellular-rule-oeis.pl SHA256 69b1b04f5bd95d36019183f90a3d28491454363231e7ea0d1fa5bdade43ef976 devel/cellular-rule-xpm.pl SHA256 ede2e21118c99b4be9105b12498354d16523c0717696292cf7130c0ec4862078 devel/cellular-rule.pl SHA256 4c971659cb414022aa396d75e56dc8a41c9790e719d07aa283829065add957d4 devel/cellular-rule62.pl SHA256 e4a61c75ab2143e7f59bd9208c263cabdfec0c4454d768dc119a467ebcab15f8 devel/cfrac-digits.pl SHA256 ea0e36b495c776896553569d8f99e2f84ee18cb2913feb76cc562e3130d26ce6 devel/chan-tree.pl SHA256 7ef51681fa935fee87a14ba4d18c3ab49060b1fb4b93e80ffb92bce6fdefe54d devel/complex-minus.pl SHA256 3fda38491ed1621b037b1f793badabc3813df69543261f7d063a203b67d97f85 devel/complex-plus.pl SHA256 53d5163b4b7757e0d863874eb9bc0f6f7c11681f2709cb9f546f66d2f9eb4f09 devel/complex-revolving.pl SHA256 97ee3fa3f6e0abdaf5a3d5285ae1976d4da5b28c2cd4d01a595c8fd1b5a0c990 devel/cont-frac.pl SHA256 0ded978f0fdc2c3b1d8ec43d4a2d0c1c9ad41dab97ff21dd503efea020cb1ec8 devel/coprime.pl SHA256 d8f1a2a1b7ce846f241676ffb6d18d8500e09a890575a02c5bba03f234228468 devel/corner-replicate.pl SHA256 bd58994fabd719f4e628849ba466feb8a4be6f5c43a0a5f0654bb3a7c49358f7 devel/cubic-base.pl SHA256 7220e82992a6d6c379a5f434ef6fa915b8480ec496c7628bdc97baf1ca876461 devel/dekking-curve.pl SHA256 b0739bb4c770725d9ea1f0dbedefc1bf161eead52689a3b02f2e63a276a1df81 devel/diagonals.pl SHA256 e38a7104bb33975a177b3b74a9131ec834cad6b162db15ef035a78e5990f6b4d devel/digit-groups.pl SHA256 607b1ba76326d52ad6ecba0ce67af22ea6fbe2db7caffd3d24e8b2a84e28b494 devel/divisible-columns.pl SHA256 6b9fdad5c38d02380f44d6828b1d3be2f2578bd9a8016e5f8910f73aa3f29a43 devel/dragon.pl SHA256 e2bd28104c076a169913d802d573ec25163ae29ece3554128e76f27c3534c592 devel/exe-atan2.c SHA256 8e77eb942cf72c8d843572a7f4cdd94770de99d831aa17c43356a0b26c039854 devel/exe-complex-minus.c SHA256 eb09cbcd4efc5fff730f31bb56abf8591b93e369493a6b91d4189b6a39c33da2 devel/exe-complex-plus.c SHA256 cbb0db8fa798c6702fbfefe93427af2d7809376db62bad6cac13afe6bbef8f1d devel/exe-complex-revolving.c SHA256 c279bf293e41640de5799609daefbf54261540762869a2ed467bc4b647bed6fc devel/factor-rationals.pl SHA256 a329486258007e2a6db3b1df43690a95fc448d3029e8c6a92f4baf2e3297b1e2 devel/fibonacci-word.logo SHA256 99a83d24c52d91923dd480c15fba2dfc609961132655edb21440425042a0b5f6 devel/fibonacci-word.pl SHA256 baab6e9be22667d0779465e818a0a356b5a230e957f50269ebd17a32b936ae9d devel/filled-rings.pl SHA256 c95e3f94d1ce843a23f6ce10ee19a9cf31a61ee60875d58d11ee87f9edf11b90 devel/flowsnake-ascii.gp SHA256 becd2091b4ce5961cd0df30864c127e5a84ed3c16fc77215335ee3d6315fcc79 devel/flowsnake-levels.pl SHA256 4169dde460f8956e11d11b9497d19f778feeca11fdf7e6a6cb1e49b05d45c184 devel/flowsnake.pl SHA256 8b570fb9dca79eb4c0baac38914782336378080b554930d893dcbd74c91fae7f devel/fractions-tree.pl SHA256 3c11dd8da46d62ced6b856ed1ec7031d1674410ba70db7f3544f26117165476e devel/gcd-rationals-integer.pl SHA256 ff6f0368147ffef031278e12d368c906b33bafed5e9192b97b4048378688dbc1 devel/gcd-rationals.pl SHA256 f043205b15233fa903e37910f6edce8cf1201198cae400df6ae3483a389b78fd devel/gosper-islands-stars.pl SHA256 abc32e740230f6a2469ffb1df153a67968d5a1842e3bbf39186df3bf506455cf devel/gosper-replicate.pl SHA256 e49a2892ccf2c7ac50e483de17e33658a284d30898c7dc69b99d8bce5dbe40a6 devel/gosper-side.pl SHA256 c8e61a8ec31608fa2b4dd34508b3ff8b242e1b2d1c11c232869b81c444cd3fa0 devel/gray.pl SHA256 6c20d3b0a23e7b3347e21f41efcf289461e207297d69b2e927a318d466daac6a devel/greek-key.pl SHA256 8bb62ddd60197ecb3f8038574d124400f4cfb0923b4490ed2a79be32905f7760 devel/grep-various-values.pl SHA256 b793898e4a2cd2e118b8b69a05bb4042a89a40eaffb8411c84271a9658ba79f9 devel/grid.pl SHA256 6ff5ad4267356f47b281d97c37940a4e2042adb5ba7109bdc9840dd99afe6e53 devel/hex-arms.pl SHA256 aa5a81e4e97deb5f7b9f58399aeceb96c37f90de5b7bbd95a7571d3680d7e802 devel/hexhypot.pl SHA256 42592f1ec812d10b6e6ae7c59f0e81e369c340fa41206a2e777c117dc8b2e514 devel/hilbert-diamond.pl SHA256 e6ad0703bb821b9b7b98d284a664ff877bba660b4aba37d7ab0b49f3b6c3df63 devel/hilbert-sides.pl SHA256 9380e3243c7420c393b7988630bcd34e76dccbb05406cf6389a328eb2fdaef7c devel/hilbert.pl SHA256 07313f92ad632e5b9f310bd862b08ac3bb02d7610a122e05e7d053bd49068c0d devel/hypot.pl SHA256 31d8abfd402178a824f663a0714fd6a97cb508de3f2ff2f00ff0580003ba09ac devel/imaginary-base.pl SHA256 1ff4bcecc63e1e6106f895a73a81a4001ec53f1fb4c5e528e4709118059b32b0 devel/imaginary-half.pl SHA256 920d6883922cb5cb9a9805cebcd6c63011c57648982193bd9af2e292935e94bf devel/interpolate.pl SHA256 3a63a3939d76c2ee98973b8560ea0bb32e2a629d2eb37ba97ccb9c5e28e29c36 devel/iterator.pl SHA256 c53e8e0dec716c3381a69f34163d0d4c327cca63c7726b51760cdc8b70ceb039 devel/junk/FibonacciSquareSpiral.pm SHA256 89cb33394257b6ed1f4778e47a98bcbc8d37ada4a6512af6856e3b2f6892ff22 devel/koch-curve.pl SHA256 c6ce4f32e8ff317cdf32744bd98751f56e47ca56d6ce61a3c0ba372cb18ec58d devel/koch-snowflakes.pl SHA256 8bbca97415a495ac84f7ff3e322eec715f002a8ffe4c5c9c76e982ae86cabb08 devel/koch-squareflakes.pl SHA256 058de387895a1d5d58acae6a3e6fb56869144025d25ea7306fbdf59cd014db9d devel/l-tiling.pl SHA256 51c63dcd99b9e04803e50e1f6835926b5acfc16f8e3e33d9e156f6fd63b3c2a0 devel/lib/Math/PlanePath/BalancedArray.pm SHA256 af4f5d215afca944c0537cf379da54feadacd41081e46814744463a529c3c52d devel/lib/Math/PlanePath/BinaryTerms-oeis.t SHA256 d529074bd3a6e60d468f23bddbbeceb042f98404c36576a05b274a1988fd11d0 devel/lib/Math/PlanePath/BinaryTerms.pm SHA256 b6950cd189aae6b4f95b1c1dd2af93772c762bdd0728468881f2e46e331411f6 devel/lib/Math/PlanePath/FibonacciWordKnott.pm SHA256 96a05d43524317c7396a6ae054caaaf1b8435924c9eb0036099aa482dc703609 devel/lib/Math/PlanePath/FourReplicate.pm SHA256 a7260a34967a2f22e740c4eefe76ccf8a90472ee3391cca029c88f3ad3a203a6 devel/lib/Math/PlanePath/Godfrey.pm SHA256 6d2199fc9d6c9067e3d41cdaf81f424631a898c9c2a367c6452a5610e0fba6c6 devel/lib/Math/PlanePath/MooreSpiral.pm SHA256 b7c16df898946f6a8d744a762b06d2e8fe5a8ea80919254f0a94c1bd95edb310 devel/lib/Math/PlanePath/NxN.pm SHA256 c0b26681714b46318d9dc4984d2fb5bbea8006da1d68597d433a5e99decb9c0f devel/lib/Math/PlanePath/NxNinv.pm SHA256 c57c27d8de2b48d6b7dbcc1e2571c92588379c6dcbcd541048d61a79fb0dd486 devel/lib/Math/PlanePath/NxNvar.pm SHA256 f0638bd7550f0afa4cdc06732dd0d463c0eb5b0db7064327d6f35e43fdc32aa2 devel/lib/Math/PlanePath/ParabolicRows.pm SHA256 670a29b4ea2fb427c51ba57f04f87ffc535e0a0cc17536d98ce8efe34e4e94cf devel/lib/Math/PlanePath/ParabolicRuns.pm SHA256 6e12f5141d95a2c033589b5cdeb6873d0a1a076a3704dcb60d2d5f22e0259c24 devel/lib/Math/PlanePath/PeanoHalf.pm SHA256 f7c12a63a931c2fe98927c5d2154143e729422f00d2510cad20b65036f89d027 devel/lib/Math/PlanePath/PeanoRounded.pm SHA256 e2f217c47fece8638287fa0519f27ce7634d49a67a0f290f89dc29c626dad46b devel/lib/Math/PlanePath/PeanoVertices.pm SHA256 5dc146f72bc2e4a4d7cba0e1663aed05de47ef8d86f984942ee2bacceebcb5a2 devel/lib/Math/PlanePath/PowerRows.pm SHA256 85c2e8276764aa9e70508db9ccd3680f73da1238cad54fe128214f6fa3cc4313 devel/lib/Math/PlanePath/PyramidReplicate.pm SHA256 8cc150a9e8e561910eb222f1512fa43b828eab62615326bac84844b3a9f11389 devel/lib/Math/PlanePath/QuintetSide.pm SHA256 ab56fdecb0be7609a0077d407581dec772fadc7a5453c6ec59b9b90d08e1a202 devel/lib/Math/PlanePath/R7DragonCurve.pm SHA256 a8c082649d441c7df57f4374fc55d4afd45acd2535ba7612858ede69a7516584 devel/lib/Math/PlanePath/SquaRecurve.pm SHA256 bac2ce5f0dc00f9aa1ea01572d5b1225b812c5ab1d2cdcd12ccf24cd9cc85266 devel/lib/Math/PlanePath/SumFractions.pm SHA256 e117cfd355b3142eb7c7200bb5de9f2509b94222fb4436cd7da8d2ba575a55ec devel/lib/Math/PlanePath/WythoffDifference-oeis.t SHA256 207f367c3a9227e9e04b0b2994b8b6cee24b452fad13ede6c24e737824bc5ded devel/lib/Math/PlanePath/WythoffDifference.pm SHA256 cfcf0709c52d17507535c64569a8fb6ded55f0db77b8ff6b03352554a896807b devel/lib/Math/PlanePath/WythoffLines.pm SHA256 1de1cf5d815edb5e15400e57e5a8a3890f012ffec2da98079a510d67a144b928 devel/lib/Math/PlanePath/WythoffTriangle-oeis.t SHA256 0cef668f0b1cdc9379bfee42dacbc6080ec2f8cfa2edb85de249523910a7f1f2 devel/lib/Math/PlanePath/WythoffTriangle.pm SHA256 369a68ac21c063f0f634a4a8e949daa9d980d15e6747d2876fba00062bfeb025 devel/lib/Math/PlanePath/Z2DragonCurve.pm SHA256 4de3e59a64df6bf9ce81d1837794da0be75a53e521fef970c53a898bbb2ae66e devel/lib/Math/PlanePath/ZeckendorfTerms-oeis.t SHA256 e3b098158ad9b059c798389172101e8684687f936492b4bc38e2d99b30e859f9 devel/lib/Math/PlanePath/ZeckendorfTerms.pm SHA256 203947dccc89de16c23b8bc436761b33ff17dd47cc4c7622540376015b74b199 devel/lib/Math/PlanePath/four-replicate.pl SHA256 94048a17f0bbe56275bcf497dd5e98e4a41194b21ba1c35f850625001743465e devel/lib/Math/PlanePath/godfrey.pl SHA256 2ce33e9c29c473fb7345c39fc664cddd888c2125117cf3b5b036e998a289c3d1 devel/lib/Math/PlanePath/squares-dispersion.pl SHA256 28e5b374dec6563d79df4b1bdacd45ed54779d07c8b2f086ef79fb770bbcc72a devel/lib/Math/PlanePath/wythoff-lines.pl SHA256 2d6918b8cdcb25f42c5bbf9a24060647a586600e13c1e616811cd10000df9628 devel/lib/Math/PlanePath/z2-dragon.pl SHA256 a7731657d308dbedf7042360d54bcbd1e9599873fa2bfb6f1889f35cf04b9599 devel/lib/Math/SquareRadical.pm SHA256 6ebb6eb1c54ab817c72d97ce04167af8968277f880835472fa3133b87840c15c devel/lib/Math/square-radical.pl SHA256 be4fd9c0aebe95bcfa61454c9e9ee2c3cc700b6aa54d7b1a0d345e307b2ffb0a devel/lib/MyFLAT.pm SHA256 562694d22627de7eafc737075b0c68f6155b2062f58f90e4f4a71df25b1c3785 devel/lib/MyGraphs.pm SHA256 e887716f9abe46e5118978c8bee504adf7ba7e5b3812e01da67c90f12c76658c devel/mephisto-waltz.logo SHA256 f2d15a59c783203d522d81793ffc681e90672e0f5524eaa8d9a526c85bcb7bd3 devel/misc.pl SHA256 96f16a900c507a193d3bf4a420cfa74cb192da08a0a49a69c2fcb37b0cc13966 devel/multiple-rings.pl SHA256 016da3cfa7611156eab741de753e8145330f7c8061f14f1bc216bce4b61e7515 devel/number-fraction.pl SHA256 1f6e6b1c3d194e7d8e6dc4fd5dbf1096ed634e23a7ed368b534ea4caa6597e84 devel/numseq.pl SHA256 67684051c380fe74cbaec4ddf18549799cd2829c53776c948ec9bb4a80757753 devel/peano.l SHA256 5317c2da46081dd9dbe43b2ce6d008a27c92a8089abf10abc324030f44d3d803 devel/peano.pl SHA256 e99330c03a32c957ef60df92dfcc5aa0190ef0304ebeebec9aed46113b0a2c12 devel/period-doubling.pl SHA256 2bc0b1436d9b4c98ff588580e3da7bbd9e07e6d90b38a690de31eeeaab99670d devel/pictures.tex SHA256 91b38bf3a7b7f2e20860296441149b701c79dc238aacc645b18cbee0ac9f69a8 devel/pixel-rings.pl SHA256 5c206d781beeefdc38691f57d613346b375f47f781efc082b6fb3a0420c3c060 devel/pythagorean.pl SHA256 e5cafcd1c2a8fd53ebb5c45751fa759ed03ab53907a7cd06c96bd2ad497ccfa7 devel/quadric.pl SHA256 8a7d744481f4771c1830d40fe0ae0d13d7fcd2491e32762a55f5d38478d42de1 devel/quintet-replicate.pl SHA256 774d93f2558a4fabb48e7d8fb796bd0ad64e0639f64f3c5d43f82ec8810dd8bb devel/quintet.pl SHA256 518f205ad32e8dbe282612d8c7d4b321e663a31c679bc30938f0072cd93f089d devel/r5-dragon.pl SHA256 339678e080c24df60edad0501aff244f5e2465bcf5d7e93f3fd3e5d11dcf5c04 devel/r5.pl SHA256 d6dc87e38f50880e8e33035d020523706f2151ff1bbf455986ef47261e2bb548 devel/rationals-tree.pl SHA256 a78c1ece2e3b5c08936db34d5f4dcd67134522793b7f1fc6002b5c5b9dd95823 devel/run.pl SHA256 05539100a770924000a9c38d114054f908e2ef5c3eaae899cf9643357898a8a5 devel/sacks.pl SHA256 f96a7b37efcbe1b122e50f1246a90fbd3399a71e2f5e3461d9f7da9e564cf9a6 devel/sierpinski-arrowhead-centres.pl SHA256 8c1c81bef68212be09ba94916745dbad1996f519a67de15a297be05a7be6234f devel/sierpinski-arrowhead-stars.pl SHA256 48917029361fe052998d6a4fc3fedd62c733196bac01801a7c8dc24b2433cda0 devel/sierpinski-arrowhead.pl SHA256 1c09ddc670763b0754f23a565ba6e70ef47d36b274f7f7786df803040fe23d28 devel/sierpinski-curve.pl SHA256 7cf6668acf66f7d1f1246e614238dd5a06b47e49dd944a55440cda13e1f836a2 devel/sierpinski-triangle.gnuplot SHA256 aa18f11218b38dad9720752d4e448f03b19d07429cc510d2dfd13b5d8c83a1d7 devel/sierpinski-triangle.pl SHA256 de764bf9e7d20ec3e787658bf2724249e5fda142df90b54cec333071a6359201 devel/square-spiral.gnuplot SHA256 e376802b15ae6e54cfafd0bfaf3355de215a5cab6d7e849784adbfc59ffa8fdb devel/square-spiral.pl SHA256 d49123415df5a9b2e5a8fcdaac6aac40248662b77022def1f5a398e780d8808c devel/squarecurve.pl SHA256 e722441fcf9730c1bcf3c69599fceca5b09a294ca1156842280c822914724e27 devel/staircase-alternating.pl SHA256 a1f365610b3ba8de003ec49b26c07894a603ec5e60f858fd8fc933098266f182 devel/t-square.pl SHA256 632f01f8ed8e0f00087360d7ab2cb34810211f02ebf0888005e554f2afae30ec devel/terdragon.pl SHA256 8aeeebad0d6f350c46b2a00ed8d2acc53ba9b6c1bb4ce26983c81ff53e55d3ed devel/theodorus.gnuplot SHA256 10028e4dcff991d77522f4095c2aa98e9f0138b6396d1d11d0c315f1560a813e devel/theodorus.pl SHA256 3c4d8b6cc46b579de052583d1b5bef24fa3650f49794e802fe438de66fc8d378 devel/tree.pl SHA256 81d1c31e85fa83f46faae767c23cd858c47197d9b8863b8c0f40f459ccc04f75 devel/triangle-spiral.pl SHA256 136f211010af7d7298a375fd33e137c762e9d3cc9374b23926188da5cf0f3988 devel/ulam-warburton.pl SHA256 5e260052952cf76e72218462301e704d7ba8e659a4e3f8f1928eb9fcfa909d60 devel/vertical.pl SHA256 2e47d2f2b19b855ad032f7ae466b68f29f80dc4c38d8c13bafe7ee0af112071d devel/vogel.pl SHA256 9249a4e946e11bbd30186baa81ea424c5243f20eae4198017e98fb9636972334 devel/wunderlich.pl SHA256 f1b72008a76237d52040a759d9b142e30196969f58ba99e1d9d12e5a1761da4b devel/wythoff-array.pl SHA256 367043c8e4668925b9a983a99e6e8432a1f9c194c3f934387b684b32d0e04e7f devel/zorder.pl SHA256 d6ea3db4420680a893b93c61a2807a135f2a1eb55adecb8e0067473ddac37c37 examples/a023531.l SHA256 10e89cca429f2633f583dad3ed7604ccb82d8d3acfe431bfa82bc5dd9a44e943 examples/c-curve-wx.pl SHA256 0da9971dbe34517795ceea2d2fa4f22c1e5e7b65a387dd74ffcd4f10d8636a83 examples/cellular-rules.pl SHA256 f17472f4db24af547d84a51c91c4e17cc8c715512c6c73772a4153c069c5ddff examples/cretan-walls.pl SHA256 325d4bd73562e88cf0bd73073bb0727673f80375a12484af37a31269c9a6bc3c examples/hilbert-oeis.pl SHA256 339bb58d3619cb6bbf5b97b2a3ca4945dc96750546175be00b54e467f7942857 examples/hilbert-path.pl SHA256 e2628455be36be3840fdf71e5589bc103a83044c3b0588a2e137d32991d16883 examples/knights-oeis.pl SHA256 8b2fd8007885dcb1607b2d89332bdbf4e78ae35eec43d861a597590c146c9e75 examples/koch-svg.pl SHA256 1949d2566cee71821663d91935198c75819704d20054445471ca0cfa26f67f7d examples/numbers.pl SHA256 5b768a2953c29fb00e6e80994317be64fddcfbb85fece46c86ded010ab9a00ba examples/other/dragon-curve.el SHA256 e869473142a9afd15cca6e99090e821460788dce3965990171c413ab5242a13d examples/other/dragon-curve.gnuplot SHA256 d9e592d8084f2010258a5b7fbc644973e3c69f582ae595a347b22604a8e67a2a examples/other/dragon-curve.logo SHA256 77d28a35773b2b86b400a081a3645dc514ccc9f468fb735625976aa59d11f0fa examples/other/dragon-curve.m4 SHA256 6faca48ae81ea9542463cc9a5805ea78c443611d4799d3470202f0c95ed32487 examples/other/dragon-pgf-latex.tex SHA256 cc1942019f84075aa8e3a75dc46f2ce274001b3a41a9881ecc67bbe8e49868b8 examples/other/dragon-pgf-plain.tex SHA256 77968f689eaafeecfa0d6190b74a23cb003ea3cdd079ad2d7a28cb5f9a0f3f61 examples/other/dragon-recursive.gri SHA256 3928e393cb4d2c9bf6f026d5bb020fde7dff973e98527e8138ce1f58ccf1252e examples/other/fibonacci-word-fractal.logo SHA256 23119ce5548085babdbf072f8d4c27ddd98bd9dfe1a39fae0df0839bb79818c5 examples/other/flowsnake-ascii-small.gp SHA256 493b400392d9c404c1739c881aa0dc46bc2812750b4ccc6a35b3bfe278a417e5 examples/other/sierpinski-triangle-bitand.gnuplot SHA256 a36fb4541eedf7b29c5573d4faa3e5eccf43d16263186827ed0ebecb16c24a41 examples/other/sierpinski-triangle-replicate.gnuplot SHA256 eccd571b571c8404e841389d3f6ceb4cb7a3586e1a1e9562a2c0dfc841c4cb25 examples/other/sierpinski-triangle-text.gnuplot SHA256 4f696685f3007b45383b68e5cc0b7be530f7397fe9dfec2b2659019bf63a50be examples/other/sierpinski-triangle-text.logo SHA256 ef42ead73ac8f2443c267b9d40c35ce2313b1126537d883740b89e06facd79fd examples/other/sierpinski-triangle.m4 SHA256 8ab0303ce513fec53bd6545308bbb2d4f273496265f1d083f68f30047905e4dc examples/rationals-tree.pl SHA256 7e3fa4dcf56df213f53ba1466c8aa772a2196f2158b27aa5d7588618b6f1f0d2 examples/sacks-xpm.pl SHA256 1f0fe74886d7ac6f6c58715bd286ce911b072ad39df093852e19a54b053fbbf3 examples/square-numbers.pl SHA256 e39cdcb58827fdf80938031453468dfc43fea671247e3bbe9cfaed0af7cef84a examples/ulam-spiral-xpm.pl SHA256 d0404ded27d29c9d90d5603ea1337b597c32b40218084d90463feea414a08fff lib/Math/NumSeq/OEIS/Catalogue/Plugin/PlanePath.pm SHA256 17b5d073784399835dced531b443e29d26e5db32c7a7e181a9b06959b4c5a948 lib/Math/NumSeq/PlanePathCoord.pm SHA256 2cf87261bf326d0d36b3945484196674a716b85867cd4de8cf4f24e22b3ce11e lib/Math/NumSeq/PlanePathDelta.pm SHA256 424db014c30d1a585e82fbf900bc905c60d5431db56c95212e28028de7d396af lib/Math/NumSeq/PlanePathN.pm SHA256 38547d92fe6e104a431e84d2f42419ba7f66ee6976c226e6603e965b5e5a6b2b lib/Math/NumSeq/PlanePathTurn.pm SHA256 f8bd4df33cd90235a4b774f4d99507fb264f811fd4fddbc6bd2d8a8028c2ba7c lib/Math/PlanePath.pm SHA256 207f5a635f4476396aa0c5c1173522777bd34ec7a9148cf11ea10394da737fbe lib/Math/PlanePath/AR2W2Curve.pm SHA256 f8b5117df69106ee1092aab5520ed476c0ffebed9032de94688b48591c3d83ec lib/Math/PlanePath/AlternatePaper.pm SHA256 dfbd469b4b774b5da54595bde0997321b90c54ded72b17e9c31470ab218c03cc lib/Math/PlanePath/AlternatePaperMidpoint.pm SHA256 baaf0e6cdf5b36564b2d8f3e6cfe303a2304a8a0961a56d1f60fce0c91341273 lib/Math/PlanePath/AlternateTerdragon.pm SHA256 c47607f3af0a4c9cd38b515e96e2e85b0b93ff9acc265dd8a8418835af0e347b lib/Math/PlanePath/AnvilSpiral.pm SHA256 470c53d3c4f57c581296443692ed0c3fbdb8129fcde9b6892c2a537179b89996 lib/Math/PlanePath/ArchimedeanChords.pm SHA256 81245cd4806cdcb9a9cd36424eafce293f51d18efe53eed7b933da8c665640f7 lib/Math/PlanePath/AztecDiamondRings.pm SHA256 4e349e01a4fbf4158516cc1e0035dc2f97f6764b1a7d73789f1dd895507720f8 lib/Math/PlanePath/Base/Digits.pm SHA256 affffd948e13736aa8bbecb96f29f2b0d2e755c752638388cb4f4507673dc3b4 lib/Math/PlanePath/Base/Generic.pm SHA256 2166e040bfee8ef5d2bc709e9ba438214c916cab9602ba2f9435db51a78937e9 lib/Math/PlanePath/Base/NSEW.pm SHA256 40466adf270f096354e8dabecd71a42ad1d4eb93127c8e583b00a69cf18bd449 lib/Math/PlanePath/BetaOmega.pm SHA256 d21c1115a2bbf5e917c36a4c14afc6ae7d713bb8db1b5e8defdf63b1e1d96513 lib/Math/PlanePath/CCurve.pm SHA256 4ae361a22064f99592b32ee24f76cd1417ba5b4a6d11725247cbab843b3bd8a4 lib/Math/PlanePath/CellularRule.pm SHA256 459351dc4c5d79c833a9ca98ff6ba44490f63100cfffc3314f7ba6df0b9d442c lib/Math/PlanePath/CellularRule190.pm SHA256 782ae3b65c445b953f853969eee32c4d131550133f9fb53e7b8657cdd89db8d0 lib/Math/PlanePath/CellularRule54.pm SHA256 606de46f792709ea12ef1b4630e193cb88ff07d2d20976df403b129ca6a75a96 lib/Math/PlanePath/CellularRule57.pm SHA256 f7141fdd79d54dfa57091a3ed6a1592673c53f0b41d36bf4d1e65f9a51b3079e lib/Math/PlanePath/CfracDigits.pm SHA256 170ded2a0f9e0b599f7ce20348fc7d0abeda076a53e7fdcc765b199b218d7e70 lib/Math/PlanePath/ChanTree.pm SHA256 b92f501f81d3baf88614d12bf7b77c53f700de6c39b20b260e1630f1953868b1 lib/Math/PlanePath/CincoCurve.pm SHA256 625e151f317dcbbb6c78c3caf823ade5bed5bdc189ede24bc6e1064b133e11c7 lib/Math/PlanePath/Columns.pm SHA256 6fcc7b8ecd5442a1c01cdf7e563eb8fca4bf27c3c48ce684d67713425a4d4576 lib/Math/PlanePath/ComplexMinus.pm SHA256 abbf01046390f44cc6f4d4bc41ff268cfe0b3b653c9cc9da03f668cf10a76a05 lib/Math/PlanePath/ComplexPlus.pm SHA256 ebdfd7fb30904f3630e2c2999c72297ca725757dc642fc504f85c2ad1676264e lib/Math/PlanePath/ComplexRevolving.pm SHA256 bc434624e21bc70b67ca1f7a776200e9cf969481dd875a10a67c00923cf48c1f lib/Math/PlanePath/CoprimeColumns.pm SHA256 0b0da17624c699cf588ddc62a16207d544e8fdd2181ee1b59ab402a614944393 lib/Math/PlanePath/Corner.pm SHA256 394db7b94eb470efa513805dfe81ff1c9021e25ae382ba17450151dcaa15af5f lib/Math/PlanePath/CornerAlternating.pm SHA256 9de7b0ae508ec148d92cab8a36e35c7e07a43241f63e3e4f7ddda77c60e2c0ab lib/Math/PlanePath/CornerReplicate.pm SHA256 7f4fe7d9d6f7de38a43ffc2ae90e207ef7f3ad217456e6c1c589e8e4d02e7d2d lib/Math/PlanePath/CretanLabyrinth.pm SHA256 305527b9aaee9f103ca868046880a411ccd303301f58e829450e97c742deef64 lib/Math/PlanePath/CubicBase.pm SHA256 624746d9ae06b511c39ed40c845bd161d10400a0ef6d8a41f7a685bf35b306ba lib/Math/PlanePath/DekkingCentres.pm SHA256 8ab7c3a587fb3e107fb8ffbc7e58fbde782717d69a3141594b88062eb4f43d6d lib/Math/PlanePath/DekkingCurve.pm SHA256 e1a22ea8e432cdf3fb7967c40ddd3dd09937dd58a36bece5c7079481aac022ec lib/Math/PlanePath/DiagonalRationals.pm SHA256 d5794142cb7929daa2b5e483288f7ee70d76e916c0a484a4796b60dcb60706be lib/Math/PlanePath/Diagonals.pm SHA256 9e41d0a5502b38fbdaf96cf0b017efdf4f1f2e0d33cf8e14656cc0c22fda5598 lib/Math/PlanePath/DiagonalsAlternating.pm SHA256 06c3af3ec1b2f276d7cea4b06ca46890c371c90441b9489bf53fa043c6a20bd6 lib/Math/PlanePath/DiagonalsOctant.pm SHA256 3fd5aa9d99d95794fbb5d218483f4f9dcb7c64b9d8c1f0b9a19410e795f3d93f lib/Math/PlanePath/DiamondArms.pm SHA256 f4e5127d5e855daba17882f66abf5f8642069c9f600e82e5a79a24751cad59ac lib/Math/PlanePath/DiamondSpiral.pm SHA256 665c14adf78d65e7a57fdd00d92901a06b59467faa92887274e90ce402d6fc1b lib/Math/PlanePath/DigitGroups.pm SHA256 78caa5561a591fdd563c3a703232357a95645a225ee389e7e649fc0e755ca1bf lib/Math/PlanePath/DivisibleColumns.pm SHA256 0ed547e8a9d8472a9baf82cb73a3c219e701a7347789673b86d44046220b9671 lib/Math/PlanePath/DragonCurve.pm SHA256 d70bb85bcadcdb2f3304a31b63084b71bd539764443143a997e26ffa348020d3 lib/Math/PlanePath/DragonMidpoint.pm SHA256 2e5b3d3583dbdc8caabcc20b88059a7bdcd6be02f73cd987a71c76690fd901b4 lib/Math/PlanePath/DragonRounded.pm SHA256 9319e70e32a3f8bbb5f31cdfd50fc8a92a94a0493c3cf5796a54da17e58c6638 lib/Math/PlanePath/FactorRationals.pm SHA256 eeb89e23750f44d6a8246872f6d76a93c83fe45c465a5445d42e3b89c96fb908 lib/Math/PlanePath/FibonacciWordFractal.pm SHA256 7c7598b47377b0bdfeee9563b98b10a9109130dfe75156981d314e30d78139c6 lib/Math/PlanePath/File.pm SHA256 22b4acb87f3e6f8d52437a90fbe8b4eef629f6e6f62943f486a59e13dc4c0654 lib/Math/PlanePath/FilledRings.pm SHA256 53305a0c1cd4122945bd6264e0eab9b1cf690b7c67912b45f8677248a3177260 lib/Math/PlanePath/Flowsnake.pm SHA256 c605861bc13d07c7a12a99fb75b18683a070eb4a2f89d89990b359469996d9e1 lib/Math/PlanePath/FlowsnakeCentres.pm SHA256 ed82bd016bfa00c04d01081b5002ca051db9bbab98d70acd22c5e9c671196aa0 lib/Math/PlanePath/FractionsTree.pm SHA256 aecff6c8e48e0a46649ea0cb1f8591d4d111040fdf3e4510d803971995263d62 lib/Math/PlanePath/GcdRationals.pm SHA256 e86bb827508034ad77d734812c0169d530b48468e1fbed79a2132a34f9303c5e lib/Math/PlanePath/GosperIslands.pm SHA256 a0a9343edfcc7dcdff882411a7f3a6618e9b15409c545d6402e8d30d3541d47b lib/Math/PlanePath/GosperReplicate.pm SHA256 8441aa3af36e67c5150942715ce24969c7f16ebde23948eef9d18fde719a8c22 lib/Math/PlanePath/GosperSide.pm SHA256 45c777462495c324c8eea8ca58503ea20058b0509c79051532328eea53e35a6d lib/Math/PlanePath/GrayCode.pm SHA256 63b65235cd1d7e6646aebd4fe793a1f14d62f635d6275d267545ce4c4a046b51 lib/Math/PlanePath/GreekKeySpiral.pm SHA256 e17dba3980f37014e7c04e89a3d173518be8c4858e9e00c9e185567aeac4b847 lib/Math/PlanePath/HIndexing.pm SHA256 9f66f589527038b041dbe8f1ced0f564492c5f940f016482ba843a6d5100e600 lib/Math/PlanePath/HeptSpiralSkewed.pm SHA256 2a57afc0e4103c849aed568cdf14e9be6ef0068c1c67d23ea1a8abe5990b6f41 lib/Math/PlanePath/HexArms.pm SHA256 d7229d8477900780889486a4f7cd53bfe004bcf4f9ea3e0c51f2bbd8753b18c1 lib/Math/PlanePath/HexSpiral.pm SHA256 28978584694b8c6a2e0180c495cff5f87a41265ecc003f947e241e47a86e5780 lib/Math/PlanePath/HexSpiralSkewed.pm SHA256 c5301e7419cabfe6b63fc119c51becb2d9adb1b30717083fb9ec16094221404a lib/Math/PlanePath/HilbertCurve.pm SHA256 251ac4f5598fcc04c6a4866a69a0ecd985c98cbc635de3eaf7906f7514472ed0 lib/Math/PlanePath/HilbertSides.pm SHA256 0f405f10678dc683a1c7c2ce2e083871afc39cca4b938ada655bd2a731c313c1 lib/Math/PlanePath/HilbertSpiral.pm SHA256 6088e48673b089c198567a34cafd43c1fd2a0e02f9681e3eab048fa75ceea4d4 lib/Math/PlanePath/Hypot.pm SHA256 1562a13fbe833b3c4c16b62356323a41dff98510807ba7ec5ccdac8ffa6f0773 lib/Math/PlanePath/HypotOctant.pm SHA256 f5a084fc6c8b44efd4246d37ed533a53de6091a56a7cf6971e5c15e3f838ab96 lib/Math/PlanePath/ImaginaryBase.pm SHA256 01526392becd8655f2e14bb70a4a1675bc872b8bfe47baaf0f83d1732ca2ea6c lib/Math/PlanePath/ImaginaryHalf.pm SHA256 d5798287defa43472cdb555ad4ce6e364be781a61383741eda23f057abc0d41c lib/Math/PlanePath/KnightSpiral.pm SHA256 142d2076294fc37c46a55d8f3fd02fd54bd23d0aa53a8d9980da3c2d6bcc6b0a lib/Math/PlanePath/KochCurve.pm SHA256 0fb0c3a187492ac56ed6bb8d2734d814ba1961f67b6f87ad497d1d194939fda6 lib/Math/PlanePath/KochPeaks.pm SHA256 052578a9e78a51bf557d6d4a1b2b102c3b8af025de0716d37ad725465145f2f7 lib/Math/PlanePath/KochSnowflakes.pm SHA256 5ecaae315053bc5327749106135940a3af3c63308526896420a2a2301ec145cd lib/Math/PlanePath/KochSquareflakes.pm SHA256 e1d9d826552fba73d368ed3b60008299253e6e459dcea83b23852217f604fbd4 lib/Math/PlanePath/KochelCurve.pm SHA256 d3be21a6a4866ff97fc67f2c82cd0a044789d888319224eaed31b3bc4122c1a0 lib/Math/PlanePath/LTiling.pm SHA256 8c826ac246f55abd1cdca6f2fc4eb00ac1e0a17c915d867e5c510f210b7b9698 lib/Math/PlanePath/MPeaks.pm SHA256 f5c6728a82e1a8c941ffbcd5b316ae036f5ea3f0f493f61857ce14e366b678e1 lib/Math/PlanePath/MultipleRings.pm SHA256 5363bf7cdc212e463cf5718b7c23033f8fad3868eeb94bce8eabedcde551505e lib/Math/PlanePath/OctagramSpiral.pm SHA256 cfe358fb51ca1b1b6f0a11c470be0dc127710330c2bf0fbab3df90033d28b2a8 lib/Math/PlanePath/PeanoCurve.pm SHA256 7fc1f0be32624a15fd6bd740a300153b30fd40f195ca165d6ab086e1858bc660 lib/Math/PlanePath/PeanoDiagonals.pm SHA256 fc8b66e5fb8fc44bad1411b9e02158ab66fefe7abca24fafabdf652fd8b0db17 lib/Math/PlanePath/PentSpiral.pm SHA256 604e6d067dafc5712e9c0f02ee7cbffd460f70498a99f3af1608cdde9f4d28a8 lib/Math/PlanePath/PentSpiralSkewed.pm SHA256 8611c78dea9215bf96407328b6f3b56b34209ed3ab69c731851e253d4f3fbe1c lib/Math/PlanePath/PixelRings.pm SHA256 2fd00a813a0846c546237e70b1be9ef580bb0c5edfa97f242aecb2bcfb08805e lib/Math/PlanePath/PowerArray.pm SHA256 fd98a12302a6a58347143f26a6f76c425b7227b909189e557591df1b37ea729d lib/Math/PlanePath/PyramidRows.pm SHA256 87af8b11ef952ad39481a309747825d82fe31841bf07b4b7caafc5fef24dbc96 lib/Math/PlanePath/PyramidSides.pm SHA256 f235469e7227bbea11d35ced7df776a2ec7b24222bad6c13210deada5b33a44d lib/Math/PlanePath/PyramidSpiral.pm SHA256 b9a79a1d8bf573606a1a24b6efca25488cbbdc39413b7af4ad5fd4747bc0bd79 lib/Math/PlanePath/PythagoreanTree.pm SHA256 ee1597610fe870aa47280321a8a2df7e3a914d9ef1bdccd96660880be1203268 lib/Math/PlanePath/QuadricCurve.pm SHA256 39b595b23ab8155d6519f7d65b0cf9f6658199e5bb9eb59a044a46fcf4622be8 lib/Math/PlanePath/QuadricIslands.pm SHA256 473db6d81d8ca1f9d3a41aae821c5789f72577e2805351fd0cfa518896eac216 lib/Math/PlanePath/QuintetCentres.pm SHA256 71ce7d21a03587f9600742eea6c0248be9ada27988d9d37fd65dbef08fd4b3d3 lib/Math/PlanePath/QuintetCurve.pm SHA256 afd0a931167ba9230a055caf2975de3652db4654dc122caa87e315ca33b7524e lib/Math/PlanePath/QuintetReplicate.pm SHA256 2923beab1fa112508c24916deaddca9f64afb83872827a32b2d59667d9759822 lib/Math/PlanePath/R5DragonCurve.pm SHA256 46505991273629cee54bcb124e45ec1e02115813104248d71762ff0b9f29f988 lib/Math/PlanePath/R5DragonMidpoint.pm SHA256 755e4892c213d8575a17b35543b74ed0d4bc429e3c29ade99c0f900f06eac315 lib/Math/PlanePath/RationalsTree.pm SHA256 acd7e7480e21c201d4288238d73a0373569a6aaa10ce8e89999310d1d21c7819 lib/Math/PlanePath/Rows.pm SHA256 39dc8448561d043018cde82b830f9c065eb3afb8343153a89174d0a95cb9f800 lib/Math/PlanePath/SacksSpiral.pm SHA256 34644faf4d2a6ac848d3fd4b7af4bf30f744df86d8146ab833fff2fc2bb8bb0b lib/Math/PlanePath/SierpinskiArrowhead.pm SHA256 5230c97c66c7f4ee5b9938f4fdcfb328938be3be623113c53be46b421e5733db lib/Math/PlanePath/SierpinskiArrowheadCentres.pm SHA256 78c99b0bfbf6058dbc55d12529e86bab12d277b81297ffa9beb2db75945d03ab lib/Math/PlanePath/SierpinskiCurve.pm SHA256 89bbf017828d6c0f58641202b5f7f3e4a5627f000f18ee3215d97be56548b0c7 lib/Math/PlanePath/SierpinskiCurveStair.pm SHA256 7379e43ba986d0cf78b8a3ca3b020c7f0ceacecbc406ce7174c865993f44cd55 lib/Math/PlanePath/SierpinskiTriangle.pm SHA256 7c4ee50b3e4d335264e5ebb274aca06595161f2457dddba9d170893f67e1f4d4 lib/Math/PlanePath/SquareArms.pm SHA256 3dcba6f4fcbc84c75572d23d19cd570dd5ee9875545f6b6d399ef07b213b2497 lib/Math/PlanePath/SquareReplicate.pm SHA256 9577f315dcd0137b1ddb5c573257123d8ad6eb2303a1442dd94933b15b68a2e8 lib/Math/PlanePath/SquareSpiral.pm SHA256 0323a46b82f030eb96bf25d4773f983c44779c9f8a1fb4865c15a8eee2be4215 lib/Math/PlanePath/Staircase.pm SHA256 e3cf6ebf8c82f9366ddf763c320568c80e19764f3fba18303795f4d1022b9f9c lib/Math/PlanePath/StaircaseAlternating.pm SHA256 c3277574a03cb95f4f95f10fc2a406aa9c88ce6f9c8c7c50631ad58b74dd4eae lib/Math/PlanePath/TerdragonCurve.pm SHA256 6b33a117bb673a542b68d587afb927eee76dbe965e07bc33609be76d0a90aa8f lib/Math/PlanePath/TerdragonMidpoint.pm SHA256 949cbbc567d59f12e40a5adf6c717534106ba5633f8a7023e31dadd110d3ad9d lib/Math/PlanePath/TerdragonRounded.pm SHA256 ff9b9ae4f720df6b32f44735212c975c9b081b00f3f3eb89f16296dfbd676f1a lib/Math/PlanePath/TheodorusSpiral.pm SHA256 3a3aebaeebdad2278dced7976f38289d28ed802fd4819c1e00c8c7db680ed47d lib/Math/PlanePath/TriangleSpiral.pm SHA256 811206e5001c16667eaeb3dc54594ac355d73d7e7a8e82ccb6cc57fdaf7d63b9 lib/Math/PlanePath/TriangleSpiralSkewed.pm SHA256 3f566652bd267995ce9af782ede6daf9284f43b630c45d2aebdfe22b35ea330a lib/Math/PlanePath/TriangularHypot.pm SHA256 fb87b8fdf037b70fa7ce9bf903fa8112da5f34fcd54f15664d92585190b6c338 lib/Math/PlanePath/UlamWarburton.pm SHA256 3d14f2e266e8e4d7a7643f6984d95e73225ca678cf3534464e9a6f55953812c8 lib/Math/PlanePath/UlamWarburtonQuarter.pm SHA256 e57be5fa083e686a8b4327fe52ff2a441abedef209c8a0c4569ea1936a8ae8c0 lib/Math/PlanePath/VogelFloret.pm SHA256 14f8d3f692baaa4c7039c7d4d10d5e8404b09154778b0af6a5e0474519ba3df6 lib/Math/PlanePath/WunderlichMeander.pm SHA256 8c5b4cc54d4861d9fffcab84104968595d480fdb6d7b939bfdf147505de68b82 lib/Math/PlanePath/WunderlichSerpentine.pm SHA256 5cc1d9ef149ba1eb61cd969a44097b2f313771cb331a455b4e86d4109396fed6 lib/Math/PlanePath/WythoffArray.pm SHA256 1151b47b7b5dd9bd4ac30f8f126701aef2d80ab7a76dd1358afa9fda561d6ee7 lib/Math/PlanePath/WythoffPreliminaryTriangle.pm SHA256 9825b7ead9931f34c814809c9cdef61387f93deffd9dcd3ca57c2b8043c8e94a lib/Math/PlanePath/ZOrderCurve.pm SHA256 ed0c8ca9296315d4a05a86f142987f2062b511566fb1d2e565068b861a800924 t/AlternatePaper.t SHA256 9c2ce371f75a1c7769f2267a1a41105c7c5ed9acb127704ec737dcf4e46fec5a t/AlternatePaperMidpoint.t SHA256 b51da42848cf30c51ea71be70dc89e7f023a9a21d164a2b0eb1e13f4ce88a1db t/AlternateTerdragon.t SHA256 8674cfae056296288f81e8f44754f80a28a03cff1a0f11fcd33f1b78033c3536 t/AnvilSpiral.t SHA256 d71bbf653b3f17feca9efca29d1ecf64dcc9de63c71088e2ce421bcdeb010c7c t/ArchimedeanChords.t SHA256 80c85814a88bd8c8b42902af68cfe72936c068b7c4b576839282344f568cdba6 t/AztecDiamondRings.t SHA256 d3cafa143d1b915b27dbca2bebf37c6617faa65463b380b0892a9cd513834714 t/Base-Digits.t SHA256 5b44ec12404492577f5e95e97c8f3fbc33fa45dd06a4640333bcdab7773d2ce2 t/Base-Generic.t SHA256 a0a2422419f33c73c7802a515d84017d5138355d39abc344d253c6aa2852df2a t/BetaOmeta.t SHA256 82712a59fe81588fa60501007e532674d325512a1514d6ced94234fd95346c9c t/CCurve.t SHA256 e949bb5fd032dde6606c574e06c4b8c0dc303eb9b165227b394e5187d3f19292 t/CellularRule.t SHA256 a8dd7778e7ed75333fc809110fa1f4e66ef0e2df19828796baeee3cad4e4da2e t/CellularRule190.t SHA256 e44f251d70cfa1c6f9e440e9f0cc594432d35300fecffe7e9c79c7035471d724 t/CfracDigits.t SHA256 4b2851faf5636d794dc580ddac19547f895575b05d15e6d7d3ce3f6c4599a683 t/ChanTree.t SHA256 50f429c69082872cc201994df53db4634007fd97965fceb01323cfb5930949da t/CincoCurve.t SHA256 93387e5424a0777bc330299f5d83d80135b972ae69490786f11c2cfd72229285 t/Columns-load.t SHA256 00ab22fae0fe48d5b99cc921010d919f5fe3739a08d87a1dbcb48913ffb29c2e t/Columns.t SHA256 94740a19ebd3083a8dcf55d09e99749c4f21fc10e454fd0db80c79b883658d86 t/ComplexMinus.t SHA256 ce27085e5395fd935598d5ab10035e9869333de5bbd9ae6b621459e0880fbe41 t/ComplexPlus.t SHA256 36fcb1e70801bc1897b0a711ca98b6caea456ca3340d7ed8db6acad5ee82b7e4 t/CoprimeColumns.t SHA256 6e14954c18d571d2112ba2c97e7bd3b3c7da96a8df9f4870e8a0c34394bc9193 t/Corner.t SHA256 38af92ae3338e069dcb8cf5f8112d967a8c5bce4d97a09ee09eee930e30fc448 t/CornerAlternating.t SHA256 fcf1d65b0e69eaac5970f10f90e8f7855dec87f509d22dbd9ba78e93ab975e23 t/CornerReplicate.t SHA256 21da6e7f147319b29c456ba6fe05bdf4ae5ea36e37d37e9c96c691aa57efdbdc t/CubicBase.t SHA256 dc03253302cd0e362be4ab0d4abdb0520a2391e93602ed02c686378204cc6ea6 t/DekkingCurve.t SHA256 9424fee8a2256c552650fb090d881f90824641ce208c95d432276691c10d2f87 t/DiagonalRationals.t SHA256 f549c81b86b38d3add26165ebbe704df601a59d2192c6550f54160347e7efe7c t/Diagonals.t SHA256 71e1edbcf5fe69f1268f853fec6ff0ca60d59c44f6e4410b53bab8bdb161a640 t/DiamondArms.t SHA256 9c1039d413935f889f4b411d333b530bc23599f935f5fbb6e568d120731d15a1 t/DiamondSpiral.t SHA256 386d7e881e4a4ac12b25553c146b095acdfde9680dd397641e97882f80cf6d84 t/DigitGroups.t SHA256 d7a95850aa1cb62d87d5a3a64131295d83a46ebe72ffc9d117e04ec9e01cdd43 t/DivisibleColumns.t SHA256 01264917fe0bea3eb9aeaba0ab4b17eb78b9ff1ae3bfe968552ca478a23c0203 t/DragonCurve.t SHA256 ea88a52fa797657234a4d87e2d416980212247e1f8e7766d6464d368db6a6035 t/DragonMidpoint.t SHA256 eca99e7d5d956d790895299418c4b2e2b7475e1ef94e838c5fff1f0af4db83e0 t/DragonRounded.t SHA256 e9998b34e04bc4574ec2f580c16a1dbea6c302df12d7f11ca436ceca04c0d501 t/FactorRationals.t SHA256 d60e20aa5584493d69304c9322b5984490ad87c12f0a914831f8854d8d4a2f81 t/FibonacciWordFractal.t SHA256 1da526b75ad51c2af67a5631ccec88236e78002f236817edaebdef3e78d5a098 t/File.t SHA256 cef1f054b06df79dbc7f183b8c424ef86269bf8ff16c84349ffb3ee8345be4ef t/FilledRings.t SHA256 cb493d12724b6fe26b305680d3ace73635e402b351132d6771202944bc229ba4 t/Flowsnake.t SHA256 fcb49ba0c94d153e0e0299bb4131522617d813cd74024b0c6a4fe9464baf5a1e t/FlowsnakeCentres.t SHA256 9a0f2e6cdce369123ab17af1e42650477aa1133a7674aee06e7332b3073c225f t/GcdRationals.t SHA256 2248a2fa7b00cbe163a0d8d9bc6e4f41bf61e6ebfea79eba89379745b0c45395 t/GosperIslands.t SHA256 019b192038987133ed1e51af4d89461a60fd9557846a1cc321529f4264fa5693 t/GosperReplicate.t SHA256 d8016e011d690a515ff4c60eeb61f9eea29b1bb4cbcd7595c3b92c2aafabdc38 t/GosperSide.t SHA256 ca785db4bb03b524ca2b584c476eee01f57f95ca3b0284124db9ad973116c565 t/GrayCode.t SHA256 cfeb07f178fa5b32fed38b39191f1d22b865d8590b46b90f3ca3a77e9d37089d t/GreekKeySpiral.t SHA256 3428818f1a55a2cb333b2abf5504920d5fffb28050eae03e11bb510c700b58b0 t/HIndexing.t SHA256 00aaf4f3e6e895aef23c74cf7c78b6b337feb508b8bcefec16f0b59913f1176f t/HexArms.t SHA256 ac24667d5a4e195edac14e33ffc0fff8971b678e5d0e167c119936e747d6944f t/HexSpiral-load.t SHA256 916e55435c954bb435b1888816bd434391e8a97da33b1ef3b85c3e66cb4392f8 t/HexSpiral.t SHA256 8ab6f6c627cbd1076c11574441eadee9ce9807d5333281d40b3aa8d3642434ef t/HexSpiralSkewed-unskew.t SHA256 4a8a24a6c4f11f844e608cc81f6a5b9d7b98b6a52f7e635161965f22654a2475 t/HexSpiralSkewed.t SHA256 daac8b060511cd00abe6502d55d453311c9aa264072f55ac4861f4bc0da3b978 t/HilbertCurve-load.t SHA256 5a5746ed38b7ed5872383135b17a03be5e7c981522397397316bcd419b817e8d t/HilbertCurve.t SHA256 f5162ca46206e63c6711fb890750053d3e0971fff0139b9acb68ca606c24c812 t/HilbertSpiral.t SHA256 dedc6f1a38e1b0634434ed6ac5d132e9b3ded72633d81a5a8de106356872f6e6 t/Hypot.t SHA256 2b6309947fb6ebf78b3e243ad6b666b41af5f36d2033c14a6ca8c56e205f1b8b t/HypotOctant.t SHA256 55c610d45058528497e826d1503b3492a4f98170896f17464c7349e079a50434 t/ImaginaryBase.t SHA256 08ad80f28b6c98077906521275b0976f7054633fe21f39c4c9fe3dfde956e557 t/ImaginaryHalf.t SHA256 c0d86221db9edb5e454309a77f5563ff1ae639a9c1d6525a2aec752fe2f08047 t/KnightSpiral.t SHA256 4c974e0451962111226073f2fdb556bc99805a319775f2f27fff731eb3968bce t/KochCurve.t SHA256 c6d1d4a8af211d4061a922ae9d96f481accf7a787a583a5cff6b91773d695995 t/KochPeaks.t SHA256 563822fd93c4d3666c118a3a9fcae5c3d6096d6b68f766b714b9e6850f85324b t/KochSnowflakes.t SHA256 bb95cc8765537255cdb35cb2f89a60c2dbf32b6b3dba20265a75556315040a9d t/KochSquareflakes.t SHA256 da516ec1f39dc54acae6863cc20aa1caa8fbea3817d988f787c025fde3347873 t/KochelCurve.t SHA256 9a395e538b31eea00923b3702a82db463daba1d979b462283bdb4e48b7d90d15 t/LTiling.t SHA256 808fac2e15a8e72e8a220e8bd9e930a8276e28e9a322b9e0e40abecba5f820d3 t/MultipleRings.t SHA256 91cd2bba3e246abf05c342ed549ed6d902e8a77a43b6c9c4e092e4c918732ac0 t/MyTestHelpers.pm SHA256 43483d5e0420bc56031d7af107e9d056f498bb2ac7b13ac0f4ac4bbd67d3a6fb t/NumSeq-PlanePathCoord.t SHA256 b2ba123cb3e9c55ffa6652d58036cfa0e6c2729dec9fd8bb5c6aaa71d0dce963 t/NumSeq-PlanePathDelta.t SHA256 f9c746b057607a908b02f322407674d4237e0b46497ea80602c2de13291cb009 t/NumSeq-PlanePathN.t SHA256 48b91611fd9832815d2d0e9142ceda648f27217b1e14d82d4353ecd7aa7e65f1 t/NumSeq-PlanePathTurn.t SHA256 7f77d06b82992814d709094cb6179804adcb8536684e45a5e2bf4b6902b6ae87 t/PeanoCurve-load.t SHA256 61f74a1fee9f5719ef768302fcf85c07f2a6fc72583ba54d7fbf2325cf52f751 t/PeanoCurve.t SHA256 3ea0fec4a503b779281dc1d24ac156e7a7ad7d6e995c5d7ef955d5302a18d69b t/PeanoDiagonals.t SHA256 8feb0d3f3ebb6fc09b486cd8aa7aba5bb5993d634e3a5570c38d9e2e9117fd86 t/PixelRings.t SHA256 ae1a5ee3dd0684c508e76db200679d23ca556ecf810ff884923f0ba9000978d5 t/PlanePath.t SHA256 abd2eba3f06b3daa8bee76288b9524c38431f58ca99fc5bcabf11fda7499f8fa t/PyramidRows.t SHA256 622fd96abcae8b67c9bce3419a78b3a1be2544c138487d90d92def8a03f8bdf0 t/PythagoreanTree.t SHA256 0b70e4edab8297067d034cf37b98157dc9ac8e7f67e8dd13876220bd8e31ea6f t/QuadricCurve.t SHA256 d9c01ad11bc5c91c78f014560905712d08e4e58d0d5d5d73ff2812c4b610d2ec t/QuadricIslands.t SHA256 c121c3e5e642753308a088f103618c976ed0233f3a471bdd8e08bc47b03d1e5d t/QuintetCentres.t SHA256 2b0ec93dc978d6b59ac9629d460d021bef80fe8bb3861053684a6f972381019a t/QuintetCurve.t SHA256 1b25cf9996239cfecc9b69d1a2953eed657d06af671b087c817cc2ac957d8d5f t/QuintetReplicate.t SHA256 2086b8bef6415b45a9ddd6f2bb48e576a18af14f31652e74144836d126bf81b5 t/RationalsTree.t SHA256 22d1b5c44700728ef899550fc2dbd345148f75da0b10e7a48f02bd66544e0288 t/Rows-load.t SHA256 4f3a3a63fd762e5ac3786115870b079d3f209b3afd06db23a964c463ff6e6ccc t/Rows.t SHA256 77d59c43c69b21da2a798e619dc2e5a97c2410a060f153a50b0937e81f54c35c t/SacksSpiral.t SHA256 87b46a7c498a521a246646c9f79d58e9da22641e8865ac767dd08be569b870d1 t/SierpinskiArrowhead.t SHA256 bc5dd2fc2961d339ddfc17322638aa4ee8df7ac15d4e62df4fcd297159416844 t/SierpinskiArrowheadCentres.t SHA256 c403d52937598558f605dd4ca46628316b7bec946a491dbd2ebe2d5d43583240 t/SierpinskiCurve.t SHA256 21008a02af8e0e0ef421fae4a7f7f7e961578c56a1e18fad902bfa1035b59dc6 t/SierpinskiCurveStair.t SHA256 95a804ceb1d08300fe4ff0be052285b40e16a4f4e43421f2de3b43a070a844a8 t/SierpinskiTriangle.t SHA256 ffb24052e1f31d30e086e2550e86132594441f2e92ea56f63af7c3cb99ba1835 t/SquareReplicate.t SHA256 6305491f21542d36ffb552348430fda20f46c4d9d697232b191cf25b2a2b9391 t/SquareSpiral.t SHA256 7f1f684b7b1e46ed64b691e6f7d3e1f0569e3c91cc0ad4fcc409626a011707ce t/Staircase.t SHA256 e34c9a3a916d05536273cad1cdb85468f0709afd63b80199bbd8bc6fd562fe5f t/StaircaseAlternating.t SHA256 90792e240112b77cabdce969e5f8add03e965104389d54eef61eba26ff009fc0 t/TerdragonCurve.t SHA256 79fd140af906f6bb93d1bb323532992cb297c63a5089636cc33e99d70048b4b9 t/TerdragonMidpoint.t SHA256 36017493ee3e8a5dba932b8d46ffeba4285da758166aae0ed5568f8a448f6447 t/TheodorusSpiral.t SHA256 ffabeff9a9e3d78d7be5e56da60aaf92c3f722d56f253a39e55747b2a7312cc1 t/TriangularHypot.t SHA256 bec0643835d03e1949259d334eb742593bd1ff16407fd135722d50075bbbe97a t/TrianguleSpiral.t SHA256 ed8711c9d89be8e510d3593fe437fa71d340aae2531fa078156707f9903b773f t/UlamWarburton.t SHA256 b46f1c115ad9c490ec7c72cb9b83358e640ea271dc7344cb5318cb614ca58fc1 t/UlamWarburtonQuarter.t SHA256 a25acd035c59b94f0216aa27782e80a76404f682f62e0a9088c78b2d33348766 t/VogelFloret.t SHA256 d9a197f5651e26ddec7d15d648b8ffa14bf361636e046a2d7aaf82c81512e96a t/WythoffArray.t SHA256 c521cec5761d0c4dc70f13677e0c41953fef32623d8e750856a54d0b69db006e t/ZOrderCurve.t SHA256 dc72cde61fba7b398f30a8369e4aab08448624a6ba5162f372df42d1ee25aa06 t/bigfloat.t SHA256 0c986a4a44d3c4e7a24462db7f6783287eb0e8c5201fe4d2defa0b8f9eb9eb57 t/bigint-lite.t SHA256 9079bd5fb016de84f23c11a0225229eb10accb291d019c1ae7550511d04b54f6 t/bigint.t SHA256 661e33342a4d3623d8e61503d2c65344702fcd3c790e65cbcc5cfe3805f73320 t/bigint_common.pm SHA256 ba98d7f30e2ff1c32b9cbd67da988df096eccbd892b22515db5279c18aba6225 t/number-fraction.t SHA256 95c346421372342407ef43b7c93cf597353e59d38c4a75ad838be446534c50b6 tools/alternate-paper-dxdy.pl SHA256 764d20ab4f0c4ace38b7ad967b2216275eea7843ba5194e15c4c49ccfa05bef9 tools/ar2w2-curve-table.pl SHA256 65091cedb17f2ff86b697face44033dc5a18f82f7b81f068b4f55b86e3eb72cc tools/beta-omega-table.pl SHA256 9d44fa5b6f8ad039de6920264c37e4605ba7a8f829a6adcb851dea4664ffc4d6 tools/cellular-rule-limits.pl SHA256 a04e2f6f7cb9a69a59eee9dec879f9a228bf82eefc80763dcd61055615df5a02 tools/cinco-curve-table.pl SHA256 705153247c077e89f3d968b65e3627d6082d68aa399018337f2b922a3cebdf35 tools/corner-replicate-table.pl SHA256 7311c2a37d37323e4f2eb66c9550cc920fad91d25d970a65f523600ccee3f769 tools/dekking-centres-table.pl SHA256 40d7ed0824bcf6161a289011bc6e6dc5b57d0329e27f4fb1559b892cfec02b8c tools/dekking-curve-table.pl SHA256 589a120c5df7d4202acd2367465c6d4bd8114fa65daa413c3c1be991caafeb2e tools/dragon-curve-dxdy.pl SHA256 cb0b7aaccaf47cc3d61359bc523efcd2fc4032b3cac4c27879176765549a2c59 tools/dragon-curve-table.pl SHA256 6d0095b39166d27fd11ea1a8eecf6a0e02712f434e5fb4e5b14980b76c2d7425 tools/flowsnake-centres-table.pl SHA256 7a9a4b45f60efd4fd3db697d4053b2fe041529904391e3a3ebd9aa2b1ddf5776 tools/flowsnake-table.pl SHA256 734e57dc08a82c05422af4e1c3c6caf1e1f7dcabd8b3fd4cac9474a113cc8af3 tools/gallery.pl SHA256 db89fb304697dc0c778c6bfe36404826a90cf3b634a9de2604ca584bbc451dd4 tools/hilbert-curve-table.pl SHA256 fb79fe8180739d25aa7dccb10fa086475ddde371b37e829f7ae207263b3133f9 tools/hilbert-spiral-table.pl SHA256 7f0caffc48139fad70a88c53263608c4de8b91d07a8b75699c453d48b22023ee tools/kochel-curve-table.pl SHA256 4f3d813d0535327cc036b9580161e124a9770546cee7e345174012641a3c5bf7 tools/man-page-listing.pl SHA256 79999d7c6277035c4d0651480e3b0756efebc743791fc6a71351a170bf3bf723 tools/moore-spiral-table.pl SHA256 bbf3f52f77ad835605d91ac14129697674265733af0078e080488dfb45be610c tools/peano-diagonal-samples.pl SHA256 82a32f9ac8f62666f363636064ebd13a11f0016f6381b1d7a9ff4e02385513b5 tools/pythagorean-tree.pl SHA256 7c84b2f8a7c4d0fc2f81a8fcc42e030e72aafd92c6d4f76ca8b5980d1c970db0 tools/r5dragon-midpoint-offset.pl SHA256 2945cbfed609c81221aba9ef3f6fb061b27b21554bdbd7637474c226f22ac16b tools/terdragon-midpoint-offset.pl SHA256 3a111d5d3fc0522c4ab76bda11756894e69f05e64714f221938f627f99dbc6ec tools/wunderlich-meander-table.pl SHA256 aa1308db52b5a4f7957983e4d4a60418f3c9a06623a3c407ead696332079e486 tools/wythoff-array-zeck.pl SHA256 ef75312e02ddcfed7095de7eecebc6b7b863d56acd9b64142737ab7a5edb57e3 xt/0-META-read.t SHA256 f03d4741c4e6dd385c7bafa06118082bad4809a64e28a094635324ef8ab4f3e5 xt/0-Test-ConsistentVersion.t SHA256 48b441e0c335e93946d913897e342662387788833229c5ba5fac57f0ff3d567c xt/0-Test-Pod.t SHA256 2e1e1d896a226aeb190cdcfbe83969f634c1be3e7344302e023915e3f7150732 xt/0-Test-Synopsis.t SHA256 d33b48c1986680cd934565250bd9e3879674dfe6aad69b1717ed76354a29ff44 xt/0-Test-YAML-Meta.t SHA256 1ae41bbd04e6aba264df79250d525e8b2b2e6b311930ef191c5e432936706cdc xt/0-examples-xrefs.t SHA256 75a73148514fad2715873d1e02a6fa8e3b9cc43f7aff97aaffac9721c086a319 xt/0-file-is-part-of.t SHA256 7d9eacc605d8cb575b2869790e4b90d71dea6a97547c725825a49e1db036dee4 xt/0-no-debug-left-on.t SHA256 819125b6b57291df19750b0c8b9f2e0333230df43b88f93fcd6f10792c9d71e2 xt/AlternatePaper-hog.t SHA256 8838dc56e4cd5d52cfd0780c1009d0d635872c20ac2b7ab10583b13e9d41e684 xt/AlternateTerdragon-hog.t SHA256 6c0489852208ab70cdf9a5535325616133159b57c89cc790bbbc0dad9c27665f xt/CCurve-hog.t SHA256 fe39f11e31fd74f82f234e2304d3fd752492bb9b1b714129774d0a37a37f42f8 xt/ChanTree-slow.t SHA256 67896b115e0f5fdaec47fdcdb7bb8fa48d82f1cf35c40189537e307fd92f9261 xt/DragonCurve-hog.t SHA256 df2e538b283c6e0a19a4e272cf0498c8c431e22ba8b0c25e4443eebf0965894a xt/DragonCurve-more.t SHA256 c643d8b13b88445b5f5dbd0422fdd3cf1b11c5f94d07c4dc713974370ccba090 xt/GrayCode-oseq.t SHA256 5894fba7ae59186764bb3524965108df34a72e797633e51041ab67178edc1f3d xt/HIndexing-more.t SHA256 1794de1eb2039febd464706bb070485818f64e766252631ac5a87fcb438c25a3 xt/KochCurve-more.t SHA256 5494b6a23d0b7e92f9c5c1416d0b86c7431e358dba28529c386a773197d8618a xt/MyOEIS.pm SHA256 ad738c0c11953825d673c7baca16be1e94ceda193ab7ec57102d15435670918d xt/PeanoDiagonals-seq.t SHA256 d2d918ca89589005556bc263685ffca308693f3387dfd88a2971a664fb402da6 xt/PixelRings-image.t SHA256 078ca6c87fafd1ece392d92c38c409d035e87af2e274e00d7c355e82bd04eca8 xt/PlanePath-subclasses.t SHA256 0da163096a999b88b394c40dd06d397b7fd1fcaeaf3cd08d3e665f1c55893309 xt/R5DragonCurve-hog.t SHA256 a30b9a9a7e00e502a6325d3fcfb21fb8ca115038eab95f8f856bee0d632af932 xt/TerdragonCurve-hog.t SHA256 2ba8002f0e5525c183bcf656914c1a90cb4b110443c8d71e7b17525841edd1a0 xt/bigrat.t SHA256 93b2b934328ff724dbac541ef358957d032fb2d83f38846b3048b0eefa1bab67 xt/oeis-duplicate.t SHA256 ae4dbd708d2bafe3c2a76c964863d3239db5d0708a4dd944ca4c39ea56356253 xt/oeis-xrefs.t SHA256 d35ea0b0c0e66e9c3e7ed361acb64ef7ee42dcff5e2b33ac656316373f4e6473 xt/oeis/AlternatePaper-oeis.t SHA256 62c1d2c2f27a3f4ad63b4bd5eb1de7ba5e1fd5b01e6d7172c90f202b7c2347f4 xt/oeis/AlternatePaperMidpoint-oeis.t SHA256 c1efd3560fd95697ee07f156532099cf0f15795406f0a5815edd49e263e2a385 xt/oeis/AlternateTerdragon-oeis.t SHA256 140de7974ceb8831eec9bfa2f0d1fc7f15145b5e9978a6a8b3bacdc7af534976 xt/oeis/AnvilSpiral-oeis.t SHA256 19ff027be0cb1c8fd99a6788f6fa7ac15242954dde8f465532d7c0a9ab4f078b xt/oeis/CCurve-oeis.t SHA256 15a639034b7a6a69bcb7af86071b10a4efb0acc5f87b7cb88a19ba725b4c2881 xt/oeis/CellularRule-oeis.t SHA256 5cb66c6f6027ab888b18d3968e0dbdf6ba8e33b3bc37881eea446c72477ffee8 xt/oeis/CellularRule190-oeis.t SHA256 8514953ce3bf6cdd86c78b737d39755f24a80bd5f92dc9e740b293b139c42b77 xt/oeis/CellularRule54-oeis.t SHA256 e9382635f2e171d972839f15badf33229072450cbe41939b533ac87fef6bac7d xt/oeis/CfracDigits-oeis.t SHA256 2e874cb6e78bf8498ccc5d0c99eb9ccb17ee7c50bafcd645d323e5a9b133376d xt/oeis/ComplexMinus-oeis.t SHA256 a75ee08743e4d2a099a891b5d6fa3f3c6fe9fef8c1615bc30d0122693defb38f xt/oeis/ComplexPlus-oeis.t SHA256 c2ea498ec189d52c785f61eaf70681fc3a9728a8ba83bda6518b33d2d0fc24f1 xt/oeis/CoprimeColumns-oeis.t SHA256 fd5b1fc5073d69efd0d1cadd48a321ace83596d652f38491f0d4205a25e30ea9 xt/oeis/Corner-oeis.t SHA256 b2c22a5697993bb849f291d1418e4f6bcc117907254433e49376bde0cc2aedab xt/oeis/CornerAlternating-oeis.t SHA256 c563e876b188ae06f3d9a3636f21616c00948f80310ef37f8fd8c368038cf282 xt/oeis/CornerReplicate-oeis.t SHA256 946824e899b7bbf91d1b7c1ce84b1560c093ad43193c4e20154265394414dc13 xt/oeis/DiagonalRationals-oeis.t SHA256 d4efa29734934c34ffb30fc84b12fc2c704281013bba0d102d0d1e6c06e96928 xt/oeis/Diagonals-oeis.t SHA256 ddebefa7d49b03114b320cd84a5a0cdf130e62c3a7bb4ea91f00264dfac416e4 xt/oeis/DiagonalsAlternating-oeis.t SHA256 d09354312359f5244f3f86111ff726b69a8ddf668191f27920d1d2d905d60e59 xt/oeis/DiagonalsOctant-oeis.t SHA256 9e36a86aaada93f62a45184ecf89de48dddb3eaced562da91eaafd699713049c xt/oeis/DiamondSpiral-oeis.t SHA256 7af1a093e8b0bb0d21dc7b2c155f305abe81b97973321fa18c5306dcf1a24d0a xt/oeis/DigitGroups-oeis.t SHA256 98ea9435f86ba5ad97b5e6e2e4ba8439c72246dbf408003fa934bf912f405b26 xt/oeis/DivisibleColumns-oeis.t SHA256 d985a658d82e905585179f5896d8c337537646fa4c75f7525d233f22ef20210e xt/oeis/DragonCurve-oeis.t SHA256 423f1559c0012d7c3a05a6a3b9ec6f35627475e0557cbe5c1b90d4bb6880b5a5 xt/oeis/DragonMidpoint-oeis.t SHA256 c2851ce1f7d14f3cee3f408121139063e19974400b17a8bf2c1636ed407c91e6 xt/oeis/FactorRationals-oeis.t SHA256 c302a3f8aec7e3ec567376d443fb97342f2f477ec28c19647590085ae538a46f xt/oeis/FibonacciWordFractal-oeis.t SHA256 a677172f14e5ec92223c34fca1ac1a6d3b8951d9fa71d29b060d9fbee0c7b8b4 xt/oeis/FilledRings-oeis.t SHA256 ce482766a4c983bc513fa6954a48de9b9e41c7ea6a29d66c71cd269370f849f6 xt/oeis/Flowsnake-oeis.t SHA256 c7be8572087d41f35b2fadea93bbabc9bd09edd1bb66f1769cd126ea8f4a1684 xt/oeis/FractionsTree-oeis.t SHA256 0bcfc5874a6264a8d8ab6a84592d77aebea6058fdd2c35fe14ec9b6ce4bb0b3a xt/oeis/GcdRationals-oeis.t SHA256 e287d7399867bedefafebc1552707ed48609d2de4bb590d6b162c43e03655222 xt/oeis/GosperSide-oeis.t SHA256 eb9d42b263ea3dcb9435e429f912803373d3e7c593da4c352a481d5fde0086f1 xt/oeis/GrayCode-oeis.t SHA256 730c7d900bfd66914710b2155d8f31deed6ba3dd5f1e409455f419334928ba30 xt/oeis/HIndexing-oeis.t SHA256 7221e0bd5744b7603fe17eebfcbcc8fd588bbefedcb03f92aa62911d94c61fdc xt/oeis/HeptSpiralSkewed-oeis.t SHA256 5788ca7213815f75a96cbfee8b753a9529531d30a8ab8e59c3a7641c2918d9d0 xt/oeis/HexSpiral-oeis.t SHA256 46ef16f3294a7636f36f876f4b7a24c2d5252e27a80f962ed68f68b912869bdf xt/oeis/HilbertCurve-oeis.t SHA256 5c846f4f035eb87d50aeddec67d3af7840efaf2c614402fd3371ef8738c30014 xt/oeis/HilbertSides-oeis.t SHA256 5b8a044dfa0674ad77a13dda9128189829a15eab3f186bed6b42821c6716b32c xt/oeis/Hypot-oeis.t SHA256 91e41135ab6093e51fc02656af1b8c74aee5d3ef3feb6bbbfbb8c7bb91df4ab6 xt/oeis/HypotOctant-oeis.t SHA256 0325bd1e1093b381310740eef81c49b789bdbda67ac1eb0297f62805520c85bf xt/oeis/ImaginaryBase-oeis.t SHA256 246fb9f3d3f53fa2f511b5f62a807050a2a47a291822c5d1732abacf2a0a5dd3 xt/oeis/KnightSpiral-oeis.t SHA256 e3e88205cea594e37fd47e477ee16b441aada4d958be3196690bde5fd7fe4ed4 xt/oeis/KochCurve-oeis.t SHA256 66c874a1a7620a4dd5bc4513f7e9e9cfb2a8bf5d73be9c6594f80a7db9adec88 xt/oeis/KochSnowflakes-oeis.t SHA256 d92262ba65b2a8dca98fae66f4d70cb9d0a28a4f3c85488256b84cfba8006c70 xt/oeis/KochSquareflakes-oeis.t SHA256 12848ba75b34f05e370bf9e7eb49de30e88158b598b391921bc5e3933bfee73f xt/oeis/LTiling-oeis.t SHA256 698e21ac7bd2c2b7d237961289f0cc632d6827857bafadc2e8b7dc15bf0403c4 xt/oeis/MPeaks-oeis.t SHA256 34dd6739d2898c5bc00916acb24f1f84bfd0d02cb13f087a6d263e89e0a0a7b0 xt/oeis/MultipleRings-oeis.t SHA256 108a85aeab6276cab356bd2bb720dc69efcb7ed5a4fc32f43759bff5dab04ed6 xt/oeis/NumSeq-PlanePath-oeis.t SHA256 3d6778a4ccbd556c64212b196b867c8fd3d097fbbb9d164fe7d6637cbd1f50ec xt/oeis/OctagramSpiral-oeis.t SHA256 1f928def00271817b91759d281c04eeb82801cacaec1f50ec3bef6c6c8a626b5 xt/oeis/PeanoCurve-oeis.t SHA256 b0fd877fceef05fdb9ba4132305939cf13e9359e90c4448afad6931f20d56903 xt/oeis/PentSpiral-oeis.t SHA256 0a2ec0711d839d3dfbad03bffe1655e9bb2cf425243a97903ac423b2a30ec9b8 xt/oeis/PentSpiralSkewed-oeis.t SHA256 e2000a3e76470aec45ebf7d5598fa1ba375eb60204841c465ccac96998e54914 xt/oeis/PowerArray-oeis.t SHA256 5c7fa0fefc1c254d866bd91f7deabc08259f03acb156cd6dcef0d65d985ff9b8 xt/oeis/PyramidRows-oeis.t SHA256 c7c616d682cfd36f85882bebc7cce9facc2d28ae510075b08719d1089274ad0f xt/oeis/PyramidSides-oeis.t SHA256 c816ac254f83ec307983dfb94a323ba14ee39026924eb52f029b9e99a72f07c5 xt/oeis/PyramidSpiral-oeis.t SHA256 4908f2c3dee60301d3e5845167984cb4e5124d8b7cfe03b4767c42fcd7d38719 xt/oeis/PythagoreanTree-oeis.t SHA256 f65a5f571a61c4a45dcf18d162a3277cdf46a78bce71c9f8acf4d04e559cd762 xt/oeis/QuadricCurve-oeis.t SHA256 b31a4e0da1eb8975580b5c27a0c187c4dc6c9ee53cff7ba14a33361822ff26c1 xt/oeis/QuintetCentres-oeis.t SHA256 b955e2d05cdc64e4af48ede9714906a7afc4c4aa33c7864cd3a24810de83102e xt/oeis/QuintetReplicate-oeis.t SHA256 c73e72b73d49e866d90682d51daedb94739eefeb08157d193f0483a0b705c888 xt/oeis/R5DragonCurve-oeis.t SHA256 79befaf6fb2b22fe4fb36a162958e154cef2923e3539e9c6b007c98e8cc1c95f xt/oeis/RationalsTree-oeis.t SHA256 dfdcdd61f699fc0531b18ac530fc13158a2a42baa17e453f63bdabb700e06ada xt/oeis/SierpinskiArrowhead-oeis.t SHA256 0c9ac180393d9243fe5717b79e76a856d5f012b0ce7b672d6f6a4fd97fef2d16 xt/oeis/SierpinskiCurve-oeis.t SHA256 09b39ae1691f4a84b124ab41a63383ca0b265a8644c00c281b5cbd79ee7f20a9 xt/oeis/SierpinskiTriangle-oeis.t SHA256 ad1583ba634eab41b56a56e4db6a2efa001891fdd36c784a4ecbc2f7b4e65f98 xt/oeis/SquareSpiral-oeis.t SHA256 1ce00d6f1681f2168c81b0e04d003d435059dccb223ff06bf6f0abd0e9f7f9b9 xt/oeis/Staircase-oeis.t SHA256 61731483e7e2ad00c7a519ca7e65f3d9335ccbefb780488530bcc01e8e509373 xt/oeis/TerdragonCurve-oeis.t SHA256 aa11977fa92a8354c17e15aab000017cd1c4f1a6fe25487aa2fc522c6e868704 xt/oeis/TheodorusSpiral-oeis.t SHA256 f738931aa3008fdc12290ea211f5e51723b7ca9612998936c3dfe707ced9c06a xt/oeis/TriangleSpiral-oeis.t SHA256 ca6e46d45a86432618aa44bfba45f11dfda20ab20b15af7929ace7c10aad4880 xt/oeis/TriangleSpiralSkewed-oeis.t SHA256 8f59b93d0e8e201b7a1e2b38a4527dd2b4829f8722702f698187011e963f4496 xt/oeis/TriangularHypot-oeis.t SHA256 e7add6a0e58ad2c3501ba7e87425b23f04947086646a6ffd222d453689d9f206 xt/oeis/UlamWarburton-oeis.t SHA256 916e51c61ed63b592973d93347dbec03351a3e4510c8c5b2f94e7178e8dbc4cf xt/oeis/UlamWarburtonQuarter-oeis.t SHA256 c5d0515d6f866dec85a7fb42bf898999adf9fc5eee5d22f3b1f49707fe584958 xt/oeis/WunderlichSerpentine-oeis.t SHA256 91bff22b2c9c977901f3352e1f0104a92620012b10ef998392ae99eb803d8707 xt/oeis/WythoffArray-oeis.t SHA256 fe8833f01b3f3d5c78dddb652461c7aae8efe2e7304ad04e7902433102726a87 xt/oeis/WythoffPreliminaryTriangle-oeis.t SHA256 bc130ad15b50f3a9ea78a0f90b36edd45d4939569f685955ec22a975f0f2d7d2 xt/oeis/ZOrderCurve-oeis.t SHA256 90e8fc61367a3b46d1f08e99dc8c31ce0f5d322ab06d9b34ccaa66534f6f0f9e xt/pod-lists.t SHA256 95103f1be9a22edacbffa24276eb66c69932e092b67e21f2293118d350809867 xt/slow/AlternatePaper-slow.t SHA256 0dbde129e051b32c21cc8b78713dfda9a2ae2940f4548bd97edb40a89afd501e xt/slow/CCurve-slow.t SHA256 55e9a2f7aa8688ec0abf39e1afc96e78abe5620011df706cb58a75b0f7de29c7 xt/slow/CellularRule-slow.t SHA256 f726fb4ec4caee65b68d240fc241438a5b5ad8a27c2baf2dc5d730151c9495ff xt/slow/ComplexMinus-slow.t SHA256 cb2d7e58cfad03d03b39c5a20b39deab3af7930a6b501f3540cfff07066bd4a1 xt/slow/DragonCurve-slow.t SHA256 602187e7437c6afc9d0982a7910c7bad4cdaf76fc632e1a28242e84dbe5d6974 xt/slow/GcdRationals-slow.t SHA256 451781a737ea4e4d9a4f81eb5c01b902b0bbf943b5790c4ae1f57ba3a8ac67f9 xt/slow/HilbertCurve-slow.t SHA256 f98054d498f6e7753a55cbf9314c9064262b022cb6cf474f47cb702afbfd520b xt/slow/NumSeq-PlanePathCoord.t SHA256 ca96a9354a57845dadf115529217b232ee9fb66f3d6ebcf59ae10faf76560c42 xt/slow/R5DragonCurve-slow.t SHA256 7d4e81c68127263b72c37d7c3fcf433fe0eff75c4e8ec29cee19e5c6e0f4d9b4 xt/slow/TerdragonCurve-slow.t SHA256 330b4711b382ca5c9961eb0270f5bb50d1dc5c43108dc5aa609e1ade77f228fe xtools/my-check-copyright-years.sh SHA256 5d5bed5cd3332e9d386b2e6175f2e6fad1e87b33f263836327935bb44a1d999b xtools/my-check-spelling.sh SHA256 0a4726b4ae47a369753e2b3c1b88e84c67f8554d529935a13fa78f1e0c727462 xtools/my-deb.sh SHA256 02d7e3c4bd8846b27dbeeb736ce4386015b0cacc1917a03d12e9db15417acc62 xtools/my-diff-prev.sh SHA256 0c9535621e35a944f06353ea646eff232edacb9faeb43b0b60d2642c6ffb535b xtools/my-kwalitee.sh SHA256 5b5355dfea048d707ef66bbc92aab6ce1058251be78a85f7bab218e90ab79912 xtools/my-manifest.sh SHA256 54be906960c753ed9025b297cba51f4056da882df119de85c16fee0a3f18a79c xtools/my-pc.sh SHA256 94f3c3184b1b1078205b93c2329dc5b34cd02c06661d945d69f0f1fe50bec019 xtools/my-tags.sh SHA256 64aa17c531171303417500b4ddd5da8bcf91c77508a39e78c4e14f8ca0aee4e0 xtools/my-wunused.sh -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAEBAwAGBQJgBkNXAAoJEPjQtOfS0hGRKZkQAIkQ/Y1BcKGdQNCpcV7g5DjF Ew0dGRdIw7xqKMeqxpmvELEosnWwICY2qPSAIFFDTsDeifQck97zpxCHWKcM+UmO 1msyhWK34JaaYtkx2DSOgVxqgtVDghCYYz+cSkRMO82DW4Tkr4NZIvVfumB2xRbH j0WJ7CxYEl75AtH0fiI0oaaRdqiBynK/gIPXMAURr8gDFm/tF9yhudqaL5JNenJm C0eS02YZUoZ7Zl6Zrtlv9m4OuMDDLm2j8V4g3Ji5bfDcGasd8OuFNFTpo7z0BDqa ugB3R0f/tec3NY/3nAKhSHt8wZqAjA9T+vuTBgn4mz/tI/wECnUAQM6aECCvhHSS 7amAVGgT+cLMnDZFslTld1VAdG7K4wm9JZFb4jDvTBAclZzPNAZe/iRjgaFCrMNO vs1+xIKhT+MctgDRz/cFlLc/ck8qzBiegAhm1RL2+zVj7dPaAQPZDPkAWIQPI571 1oP7FKXz5f3w5aIIXisOHcFC8yRrrcxCFNcCEvOKrU7IamPVJXrMwgVvCZ1jiwQQ qw1g538eV2ilv25nAmfzkJ8sgvcDJ1cWq9sN0iNVAgnG0g1BNUi4Ya3dlcicL1l3 OyDu/MwJKcjVV+zwOKC9yMLEAMYm/XeNvmbSDfzwawcHfHkEFkCFFJii+LeU1G3R oBCQeEuwtKHKGm4sUpyn =fX91 -----END PGP SIGNATURE----- Math-PlanePath-129/MANIFEST0000644000175000017500000003664714001441522013050 0ustar ggggChanges COPYING debian/changelog debian/compat debian/control debian/copyright debian/rules debian/source/format debian/watch devel/a240025.l devel/alternat.l devel/alternate-paper-midpoint.pl devel/alternate-paper.pl devel/alternate-terdragon.pl devel/archimedean.pl devel/aztec-diamond.pl devel/beta-omega.pl devel/bigint-lite.pl devel/bignums.pl devel/biguv.pl devel/c-curve.pl devel/cellular-rule-oeis.pl devel/cellular-rule-xpm.pl devel/cellular-rule.pl devel/cellular-rule62.pl devel/cfrac-digits.pl devel/chan-tree.pl devel/complex-minus.pl devel/complex-plus.pl devel/complex-revolving.pl devel/cont-frac.pl devel/coprime.pl devel/corner-replicate.pl devel/cubic-base.pl devel/dekking-curve.pl devel/diagonals.pl devel/digit-groups.pl devel/divisible-columns.pl devel/dragon.pl devel/exe-atan2.c devel/exe-complex-minus.c devel/exe-complex-plus.c devel/exe-complex-revolving.c devel/factor-rationals.pl devel/fibonacci-word.logo devel/fibonacci-word.pl devel/filled-rings.pl devel/flowsnake-ascii.gp devel/flowsnake-levels.pl devel/flowsnake.pl devel/fractions-tree.pl devel/gcd-rationals-integer.pl devel/gcd-rationals.pl devel/gosper-islands-stars.pl devel/gosper-replicate.pl devel/gosper-side.pl devel/gray.pl devel/greek-key.pl devel/grep-various-values.pl devel/grid.pl devel/hex-arms.pl devel/hexhypot.pl devel/hilbert-diamond.pl devel/hilbert-sides.pl devel/hilbert.pl devel/hypot.pl devel/imaginary-base.pl devel/imaginary-half.pl devel/interpolate.pl devel/iterator.pl devel/junk/FibonacciSquareSpiral.pm devel/koch-curve.pl devel/koch-snowflakes.pl devel/koch-squareflakes.pl devel/l-tiling.pl devel/lib/Math/PlanePath/BalancedArray.pm devel/lib/Math/PlanePath/BinaryTerms-oeis.t devel/lib/Math/PlanePath/BinaryTerms.pm devel/lib/Math/PlanePath/FibonacciWordKnott.pm devel/lib/Math/PlanePath/four-replicate.pl devel/lib/Math/PlanePath/FourReplicate.pm devel/lib/Math/PlanePath/godfrey.pl devel/lib/Math/PlanePath/Godfrey.pm devel/lib/Math/PlanePath/MooreSpiral.pm devel/lib/Math/PlanePath/NxN.pm devel/lib/Math/PlanePath/NxNinv.pm devel/lib/Math/PlanePath/NxNvar.pm devel/lib/Math/PlanePath/ParabolicRows.pm devel/lib/Math/PlanePath/ParabolicRuns.pm devel/lib/Math/PlanePath/PeanoHalf.pm devel/lib/Math/PlanePath/PeanoRounded.pm devel/lib/Math/PlanePath/PeanoVertices.pm devel/lib/Math/PlanePath/PowerRows.pm devel/lib/Math/PlanePath/PyramidReplicate.pm devel/lib/Math/PlanePath/QuintetSide.pm devel/lib/Math/PlanePath/R7DragonCurve.pm devel/lib/Math/PlanePath/SquaRecurve.pm devel/lib/Math/PlanePath/squares-dispersion.pl devel/lib/Math/PlanePath/SumFractions.pm devel/lib/Math/PlanePath/wythoff-lines.pl devel/lib/Math/PlanePath/WythoffDifference-oeis.t devel/lib/Math/PlanePath/WythoffDifference.pm devel/lib/Math/PlanePath/WythoffLines.pm devel/lib/Math/PlanePath/WythoffTriangle-oeis.t devel/lib/Math/PlanePath/WythoffTriangle.pm devel/lib/Math/PlanePath/z2-dragon.pl devel/lib/Math/PlanePath/Z2DragonCurve.pm devel/lib/Math/PlanePath/ZeckendorfTerms-oeis.t devel/lib/Math/PlanePath/ZeckendorfTerms.pm devel/lib/Math/square-radical.pl devel/lib/Math/SquareRadical.pm devel/lib/MyFLAT.pm devel/lib/MyGraphs.pm devel/Makefile devel/mephisto-waltz.logo devel/misc.pl devel/multiple-rings.pl devel/number-fraction.pl devel/numseq.pl devel/peano.l devel/peano.pl devel/period-doubling.pl devel/pictures.tex devel/pixel-rings.pl devel/pythagorean.pl devel/quadric.pl devel/quintet-replicate.pl devel/quintet.pl devel/r5-dragon.pl devel/r5.pl devel/rationals-tree.pl devel/run.pl devel/sacks.pl devel/sierpinski-arrowhead-centres.pl devel/sierpinski-arrowhead-stars.pl devel/sierpinski-arrowhead.pl devel/sierpinski-curve.pl devel/sierpinski-triangle.gnuplot devel/sierpinski-triangle.pl devel/square-spiral.gnuplot devel/square-spiral.pl devel/squarecurve.pl devel/staircase-alternating.pl devel/t-square.pl devel/terdragon.pl devel/theodorus.gnuplot devel/theodorus.pl devel/tree.pl devel/triangle-spiral.pl devel/ulam-warburton.pl devel/vertical.pl devel/vogel.pl devel/wunderlich.pl devel/wythoff-array.pl devel/zorder.pl examples/a023531.l examples/c-curve-wx.pl examples/cellular-rules.pl examples/cretan-walls.pl examples/hilbert-oeis.pl examples/hilbert-path.pl examples/knights-oeis.pl examples/koch-svg.pl examples/numbers.pl examples/other/dragon-curve.el examples/other/dragon-curve.gnuplot examples/other/dragon-curve.logo examples/other/dragon-curve.m4 examples/other/dragon-pgf-latex.tex examples/other/dragon-pgf-plain.tex examples/other/dragon-recursive.gri examples/other/fibonacci-word-fractal.logo examples/other/flowsnake-ascii-small.gp examples/other/sierpinski-triangle-bitand.gnuplot examples/other/sierpinski-triangle-replicate.gnuplot examples/other/sierpinski-triangle-text.gnuplot examples/other/sierpinski-triangle-text.logo examples/other/sierpinski-triangle.m4 examples/rationals-tree.pl examples/sacks-xpm.pl examples/square-numbers.pl examples/ulam-spiral-xpm.pl lib/Math/NumSeq/OEIS/Catalogue/Plugin/PlanePath.pm lib/Math/NumSeq/PlanePathCoord.pm lib/Math/NumSeq/PlanePathDelta.pm lib/Math/NumSeq/PlanePathN.pm lib/Math/NumSeq/PlanePathTurn.pm lib/Math/PlanePath.pm lib/Math/PlanePath/AlternatePaper.pm lib/Math/PlanePath/AlternatePaperMidpoint.pm lib/Math/PlanePath/AlternateTerdragon.pm lib/Math/PlanePath/AnvilSpiral.pm lib/Math/PlanePath/AR2W2Curve.pm lib/Math/PlanePath/ArchimedeanChords.pm lib/Math/PlanePath/AztecDiamondRings.pm lib/Math/PlanePath/Base/Digits.pm lib/Math/PlanePath/Base/Generic.pm lib/Math/PlanePath/Base/NSEW.pm lib/Math/PlanePath/BetaOmega.pm lib/Math/PlanePath/CCurve.pm lib/Math/PlanePath/CellularRule.pm lib/Math/PlanePath/CellularRule190.pm lib/Math/PlanePath/CellularRule54.pm lib/Math/PlanePath/CellularRule57.pm lib/Math/PlanePath/CfracDigits.pm lib/Math/PlanePath/ChanTree.pm lib/Math/PlanePath/CincoCurve.pm lib/Math/PlanePath/Columns.pm lib/Math/PlanePath/ComplexMinus.pm lib/Math/PlanePath/ComplexPlus.pm lib/Math/PlanePath/ComplexRevolving.pm lib/Math/PlanePath/CoprimeColumns.pm lib/Math/PlanePath/Corner.pm lib/Math/PlanePath/CornerAlternating.pm lib/Math/PlanePath/CornerReplicate.pm lib/Math/PlanePath/CretanLabyrinth.pm lib/Math/PlanePath/CubicBase.pm lib/Math/PlanePath/DekkingCentres.pm lib/Math/PlanePath/DekkingCurve.pm lib/Math/PlanePath/DiagonalRationals.pm lib/Math/PlanePath/Diagonals.pm lib/Math/PlanePath/DiagonalsAlternating.pm lib/Math/PlanePath/DiagonalsOctant.pm lib/Math/PlanePath/DiamondArms.pm lib/Math/PlanePath/DiamondSpiral.pm lib/Math/PlanePath/DigitGroups.pm lib/Math/PlanePath/DivisibleColumns.pm lib/Math/PlanePath/DragonCurve.pm lib/Math/PlanePath/DragonMidpoint.pm lib/Math/PlanePath/DragonRounded.pm lib/Math/PlanePath/FactorRationals.pm lib/Math/PlanePath/FibonacciWordFractal.pm lib/Math/PlanePath/File.pm lib/Math/PlanePath/FilledRings.pm lib/Math/PlanePath/Flowsnake.pm lib/Math/PlanePath/FlowsnakeCentres.pm lib/Math/PlanePath/FractionsTree.pm lib/Math/PlanePath/GcdRationals.pm lib/Math/PlanePath/GosperIslands.pm lib/Math/PlanePath/GosperReplicate.pm lib/Math/PlanePath/GosperSide.pm lib/Math/PlanePath/GrayCode.pm lib/Math/PlanePath/GreekKeySpiral.pm lib/Math/PlanePath/HeptSpiralSkewed.pm lib/Math/PlanePath/HexArms.pm lib/Math/PlanePath/HexSpiral.pm lib/Math/PlanePath/HexSpiralSkewed.pm lib/Math/PlanePath/HilbertCurve.pm lib/Math/PlanePath/HilbertSides.pm lib/Math/PlanePath/HilbertSpiral.pm lib/Math/PlanePath/HIndexing.pm lib/Math/PlanePath/Hypot.pm lib/Math/PlanePath/HypotOctant.pm lib/Math/PlanePath/ImaginaryBase.pm lib/Math/PlanePath/ImaginaryHalf.pm lib/Math/PlanePath/KnightSpiral.pm lib/Math/PlanePath/KochCurve.pm lib/Math/PlanePath/KochelCurve.pm lib/Math/PlanePath/KochPeaks.pm lib/Math/PlanePath/KochSnowflakes.pm lib/Math/PlanePath/KochSquareflakes.pm lib/Math/PlanePath/LTiling.pm lib/Math/PlanePath/MPeaks.pm lib/Math/PlanePath/MultipleRings.pm lib/Math/PlanePath/OctagramSpiral.pm lib/Math/PlanePath/PeanoCurve.pm lib/Math/PlanePath/PeanoDiagonals.pm lib/Math/PlanePath/PentSpiral.pm lib/Math/PlanePath/PentSpiralSkewed.pm lib/Math/PlanePath/PixelRings.pm lib/Math/PlanePath/PowerArray.pm lib/Math/PlanePath/PyramidRows.pm lib/Math/PlanePath/PyramidSides.pm lib/Math/PlanePath/PyramidSpiral.pm lib/Math/PlanePath/PythagoreanTree.pm lib/Math/PlanePath/QuadricCurve.pm lib/Math/PlanePath/QuadricIslands.pm lib/Math/PlanePath/QuintetCentres.pm lib/Math/PlanePath/QuintetCurve.pm lib/Math/PlanePath/QuintetReplicate.pm lib/Math/PlanePath/R5DragonCurve.pm lib/Math/PlanePath/R5DragonMidpoint.pm lib/Math/PlanePath/RationalsTree.pm lib/Math/PlanePath/Rows.pm lib/Math/PlanePath/SacksSpiral.pm lib/Math/PlanePath/SierpinskiArrowhead.pm lib/Math/PlanePath/SierpinskiArrowheadCentres.pm lib/Math/PlanePath/SierpinskiCurve.pm lib/Math/PlanePath/SierpinskiCurveStair.pm lib/Math/PlanePath/SierpinskiTriangle.pm lib/Math/PlanePath/SquareArms.pm lib/Math/PlanePath/SquareReplicate.pm lib/Math/PlanePath/SquareSpiral.pm lib/Math/PlanePath/Staircase.pm lib/Math/PlanePath/StaircaseAlternating.pm lib/Math/PlanePath/TerdragonCurve.pm lib/Math/PlanePath/TerdragonMidpoint.pm lib/Math/PlanePath/TerdragonRounded.pm lib/Math/PlanePath/TheodorusSpiral.pm lib/Math/PlanePath/TriangleSpiral.pm lib/Math/PlanePath/TriangleSpiralSkewed.pm lib/Math/PlanePath/TriangularHypot.pm lib/Math/PlanePath/UlamWarburton.pm lib/Math/PlanePath/UlamWarburtonQuarter.pm lib/Math/PlanePath/VogelFloret.pm lib/Math/PlanePath/WunderlichMeander.pm lib/Math/PlanePath/WunderlichSerpentine.pm lib/Math/PlanePath/WythoffArray.pm lib/Math/PlanePath/WythoffPreliminaryTriangle.pm lib/Math/PlanePath/ZOrderCurve.pm Makefile.PL MANIFEST This list of files MANIFEST.SKIP SIGNATURE t/AlternatePaper.t t/AlternatePaperMidpoint.t t/AlternateTerdragon.t t/AnvilSpiral.t t/ArchimedeanChords.t t/AztecDiamondRings.t t/Base-Digits.t t/Base-Generic.t t/BetaOmeta.t t/bigfloat.t t/bigint-lite.t t/bigint.t t/bigint_common.pm t/CCurve.t t/CellularRule.t t/CellularRule190.t t/CfracDigits.t t/ChanTree.t t/CincoCurve.t t/Columns-load.t t/Columns.t t/ComplexMinus.t t/ComplexPlus.t t/CoprimeColumns.t t/Corner.t t/CornerAlternating.t t/CornerReplicate.t t/CubicBase.t t/DekkingCurve.t t/DiagonalRationals.t t/Diagonals.t t/DiamondArms.t t/DiamondSpiral.t t/DigitGroups.t t/DivisibleColumns.t t/DragonCurve.t t/DragonMidpoint.t t/DragonRounded.t t/FactorRationals.t t/FibonacciWordFractal.t t/File.t t/FilledRings.t t/Flowsnake.t t/FlowsnakeCentres.t t/GcdRationals.t t/GosperIslands.t t/GosperReplicate.t t/GosperSide.t t/GrayCode.t t/GreekKeySpiral.t t/HexArms.t t/HexSpiral-load.t t/HexSpiral.t t/HexSpiralSkewed-unskew.t t/HexSpiralSkewed.t t/HilbertCurve-load.t t/HilbertCurve.t t/HilbertSpiral.t t/HIndexing.t t/Hypot.t t/HypotOctant.t t/ImaginaryBase.t t/ImaginaryHalf.t t/KnightSpiral.t t/KochCurve.t t/KochelCurve.t t/KochPeaks.t t/KochSnowflakes.t t/KochSquareflakes.t t/LTiling.t t/MultipleRings.t t/MyTestHelpers.pm t/number-fraction.t t/NumSeq-PlanePathCoord.t t/NumSeq-PlanePathDelta.t t/NumSeq-PlanePathN.t t/NumSeq-PlanePathTurn.t t/PeanoCurve-load.t t/PeanoCurve.t t/PeanoDiagonals.t t/PixelRings.t t/PlanePath.t t/PyramidRows.t t/PythagoreanTree.t t/QuadricCurve.t t/QuadricIslands.t t/QuintetCentres.t t/QuintetCurve.t t/QuintetReplicate.t t/RationalsTree.t t/Rows-load.t t/Rows.t t/SacksSpiral.t t/SierpinskiArrowhead.t t/SierpinskiArrowheadCentres.t t/SierpinskiCurve.t t/SierpinskiCurveStair.t t/SierpinskiTriangle.t t/SquareReplicate.t t/SquareSpiral.t t/Staircase.t t/StaircaseAlternating.t t/TerdragonCurve.t t/TerdragonMidpoint.t t/TheodorusSpiral.t t/TriangularHypot.t t/TrianguleSpiral.t t/UlamWarburton.t t/UlamWarburtonQuarter.t t/VogelFloret.t t/WythoffArray.t t/ZOrderCurve.t tools/alternate-paper-dxdy.pl tools/ar2w2-curve-table.pl tools/beta-omega-table.pl tools/cellular-rule-limits.pl tools/cinco-curve-table.pl tools/corner-replicate-table.pl tools/dekking-centres-table.pl tools/dekking-curve-table.pl tools/dragon-curve-dxdy.pl tools/dragon-curve-table.pl tools/flowsnake-centres-table.pl tools/flowsnake-table.pl tools/gallery.pl tools/hilbert-curve-table.pl tools/hilbert-spiral-table.pl tools/kochel-curve-table.pl tools/man-page-listing.pl tools/moore-spiral-table.pl tools/peano-diagonal-samples.pl tools/pythagorean-tree.pl tools/r5dragon-midpoint-offset.pl tools/terdragon-midpoint-offset.pl tools/wunderlich-meander-table.pl tools/wythoff-array-zeck.pl xt/0-examples-xrefs.t xt/0-file-is-part-of.t xt/0-META-read.t xt/0-no-debug-left-on.t xt/0-Test-ConsistentVersion.t xt/0-Test-Pod.t xt/0-Test-Synopsis.t xt/0-Test-YAML-Meta.t xt/AlternatePaper-hog.t xt/AlternateTerdragon-hog.t xt/bigrat.t xt/CCurve-hog.t xt/ChanTree-slow.t xt/DragonCurve-hog.t xt/DragonCurve-more.t xt/GrayCode-oseq.t xt/HIndexing-more.t xt/KochCurve-more.t xt/MyOEIS.pm xt/oeis-duplicate.t xt/oeis-xrefs.t xt/oeis/AlternatePaper-oeis.t xt/oeis/AlternatePaperMidpoint-oeis.t xt/oeis/AlternateTerdragon-oeis.t xt/oeis/AnvilSpiral-oeis.t xt/oeis/CCurve-oeis.t xt/oeis/CellularRule-oeis.t xt/oeis/CellularRule190-oeis.t xt/oeis/CellularRule54-oeis.t xt/oeis/CfracDigits-oeis.t xt/oeis/ComplexMinus-oeis.t xt/oeis/ComplexPlus-oeis.t xt/oeis/CoprimeColumns-oeis.t xt/oeis/Corner-oeis.t xt/oeis/CornerAlternating-oeis.t xt/oeis/CornerReplicate-oeis.t xt/oeis/DiagonalRationals-oeis.t xt/oeis/Diagonals-oeis.t xt/oeis/DiagonalsAlternating-oeis.t xt/oeis/DiagonalsOctant-oeis.t xt/oeis/DiamondSpiral-oeis.t xt/oeis/DigitGroups-oeis.t xt/oeis/DivisibleColumns-oeis.t xt/oeis/DragonCurve-oeis.t xt/oeis/DragonMidpoint-oeis.t xt/oeis/FactorRationals-oeis.t xt/oeis/FibonacciWordFractal-oeis.t xt/oeis/FilledRings-oeis.t xt/oeis/Flowsnake-oeis.t xt/oeis/FractionsTree-oeis.t xt/oeis/GcdRationals-oeis.t xt/oeis/GosperSide-oeis.t xt/oeis/GrayCode-oeis.t xt/oeis/HeptSpiralSkewed-oeis.t xt/oeis/HexSpiral-oeis.t xt/oeis/HilbertCurve-oeis.t xt/oeis/HilbertSides-oeis.t xt/oeis/HIndexing-oeis.t xt/oeis/Hypot-oeis.t xt/oeis/HypotOctant-oeis.t xt/oeis/ImaginaryBase-oeis.t xt/oeis/KnightSpiral-oeis.t xt/oeis/KochCurve-oeis.t xt/oeis/KochSnowflakes-oeis.t xt/oeis/KochSquareflakes-oeis.t xt/oeis/LTiling-oeis.t xt/oeis/MPeaks-oeis.t xt/oeis/MultipleRings-oeis.t xt/oeis/NumSeq-PlanePath-oeis.t xt/oeis/OctagramSpiral-oeis.t xt/oeis/PeanoCurve-oeis.t xt/oeis/PentSpiral-oeis.t xt/oeis/PentSpiralSkewed-oeis.t xt/oeis/PowerArray-oeis.t xt/oeis/PyramidRows-oeis.t xt/oeis/PyramidSides-oeis.t xt/oeis/PyramidSpiral-oeis.t xt/oeis/PythagoreanTree-oeis.t xt/oeis/QuadricCurve-oeis.t xt/oeis/QuintetCentres-oeis.t xt/oeis/QuintetReplicate-oeis.t xt/oeis/R5DragonCurve-oeis.t xt/oeis/RationalsTree-oeis.t xt/oeis/SierpinskiArrowhead-oeis.t xt/oeis/SierpinskiCurve-oeis.t xt/oeis/SierpinskiTriangle-oeis.t xt/oeis/SquareSpiral-oeis.t xt/oeis/Staircase-oeis.t xt/oeis/TerdragonCurve-oeis.t xt/oeis/TheodorusSpiral-oeis.t xt/oeis/TriangleSpiral-oeis.t xt/oeis/TriangleSpiralSkewed-oeis.t xt/oeis/TriangularHypot-oeis.t xt/oeis/UlamWarburton-oeis.t xt/oeis/UlamWarburtonQuarter-oeis.t xt/oeis/WunderlichSerpentine-oeis.t xt/oeis/WythoffArray-oeis.t xt/oeis/WythoffPreliminaryTriangle-oeis.t xt/oeis/ZOrderCurve-oeis.t xt/PeanoDiagonals-seq.t xt/PixelRings-image.t xt/PlanePath-subclasses.t xt/pod-lists.t xt/R5DragonCurve-hog.t xt/slow/AlternatePaper-slow.t xt/slow/CCurve-slow.t xt/slow/CellularRule-slow.t xt/slow/ComplexMinus-slow.t xt/slow/DragonCurve-slow.t xt/slow/GcdRationals-slow.t xt/slow/HilbertCurve-slow.t xt/slow/NumSeq-PlanePathCoord.t xt/slow/R5DragonCurve-slow.t xt/slow/TerdragonCurve-slow.t xt/TerdragonCurve-hog.t xtools/my-check-copyright-years.sh xtools/my-check-spelling.sh xtools/my-deb.sh xtools/my-diff-prev.sh xtools/my-kwalitee.sh xtools/my-manifest.sh xtools/my-pc.sh xtools/my-tags.sh xtools/my-wunused.sh META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) Math-PlanePath-129/MANIFEST.SKIP0000644000175000017500000000623013434325040013603 0ustar gggg#!/usr/bin/perl # MANIFEST.SKIP -- Kevin's various excluded files # Copyright 2008, 2009, 2010, 2011, 2012, 2013, 2015, 2019 Kevin Ryde # This file is shared among several distributions. # # This file 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, or (at your option) # any later version. # # This file 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 file. If not, see . # cf. /usr/share/perl/5.14/ExtUtils/MANIFEST.SKIP # emacs backups ~$ # emacs locks (^|/)\.# # emacs autosave (^|/)# # own distdir ^[A-Za-z][A-Za-z0-9-_]*-\d+/ # own dist files \.tar$ \.tar\.gz$ \.deb$ \.dsc$ \.diff.gz$ # ExtUtils::MakeMaker leaving Makefile.old # and "myman" leaving MANIFEST.old \.old$ # ExtUtils::MakeMaker "metafile" rule temporary, left behind if interrupted ^META_new\.yml$ # built - MakeMaker ^Makefile$ ^blib ^pm_to_blib ^TAGS$ # MakeMaker 6.18 to 6.25, apparently ^blibdirs\.ts$ # msdos compiler output stuff using gcc, # "XSFILENAME.def" extension, and others fixed names it seems \.def$ ^dll\.base$ ^dll\.exp$ # msdos compiler stuff using ms c \.pdb$ # built - recent Module::Build nonsense ^MYMETA\.yml$ # built - something recent ExtUtils::MakeMaker ^MYMETA\.json$ # built - cdbs and debhelper ^debian/stamp- ^debian/.*\.log$ # built - texinfo.tex temporaries \.(aux|cp|cps|fn|fns|ky|log|pg|toc|tp|tps|vr)$ # built - PGF temporary \.pgf$ # tex or latex output, not distributed for now \.(dvi|pdf|ps)$ # toplevel .c files built from .xs ^[^/]+\.c$ # built .o compiled and .bs from .xs \.o$ \.obj$ \.bs$ # #(^|/)[A-Z][A-Za-z0-9_]*\.c$ #/[^Z])[^/]+\.c$ # built - configury ^a\.out$ ^conftest ^config\.h$ ^myconfig\.h$ # built - toplevel html pages ^[a-zA-Z][^./]+\.html$ # inc/MyMakefileExtras.pm "diff-prev" ^diff\.tmp # inc/MyMakefileExtras.pm "lintian-source" ^temp-lintian # various testing ^tempfile\.png$ ^tempfile \.tmp$ # my dists ^dist-deb ^up$ ^c$ # special case p..ulp test build stuff devel/h2xs/TestConstFoo/ # special case various executables ^devel/exe-[^/.]+$ # special case mall executables ^devel/hblk$ ^devel/mallopt$ ^devel/mallstats$ ^devel/malltrim$ # special case fli executables ^devel/mmap-multi$ # special case widget-bits executables ^devel/grandom$ ^devel/grandom-[a-z]+$ # special case widget-cursor executables ^devel/invisible-blank$ # special case x'or executables ^devel/gtk-gc-colour$ # special case combo executables ^devel/toolbutton-overflow-leak$ # special case xpother executables ^devel/encode-all$ ^devel/encode-dump$ ^devel/Encode-X11-xlib$ ^devel/Encode-X11-xlib2$ # special case xpother samples ^devel/encode.*\.ctext$ ^devel/encode.*\.utf8$ # special htmlext environs ^test-dist/ # TeX _whizzy.* \.aux$ \.dvi$ \.fls$ \.log$ \.out$ \.pdf$ \.waux$ \.wdvi$ \.fdb_latexmk$ \.junk$ \.bak$ ^backup ^misc ^maybe ^samples ^samp ^formats Math-PlanePath-129/debian/0002755000175000017500000000000014001441522013123 5ustar ggggMath-PlanePath-129/debian/control0000644000175000017500000000406013351377462014546 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2016, 2017, 2018 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at # your option) any later version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Build-Depends could have the "maximum_tests" modules from META.yml # for more tests, but they're optional author tests really and would # just make the build tools drag in more stuff. Source: libmath-planepath-perl Section: perl Priority: optional Build-Depends: cdbs, debhelper (>= 5) Maintainer: Kevin Ryde Standards-Version: 4.2.1 Homepage: http://user42.tuxfamily.org/math-planepath/index.html Bugs: mailto:user42_kevin@yahoo.com.au Package: libmath-planepath-perl Architecture: all Depends: perl (>= 5.004), libconstant-perl (>= 1.02) | perl (>= 5.6), libmath-libm-perl, libscalar-list-utils-perl | perl (>= 5.8), ${perl:Depends}, ${misc:Depends} Description: Mathematical paths through the plane Perl code to generate some paths through the 2-D X,Y plane, mainly integer oriented, and including . * Square numbering of Ulam's spiral * Pentagonal, hexagonal, heptagonal spirals * Pyramid and triangular spirals and rows * An infinite knight's tour * Vogel's sunflower floret (and variations) * Sacks' quadratic spiral * Spiral of Theodorus * Peano, Hilbert and Z-Order * Gosper's flowsnake * Koch curve and quadric curve * Sierpinski triangle * Dragon curves * Pixellated rings, and by hypotenuse distance * Trees of rationals and Pythagorean triples * Some complex base related patternsMath-PlanePath-129/debian/watch0000644000175000017500000000172313174733617014200 0ustar gggg# Web site version watch, for devscripts uscan program. # Copyright 2010, 2017 Kevin Ryde # # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at # your option) any later version. # # Math-PlanePath 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 Math-PlanePath. If not, see . # Crib notes: # "man uscan" describes the format. # test with: uscan --report --verbose --watchfile=debian/watch version=3 https://user42.tuxfamily.org/math-planepath/index.html \ .*/Math-PlanePath-([0-9]+)\.tar\.gz Math-PlanePath-129/debian/source/0002755000175000017500000000000014001441522014423 5ustar ggggMath-PlanePath-129/debian/source/format0000644000175000017500000000000411413032707015633 0ustar gggg1.0 Math-PlanePath-129/debian/compat0000644000175000017500000000000213035657234014336 0ustar gggg9 Math-PlanePath-129/debian/copyright0000644000175000017500000000061213734026674015076 0ustar ggggMath-PlanePath is Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Kevin Ryde Math-PlanePath is licensed under the GNU General Public License GPL version 3 or at your option any later version. The complete text of GPL version 3 is in the file /usr/share/common-licenses/GPL-3 The program home page is http://user42.tuxfamily.org/math-planepath/index.html Math-PlanePath-129/debian/changelog0000644000175000017500000000023313734026674015014 0ustar gggglibmath-planepath-perl (129-0.1) unstable; urgency=low * Packaged version. -- Kevin Ryde Sun, 27 Sep 2020 16:13:05 +1000 Math-PlanePath-129/debian/rules0000755000175000017500000000155312374741314014222 0ustar gggg#!/usr/bin/make -f # Copyright 2010, 2014 Kevin Ryde # This file is part of Math-PlanePath. # # Math-PlanePath 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, or (at your option) any # later version. # # Math-PlanePath 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 Math-PlanePath. If not, see . include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/class/perl-makemaker.mk DEB_INSTALL_EXAMPLES_libmath-planepath-perl = examples/*