Math-PlanePath-122/0002755000175000017500000000000012641645163011711 5ustar ggggMath-PlanePath-122/COPYING0000644000175000017500000010437410641206144012741 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-122/tools/0002755000175000017500000000000012641645163013051 5ustar ggggMath-PlanePath-122/tools/alternate-paper-dxdy.pl0000644000175000017500000000407012022542003017415 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-122/tools/dragon-curve-table.pl0000644000175000017500000001463312021026530017053 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-122/tools/hilbert-spiral-table.pl0000644000175000017500000001627411666767377017446 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-122/tools/dekking-curve-table.pl0000644000175000017500000001543512021305065017221 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-122/tools/beta-omega-table.pl0000644000175000017500000002703712161517122016471 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-122/tools/pythagorean-tree.pl0000644000175000017500000000322312301760112016643 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 . # 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" +-> 00005 +-> 00002 --+-> 00006 | +-> 00007 | | +-> 00008 001 --+-> 00003 --+-> 00009 | +-> 00010 | | +-> 00011 +-> 00004 --+-> 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 $len = length($1); sprintf '%-*s', $len, "$x,$y"; }ge; print $str; } Math-PlanePath-122/tools/hilbert-curve-table.pl0000644000175000017500000001471312036160013017232 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-122/tools/kochel-curve-table.pl0000644000175000017500000002026411666767323017075 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-122/tools/dragon-curve-dxdy.pl0000644000175000017500000001163412022543023016734 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-122/tools/flowsnake-centres-table.pl0000644000175000017500000001373212063226253020122 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-122/tools/flowsnake-table.pl0000644000175000017500000001717312065504530016463 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 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 $plain_digit = ($rev ? 6-$digit : $digit); my ($i, $j); if ($rev) { # 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_rot += 2; $new_rev = 1; } elsif ($digit == 4) { $i = 2; $j = 0; $new_rot += 3; $new_rev = 0; } elsif ($digit == 5) { $i = 1; $j = 0; $new_rot += 1; $new_rev = 0; } elsif ($digit == 6) { $i = 1; $j = 1; $new_rev = 1; } } else { # 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; } } 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-122/tools/moore-spiral-table.pl0000644000175000017500000000663711713712763017116 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-122/tools/cellular-rule-limits.pl0000644000175000017500000010124712311703413017443 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-122/tools/terdragon-midpoint-offset.pl0000644000175000017500000000321611711717744020502 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 Math::PlanePath::TerdragonMidpoint; # uncomment this to run the ### lines #use Smart::Comments; 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-122/tools/corner-replicate-table.pl0000644000175000017500000000705211660104640017721 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-122/tools/wythoff-array-zeck.pl0000644000175000017500000000374612113742706017145 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-122/tools/dekking-centres-table.pl0000644000175000017500000001374512020130531017533 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-122/tools/cinco-curve-table.pl0000644000175000017500000002023211665051545016705 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-122/tools/gallery.pl0000644000175000017500000014503312551142301015033 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 . # 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 ( ['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'], ['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'], ['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'], ['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'], ['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'], ['knight-small.png', 'math-image --path=KnightSpiral --lines --scale=7 --size=32'], ['knight-big.png', 'math-image --path=KnightSpiral --lines --scale=11 --size=197'], ['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-122/tools/ar2w2-curve-table.pl0000644000175000017500000002763712161517106016557 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-122/tools/r5dragon-midpoint-offset.pl0000644000175000017500000000421112201363223020212 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 Math::PlanePath::R5DragonMidpoint; # uncomment this to run the ### lines #use Smart::Comments; 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-122/tools/wunderlich-meander-table.pl0000644000175000017500000001553511660132465020253 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-122/Changes0000644000175000017500000003713512641634630013210 0ustar ggggCopyright 2010, 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 . Version 122, January 2016 - test fix a sloppy test 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-122/xtools/0002755000175000017500000000000012641645163013241 5ustar ggggMath-PlanePath-122/xtools/my-wunused.sh0000755000175000017500000000312512606127271015710 0ustar gggg#!/bin/sh # my-wunused.sh -- run warnings::unused on dist files # Copyright 2009, 2010, 2011, 2012, 2013, 2015 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-5.10.0 -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-122/xtools/my-diff-prev.sh0000755000175000017500000000300611776230514016100 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-122/xtools/my-tags.sh0000644000175000017500000000200311714065142015140 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-122/xtools/my-check-spelling.sh0000755000175000017500000000320612350135425017102 0ustar gggg#!/bin/sh # my-check-spelling.sh -- grep for spelling errors # Copyright 2009, 2010, 2011, 2012, 2013, 2014 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)' \ if find . -name my-check-spelling.sh -prune \ -o -type f -print0 \ | xargs -0 egrep --color=always -nHi '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' then # nothing found exit 1 else exit 0 fi Math-PlanePath-122/xtools/gp-inline0000755000175000017500000007351512637673012015061 0ustar gggg#!/usr/bin/perl -w # Copyright 2014, 2015 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. If not, see . use 5.006; use strict; use warnings; use Carp 'croak'; use FindBin; use File::Copy; use File::Spec; use File::Temp; use Getopt::Long; use List::Util 'max'; use IPC::Run; # uncomment this to run the ### lines # use Smart::Comments; our $VERSION = 0; my $action = 'run'; my $gp = 'gp'; my $verbose = 0; my $stdin = 0; my $stacksize; my $exit = 0; my $total_files = 0; my $total_expressions = 0; ### $action # in $str change any decimals 0.123 to fractions (123/1000) sub decimals_to_fraction { my ($str) = @_; $str =~ s{(\d*)\.(\d*)} {length($1) || length($2) ? "($1$2/1".('0' x length($2)).")" : "$1.$2" # bare dot unchanged }ge; return $str; } my $comment_prefix_re = qr{^\s* ([\#%*]+ # # Perl, % TeX, * C continuing |//+ # // C++ |/\*+ # /* C |=for\s # =for Perl POD )? # or nothing }x; my $tex_measure_re = qr/[0-9.]*(em|ex|pt|mm|cm|in)/; sub pos_linenum { my ($str, $pos) = @_; $pos //= pos($_[0]); $str = substr($str, 0, $pos); return 1 + scalar($str =~ tr/\n//); } sub parse_constants { my ($str, %options) = @_; my $type = $options{'type'}; ### parse_constants() ... ### $type my $bad = sub { my ($message) = @_; ### pos: pos($str) my $linenum = $options{'linenum'} + pos_linenum($str, pos($str)); print STDERR "$options{'filename'}:$linenum: $message\n"; $str =~ /\G\s*[^\n]{0,20}/s; my $near = $&; if ($near eq '') { print STDERR " (at end)\n"; } else { print STDERR " near $&\n"; } $exit = 1; }; my $value_maybe; my $whitespace = sub { # ignored stuff $str =~ /\G( \s+ # whitespace |\\[,;] # \, \; TeX |\\kern$tex_measure_re # \kern1.5em |\\hspace\{$tex_measure_re} # \hspace{1.5em} |\\(degree|dots[bc]?|q?quad) # \degree \dotsc \dotsb \quad \qquad |\\phantom\{[^}]*\} # \phantom{...} )*/gcsx; }; my $fraction_maybe = sub { $whitespace->(); ### fraction_maybe(): substr($str, pos($str), 20) if ($str =~ m/\G(\d+(\.\d*)?|\d*\.\d+)/gc) { # number 123.456 my $number = $1; ### $number ### to: substr($str, pos($str), 20) return decimals_to_fraction($number); } if ($str =~ /\G\\[td]?frac(\d)(\d)/gc) { # TeX \frac34 return "$1/$2"; } if ($str =~ /\G\\[td]?frac\{/gc) { # TeX \frac{123}{456} my $num = $value_maybe->(); $whitespace->(); unless ($str =~ /\G\}\s*\{/sgc) { # }{ $bad->("unrecognised \\frac{}{}"); return undef; } my $den = $value_maybe->(); $whitespace->(); unless ($str =~ /\G\}/gc) { # } $bad->("unclosed \\frac{}{}"); } ### end fraction: substr($str, pos($str), 20) return "($num)/($den)"; } return undef; }; my $addend_maybe = sub { ### addend_maybe(): substr($str, pos($str), 20) my $ret = $fraction_maybe->(); my $complex = 0; $whitespace->(); ### try complex: substr($str, pos($str), 20) if ($str =~ m/\Gi/gc) { if (defined $ret) { $ret .= "*I"; # 123 i } else { $ret = "I"; # i alone } $complex = 1; } ### $ret ### $complex return ($ret, $complex); }; my $sign_maybe = sub { $whitespace->(); ### sign_maybe(): substr($str, pos($str), 20) if ($str =~ m{\G(([-+])|\{([-+])\})}gc) { my $sign = $2 || $3; ### $sign ### leave: substr($str, pos($str), 20) return $sign; } else { return undef; } }; $value_maybe = sub { my $sign = $sign_maybe->(); my ($add,$complex1) = $addend_maybe->(); if (! defined $add) { if (defined $sign) { $bad->("unrecognised expression after $sign"); } return undef; } my $ret = $sign || ''; $ret .= $add; $sign = $sign_maybe->() || return $ret; ($add, my $complex2) = $addend_maybe->(); if (! defined $add) { $bad->("unrecognised expression after $sign"); return $ret; } $ret .= $sign; $ret .= $add; if ($complex1 == $complex2) { $bad->("no arithmetic expressions (only complex numbers)"); } return $ret; }; $whitespace->(); $str =~ /\G&?=/gc; # optional initial = or &= # secret undocumented ... $whitespace->(); $str =~ /\G[[(]/gcx; # optional initial [ or ( my $separator_maybe = sub { my $comma; my $semi; for (;;) { ### separator_maybe(): substr($str, pos($str), 20) $whitespace->(); if ($str =~ /\G([,&]|\{,\})/gc) { # & , {,} separator $comma = ','; } elsif ($str =~ /\G\\\\/gc) { if ($type eq 'MATRIX') { $semi = ';'; # \\ for matrix rows } else { $comma = ','; # \\ separator in vector or constant } } else { last; } } return $semi || $comma; }; $separator_maybe->(); my $ret = $value_maybe->(); if (! defined $ret) { $bad->("unrecognised expression"); return ''; } for (;;) { my $sep = $separator_maybe->() || last; my $more = $value_maybe->(); if (! defined $more) { last; } if ($type eq 'CONSTANT') { $bad->("multiple values in CONSTANT"); last; } $ret .= $sep; $ret .= $more; } ### end of values: substr($str, pos($str), 20) # secret undocumented ... $whitespace->(); $str =~ /\G[])]/gcx; # optional initial ] or ) $whitespace->(); if (pos($str) != length($str)) { $bad->("unrecognised expression"); } return $ret; } sub test_fh { my ($fh, $filename) = @_; my $output_fh; my $runner_tempfh; if ($action eq 'run') { $output_fh = File::Temp->new (TEMPLATE => 'gp-inline-XXXXXX', SUFFIX => '.gp', TMPDIR => 1); } else { $output_fh = \*STDOUT; } my $test_last_fh = File::Temp->new (TEMPLATE => 'gp-inline-XXXXXX', SUFFIX => '.gp', TMPDIR => 1); my $test_last; my $output = sub { my $fh = ($test_last ? $test_last_fh : $output_fh); print $fh @_ or die "Error writing: $!"; }; my $output_test = sub { if ($action ne 'defines') { $output->(@_); } }; $output->(<<'HERE'); /* gp-inline test boilerplate begin */ gp_inline__location = ""; gp_inline__bad_location = ""; gp_inline__notbool_location = ""; gp_inline__good = 0; gp_inline__bad = 0; gp_inline__check(location,bool) = { gp_inline__location = location; check(bool); } check(bool) = { /* use "===" so that a vector like [1] is not reckoned as success */ if(bool===1, gp_inline__good++, bool===0, gp_inline__bad++; if(gp_inline_location!=gp_inline__bad_location, print(gp_inline__location": gp-inline fail"), gp_inline__bad_location=gp_inline_location), gp_inline__bad++; if(gp_inline_location!=gp_inline__notbool_location, print(gp_inline__location": gp-inline expected result 0 or 1, got ", bool); gp_inline__notbool_location = gp_inline_location) ); } /* gp-inline test boilerplate end */ HERE # Possible equality check instead of "==" # gp_inline__equal(got,want) = # { # if(x==y,gp_inline__good++, # gp_inline__bad++; # print(gp_inline__location": gp-inline fail"); # print("got "got); # print("want "want)); # print1(); # } if ($verbose) { $output->("\\e 1\n"); } { my $end = ''; my $within = ''; my $within_linenum; my $within_str; my $join = ''; my $linenum = 1; my $prev_type = ''; while (defined (my $line = readline $fh)) { $linenum = $.; ### $line ### $within if ($line =~ s{(?$comment_prefix_re)\s*GP-(?[-A-Za-z0-9]+)(:|\s)}{}) { my $type = $+{'type'}; if ($+{'prefix'} =~ m{/\*}) { $line =~ s{\*+/\s*$}{}; # strip C comment close */ } $line =~ s/\n$//; $type = uc($type); ### $type if ($type eq 'TEST-LAST') { $test_last = 1; $type = 'TEST'; } else { $test_last = 0; } if ($type eq 'END') { if (defined $end) { $output->(parse_constants($within_str, filename => $filename, linenum => $within_linenum, type => $within)); $output->($end); undef $end; } else { print STDERR "$filename:$linenum: unexpected GP-END\n"; $exit = 1; } $within = ''; next; } if ($type eq 'TEST') { if ($within ne 'TEST') { if ($within ne '') { print STDERR "$filename:$linenum: still within $within from line $within_linenum\n"; $exit = 1; } $within_linenum = $linenum; $output_test->("gp_inline__test() = "); } if ($line =~ /\\$/) { ### test continues after this line ... ### $line $within = 'TEST'; $output_test->("$line\n"); } else { ### test ends at this line ... ### $line # no final : on the filename:linenum so it's disguised from Emacs # compilation-mode my $location = gp_quote("$filename:$within_linenum"); $output_test->("$line;\n", "gp_inline__check($location, gp_inline__test())\n"); $within = ''; } next; } if (! $within && $prev_type eq 'not-gp-inline') { # location string creation obscured against Emacs compilation-mode # taking it to be many locations to mark etc $output->("\ngp_inline__location=", gp_quote("$filename:$linenum"), ";\n"); } if ($within) { print STDERR "$filename:$linenum: still within $within from line $within_linenum\n"; $exit = 1; } if ($type eq 'DEFINE') { $output->($line,"\n"); } elsif ($type eq 'INLINE') { $output_test->($line,"\n"); } elsif ($type eq 'CONSTANT') { if ($line =~ /^\s*$/) { print STDERR "$filename:$linenum: missing name for CONSTANT\n"; $exit = 1; } $output->("$line = {"); $join = "\n"; $end = "};\n"; $within = 'CONSTANT'; $within_linenum = $linenum; $within_str = ''; } elsif ($type eq 'VECTOR') { if ($line =~ /^\s*$/) { print STDERR "$filename:$linenum: missing name for VECTOR\n"; $exit = 1; } $output->("$line = {["); $join = "\n"; $end = "]};\n"; $within = 'VECTOR'; $within_linenum = $linenum; $within_str = ''; } elsif ($type eq 'MATRIX') { if ($line =~ /^\s*$/) { print STDERR "$filename:$linenum: missing name for MATRIX\n"; $exit = 1; } $output->("$line = {["); $join = "\n"; $end = "]};\n"; $within = 'MATRIX'; $within_linenum = $linenum; $within_str = ''; } else { print STDERR "$filename:$linenum: ignoring unrecognised \"$type\"\n"; } $prev_type = $type; } elsif ($within eq 'CONSTANT' || $within eq 'VECTOR' || $within eq 'MATRIX') { $within_str .= $line; # $line =~ s/(^|[^\\])(\\\\)*%.*//; # % comments # $line =~ s/\\[,;]/ /g; # ignore \, or \; spacing # $line =~ s/\\(phantom|hspace){[^}]*}/ /g; # ignore TeX \phantom{...} # $line =~ s/\\(kern)-?[0-9.]+[a-z]+/ /g; # ignore TeX \kern... # $line =~ s/\{([+-])\}/$1/g; # {+} or {-} # $line =~ s/&/,/g; # & as field separator # $line =~ s|\\[td]?frac(\d)(\d)|($1)/($2)|g; # \frac23 # $line =~ s|\\[td]?frac\{([^}]*)}\{([^}]*)}|($1)/($2)|g; # \frac{}{} # $line =~ s/\\(sqrt\d+)\s*(i?)/$1$2/g; # \sqrt2 or \sqrt3 i # $line =~ s/([0-9.)]+)[ \t]*i/$1*I/g; # complex number 123 i # $line =~ s/\bi[ \t]*([0-9.]+)/I*$1/g; # complex number i 123 # $line =~ s/([+-])[ \t]*(I)\b/$1$2/g; # complex number +- i 123 # $line =~ s/\bi\b/I/g; # complex number i -> I # if ($within eq 'MATRIX') { # $line =~ s/\\\\/;/g; # row separator \\ # } else { # $line =~ s/;/,/g; # semi as separator # } # $line =~ s|[^-+*/^()0-9.I,; \t]||sg; # strip anything else # $line =~ s/(^|;)(\s*,)+/$1/sg; # strip leading commas # $line =~ s/,(\s*,)+/,/sg; # strip duplicated commas # $line =~ s/,[ \t]*$//; # strip trailing commas # # print "\\ ",$line,"\n"; # $line =~ s/[ \t]*$//; # strip trailing whitespace # $line = decimals_to_fractions($line); # if ($line ne '') { # $output->($join,$line,"\n"); # $join = ($line =~ /;$/ ? "\n" : ",\n"); # } next; } else { ### non test line ... $prev_type = 'not-gp-inline'; } } ### EOF ... if ($within) { print STDERR "$filename:$linenum: end of file within \"$within\"\n"; $exit = 1; } } $test_last = 0; $output_fh->flush; $test_last_fh->flush; File::Copy::copy($test_last_fh->filename, $output_fh) or die "Error copying Test-Last: $!"; $output_test->(<<'HERE'); print("Total ",(gp_inline__good+gp_inline__bad)," tests, "gp_inline__good" good, "gp_inline__bad" bad"); if(gp_inline__bad,quit(1)) HERE if ($action eq 'run') { $runner_tempfh = File::Temp->new (TEMPLATE => 'gp-inline-XXXXXX', SUFFIX => '.gp', TMPDIR => 1); my $read_filename = gp_quote($output_fh->filename); print $runner_tempfh <<"HERE"; { read($read_filename); } HERE # iferr(read($read_filename),err, # print("rethrow"); # error(err), /* rethrow */ # 0); # /* print(gp_inline__location,"error reading"); 0 */ $output_fh->flush; my @command = ('gp', '--quiet', '-f', # "fast" do not read .gprc (defined $stacksize ? ('-s', $stacksize) : ()), '--default', 'recover=0', # $runner_tempfh->filename, $output_fh->filename); if ($verbose) { print join(' ',@command),"\n"; } if (! IPC::Run::run(\@command, '<', File::Spec->devnull)) { $exit = 1; } } } # Return $str as a string "$str" for use in a gp script. # Any " quotes etc in $str are suitably escaped. sub gp_quote { my ($str) = @_; $str =~ s/\"/\\"/g; return '"'.$str.'"'; } sub test_file { my ($filename) = @_; ### test_file(): $filename $total_files++; open my $fh, '<', $filename or die "Cannot open $filename: $!"; test_fh($fh, $filename); close $fh or die "Error closing $filename: $!"; } sub test_files { # ($filename, ...) foreach my $filename (@_) { test_file($filename); } } #------------------------------------------------------------------------------ # mainline { my $help = sub { print "gp-inline [--options] filename...\n"; my @opts = (['-h, --help', 'Print this help'], ['-v, --version', 'Print program version'], ['--verbose', 'Print extra messages'], ['--run', 'Run the inline tests in each FILENAME'], ['--extract', 'Print the test code from each FILENAME'], ['--defines', 'Print just the definitions from each FILENAME'], ); my $width = 2 + max (map { length ($_->[0]) } @opts); foreach (@opts) { printf "%-*s%s\n", $width, $_->[0], $_->[1]; } print "\n"; exit 0; }; GetOptions ('help|?' => $help, version => sub { print "$FindBin::Script version $VERSION\n"; exit 0; }, run => sub { $action = 'run' }, defines => sub { $action = 'defines' }, extract => sub { $action = 'extract' }, 'gp=s' => \$gp, stdin => \$stdin, verbose => \$verbose, 's=i' => \$stacksize, ) or exit 1; ($stdin || @ARGV) or $help->(); } if ($stdin) { test_fh(\*STDIN, '(stdin)'); } test_files(@ARGV); exit $exit; #------------------------------------------------------------------------------ __END__ # } elsif ($arg eq '-dist') { # $exit = 1; # require ExtUtils::Manifest; # my $href = ExtUtils::Manifest::maniread(); # my @filenames = grep m{^lib/.*\.pm$|^[^/]\.pm$}, keys %$href; # $good &= $class->test_files(@filenames); # # if ($exit) { # # $class->diag ("gp-inline total $total_expressions checks in $total_files files"); # # exit($good ? 0 : 1); # # } # # sub diag { # my $self = shift; # if (eval { Test::More->can('diag') }) { # Test::More::diag (@_); # } else { # my $msg = join('', map {defined($_)?$_:'[undef]'} @_)."\n"; # # $msg =~ s/^/# /mg; # print STDERR $msg; # } # } =for stopwords gp Ryde globals backslashing backtrace multi-file multi-line =head1 NAME gp-inline -- run Pari/GP code inline in a document =head1 SYNOPSIS gp-inline [--options] filename... =head1 DESCRIPTION C extracts and executes Pari/GP code from comments written inline in a document such as TeX or POD, or even in some C code or similar. This can be used to check calculations or formulas alongside their statement in a document. For example in TeX Blah blah and from which it is seen that $1+1 = 2$. % GP-Test 1+1 == 2 which is checked by running gp-inline foo.tex GP is a mathematical system and these checks will usually be for mathematical calculations and formulas, but can be useful even for just basic arithmetic. =head2 Test A C line must evaluate to 0 or 1. The evaluation is inside a function body so semicolons can separate multiple expressions and the last is the result. % GP-Test my(n=5); 2*n^2 + n == 55 Requiring a result 0 or 1 helps avoid mistakes like forgetting "== 123" etc. The suggestion is not to end C with a semicolon so that it can be pasted into GP to see the result when experimenting, but C works with or without. The suggestion is also to keep variables local with C to avoid one test depending on another accidentally, but that's not enforced. See L below for making global variables. Multi-line tests can be written with GP style backslashing % GP-Test some_thing() \ % GP-Test == 123 Comments can be included in a test in GP C style. Don't use C<\\> style as the expressions C constructs don't work properly with that yet. % GP-Test 105 == 3*5*7 /* its prime factors */ Tests are run with C so any F<~/.gprc> or C<$GPRC> file is not evaluated. This is designed to give consistent test results without personal preferences wanted for C interactively etc. =head2 Prefix The following prefixes are recognised for a C line (etc) GP-Test 1+1==2 # GP-Test 1+1==2 % GP-Test 1+1==2 /* GP-Test 1+1==2 */ * GP-Test 1+1==2 // GP-Test 1+1==2 =for GP-Test 1+1==2 These are comments in Perl, TeX, C, C++, and Perl POD directive C<=for>. In C style C an optional trailing C<*/> is stripped. Or its comment parts can be on separate lines if desired /* GP-Test 1+1==2 */ /* * GP-Test 1+1==2 */ A Perl POD C<=for> should be a single line and will usually want a blank line before and after to be valid POD. Those blanks can be a tedious for many tests and in that case the suggestion is to C<=cut> and write a block of tests =cut # GP-Test 2+2==4 # GP-Test 4+4==8 =pod The C<#> prefix here is not needed if already after an C<__END__> so not evaluated by Perl, but it's a good way for human readers to distinguish those lines from the POD text. =head2 Definitions Definition lines can create new GP functions or globals % GP-DEFINE my_func(n) = 2*n + 3; % GP-DEFINE my_vector = [ 1, 2, 3, 5 ]; These lines are arbitrary code passed directly to GP. Generally they should end with a C<;> to suppress result printing in the usual way, but that's not enforced. Multi-line functions or expressions can use either backslashing or braces % GP-DEFINE long_func(n) = \ % GP-DEFINE some + long \ % GP-DEFINE - expression; % GP-DEFINE my_matrix = {[ % GP-DEFINE 1, 2; % GP-DEFINE 2, 1 % GP-DEFINE ]}; Definition lines can also make initial settings. For example C is a good way to guard against mistakes in function arguments (assuming you're not deliberately lazy with such things) % GP-DEFINE default(strictargs,1); External GP code modules can be included with the usual C. Normally this will be in a C. % GP-DEFINE read("my-library.gp"); =head2 Test Last C tests are run last, after the rest of the input file. This lets a test precede a formula or data definition it depends on. This doesn't happen often, usually only when some a document gives examples of a formula before the full statement. If you keep the function definition with the statement of the formula then C allows tests to be written before. We will want f(6)=10 and ... % GP-Test-Last f(6) == 10 The unique function satisfying is then f(n) = 2n - 2. % GP-DEFINE f(n) = 2*n - 2; Care should be taken not to redefine globals which C tests will use. But it's wise anyway not to change the meaning of globals through a document so that rearranging sections etc doesn't upset the checks. =head2 Errors Syntax errors and type errors in tests and definitions are fatal. The current implementation runs C so such problems cause a non-zero exit code. A location string is included in the test expression so the backtrace has something like *** at top-level: ...inline("foo.tex:153",(()->bar())()) ... which means input file F line 153 was the offending C. Errors in C statements don't have this location in the backtrace (since they're a "top-level" evaluation). If the offending part is not obvious then try C to see a C<\e> trace of each expression. It includes some C<"foo.tex:150"> etc strings which are the source locations. (This locations printing is not very good. An equivalent of C<#line> would help. Or is there a way to insert a print before an error backtrace? An C trap loses the backtrace.) =head2 Constants, Vectors and Matrices Numbers in the document text can be extracted as GP definitions. For example a constant C, % GP-CONSTANT foo 123 % GP-END Or a vector C, % GP-VECTOR bar 1, 2, 3 % GP-END Or a matrix C, % GP-MATRIX quux 1 & 2 \\ 3 & 4 % GP-END These GP definitions can be used in subsequent tests, and the numbers are also document text or program code, etc. The number forms accepted are 123 integer {-}1 signs, optionally with TeX {} 1.42 decimal fraction \frac58 TeX \frac, \tfrac, \dfrac \tfrac{12}{34} -3-4i complex number, lower case i \tfrac{5}{2+i} fractions with complex numbers , & vector separator commas \\ matrix row separator Multiple commas etc are treated as just one. The matrix separator C<\\> is treated as comma in a C. There should be just one value in a C but leading or trailing commas are ignored. Decimal fractions C<12.45> become rationals like C<1245/100> to preserve an exact value. If it's some irrational which has been truncated then staying it exact lets you make an exact check of all the decimals given. The number syntax accepted is quite strict. This is designed to ensure C doesn't quietly ignore something which it wasn't supposed to. Various bits of TeX are ignored. These are things often wanted in a list of numbers. However in general it's best to confine C etc to just the numbers and keep TeX outside. = initial = sign &= initial TeX align and = sign \, \; \quad \qquad various TeX spacing and macros \kern1.5em measures em,ex,pt,mm,cm,in \hspace{5pt} \phantom{...} \degree \dotsc \dotsb C<\kern> should be a single numbered measure C, C. Don't use a comma for the decimal, and don't use C etc calculations. C<\phantom{}> cannot contain nested C<{ }> braces (though it can contain equivalent C<\begingroup> and C<\endgroup> if desired). Comments, both TeX or other styles, cannot be in a list of numbers. Perhaps this will change. =head1 Other Notes When including numbers in a document there's a bit of a choice between writing them in the document and applying checks, versus generating the numbers externally and C<#include> (or equivalent) to bring them in. The latter has the disadvantage of several little files (probably), the former is a little tedious to write manually but then isn't vulnerable to breakage in a generator program. Various arithmetic programs or computer algebra systems could be used in a similar way to C. GP has the attraction of a compact syntax for calculations and new functions, and having a range of arbitrary precision basic types such as fractions, complex numbers, polynomials, even quads for exact square roots, plus a lot of number theory things for higher mathematics. =head1 OPTIONS The command line options are =over 4 =item C<--run> Run the inline tests in each given file. This is the default action. =item C<--stdin> Read a document from standard input (instead of named files). =item C<--extract> Extract the inline C code from each file and print to standard output. This output is what C<--run> would run with C. Usually C<--extract> should be used on just one input file, otherwise the tests of each file are output one after the other and globals left by the first might upset later tests. =item C<--defines> Extract just the definitions from the given files and print to standard output. This is good for extracting definitions so they can be used separately in further calculations or experiments. It's also possible to go the other way, have definitions in a separate file which the document loads with C. Usually it avoids mistakes to keep a definition with the formula etc in the document. But generic or very large code could be kept separate. =item C<--help> Print a brief help message. =item C<--version> Print the program version number and exit. =back =head1 EXIT CODE C exits with 0 if all tests in all files are successful, or non-zero if any problems. =head1 BUGS There's no support for a multi-file document where defines would be carried over from one part to the next. The suggestion is either to C them together and pass a single file, possibly to C; or use C<--defines> to get the definitions from one file and do a C of them in the next. The latter is good to get the defines out of a main document which can then be read into a separate work-in-progress document which has text and tests destined for the main, when ready. The C and C number syntax is not enough for all purposes. There will always be some layout which is too specific for C to recognise. The suggestion in that case is to write C beside the relevant values. The duplication is not nice, but done once and with the define right beside the numbers it's not too bad. Some sort of C to pick a polynomial out some TeX could be good. It could include polynomial fractions (C) since they're a native type in GP and suit generating functions. =head1 SEE ALSO L =head1 HOME PAGE L =head1 LICENSE Copyright 2015 Kevin Ryde gp-inline is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. gp-inline is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with gp-inline. If not, see . =cut # Maybe: # GP-Define foo(x) = x+1; # GP-Test foo(2) == 3 # GP-Inline for(i=1,10, foo(i)==i+1 # GP-Vector # GP-End # GP-Constant # GP-End # GP-Matrix # GP-End # # GP-Inline check(bool) # ... names that won't clash # # GP-Define all defines at start then all GP-Inline and GP-Test ? Math-PlanePath-122/xtools/my-kwalitee.sh0000755000175000017500000000214611775434756016044 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-122/xtools/my-pc.sh0000755000175000017500000000323312206324154014613 0ustar gggg#!/bin/sh # my-pc.sh -- run cpants_lint kwalitee checker # Copyright 2009, 2010, 2011, 2012, 2013 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-122/xtools/my-deb.sh0000755000175000017500000000720612633077111014751 0ustar gggg#!/bin/sh # my-deb.sh -- make .deb # Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015 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 VERSION=`sed -n 's/^VERSION = \(.*\)/\1/p' Makefile` if test -z "$VERSION"; then echo "VERSION not found" exit 1 fi 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/"` XS_FILES=`sed -n 's/^XS_FILES = \(.*\)/\1/p' Makefile` EXE_FILES=`sed -n 's/^EXE_FILES = \(.*\)/\1/p' Makefile` if test -z "$XS_FILES" then DPKG_ARCH=all else DPKG_ARCH=`dpkg --print-architecture` fi # 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-//'` case "$EXE_FILES" in gtk2-ex-splash|wx-perl-podbrowser|'') DEBNAME="lib${DEBNAME}-perl" ;; esac DEBVNAME="${DEBNAME}_$VERSION-0.1" DEBFILE="${DEBVNAME}_$DPKG_ARCH.deb" # 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 dpkg-checkbuilddeps debian/control fakeroot debian/rules binary cd .. rm -rf $DISTVNAME #------------------------------------------------------------------------------ # lintian .deb and source lintian -I -i \ --suppress-tags new-package-should-close-itp-bug,desktop-entry-contains-encoding-key \ $DEBFILE TEMP="/tmp/temp-lintian-$DISTVNAME" rm -rf $TEMP mkdir $TEMP cp $DISTVNAME.tar.gz $TEMP/${DEBNAME}_$VERSION.orig.tar.gz cd $TEMP 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; \ lintian -I -i \ --suppress-tags maintainer-upload-has-incorrect-version-number,changelog-should-mention-nmu,empty-debian-diff,debian-rules-uses-deprecated-makefile *.dsc cd / rm -rf $TEMP exit 0 Math-PlanePath-122/xtools/my-manifest.sh0000755000175000017500000000165211764227757016045 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-122/xtools/my-check-copyright-years.sh0000755000175000017500000000465012635703146020431 0ustar gggg#!/bin/sh # my-check-copyright-years.sh -- check copyright years in dist # Copyright 2009, 2010, 2011, 2012, 2013, 2014, 2015 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 \ | tools/configurations-gfs-generated.gp \ | devel/configurations-t-generated.gp \ | */_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-122/xt/0002755000175000017500000000000012641645163012344 5ustar ggggMath-PlanePath-122/xt/0-Test-ConsistentVersion.t0000644000175000017500000000225311655356324017304 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-122/xt/0-Test-Synopsis.t0000755000175000017500000000176411655356314015444 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-122/xt/oeis-duplicate.t0000644000175000017500000000317112136177165015441 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 . # 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(); } # uncomment this to run the ### lines #use Devel::Comments '###'; 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-122/xt/0-no-debug-left-on.t0000755000175000017500000000651512044143060015722 0ustar gggg#!/usr/bin/perl -w # 0-no-debug-left-on.t -- check no Smart::Comments left on # Copyright 2011, 2012 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)/ || /^[ \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 () { if (/^#\s*define\s+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-122/xt/bigrat.t0000644000175000017500000005354612563052643014012 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 . # Crib notes: # # In perl 5.8.4 "BigInt != BigRat" doesn't work, must have it other way # around as "BigRat != BigInt". 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 => 471)[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 #------------------------------------------------------------------------------ # 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); #------------------------------------------------------------------------------ # 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); 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"); } } #------------------------------------------------------------------------------ # 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); } } #------------------------------------------------------------------------------ # Diagonals { require Math::PlanePath::Diagonals; my $path = Math::PlanePath::Diagonals->new; { my $x = Math::BigRat->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, 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); } } #------------------------------------------------------------------------------ # 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 = ( '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', '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', '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 'Corner', 'PyramidSides', '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-122/xt/pod-lists.t0000644000175000017500000001555712344544606014462 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-122/xt/HIndexing-more.t0000644000175000017500000000602112377314541015342 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 Math::PlanePath::HIndexing; use Test; plan tests => 35; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/0-file-is-part-of.t0000644000175000017500000000622212536755447015576 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-122/xt/oeis/0002755000175000017500000000000012641645163013303 5ustar ggggMath-PlanePath-122/xt/oeis/Corner-oeis.t0000644000175000017500000001525012301301163015634 0ustar gggg#!/usr/bin/perl -w # 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 . use 5.004; use strict; use Test; plan tests => 7; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::Corner; # uncomment this to run the ### lines #use Smart::Comments; #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # A000290 -- N on X axis, perfect squares starting from 1 MyOEIS::compare_values (anum => 'A000290', func => sub { my ($count) = @_; my @got = (0); my $path = Math::PlanePath::Corner->new; for (my $x = 0; @got < $count; $x++) { push @got, $path->xy_to_n ($x, 0); } 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; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/CornerReplicate-oeis.t0000644000175000017500000000541712460527060017505 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 Test; plan tests => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::CornerReplicate; use Math::PlanePath::Base::Digits 'bit_split_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments '###'; 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-122/xt/oeis/DigitGroups-oeis.t0000644000175000017500000000552512141661307016663 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 => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::DigitGroups; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/ComplexMinus-oeis.t0000644000175000017500000000720612240240414017034 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 => 5; use Math::BigInt try => 'GMP'; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines #use Smart::Comments '###'; use Math::PlanePath::ComplexMinus; my $path = Math::PlanePath::ComplexMinus->new; #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # 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; } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/WythoffPreliminaryTriangle-oeis.t0000644000175000017500000000477312112610147021751 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-122/xt/oeis/UlamWarburtonQuarter-oeis.t0000644000175000017500000000405012376763750020600 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 Test; plan tests => 6; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::UlamWarburtonQuarter; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/AlternatePaperMidpoint-oeis.t0000644000175000017500000000314712563464110021035 0ustar gggg#!/usr/bin/perl -w # Copyright 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::PlanePath::AlternatePaperMidpoint; use Test; plan tests => 11; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # A016116 -- X/2 at N=2^k, starting k=1, being 2^floor(k/2) require Math::NumSeq::PlanePathN; my $bigclass = Math::NumSeq::PlanePathN::_bigint(); MyOEIS::compare_values (anum => 'A016116', max_count => 1000, func => sub { my ($count) = @_; my $path = Math::PlanePath::AlternatePaperMidpoint->new; my @got; for (my $n = $bigclass->new(2); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); push @got, $x/2; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/HilbertSides-oeis.t0000644000175000017500000000333712562474627017021 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; plan tests => 47; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::HilbertSides; # uncomment this to run the ### lines #use Smart::Comments '###'; my $path = Math::PlanePath::HilbertSides->new; #------------------------------------------------------------------------------ # 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-122/xt/oeis/KochSnowflakes-oeis.t0000644000175000017500000000504512253200234017331 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-122/xt/oeis/UlamWarburton-oeis.t0000644000175000017500000001216212563473016017226 0ustar gggg#!/usr/bin/perl -w # 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 . use 5.004; use strict; use Test; plan tests => 7; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines #use Devel::Comments '###'; use Math::PlanePath::UlamWarburton; my $path = Math::PlanePath::UlamWarburton->new; #------------------------------------------------------------------------------ # 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 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 $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 @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; }); #------------------------------------------------------------------------------ # A147582 - count new cells in each level MyOEIS::compare_values (anum => 'A147582', func => sub { my ($count) = @_; my @got; my $prev = $path->tree_depth_to_n(0); for (my $depth = 1; @got < $count; $depth++) { my $n = $path->tree_depth_to_n($depth); push @got, $n - $prev; $prev = $n; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/KnightSpiral-oeis.t0000644000175000017500000000735012136177300017017 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 => 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; #------------------------------------------------------------------------------ # 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-122/xt/oeis/DragonCurve-oeis.t0000644000175000017500000004174612465550055016655 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 Test; plan tests => 23; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::DragonCurve; # uncomment this to run the ### lines #use Smart::Comments '###'; my $dragon = Math::PlanePath::DragonCurve->new; #------------------------------------------------------------------------------ # A099545 -- relative direction 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 => 'Right'); while (@got < $count) { my ($i, $value) = $seq->next; push @got, $value ? 3 : 1; } return \@got; }); #------------------------------------------------------------------------------ # A003476 Daykin and Tucker alpha[n] # = squares on right boundary, OFFSET=1 values 1, 2, 3, 5 # = 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 power something is 1=left,-1=right, extra initial 1 # A088585 # A088575 # 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; }); #------------------------------------------------------------------------------ # A088748 - dragon cumulative turn +/-1 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 => 'Left'); my $cumulative = 1; while (@got < $count) { push @got, $cumulative; my ($i, $value) = $seq->next; if ($value) { $cumulative += 1; # left } else { $cumulative -= 1; # right } } return \@got; }); #------------------------------------------------------------------------------ # A014710 -- relative direction 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 -- relative direction 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 -- relative direction 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 -- relative direction 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 ($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 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 ($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 MyOEIS::compare_values (anum => 'A003460', func => sub { my ($count) = @_; my @got; require Math::BigInt; 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; }); #------------------------------------------------------------------------------ # A164910 - dragon cumulative turn +/-1, partial sums of that cumulative 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 => 'Left'); my $cumulative = 1; my $partial_sum = $cumulative; while (@got < $count) { push @got, $partial_sum; my ($i, $value) = $seq->next; if ($value) { $cumulative += 1; } else { $cumulative -= 1; } $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 => 'Left'); my $cumulative = 0; while (@got < $count) { push @got, $cumulative; my ($i, $value) = $seq->next; if ($value) { $cumulative += 1; } else { $cumulative -= 1; } } 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; }); #------------------------------------------------------------------------------ # A126937 -- points numbered as 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; }); #------------------------------------------------------------------------------ # Ba2 boundary of arms=2 around whole of level k # * # | # 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 < 5; $k++) { my $len = MyOEIS::path_boundary_length ($path, 2*2**$k + 1); my $diff = $len - $prev; push @got, $prev; $prev = $len; } 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-122/xt/oeis/CellularRule190-oeis.t0000644000175000017500000000617212136177302017250 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 => 4; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::CellularRule190; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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) = @_; require Math::BigInt; 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-122/xt/oeis/LTiling-oeis.t0000644000175000017500000000510312563472057015767 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 Test; plan tests => 1; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::LTiling; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/Flowsnake-oeis.t0000644000175000017500000000400112561502027016337 0ustar gggg#!/usr/bin/perl -w # Copyright 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 Test; plan tests => 11; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::Flowsnake; # uncomment this to run the ### lines #use Smart::Comments '###'; my $path = Math::PlanePath::Flowsnake->new; #------------------------------------------------------------------------------ # 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-122/xt/oeis/NumSeq-PlanePath-oeis.t0000644000175000017500000002750012564212205017500 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 . # Check PlanePathCoord etc sequences against OEIS data. # use 5.004; use strict; 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 =~ /A246960/; # return 0 unless $anum =~ /A151922|A183060/; # return 0 unless $anum =~ /A177702|A102283|A131756/; return 1; } sub want_planepath { my ($planepath) = @_; return 0 unless $planepath =~ /Gray/; # return 0 unless $planepath =~ /Octag|Pent|Hept/; # return 0 unless $planepath =~ /Divis|DiagonalRationals|CoprimeCol/; # return 0 unless $planepath =~ /DiamondSpiral/; # return 0 unless $planepath =~ /Coprime/; # return 0 unless $planepath =~ /LCorn|RationalsTree/; # return 0 unless $planepath =~ /^Corner$/i; # return 0 unless $planepath =~ /SierpinskiArrowheadC/; # return 0 unless $planepath =~ /TriangleSpiralSkewed/; # return 0 unless $planepath =~ /^Rows/; # return 0 unless $planepath =~ /DiagonalRationals/; # return 0 unless $planepath =~ /Hilbert/; return 1; } sub want_coordinate { my ($type) = @_; return 0 unless $type =~ /BitXor/; # return 0 unless $type =~ /^Abs[XY]/; # return 0 unless $type =~ /DiffYX/i; # return 0 unless $type =~ /^Depth/; # 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; require 'lib/Math/NumSeq/OEIS/Catalogue/Plugin/TempOther.pm'; unlink 'lib/Math/NumSeq/OEIS/Catalogue/Plugin/TempOther.pm' or die; 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-122/xt/oeis/SierpinskiArrowhead-oeis.t0000644000175000017500000000577112136177277020417 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; plan tests => 14; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::SierpinskiCurve; use Math::NumSeq::PlanePathTurn; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/DiagonalsAlternating-oeis.t0000644000175000017500000001124212563466035020517 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 . use 5.004; use strict; use Test; plan tests => 8; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::DiagonalsAlternating; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/DiagonalsOctant-oeis.t0000644000175000017500000002042112400213365017460 0ustar gggg#!/usr/bin/perl -w # 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 . use 5.004; use strict; use Math::BigInt; use Math::PlanePath::DiagonalsOctant; 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 '###'; #------------------------------------------------------------------------------ # 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; require Math::PlanePath::PyramidRows; require Math::BigInt; 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; require Math::PlanePath::PyramidRows; 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; require Math::PlanePath::PyramidRows; 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; require Math::PlanePath::PyramidRows; 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; require Math::PlanePath::PyramidRows; 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; require Math::PlanePath::PyramidRows; 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; require Math::PlanePath::PyramidRows; 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; require Math::PlanePath::PyramidRows; 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-122/xt/oeis/ImaginaryBase-oeis.t0000644000175000017500000001010512206030357017121 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 => 4; 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'; # uncomment this to run the ### lines # use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/DiagonalRationals-oeis.t0000644000175000017500000001026112136177302020010 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; plan tests => 5; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::DiagonalRationals; 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-122/xt/oeis/TheodorusSpiral-oeis.t0000644000175000017500000000621112136177277017557 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 => 8; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::TheodorusSpiral; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/CellularRule54-oeis.t0000644000175000017500000000406112611251201017147 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 . use 5.004; use strict; use Test; plan tests => 2; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines #use Devel::Comments '###'; 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; 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; }); #------------------------------------------------------------------------------ # 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-122/xt/oeis/RationalsTree-oeis.t0000644000175000017500000007452112337766115017213 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 . # cf A152975/A152976 redundant Stern-Brocot # inserting mediants to make ternary tree use 5.004; use strict; use Test; plan tests => 49; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::RationalsTree; # 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); } } #------------------------------------------------------------------------------ # 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; require Math::BigInt; 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 require Math::BigInt; 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; require Math::BigInt; 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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # 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 require Math::BigInt; 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-122/xt/oeis/HeptSpiralSkewed-oeis.t0000644000175000017500000000273612563467041017651 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 . use 5.004; use strict; use Test; plan tests => 4; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use List::Util 'min', 'max'; use Math::PlanePath::HeptSpiralSkewed; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/CfracDigits-oeis.t0000644000175000017500000000626512136177302016610 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; 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'; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/OctagramSpiral-oeis.t0000644000175000017500000000267212164405074017335 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 => 7; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::OctagramSpiral; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/SquareSpiral-oeis.t0000644000175000017500000012005612301301565017026 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 . # 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. # A217014 Permutation of natural numbers arising from applying the walk of a square spiral (e.g. A214526) to the data of triangular horizontal-last spiral (defined in A214226). # A217015 Permutation of natural numbers arising from applying the walk of a square spiral (e.g. A214526) to the data of rotated-square spiral (defined in A215468). # 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. # A113688 Isolated semiprimes in the semiprime spiral. # A113689 Number of semiprimes in clumps of size >1 through n^2 in the semiprime spiral. # A114254 Sum of all terms on the two principal diagonals of a 2n+1 X 2n+1 square spiral. use 5.004; use strict; use Test; plan tests => 64; 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 $path = Math::PlanePath::SquareSpiral->new; # 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 } #------------------------------------------------------------------------------ # A059924 Write the numbers from 1 to n^2 in a spiraling square; a(n) is the # total of the sums of the two diagonals. MyOEIS::compare_values (anum => 'A059924', max_count => 1000, func => sub { my ($count) = @_; my @got = (0); for (my $n = 1; @got < $count; $n++) { push @got, my_A059924($n); } return \@got; }); BEGIN { my $path = Math::PlanePath::SquareSpiral->new; # A059924 spirals inwards, use $square+1 - $t to reverse the path numbering sub my_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 $path = Math::PlanePath::SquareSpiral->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::SquareSpiral->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; }); #------------------------------------------------------------------------------ # A094768 -- cumulative spiro-fibonacci total of 4 neighbours { my @surround4_dx = (1, 0, -1, 0); my @surround4_dy = (0, 1, 0, -1); MyOEIS::compare_values (anum => q{A094768}, func => sub { my ($count) = @_; my $path = Math::PlanePath::SquareSpiral->new (n_start => 0); require Math::BigInt; 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 .. $#surround4_dx) { my $sn = $path->xy_to_n ($x+$surround4_dx[$i], $y+$surround4_dy[$i]); if ($sn < $n) { $total += $got[$sn]; } } $got[$n] = $total; } return \@got; }); } #------------------------------------------------------------------------------ # A094767 -- cumulative spiro-fibonacci total of 8 neighbours my @surround8_dx = (1, 1, 0, -1, -1, -1, 0, 1); my @surround8_dy = (0, 1, 1, 1, 0, -1, -1, -1); MyOEIS::compare_values (anum => q{A094767}, func => sub { my ($count) = @_; my $path = Math::PlanePath::SquareSpiral->new (n_start => 0); require Math::BigInt; 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 .. $#surround8_dx) { my $sn = $path->xy_to_n ($x+$surround8_dx[$i], $y+$surround8_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 = Math::PlanePath::SquareSpiral->new (n_start => 0); require Math::BigInt; 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 .. $#surround8_dx) { my $sn = $path->xy_to_n ($x+$surround8_dx[$i], $y+$surround8_dy[$i]); if ($sn < $n) { $total += $got[$sn]; } } $got[$n] = $total; } return \@got; }); #------------------------------------------------------------------------------ # A136626 -- count surrounding primes MyOEIS::compare_values (anum => q{A136626}, fixup => sub { my ($bvalues) = @_; $bvalues->[31] = 3; # DODGY-DATA: 3 primes 13,31,59 surrounding 32 }, 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); push @got, ((!! 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))) ); } return \@got; }); # A136627 -- count self and surrounding primes MyOEIS::compare_values (anum => q{A136627}, fixup => sub { my ($bvalues) = @_; $bvalues->[31] = 3; # DODGY-DATA: 3 primes 13,31,59 surrounding 32 }, 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); push @got, (Math::Prime::XS::is_prime($n) + (!! 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))) ); } return \@got; }); #------------------------------------------------------------------------------ # A078784 -- primes on any axis positive or negative MyOEIS::compare_values (anum => 'A078784', func => sub { my ($count) = @_; require Math::Prime::XS; my @got; for (my $n = $path->n_start; @got < $count; $n++) { next unless Math::Prime::XS::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 => 'A078765', func => sub { my ($count) = @_; require Math::Prime::XS; my @got; for (my $n = $path->n_start; @got < $count; $n++) { next unless Math::Prime::XS::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 -- 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 MyOEIS::compare_values (anum => 'A113688', func => sub { my ($count) = @_; my @got; require Math::NumSeq::AlmostPrimes; my $seq = Math::NumSeq::AlmostPrimes->new; for (my $n = $path->n_start; @got < $count; $n++) { next unless $seq->pred($n); my ($x,$y) = $path->n_to_xy ($n); if (! $seq->pred ($path->xy_to_n($x+1,$y)) && ! $seq->pred ($path->xy_to_n($x-1,$y)) && ! $seq->pred ($path->xy_to_n($x,$y+1)) && ! $seq->pred ($path->xy_to_n($x,$y-1)) && ! $seq->pred ($path->xy_to_n($x+1,$y+1)) && ! $seq->pred ($path->xy_to_n($x-1,$y-1)) && ! $seq->pred ($path->xy_to_n($x-1,$y+1)) && ! $seq->pred ($path->xy_to_n($x+1,$y-1)) ) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A215470 -- primes with >=4 prime neighbours in 8 surround MyOEIS::compare_values (anum => 'A215470', func => sub { my ($count) = @_; require Math::Prime::XS; my @got; for (my $n = $path->n_start; @got < $count; $n++) { next unless Math::Prime::XS::is_prime($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 >= 4) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A033638 -- N positions of the turns MyOEIS::compare_values (anum => 'A033638', max_value => 100_000, func => sub { my ($count) = @_; my @got; push @got, 1,1; require Math::NumSeq::PlanePathTurn; 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; }); # A172979 -- N positions of the turns which are also primes MyOEIS::compare_values (anum => 'A172979', func => sub { my ($count) = @_; my @got; require Math::NumSeq::PlanePathTurn; require Math::Prime::XS; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'SquareSpiral', turn_type => 'LSR'); while (@got < $count) { my ($i,$value) = $seq->next; if ($value != 0 && Math::Prime::XS::is_prime($i)) { push @got, $i; } } 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; }); 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) = @_; require Math::NumSeq::PlanePathTurn; 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; }); #------------------------------------------------------------------------------ # A141481 -- values as sum of eight surrounding MyOEIS::compare_values (anum => q{A141481}, func => sub { my ($count) = @_; require Math::BigInt; my $path = Math::PlanePath::SquareSpiral->new (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 $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), $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)) { if ($sn < $n) { $sum += $got[$sn]; # @got is 0-based } } push @got, $sum; } return \@got; }); #------------------------------------------------------------------------------ # A156859 Y axis positive and negative MyOEIS::compare_values (anum => 'A156859', func => sub { my ($count) = @_; my $path = Math::PlanePath::SquareSpiral->new (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; }); #------------------------------------------------------------------------------ # A172294 -- jewels, composite surrounded by 4 primes, starting N=0 MyOEIS::compare_values (anum => 'A172294', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::SquareSpiral->new (n_start => 0); require Math::Prime::XS; for (my $n = $path->n_start; @got < $count; $n++) { next if Math::Prime::XS::is_prime($n); my ($x,$y) = $path->n_to_xy ($n); if (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)) ) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A115258 -- isolated primes MyOEIS::compare_values (anum => 'A115258', func => sub { my ($count) = @_; my @got; require Math::Prime::XS; for (my $n = $path->n_start; @got < $count; $n++) { next unless Math::Prime::XS::is_prime($n); my ($x,$y) = $path->n_to_xy ($n); if (! 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)) ) { push @got, $n; } } return \@got; }); #------------------------------------------------------------------------------ # A214177 -- sum of 4 neighbours MyOEIS::compare_values (anum => 'A214177', 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 => 'A214176', 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; require Math::Prime::XS; for (my $n = $path->n_start; @got < $count; $n++) { next unless Math::Prime::XS::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; require Math::Prime::XS; for (my $n = $path->n_start; @got < $count; $n++) { next unless Math::Prime::XS::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; require Math::Prime::XS; for (my $n = $path->n_start; @got < $count; $n++) { next unless Math::Prime::XS::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; require Math::Prime::XS; for (my $n = $path->n_start; @got < $count; $n++) { next unless Math::Prime::XS::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; }); #------------------------------------------------------------------------------ # A063826 -- direction 1,2,3,4 = E,N,W,S MyOEIS::compare_values (anum => 'A063826', 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; }); #------------------------------------------------------------------------------ # 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; require Math::BigInt; 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; }); #------------------------------------------------------------------------------ # A141481 -- plot sum of existing eight surrounding values entered MyOEIS::compare_values (anum => q{A141481}, # not in POD func => sub { my ($count) = @_; my @got; require Math::BigInt; 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; }); #------------------------------------------------------------------------------ # 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; require Math::NumSeq::AllDigits; 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; require Math::NumSeq::AllDigits; 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; require Math::NumSeq::AllDigits; 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; require Math::NumSeq::AllDigits; 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; require Math::NumSeq::AllDigits; 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; }); #------------------------------------------------------------------------------ # A054567 -- N values on negative X axis 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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/PeanoCurve-oeis.t0000644000175000017500000002705412563476437016513 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 . 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; use Math::PlanePath::Diagonals; use Math::PlanePath::ZOrderCurve; # uncomment this to run the ### lines #use Smart::Comments '###'; my $peano = Math::PlanePath::PeanoCurve->new; #------------------------------------------------------------------------------ # A163334 -- 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 -- 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 2011 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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # 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 verticals 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; }); #------------------------------------------------------------------------------ # A163332 -- 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; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/HilbertCurve-oeis.t0000644000175000017500000006251712563501553017031 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; plan tests => 46; 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'; # uncomment this to run the ### lines # use Smart::Comments '###'; 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) { my ($x, $y) = $zorder->n_to_xy ($n); $n = $hilbert->xy_to_n ($x, $y); } 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); } #------------------------------------------------------------------------------ # A147600 - num fixed points in 4^k blocks MyOEIS::compare_values (anum => 'A147600', max_count => 9, 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 => 200, 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; } } } #------------------------------------------------------------------------------ # A083885 etc counts of segments in direction foreach my $elem ([0, 'A083885', 0], # [1, '', 0], # [2, '', 1], # [3, '', 0] ) { my ($dir, $anum, $initial_k) = @$elem; MyOEIS::compare_values (anum => $anum, max_value => 10_000, func => sub { my ($count) = @_; my @got; my $n = $hilbert->n_start; my $total = 0; my $k = $initial_k; while (@got < $count) { my $n_end = 4**$k; for ( ; $n < $n_end; $n++) { $total += (dxdy_to_dir4($hilbert->n_to_dxdy($n)) == $dir); } push @got, $total; $k++; } return \@got; }); } # 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 } #------------------------------------------------------------------------------ # A163541 -- absolute direction transpose 0=east, 1=south, 2=west, 3=north MyOEIS::compare_values (anum => 'A163541', name => 'absolute direction transpose', func => sub { my ($count) = @_; my @got; for (my $n = $hilbert->n_start; @got < $count; $n++) { my ($dx, $dy) = $hilbert->n_to_dxdy ($n); ($dx,$dy) = ($dy,$dx); # transpose push @got, MyOEIS::dxdy_to_direction ($dx, $dy); } return \@got; }); #------------------------------------------------------------------------------ # 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 => q{A139351}, func => sub { my ($count) = @_; my @got; for (my $n = 0; @got < $count; $n++) { my @nbits = bit_split_lowtohigh($n); my $count = 0; for (my $i = 0; $i <= $#nbits; $i+=2) { $count += $nbits[$i]; } push @got, $count; } 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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # A163540 -- 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 => '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; }); #------------------------------------------------------------------------------ # 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-122/xt/oeis/DragonMidpoint-oeis.t0000644000175000017500000001357012563463163017350 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 Test; plan tests => 26; 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 turn=1 straight=0, 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 => 'LSR'); while (@got < $count) { my ($i, $value) = $seq->next; push @got, abs($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 require Math::NumSeq::PlanePathN; my $bigclass = Math::NumSeq::PlanePathN::_bigint(); # 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 = $bigclass->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-122/xt/oeis/FractionsTree-oeis.t0000644000175000017500000000644212563466242017203 0ustar gggg#!/usr/bin/perl -w # 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 . use 5.004; use strict; use Test; plan tests => 2; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::FractionsTree; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # A093873 -- Kepler numerators # { # my $path = Math::PlanePath::FractionsTree->new (tree_type => 'Kepler'); # my $anum = 'A093873'; # my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); # my @got; # if ($bvalues) { # foreach my $n (1 .. @$bvalues) { # my ($x, $y) = $path->n_to_xy (int(($n+1)/2)); # push @got, $x; # } # } # skip (! $bvalues, # numeq_array(\@got, $bvalues), # 1, "$anum -- Kepler tree numerators"); # } # # sub sans_high_bit { # my ($n) = @_; # return $n ^ high_bit($n); # } # sub high_bit { # my ($n) = @_; # my $bit; # for ($bit = 1; $bit <= $n; $bit <<= 1) { # $bit <<= 1; # } # return $bit >> 1; # } #------------------------------------------------------------------------------ # A093875 -- Kepler denominators # { # my $path = Math::PlanePath::FractionsTree->new (tree_type => 'Kepler'); # my $anum = 'A093875'; # my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); # my @got; # if ($bvalues) { # foreach my $n (2 .. @$bvalues) { # my ($x, $y) = $path->n_to_xy (int($n/2)); # push @got, $y; # } # } # skip (! $bvalues, # numeq_array(\@got, $bvalues), # 1, "$anum -- Kepler tree denominators"); # } #------------------------------------------------------------------------------ # 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-122/xt/oeis/CoprimeColumns-oeis.t0000644000175000017500000001724112136177302017361 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 => 10; 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 { my $anum = 'A127368'; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my $good = 1; my $count = 0; if ($bvalues) { my $x = 1; my $y = 1; for (my $i = 0; $i < @$bvalues; $i++) { my $want = $bvalues->[$i]; my $got = (Math::PlanePath::CoprimeColumns::_coprime($x,$y) ? $y : 0); if ($got != $want) { MyTestHelpers::diag ("wrong _coprime($x,$y)=$got want=$want at i=$i of $filename"); $good = 0; } $y++; if ($y > $x) { $x++; $y = 1; } $count++; } } ok ($good, 1, "$anum count $count"); } MyOEIS::compare_values (anum => q{A127368}, 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, $y; } else { push @got, 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 => qq{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 { my $anum = 'A054521'; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); { my $good = 1; my $count = 0; if ($bvalues) { my $x = 1; my $y = 1; for (my $i = 0; $i < @$bvalues; $i++) { my $want = $bvalues->[$i]; my $got = (Math::PlanePath::CoprimeColumns::_coprime($x,$y) ? 1 : 0); if ($got != $want) { MyTestHelpers::diag ("wrong _coprime($x,$y)=$got want=$want at i=$i of $filename"); $good = 0; } $y++; if ($y > $x) { $x++; $y = 1; } $count++; } } ok ($good, 1, "$anum count $count"); } } MyOEIS::compare_values (anum => q{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-122/xt/oeis/FactorRationals-oeis.t0000644000175000017500000001535212264602510017512 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.004; use strict; use Test; plan tests => 14; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::FactorRationals; # uncomment this to run the ### lines #use Smart::Comments '###'; my $path = Math::PlanePath::FactorRationals->new; #------------------------------------------------------------------------------ # A053985 - negabinary pos->pn MyOEIS::compare_values (anum => 'A053985', func => sub { my ($count) = @_; my @got; require Math::PlanePath::FactorRationals; 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; require Math::PlanePath::FactorRationals; 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; require Math::PlanePath::FactorRationals; 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; require Math::PlanePath::FactorRationals; 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; require Math::PlanePath::FactorRationals; for (my $i = 1; @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; require Math::PlanePath::FactorRationals; 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; require Math::PlanePath::FactorRationals; 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; require Math::PlanePath::DiagonalRationals; 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; require Math::PlanePath::RationalsTree; 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-122/xt/oeis/PentSpiral-oeis.t0000644000175000017500000000343312563472235016510 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 Test; plan tests => 2; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::PentSpiral; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/CCurve-oeis.t0000644000175000017500000002223412563464775015630 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; plan tests => 7; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::CCurve; # uncomment this to run the ### lines # use Smart::Comments '###'; my $path = Math::PlanePath::CCurve->new; # return 0,1,2,3 turn 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) % 4; } # return 0,1,2,3 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, 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 right_boundary { my ($n_end) = @_; return MyOEIS::path_boundary_length ($path, $n_end, side => 'right'); } use Memoize; Memoize::memoize('right_boundary'); #------------------------------------------------------------------------------ # 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 => '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; } #------------------------------------------------------------------------------ # 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, max_value => 100_000, func => sub { my ($count) = @_; my @got; my $n = $path->n_start; my $total = 0; my $k = $initial_k; while (@got < $count) { my $n_end = 2**$k; for ( ; $n < $n_end; $n++) { $total += (dxdy_to_dir4($path->n_to_dxdy($n)) == $dir); } push @got, $total; $k++; } return \@got; }); } #------------------------------------------------------------------------------ # A007814 - count low 0s, is turn right - 1 MyOEIS::compare_values (anum => 'A007814', fixup => sub { my ($bvalues) = @_; @$bvalues = map {$_ % 4} @$bvalues; }, func => sub { my ($count) = @_; my @got; my $total_turn = 0; for (my $n = $path->n_start + 1; @got < $count; $n++) { push @got, (1 - path_n_turn($path,$n)) % 4; # negate to right } return \@got; }); #------------------------------------------------------------------------------ # A003159 - positions ending even 0 bits is where turn either left or right MyOEIS::compare_values (anum => 'A003159', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my @got; while (@got < $count) { my ($i, $lsr) = $seq->next; if ($lsr) { # left or right push @got, $i; } } return \@got; }); # A036554 - positions ending odd 0 bits is where turn straight or reverse MyOEIS::compare_values (anum => 'A036554', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my @got; while (@got < $count) { my ($i, $lsr) = $seq->next; if ($lsr == 0) { # straight push @got, $i; } } 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 => 10_000, 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 => 50_000, 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; }); #------------------------------------------------------------------------------ # A035263 - morphism turn 0=straight, 1=not-straight MyOEIS::compare_values (anum => 'A035263', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'CCurve', turn_type => 'LSR'); my @got; for (my $n = 1; @got < $count; $n++) { my ($i,$value) = $seq->next; push @got, $value == 0 ? 0 : 1; } return \@got; }); MyOEIS::compare_values (anum => q{A035263}, func => sub { my ($count) = @_; my @got; for (my $n = 1; @got < $count; $n++) { push @got, (count_low_0_bits($n) + 1) % 2; } 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) require Math::NumSeq::PlanePathN; my $bigclass = Math::NumSeq::PlanePathN::_bigint(); MyOEIS::compare_values (anum => 'A146559', func => sub { my ($count) = @_; my @got; for (my $n = $bigclass->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 = $bigclass->new(1); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A000120 - count 1 bits total turn MyOEIS::compare_values (anum => 'A000120', fixup => sub { my ($bvalues) = @_; @$bvalues = map {$_ % 4} @$bvalues; }, func => sub { my ($count) = @_; my @got = (0); my $total_turn = 0; for (my $n = $path->n_start + 1; @got < $count; $n++) { $total_turn += path_n_turn($path,$n); push @got, $total_turn % 4; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/PyramidSpiral-oeis.t0000644000175000017500000000765712136177277017227 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 . # 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 => 4; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::PyramidSpiral; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # A217013 - inverse permutation, SquareSpiral -> PyramidSpiral # X,Y in SquareSpiral order, N of PyramidSpiral MyOEIS::compare_values (anum => 'A217013', 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 = $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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # A053615 -- distance to pronic is abs(X) MyOEIS::compare_values (anum => 'A053615', 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, abs($x); } 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-122/xt/oeis/MultipleRings-oeis.t0000644000175000017500000000367112167160676017234 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::MultipleRings; use Test; plan tests => 11; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines # use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/TriangularHypot-oeis.t0000644000175000017500000006005512136177277017572 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 . # 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-122/xt/oeis/GosperSide-oeis.t0000644000175000017500000002173612563466277016512 0ustar gggg#!/usr/bin/perl -w # Copyright 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 Test; plan tests => 11; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::GosperSide; # uncomment this to run the ### lines #use Smart::Comments '###'; 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; require Math::BigInt; 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 { 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-122/xt/oeis/QuadricCurve-oeis.t0000644000175000017500000000311312153211070016776 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::QuadricCurve; use Test; plan tests => 11; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # A133851 -- Y at N=2^k is 2^(k/4) when k=0mod4, starting require Math::NumSeq::PlanePathN; my $bigclass = Math::NumSeq::PlanePathN::_bigint(); MyOEIS::compare_values (anum => 'A133851', max_count => 1000, func => sub { my ($count) = @_; my $path = Math::PlanePath::QuadricCurve->new; my @got; for (my $n = $bigclass->new(2); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/AnvilSpiral-oeis.t0000644000175000017500000000354012136177303016644 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; plan tests => 2; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::AnvilSpiral; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/CellularRule-oeis.t0000644000175000017500000007213112616362470017021 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 . # cf A094605 rule 30 period of nth diagonal # A094606 log2 of that period # use 5.004; use strict; use Test; use List::Util 'min'; plan tests => 199; 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 (# [ 'A071030', 'A118109', 'rule=54' ], # [ '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' ], [ 'A098608', 2, 'bignum', base=>2 ], # 100^n [ 'A011557', 4, 'bignum', base=>2 ], # 10^n [ 'A245549', 30, 'bignum', base=>2 ], [ 'A094028', 50, 'bignum', base=>2 ], [ 'A006943', 60, 'bignum', base=>2 ], # Sierpinski [ 'A245548', 150, 'bignum', base=>2 ], [ 'A100706', 151, 'bignum', base=>2 ], [ 'A109241', 206, 'bignum', base=>2 ], [ 'A000042', 220, 'bignum', base=>2 ], # half-width 1s # http://oeis.org/A118110 # http://oeis.org/A245548 # 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' ], [ 'A071022', 70, 'bits', part=>'left' ], [ 'A071022', 198, 'bits', part=>'left' ], [ 'A071023', 78, 'bits', part=>'left' ], [ 'A071024', 92, 'bits', part=>'right' ], [ 'A071025', 124, 'bits', part=>'right' ], [ 'A071026', 188, 'bits', part=>'right' ], [ 'A071027', 230, 'bits', part=>'left' ], [ 'A071028', 50, 'bits' ], [ 'A071029', 22, 'bits' ], [ 'A071030', 54, 'bits' ], [ 'A071031', 62, 'bits' ], [ 'A071032', 86, 'bits' ], [ 'A071033', 94, 'bignum', base=>2 ], [ 'A071034', 118, 'bits' ], [ 'A071035', 126, 'bits' ], [ 'A071036', 150, 'bits' ], # same as A118110 [ 'A071037', 158, 'bits' ], [ 'A071038', 182, 'bits' ], [ 'A071039', 190, 'bits' ], [ 'A071040', 214, 'bits' ], [ 'A071041', 246, 'bits' ], # [ 'A060576', 255, 'bits' ], # homeomorphically irreducibles ... [ 'A070909', 28, 'bits', part=>'right' ], [ 'A070909', 156, 'bits', part=>'right' ], [ 'A075437', 110, 'bits' ], [ 'A118101', 94, 'bignum' ], [ 'A118102', 94, 'bits' ], [ 'A118108', 54, 'bignum' ], [ 'A118109', 54, 'bignum', base=>2 ], [ 'A118110', 150, 'bignum', base=>2 ], [ 'A118111', 190, 'bits' ], [ 'A118171', 158, 'bignum' ], [ 'A118172', 158, 'bits' ], [ 'A118173', 188, 'bignum' ], [ 'A118174', 188, 'bits' ], [ 'A118175', 220, 'bits' ], [ 'A118175', 252, 'bits' ], [ 'A070887', 110, 'bits', part=>'left' ], [ 'A071042', 90, 'number_of', value=>0 ], [ 'A071043', 22, 'number_of', value=>0 ], [ 'A071044', 22, 'number_of', value=>1 ], [ 'A071045', 54, 'number_of', value=>0 ], [ 'A071046', 62, 'number_of', value=>0 ], [ 'A071047', 62, 'number_of', value=>1 ], [ 'A071049', 110, 'number_of', value=>1, initial=>[0] ], [ 'A071048', 110, 'number_of', value=>0, part=>'left' ], [ 'A071050', 126, 'number_of', value=>0 ], [ 'A071051', 126, 'number_of', value=>1 ], [ 'A071052', 150, 'number_of', value=>0 ], [ 'A071053', 150, 'number_of', value=>1 ], [ 'A071054', 158, 'number_of', value=>1 ], [ 'A071055', 182, 'number_of', value=>0 ], [ 'A038184', 150, 'bignum' ], [ 'A038185', 150, 'bignum', part=>'left' ], # cut after central column [ 'A001045', 28, 'bignum', initial=>[0,1] ], # Jacobsthal [ 'A110240', 30, 'bignum' ], # cf A074890 some strange form [ 'A117998', 102, 'bignum' ], [ 'A117999', 110, 'bignum' ], [ 'A037576', 190, 'bignum' ], [ 'A002450', 250, 'bignum', initial=>[0] ], # (4^n-1)/3 10101 extra 0 at start [ 'A006977', 230, 'bignum', part=>'left' ], [ 'A078176', 225, 'bignum', part=>'whole', ystart=>1, inverse=>1 ], [ 'A051023', 30, 'bits', part=>'centre' ], [ 'A070950', 30, 'bits' ], [ '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 ... ], [ 'A092539', 30, 'bignum_central_column' ], [ 'A094603', 30, 'trailing_number_of', value=>1 ], [ 'A094604', 30, 'new_maximum_trailing_number_of', 1 ], [ 'A001316', 90, 'number_of', value=>1 ], # Gould's sequence #-------------------------------------------------------------------------- # 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, 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', 220, 'bignum', initial=>[0] ], # 2^n-1 want start from 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"; # } # } # } # } 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'} // 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 $part = $params{'part'} || 'whole'; my $want_value = $params{'value'} // 1; my $max_count = $params{'max_count'}; my $path = Math::PlanePath::CellularRule->new (rule => $rule); my @got = @$initial; for (my $y = 0; @got < $count; $y++) { my $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 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'} // 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; require Math::BigInt; 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; push @got, ($path->xy_to_n ($x, $y) ? 1 : 0); } } return \@got; }); } #------------------------------------------------------------------------------ # bignum central vertical column in decimal sub bignum_central_column { my ($anum, $rule) = @_; 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; require Math::BigInt; my $b = Math::BigInt->new(0); for (my $y = 0; @got < $count; $y++) { my $bit = ($path->xy_to_n (0, $y) ? 1 : 0); $b = 2*$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 # exit 0; Math-PlanePath-122/xt/oeis/TriangleSpiral-oeis.t0000644000175000017500000001051012563462277017346 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 . 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::TriangleSpiral; use Math::PlanePath::TriangleSpiralSkewed; # uncomment this to run the ### lines #use Smart::Comments '###'; my $path = Math::PlanePath::TriangleSpiral->new; #------------------------------------------------------------------------------ # 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 @got; require Math::BigInt; 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-122/xt/oeis/AlternatePaper-oeis.t0000644000175000017500000002643412563464524017346 0ustar gggg#!/usr/bin/perl -w # Copyright 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 Math::PlanePath::AlternatePaper; use Test; plan tests => 14; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines #use Smart::Comments '###'; my $paper = Math::PlanePath::AlternatePaper->new; require Math::NumSeq::PlanePathN; my $bigclass = Math::NumSeq::PlanePathN::_bigint(); #------------------------------------------------------------------------------ # 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 = $bigclass->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 = $bigclass->new(2); @got < $count; $n *= 2) { my ($x,$y) = $paper->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # A052955 single-visited points to N=2^k MyOEIS::compare_values (anum => 'A052955', max_value => 10_000, 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 => 10_000, 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; }); #------------------------------------------------------------------------------ # A122746 area increment to N=2^k MyOEIS::compare_values (anum => '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; }); #------------------------------------------------------------------------------ # 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}, 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; }); #------------------------------------------------------------------------------ # A020991 - position of last occurance of n, last time of X+Y=n 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 $d = $x + $y; $occur[$d]++; if ($occur[$d] == $d) { push @got, $n-1; $target++; } } 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}, 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; }); #------------------------------------------------------------------------------ # A212591 - position of first occurance of n, first time getting to X+Y=n # 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 $d = $x + $y; if ($d == $target) { push @got, $n-1; $target++; } } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/KochCurve-oeis.t0000644000175000017500000002562612563471632016330 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 Test; plan tests => 8; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::KochCurve; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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; } #------------------------------------------------------------------------------ # A002450 number of right turns N=1 to N < 4^k # # 2 # / \ / # 0---1 3---4 # 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) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'KochCurve', turn_type => 'Left'); my @got; my $total = 0; my $target = 1; while (@got < $count) { my ($i,$value) = $seq->next; if ($i == $target) { push @got, $total; $target *= 4; } $total += $value; } return \@got; }); MyOEIS::compare_values (anum => 'A002450', max_value => 100_000, func => sub { my ($count) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'KochCurve', turn_type => 'Right'); my @got; my $total = 0; my $target = 1; while (@got < $count) { my ($i,$value) = $seq->next; if ($i == $target) { push @got, $total; $target *= 4; } $total += $value; } return \@got; }); #------------------------------------------------------------------------------ # A177702 - abs(dX) from N=1 onwards, repeating 1,1,2 MyOEIS::compare_values (anum => 'A177702', func => sub { my ($count) = @_; require Math::NumSeq::PlanePathDelta; my $seq = Math::NumSeq::PlanePathDelta->new (planepath => 'KochCurve', 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) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'KochCurve', 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) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'KochCurve', 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) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'KochCurve', 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) = @_; # require Math::NumSeq::PlanePathTurn; # my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'KochCurve', # 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) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'KochCurve', 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) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'KochCurve', 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) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'KochCurve', 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) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'KochCurve', 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) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => 'KochCurve', 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-122/xt/oeis/ZOrderCurve-oeis.t0000644000175000017500000001463312136177276016650 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 => 10; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::ZOrderCurve; use Math::PlanePath::Diagonals; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # A163328 -- radix=3 diagonals 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 same axis, inverse 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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # A054238 -- permutation, 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, 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; }); #------------------------------------------------------------------------------ # A057300 -- N at transpose Y,X, radix=2 MyOEIS::compare_values (anum => 'A057300', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::ZOrderCurve->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::ZOrderCurve->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::ZOrderCurve->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::ZOrderCurve->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; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/DiamondSpiral-oeis.t0000644000175000017500000001327312136177302017151 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 => 1; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines # use Smart::Comments '###'; use Math::PlanePath::DiamondSpiral; my $path = Math::PlanePath::DiamondSpiral->new; #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # A217015 -- permutation SquareSpiral rotate -90 -> DiamondSpiral # 1 2 3 4 5 6 # 1, 5, 6, 2, 8, 3, 10, 4, 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-122/xt/oeis/Staircase-oeis.t0000644000175000017500000000271612164406220016334 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 => 7; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::Staircase; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/GcdRationals-oeis.t0000644000175000017500000001300712136177301016767 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 => 6; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::GcdRationals; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # A050873 = ceil(X/Y) MyOEIS::compare_values (anum => 'A050873', 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; }); } #------------------------------------------------------------------------------ # A178340 Bernoulli denominator = int(X/Y) + 1 # Not quite since A178340 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; # }); #------------------------------------------------------------------------------ # 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-122/xt/oeis/MPeaks-oeis.t0000644000175000017500000000577412317701665015620 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 Test; plan tests => 4; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::MPeaks; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/PythagoreanTree-oeis.t0000644000175000017500000004326212257422351017526 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 => 2; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::BigInt try => 'GMP'; use Math::PlanePath::PythagoreanTree; # uncomment this to run the ### lines # use Smart::Comments '###'; #------------------------------------------------------------------------------ # A002315 NSW numbers, sum Pell(2k)-Pell(2k-1), is row P-Q 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 $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; }); # A001541 is row P+Q 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) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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 => 100, func => sub { my ($count) = @_; require Math::BigInt; 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 => 100, func => sub { my ($count) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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 => 100, func => sub { my ($count) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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 => 100, func => sub { my ($count) = @_; require Math::BigInt; 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 => 100, func => sub { my ($count) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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" coordinate MyOEIS::compare_values (anum => 'A001652', # max_count => 50, func => sub { my ($count) = @_; require Math::BigInt; 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 => 100, func => sub { my ($count) = @_; require Math::BigInt; 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 => 100, func => sub { my ($count) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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 => 100, func => sub { my ($count) = @_; require Math::BigInt; 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 => 500, func => sub { my ($count) = @_; require Math::BigInt; 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', func => sub { my ($count) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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) = @_; require Math::BigInt; 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-122/xt/oeis/HypotOctant-oeis.t0000644000175000017500000000624212136177301016674 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 => 4; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use List::Util 'min', 'max'; use Math::PlanePath::HypotOctant; # uncomment this to run the ### lines #use Smart::Comments '###'; # #------------------------------------------------------------------------------ # # 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-122/xt/oeis/PyramidSides-oeis.t0000644000175000017500000000365512271045176017026 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 Test; plan tests => 2; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::PyramidSides; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/WythoffArray-oeis.t0000644000175000017500000006711512400213363017044 0ustar gggg#!/usr/bin/perl -w # 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 . # 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. use 5.004; use strict; use Carp 'croak'; use List::Util 'max'; use Test; plan tests => 46; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::WythoffArray; use Math::PlanePath::CoprimeColumns; *_coprime = \&Math::PlanePath::CoprimeColumns::_coprime; # uncomment this to run the ### lines # use Smart::Comments '###'; sub BIGINT { require Math::NumSeq::PlanePathN; return Math::NumSeq::PlanePathN::_bigint(); } # 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"; } #------------------------------------------------------------------------------ # 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 { require Math::PlanePath::Diagonals; 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) = @_; require Math::PlanePath::Diagonals; 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) = @_; require Math::PlanePath::Diagonals; 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; require Math::PlanePath::Diagonals; 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); require Math::PlanePath::Diagonals; 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; }); #------------------------------------------------------------------------------ # A114579 -- N at transpose Y,X MyOEIS::compare_values (anum => 'A114579', 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 (BIGINT()->new($n)); my $t = $path->xy_to_n ($y, $x); push @got, $t; } return \@got; }); #------------------------------------------------------------------------------ # A220249 -- which row is n * Lucas numbers MyOEIS::compare_values (anum => 'A220249', func => sub { my ($count) = @_; require Math::PlanePath::Diagonals; 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) = @_; require Math::PlanePath::Diagonals; 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) = @_; require Math::NumSeq::PlanePathTurn; 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); } { require Math::NumSeq::Fibbinary; 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); } { require Math::NumSeq::PlanePathTurn; 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) = @_; require Math::NumSeq::PlanePathTurn; 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) = @_; require Math::NumSeq::PlanePathTurn; 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 { require Math::NumSeq::PlanePathTurn; require Math::NumSeq::FibonacciWord; 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, 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) = @_; require Math::PlanePath::Diagonals; 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) = @_; require Math::NumSeq::FibbinaryBitCount; 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) = @_; # require Math::PlanePath::Diagonals; # 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 = 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 $d = 0; 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 = 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 = 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) = @_; require Math::PlanePath::Diagonals; 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 = BIGINT()->new($x); $y = 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) = @_; require Math::PlanePath::Diagonals; 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) = @_; require Math::PlanePath::Diagonals; 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 = BIGINT()->new($x); $y = BIGINT()->new($y); push @got, $wythoff->xy_to_n($x,$y); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/TerdragonCurve-oeis.t0000644000175000017500000003546612563471745017401 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 Test; plan tests => 12; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::TerdragonCurve; # 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); } #------------------------------------------------------------------------------ # A005823 - N positions with net turn == 0, no ternary 1s 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; }); #------------------------------------------------------------------------------ # 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,1]], # Y arms=3 ['A103312', 3, 1, 0, [0,1,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, func => sub { my ($count) = @_; require Math::BigInt; 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 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 => 9, 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 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 => '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) = @_; # require Math::NumSeq::PlanePathTurn; # 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) = @_; require Math::NumSeq::PlanePathTurn; my $seq = Math::NumSeq::PlanePathTurn->new (planepath_object => $path, turn_type => 'LSR'); my @got; for (my $level = 0; @got < $count; $level++) { require Math::BigInt; 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) = @_; 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; }); #------------------------------------------------------------------------------ # A062756 - ternary count 1s, is cumulative turn 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; 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) = @_; 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 { 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 - N 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 == 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 ... require Math::NumSeq::PlanePathTurn; 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 => '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-122/xt/oeis/QuintetCentres-oeis.t0000644000175000017500000000471012563472320017376 0ustar gggg#!/usr/bin/perl -w # Copyright 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 Math::PlanePath::QuintetCentres; use Test; plan tests => 11; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # A099456 -- level end Y MyOEIS::compare_values (anum => 'A099456', func => sub { my ($count) = @_; my $path = Math::PlanePath::QuintetCentres->new; my @got; require Math::BigInt; 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; require Math::BigInt; 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; require Math::BigInt; 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-122/xt/oeis/HexSpiral-oeis.t0000644000175000017500000001410112240242753016310 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 . # 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 => 4; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use List::Util 'min', 'max'; use Math::PlanePath::HexSpiral; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/PentSpiralSkewed-oeis.t0000644000175000017500000000432112240240721017631 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 => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::PentSpiralSkewed; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/HIndexing-oeis.t0000644000175000017500000000301212153014614016261 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::HIndexing; use Test; plan tests => 11; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # A097110 -- Y at N=2^k require Math::NumSeq::PlanePathN; my $bigclass = Math::NumSeq::PlanePathN::_bigint(); MyOEIS::compare_values (anum => 'A097110', func => sub { my ($count) = @_; my $path = Math::PlanePath::HIndexing->new; my @got; for (my $n = $bigclass->new(1); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/FilledRings-oeis.t0000644000175000017500000000643212136177301016623 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::BigInt; use Math::PlanePath::FilledRings; use Test; plan tests => 5; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/PowerArray-oeis.t0000644000175000017500000003361212167157313016521 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 => 18; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::PowerArray; # uncomment this to run the ### lines #use Smart::Comments '###'; require Math::NumSeq::PlanePathN; my $bigclass = Math::NumSeq::PlanePathN::_bigint(); #------------------------------------------------------------------------------ # 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) = @_; require Math::PlanePath::PowerArray; 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 = $bigclass->new(2); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); $x == 0 or die; push @got, $y; } return \@got; }); #------------------------------------------------------------------------------ # 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 = $bigclass->new(1); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); 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) = @_; require Math::PlanePath::Diagonals; 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; require Math::PlanePath::Diagonals; 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; }); #------------------------------------------------------------------------------ # A135764 -- dispersion traversed by diagonals, down from Y axis MyOEIS::compare_values (anum => 'A135764', func => sub { my ($count) = @_; my @got; require Math::PlanePath::Diagonals; my $diagonals = Math::PlanePath::Diagonals->new (direction => 'down'); my $power = Math::PlanePath::PowerArray->new; 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) = @_; require Math::PlanePath::Diagonals; 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); require Math::BigInt; 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; require Math::PlanePath::Diagonals; 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 MyOEIS::compare_values (anum => 'A054582', func => sub { my ($count) = @_; my @got; require Math::PlanePath::Diagonals; my $diagonals = Math::PlanePath::Diagonals->new (direction => 'up'); my $power = Math::PlanePath::PowerArray->new; 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; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/GrayCode-oeis.t0000644000175000017500000005534112563466437016140 0ustar gggg#!/usr/bin/perl -w # Copyright 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 Test; plan tests => 33; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh'; use Math::PlanePath::GrayCode; use Math::PlanePath::Diagonals; use Math::PlanePath::Base::Digits 'digit_join_lowtohigh'; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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 => '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 => '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; }); #------------------------------------------------------------------------------ # A003159 -- (N+1)/2 of positions of Left turns MyOEIS::compare_values (anum => 'A003159', func => sub { my ($count) = @_; my @got; my $path = Math::PlanePath::GrayCode->new; for (my $n = 2; @got < $count; $n += 2) { if (path_n_turn($path,$n) == 1) { push @got, $n/2; } } return \@got; }); #------------------------------------------------------------------------------ # A036554 -- (N+1)/2 of positions of Left turns MyOEIS::compare_values (anum => 'A036554', func => sub { my ($count) = @_; my $path = Math::PlanePath::GrayCode->new; my @got; for (my $n = 2; @got < $count; $n += 2) { if (path_n_turn($path,$n) == 0) { push @got, $n/2; } } return \@got; }); #------------------------------------------------------------------------------ # A039963 -- Left turns MyOEIS::compare_values (anum => 'A039963', func => sub { my ($count) = @_; my $path = Math::PlanePath::GrayCode->new; my @got; for (my $n = $path->n_start + 1; @got < $count; $n++) { push @got, path_n_turn($path,$n); } return \@got; }); #------------------------------------------------------------------------------ # A035263 -- Left turns undoubled, skip N even MyOEIS::compare_values (anum => 'A035263', func => sub { my ($count) = @_; my $path = Math::PlanePath::GrayCode->new; my @got; for (my $n = $path->n_start + 1; @got < $count; $n += 2) { push @got, path_n_turn($path,$n); } return \@got; }); #------------------------------------------------------------------------------ # A065882 -- low base4 non-zero digit MyOEIS::compare_values (anum => 'A065882', fixup => sub { my ($bvalues) = @_; foreach (@$bvalues) { $_ %= 2; } }, func => sub { my ($count) = @_; my $path = Math::PlanePath::GrayCode->new; my @got; for (my $n = $path->n_start + 1; @got < $count; $n += 2) { push @got, path_n_turn($path,$n); } return \@got; }); #------------------------------------------------------------------------------ # A007913 -- Left turns from square free part of N, skip N even MyOEIS::compare_values (anum => q{A007913}, # not xreffed in GrayCode.pm fixup => sub { my ($bvalues) = @_; foreach (@$bvalues) { $_ %= 2; } }, func => sub { my ($count) = @_; my $path = Math::PlanePath::GrayCode->new; my @got; for (my $n = $path->n_start + 1; @got < $count; $n += 2) { push @got, path_n_turn($path,$n); } return \@got; }); # 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 == 2) { return 0; } die "Oops, unrecognised turn"; } # return 0,1,2,3 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, 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 } #------------------------------------------------------------------------------ # 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 gray reflected # modular and reflected same in binary 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_reflected($digits,$radix); push @got, digit_join_lowtohigh($digits,$radix); } 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 => '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 => '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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # A098488 - decimal gray modular 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; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/SierpinskiCurve-oeis.t0000644000175000017500000000605512153015455017547 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; plan tests => 14; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::SierpinskiCurve; use Math::NumSeq::PlanePathDelta; use Math::NumSeq::PlanePathTurn; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # A081026 -- X at N=2^k require Math::NumSeq::PlanePathN; my $bigclass = Math::NumSeq::PlanePathN::_bigint(); MyOEIS::compare_values (anum => 'A081026', func => sub { my ($count) = @_; my $path = Math::PlanePath::SierpinskiCurve->new; my @got = (1); for (my $n = $bigclass->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-122/xt/oeis/Diagonals-oeis.t0000644000175000017500000002070312153016342016313 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 => 12; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::Diagonals; # uncomment this to run the ### lines #use Smart::Comments '###'; # A079824 #------------------------------------------------------------------------------ # A057046 -- X at N=2^k require Math::NumSeq::PlanePathN; my $bigclass = Math::NumSeq::PlanePathN::_bigint(); { my $path = Math::PlanePath::Diagonals->new (n_start => 1, x_start => 1, y_start => 1); MyOEIS::compare_values (anum => 'A057046', func => sub { my ($count) = @_; my @got; for (my $n = $bigclass->new(1); @got < $count; $n *= 2) { my ($x,$y) = $path->n_to_xy($n); push @got, $x; } return \@got; }); MyOEIS::compare_values (anum => 'A057047', func => sub { my ($count) = @_; my @got; for (my $n = $bigclass->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) = @_; require Math::NumSeq::PlanePathTurn; 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) = @_; require Math::NumSeq::PlanePathTurn; 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); require Math::NumSeq::PlanePathCoord; 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-122/xt/oeis/TriangleSpiralSkewed-oeis.t0000644000175000017500000002371712136177277020525 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 . # "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; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/PyramidRows-oeis.t0000644000175000017500000002026112271045012016666 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 Test; use List::Util 'sum'; plan tests => 13; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::PyramidRows; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/oeis/FibonacciWordFractal-oeis.t0000644000175000017500000000314712207313504020423 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 => 1; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::FibonacciWordFractal; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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 => 'FibonacciWordFractal', turn_type => 'LSR'); # turn_type=Straight my @got; while (@got < $count) { my ($i,$value) = $seq->next; push @got, $value == 0 ? 1 : 0; } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/DivisibleColumns-oeis.t0000644000175000017500000000513312136177301017671 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 => 3; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::DivisibleColumns; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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 { my $anum = 'A006218'; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my $good = 1; my $count = 0; if ($bvalues) { my $path = Math::PlanePath::DivisibleColumns->new; for (my $i = 0; $i < @$bvalues; $i++) { my $x = $i+1; my $want = $bvalues->[$i]; my $got = $path->xy_to_n($x,1); if ($got != $want) { MyTestHelpers::diag ("wrong totient sum xy_to_n($x,1)=$got want=$want at i=$i of $filename"); $good = 0; } $count++; } } ok ($good, 1, "$anum count $count"); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/oeis/Hypot-oeis.t0000644000175000017500000001467712136645623015545 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 => 1; 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) = @_; require Math::BigRat; 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 use Math::Trig 'pi'; MyOEIS::compare_values (anum => q{A093832}, func => sub { my ($count) = @_; require Math::BigRat; 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-122/xt/oeis/SierpinskiTriangle-oeis.t0000644000175000017500000005744212503737432020243 0ustar gggg#!/usr/bin/perl -w # 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 . use 5.004; use strict; use Test; plan tests => 16; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::BigInt try => 'GMP'; 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; # } #------------------------------------------------------------------------------ { 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); } } #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # 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; }); #------------------------------------------------------------------------------ # 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-122/xt/oeis/R5DragonCurve-oeis.t0000644000175000017500000001417612563472625017066 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 Test; plan tests => 12; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::R5DragonCurve; # uncomment this to run the ### lines #use Smart::Comments '###'; my $path = Math::PlanePath::R5DragonCurve->new; #------------------------------------------------------------------------------ # A006495 -- level end X, b^k MyOEIS::compare_values (anum => 'A006495', func => sub { my ($count) = @_; my @got; require Math::BigInt; 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; require Math::BigInt; 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-122/xt/KochCurve-more.t0000644000175000017500000000641512136177167015371 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 Math::PlanePath::DragonCurve; use Test; plan tests => 1; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # rect_to_n_range() on various boxes { require Math::PlanePath::KochCurve; 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 = 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() 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-122/xt/MyOEIS.pm0000644000175000017500000005126712611261476013756 0ustar gggg# Copyright 2010, 2011, 2012, 2013, 2014, 2015 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() ... 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) }) { ### from 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 $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'}); 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"; } $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"); } 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"; } } 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 ($x,$y) = $path->n_to_xy($n) or next; my @n_list = $path->xy_to_n_list($x,$y); 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 ($x,$y) = $path->n_to_xy($n) or next; my @n_list = $path->xy_to_n_list($x,$y); 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 ($x,$y) = $path->n_to_xy($n) or next; my @n_list = $path->xy_to_n_list($x,$y); 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-122/xt/0-examples-xrefs.t0000644000175000017500000000430012230011245015602 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-122/xt/PlanePath-subclasses.t0000644000175000017500000032413112606435140016545 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 . # 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 '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', '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', 'PeanoCurve', 'PeanoCurve,radix=2', 'PeanoCurve,radix=4', 'PeanoCurve,radix=5', 'PeanoCurve,radix=17', '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', 'QuintetCentres', 'QuintetCentres,arms=2', 'QuintetCentres,arms=3', 'QuintetCentres,arms=4', 'QuintetReplicate', 'QuintetCurve', 'QuintetCurve,arms=2', 'QuintetCurve,arms=3', 'QuintetCurve,arms=4', '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', 'CCurve', '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', 'GosperReplicate', 'GosperSide', 'SquareReplicate', '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', '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', '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', 'AlternatePaper', 'AlternatePaper,arms=2', 'AlternatePaper,arms=3', 'AlternatePaper,arms=4', 'AlternatePaper,arms=5', 'AlternatePaper,arms=6', 'AlternatePaper,arms=7', 'AlternatePaper,arms=8', '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 = 122; 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, 'Math::PlanePath::TerdragonCurve' => 3, 'Math::PlanePath::KochSnowflakes' => 2, 'Math::PlanePath::QuadricIslands' => 2, ); my %xy_maximum_duplication_at_origin = ('Math::PlanePath::DragonCurve' => 4, 'Math::PlanePath::TerdragonCurve' => 6, 'Math::PlanePath::R5DragonCurve' => 4, ); # modules for which rect_to_n_range() is exact my %rect_exact = ( # 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, ); #------------------------------------------------------------------------------ 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 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{$class} || 0; # # 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{$class}) { &$report ("n_to_xy($n) duplicate$count_n_to_xy{$xystr} xy=$xystr prev n=$saw_n_to_xy{$xystr}"); } } $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; } } my $prev_dx = $prev_dx[$arm]; my $prev_dy = $prev_dy[$arm]; if (defined $prev_dx) { my $n_of_turn = $n - $arms_count; my $LSR = ($dy*$prev_dx - $dx*$prev_dy); # allow for floating point round-off on MultipleRings polygon straights if (abs($LSR) < 1e-10) { $LSR = 0; } $LSR = ($LSR <=> 0); # 1,undef,-1 # 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_of_turn"); if (! defined $got_turn_any_left_at_n) { $got_turn_any_left_at_n = $n_of_turn; } } if (! $LSR) { $turn_any_straight or &$report ("turn_any_straight() false but straight at N=$n_of_turn"); if (! defined $got_turn_any_straight_at_n) { $got_turn_any_straight_at_n = $n_of_turn; } # 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_of_turn"); if (! defined $got_turn_any_right_at_n) { $got_turn_any_right_at_n = $n_of_turn; } } } $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); } } } } } #-------------------------------------------------------------------------- # 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"); } 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_n_to_LSR($path,$n); $got_LSR == $want_LSR or &$report ("$method()=$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"); } } } #-------------------------------------------------------------------------- ### _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 { 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-122/xt/0-Test-YAML-Meta.t0000755000175000017500000000345512347217470015240 0ustar gggg#!/usr/bin/perl -w # 0-Test-YAML-Meta.t -- run Test::CPAN::Meta::YAML if available # Copyright 2009, 2010, 2011, 2013, 2014 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-122/xt/PixelRings-image.t0000644000175000017500000001061312136177167015677 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-122/xt/0-META-read.t0000755000175000017500000001071512136177162014330 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-122/xt/ChanTree-slow.t0000644000175000017500000000717012136177167015212 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'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } 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; require Math::PlanePath::ChanTree; #------------------------------------------------------------------------------ # 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-122/xt/slow/0002755000175000017500000000000012641645163013330 5ustar ggggMath-PlanePath-122/xt/slow/TerdragonCurve-slow.t0000644000175000017500000003265012451351065017427 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-122/xt/slow/R5DragonCurve-slow.t0000644000175000017500000003563612451431730017130 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-122/xt/slow/ComplexMinus-slow.t0000644000175000017500000000546312302552226017117 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-122/xt/slow/DragonCurve-slow.t0000644000175000017500000003621612321135263016712 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-122/xt/slow/HilbertCurve-slow.t0000644000175000017500000000561112342460401017062 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-122/xt/slow/NumSeq-PlanePathCoord.t0000644000175000017500000021657412563217176017605 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 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 => 1045)[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 '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', 'Corner', 'Corner,wider=1', 'Corner,wider=2', 'Corner,wider=5', 'Corner,wider=37', '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', 'PeanoCurve', 'PeanoCurve,radix=2', 'PeanoCurve,radix=4', 'PeanoCurve,radix=5', 'PeanoCurve,radix=17', '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-122/xt/slow/AlternatePaper-slow.t0000644000175000017500000001555612451431554017414 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-122/xt/slow/GcdRationals-slow.t0000644000175000017500000000717612136177164017062 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-122/xt/slow/CellularRule-slow.t0000644000175000017500000001067112272073126017067 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 => 637; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require 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-122/xt/slow/CCurve-slow.t0000644000175000017500000001110012303231756015647 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::CCurve; 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 ($x,$y) = $path->n_to_xy($n); my @n_list = $path->xy_to_n_list($x,$y); 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-122/xt/GrayCode-oseq.t0000644000175000017500000002737712201357501015175 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::Prime::XS 0.23 'is_prime'; # version 0.23 fix for 1928099 use Test; plan tests => 13; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use Math::PlanePath::GrayCode; use Math::PlanePath::Diagonals; # uncomment this to run the ### lines #use Smart::Comments '###'; 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); } 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); } #------------------------------------------------------------------------------ # A048641 - binary gray cumulative sum { my $anum = 'A048641'; my $radix = 2; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my @got; if ($bvalues) { my $cumulative = 0; for (my $n = 0; @got < @$bvalues; $n++) { $cumulative += to_binary_gray($n); push @got, $cumulative; } if (! numeq_array(\@got, $bvalues)) { MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..20])); MyTestHelpers::diag ("got: ",join(',',@got[0..20])); } } skip (! $bvalues, numeq_array(\@got, $bvalues), 1, "$anum - binary gray cumulative sum"); } #------------------------------------------------------------------------------ # A048644 - binary gray cumulative sum difference from triangular(n) { my $anum = 'A048644'; my $radix = 2; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my @got; if ($bvalues) { my $cumulative = 0; for (my $n = 0; @got < @$bvalues; $n++) { $cumulative += to_binary_gray($n); push @got, $cumulative - triangular($n); } if (! numeq_array(\@got, $bvalues)) { MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..20])); MyTestHelpers::diag ("got: ",join(',',@got[0..20])); } } skip (! $bvalues, numeq_array(\@got, $bvalues), 1, "$anum - binary gray cumulative sum"); } sub triangular { my ($n) = @_; return $n*($n+1)/2; } #------------------------------------------------------------------------------ # A048642 - binary gray cumulative product { my $anum = 'A048642'; my $radix = 2; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my @got; if ($bvalues) { require Math::BigInt; my $product = Math::BigInt->new(1); for (my $n = 0; @got < @$bvalues; $n++) { $product *= (to_binary_gray($n) || 1); push @got, $product; } if (! numeq_array(\@got, $bvalues)) { MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..20])); MyTestHelpers::diag ("got: ",join(',',@got[0..20])); } } skip (! $bvalues, numeq_array(\@got, $bvalues), 1, "$anum - binary gray cumulative product"); } #------------------------------------------------------------------------------ # A048643 - binary gray cumulative product, diff to factorial(n) { my $anum = 'A048643'; my $radix = 2; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my @got; if ($bvalues) { require Math::BigInt; my $product = Math::BigInt->new(1); my $factorial = Math::BigInt->new(1); for (my $n = 0; @got < @$bvalues; $n++) { $product *= (to_binary_gray($n) || 1); $factorial *= ($n||1); push @got, $product - $factorial; } if (! numeq_array(\@got, $bvalues)) { MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..20])); MyTestHelpers::diag ("got: ",join(',',@got[0..20])); } } skip (! $bvalues, numeq_array(\@got, $bvalues), 1, "$anum - binary gray cumulative product"); } #------------------------------------------------------------------------------ # A143329 - gray(prime(n)) which is prime too { my $anum = 'A143329'; my $radix = 2; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my @got; my $diff; if ($bvalues) { for (my $n = 0; @got < @$bvalues; $n++) { next unless is_prime($n); my $gray = to_binary_gray($n); next unless is_prime($gray); push @got, $gray; } $diff = MyOEIS::diff_nums(\@got, $bvalues); if ($diff) { MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..45])); MyTestHelpers::diag ("got: ",join(',',@got[0..45])); } } skip (! $bvalues, $diff, undef, "$anum - gray(prime(n)) which is prime too"); } #------------------------------------------------------------------------------ # A143292 - binary gray of primes { my $anum = 'A143292'; my $radix = 2; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my @got; if ($bvalues) { for (my $n = 0; @got < @$bvalues; $n++) { next unless is_prime($n); push @got, to_binary_gray($n); } if (! numeq_array(\@got, $bvalues)) { MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..20])); MyTestHelpers::diag ("got: ",join(',',@got[0..20])); } } skip (! $bvalues, numeq_array(\@got, $bvalues), 1, "$anum - binary gray of primes"); } #------------------------------------------------------------------------------ # A005811 - count 1 bits in gray(n), is num runs { my $anum = 'A005811'; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my @got; if ($bvalues) { for (my $n = 0; @got < @$bvalues; $n++) { my $gray = to_binary_gray($n); push @got, count_1_bits($gray); } if (! numeq_array(\@got, $bvalues)) { MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..20])); MyTestHelpers::diag ("got: ",join(',',@got[0..20])); } } skip (! $bvalues, numeq_array(\@got, $bvalues), 1, "$anum - primes for which binary gray is also prime"); } 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 { my $anum = 'A173318'; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my @got; if ($bvalues) { my $cumulative = 0; for (my $n = 0; @got < @$bvalues; $n++) { $cumulative += count_1_bits(to_binary_gray($n)); push @got, $cumulative; } if (! numeq_array(\@got, $bvalues)) { MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..20])); MyTestHelpers::diag ("got: ",join(',',@got[0..20])); } } skip (! $bvalues, numeq_array(\@got, $bvalues), 1, "$anum - primes for which binary gray is also prime"); } #------------------------------------------------------------------------------ # A099891 -- triangle cumulative XOR # { my $anum = 'A099891'; my $radix = 2; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my @got; if ($bvalues) { my @array; for (my $y = 0; @got < @$bvalues; $y++) { my $gray = to_binary_gray($y,$radix); 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 < @$bvalues; $x++) { push @got, $array[$y][$x]; } } if (! numeq_array(\@got, $bvalues)) { MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..10])); MyTestHelpers::diag ("got: ",join(',',@got[0..10])); } } skip (! $bvalues, numeq_array(\@got, $bvalues), 1); } #------------------------------------------------------------------------------ # A195467 -- diagonals powered permutation, starting from perm^0=identity # { my $anum = 'A195467'; my $radix = 2; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my @got; if ($bvalues) { require Math::PlanePath::Diagonals; my $diagonal_path = Math::PlanePath::Diagonals->new; for (my $n = $diagonal_path->n_start; @got < @$bvalues; $n++) { my ($x, $y) = $diagonal_path->n_to_xy ($n); my $digits = [ digit_split_lowtohigh($y,$radix) ]; foreach (1 .. $x) { # x=0 unpermuted Math::PlanePath::GrayCode::_digits_to_gray_reflected($digits,$radix); } push @got, digit_join_lowtohigh($digits,$radix); } if (! numeq_array(\@got, $bvalues)) { MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..10])); MyTestHelpers::diag ("got: ",join(',',@got[0..10])); } } skip (! $bvalues, numeq_array(\@got, $bvalues), 1); } #------------------------------------------------------------------------------ # A064706 - binary gray reflected permutation applied twice { my $anum = 'A064706'; my $radix = 2; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my @got; if ($bvalues) { for (my $n = 0; @got < @$bvalues; $n++) { push @got, to_binary_gray(to_binary_gray($n)); } if (! numeq_array(\@got, $bvalues)) { MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..20])); MyTestHelpers::diag ("got: ",join(',',@got[0..20])); } } skip (! $bvalues, numeq_array(\@got, $bvalues), 1, "$anum - binary gray applied twice"); } #------------------------------------------------------------------------------ # A055975 - binary gray first diffs { my $anum = 'A055975'; my $radix = 2; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my @got; if ($bvalues) { my $prev = 0; for (my $n = 1; @got < @$bvalues; $n++) { my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_to_gray_reflected($digits,$radix); my $gray = digit_join_lowtohigh($digits,$radix); push @got, $gray - $prev; $prev = $gray; } if (! numeq_array(\@got, $bvalues)) { MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..20])); MyTestHelpers::diag ("got: ",join(',',@got[0..20])); } } skip (! $bvalues, numeq_array(\@got, $bvalues), 1, "$anum - binary gray first diffs"); } #------------------------------------------------------------------------------ # A055975 - binary gray first diffs { my $anum = 'A055975'; my $radix = 2; my ($bvalues, $lo, $filename) = MyOEIS::read_values($anum); my @got; if ($bvalues) { my $prev = 0; for (my $n = 1; @got < @$bvalues; $n++) { my $digits = [ digit_split_lowtohigh($n,$radix) ]; Math::PlanePath::GrayCode::_digits_to_gray_reflected($digits,$radix); my $gray = digit_join_lowtohigh($digits,$radix); push @got, $gray - $prev; $prev = $gray; } if (! numeq_array(\@got, $bvalues)) { MyTestHelpers::diag ("bvalues: ",join(',',@{$bvalues}[0..20])); MyTestHelpers::diag ("got: ",join(',',@got[0..20])); } } skip (! $bvalues, numeq_array(\@got, $bvalues), 1, "$anum - binary gray first diffs"); } #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/xt/DragonCurve-more.t0000644000175000017500000000542412136177170015710 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 Math::PlanePath::DragonCurve; use Test; plan tests => 28; use lib 't','xt'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use MyOEIS; # uncomment this to run the ### lines #use Smart::Comments '###'; #------------------------------------------------------------------------------ # 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-122/xt/0-Test-Pod.t0000755000175000017500000000175111655356337014340 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-122/xt/oeis-xrefs.t0000755000175000017500000001300312563465610014613 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 . # 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 among xt/oeis/*-oeis.t scripts, # 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 explicitly listed here. # use 5.005; use strict; use FindBin; use ExtUtils::Manifest; use File::Spec; use File::Slurp; use Test::More; use List::MoreUtils; 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 plan tests => 1; 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 $bad = 0; my $RE_OEIS_anum = qr/A\d{6,7}/; #------------------------------------------------------------------------------ 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): +(A\d+)([^#]+)/) { my $anum = $2; my @args = split /\s/, $3; my %args = map { split /=/, $_, 2 } @args; ### %args my $planepath = $args{'planepath'} || die "Oops, no planepath parameter"; my ($path_name, @path_args) = split /,/, $planepath; push @{$path_seq_anums{$path_name}}, $anum; } } } foreach (values %path_seq_anums) { $_ = [ List::MoreUtils::uniq(@$_) ]; } #------------------------------------------------------------------------------ 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 ($path_name) = @_; my $filename = "lib/Math/PlanePath/$path_name.pm"; open my $fh, '<', $filename or die "Oops, cannot open module filename $filename"; my @ret; while (<$fh>) { if (/^ +($RE_OEIS_anum)/) { push @ret, $1; } } return @ret; } sub path_checked_anums { my ($path_name) = @_; return (path_xt_anums ($path_name), @{$path_seq_anums{$path_name} || []}); } sub path_xt_anums { my ($path_name) = @_; my @ret; if (open my $fh, '<', "xt/oeis/$path_name-oeis.t") { while (<$fh>) { if (/^[^#]*\$anum = '($RE_OEIS_anum)'/mg) { push @ret, $1; } if (/^[^#]*anum => '($RE_OEIS_anum)'/mg) { push @ret, $1; } } } return @ret; } sub str_duplicates { my %seen; return map {$seen{$_}++ == 1 ? ($_) : ()} @_; } foreach my $path_name (@path_names) { my @pod_anums = path_pod_anums ($path_name); my @checked_anums = path_checked_anums ($path_name); my %pod_anums = map {$_=>1} @pod_anums; my %checked_anums = map {$_=>1} @checked_anums; foreach my $anum (str_duplicates(@pod_anums)) { diag "Math::PlanePath::$path_name duplicate pod $anum"; } @pod_anums = List::MoreUtils::uniq(@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::$path_name duplicate check $anum"; } @checked_anums = List::MoreUtils::uniq(@checked_anums); diag ""; foreach my $anum (@pod_anums) { next if $anum eq 'A191689'; # CCurve fractal dimension if (! exists $checked_anums{$anum}) { diag "Math::PlanePath::$path_name pod anum $anum 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 'A081274'; # oeis duplicate next if $anum eq 'A000035'; # 0,1 reps next if $anum eq 'A059841'; # 1,0 reps next if $anum eq 'A165211'; # 0,1,0,1, 1,0,1,0, repeating if (! exists $pod_anums{$anum}) { diag "Math::PlanePath::$path_name checked anum $anum not in pod"; } } } is ($bad, 0); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/Makefile.PL0000755000175000017500000000517712641634711013673 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 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, }, }, }, }, ); Math-PlanePath-122/examples/0002755000175000017500000000000012641645163013527 5ustar ggggMath-PlanePath-122/examples/knights-oeis.pl0000755000175000017500000000356012041154023016455 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-122/examples/cellular-rules.pl0000755000175000017500000000636112041153426017014 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-122/examples/koch-svg.pl0000644000175000017500000000531012041154170015565 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-122/examples/ulam-spiral-xpm.pl0000755000175000017500000000600512041155744017111 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-122/examples/sacks-xpm.pl0000755000175000017500000000362712041155624015773 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-122/examples/hilbert-path.pl0000755000175000017500000000503212041154004016427 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-122/examples/rationals-tree.pl0000644000175000017500000001067512136175114017015 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-122/examples/cretan-walls.pl0000644000175000017500000000436011746612502016455 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-122/examples/hilbert-oeis.pl0000755000175000017500000000425612066001433016445 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-122/examples/other/0002755000175000017500000000000012641645163014650 5ustar ggggMath-PlanePath-122/examples/other/sierpinski-triangle.m40000644000175000017500000000243512241344134021065 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-122/examples/other/sierpinski-triangle-text.gnuplot0000644000175000017500000000332012062333616023215 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-122/examples/other/dragon-curve.logo0000644000175000017500000000554512335526416020134 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-122/examples/other/fibonacci-word-fractal.logo0000644000175000017500000000435112335737560022036 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-122/examples/other/sierpinski-triangle-replicate.gnuplot0000644000175000017500000000324712041164144024204 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-122/examples/other/dragon-curve.m40000644000175000017500000001373012221425116017474 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-122/examples/other/sierpinski-triangle-bitand.gnuplot0000644000175000017500000000264612177346233023512 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-122/examples/other/flowsnake-ascii-small.gp0000644000175000017500000001000112544112624021344 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-122/examples/other/dragon-curve.el0000644000175000017500000000771212241340154017557 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-122/examples/other/dragon-recursive.gri0000644000175000017500000000644312217673641020640 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-122/examples/other/dragon-pgf-plain.tex0000644000175000017500000000466512246063147020525 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-122/examples/other/sierpinski-triangle-text.logo0000644000175000017500000000304012062333621022460 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-122/examples/other/dragon-pgf-latex.tex0000644000175000017500000000334112246063234020522 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-122/examples/other/dragon-curve.gnuplot0000644000175000017500000000554712041161321020646 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-122/examples/square-numbers.pl0000755000175000017500000000332512041155563017033 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-122/examples/numbers.pl0000755000175000017500000004474412551140631015543 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 . # 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', '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', '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', 'CornerReplicate', 'LTiling', 'LTiling,L_fill=ends', 'LTiling,L_fill=all', 'DigitGroups', 'FibonacciWordFractal', 'Flowsnake', 'Flowsnake,arms=3', 'FlowsnakeCentres', 'FlowsnakeCentres,arms=3', 'GosperReplicate', 'GosperIslands', 'GosperSide', 'QuintetCurve', 'QuintetCurve,arms=4', 'QuintetCentres', 'QuintetReplicate', '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', '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-122/examples/c-curve-wx.pl0000644000175000017500000006661612641634400016071 0ustar gggg#!/usr/bin/perl -w # Copyright 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 . # Usage: perl c-curve-wx.pl # # This is a WxWidges 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 as triangles (on the expansion side # of the 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.) # # 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 move or change 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 = 122; 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 => '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; ($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,$width,0,$height) || xy_in_rect($prev_x,$prev_y, 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); ($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-122/devel/0002755000175000017500000000000012641645163013010 5ustar ggggMath-PlanePath-122/devel/r5.pl0000644000175000017500000000470311507022742013665 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-122/devel/sierpinski-arrowhead.pl0000644000175000017500000001007712021370606017467 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::SierpinskiArrowhead; # 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 => '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); print "$i $i3 $value $calc\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; } 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); 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 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-122/devel/koch-squareflakes.pl0000644000175000017500000001174012375744415016762 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-122/devel/cubic-base.pl0000644000175000017500000000534212003406621015326 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-122/devel/pixel-rings.pl0000644000175000017500000001514611667347222015615 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 (); use List::Util 'min', 'max'; # uncomment this to run the ### lines use Smart::Comments; { # vs spectrum 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 .. 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-122/devel/fibonacci-word.logo0000644000175000017500000000270712335325716016563 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-122/devel/theodorus.pl0000644000175000017500000002562712040145574015365 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-122/devel/exe-atan2.c0000644000175000017500000000347212005653477014745 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-122/devel/sierpinski-triangle.gnuplot0000644000175000017500000000663612041164262020404 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-122/devel/sierpinski-arrowhead-stars.pl0000644000175000017500000000263411612663016020626 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-122/devel/quintet.pl0000644000175000017500000001176711755772113015051 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 Math::Libm 'M_PI', 'hypot'; { 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-122/devel/r5-dragon.pl0000644000175000017500000007646012435205200015137 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-122/devel/greek-key.pl0000644000175000017500000000561111774517323015233 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-122/devel/vogel.pl0000644000175000017500000002316112067770710014461 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-122/devel/beta-omega.pl0000644000175000017500000000402012507664322015337 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-122/devel/flowsnake.pl0000644000175000017500000004755012561316063015342 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.010; use strict; use warnings; use FindBin; use Math::Libm 'M_PI', 'hypot'; 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; { } { # 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'); sub path_n_to_tturn6 { 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 sub calc_n_to_tturn6 { # 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-122/devel/diagonals.pl0000644000175000017500000000643712157255652015320 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-122/devel/ulam-warburton.pl0000644000175000017500000001324112400225034016303 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-122/devel/corner-replicate.pl0000644000175000017500000000226112157300664016576 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-122/devel/terdragon.pl0000644000175000017500000011502612561577603015341 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::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 { # 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 ($x,$y) = $path->n_to_xy($n); my @n_list = $path->xy_to_n_list($x,$y); 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_dir { 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); } Math-PlanePath-122/devel/cellular-rule-oeis.pl0000644000175000017500000000440612611264071017044 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-122/devel/sierpinski-triangle.pl0000644000175000017500000002672712536646441017347 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-122/devel/fibonacci-word.pl0000644000175000017500000001631712150501071016221 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-122/devel/bignums.pl0000644000175000017500000000576512136655673015032 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 POSIX (); # uncomment this to run the ### lines use Devel::Comments; # 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-122/devel/tree.pl0000644000175000017500000001403111765112630014273 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-122/devel/multiple-rings.pl0000644000175000017500000003552012601460724016315 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-122/devel/gray.pl0000644000175000017500000002003612514570663014307 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::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'; # uncomment this to run the ### lines use Smart::Comments; { 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; } Math-PlanePath-122/devel/quadric.pl0000644000175000017500000000761311753407263015002 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; { # 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; } } { # 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-122/devel/dragon.pl0000644000175000017500000027152012447345064014625 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 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 ($x, $y) = $path->n_to_xy ($n); my @n_list = $path->xy_to_n_list($x,$y); 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 ($x, $y) = $path->n_to_xy ($n); my @n_list = $path->xy_to_n_list($x,$y); 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 ($x,$y) = $path->n_to_xy($n) or return 0; my @n_list = $path->xy_to_n_list($x,$y); 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-122/devel/interpolate.pl0000644000175000017500000001502512165377675015707 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-122/devel/gosper-islands-stars.pl0000644000175000017500000000233411777406713017436 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-122/devel/koch-curve.pl0000644000175000017500000000426312252723363015413 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-122/devel/factor-rationals.pl0000644000175000017500000001773312236024533016616 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-122/devel/complex-minus.pl0000644000175000017500000010101012562515230016125 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-122/devel/grep-values.pl0000644000175000017500000016507312562515170015605 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 . # 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 # # A001541 # alt paper # A129284 A129150(n) / 4. # A129285 A129151(n) / 27. # A131128 Binomial transform of [1, 1, 5, 1, 5, 1, 5,...]. # # Math::PlanePath::AlternatePaper area: # = alt midpoint unit squares # A027383 Number of balanced strings of length n: let d(S)= #(1)'s in S - #(0)'s, then S is balanced if every substring T has -2<=d(T)<=2. # partial sums of A016116 # a(2n-1) = 2^(n+1)-2 = A000918(n+1). # a(2n) = 3*2^n-2 = A033484(n); # a(2n+1) = 2^(n+2)-2 # = 4*2^n-2 # Math::PlanePath::GosperReplicate unit hexagons boundary # A178674 = 3^n+3 use 5.010; use strict; use List::Util 'min', 'max'; use Module::Load; use Math::Libm 'hypot'; use List::Pairwise; use Math::BaseCnv; use lib 'xt'; use MyOEIS; use Math::OEIS::Grep; # uncomment this to run the ### lines # use Smart::Comments; { # 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 ($x,$y) = $path->n_to_xy($n); my @n_list = $path->xy_to_n_list($x,$y); 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 ($x,$y) = $path->n_to_xy($n); my @n_list = $path->xy_to_n_list($x,$y); $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--; } } { # 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 ($x,$y) = $path->n_to_xy($n); my @n_list = $path->xy_to_n_list($x,$y); if ($n_list[0] == $n) { @n_list = grep {$_<=$n_end} @n_list; $counts[scalar(@n_list)]++; } } shift @counts; return @counts; } } { # 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; } { # 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 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; @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; } { # 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; } { # permutation between two paths 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 = ('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; 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 = "$from_fullname -> $to_fullname"; 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; } { # 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-122/devel/cfrac-digits.pl0000644000175000017500000001400412155466372015704 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-122/devel/pythagorean.pl0000644000175000017500000006014712252501660015663 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.010; use strict; use warnings; use List::Util 'min', 'max'; use Math::Libm 'hypot'; use Math::PlanePath::PythagoreanTree; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_join_lowtohigh', 'digit_split_lowtohigh'; # uncomment this to run the ### lines # use Smart::Comments; { # 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-122/devel/flowsnake-ascii.gp0000644000175000017500000002557212544112136016417 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-122/devel/dekking-curve.pl0000644000175000017500000001212212535705354016100 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.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'; # uncomment this to run the ### lines # use Smart::Comments; { # 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-122/devel/staircase-alternating.pl0000644000175000017500000000175011717623507017633 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-122/devel/alternate-paper.pl0000644000175000017500000004717012451351455016435 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::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 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, being the stair-step diagonal, are 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 { # 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 } Math-PlanePath-122/devel/square-spiral.pl0000644000175000017500000000226311722575776016151 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::SquareSpiral; # uncomment this to run the ### lines #use Smart::Comments; { 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-122/devel/gosper-side.pl0000644000175000017500000000526312402275640015564 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-122/devel/number-fraction.pl0000644000175000017500000000247011663776631016451 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 warnings; use lib '/so/perl/number-fraction/number-fraction/lib/'; use Number::Fraction; print Number::Fraction->VERSION,"\n"; # uncomment this to run the ### lines use Smart::Comments; { 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-122/devel/bigint-lite.pl0000644000175000017500000000403612523324765015556 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-122/devel/wythoff-array.pl0000644000175000017500000002230212240271436016135 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-122/devel/filled-rings.pl0000644000175000017500000000263611720341360015716 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-122/devel/vertical.pl0000644000175000017500000000514111520123441015136 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-122/devel/cellular-rule-xpm.pl0000644000175000017500000000341211646222723016712 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-122/devel/lib/0002755000175000017500000000000012641645162013555 5ustar ggggMath-PlanePath-122/devel/lib/Math/0002755000175000017500000000000012641645162014446 5ustar ggggMath-PlanePath-122/devel/lib/Math/SquareRadical.pm0000644000175000017500000001144012606435146017522 0ustar gggg# Copyright 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 . package Math::SquareRadical; use 5.004; use strict; use Carp 'croak'; use Scalar::Util 'blessed'; use vars '$VERSION', '@ISA'; $VERSION = 122; # 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-122/devel/lib/Math/PlanePath/0002755000175000017500000000000012641645163016323 5ustar ggggMath-PlanePath-122/devel/lib/Math/PlanePath/R7DragonCurve.pm0000644000175000017500000001634512606435145021316 0ustar gggg# Copyright 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 . # 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 = 122; @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-122/devel/lib/Math/PlanePath/FourReplicate.pm0000644000175000017500000000645512606435145021433 0ustar gggg# Copyright 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 . # 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 = 122; @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-122/devel/lib/Math/PlanePath/ZeckendorfTerms-oeis.t0000644000175000017500000000314212132222017022530 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-122/devel/lib/Math/PlanePath/PeanoVertices.pm0000644000175000017500000000733312606435145021432 0ustar gggg# works, worth having separately ? # alternating diagonals when even radix ? # 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 . # 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 = 122; 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-122/devel/lib/Math/PlanePath/four-replicate.pl0000644000175000017500000000360412165124417021575 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-122/devel/lib/Math/PlanePath/NxNinv.pm0000644000175000017500000000616712606435145020107 0ustar gggg# Copyright 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 . # A072732 # A072733 inverse # A072736 X coord # A072737 Y coord # # A072734 # A072740 X coord # A072741 Y coord package Math::PlanePath::NxNinv; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 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((sqrt(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-122/devel/lib/Math/PlanePath/WythoffDifference.pm0000644000175000017500000001116312606435145022260 0ustar gggg# Copyright 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 . package Math::PlanePath::WythoffDifference; use 5.004; use strict; use List::Util 'max'; use vars '$VERSION', '@ISA'; $VERSION = 122; 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-122/devel/lib/Math/PlanePath/squares-dispersion.pl0000644000175000017500000000565611770201234022517 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-122/devel/lib/Math/PlanePath/FibonacciWordKnott.pm0000644000175000017500000002302312606435145022406 0ustar gggg# 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 . # 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 = 122; 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-122/devel/lib/Math/PlanePath/SumFractions.pm0000644000175000017500000000720312606435145021274 0ustar gggg# Copyright 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 . package Math::PlanePath::SumFractions; use 5.004; use strict; use List::Util 'max'; use vars '$VERSION', '@ISA'; $VERSION = 122; 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) = @_; ### 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((sqrt(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-122/devel/lib/Math/PlanePath/MooreSpiral.pm0000644000175000017500000004256312606435145021123 0ustar gggg# 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/devel/lib/Math/PlanePath/WythoffLines.pm0000644000175000017500000002572512606435145021311 0ustar gggg# Copyright 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 . # 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 = 122; 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 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((sqrt(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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/devel/lib/Math/PlanePath/BinaryTerms-oeis.t0000644000175000017500000000663412132055333021700 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-122/devel/lib/Math/PlanePath/Godfrey.pm0000644000175000017500000000760512606435145020264 0ustar gggg# 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 . # 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 = 122; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem = \&Math::PlanePath::_divrem; 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((sqrt(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-122/devel/lib/Math/PlanePath/BinaryTerms.pm0000644000175000017500000002276512606435146021131 0ustar gggg# Copyright 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 . # 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 = 122; 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-122/devel/lib/Math/PlanePath/WythoffTriangle.pm0000644000175000017500000000564412606435145022002 0ustar gggg# Copyright 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 . package Math::PlanePath::WythoffTriangle; use 5.004; use strict; use List::Util 'max'; use vars '$VERSION', '@ISA'; $VERSION = 122; 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-122/devel/lib/Math/PlanePath/QuintetSide.pm0000644000175000017500000001702212606435145021115 0ustar gggg# mostly works, but any good ? # 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/devel/lib/Math/PlanePath/ParabolicRows.pm0000644000175000017500000000722212606435145021427 0ustar gggg# Copyright 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 . # 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 = 122; 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 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 = int(sqrt($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 = sqrt($n); if ($sqrt*$sqrt < $n) { $sqrt += 1; } return $sqrt; } 1; __END__ Math-PlanePath-122/devel/lib/Math/PlanePath/PyramidReplicate.pm0000644000175000017500000001643512606435145022124 0ustar gggg# 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/devel/lib/Math/PlanePath/wythoff-lines.pl0000644000175000017500000000254012375744415021461 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-122/devel/lib/Math/PlanePath/NxNvar.pm0000644000175000017500000000602012606435145020067 0ustar gggg# Copyright 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 . package Math::PlanePath::NxNvar; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 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((sqrt(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-122/devel/lib/Math/PlanePath/godfrey.pl0000644000175000017500000000277212375744415020331 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-122/devel/lib/Math/PlanePath/WythoffDifference-oeis.t0000644000175000017500000000654512113223613023040 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::WythoffDifference; sub BIGINT { require Math::NumSeq::PlanePathN; return Math::NumSeq::PlanePathN::_bigint(); } #------------------------------------------------------------------------------ # A191361 -- Wythoff difference array X-Y, diagonal containing n MyOEIS::compare_values (anum => 'A191361', func => sub { my ($count) = @_; require Math::PlanePath::Diagonals; 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; }); #------------------------------------------------------------------------------ # A080164 -- Wythoff difference array by anti-diagonals MyOEIS::compare_values (anum => 'A080164', func => sub { my ($count) = @_; require Math::PlanePath::Diagonals; 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; }); #------------------------------------------------------------------------------ # 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 = 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 = 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 = BIGINT()->new(0); @got < $count; $x++) { push @got, $path->xy_to_n ($x, 0); } return \@got; }); #------------------------------------------------------------------------------ exit 0; Math-PlanePath-122/devel/lib/Math/PlanePath/BalancedArray.pm0000644000175000017500000000642512606435146021355 0ustar gggg# Copyright 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 . package Math::PlanePath::BalancedArray; use 5.004; use strict; use List::Util 'max'; use vars '$VERSION', '@ISA'; $VERSION = 122; 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-122/devel/lib/Math/PlanePath/WythoffTriangle-oeis.t0000644000175000017500000000375012112751302022546 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-122/devel/lib/Math/PlanePath/PowerRows.pm0000644000175000017500000000757512606435145020642 0ustar gggg# Copyright 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 . package Math::PlanePath::PowerRows; use 5.004; use strict; #use List::Util 'min', 'max'; *min = \&Math::PlanePath::_min; use vars '$VERSION', '@ISA'; $VERSION = 122; 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-122/devel/lib/Math/PlanePath/PeanoHalf.pm0000644000175000017500000002373112606435145020520 0ustar gggg# 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/devel/lib/Math/PlanePath/Z2DragonCurve.pm0000644000175000017500000001137012606435144021311 0ustar gggg# 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 . # 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 = 122; @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-122/devel/lib/Math/PlanePath/NxN.pm0000644000175000017500000000602512606435145017363 0ustar gggg# Copyright 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 . package Math::PlanePath::NxN; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 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((sqrt(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-122/devel/lib/Math/PlanePath/z2-dragon.pl0000644000175000017500000000566712300052537020464 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-122/devel/lib/Math/PlanePath/ZeckendorfTerms.pm0000644000175000017500000000760212606435144021766 0ustar gggg# Copyright 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 . # 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 = 122; 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-122/devel/lib/Math/PlanePath/PeanoRounded.pm0000644000175000017500000003400512606435145021242 0ustar gggg# works, worth having separately ? # alternating diagonals when even radix ? # 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 . # 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 = 122; 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 Guiseppe 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 Guiseppe 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/devel/lib/Math/PlanePath/ParabolicRuns.pm0000644000175000017500000000476612606435145021436 0ustar gggg# Copyright 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 . package Math::PlanePath::ParabolicRuns; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; 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-122/devel/lib/Math/square-radical.pl0000644000175000017500000000166212171603336017676 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-122/devel/fractions-tree.pl0000644000175000017500000000320011745170634016263 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-122/devel/numseq.pl0000644000175000017500000005277712606146374014676 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 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"; my $seq = Math::NumSeq::PlanePathTurn->new (planepath => $planepath, turn_type => 'LSR'); # $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-122/devel/biguv.pl0000644000175000017500000000207311753117277014464 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-122/devel/t-square.pl0000644000175000017500000000467212255722606015114 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-122/devel/exe-complex-minus.c0000644000175000017500000000640211701770574016534 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-122/devel/gcd-rationals-integer.pl0000644000175000017500000000325011702424166017520 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-122/devel/cont-frac.pl0000644000175000017500000000257411535000617015215 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-122/devel/Makefile0000644000175000017500000000203012530306624014432 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-122/devel/complex-revolving.pl0000644000175000017500000000703511703471336017025 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-122/devel/exe-complex-plus.c0000644000175000017500000000640011702125647016356 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-122/devel/run.pl0000644000175000017500000004443412551133311014143 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 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::TerdragonCurve'; $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::TerdragonRounded'; $path_class = 'Math::PlanePath::HilbertSpiral'; $path_class = 'Math::PlanePath::GreekKeySpiral'; $path_class = 'Math::PlanePath::ComplexMinus'; $path_class = 'Math::PlanePath::QuintetReplicate'; $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::QuintetCurve'; $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::PeanoCurve'; $path_class = 'Math::PlanePath::CellularRule190'; $path_class = 'Math::PlanePath::CellularRule54'; $path_class = 'Math::PlanePath::PeanoVertices'; $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::AlternatePaper'; $path_class = 'Math::PlanePath::HilbertSides'; my $lo = 0; my $hi = 32; Module::Load::load($path_class); my $path = $path_class->new ( # arms => 8, # align => 'right', # parts => 'left', # direction => 'up', # coordinates => 'ST', # tree_type => 'UAD', # ring_shape => 'polygon', # step => 1, # sign_encoding => 'revbinary', # n_start => 0, # parts => 'wedge', # shift => 6, # pn_encoding => 'negabinary', # points => 'all_mul', # k => 4, # digit_order => 'HtoL', # digit_order => 'LtoH', # reduced => 1, # radix => 4, # rule => 14, # x_start => 5, # y_start => 2, # divisor_type => 'proper', # wider => 3, # 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; } Math-PlanePath-122/devel/archimedean.pl0000644000175000017500000002314212000752040015563 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-122/devel/square-spiral.gnuplot0000644000175000017500000000733712026721454017215 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-122/devel/rationals-tree.pl0000644000175000017500000010440112375744415016300 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 POSIX (); use List::Util 'sum'; use Math::PlanePath::Base::Digits 'round_down_pow', 'digit_split_lowtohigh', 'digit_join_lowtohigh'; use Math::PlanePath::RationalsTree; # uncomment this to run the ### lines use Smart::Comments; { # 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; } { # lamplighter require Math::NumSeq::OEIS::File; my $lamp = Math::NumSeq::OEIS::File->new(anum=>'A154435'); my $n = 0b1000001000; my $l = $lamp->ith($n); printf "%d %b = %d\n", $n, $l, $l; 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; } { # permutations in N row my $choices = Math::PlanePath::RationalsTree->parameter_info_hash ->{'tree_type'}->{'choices'}; my %seen; foreach my $from_type (@$choices) { 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_path = Math::PlanePath::RationalsTree->new (tree_type => $to_type); { 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); $str .= "$to_n,"; } next if $seen{$str}++; print "$from_type->$to_type http://oeis.org/search?q=$str\n"; } { 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; } { # 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-122/devel/hilbert-diamond.pl0000644000175000017500000000312712023454743016404 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-122/devel/cellular-rule62.pl0000644000175000017500000000314311646221732016260 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-122/devel/l-tiling.pl0000644000175000017500000000532312142113042015043 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-122/devel/triangular-spiral.pl0000644000175000017500000000326412551130024016771 0ustar gggg#!/usr/bin/perl -w # 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 . use 5.004; use strict; # uncomment this to run the ### lines # use Smart::Comments; { # 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-122/devel/hexhypot.pl0000644000175000017500000000305411566406004015207 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-122/devel/gcd-rationals.pl0000644000175000017500000001110212307003455016054 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-122/devel/flowsnake-levels.pl0000644000175000017500000000441611620710560016617 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-122/devel/junk/0002755000175000017500000000000012641645162013756 5ustar ggggMath-PlanePath-122/devel/junk/FibonacciSquareSpiral.pm0000644000175000017500000001043012145611505020512 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-122/devel/hex-arms.pl0000644000175000017500000001270112014333612015053 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-122/devel/chan-tree.pl0000644000175000017500000001237012155012130015172 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-122/devel/sierpinski-curve.pl0000644000175000017500000003260712611353343016646 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-122/devel/mephisto-waltz.logo0000644000175000017500000000255212061163535016655 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-122/devel/grid.pl0000644000175000017500000000205411423250646014264 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-122/devel/hypot.pl0000644000175000017500000000511011767504527014511 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-122/devel/koch-snowflakes.pl0000644000175000017500000001766212375744415016461 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-122/devel/cellular-rule.pl0000644000175000017500000000711412042067504016106 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-122/devel/peano.pl0000644000175000017500000001541012136645623014446 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; # uncomment this to run the ### lines #use Smart::Comments; { # dx,dy on even radix require Math::PlanePath::PeanoCurve; require Math::BigInt; require Math::BaseCnv; 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 = Math::BaseCnv::cnv($x,10,$radix); my $dr = Math::BaseCnv::cnv($dx,10,$radix); my $nr = Math::BaseCnv::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 = Math::BaseCnv::cnv($dy,10,$radix); my $nr = Math::BaseCnv::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; require Math::BaseCnv; 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 = Math::BaseCnv::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; require Math::BaseCnv; 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 = Math::BaseCnv::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; require Math::BaseCnv; 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 = 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 : -1; printf "%d %s %.5f %s %s %.3f\n", $i, $ri, $value, $rdx,$rdy, $f; $max = $value; } } exit 0; } Math-PlanePath-122/devel/theodorus.gnuplot0000644000175000017500000000210111463457477016440 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-122/devel/aztec-diamond.pl0000644000175000017500000000732112624206362016060 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-122/devel/imaginary-base.pl0000644000175000017500000001475012003102512016214 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-122/devel/digit-groups.pl0000644000175000017500000001111212000734245015741 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-122/devel/imaginary-half.pl0000644000175000017500000000546512003406621016227 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-122/devel/alternate-paper-midpoint.pl0000644000175000017500000001521012023012405020224 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-122/devel/sacks.pl0000644000175000017500000000204211617621117014440 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-122/devel/c-curve.pl0000644000175000017500000030356412624206404014712 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','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) = local(h); 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) = local(h); 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) = local(h); 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) = local(h); 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-122/devel/hilbert.pl0000644000175000017500000005162612565303500014775 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 2011, 2012, 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.010; use strict; use List::Util 'min','max'; use Math::PlanePath::HilbertCurve; use Math::PlanePath::Base::Digits 'digit_split_lowtohigh'; use Smart::Comments; { # 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 number of overlaps # 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) ) 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 N 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 ]); # 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 # 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 */ 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; } { # 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::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; } { 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; } { 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 # 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; # 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-122/devel/quintet-replicate.pl0000644000175000017500000000610412003406621016765 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 Math::Libm 'M_PI', 'hypot'; { # 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; } { # 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; } Math-PlanePath-122/devel/iterator.pl0000644000175000017500000001704711604443335015200 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-122/devel/misc.pl0000644000175000017500000000216412400452234014265 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-122/devel/divisible-columns.pl0000644000175000017500000000245611775424704017005 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-122/devel/exe-complex-revolving.c0000644000175000017500000000554511701165116017411 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-122/lib/0002755000175000017500000000000012641645162012456 5ustar ggggMath-PlanePath-122/lib/Math/0002755000175000017500000000000012641645162013347 5ustar ggggMath-PlanePath-122/lib/Math/NumSeq/0002755000175000017500000000000012641645162014557 5ustar ggggMath-PlanePath-122/lib/Math/NumSeq/PlanePathDelta.pm0000644000175000017500000045667612606435146017771 0ustar gggg# 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 . # 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 = 122; 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::SquareSpiral; 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; # 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_DSquared_max => 1; # NSEW only 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_DSquared_max => 1; # NSEW only 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; } { 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; use constant _NumSeq_Delta_DSquared_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_DSquared_max => 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::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_DSquared_max => 1; # NSEW only 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_DSquared_max => 1; # NSEW only 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; # # inherit from FlowsnakeCentres # } { 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_dSum_integer => 1; use constant _NumSeq_Delta_dSumAbs_integer => 1; use constant _NumSeq_Delta_dDiffXY_integer => 1; use constant _NumSeq_Delta_dAbsDiff_integer => 1; 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_DSquared_max => 1; # NSEW only 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_DSquared_max => 1; # NSEW only 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_DSquared_max => 1; # NSEW only 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_DSquared_max => 1; # NSEW only 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_DSquared_max => 1; # NSEW only 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_DSquared_max => 1; # NSEW only 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_DSquared_max => 1; # NSEW only 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_DSquared_max => 1; # NSEW only 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_DSquared_max => 1; # NSEW only 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::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::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, except 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_DSquared_max => 1; # NSEW only 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; use constant _NumSeq_Delta_DSquared_max => 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_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_DSquared_max => 1; # NSEW only use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } { package Math::PlanePath::BetaOmega; # NSEW 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_DSquared_max => 1; # NSEW only use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } { package Math::PlanePath::DekkingCurve; # NSEW use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_dSumAbs_min => -1; use constant _NumSeq_Delta_dSumAbs_max => 1; # 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_DSquared_max => 1; # NSEW only 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_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_DSquared_max => 1; # NSEW only use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } { package Math::PlanePath::WunderlichMeander; # NSEW use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_DSquared_max => 1; # NSEW only use constant _NumSeq_Delta_Dist_non_decreasing => 1; use constant _NumSeq_Delta_TDSquared_max => 3; } { package Math::PlanePath::HIndexing; # NSEW use constant _NumSeq_Delta_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_DSquared_max => 1; # NSEW only 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_dAbsDiff_min => -1; use constant _NumSeq_Delta_dAbsDiff_max => 1; use constant _NumSeq_Delta_DSquared_max => 1; # NSEW only 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 =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 a particular arm). i values start from the usual C<$path-En_start()>. =head2 AbsdX,AbsdY If a path always step NSEW by 1 then AbsdX and AbsdY behave as a boolean indicating horizontal or vertical step, NSEW steps by 1 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 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 "taxi-cab" distance from the origin, or equivalently a move between diamond shaped rings. As an example, a path such as C follows a diamond shape ring around and so has dSumAbs=0 until stepping out to the next diamond with dSumAbs=1. A path might make a big jump which is only a small change in SumAbs. For example C in its default step=2 going 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, measured 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) or away from (positive) the origin, ignoring the direction. Notice that dRadius is not sqrt(dRSquared), since sqrt(n^2-t^2) != n-t unless n or t is zero (which here would mean a step 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, being 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 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 in steps of 1.5. Verticals North and South normally doesn't occur in the triangular lattice paths but TDir6 can be applied on any path. The sqrt(3) factor increases angles in the middle of the quadrants away from the axes. 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 SEE ALSO L, L, L, L L =head1 HOME PAGE L =head1 LICENSE 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 . =cut Math-PlanePath-122/lib/Math/NumSeq/PlanePathN.pm0000644000175000017500000035747512606435146017132 0ustar gggg# 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 . # 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 = 122; 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 { # Crib note: don't change the back-end if already loaded unless (Math::BigInt->can('new')) { require Math::BigInt; eval { Math::BigInt->import (try => 'GMP') }; } 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; 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::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,i_start=1' => { X_axis => 'A037314', # base 9 digits 0,1,2 only, starting OFFSET=1 value=1 Y_axis => 'A208665', # base 9 digits 0,3,6 only, starting OFFSET=1 value=3 # OEIS-Catalogue: A037314 planepath=ZOrderCurve,radix=3 i_start=1 # 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 # OEIS-Catalogue: A051022 planepath=ZOrderCurve,radix=10 }, }; } { 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::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 # OEIS-Catalogue: A066321 planepath=ComplexMinus # 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' => { X_axis => 'A001477', # integers 0,1,2,3,etc # OEIS-Other: A001477 planepath=Rows,width=1,n_start=0 }, '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::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', # # 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 }, }; } { 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 "Y_axis" Y axis "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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/NumSeq/PlanePathCoord.pm0000644000175000017500000056477012606435146020001 0ustar gggg# 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 . 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 = 122; use Math::NumSeq; @ISA = ('Math::NumSeq'); use Math::PlanePath; *_divrem = \&Math::PlanePath::_divrem; 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', # 'ExperimentalNumSurround4', # 'ExperimentalNumSurround4d', # 'ExperimentalNumSurround6', # 'ExperimentalNumSurround8', # 'ExperimentalNumOverlap', # 'ExperimentalRevisit', ]; 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 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=ExperimentalNumSurround4,planepath=DragonCurve --path=DragonCurve --scale=10 { my $surround = [ 1,0, 0,1, -1,0, 0,-1 ]; sub _coordinate_func_ExperimentalNumSurround4 { my ($self, $n) = @_; return _path_n_surround_count ($self->{'planepath_object'}, $n, $surround); } } { my $surround = [ 1,1, -1,1, -1,-1, 1,-1 ]; sub _coordinate_func_ExperimentalNumSurround4d { my ($self, $n) = @_; return _path_n_surround_count ($self->{'planepath_object'}, $n, $surround); } } { my $surround = [ 2,0, 1,1, -1,1, -2,0, -1,-1, 1,-1 ]; sub _coordinate_func_ExperimentalNumSurround6 { my ($self, $n) = @_; return _path_n_surround_count ($self->{'planepath_object'}, $n, $surround); } } { my $surround = [ 1,0, 0,1, -1,0, 0,-1, 1,1, -1,1, 1,-1, -1,-1 ]; sub _coordinate_func_ExperimentalNumSurround8 { my ($self, $n) = @_; return _path_n_surround_count ($self->{'planepath_object'}, $n, $surround); } } sub _path_n_surround_count { my ($path, $n, $surround_aref) = @_; # my $aref = $surround[$num_points] # || croak "_path_n_surround_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 < @$surround_aref; $i+=2) { $count += $path->xy_is_visited($x + $surround_aref->[$i], $y + $surround_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++; } } sub _coordinate_func_ExperimentalRevisit { my ($self, $n) = @_; return path_n_to_revisit($self->{'planepath_object'}, $n); } # $path->n_to_other_count($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_list($path,$n)) { if ($n == $n_list) { return $ret; } $ret++; } } return undef; } # $path->n_to_list($n) # Return the list of N points which are at the same X,Y as C<$n>. # C<$n> itself is included in the return. sub path_n_to_list { my ($path, $n) = @_; my ($x, $y) = $path->n_to_xy($n) or return; return $path->xy_to_n_list($x,$y); } # 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_ExperimentalNumSurround4_min => 0; use constant _NumSeq_Coord_ExperimentalNumSurround6_min => 0; use constant _NumSeq_Coord_ExperimentalNumSurround8_min => 0; use constant _NumSeq_Coord_ExperimentalNumSurround4 => 4; use constant _NumSeq_Coord_ExperimentalNumSurround6 => 6; use constant _NumSeq_Coord_ExperimentalNumSurround8 => 8; use constant _NumSeq_Coord_ExperimentalNumSurround4_integer => 1; # always integers use constant _NumSeq_Coord_ExperimentalNumSurround6_integer => 1; use constant _NumSeq_Coord_ExperimentalNumSurround8_integer => 1; use constant _NumSeq_Coord_oeis_anum => {}; #------ # 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_ExperimentalRevisit_min => 0; use constant _NumSeq_Coord_ExperimentalRevisit_max => 0; } { package Math::PlanePath::SquareSpiral; 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 # OEIS-Catalogue: A174344 planepath=SquareSpiral coordinate_type=X # OEIS-Catalogue: A214526 planepath=SquareSpiral coordinate_type=SumAbs }, '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; sub _NumSeq_Coord_MaxAbs_non_decreasing { my ($self) = @_; return ($self->{'turns'} == 0); # when same as SquareSpiral } } { package Math::PlanePath::PyramidSpiral; use constant _NumSeq_Coord_oeis_anum => { 'n_start=0' => { 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_ExperimentalParity_max => 0; # even always } # { package Math::PlanePath::TriangleSpiralSkewed; # } { package Math::PlanePath::DiamondSpiral; 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 ExperimentalAbsY => 'A053616', # OEIS-Catalogue: A010751 planepath=DiamondSpiral,n_start=0 # OEIS-Other: A053616 planepath=DiamondSpiral,n_start=0 coordinate_type=ExperimentalAbsY }, }; } # { package Math::PlanePath::AztecDiamondRings; # } # { package Math::PlanePath::PentSpiralSkewed; # } { package Math::PlanePath::HexSpiral; # 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; } # { package Math::PlanePath::HexSpiralSkewed; # } { package Math::PlanePath::HexArms; use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always } # { package Math::PlanePath::HeptSpiralSkewed; # } # { package Math::PlanePath::AnvilSpiral; # } # { package Math::PlanePath::OctagramSpiral; # } # { package Math::PlanePath::KnightSpiral; # } # { package Math::PlanePath::CretanLabyrinth; # } { package Math::PlanePath::SquareArms; use constant _NumSeq_Coord_MaxAbs_non_decreasing => 1; # successive squares } # { package Math::PlanePath::DiamondArms; # } { 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; # } { package Math::PlanePath::Hypot; 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; 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))); # } } { 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: 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_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::WunderlichSerpentine; 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_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_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_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_oeis_anum => { 'radix=2' => { X => 'A059905', # alternate bits first Y => 'A059906', # alternate bits second # OEIS-Catalogue: A059905 planepath=ZOrderCurve coordinate_type=X # OEIS-Catalogue: A059906 planepath=ZOrderCurve coordinate_type=Y }, '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,i_start=1' => { # i_start=1 per A080463 offset=1, it skips initial zero Sum => 'A080463', SumAbs => 'A080463', # OEIS-Catalogue: A080463 planepath=ZOrderCurve,radix=10 coordinate_type=Sum i_start=1 # OEIS-Other: A080463 planepath=ZOrderCurve,radix=10 coordinate_type=SumAbs i_start=1 }, 'radix=10,i_start=10' => { # i_start=10 per A080464 OFFSET=10, it skips all but one initial zeros Product => 'A080464', # OEIS-Catalogue: A080464 planepath=ZOrderCurve,radix=10 coordinate_type=Product i_start=10 AbsDiff => 'A080465', # OEIS-Catalogue: A080465 planepath=ZOrderCurve,radix=10 coordinate_type=AbsDiff i_start=10 }, }; } { package Math::PlanePath::GrayCode; use constant _NumSeq_Coord_oeis_anum => { do { my $peano = { X => 'A163528', Y => 'A163529', Sum => 'A163530', SumAbs => 'A163530', RSquared => 'A163531', }; my $z = { BitXor => 'A059905', }; ('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 '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 ), }, }; } # { package Math::PlanePath::ImaginaryBase; # } # { package Math::PlanePath::ImaginaryHalf; # } { package Math::PlanePath::CubicBase; use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always } { package Math::PlanePath::Flowsnake; use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always } { 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_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 } { 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; } { 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; # # except 0/0=inf # # use constant _NumSeq_Coord_IntXY_max => 1; # upper octant X<=Y so X/Y<=1 # } { package Math::PlanePath::DragonCurve; use constant _NumSeq_Coord_ExperimentalNumSurround4_min => 2; # use constant _NumSeq_Coord_ExperimentalNumSurround6_min => 0; # ??? use constant _NumSeq_Coord_ExperimentalNumSurround8_min => 3; use constant _NumSeq_Coord_ExperimentalRevisit_max => 1; } { 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; { 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_ExperimentalRevisit_max => 1; use constant _NumSeq_Coord_oeis_anum => { 'i_start=1' => { DiffXY => 'A020990', # GRS*(-1)^n cumulative AbsDiff => 'A020990', # Not quite, OFFSET=0 value=1 which 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]; } } } { package Math::PlanePath::TerdragonCurve; use constant _NumSeq_Coord_ExperimentalParity_max => 0; # even always use constant _NumSeq_Coord_ExperimentalRevisit_max => 2; } { 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::R5DragonCurve; use constant _NumSeq_Coord_ExperimentalRevisit_max => 1; } # { package Math::PlanePath::R5DragonMidpoint; # } { package Math::PlanePath::CCurve; use constant _NumSeq_Coord_ExperimentalRevisit_max => 3; } # { package Math::PlanePath::ComplexPlus; # Sum X+Y < 0 at N=16 # } # { package Math::PlanePath::ComplexMinus; # } # { package Math::PlanePath::ComplexRevolving; # } { 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_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=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=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 }, '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 # 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 # 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 # 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 # 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 }, }; } { package Math::PlanePath::DiagonalsAlternating; 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' => { 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-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; # } # { package Math::PlanePath::Staircase; # } # { package Math::PlanePath::StaircaseAlternating; # } { package Math::PlanePath::Corner; 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', # manhatten 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 }, }; } { 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_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 # 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 }, }; } { 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 # 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 }, # 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_ExperimentalLeafDistance_max => 4; } { package Math::PlanePath::UlamWarburtonQuarter; 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; # } # { package Math::PlanePath::QuintetReplicate; # } # { package Math::PlanePath::AR2W2Curve; # } # { package Math::PlanePath::BetaOmega; # } # { package Math::PlanePath::KochelCurve; # } # { package Math::PlanePath::DekkingCurve; # } # { package Math::PlanePath::DekkingCentres; # } # { package Math::PlanePath::CincoCurve; # } # { package Math::PlanePath::SquareReplicate; # } { package Math::PlanePath::CornerReplicate; 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; # # 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; # 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; # 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_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; # } # { 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 Manhatten 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 "TRadius" sqrt(X^2+3*Y^2) triangular radius "TRSquared" X^2+3*Y^2 triangular radius squared "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 "Manhatten" 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-122/lib/Math/NumSeq/OEIS/0002755000175000017500000000000012641645162015316 5ustar ggggMath-PlanePath-122/lib/Math/NumSeq/OEIS/Catalogue/0002755000175000017500000000000012641645162017222 5ustar ggggMath-PlanePath-122/lib/Math/NumSeq/OEIS/Catalogue/Plugin/0002755000175000017500000000000012641645162020460 5ustar ggggMath-PlanePath-122/lib/Math/NumSeq/OEIS/Catalogue/Plugin/PlanePath.pm0000644000175000017500000013512112641645162022673 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 = 122; use Math::NumSeq::OEIS::Catalogue::Plugin; @ISA = ('Math::NumSeq::OEIS::Catalogue::Plugin'); ## no critic (CodeLayout::RequireTrailingCommaAtNewline) # total 245 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' => 'A180714', 'class' => 'Math::NumSeq::PlanePathCoord', 'parameters' => [ 'planepath', 'SquareSpiral,n_start=0', 'coordinate_type', 'Sum' ] }, { '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' => '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' => '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', 'i_start', 1 ] }, { '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' => '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' => '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' => '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' => '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' => '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' => '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' => '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' => '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', 'i_start', 1 ] }, { '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' => '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' => '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' => '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' => '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' => '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' => '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-122/lib/Math/NumSeq/PlanePathTurn.pm0000644000175000017500000016421512606435146017651 0ustar gggg# 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 . # 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 = 122; 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', 'LSR','SLR','SRL', # 'NotStraight', # 'Straight', # 'RSL', # 'Turn4', # Turn4 is 0<=value<4. # 'Turn4n', # 'TTurn6', ], 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); } # sub _turn_func_LR_01 { # my ($dx,$dy, $next_dx,$next_dy) = @_; # ### _turn_func_LR_01() ... # return ($next_dy * $dx >= $next_dx * $dy || 0); # } 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); } 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; } 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); } 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') { 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_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 # } { 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::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 # 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 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 # # 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::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; # Not quite, A096268 OFFSET=1 vs first turn N=1 here # Straight => 'A096268' } # { package Math::PlanePath::ComplexPlus; # } # { package Math::PlanePath::ComplexMinus; # } # { 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 # OEIS-Other: A023531 planepath=Diagonals,n_start=-1 turn_type=Right }, '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 }, }; } { 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 "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 i is the turn which occurs at N=i, i+1 ^ | | i-1 ---> i turn at i first turn at i = n_start + 1 For multiple "arms" the turn follows that particular arm so it's i-arms, i, i+arms. i values start C so i-arms is C, 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/0002755000175000017500000000000012641645163015224 5ustar ggggMath-PlanePath-122/lib/Math/PlanePath/TerdragonMidpoint.pm0000644000175000017500000006353312606435147021223 0ustar gggg# 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 . # 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 = 122; 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 ... return undef; } ### at: "$x,$y (k=$k) n=$n 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 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/FactorRationals.pm0000644000175000017500000006460312606435152020660 0ustar gggg# 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 . # 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 = 122; 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 =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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/TriangleSpiral.pm0000644000175000017500000002642612606435147020512 0ustar gggg# Copyright 2010, 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 . package Math::PlanePath::TriangleSpiral; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; # 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 + sqrt(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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/HilbertSides.pm0000644000175000017500000003220312611353341020130 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 . package Math::PlanePath::HilbertSides; use 5.004; use strict; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 =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. -------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 consecutive segments N=7to8 and N=8to9 overlap. Non-consecutive segments can overlap too, as for example N=27to28 and N=36to37 overlap. 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 Hilbert curve 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 forward straight 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 =for GP-DEFINE Xsegs(k) = 1/3*2^k + 1/2 + 1/6*(-1)^k; =for GP-DEFINE Ysegs(k) = 1/3*2^k - 1/2 + 1/6*(-1)^k; =for GP-Test vector(9,k,k--; Xsegs(k)) == [1,1,2,3,6,11,22,43,86] =for GP-Test vector(9,k,k--; Ysegs(k)) == [0,0,1,2,5,10,21,42,85] =for GP-DEFINE from_binary_vector(v) = subst(Polrev(v),'x,2); =for GP-Test from_binary_vector([1,1,0,1]) == 11 =for GP-DEFINE Ysegs_by_binary(k) = from_binary_vector(vector(max(0,k-1),i, (k-i)%2)); =for GP-Test vector(100,k,k--; Ysegs_by_binary(k)) == vector(100,k,k--; Ysegs(k)) 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 =head1 HOME PAGE L =head1 LICENSE 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 . =cut Math-PlanePath-122/lib/Math/PlanePath/DiagonalsAlternating.pm0000644000175000017500000002152712606435153021656 0ustar gggg# 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 . package Math::PlanePath::DiagonalsAlternating; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; use Math::PlanePath; @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 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( (sqrt(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 again, =cut # math-image --path=DiagonalsAlternating --expression='i<=31?i:0' --output=numbers =pod 7 | 29 6 | 28 30 5 | 16 27 31 4 | 15 17 26 ... 3 | 7 14 18 25 2 | 6 8 13 19 24 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 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/Flowsnake.pm0000644000175000017500000007473312606435152017523 0ustar gggg# Copyright 2010, 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 . # 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 # 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 = 122; # 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 =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 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 A229214 direction 1,2,3,-1,-2,-3 (clockwise) =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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/DiamondSpiral.pm0000644000175000017500000003323012606435153020304 0ustar gggg# Copyright 2010, 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 . package Math::PlanePath::DiamondSpiral; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; use Math::PlanePath; @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 _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 + sqrt(int(2*$n)-1)) / 2); #### $d #### d frac: ((1 + sqrt(int(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 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/Columns.pm0000644000175000017500000002073412606435153017203 0ustar gggg# Copyright 2010, 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 . package Math::PlanePath::Columns; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/DekkingCentres.pm0000644000175000017500000003014412606435153020457 0ustar gggg# 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 . package Math::PlanePath::DekkingCentres; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 =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 per =over F. M. Dekking, "Recurrent Sets", Advances in Mathematics, volume 44, 1982, pages 79-104, section 4.9 "Gosper-Type Curves" =back The form visits the "centres" of the 5x5 self-similar unit squares of Dekking's 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/StaircaseAlternating.pm0000644000175000017500000003244112606435147021673 0ustar gggg# 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 . # 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 = 122; use Math::PlanePath; @ISA = ('Math::PlanePath'); use Math::PlanePath::Base::Generic 'round_nearest'; use Math::PlanePath::Base::NSEW; # 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 + sqrt(int(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 + sqrt(int(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 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-122/lib/Math/PlanePath/HeptSpiralSkewed.pm0000644000175000017500000002254512606435152021002 0ustar gggg# Copyright 2010, 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 . package Math::PlanePath::HeptSpiralSkewed; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; use Math::PlanePath; @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 _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((sqrt(56*$n+9) + 11) / 14); ### $d ### d frac: (sqrt(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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/PythagoreanTree.pm0000644000175000017500000022344612606435150020666 0ustar gggg# 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 . # 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 = 122; use Math::PlanePath; *_divrem = \&Math::PlanePath::_divrem; @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 = int(sqrt($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 = int(sqrt($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 = int(sqrt($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 = int(sqrt($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 = int(sqrt($s)); return unless $q*$q == $s; return unless $r >= 1; my $p_plus_q = int(sqrt($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 = int(sqrt($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, et al "FB" Price "UMT" a third form 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 triples ordered as 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 # printed by tools/pythagorean-tree.pl =pod depth=0 depth=1 depth=2 depth=3 N=1 N=2..4 N=5..13 N=14... +-> 7,24 A,B coordinates +-> 5,12 --+-> 55,48 | +-> 45,28 | | +-> 39,80 3,4 --+-> 21,20 --+-> 119,120 | +-> 77,36 | | +-> 33,56 +-> 15,8 --+-> 65,72 +-> 35,12 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 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. =head2 A Repeatedly Taking the middle "A" matrix repeatedly gives 3,4 -> 21,20 -> 119,120 -> 697,696 -> etc which are the triples with legs A,B differing by 1 and therefore just above or 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 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 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. =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 / This is also described by XFibonacci in his XI (XI) 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 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. +-> 7,24 tree_type => "UArD" +-> 5,12 --+-> 55,48 A,B coordinates | +-> 45,28 | | +-> 77,36 <-+- U,D legs swapped 3,4 --+-> 21,20 --+-> 119,120 | | +-> 39,80 <-+ | | +-> 33,56 +-> 15,8 --+-> 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. 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 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 the 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 # printed by tools/pythagorean-tree.pl =pod N=1 N=2..4 N=5..13 N=14... +-> 9,40 A,B coordinates +-> 5,12 --+-> 35,12 | +-> 11,60 | | +-> 21,20 3,4 --+-> 15,8 --+-> 55,48 | +-> 39,80 | | +-> 13,84 +-> 7,24 --+-> 63,16 +-> 15,112 =head2 UMT Tree XOption C "UMT"> is a third tree type by Firstov (reference above). It's a combination of "U", "M2" and a third matrix T = M1*D. =cut # printed by tools/pythagorean-tree.pl =pod U +-> 7,24 A,B coordinates +-> 5,12 --+-> 35,12 | +-> 65,72 | | M2 +-> 33,56 3,4 --+-> 15,8 --+-> 55,48 | +-> 45,28 | | T +-> 39,80 +-> 21,20 --+-> 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 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 go clockwise too. =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, but with 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, but with 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 from the diagrams, the 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) 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 Diophantus, and anonymous Arabic manuscript for constraining it to primitive triples (Dickson's I). 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 both 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. =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 eighth, 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 0.7*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 0.7*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. A P,Q point 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 comes 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 "U" repeatedly A046092 coordinate B, 2n(n+1) = 4*triangular numbers A099776 \ coordinate C, being 2n(n+1)+1 A001844 / which is the "centred squares" "A" repeatedly A046727 \ coordinate A A084159 / "Pell oblongs" A046729 coordinate B A001653 coordinate C, numbers n where 2*n^2-1 is square A000129 coordinate P and Q, the Pell numbers A001652 coordinate S, the smaller leg A046090 coordinate M, the bigger leg "D" repeatedly A000466 coordinate A, being 4*n^2-1 for n>=1 "M1" repeatedly A028403 coordinate B, binary 10..010..000 A007582 coordinate B/4, binary 10..010..0 A085601 coordinate C, binary 10..010..001 "M2" repeatedly A015249 \ coordinate A, binary 111000111000... A084152 | A084175 / A054881 coordinate B, binary 1010..1010000..00 "M3" repeatedly A106624 coordinate P,Q pairs, 2^k-1,2^k "T" repeatedly A134057 coordinate A, binomial(2^n-1,2) binary 111..11101000..0001 A093357 coordinate B, binary 10111..111000..000 A052940 \ A055010 | coordinate 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/CoprimeColumns.pm0000644000175000017500000003205312606435153020517 0ustar gggg# 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 . # 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 = 122; 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 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-122/lib/Math/PlanePath/KochCurve.pm0000644000175000017500000006660012606435151017454 0ustar gggg# 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 . # 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, pages 3201-3212, # 1983. # package Math::PlanePath::KochCurve; use 5.004; use strict; use List::Util 'sum','first'; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 A177702 abs(dX) from N=1 onwards, being 1,1,2 repeating A011655 abs(dY), being 0,1,1 repeating A035263 turn 1=left,0=right, by morphism A096268 turn 0=left,1=right, by morphism A056832 turn 1=left,2=right, by replicate and flip last A029883 turn +/-1=left,0=right, Thue-Morse first differences A089045 turn +/-1=left,0=right, by +/- something A003159 N positions of left turns, ending even number 0 bits A036554 N positions of right turns, ending odd number 0 bits A020988 number of left turns N=0 to N < 4^k, being 2*(4^k-1)/3 A002450 number of right turns N=0 to N < 4^k, being (4^k-1)/3 A016153 area under the curve, (9^n-4^n)/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 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-122/lib/Math/PlanePath/FractionsTree.pm0000644000175000017500000003360612606435152020334 0ustar gggg# 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/ImaginaryHalf.pm0000644000175000017500000004540212606435151020273 0ustar gggg# Copyright 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 . 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 = 122; 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 quater-imaginary 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/CellularRule190.pm0000644000175000017500000003732712606435154020417 0ustar gggg# 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 . # 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 = 122; use Math::PlanePath; @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 ((sqrt(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 A071039 \ 1/0 used and unused cells across rows A118111 / 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/HexSpiral.pm0000644000175000017500000003425412606435152017463 0ustar gggg# Copyright 2010, 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 . # 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 = 122; use Math::PlanePath; @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((sqrt(int(3*$n) + ($w+2)*$w + 1) - 1 - $w) / 3); #### d frac: (sqrt(int(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 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 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/ChanTree.pm0000644000175000017500000011010312606435154017243 0ustar gggg# Copyright 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 . # 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 = 122; 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 =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 The C $integer> parameter 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/CornerReplicate.pm0000644000175000017500000004044112606435153020641 0ustar gggg# 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 . package Math::PlanePath::CornerReplicate; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 =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 the least significant bit then this is the 1-bits in even positions, HammingDist(X,Y) = count 1-bits at even bit positions in N =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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/CellularRule54.pm0000644000175000017500000003113012606435154020320 0ustar gggg# 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 . # 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 = 122; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem = \&Math::PlanePath::_divrem; 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((sqrt(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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/TerdragonCurve.pm0000644000175000017500000010233012606435147020511 0ustar gggg# 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 . # 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 = 122; @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)); } } # 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) = @_; ### TerdragonCurve xy_to_n_list(): "$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 @n_list; 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; } # 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'}); } 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 (my $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 Online terdragon ie morphism si,sj,sk dX,dY Pari rhombi dX si =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. =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. \ / \ / \ / \ / --- 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 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 and area 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 applies C C to calculate six candidate N from the six edges around a point. Those N values which convert back to the target X,Y by C are the results for C. The six edges are three going towards the point and three going away. The midpoint calculation gives N-1 for the towards and N for the away. Is there a good way to tell which edge will be the smaller? Or just which 3 edges lead away? It would be directions 0,2,4 for the even arms and 1,3,5 for the odd ones, but identifying the boundaries of those arms to know which is which is difficult. =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 A080846 next turn 0=left,1=right, by 120 degrees (n=0 is turn at N=1) 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) A189673 turn 1=left,0=right (morphism, extra initial 0) A038502 strip trailing ternary 0s, taken mod 3 is turn 1=left,2=right 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 a 1 A026179 N positions of right turns (except initial 1) A060032 bignum turns 1=left,2=right to 3^level A062756 total turn, count ternary 1s A005823 N positions where net turn == 0, ternary no 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 A092236 count East segments N=0 to N=3^k A135254 count North-West segments N=0 to N=3^k, extra 0 A133474 count South-West segments N=0 to N=3^k A057083 count segments diff from 3^(k-1) 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 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/VogelFloret.pm0000644000175000017500000006542512606435146020023 0ustar gggg# Copyright 2010, 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/SierpinskiArrowheadCentres.pm0000644000175000017500000005522612606435147023073 0ustar gggg# 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 . # 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 = 122; 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 _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 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-122/lib/Math/PlanePath/ComplexPlus.pm0000644000175000017500000003307012606435153020033 0ustar gggg# 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 . # 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 = 122; 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 my $x = my $y = ($n * 0); # inherit bignum my $dx; my $dy = 0; { my $arm = _divrem_mutate ($n, $self->{'arms'}); if ($arm) { $y += 1; # start X=0,Y=1 $dx = -1; } else { $dx = 1; } } 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 =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 with 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 the default i+1 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. =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 above N=0 to N=4, then N=5 to N=9, etc. 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 the 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 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 SEE ALSO L, L, L, L The author's mathematical write-up of the dragon curve includes some correspondences and measurements for the i+1 complex base shape =over L =back =head1 HOME PAGE L =head1 LICENSE 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 . =cut Math-PlanePath-122/lib/Math/PlanePath/SacksSpiral.pm0000644000175000017500000003077012606435147020006 0ustar gggg# Copyright 2010, 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 . # 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 = 122; @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 XXThe 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 each loop is a constant distance from the preceding, in this case 1 unit. 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 in the Math-PlanePath sources 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/HexArms.pm0000644000175000017500000002552712606435152017136 0ustar gggg# 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 . # 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 = 122; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; 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 + sqrt(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 $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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/TerdragonRounded.pm0000644000175000017500000003057712606435147021042 0ustar gggg# Copyright 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 . package Math::PlanePath::TerdragonRounded; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; 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); if (($x+$y) % 2) { return undef; } ($x,$y) = (($x-3*$y)/2, # rotate +60 ($x+$y)/2); ### rot: "$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); 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 Guiseppe 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/SquareReplicate.pm0000644000175000017500000002131712606435147020655 0ustar gggg# 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 . # math-image --path=SquareReplicate --lines --scale=10 # math-image --path=SquareReplicate --all --output=numbers_dash --size=80x50 package Math::PlanePath::SquareReplicate; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 Devel::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 => 6; use constant ddiffxy_maximum => 1; use constant dir_maximum_dxdy => (0,-1); # South #------------------------------------------------------------------------------ # 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 foreach my $digit (digit_split_lowtohigh($n,9)) { ### 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 $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) = @_; ### 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 =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 in 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 =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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/WythoffPreliminaryTriangle.pm0000644000175000017500000002242512606435146023114 0ustar gggg# Copyright 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/PyramidSides.pm0000644000175000017500000002202212606435150020145 0ustar gggg# Copyright 2010, 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 . package Math::PlanePath::PyramidSides; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; use Math::PlanePath; @ISA = ('Math::PlanePath'); 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 => 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( sqrt(int($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), 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/CellularRule.pm0000644000175000017500000017401212606435154020156 0ustar gggg# 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 . # 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 = 122; 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 = 122; 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 = 122; 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 = 122; 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 = 122; 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 =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 based 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 possible 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 Math World page above, or can be printed with the F program in the Math-PlanePath sources. 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/GosperIslands.pm0000644000175000017500000005230512606435152020336 0ustar gggg# 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 . # 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 = 122; 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 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-122/lib/Math/PlanePath/WunderlichMeander.pm0000644000175000017500000003575112606435146021172 0ustar gggg# 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 . package Math::PlanePath::WunderlichMeander; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 _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 XThis is an integer version of the 3x3 self-similar meander by Walter Wunderlich, 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 going from one corner 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 and the transposing in that case applies to ever smaller parts. But for the integer version here the start direction is 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 which is similar to the C and 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 Walter Wunderlich "Uber Peano-Kurven", Elemente der Mathematik, 28(1):1-10, 1973. =over L L (scanned copy, in German) =back =head1 HOME PAGE L =head1 LICENSE 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 . =cut Math-PlanePath-122/lib/Math/PlanePath/KochPeaks.pm0000644000175000017500000003650612611353341017431 0ustar gggg# 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 . package Math::PlanePath::KochPeaks; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 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-122/lib/Math/PlanePath/GosperSide.pm0000644000175000017500000003274512611353341017626 0ustar gggg# 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 . # 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 = 122; 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 _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 =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 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-122/lib/Math/PlanePath/FilledRings.pm0000644000175000017500000003326412606435152017766 0ustar gggg# Copyright 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/TriangleSpiralSkewed.pm0000644000175000017500000004201712606435147021647 0ustar gggg# Copyright 2010, 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 . 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 = 122; use Math::PlanePath; @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 => [ { 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((sqrt(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 the 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/File.pm0000644000175000017500000001665712606435152016452 0ustar gggg# 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/CretanLabyrinth.pm0000644000175000017500000002605012606435153020651 0ustar gggg# Copyright 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 . # 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 = 122; 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 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 + sqrt(4*int($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 in the Math-PlanePath sources 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/DragonCurve.pm0000644000175000017500000013732612617551230020005 0ustar gggg# 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 . # 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' # # cf A088431 run lengths of dragon turns # 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) # # A060833 not adding to 2^k+2, # superset of positions of left turns ... # # A166242 double or half according to dragon turn # # connection points # N = 26 = 11010 # = 27 = 11011 # N = 51 = 110011 = (3*16^k-1)/15 -> 1/5 # = 52 = 110100 # N = 101 = 1100101 # = 102 = 1100110 = 2*(3*16^k-1)/15 -> 2/5 # N # Martin Gardner, "The Dragon Curve and Other Problems (Mathematical # Games)", Scientific American, March 1967 (addenda from readers April and # July). # # Reprinted in "Mathematical Magic Show", 1978. # Chandler Davis and Donald Knuth, # "Number Representations and Dragon Curves - I", C. Davis & D. E. Knuth, # Journal Recreational Math., volume 3, number 2 (April 1970), pages 66-81. # 16 pages # # Chandler Davis and Donald Knuth, # "Number Representations and Dragon Curves - II", C. Davis & D. E. Knuth, # Journal Recreational Math., volume 3, number 3 (July 1970), pages 133-149. # 17 pages # # Revised in "Selected Papers on Fun and Games", 2010, pages 571-603 with # addendum pages 603-614. # http://trove.nla.gov.au/version/50039930 # 32+12=44 pages # Sze-Man Ngai and Nhu Nguyen, "The Heighway Dragon Revisited", Discrete and # Computational Geometry, May 2003 volume 29, issue 4, pages 603-623 # http://www.math.nmsu.edu/~nnguyen/23paper.ps package Math::PlanePath::DragonCurve; use 5.004; use strict; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 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 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 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 boundary length and area can be found in the author's mathematical write-up =over L =back =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 A246960 direction 0,1,2,3 A038189 turn, 0=left,1=right, bit above lowest 1, extra 0 A089013 same as A038189, but initial 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 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 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 total turn A088748 total turn + 1 A164910 cumulative(total turn + 1) A166242 2^(total turn), by double/halving A088431 turn sequence run lengths A007400 2*runlength A091072 N positions of the left turns, being odd part form 4K+1 A003460 turns N=1 to N=2^n-1 packed as bits 1=left,0=right low to high, then written in octal A126937 points numbered like SquareSpiral (start N=0 and flip Y) 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 also differences of total boundary 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 connected block A003479 join area between N=2^k replications A003229 join area increment, also area left side extra over doubling A077949 same A003476 squares on right boundary also single points N=0 to N=2^(k-1) inclusive A203175 squares on left boundary A164395 single points N=0 to N=2^k-1 inclusive, for k=4 up 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) XXJeffrey 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 SEE ALSO 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 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-122/lib/Math/PlanePath/CubicBase.pm0000644000175000017500000003523412606435153017404 0ustar gggg# Copyright 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/PixelRings.pm0000644000175000017500000003704012606435150017642 0ustar gggg# 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 . # 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 = 122; 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 _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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/RationalsTree.pm0000644000175000017500000015505212606435147020344 0ustar gggg# 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 . # 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 # A065249 - permutation SB X -> X/2 # A065250 - permutation SB X -> 2X # 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 = 122; 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]; # } # } =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 =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 in the Math-PlanePath sources 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, Ser. 3, 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 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) 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 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 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 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. =head1 SEE ALSO L, L, L, L L, L, L, L, L L, L =head1 HOME PAGE L =head1 LICENSE 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 . =cut # # =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 Math-PlanePath-122/lib/Math/PlanePath/FlowsnakeCentres.pm0000644000175000017500000007247012606435152021043 0ustar gggg# 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/HilbertSpiral.pm0000644000175000017500000003327412606435152020331 0ustar gggg# 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 . package Math::PlanePath::HilbertSpiral; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/PentSpiral.pm0000644000175000017500000002434512606435150017643 0ustar gggg# Copyright 2010, 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 . package Math::PlanePath::PentSpiral; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; use Math::PlanePath; @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(), ]; 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 _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( (sqrt(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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/LTiling.pm0000644000175000017500000004425312606435151017125 0ustar gggg# 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 . 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/WunderlichSerpentine.pm0000644000175000017500000005630512606435146021731 0ustar gggg# 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 . # http://sodwana.uni-ak.ac.at/geom/mitarbeiter/wallner/wunderlich/pdf/125.pdf # [8.5mb] # # 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 # 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 = 122; 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. 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, any 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 Walter Wunderlich "Uber Peano-Kurven", Elemente der Mathematik, 28(1):1-10, 1973. LZ<> L =head1 HOME PAGE L =head1 LICENSE 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 . =cut Math-PlanePath-122/lib/Math/PlanePath/DiagonalRationals.pm0000644000175000017500000002667212606435153021165 0ustar gggg# 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 . # 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 = 122; 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 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-122/lib/Math/PlanePath/HIndexing.pm0000644000175000017500000004377412611353341017443 0ustar gggg# 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 . # 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 = 122; 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 _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 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/R5DragonMidpoint.pm0000644000175000017500000003554012606435150020706 0ustar gggg# Copyright 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/TriangularHypot.pm0000644000175000017500000010143212606435147020715 0ustar gggg# 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/BetaOmega.pm0000644000175000017500000006213712606435154017413 0ustar gggg# 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 . # 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 = 122; 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 _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 Center 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/ArchimedeanChords.pm0000644000175000017500000005056512606435154021134 0ustar gggg# 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/PentSpiralSkewed.pm0000644000175000017500000001745312606435150021010 0ustar gggg# Copyright 2010, 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 . 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 = 122; use Math::PlanePath; @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 _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( (sqrt(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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/TheodorusSpiral.pm0000644000175000017500000003445512606435147020722 0ustar gggg# Copyright 2010, 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 . # Hlawka, angles of point N is # phi(n) = sum k=1 to n of arcsin 1/sqrt(k+1) # is equidistributed mod 2pi 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 = 122; 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__ =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 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 there doesn't seem to be 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 =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 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 . =cut # 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 Math-PlanePath-122/lib/Math/PlanePath/Staircase.pm0000644000175000017500000002073512606435147017505 0ustar gggg# Copyright 2010, 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 . package Math::PlanePath::Staircase; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; 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( (sqrt(int($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 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/ComplexMinus.pm0000644000175000017500000005602512611353341020202 0ustar gggg# 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 . # 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 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 = 122; 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 $dx = 1; my $dy = 0; 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) $dy = -$dy; ($dx,$dy) = ($dy - $realpart*$dx, $dx + $realpart*$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 characterises =head1 NAME Math::PlanePath::ComplexMinus -- twindragon and other complex number base 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 Walter Penny, "A 'Binary' System for Complex Numbers", Journal of the ACM, volume 12, number 2, April 1965, pages 247-248. =back When continued to a power-of-2 extent this is called the "twindragon" shape. =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 N=2^level-1 repeats as N=2^level to N=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 around and do so become large if norm=r*r+1 is large. =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 =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 we 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, 710, ... 4 + 2*x + 4*x^2 generating function --------------- 1 - x - 2*x^3 =for GP-Test 2*4+10 == 18 =for GP-Test 2*6+18 == 30 =for GP-DEFINE gB1(x) = (4 + 2*x + 4*x^2) / (1 - x - 2*x^3) =for GP-Test Vec(gB1(x) - O(x^11)) == [4, 6, 10, 18, 30, 50, 86, 146, 246, 418, 710] 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 the twindragon 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 the twindragon 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, 90884, ... 4 - 4*x^2 generating function --------------------- 1 - 3*x - x^2 - 5*x^3 =for GP-DEFINE gB2(x) = (4 - 4*x^2) / (1 - 3*x - x^2 - 5*x^3) =for GP-Test Vec(gB2(x) - O(x^9)) == [4, 12, 36, 140, 516, 1868, 6820, 24908, 90884] =for GP-Test 5*4+1*12+3*36 == 140 =for GP-Test 5*12+1*36+3*140 == 516 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 (twindragon, the default) A066321 N on X axis, being the base i-1 positive reals A066323 N on X axis, in binary A066322 diffs (N at X=16k+4) - (N at X=16k+3) 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 =head1 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015 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-122/lib/Math/PlanePath/HexSpiralSkewed.pm0000644000175000017500000003037212606435152020623 0ustar gggg# Copyright 2010, 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 . package Math::PlanePath::HexSpiralSkewed; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; use Math::PlanePath; @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 _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((sqrt(int(3*$n) + ($w+2)*$w + 1) - 1 - $w) / 3); #### d frac: (sqrt(int(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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/KnightSpiral.pm0000644000175000017500000003400612606435151020155 0ustar gggg# Copyright 2010, 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 . package Math::PlanePath::KnightSpiral; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; use Math::PlanePath; @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; 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 _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 + sqrt(int($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 can be seen at the following page (quarter way down under "Open Knight's Tour"), =over L L L =back 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 spiralling the 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/AR2W2Curve.pm0000644000175000017500000006545512606435154017377 0ustar gggg# 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/HypotOctant.pm0000644000175000017500000003656612606435151020047 0ustar gggg# 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/R5DragonCurve.pm0000644000175000017500000005055112606435150020206 0ustar gggg# Copyright 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 . 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 = 122; 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 XThe 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 and area 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 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 A008776 count single-visited points N=0 to 5^k being 2*3^k A024024 C[k] boundary lengths, 3^k-k A104743 E[k] boundary lengths, 3^k+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 SEE ALSO L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 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 . =cut Math-PlanePath-122/lib/Math/PlanePath/OctagramSpiral.pm0000644000175000017500000002647712606435150020502 0ustar gggg# Copyright 2010, 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 . # 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 = 122; use Math::PlanePath; @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 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 ((sqrt(int(32*$n) + 17) + 7) / 16); #### d frac: ((sqrt(int(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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/KochelCurve.pm0000644000175000017500000005006312606435151017771 0ustar gggg# 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 . package Math::PlanePath::KochelCurve; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 XThis is an integer version of the Kochel curve by Herman Haverkort. 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, like other space-filling curves. For that the top-level can be any of the patterns. To descend any of the shapes can be used for the start, 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. But 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 Herman Haverkort, "Recursive Tilings and Space-Filling Curves with Little Fragmentation", Journal of Computational Geometry, 2(1), 92-127, 2011. =over L L L L (slides) L (short form) =back =head1 HOME PAGE L =head1 LICENSE 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 . =cut Math-PlanePath-122/lib/Math/PlanePath/Rows.pm0000644000175000017500000002056612606435147016523 0ustar gggg# Copyright 2010, 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 . package Math::PlanePath::Rows; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/ComplexRevolving.pm0000644000175000017500000001774212606435153021073 0ustar gggg# Copyright 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 . package Math::PlanePath::ComplexRevolving; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/DiagonalsOctant.pm0000644000175000017500000004026112606435153020632 0ustar gggg# Copyright 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 . package Math::PlanePath::DiagonalsOctant; use 5.004; use strict; use Carp 'croak'; use vars '$VERSION', '@ISA'; $VERSION = 122; use Math::PlanePath; @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(sqrt($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 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/CincoCurve.pm0000644000175000017500000006162212606435154017625 0ustar gggg# 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 . # 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 = 122; 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 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-122/lib/Math/PlanePath/PyramidSpiral.pm0000644000175000017500000002201612606435150020333 0ustar gggg# Copyright 2010, 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 . package Math::PlanePath::PyramidSpiral; use 5.004; use strict; #use List::Util 'min'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; use Math::PlanePath; @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 _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(sqrt(int($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 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/WythoffArray.pm0000644000175000017500000004575012606435146020217 0ustar gggg# Copyright 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 . # 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 = 122; 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 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((sqrt(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 and 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 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/Diagonals.pm0000644000175000017500000004052712606435153017466 0ustar gggg# Copyright 2010, 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 . # 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 = 122; use Math::PlanePath; @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(), { 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; } $d = int((sqrt(int($r)) - 1) / 2); ### assert: $d >= 0 } # subtract for offset into diagonal, range -0.5 <= $n < $d+0.5 $n -= $d*($d+1)/2; 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 =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 =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 = d - X 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 therefor X extends back to -0.5. =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 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 direction=down, n_start=0 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, n_start=1 A038722 permutation N at transpose Y,X which is direction=down <-> direction=up n_start=1, x_start=1, y_start=1, either direction A003991 X*Y coordinate product A003989 GCD(X,Y) greatest common divisor starting (1,1) A003983 min(X,Y) A051125 max(X,Y) n_start=1, x_start=1, y_start=1, direction=down A057046 X for N=2^k A057047 Y for N=2^k n_start=0 (either direction) 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/QuintetCentres.pm0000644000175000017500000005410112606435150020530 0ustar gggg# 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 . # 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 = 122; 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 =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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/DiamondArms.pm0000644000175000017500000002421712606435153017761 0ustar gggg# 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 . # 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 = 122; use Math::PlanePath; @ISA = ('Math::PlanePath'); *_divrem_mutate = \&Math::PlanePath::_divrem_mutate; 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 _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 + sqrt(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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/KochSquareflakes.pm0000644000175000017500000004607712606435151021024 0ustar gggg# 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 . package Math::PlanePath::KochSquareflakes; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; 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) =head1 SEE ALSO L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015 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-122/lib/Math/PlanePath/SierpinskiTriangle.pm0000644000175000017500000011706312606435147021376 0ustar gggg# 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 . # 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 = 122; 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 The number of siblings of a given node is determined by its 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 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 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-122/lib/Math/PlanePath/GreekKeySpiral.pm0000644000175000017500000005032412606435152020441 0ustar gggg# 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 . # 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 = 122; 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; # 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 = int (sqrt($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((sqrt(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 spiraling 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/DivisibleColumns.pm0000644000175000017500000003535212606435153021040 0ustar gggg# 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 . # 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 = 122; 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 => '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 = int(sqrt($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 = int(sqrt($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 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-122/lib/Math/PlanePath/FibonacciWordFractal.pm0000644000175000017500000003655512606435152021600 0ustar gggg# 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 . # http://alexis.monnerot-dumaine.neuf.fr/articles/fibonacci%20fractal.pdf # [gone] # # math-image --path=FibonacciWordFractal --output=numbers_dash package Math::PlanePath::FibonacciWordFractal; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 $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 XXThis is an integer version of the Fibonacci word fractal =over Alexis Monnerot-Dumaine, "The Fibonacci Word Fractal", February 2009. L L =back 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 A156596 - turn sequence, 0=straight,1=right,2=left A171587 - abs(dX), so 1=horizontal,0=vertical 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/AnvilSpiral.pm0000644000175000017500000003730212606435154020007 0ustar gggg# 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 . # 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 = 122; use Math::PlanePath; @ISA = ('Math::PlanePath'); 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 _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((sqrt(int(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 =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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/AlternatePaperMidpoint.pm0000644000175000017500000004203612606435154022176 0ustar gggg# 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 . # math-image --path=AlternatePaperMidpoint,arms=8 --all --output=numbers_dash # math-image --path=AlternatePaperMidpoint --lines --scale=20 package Math::PlanePath::AlternatePaperMidpoint; use 5.004; use strict; use List::Util 'min'; # 'max' *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 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 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-122/lib/Math/PlanePath/PyramidRows.pm0000644000175000017500000007033712606435150020044 0ustar gggg# Copyright 2010, 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 . # 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 = 122; use Math::PlanePath; @ISA = ('Math::PlanePath'); 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 + sqrt(int(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 factorizations OEIS =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 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. step => 1 11 12 13 14 15 4 7 8 9 10 3 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 unlikely to be much use. The Rows path with C 1 does this too. step => 0 5 4 4 3 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/ImaginaryBase.pm0000644000175000017500000004635412606435151020302 0ustar gggg# 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 . # 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 = 122; 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, b = i*sqrt(r) a[i] = 0 to r-1 digits X+Y*i*sqrt(r) = a[k]*b^k + ... + a[2]*b^2 + a[1]*b + a[0] and N is the a[i] digits in base r N = a[k]*r^k + ... + a[2]*r^2 + a[1]*r + a[0] XThe factor sqrt(r) makes the generated Y an integer. 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. It takes two steps to 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. The 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 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 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-122/lib/Math/PlanePath/MPeaks.pm0000644000175000017500000002323212606435151016735 0ustar gggg# 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 . 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 = 122; use Math::PlanePath; @ISA = ('Math::PlanePath'); 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( (sqrt(int($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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/SquareArms.pm0000644000175000017500000002306012606435147017644 0ustar gggg# 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 . # 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 = 122; 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; # 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 + sqrt(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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/CfracDigits.pm0000644000175000017500000004362312606435154017750 0ustar gggg# Copyright 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 . package Math::PlanePath::CfracDigits; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/GosperReplicate.pm0000644000175000017500000002501512606435152020647 0ustar gggg# 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 . # math-image --path=GosperReplicate --lines --scale=10 # math-image --path=GosperReplicate --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 = 122; 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; *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 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 = 0; my $y = 0; my $sx = 2; my $sy = 0; # digit # 3 2 # \ / # 4---0---1 # / \ # 5 6 foreach my $digit (digit_split_lowtohigh($n,7)) { ### 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); } 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 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 The points are spread out on every second X coordinate to make a 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 represents a little hexagon, thus tiling 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 at N=7 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 following edges. =head2 Complex Base The path corresponds to expressing complex integers X+i*Y in a base b = 5/2 + i*sqrt(3)/2 with some scaling to put equilateral triangles on a square grid. So for integer X,Y with X and 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 N as base 7 digits, r = e^(i*pi/3) = 1/2 + i*sqrt(3)/2 sixth root of unity N digit a[i] complex number ------- ------------------- 0 0 1 r^0 = 1 2 r^2 = 1/2 + i*sqrt(3)/2 3 r^3 = -1/2 + i*sqrt(3)/2 4 r^4 = -1 5 r^5 = -1/2 - i*sqrt(3)/2 6 r^6 = 1/2 - i*sqrt(3)/2 7 digits suffice because norm(b) = (5/2)^2 + (sqrt(3)/2)^2 = 7 =head1 FUNCTIONS See L for behaviour common to all path classes. =over 4 =item C<$path = Math::PlanePath::GosperReplicate-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, 7**$level - 1)>. =back =head1 SEE ALSO L, L, L, L, L, L =head1 HOME PAGE L =head1 LICENSE 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 . =cut Math-PlanePath-122/lib/Math/PlanePath/Hypot.pm0000644000175000017500000005013212606435151016657 0ustar gggg# 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/DigitGroups.pm0000644000175000017500000002660412606435153020025 0ustar gggg# 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 . # 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 = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/DekkingCurve.pm0000644000175000017500000005331412606435153020144 0ustar gggg# 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 . package Math::PlanePath::DekkingCurve; use 5.004; use strict; #use List::Util 'max'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 =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 per =over F. M. Dekking, "Recurrent Sets", Advances in Mathematics, volume 44, 1982, pages 79-104, section 4.9 "Gosper-Type Curves" =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. This is the side the curve 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 reverse 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 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 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. =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE 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 . =cut Math-PlanePath-122/lib/Math/PlanePath/MultipleRings.pm0000644000175000017500000014015612606435151020360 0ustar gggg# Copyright 2010, 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 . # 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; $VERSION = 122; 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((sqrt(int(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; 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)); # } my $theta = 2 * $pi * $side/$numsides; return ($r * cos($theta), $r * sin($theta)); } # 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((sqrt(int(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, ring_shape=>'polygon', step=>4 16 / \ 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/QuadricCurve.pm0000644000175000017500000003215212606435150020152 0ustar gggg# 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 . package Math::PlanePath::QuadricCurve; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; 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'; # 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; } #------------------------------------------------------------------------------ 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 =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 2 -90 3 -90 4 0 5 +90 6 +90 7 -90 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 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/SquareSpiral.pm0000644000175000017500000007015212640664451020200 0ustar gggg# Copyright 2010, 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 . # 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://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, J. Recreational # Mathematics 29 (#3, 1998) 188; 30 (#4, 1999-2000), 246-250. # # Stein, M. and Ulam, S. M. "An Observation on the # Distribution of Primes." Amer. Math. Monthly 74, 43-44, # 1967. # # Stein, M. L.; Ulam, S. M.; and Wells, M. B. "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 = 122; use Math::PlanePath; 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 + sqrt(int(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 + sqrt(int(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((sqrt(int(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 =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 in the sources 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 in the sources for a standalone program, or see L using this C to draw this pattern and more. Stein, Ulam and Wells 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, as the widening is basically 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 rearranging 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 as an exercise in Graham, Knuth and Patashnik "Concrete Mathematics", chapter 3 "Integer Functions", exercise 40, page 99. They start the spiral from 0, and vertically 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. =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 A214526 abs(X)+abs(Y) "Manhattan" distance A079813 abs(dY), being k 0s followed by k 1s 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 A033638 N turn positions (extra initial 1, 1) A172979 N turn positions which are primes too 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 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 to the N to its right, X+1,Y A121496 run lengths of consecutive N in that permutation A068226 permutation N to the N to its left, X-1,Y 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" in the OEIS and therefore are based on starting 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/SierpinskiCurveStair.pm0000644000175000017500000007210512611353340021703 0ustar gggg# 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 . # 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 = 122; 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 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-122/lib/Math/PlanePath/UlamWarburtonQuarter.pm0000644000175000017500000006145712611353340021731 0ustar gggg# 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 . package Math::PlanePath::UlamWarburtonQuarter; use 5.004; use strict; use Carp 'croak'; use List::Util 'sum'; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/GcdRationals.pm0000644000175000017500000010270712606435152020135 0ustar gggg# 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 . # 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 = 122; use Math::PlanePath; @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((sqrt(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((sqrt(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 = int(sqrt($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 = int(sqrt($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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/QuintetCurve.pm0000644000175000017500000003603212606435150020214 0ustar gggg# 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 . # 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 = 122; # 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; } #------------------------------------------------------------------------------ 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 traces out a spiralling self-similar "+" shape, 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 ... The base figure is the initial N=0 to N=4. 5 | | 0---1 4 base figure | | | | 2---3 It corresponds to a traversal of the following "+" shape, .... 5 . | . <| | 0----1 .. 4 .... v | | . . |> |> . . | | . .... 2----3 .... . v . . . . . . .. . The "v" and ">" notches are the side the figure is directed at the higher replications. The 0, 2 and 3 parts 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. The first such reversal is seen above as N=5 to N=10. ..... . . 5---6---7 ... . . | . . | . reversed figure ... 9---8 ... | . | . 10 ... 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. N = 5^level angle = level * atan(1/2) = level * 26.56 degrees radius = sqrt(5) ^ level 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 into the second quadrant with X negative. A full circle around the plane is around 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 can be packed as follows, +---+ | | +---+--- +---+ | | A | +---+ +---+ +---+ | B | | | +---+ +---O---+ +---+ | | | D | +---+ +---+ +---+ | C | | +---+ +---+---+ | | +---+ At higher replication levels the sides are wiggly and spiralling and the centres of each rotated around, but they 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 =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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/PowerArray.pm0000644000175000017500000003563112606435150017655 0ustar gggg# Copyright 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 . # area package Math::PlanePath::PowerArray; use 5.004; use strict; use List::Util 'max'; use vars '$VERSION', '@ISA'; $VERSION = 122; 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__ =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 14 | 29 58 116 232 464 928 1856 3712 7424 14848 13 | 27 54 108 216 432 864 1728 3456 6912 13824 12 | 25 50 100 200 400 800 1600 3200 6400 12800 11 | 23 46 92 184 368 736 1472 2944 5888 11776 10 | 21 42 84 168 336 672 1344 2688 5376 10752 9 | 19 38 76 152 304 608 1216 2432 4864 9728 8 | 17 34 68 136 272 544 1088 2176 4352 8704 7 | 15 30 60 120 240 480 960 1920 3840 7680 6 | 13 26 52 104 208 416 832 1664 3328 6656 5 | 11 22 44 88 176 352 704 1408 2816 5632 4 | 9 18 36 72 144 288 576 1152 2304 4608 3 | 7 14 28 56 112 224 448 896 1792 3584 2 | 5 10 20 40 80 160 320 640 1280 2560 1 | 3 6 12 24 48 96 192 384 768 1536 Y=0 | 1 2 4 8 16 32 64 128 256 512 +----------------------------------------------------------- X=0 1 2 3 4 5 6 7 8 9 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 =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 9 | 14 42 126 378 1134 3402 10206 30618 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 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. For other 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 again. So 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, 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 +------------------- =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 A135764 permutation N by diagonals, downwards 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 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/DragonMidpoint.pm0000644000175000017500000007412612611353341020477 0ustar gggg# 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 . # 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 = 122; 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 turn=1, straight=0 (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 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-122/lib/Math/PlanePath/DragonRounded.pm0000644000175000017500000004034012606435152020310 0ustar gggg# 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 . # 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 = 122; 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 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-122/lib/Math/PlanePath/CellularRule57.pm0000644000175000017500000003616212606435154020335 0ustar gggg# 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 . package Math::PlanePath::CellularRule57; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; use Math::PlanePath; @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 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 ((sqrt(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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/UlamWarburton.pm0000644000175000017500000010445012606435146020365 0ustar gggg# 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 . #------------------------------------------------------------------------------ # 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 = 122; 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 =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. An optional 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 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/AztecDiamondRings.pm0000644000175000017500000003324612606435154021133 0ustar gggg# 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 . # 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 = 122; use Math::PlanePath; @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 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 _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( (sqrt(2*int($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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/Corner.pm0000644000175000017500000004570212606435153017015 0ustar gggg# Copyright 2010, 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 . # cf Matthew Szudzik 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 4 7 13 21 # | | | | | # Y=0 | 0 2 6 12 20 # +--------------------- # X=0 1 2 3 4 # # cf A185728 where diagonal is last in each gnomon # A185725 gnomon sides alternately starting from ends # A185726 gnomon sides alternately starting from diagonal # # corner going alternately up and down # A081344,A194280 by diagonals # A081345 X axis, A081346 Y axis # # corner alternately up and down, starting with 3-wide # A080335 N on diagonal # A081347 N on axis # A081348 N on axis # # cf A004120 ?? # package Math::PlanePath::Corner; use 5.004; use strict; use List::Util 'min'; use vars '$VERSION', '@ISA'; $VERSION = 122; use Math::PlanePath; @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((sqrt(int(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((sqrt(int(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=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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/SierpinskiArrowhead.pm0000644000175000017500000005574612606435147021556 0ustar gggg# 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 . 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 = 122; 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 _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 =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 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * =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 OEIS Entries in Sloane's Online Encyclopedia of Integer Sequences related to this path include, =over L (etc) =back A189706 turn 0=left,1=right at odd positions 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 A156595 turn 0=left,1=right at even positions N=2,4,6,etc =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015 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-122/lib/Math/PlanePath/SierpinskiCurve.pm0000644000175000017500000011053112611353341020675 0ustar gggg# 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 . 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 = 122; 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 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-122/lib/Math/PlanePath/PeanoCurve.pm0000644000175000017500000007355512606435150017640 0ustar gggg# Copyright 2010, 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 . # cf # # http://www.cut-the-knot.org/Curriculum/Geometry/PeanoComplete.shtml # Java applet, directions in 9 sub-parts # # math-image --path=PeanoCurve,radix=5 --all --output=numbers # math-image --path=PeanoCurve,radix=5 --lines # # T = 0.a1 a2 ... # X = 0.b1 b2 ... # Y = 0.c1 c2 ... # # 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 = 122; 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_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 $radix = $self->{'radix'}; my @ndigits = digit_split_lowtohigh($n,$radix) or return (0,0); # high to low style # my $radix_minus_1 = $radix - 1; my $xk = 0; my $yk = 0; my @ydigits; my @xdigits; $#ndigits |= 1; # ensure even number of entries $ndigits[-1] ||= 0; # possible 0 as extra high digit ### @ndigits foreach my $i (reverse 0 .. ($#ndigits >> 1)) { ### $i { my $ndigit = pop @ndigits; # high to low $xk ^= $ndigit; $ydigits[$i] = ($yk & 1 ? $radix_minus_1-$ndigit : $ndigit); } @ndigits || last; { 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); } 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; } my $radix = $self->{'radix'}; my $zero = ($x * 0 * $y); # inherit bignum 0 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; } } return digit_join_lowtohigh (\@n, $radix, $zero); } # 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 Guiseppe 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 =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 XThis path is an integer version of the curve described by Peano for filling a unit square, =over Guiseppe Peano, "Sur Une Courbe, Qui Remplit Toute Une Aire Plane", Mathematische Annalen, volume 36, number 1, 1890, p157-160. DOI 10.1007/BF01199438. 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, 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. Georg 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.) =head2 Diagonal Lines Moore in =over XE. H. Moore, "On Certain Crinkly Curves", Trans. Am. Math. Soc., volume 1, number 1, 1900, pages 72-90. L L L L =back draws the curve as a base shape +-----+ | | -----+-----+----- | | +-----+ with each line segment replaced by the same for the next level (with suitable mirror image in odd segments). 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 "\" Rounding off the corners of the diagonal form so they don't touch can help show the equivalence, =cut # cf Math::PlanePath::PeanoRounded ... if finished =pod -----7 / / \ / 6 -----8 | | 4----- \ / \ 5----- 3 | -----1 | / \ / 0 -----2 =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 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 appears either added to or subtracted from X or 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)> Return a range of N values which occur in 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 is appended to Y then the next appended to X. Beginning at an even digit position in N makes the last digit go to X so the first N=0,1,2 goes 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 to the left, 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 either before or after is fine, 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. When 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, but instead 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 onto 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 some trit-twiddling could no doubt do it. For the 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. =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 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 =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 thue-morse count low 0-bits + 1 mod 2 A182581 abs(dY) from n-1 to n, 0=horiz 1=vertical thue-morse count low 0-bits mod 2 A163534 direction of each step (up,down,left,right) A163535 direction, 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 and vice versa since is 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 =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 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 . =cut Math-PlanePath-122/lib/Math/PlanePath/QuintetReplicate.pm0000644000175000017500000002147612606435150021046 0ustar gggg# 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 . # math-image --path=QuintetReplicate --lines --scale=10 # math-image --path=QuintetReplicate --output=numbers --all # math-image --path=QuintetReplicate --expression='5**i' package Math::PlanePath::QuintetReplicate; use 5.004; use strict; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 xy_is_visited => 1; use constant x_negative_at_n => 3; use constant y_negative_at_n => 4; # 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 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 foreach my $digit (digit_split_lowtohigh($n,5)) { ### $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); } 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; } #------------------------------------------------------------------------------ 1; __END__ =for stopwords eg Ryde Math-PlanePath =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 rotated around by an angle atan(1/2)=26.565 degrees, as noted below. =head2 Complex Base This tiling corresponds to expressing a complex integer X+i*Y in base b=2+i X+Yi = a[n]*b^n + ... + a[2]*b^2 + a[1]*b + a[0] where each digit a[i] is a[i] digit N digit ---------- ------- 0 0 1 1 i 2 -1 3 -i 4 The base b=2+i is at an angle 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 angle = level*26.56 degrees radius = sqrt(5) ^ level =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 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE Copyright 2011, 2012, 2013, 2014, 2015 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-122/lib/Math/PlanePath/QuadricIslands.pm0000644000175000017500000003662612611353341020472 0ustar gggg# 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 . # 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 = 122; 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 =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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/CCurve.pm0000644000175000017500000016551012641645141016753 0ustar gggg# 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 . package Math::PlanePath::CCurve; use 5.004; use strict; use List::Util 'min','max','sum'; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 _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); sub n_to_dxdy { my ($self, $n) = @_; ### n_to_dxdy(): $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 =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 in the PlanePath sources 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 =for GP-DEFINE Mdir_vec = [0, 1, 1, 1, 0, -1, -1, -1] =for GP-DEFINE Mdir(n) = Mdir_vec[(n%8)+1] /* +1 for vector start index 1 */ =for GP-DEFINE M0half(k) = local(h); h=floor(k/2); if(k==0,1, 2^(k-2) + Mdir(k+2)*2^(h-1)) =for GP-DEFINE M1half(k) = local(h); h=floor(k/2); if(k==0,0, 2^(k-2) + Mdir(k+0)*2^(h-1)) =for GP-DEFINE M2half(k) = local(h); h=floor(k/2); if(k==0,0, 2^(k-2) + Mdir(k-2)*2^(h-1)) =for GP-DEFINE M3half(k) = local(h); h=floor(k/2); if(k==0,0, 2^(k-2) + Mdir(k-4)*2^(h-1)) =for GP-DEFINE M0samples = [ 1, 1, 1, 1, 2, 6, 16, 36, 72, 136, 256 ] =for GP-DEFINE M1samples = [ 0, 1, 2, 3, 4, 6, 12, 28, 64, 136, 272 ] =for GP-DEFINE M2samples = [ 0, 0, 1, 3, 6, 10, 16, 28, 56, 120, 256 ] =for GP-DEFINE M3samples = [ 0, 0, 0, 1, 4, 10, 20, 36, 64, 120, 240 ] =for GP-Test vector(length(M0samples),k,M0half(k-1)) == M0samples =for GP-Test vector(length(M1samples),k,M1half(k-1)) == M1samples =for GP-Test vector(length(M2samples),k,M2half(k-1)) == M2samples =for GP-Test vector(length(M3samples),k,M3half(k-1)) == M3samples 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 =for GP-DEFINE M0rec(k) = if(k<4,1, 4*M0rec(k-1) - 6*M0rec(k-2) + 4*M0rec(k-3)) =for GP-DEFINE M1rec(k) = if(k<4,k, 4*M1rec(k-1) - 6*M1rec(k-2) + 4*M1rec(k-3)) =for 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)))) =for GP-DEFINE M3rec(k) = if(k<3,0, if(k==3,1, 4*M3rec(k-1) - 6*M3rec(k-2) + 4*M3rec(k-3))) =for GP-Test vector(20,k,M0rec(k-1)) == vector(20,k,M0half(k-1)) =for GP-Test vector(20,k,M1rec(k-1)) == vector(20,k,M1half(k-1)) =for GP-Test vector(20,k,M2rec(k-1)) == vector(20,k,M2half(k-1)) =for GP-Test vector(20,k,M3rec(k-1)) == vector(20,k,M3half(k-1)) 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 =for GP-DEFINE M0pow(k) = if(k==0,1, (1/4)*(2^k + (1-I)^k + (1+I)^k)) =for GP-DEFINE M1pow(k) = if(k==0,0, (1/4)*(2^k + I*(1-I)^k - I*(1+I)^k)) =for GP-DEFINE M2pow(k) = if(k==0,0, (1/4)*(2^k - (1-I)^k - (1+I)^k)) =for GP-DEFINE M3pow(k) = if(k==0,0, (1/4)*(2^k - I*(1-I)^k + I*(1+I)^k)) =for GP-Test vector(50,k,M0pow(k-1)) == vector(50,k,M0half(k-1)) =for GP-Test vector(50,k,M1pow(k-1)) == vector(50,k,M1half(k-1)) =for GP-Test vector(50,k,M2pow(k-1)) == vector(50,k,M2half(k-1)) =for GP-Test vector(50,k,M3pow(k-1)) == vector(50,k,M3half(k-1)) 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/ =for GP-DEFINE M0sum(k) = sum(i=0,floor(k/4), binomial(k, 4*i)) =for GP-DEFINE M1sum(k) = sum(i=0,floor(k/4), binomial(k, 4*i+1)) =for GP-DEFINE M2sum(k) = sum(i=0,floor(k/4), binomial(k, 4*i+2)) =for GP-DEFINE M3sum(k) = sum(i=0,floor(k/4), binomial(k, 4*i+3)) =for GP-Test vector(length(M0samples),k,M0sum(k-1)) == M0samples =for GP-Test vector(length(M1samples),k,M1sum(k-1)) == M1samples =for GP-Test vector(length(M2samples),k,M2sum(k-1)) == M2samples =for GP-Test vector(length(M3samples),k,M3sum(k-1)) == M3samples 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] =for GP-DEFINE Rsamples = [1, 2, 4, 8, 14, 24, 38, 60, 90, 136, 198, 292, 418] =for GP-DEFINE Rcases(k)=if(k%2,10,7)*2^floor(k/2) - 2*k - 6 =for GP-Test vector(length(Rsamples), k, Rcases(k-1)) == Rsamples =for GP-DEFINE Rrec(k)=if(k<4,Rsamples[k+1], 2*Rrec(k-1) + Rrec(k-2) - 4*Rrec(k-3) + 2*Rrec(k-4)) =for GP-Test vector(length(Rsamples), k, Rrec(k-1)) == Rsamples =for GP-DEFINE nearint(x)=if(abs(x-round(x)) < 0.000001, round(x), x) =for GP-DEFINE Rpow(k)=nearint( (7/2 + 5/2 * sqrt(2))*( sqrt(2))^k + (7/2 - 5/2 * sqrt(2))*(-sqrt(2))^k ) - 2*k - 6 =for GP-Test vector(length(Rsamples), k, Rpow(k-1)) == Rsamples =cut # 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 ) =for GP-DEFINE gd(x)=(x+x^2) / ( (1-2*x^2)*(1-x^8) ) =for GP-Test gd(x) == (x+x^2) / ( (1-x^2)*(1+x^2)*(1-2*x^2)*(1+x^4) ) =for GP-Test Vec(gd(x) - O(x^19)) == [1,1,2,2,4,4,8,8,17,17,34,34,68,68,136,136,273,273] /* sans initial 0s */ =for GP-DEFINE vector_modulo(v,i) = v[(i% #v)+1]; =for GP-DEFINE d_by_powers(k) = 8/15*2^ceil(k/2) - 1/15*vector_modulo([8,1,1,2,2,4,4,8],k); =for GP-Test vector(19,k,k--; d_by_powers(k)) == [0,1,1,2,2,4,4,8,8,17,17,34,34,68,68,136,136,273,273] /* sans initial 0s */ =for GP-DEFINE d_by_powers(k) = floor(8/15*2^ceil(k/2)); =for GP-Test vector(19,k,k--; d_by_powers(k)) == [0,1,1,2,2,4,4,8,8,17,17,34,34,68,68,136,136,273,273] /* sans initial 0s */ 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 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 interior boundary 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 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/KochSnowflakes.pm0000644000175000017500000004607112606435151020504 0ustar gggg# 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 . # 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 = 122; 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 in the Math-PlanePath sources 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 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-122/lib/Math/PlanePath/HilbertCurve.pm0000644000175000017500000007072612606435152020166 0ustar gggg# Copyright 2010, 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 . # http://www.cut-the-knot.org/do_you_know/hilbert.shtml # Java applet # # 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 # package Math::PlanePath::HilbertCurve; use 5.004; use strict; #use List::Util 'max','min'; *max = \&Math::PlanePath::_max; use vars '$VERSION', '@ISA'; $VERSION = 122; 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 =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 XThis path is an integer version of the curve described by David 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, ... | | 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. Right is used here since that's what most of the other PlanePaths do. Swap X and Y for upwards first instead. See F in the Math-PlanePath sources 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 with the Hilbert curve N as an index. A search through an 2-D region is then usually a fairly modest linear search through N values. C gives exact N range for a rectangle, or see 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. William Gosper's HAKMEM item 115 has this with tables for the state and X,Y bits, =over L =back XXAnd 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 since 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 9 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 combination 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 "reversal" 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). The only thing to note is that the numbering there starts n=1, unlike the PlanePath 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, version 1.) =cut =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 form in the path 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, ... 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. As higher levels these the 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 each 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 has 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=S,2=W,3=N) 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 These 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 in the Math-PlanePath sources for a sample program printing the A163359 permutation values. =head1 SEE ALSO L, L, L L, L, L, L L, L David Hilbert, "Ueber die stetige Abbildung einer Line auf ein FlE<228>chenstE<252>ck", Mathematische Annalen, volume 38, number 3, p459-460, DOI 10.1007/BF01199431. L Z<> L =head1 HOME PAGE L =head1 LICENSE Copyright 2010, 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 . =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-122/lib/Math/PlanePath/AlternatePaper.pm0000644000175000017500000015756412611353341020500 0ustar gggg# 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 . # 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 = 122; 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 /| +----+----+ # / | |\ 1||<--/ # /2 | |^\ || 0/ # /-->| || \v| / # +----+ ||3 \|/ # /|\ 3|| +----+ # / |^\ || |<--/ state=4 # / 0|| \v| | 2/ # /-->||1 \| | / # +----+----+ |/ # # |\ state=8 +----+----+ state=12 # |^\ \ 1||<--/| # || \ \ || 0/ | # ||3 \ \v| /2 | # +----+ \|/-->| # |<--/|\ +----+ # | 2/ |^\ \ 3|| # | /0 || \ \ || # |/-->||1 \ \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, undef,undef,undef, -1, undef,undef,undef, 0, undef,undef,undef, 0, undef,undef,undef, ); my @state_to_dy = (0, undef,undef,undef, 0, undef,undef,undef, 1, undef,undef,undef, -1, undef,undef,undef, ); 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] + $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 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__ #------------------------------------------------------------------------------ # Old code 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); # } =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 =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 =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. If that's done then the turn calculation is 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 has two 11 bit pairs. The GRS is -1 on an even length run, for example 1111 has 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.) =head2 Area The area enclosed by the curve for points N=0 to N=2^k inclusive is A[k] = (2^floor((k-1)/2) - 1) * (2^ceil((k-1)/2) - 1) = / (2^k - 3*2^h + 2) / 2 if k odd \ (2^k - 4*2^h + 2) / 2 if k even where h=floor(k/2) = 1/2*0, 0*0, 0*1, 1*1, 1*3, 3*3, 3*7, 7*7, 7*15, 15*15, ... = 0, 0, 0, 1, 3, 9, 21, 49, 105, 225, 465, 961, ... (A027556/2) =for GP-DEFINE AsamplesP = [0, 0, 0, 1*1, 1*3, 3*3, 3*7, 7*7, 7*15, 15*15, 15*31, 31*31, 31*63, 63*63, 63*127, 127*127, 127*255] =for GP-DEFINE Asamples = [0, 0, 0, 1, 3, 9, 21, 49, 105, 225, 465, 961, 1953, 3969, 8001, 16129, 32385] =for GP-DEFINE A(k) = (2^floor((k-1)/2) - 1) * (2^ceil((k-1)/2) - 1) =for GP-DEFINE A2(k)= local(h); h=floor(k/2); if(k%2, (2^k - 4*2^h + 2)/2, (2^k - 3*2^h + 2)/2) =for GP-DEFINE A3(k)= local(h); h=floor(k/2); (2^h-1)*(2^if(k%2,h,h-1) - 1) =for GP-Test vector(length(Asamples), k, A(k-1)) == Asamples =for GP-Test vector(length(Asamples), k, A2(k-1)) == Asamples =for GP-Test vector(length(Asamples), k, A3(k-1)) == Asamples =for GP-Test Asamples == AsamplesP =cut # Pari: for(k=0,16,print1(A(k),", ")) # K = H^2 # (H-1)*(H-1 + 1)/2 - (H-2)/2 - (H-2)/2 - 1 # = 1/2*H^2 - 3/2*H + 1 # = (H^2 - 3*H + 2)/2 # = (H-1)(H-2)/2 =pod When k is even the curve is a triangular stack with every second block along the bottom and right sides unfilled. *--* Y=2^h-1 | | where h=k/2 *--*--* | | *--*--*--* | | | | *--*--*--*--* | | | | *--*--*--*--*--* | | | | | | *--*--*--*--*--*--* | | | | | | *--*--*--*--*--*--*--* | | | | | | | | *--* *--* *--* *--* * Y=0 X=1 X=2^h The area formula can be found by moving the alternating blocks in the right column to fill the gaps in the bottom row, and moving the top half of the triangle down to complete a rectangle *--------*--*--*--*--* | | | | | | height = 2^(h-1) - 1 | *--*--*--*--*--* = 2^floor((k-1)/2) - 1 | | | | | | | | *--*--*--*--*--*--* width = 2^h - 1 | | | | | | | | = 2^ceil((k-1)/2) - 1 *--*__*--*__*--*__*--* When k is odd the curve is a pyramid stack with every second block along the bottom unfilled. * Y=2^h | *--*--* Y=2^h-1 | | | where h=floor(k/2) *--*--*--*--* | | | | | *--*--*--*--*--*--* | | | | | | | *--*--*--*--*--*--*--*--* | | | | | | | | | *--*--*--*--*--*--*--*--*--*--* | | | | | | | | | | | *--*--*--*--*--*--*--*--*--*--*--*--* | | | | | | | | | | | | | *--*--*--*--*--*--*--*--*--*--*--*--*--*--* | | | | | | | | | | | | | | | *--* *--* *--* *--* *--* *--* *--* *--* X=1 X=2^h X=2^(2h)-1 This too can be rearranged, this time to make a square. The right hand half of the bottom row fills the gaps in the left. The remaining right hand triangle then goes above the left triangle. * Y=2^h | *-----------------*--* Y=2^h - 1 | | | | *--*--* | | | | | *--*--*--* height = 2^h - 1 | | | | | = 2^floor((k-1)/2) | *--*--*--*--* | | | | | | width = 2^h - 1 | *--*--*--*--*--* = 2^ceil((k-1)/2) | | | | | | | | *--*--*--*--*--*--* floor((k-1)/2) = ceil((k-1)/2) | | | | | | | | since (k-1)/2 is an integer *--*--*--*--*--*--*--* when k is odd | | | | | | | | *--*__*--*__*--*__*--*__* X=1 X=2^h For k=0 through k=2 there are no areas to copy this way but 2^0-1=0 in the formula gives the desired A[0]=A[1]=A[2]=0. =head2 Area Increment The new area added between N=2^k and N=2^(k+1) is dA[k] = A[k+1] - A[k] = (2^floor(k/2) - 1) * 2^ceil(k/2) / 2 = (2^k - 2^ceil(k/2)) / 2 = 0, 0, 1, 2, 6, 12, 28, 56, 120, 240, 496, 992, ... (A122746) =for GP-DEFINE dAsamples = [0, 0, 1, 2, 6, 12, 28, 56, 120, 240, 496, 992, 2016, 4032, 8128, 16256, 32640] =for GP-DEFINE dA(k) = (2^floor(k/2) - 1) * 2^ceil(k/2) / 2 =for GP-DEFINE dA2(k) = (2^k - 2^ceil(k/2)) / 2 =for GP-Test vector(length(dAsamples), k, dA(k-1)) == dAsamples =for GP-Test vector(length(dAsamples), k, dA2(k-1)) == dAsamples =for GP-Test vector(20, k, dA(k-1)) == vector(20, k, A(k+1 -1) - A(k -1)) =cut # dA[k] = A[k+1]-A[k] # = (2^floor(k/2) - 1) * (2^ceil(k/2) - 1) # - (2^floor((k-1)/2) - 1) * (2^ceil((k-1)/2) - 1) # if k even h=floor(k/2) k/2 integer # = (2^h - 1) * (2^h - 1) - (2^h/2 - 1) * (2^h - 1) # = (2^h - 1 - (2^h/2 - 1)) * (2^h - 1) # = (2^h - 1 - 2^h/2 + 1) * (2^h - 1) # = 2^h * (2^h - 1) / 2 # = 2^k/2 - 2^h/2 # if k odd h=floor(k/2) k/2 not integer # = (2^h - 1) * (2*2^h - 1) - (2^h - 1) * (2^h - 1) # = (2^h - 1) * (2*2^h - 1 - (2^h - 1)) # = (2^h - 1) * (2*2^h - 1 - 2^h + 1) # = (2^h - 1) * 2^h # = 2^k/2 - 2^h # dA[k] = (2^floor(k/2) - 1) * 2^ceil(k/2) / 2 =pod =head2 Convex Hull Area A convex hull is the smallest convex polygon which contains a given set of points. For the alternate paper the area of the convex hull for points N=0 to N=2^k inclusive is HA[k] = (2^k - 1)/2 The hull is a triangle of area 2^k/2 except for an end triangle of area 1/2 at the top for even level or right for odd level. =head2 Right Boundary The boundary length of the curve from N=0 to N=2^k on its right side is R[k] = / 1 if k=0 | 2*2^h if k even >= 2 \ 6*2^h - 4 if k odd >= 1 where h=floor(k/2) = 1, 2, 4, 8, 8, 20, 16, 44, 32, 92, 64, 188, 128, 380, 256, ... =for GP-DEFINE Rsamples = [1, 2, 4, 8, 8, 20, 16, 44, 32, 92, 64, 188, 128, 380, 256, 764, 512] =for GP-DEFINE R(k) = local(h); h=floor(k/2); if(k==0, 1, if(k%2, 6*2^h-4, 2*2^h)) =for GP-Test vector(length(Rsamples), k, R(k-1)) == Rsamples For k even the right boundary is along the X axis 2^h X axis horizontals 2^h X axis indentations, if k >= 2 ----- 2*2^h For k odd the right boundary is along the X axis and then up the right side to the top, 2*2^h - 1 X axis horizontals 2*2^h - 2 X axis indentations 2^h right slope verticals 2^h - 1 right slope horizontals ------- 6*2^h - 4 =head2 Left Boundary The boundary length of the curve from N=0 to N=2^k on its left side is L[k] = / 1 if k=0 | 4*2^h - 4 if k even >= 2 \ 2*2^h if k odd >= 1 where h=floor(k/2) = 1, 2, 4, 4, 12, 8, 28, 16, 60, 32, 124, 64, 252, 128, 508, ... =for GP-DEFINE Lsamples = [1, 2, 4, 4, 12, 8, 28, 16, 60, 32, 124, 64, 252, 128, 508, 256, 1020] =for GP-DEFINE L(k) = local(h); h=floor(k/2); if(k==0, 1, if(k%2, 2*2^h, 4*2^h-4)) =for GP-Test vector(length(Lsamples), k, L(k-1)) == Lsamples For k even the left boundary is up the left slope then down the vertical 2^h left slope horizontals 2^h - 1 left slope verticals 2^h - 1 right edge verticals 2^h - 2 right edge indentations ----- 4*2^h - 4 For k odd the left boundary is the left slope, and this time it includes a final vertical line segment 2^h left slope horizontals 2^h left slope verticals ------- 2*2^h =cut # *---* k=2 # | | right=4 # O---* E left=4 # # E k=3 # | right=8 # *---*---* left=4 # | | | # O---* *---* # E # | # *---*---* # | | | # *---*---*---*---* k=5 # | | | | | right=20 # *---*---*---*---*---*---* left=8 # | | | | | | | # O---* *---* *---* *---* # =pod =head2 Boundary The total boundary length of the curve from N=0 to N=2^k is B[k] = L[k] + R[k] = / 6*2^h - 4 if k even \ 8*2^h - 4 if k odd where h=floor(k/2) = 2, 4, 8, 12, 20, 28, 44, 60, 92, 124, 188, 252, 380, ... (2*A027383) =for GP-DEFINE Bsamples = [2, 4, 8, 12, 20, 28, 44, 60, 92, 124, 188, 252, 380, 508, 764, 1020, 1532] =for GP-DEFINE B(k) = local(h); h=floor(k/2); if(k%2, 8*2^h-4, 6*2^h-4) =for GP-Test vector(length(Bsamples), k, B(k-1)) == Bsamples =for GP-Test vector(20, k, B(k-1)) == vector(20, k, R(k-1)+L(k-1)) =for GP-Test vector(20, k, 4*A(k-1)+B(k-1)) == vector(20, k, 2*2^(k-1)) =for GP-Test 4*(p/2 - 1)*(p-1) + 6*p-4 == 2*p^2 =for GP-Test 4*(p-1)*(p-1) + 8*p-4 == 4*p^2 The special case for k=0 is eliminated since the k even 6*2^h-4 is the desired 2 when k=0, h=0. Every enclosed unit square has all four sides traversed so by counting inside and outside sides of the segments have 2*N = 4*A + B. This can be verified for A[k] and B[k] 4*A[k] + B[k] = 4* / (2^h/2 - 1) * (2^h - 1) if k even \ (2^h - 1) * (2^h - 1) if k odd + / 6*2^h - 4 if k even \ 8*2^h - 4 if k odd = / 2 * 2^h * 2^h if k even \ 4 * 2^h * 2^h if k odd = 2*2^k This relation also gives a formula for B[k] using the floor and ceil pair from A[k] B[k] = 2*2^k - 4*A[k] = 2*2^k - (2^floor((k+1)/2) - 2) * (2^ceil((k+1)/2) - 2) =for GP-DEFINE BfromA(k) = 2*2^k - (2^floor((k+1)/2) - 2) * (2^ceil((k+1)/2) - 2) =for GP-Test vector(length(Bsamples), k, BfromA(k-1)) == Bsamples =head2 Single Points The number of single-visited points for N=0 to N=2^k inclusive is S[k] = / 3*2^h - 1 if k even \ 4*2^h - 1 if k odd = 2, 3, 5, 7, 11, 15, 23, 31, 47, 63, 95, 127, ... (A052955) =for GP-DEFINE Ssamples = [2, 3, 5, 7, 11, 15, 23, 31, 47, 63, 95, 127, 191, 255, 383, 511, 767] =for GP-DEFINE S(k) = local(h); h=floor(k/2); if(k%2, 4*2^h-1, 3*2^h-1) =for GP-Test vector(length(Ssamples), k, S(k-1)) == Ssamples =cut # Pari: for(k=0,16,print1(S(k),", ")) =pod The single points are all on the outer edges and those sides can be counted easily. The singles can also be obtained from the boundary. Each new line segment which increases the area also increases the double points, so area=doubles. Such a segment decreases the singles by -1 and the boundary by -2. A new line segment which doesn't enclose new area increases the singles by +1 and the boundary by +2. Starting from singles=1 boundary=0 means S[N] = B[N]/2 + 1 =for GP-Test vector(20, k, S(k-1)) == vector(20, k, B(k-1)/2+1) Or with singles and doubles adding up to N+1 points the doubles=area can give the singles from the area. S + 2*D = N+1 N=number of segments, N+1=number of points =for GP-Test vector(20, k, S(k-1)) == vector(20, k, 2^(k-1)+1 - 2*A(k-1)) =head1 OEIS The alternate paper folding curve is in Sloane's Online Encyclopedia of Integer Sequences as =over L (etc) =back A106665 next turn 1=left,0=right, a(0) is turn at N=1 A209615 turn 1=left,-1=right A020985 Golay/Rudin/Shapiro sequence +1,-1 dX and dY alternately dSum, change in X+Y A020986 Golay/Rudin/Shapiro cumulative X coordinate (undoubled) X+Y coordinate sum A020990 Golay/Rudin/Shapiro * (-1)^n cumulative Y coordinate (undoubled) X-Y diff, starting from N=1 A020987 GRS with values 0,1 instead of +1,-1 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. A077957 Y at N=2^k, being alternately 0 and 2^(k/2) A000695 N on X axis, base 4 digits 0,1 only A062880 N on diagonal, base 4 digits 0,2 only A022155 N positions of left or down segment, being GRS < 0, ie. dSum < 0 so move to previous anti-diagonal A203463 N positions of up or right segment, being GRS > 0, ie. dSum > 0 so move to next anti-diagonal A020991 N-1 of first time on X+Y=k anti-diagonal A212591 N-1 of last time on X+Y=k anti-diagonal A093573 N-1 of points on the anti-diagonals d=X+Y, by ascending N-1 value within each diagonal 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. A027556 area*2 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) 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 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 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/ZOrderCurve.pm0000644000175000017500000004617312606435146020004 0ustar gggg# Copyright 2010, 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 . # 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 = 122; 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 X=Y diagonal, base 9 digits 0,3,6 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) 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/GrayCode.pm0000644000175000017500000006164712606435152017267 0ustar gggg# 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 . # 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 = 122; 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 $offset = 0; foreach my $digit (reverse @$aref) { # high to low $offset += ($digit = ($digit - $offset) % $radix); # mutate $aref->[i] } } # $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 =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 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. The C $r> option can select another radix. This 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 what type of Gray code is used. 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 downwards 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 as given 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). 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 The 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="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 with values written in binary 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 =head1 SEE ALSO L, L, L, L =head1 HOME PAGE L =head1 LICENSE 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 . =cut Math-PlanePath-122/lib/Math/PlanePath/Base/0002755000175000017500000000000012641645163016076 5ustar ggggMath-PlanePath-122/lib/Math/PlanePath/Base/NSEW.pm0000644000175000017500000000633512606435146017214 0ustar gggg# 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 . package Math::PlanePath::Base::NSEW; use 5.004; use strict; use vars '$VERSION'; $VERSION = 122; 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 _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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/Base/Generic.pm0000644000175000017500000001512212606435146020006 0ustar gggg# Copyright 2010, 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 . package Math::PlanePath::Base::Generic; use 5.004; use strict; use vars '$VERSION','@ISA','@EXPORT_OK'; $VERSION = 122; use Exporter; @ISA = ('Exporter'); @EXPORT_OK = ('round_nearest', # not documented yet 'is_infinite', 'floor', 'xy_is_even'); # 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 ... 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 { 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath/Base/Digits.pm0000644000175000017500000003017612606435146017663 0ustar gggg# Copyright 2010, 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 . # # bit_join_lowtohigh package Math::PlanePath::Base::Digits; use 5.004; use strict; use vars '$VERSION','@ISA','@EXPORT_OK'; $VERSION = 122; 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; } 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 Kevin Ryde This file is part of Math-PlanePath. Math-PlanePath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Math-PlanePath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 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-122/lib/Math/PlanePath.pm0000644000175000017500000023252012641634561015564 0ustar gggg# Copyright 2010, 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 . package Math::PlanePath; use 5.004; use strict; use vars '$VERSION'; $VERSION = 122; # 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 _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_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; foreach my $n1 (@n1) { foreach my $n2 (@n2) { if (abs($n1 - $n2) == $arms) { push @n, _min($n1,$n2); } } } @n = sort {$a<=>$b} @n; return @n; } sub xyxy_to_n_either { my $self = shift; my @n_list = $self->xyxy_to_n_list_either(@_); return $n_list[0]; } #------------------------------------------------------------------------------ # 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 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]; } 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 Manhatten SumAbs infimum =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 WunderlichSerpentine transpose parts of PeanoCurve HilbertCurve 2x2 self-similar quadrant HilbertSides 2x2 self-similar quadrant segments 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-simlar 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 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 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. =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 or exceeding a 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 can guarantee an exact lo/hi range 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 might 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 some 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 "Manhatten" 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. left ^ | N-1 ----------- N --> straight | v left 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, PyramidSides, Corner, 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), WunderlichSerpentine (default), WunderlichMeander, KochelCurve, GosperIslands, GosperSide SierpinskiTriangle, SierpinskiArrowhead, SierpinskiArrowheadCentres, TerdragonCurve, TerdragonRounded, TerdragonMidpoint, 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, 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, (Y+X)/2 rotate +60 (anti-clockwise) (X+3Y)/2, (Y-X)/2 rotate -60 (clockwise) -(X+3Y)/2, (X-Y)/2 rotate +120 (3Y-X)/2, -(X+Y)/2 rotate -120 -X,-Y rotate 180 (X+3Y)/2, (X-Y)/2 mirror across the X=3*Y twelfth line 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 even characterizes 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 can 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 should implement C to return all those Ns and 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 =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 in the Math-PlanePath source code, 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-122/t/0002755000175000017500000000000012641645163012154 5ustar ggggMath-PlanePath-122/t/number-fraction.t0000644000175000017500000004233212563061161015427 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments '###'; my $test_count = (tests => 447)[1]; plan tests => $test_count; # version 1.14 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); #------------------------------------------------------------------------------ # 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 ($got_y == $y); 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 = ( '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', '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', 'TerdragonMidpoint', 'TerdragonMidpoint,arms=1', 'TerdragonMidpoint,arms=2', 'TerdragonMidpoint,arms=6', 'TerdragonCurve', 'TerdragonCurve,arms=1', 'TerdragonCurve,arms=2', 'TerdragonCurve,arms=6', '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', '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 'Corner', 'PyramidSides', '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-122/t/bigint_common.pm0000644000175000017500000004060112523323472015330 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-122/t/AlternatePaperMidpoint.t0000644000175000017500000001607012606435144016753 0ustar gggg#!/usr/bin/perl -w # Copyright 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 Test; plan tests => 176; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::AlternatePaperMidpoint; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/DiamondArms.t0000644000175000017500000001027212606435143014533 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 Test; plan tests => 1568; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::DiamondArms; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/BetaOmeta.t0000644000175000017500000001745012606435144014204 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 Test; plan tests => 344; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::BetaOmega; my $path = Math::PlanePath::BetaOmega->new; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/SierpinskiArrowhead.t0000644000175000017500000001366712606435141016323 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 Test; plan tests => 165; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::SierpinskiArrowhead; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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()'); } #------------------------------------------------------------------------------ # 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-122/t/FlowsnakeCentres.t0000644000175000017500000001416412606435143015616 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 Test; plan tests => 347; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::FlowsnakeCentres; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/bigfloat.t0000644000175000017500000002252312641426375014134 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 # } #------------------------------------------------------------------------------ # 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'); } #------------------------------------------------------------------------------ # 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); #------------------------------------------------------------------------------ # 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"); } #------------------------------------------------------------------------------ # 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, "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, "round_down_pow(3) 3^64 + 1.25 exp"); } exit 0; Math-PlanePath-122/t/HilbertSpiral.t0000644000175000017500000001514412606435142015103 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 Test; plan tests => 212; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::HilbertSpiral; my $path = Math::PlanePath::HilbertSpiral->new; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/ChanTree.t0000644000175000017500000001377512606435144014042 0ustar gggg#!/usr/bin/perl -w # Copyright 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 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; # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::ChanTree; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/HexSpiral-load.t0000644000175000017500000000201011554146764015151 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-122/t/KochPeaks.t0000644000175000017500000001342212606435142014204 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 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 = 122; 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-122/t/HexArms.t0000644000175000017500000001154612606435142013710 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 Test; plan tests => 126; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::HexArms; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/CellularRule190.t0000644000175000017500000001441212606435144015163 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 Test; plan tests => 506; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::CellularRule190; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/KnightSpiral.t0000644000175000017500000000534012606435142014733 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; plan tests => 1010;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::KnightSpiral; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/DekkingCurve.t0000644000175000017500000002100212606435143014707 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 Test; plan tests => 219;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments; require Math::PlanePath::DekkingCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/ArchimedeanChords.t0000644000175000017500000001235212606435144015702 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 Test; plan tests => 74; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments; require Math::PlanePath::ArchimedeanChords; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/HexSpiral.t0000644000175000017500000000714712606435142014242 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; plan tests => 35; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::HexSpiral; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/ImaginaryBase.t0000644000175000017500000000754112606435142015054 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 Test; plan tests => 311; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::ImaginaryBase; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/DragonCurve.t0000644000175000017500000002044612606435143014560 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 Test; plan tests => 615; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::DragonCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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"); } #------------------------------------------------------------------------------ # 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'); } #------------------------------------------------------------------------------ # 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-122/t/GcdRationals.t0000644000175000017500000002160012606435143014704 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 Test; plan tests => 625; 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'}}; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/TriangularHypot.t0000644000175000017500000001637512606435141015501 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 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 = 122; 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-122/t/HilbertCurve.t0000644000175000017500000001417612606435142014741 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 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 = 122; 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-122/t/DiagonalRationals.t0000644000175000017500000001263112606435143015731 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 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 = 122; 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-122/t/GosperSide.t0000644000175000017500000001562612606435143014411 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 Test; plan tests => 1576; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::GosperSide; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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_dir { 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_dir($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_dir($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_dir($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-122/t/DiamondSpiral.t0000644000175000017500000001017512606435143015065 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; plan tests => 75; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::DiamondSpiral; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/Base-Digits.t0000644000175000017500000001665312563055565014451 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 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) { my $p = pow3_if_exact($i); if (! $p) { MyTestHelpers::diag ("round_up_pow(3) tests stop for round-off at i=$i"); last; } { 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-122/t/HIndexing.t0000644000175000017500000001327712606435142014221 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 Test; plan tests => 466; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::HIndexing; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/KochSquareflakes.t0000644000175000017500000001002712606435142015565 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 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 = 122; 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); } 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); } } #------------------------------------------------------------------------------ # 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-122/t/QuintetCurve.t0000644000175000017500000001605212606435141014773 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 Test; plan tests => 358; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::QuintetCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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()'); } #------------------------------------------------------------------------------ # 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 ... if (0) { my $good = 1; foreach my $arms (1 .. 4) { my $path = Math::PlanePath::QuintetCurve->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 = 'undef'; } if (! defined $rev_n || $rev_n != $n) { $good = 0; MyTestHelpers::diag ("xy_to_n($x,$y) reverse to expect n=$n, got $rev_n"); } } } ok ($good, 1); } exit 0; Math-PlanePath-122/t/CellularRule.t0000644000175000017500000001617512606435144014741 0ustar gggg#!/usr/bin/perl -w # Copyright 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 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 = 122; 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-122/t/HexSpiralSkewed-unskew.t0000644000175000017500000000332612136177346016721 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-122/t/RationalsTree.t0000644000175000017500000003142012606435141015105 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 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 = 122; 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-122/t/GreekKeySpiral.t0000644000175000017500000001411012606435142015210 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 Test; plan tests => 2251; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::GreekKeySpiral; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/MultipleRings.t0000644000175000017500000003271212606435142015135 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 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 = 122; 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-122/t/PyramidRows.t0000644000175000017500000001410212606435142014610 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 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 = 122; 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-122/t/VogelFloret.t0000644000175000017500000000767112606435140014573 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; plan tests => 29; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::VogelFloret; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/bigint-lite.t0000644000175000017500000000240712523350431014537 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 . use 5.004; use strict; use Test; use lib 't'; use MyTestHelpers; my $test_count = (tests => 648)[1]; plan tests => $test_count; # uncomment this to run the ### lines #use Smart::Comments '###'; 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-122/t/UlamWarburtonQuarter.t0000644000175000017500000001454612606435140016510 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 Test; plan tests => 317; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::UlamWarburtonQuarter; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/ComplexMinus.t0000644000175000017500000001370012606435144014760 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 Test; plan tests => 473; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::ComplexMinus; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/AnvilSpiral.t0000644000175000017500000000725212606435144014566 0ustar gggg#!/usr/bin/perl -w # Copyright 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 Test; plan tests => 31; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::AnvilSpiral; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/Base-Generic.t0000644000175000017500000000546212136177406014571 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-122/t/Rows.t0000644000175000017500000000720612606435141013270 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; plan tests => 40; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::Rows; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/SierpinskiArrowheadCentres.t0000644000175000017500000001441012606435141017632 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 Test; plan tests => 336; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::SierpinskiArrowheadCentres; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/Hypot.t0000644000175000017500000000676112606435142013447 0ustar gggg#!/usr/bin/perl -w # Copyright 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 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 = 122; 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-122/t/FibonacciWordFractal.t0000644000175000017500000000524612606435143016350 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 Test; plan tests => 6; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::FibonacciWordFractal; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/Columns.t0000644000175000017500000000746312606435144013766 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; plan tests => 42;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::Columns; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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'); } #------------------------------------------------------------------------------ # 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-122/t/CubicBase.t0000644000175000017500000001752712606435143014167 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 Test; plan tests => 191; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::CubicBase; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/MyTestHelpers.pm0000644000175000017500000002527512635103470015264 0ustar gggg# MyTestHelpers.pm -- my shared test script helpers # Copyright 2008, 2009, 2010, 2011, 2012, 2015 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; # uncomment this to run the ### lines #use Smart::Comments; # 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-122/t/ComplexPlus.t0000644000175000017500000000710012606435144014605 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 Test; plan tests => 31; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::ComplexPlus; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/StaircaseAlternating.t0000644000175000017500000001425012606435141016442 0ustar gggg#!/usr/bin/perl -w # Copyright 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 Test; plan tests => 37; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::StaircaseAlternating; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/SquareSpiral.t0000644000175000017500000002523112606435141014747 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; plan tests => 477; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::SquareSpiral; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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); } } #------------------------------------------------------------------------------ # 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-122/t/KochSnowflakes.t0000644000175000017500000001360412606435142015257 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 Test; plan tests => 160; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::KochSnowflakes; my $path = Math::PlanePath::KochSnowflakes->new; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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 (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,1); ok($dy,1); } } #------------------------------------------------------------------------------ # 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-122/t/TerdragonMidpoint.t0000644000175000017500000000742112606435141015766 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 Test; plan tests => 39; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::TerdragonMidpoint; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/Flowsnake.t0000644000175000017500000001567712606435143014304 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 Test; plan tests => 389; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::Flowsnake; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/SacksSpiral.t0000644000175000017500000001455612606435141014563 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 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 = 122; 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-122/t/CornerReplicate.t0000644000175000017500000001345512606435143015424 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 Test; plan tests => 396; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::CornerReplicate; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/PixelRings.t0000644000175000017500000000717012606435142014423 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 Test; plan tests => 15; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::PixelRings; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/Staircase.t0000644000175000017500000000707512606435141014260 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; plan tests => 76; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::Staircase; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/bigint.t0000644000175000017500000000334512523350412013605 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 . use 5.004; use strict; use Test; use lib 't'; use MyTestHelpers; my $test_count = (tests => 681)[1]; plan tests => $test_count; # uncomment this to run the ### lines #use Smart::Comments '###'; 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-122/t/LTiling.t0000644000175000017500000000703512606435142013701 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 Test; plan tests => 318; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::LTiling; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/DragonRounded.t0000644000175000017500000001567412606435143015103 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 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 = 122; 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-122/t/SierpinskiCurve.t0000644000175000017500000002307012606435141015460 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 Test; plan tests => 1254; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::SierpinskiCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/NumSeq-PlanePathDelta.t0000644000175000017500000004461612136646214016403 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-122/t/PlanePath.t0000644000175000017500000000467712167205763014232 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; plan tests => 29; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath; my $have_64bits = ((1 << 63) != 0); my $modulo_64bit_dodginess = ($have_64bits && ((~0)%2) != ((~0)&1)); #---------------------------------------------------------------------------- # _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-122/t/ImaginaryHalf.t0000644000175000017500000001344112606435142015050 0ustar gggg#!/usr/bin/perl -w # Copyright 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 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; # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::ImaginaryHalf; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/ZOrderCurve.t0000644000175000017500000000500712606435140014544 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; plan tests => 15; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments '###'; require Math::PlanePath::ZOrderCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/Diagonals.t0000644000175000017500000000761212606435143014242 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; plan tests => 95; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::Diagonals; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/HexSpiralSkewed.t0000644000175000017500000000561612606435142015404 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; plan tests => 23; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::HexSpiralSkewed; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/Columns-load.t0000644000175000017500000000201111554145242014661 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-122/t/DivisibleColumns.t0000644000175000017500000001613612606435143015615 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 Test; plan tests => 1426; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::DivisibleColumns; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/NumSeq-PlanePathTurn.t0000644000175000017500000001440512136177345016277 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 => 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_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-122/t/CoprimeColumns.t0000644000175000017500000001567412606435144015310 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 Test; plan tests => 236; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::CoprimeColumns; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/FactorRationals.t0000644000175000017500000001446212606435143015435 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 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 = 122; 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-122/t/UlamWarburton.t0000644000175000017500000002102612606435141015134 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 Test; plan tests => 1071; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments; require Math::PlanePath::UlamWarburton; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/KochelCurve.t0000644000175000017500000000716612606435142014556 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 Test; plan tests => 132; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::KochelCurve; my $path = Math::PlanePath::KochelCurve->new; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/PeanoCurve-load.t0000644000175000017500000000201311554145271015314 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-122/t/Corner.t0000644000175000017500000001511212606435143013563 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 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 = 122; 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-122/t/DragonMidpoint.t0000644000175000017500000001311212606435143015247 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 Test; plan tests => 576; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::DragonMidpoint; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/AlternatePaper.t0000644000175000017500000002621212606435144015246 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 Test; plan tests => 3485; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments; require Math::PlanePath::AlternatePaper; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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_dir { 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_dir ($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-122/t/TheodorusSpiral.t0000644000175000017500000000662412606435141015470 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 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 = 122; 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-122/t/SierpinskiTriangle.t0000644000175000017500000001472112606435141016144 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 Test; plan tests => 206; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::SierpinskiTriangle; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/TerdragonCurve.t0000644000175000017500000002204512606435141015266 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 Test; plan tests => 632; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments; require Math::PlanePath::TerdragonCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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"); } #------------------------------------------------------------------------------ # 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); } #------------------------------------------------------------------------------ # 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_dir { 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_dir ($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-122/t/NumSeq-PlanePathCoord.t0000644000175000017500000001622612375233105016410 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 Test; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments '###'; 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-122/t/SierpinskiCurveStair.t0000644000175000017500000001376312606435141016473 0ustar gggg#!/usr/bin/perl -w # Copyright 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 Test; plan tests => 53; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::SierpinskiCurveStair; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/KochCurve.t0000644000175000017500000002125312606435142014226 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 Test; plan tests => 146; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::KochCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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_dir { 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_dir($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_dir($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-122/t/HypotOctant.t0000644000175000017500000000767712606435142014627 0ustar gggg#!/usr/bin/perl -w # Copyright 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 Test; plan tests => 21; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::HypotOctant; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/CincoCurve.t0000644000175000017500000000740312606435144014400 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 Test; plan tests => 134; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Smart::Comments; require Math::PlanePath::CincoCurve; my $path = Math::PlanePath::CincoCurve->new; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/NumSeq-PlanePathN.t0000644000175000017500000000510212136177345015536 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 => 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-122/t/HilbertCurve-load.t0000644000175000017500000000202111554145257015646 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-122/t/File.t0000644000175000017500000000442112606435143013213 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 Test; plan tests => 13;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::File; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/QuintetCentres.t0000644000175000017500000001576112606435141015320 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 Test; plan tests => 357; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments; require Math::PlanePath::QuintetCentres; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/FilledRings.t0000644000175000017500000000743212606435143014543 0ustar gggg#!/usr/bin/perl -w # Copyright 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 Test; plan tests => 99; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } use Math::PlanePath::FilledRings; # uncomment this to run the ### lines #use Smart::Comments; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/QuadricCurve.t0000644000175000017500000001235412606435142014734 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 Test; plan tests => 20; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines #use Devel::Comments; require Math::PlanePath::QuadricCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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 n_to_turn { my ($n) = @_; for (;;) { if ($n == 0) { die "oops n=0"; } my $mod = $n % 8; if ($mod == 1) { return 1; } if ($mod == 2) { return -1; } if ($mod == 3) { return -1; } if ($mod == 4) { return 0; } if ($mod == 5) { return 1; } if ($mod == 6) { return 1; } if ($mod == 7) { return -1; } $n = int($n/8); } } sub dxdy_to_dir { 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_dir($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_dir($dx,$dy); my $got_turn = ($dir - $prev_dir) % 4; my $want_turn = n_to_turn($n-1) % 4; 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-122/t/DigitGroups.t0000644000175000017500000000626612606435143014605 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 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 = 122; 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-122/t/GrayCode.t0000644000175000017500000002116612606435143014036 0ustar gggg#!/usr/bin/perl -w # Copyright 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 Test; plan tests => 308; 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 = 122; 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); } } #------------------------------------------------------------------------------ # 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_dir { 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_dir ($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-122/t/WythoffArray.t0000644000175000017500000000516412606435140014763 0ustar gggg#!/usr/bin/perl -w # Copyright 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 Test; plan tests => 15;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::WythoffArray; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/PeanoCurve.t0000644000175000017500000000554712606435142014414 0ustar gggg#!/usr/bin/perl -w # Copyright 2010, 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 Test; plan tests => 20; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::PeanoCurve; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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'); } #------------------------------------------------------------------------------ # 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-122/t/CCurve.t0000644000175000017500000002740712606435144013535 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 Test; plan tests => 289; 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 = 122; 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"); } #------------------------------------------------------------------------------ # 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); } } #------------------------------------------------------------------------------ # 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 ], [ 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"); # } } #------------------------------------------------------------------------------ # 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 } #------------------------------------------------------------------------------ # 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; } #------------------------------------------------------------------------------ # 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_dir { 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_dir ($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-122/t/GosperIslands.t0000644000175000017500000001224212606435143015111 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 Test; plan tests => 596; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::GosperIslands; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/AztecDiamondRings.t0000644000175000017500000001353312606435144015706 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 Test; plan tests => 216;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::AztecDiamondRings; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/Rows-load.t0000644000175000017500000000177711554145213014213 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-122/t/PythagoreanTree.t0000644000175000017500000003255412606435142015444 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 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 = 122; 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-122/t/CfracDigits.t0000644000175000017500000001337112606435144014523 0ustar gggg#!/usr/bin/perl -w # Copyright 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 Test; plan tests => 39;; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } require Math::PlanePath::CfracDigits; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/t/QuadricIslands.t0000644000175000017500000000600512606435141015240 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 Test; plan tests => 31; use lib 't'; use MyTestHelpers; BEGIN { MyTestHelpers::nowarnings(); } # uncomment this to run the ### lines # use Smart::Comments; require Math::PlanePath::QuadricIslands; #------------------------------------------------------------------------------ # VERSION { my $want_version = 122; 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-122/META.json0000644000175000017500000000354012641645163013332 0ustar gggg{ "abstract" : "Mathematical paths through the 2-D plane.", "author" : [ "Kevin Ryde " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 6.98, CPAN::Meta::Converter version 2.150005", "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" } } }, "release_status" : "stable", "resources" : { "homepage" : "http://user42.tuxfamily.org/math-planepath/index.html", "license" : [ "http://www.gnu.org/licenses/gpl.html" ] }, "version" : "122", "x_serialization_backend" : "JSON::PP version 2.27203" } Math-PlanePath-122/META.yml0000644000175000017500000000202612641645163013160 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 6.98, CPAN::Meta::Converter version 2.150005' 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: '122' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' Math-PlanePath-122/SIGNATURE0000644000175000017500000011574212641645163013205 0ustar ggggThis file contains message digests of all files listed in MANIFEST, signed via the Module::Signature module, version 0.79. 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: SHA1 SHA1 842745cb706f8f2126506f544492f7a80dbe29b3 COPYING SHA1 27ae419ca53e8ae41c872aebba1acfb3c7cadc1a Changes SHA1 04cccc963e12a59bd4ad3fb22f48f45fb20ae202 MANIFEST SHA1 9b875407d60f086714a3ca39de8ef195c106d889 MANIFEST.SKIP SHA1 f8c9b8af3c080718831ff8365eceb1f658a0567c META.json SHA1 20b3131c28988f728b445f9610b3c68399653512 META.yml SHA1 6f78052217e39e3e34ff3fa3030051c828b4884e Makefile.PL SHA1 81a2514c77c639c7c7f27397d321f577efd44469 debian/changelog SHA1 ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4 debian/compat SHA1 8af7c0a0e4b6dc0de3d1eb1fb97ee52adacaf600 debian/control SHA1 9a5bfbfa84f6192db086129d4d14a8bbef70005f debian/copyright SHA1 8809cf474c2cbb3adae76f1d16e63ca5ef607878 debian/patches/pod-examples-dir.diff SHA1 5dc82115fcde301eb1313709f2853ea869d96945 debian/rules SHA1 61652cd1568dcf2614df833eba241755eee34e89 debian/source/format SHA1 273a458118fa59ed61d2af74b48622637cf37cb2 debian/watch SHA1 c75571d6a88ee204174b0db614865f420c63678b devel/Makefile SHA1 1f8064a685e76216741374163d035a75962233bc devel/alternate-paper-midpoint.pl SHA1 fb4963b17efb32f530f1f10be08ae101cf3cccb2 devel/alternate-paper.pl SHA1 fba90ce495e8b326eed3d1d7f0c49e0d9bc69efa devel/archimedean.pl SHA1 f3ae848c61e6b0fe9757b35f7af55fd093d9563b devel/aztec-diamond.pl SHA1 40ca6f1e4015dd001a55e2d704b49de63050c514 devel/beta-omega.pl SHA1 57b7960572aa8bb391b5f2172025990ddb7562c1 devel/bigint-lite.pl SHA1 51d5b2cca0b11351d5343deda91cd3f9ee797d54 devel/bignums.pl SHA1 cf4662438d0610e0b07683a528e1deff4b347a7c devel/biguv.pl SHA1 1924b974b429df576dc0cdcdf69cfab6bcacc565 devel/c-curve.pl SHA1 00debc4c9e782bd140aacbfd0332bf5e745fe3e2 devel/cellular-rule-oeis.pl SHA1 c091c01c399d7449ded17f198c02e4b6c7428f01 devel/cellular-rule-xpm.pl SHA1 678d94d2aca4c26e2efb0c9cbbbfdea6cfdd56c7 devel/cellular-rule.pl SHA1 4124d034132fb55ac638770d6601a952a467e4c1 devel/cellular-rule62.pl SHA1 908e399b39b4e65b76945fe787195ca33755f4d6 devel/cfrac-digits.pl SHA1 328ecca15e2b96684f51e474f6648aeca61f7bea devel/chan-tree.pl SHA1 6aa77682c0c09a2eed95018a669fcd591562de65 devel/complex-minus.pl SHA1 d2dae19a907aa8b0bb25c1e4e11ada8d0c3a7a07 devel/complex-revolving.pl SHA1 f7da96c8b1eec2339e6c20e4de55425b5dc1be3c devel/cont-frac.pl SHA1 8ace0982c31cf4e11ea8c3d558a66aebdb4e3af7 devel/coprime.pl SHA1 0b3fc8a3174ea7f659100dcaa3222c06ad60009d devel/corner-replicate.pl SHA1 e7cf90f01f86e7e2f62f26a3e343f1c4565b7910 devel/cubic-base.pl SHA1 c7ea2331ba64bf4bbe038e0d07d0f960d4b2d949 devel/dekking-curve.pl SHA1 03acfa3428efe211702f1b69b41f2994f55a4b52 devel/diagonals.pl SHA1 7051ef4c228460089f687175219e10b4fa141e35 devel/digit-groups.pl SHA1 b83f91d105fc779285f7d0cea2103ecb2a02bac5 devel/divisible-columns.pl SHA1 c0291d2625029010e5074aa227baaa22246eb579 devel/dragon.pl SHA1 5571bc81d4abcb828fd708c67d716e56a26d9b7c devel/exe-atan2.c SHA1 aaf6d4d455228cfb8995d07ba1861a01dbe51b43 devel/exe-complex-minus.c SHA1 f1ae223671c84c0a50d3eacd256a5e640e119047 devel/exe-complex-plus.c SHA1 1c0edcc63d503aea473f29a057afb6b08aa53d8c devel/exe-complex-revolving.c SHA1 03b5b85d9ee5319dd81249d6c9dc9e092035b4bb devel/factor-rationals.pl SHA1 ef84645e41ca11b369068d1cb98d3be8776ae368 devel/fibonacci-word.logo SHA1 3dd2ce4a990cdc5e882dca16a407bb7985a47438 devel/fibonacci-word.pl SHA1 64358fec0d5f6026aa9fa5d863df683e7140c681 devel/filled-rings.pl SHA1 67e251beef1e2a0cd3d1b0b4cf8c78b96800a7b0 devel/flowsnake-ascii.gp SHA1 606afaa68688aca3f408290bfff8d876098098b2 devel/flowsnake-levels.pl SHA1 c78f66065c7724bb5dbb67213720a7ff22089d82 devel/flowsnake.pl SHA1 49a218bea62de5a50d7291f6581aa83127d7456b devel/fractions-tree.pl SHA1 813c3577a3a530e7856e17512374387645b4498a devel/gcd-rationals-integer.pl SHA1 5254c84b691547d858f32b3bc25f49f2b9e68cd7 devel/gcd-rationals.pl SHA1 c686f82422018ca5f5293dbfb2d9bea6a2935f5a devel/gosper-islands-stars.pl SHA1 0aec72137f95f16ef8b475a647ea93af520b04d7 devel/gosper-side.pl SHA1 3ff1ce20b59032b01d074821ce3492ec39e91f71 devel/gray.pl SHA1 75ada1f6124a6a970eb30eb7ca75039b45157e9d devel/greek-key.pl SHA1 48455960d45b1e56419642f4e1b6116fdc41c8e2 devel/grep-values.pl SHA1 e18485a0db05d5512228bead6d978f779b227189 devel/grid.pl SHA1 d0eb3f72182d2a364e119c0894738002a21bf18c devel/hex-arms.pl SHA1 1e3c96450557e868655d7d84d31f74e7cfb2bb0c devel/hexhypot.pl SHA1 0cefa2a1618131177a0b5b92a3027780c840fd47 devel/hilbert-diamond.pl SHA1 7103c939d1574668226c077fb0a0d57c9c18e4ef devel/hilbert.pl SHA1 5db0576c6fccc34492b34a365292e3b61f2b1a03 devel/hypot.pl SHA1 bd068e155eb078e2e4d81ca6d3668a51b7f20943 devel/imaginary-base.pl SHA1 31bd44e8f7477b18dfe68714ddd3258a50105073 devel/imaginary-half.pl SHA1 bb029a6c372a441f93b7dbace0410fc2975b3b23 devel/interpolate.pl SHA1 5fd812644517f5d59c05a6ea538b62b22ef0786a devel/iterator.pl SHA1 b4059ce0ccb537712614167ed325c3428fd8ec3d devel/junk/FibonacciSquareSpiral.pm SHA1 debdd6131ab93314e13efa396a16048c3997b719 devel/koch-curve.pl SHA1 2d71f1329e54886bbe7c558e9660f4daf1a59a6c devel/koch-snowflakes.pl SHA1 088b226ed395de939047b1bd45f2b52b79b9d118 devel/koch-squareflakes.pl SHA1 acf75d82f55c20d8b4053abad5152ada8ba357fc devel/l-tiling.pl SHA1 944977bbf881a2284a59db9f850316f34e2e52b1 devel/lib/Math/PlanePath/BalancedArray.pm SHA1 83bcae7e3176cf8a92cac8e116906c5ceda5349c devel/lib/Math/PlanePath/BinaryTerms-oeis.t SHA1 1f5b670bd33fcf2a9f53d024d585e144c6afba7c devel/lib/Math/PlanePath/BinaryTerms.pm SHA1 74b85fc366e95fe8258a999ec6078894b5e97b39 devel/lib/Math/PlanePath/FibonacciWordKnott.pm SHA1 0bd9c41693e1b662a10ce29139fe70562dbf2562 devel/lib/Math/PlanePath/FourReplicate.pm SHA1 30b66cbdc874b277ce2d0fc44599350fa7a33753 devel/lib/Math/PlanePath/Godfrey.pm SHA1 0c63042a4fc05f2715814cb51ecad07e6c4bde24 devel/lib/Math/PlanePath/MooreSpiral.pm SHA1 fb0bf8d00d505047631d6a234a30cd9c35f8864a devel/lib/Math/PlanePath/NxN.pm SHA1 ec4c81067842f877472630b1cd3acaacfedd0c16 devel/lib/Math/PlanePath/NxNinv.pm SHA1 ed4187cf90c268005cb6673dc6b95512954ad4aa devel/lib/Math/PlanePath/NxNvar.pm SHA1 c8c01268a55103484c10ea344508e803dbaccb78 devel/lib/Math/PlanePath/ParabolicRows.pm SHA1 aba73be05381ff2aaa2605f24f3ba6c9821ddb5c devel/lib/Math/PlanePath/ParabolicRuns.pm SHA1 d6447a25394d8c68d1df1320945ef029e21eda27 devel/lib/Math/PlanePath/PeanoHalf.pm SHA1 3fa63f2c04186d28ff88c47705f2ff9f15107b9a devel/lib/Math/PlanePath/PeanoRounded.pm SHA1 0a7e072662a3d7a2d39be8c15b3b46159c10ff1e devel/lib/Math/PlanePath/PeanoVertices.pm SHA1 63ac28c4504ac5a7c5e5940dde33f766cfbf17b5 devel/lib/Math/PlanePath/PowerRows.pm SHA1 a005474b1a86cc5f7c0a1fd7bdf37e64e40cbbee devel/lib/Math/PlanePath/PyramidReplicate.pm SHA1 64726928c1be28089057afbe3cd3abd70e6971b6 devel/lib/Math/PlanePath/QuintetSide.pm SHA1 b29d43828e56dc68acc2ee1cf4da51cdc4eddb18 devel/lib/Math/PlanePath/R7DragonCurve.pm SHA1 66298ca2105cf4451d38002b85fb10267d79ee6e devel/lib/Math/PlanePath/SumFractions.pm SHA1 0a903dee5a12a51904b06e46c31385c2503673fb devel/lib/Math/PlanePath/WythoffDifference-oeis.t SHA1 4849ff21bdcd60c21d97c8ea5be9111d08edba0f devel/lib/Math/PlanePath/WythoffDifference.pm SHA1 e02cf35ee21a6e650fcab87a714a130ce948bbd2 devel/lib/Math/PlanePath/WythoffLines.pm SHA1 80df35459adf6dcd9620a12b0ad62126c07ced7e devel/lib/Math/PlanePath/WythoffTriangle-oeis.t SHA1 da611553695b198744af78c60ad26253a96f50cf devel/lib/Math/PlanePath/WythoffTriangle.pm SHA1 bcb69599d2c31cacc77534b6d086e94893101084 devel/lib/Math/PlanePath/Z2DragonCurve.pm SHA1 fa26df43eb2e9f6a99eb1a782f851701951a22ab devel/lib/Math/PlanePath/ZeckendorfTerms-oeis.t SHA1 19990222cb59aa1d8e84488f889b6b07060c0345 devel/lib/Math/PlanePath/ZeckendorfTerms.pm SHA1 132ea3b0a54c1d76bd6e33721f47156d3a1b9ec9 devel/lib/Math/PlanePath/four-replicate.pl SHA1 32960fe5a9ad6b311288cc1994735f016e6a0536 devel/lib/Math/PlanePath/godfrey.pl SHA1 9ae25b8d4a6858e5d23498c00eafae98bc249fcd devel/lib/Math/PlanePath/squares-dispersion.pl SHA1 fca6dfbf36e9182bbc0dfb82de9aa0aaf46c3d56 devel/lib/Math/PlanePath/wythoff-lines.pl SHA1 eca661b78c40122fe42d50fe159989c83340971c devel/lib/Math/PlanePath/z2-dragon.pl SHA1 ea886f9f74f3f46526c1f4692ff787c47ae3ca03 devel/lib/Math/SquareRadical.pm SHA1 8af28cb75595d086df2df2e6390cf6fd00089d81 devel/lib/Math/square-radical.pl SHA1 49e74075b9c3e9d0a15a2325d957882942e427c8 devel/mephisto-waltz.logo SHA1 591bb1b07fc26ab5adf0bdee8ea261578df8fe39 devel/misc.pl SHA1 26f8517646291da92ca136c20eb8401056414a44 devel/multiple-rings.pl SHA1 45005340fea236f43f42faebf9713ee11d6eac33 devel/number-fraction.pl SHA1 490b937a510fda6a53178aac27f7629f38f3c4e9 devel/numseq.pl SHA1 856703d32788785382fa2c0712b9685c3a7e3214 devel/peano.pl SHA1 815dda1f34f05af0176e7d894efef77baa62c56c devel/pixel-rings.pl SHA1 a0599b382d43115d5652264e60d8353b847a2650 devel/pythagorean.pl SHA1 9e3998835f633a839a7401ac659c2b2f5e55d0aa devel/quadric.pl SHA1 d446b2bd6297e62fe31538d52567619260a20766 devel/quintet-replicate.pl SHA1 81d1e0a182ba4271163a1dbe705bb58eb5121a0e devel/quintet.pl SHA1 e05f0510388397af2dcd1b49cab8c5b220494340 devel/r5-dragon.pl SHA1 3c1b0c1b1b862f86452718535b2569aedb445cf9 devel/r5.pl SHA1 c790a5b52c7e97a57dce7da31c38ba2f2c95ae64 devel/rationals-tree.pl SHA1 fefbde85ea52e02b2539ebd992ba6e178c6dcfd5 devel/run.pl SHA1 dcb8e8232f3c6892a91dab561e59d268974f8261 devel/sacks.pl SHA1 a0b812f5ecbb175f47f4584147ffff15f2a4b69b devel/sierpinski-arrowhead-stars.pl SHA1 593fff4bc7948c6a3732eb3273ea646349f87e18 devel/sierpinski-arrowhead.pl SHA1 e7d05dea0d34a349e6612f278b94652e42b2bc66 devel/sierpinski-curve.pl SHA1 91fd0463108513c9c506bba9b2e59d67177bccef devel/sierpinski-triangle.gnuplot SHA1 1592a5342dc04d828b22799967c555cc306feb28 devel/sierpinski-triangle.pl SHA1 0e56d4aa5e98893bddd5cd3db9648bb1152ac258 devel/square-spiral.gnuplot SHA1 fb880e2c18b1b18f36ab5625a71c4dae25105121 devel/square-spiral.pl SHA1 a03dea379b3560aa1b06161dd55f414582793468 devel/staircase-alternating.pl SHA1 3422445d4e837f90f2220747b0474f0aae6deadd devel/t-square.pl SHA1 53ce400820f27b517485eb46f5774b0d3c2632e3 devel/terdragon.pl SHA1 3ec6e5c13f90d48770fc2dc839a5777e0208875a devel/theodorus.gnuplot SHA1 5501b64523eed69f94045ca93bd9460bc01cbc14 devel/theodorus.pl SHA1 6cf07549e9004d8c6949f32437537a390966be4a devel/tree.pl SHA1 6f1e7432d6b739666d35bfa5abd60e029f82118b devel/triangular-spiral.pl SHA1 a561da6cc3c1d4522fdcaed30fd7419e649f1988 devel/ulam-warburton.pl SHA1 6210dd0708e981c1f842ecad232b4a4e3efb9552 devel/vertical.pl SHA1 9e9d4fb0d4dc83bfb8767bbd6dee037658fbc509 devel/vogel.pl SHA1 c6a93df95f1fdf3f9ebd966a191ce27a4002430d devel/wythoff-array.pl SHA1 4ab323e54160080ae17e4ea79bda687673a0ed51 examples/c-curve-wx.pl SHA1 a64e5948b56c28f877eb9946af44b6aac8a55c06 examples/cellular-rules.pl SHA1 35c48be5e10fde9cd4b26dc9eb19981d2acae764 examples/cretan-walls.pl SHA1 8128c14faf1c7ef6dcbbe5c10824a8177e1c7830 examples/hilbert-oeis.pl SHA1 35c83804512b7b6d0f669fbbfbb51a5cbbf71e3f examples/hilbert-path.pl SHA1 9acb8278e76ac291550f42ae2905ea8c63cebf84 examples/knights-oeis.pl SHA1 50488b1b7cbfc275412f411764084694ec891e2d examples/koch-svg.pl SHA1 cab83802d48994de0b7d2255a1121427bd96c2c9 examples/numbers.pl SHA1 f3c93dc3d1c87cf08205105d63f4a60fe71860f1 examples/other/dragon-curve.el SHA1 957ba2c9051631675c3a71ba74787055505c8581 examples/other/dragon-curve.gnuplot SHA1 e0770ee9db2d662ef01d1a7ea827a505f7413db5 examples/other/dragon-curve.logo SHA1 c246cc17d195b503814d33edc32b70b717870432 examples/other/dragon-curve.m4 SHA1 cd4a0645cc7875182a62a474b8cb1e9622e6f65e examples/other/dragon-pgf-latex.tex SHA1 58f2e3924007e802da929dcd9f44d0496170cd96 examples/other/dragon-pgf-plain.tex SHA1 df88e0a9a1b9fca4c5dfd9ec6486dc7b07ce7c5a examples/other/dragon-recursive.gri SHA1 0c29c79b8e1f41f48c0c094d6ca413228889f0c2 examples/other/fibonacci-word-fractal.logo SHA1 5ef65a052bba24d029fb0a912b26fbc0ba8a519f examples/other/flowsnake-ascii-small.gp SHA1 70449064f9d3e60b94ea594c2c29d7db2535b52f examples/other/sierpinski-triangle-bitand.gnuplot SHA1 373803ce248934a79007a6e9658020bea1d749f0 examples/other/sierpinski-triangle-replicate.gnuplot SHA1 069c35eca1cd4e65a5cd7235fdf19494489e2884 examples/other/sierpinski-triangle-text.gnuplot SHA1 fea5432deb95abd2455e7fa5117ad0f46b787e47 examples/other/sierpinski-triangle-text.logo SHA1 73eb2ce88b35df853d40e1020bf606a66f018bc1 examples/other/sierpinski-triangle.m4 SHA1 4326cdd8b5a312bac01b754093fbeb080c342046 examples/rationals-tree.pl SHA1 98f93c3bb89653e8635ae26fd2a764d500b0212e examples/sacks-xpm.pl SHA1 c18eb8bb049028581869a8b618d993a5fe620742 examples/square-numbers.pl SHA1 40769848b1c25aed832a4dc9179b8ffb648804d7 examples/ulam-spiral-xpm.pl SHA1 fdb5a8e06ac9734738edd0a6c0ac77305ef38659 lib/Math/NumSeq/OEIS/Catalogue/Plugin/PlanePath.pm SHA1 5259dd048f9db2ad4686f489b3cdb52a279ce514 lib/Math/NumSeq/PlanePathCoord.pm SHA1 ec3d652f17ae3570f88e76baaa765ad7048fb4d5 lib/Math/NumSeq/PlanePathDelta.pm SHA1 37829de1940556d223a9a4cd11a20ba33804c21e lib/Math/NumSeq/PlanePathN.pm SHA1 36c9d0b8283130a55687a3b91c3f8163a9bedd50 lib/Math/NumSeq/PlanePathTurn.pm SHA1 d23c8013ed47a851508404dc5b502785a9c581d9 lib/Math/PlanePath.pm SHA1 5ac0436fbfe2a2fa5ee85d2c9a05f9f288065870 lib/Math/PlanePath/AR2W2Curve.pm SHA1 efb5b61492403a676588f3c56e121ac07be13e93 lib/Math/PlanePath/AlternatePaper.pm SHA1 195c3cc9da43e69b547f22b0c40b9828e4fd09af lib/Math/PlanePath/AlternatePaperMidpoint.pm SHA1 20c6c37cf31241205e3adf4f8c31096362358d5f lib/Math/PlanePath/AnvilSpiral.pm SHA1 063a493152a1fc4bfda8eb0b5265fdbc7bbf5f9e lib/Math/PlanePath/ArchimedeanChords.pm SHA1 bfce42900f2103e01ed2d9ec6e3f7624a6e760d4 lib/Math/PlanePath/AztecDiamondRings.pm SHA1 13b2d2a00ef85cdf1cfba9cce80e502c032e4df8 lib/Math/PlanePath/Base/Digits.pm SHA1 ff702f887ac894d465942c00fdd9176247ce88ec lib/Math/PlanePath/Base/Generic.pm SHA1 44a9d1c2c7315630956b8ccd8795859ab0170f85 lib/Math/PlanePath/Base/NSEW.pm SHA1 448d2c51684493327921352c3f02c12462d40a62 lib/Math/PlanePath/BetaOmega.pm SHA1 3a1340c1c15035c67db9e77fc24af38da5208d9a lib/Math/PlanePath/CCurve.pm SHA1 374844d562ba87b5413819a85b40cdb56748d490 lib/Math/PlanePath/CellularRule.pm SHA1 5d49ef62630d634d01d60989743603e7cb4f2906 lib/Math/PlanePath/CellularRule190.pm SHA1 eacf9d1f8dff4792b8c3156cb2fdc74664fbcc44 lib/Math/PlanePath/CellularRule54.pm SHA1 2c53521cc2d3d84dd29bd93121559151a5cd0ad0 lib/Math/PlanePath/CellularRule57.pm SHA1 80bfe9dec142b1cbc040a3c4cc1b8697f5f9992a lib/Math/PlanePath/CfracDigits.pm SHA1 1c8da50e8e8114eaa29fe8060f4d5e191ece2e83 lib/Math/PlanePath/ChanTree.pm SHA1 3c7bcf9028fbc16c3c13e21009949c0c52f820c1 lib/Math/PlanePath/CincoCurve.pm SHA1 968ee128e356b9789f337d138d1d2f4f4cc823cb lib/Math/PlanePath/Columns.pm SHA1 6c3482addbc9cb786f8ed1f11b7b5c07b4e85ed4 lib/Math/PlanePath/ComplexMinus.pm SHA1 5893cec279e8893ffa8a7c6c8497af4ac3ef7c96 lib/Math/PlanePath/ComplexPlus.pm SHA1 a52fe7c4d8a3fd898a3391bb3258ebee354ae1b2 lib/Math/PlanePath/ComplexRevolving.pm SHA1 0b3ddeeb2039abb68e457bab89843608b60ea9d9 lib/Math/PlanePath/CoprimeColumns.pm SHA1 0e273a6a5ba517a76a51b037be5613b74cddebc1 lib/Math/PlanePath/Corner.pm SHA1 667a334bc87fa90c58602e188c28fae8f468c89c lib/Math/PlanePath/CornerReplicate.pm SHA1 4227e544576c9ebdbc909520467cc0620c62f114 lib/Math/PlanePath/CretanLabyrinth.pm SHA1 70d0845a8a7947f8601207b077a51981730aeeb8 lib/Math/PlanePath/CubicBase.pm SHA1 4da9207b37dceae007e4118eca8526e85823d01e lib/Math/PlanePath/DekkingCentres.pm SHA1 1c728436a5b7b78bb8381108e8064210699d1b60 lib/Math/PlanePath/DekkingCurve.pm SHA1 819fce3ccdad5178bf1be9ed4bbf153c19800203 lib/Math/PlanePath/DiagonalRationals.pm SHA1 d7ef14f0c4f75a339398a752a14db5241d23aa1a lib/Math/PlanePath/Diagonals.pm SHA1 d208a4d7588c05eb02d7f3262975248d6a24e61b lib/Math/PlanePath/DiagonalsAlternating.pm SHA1 882e74b8eaf3cbbb4eb32f473c567394708e73dd lib/Math/PlanePath/DiagonalsOctant.pm SHA1 ee8c03603299f5454c4f5d8ecf11ea96600a0145 lib/Math/PlanePath/DiamondArms.pm SHA1 39c1270e6eb6fcbbff6cd1e9ac0604efa32310c8 lib/Math/PlanePath/DiamondSpiral.pm SHA1 433785c863093d46586cefdcab4ca62640cad79d lib/Math/PlanePath/DigitGroups.pm SHA1 4fd8f943d8b85d8249720866cb8809a5d3cb7720 lib/Math/PlanePath/DivisibleColumns.pm SHA1 bba4924cd3f1bd6b3bb2958f2d16dd7bc35eaca8 lib/Math/PlanePath/DragonCurve.pm SHA1 b510a889add747fded115210b158d58c406b5ff6 lib/Math/PlanePath/DragonMidpoint.pm SHA1 ab41b65fdf2524fd146cbccdeb8d7cc44be0a777 lib/Math/PlanePath/DragonRounded.pm SHA1 3f8487c8d9d0fda5a9ff460811049404f17f9187 lib/Math/PlanePath/FactorRationals.pm SHA1 7af978541f4482d75c002b1e5cb7d0722c273b15 lib/Math/PlanePath/FibonacciWordFractal.pm SHA1 7086d2c42b5c94e97540747ec294770e531b5e1f lib/Math/PlanePath/File.pm SHA1 47e3db5c38989322429ded6adac5f2964ff787f7 lib/Math/PlanePath/FilledRings.pm SHA1 031e387beae936ffe9b99b53a32ede3dd45818f3 lib/Math/PlanePath/Flowsnake.pm SHA1 cbf47fb99a6eae36e8e3991f7b8b446d20662b8b lib/Math/PlanePath/FlowsnakeCentres.pm SHA1 ab64c036b7721faef36a07b707d1228079db57a9 lib/Math/PlanePath/FractionsTree.pm SHA1 d684b7bcdee40ce02c93f1528e1e9c64f43a670c lib/Math/PlanePath/GcdRationals.pm SHA1 1cce27bf08b8376e14d567b5cc6ae808226824d8 lib/Math/PlanePath/GosperIslands.pm SHA1 1c7a6831dc998b5a93e5a6839da3694f6adaf411 lib/Math/PlanePath/GosperReplicate.pm SHA1 916de2607e90d253fcf6c90a48198364434386b5 lib/Math/PlanePath/GosperSide.pm SHA1 314f8d870ed62a7450dcd23a24b157803ff38c26 lib/Math/PlanePath/GrayCode.pm SHA1 666a94303b5ce61c1f7133539225f3af1789cc83 lib/Math/PlanePath/GreekKeySpiral.pm SHA1 712953d9311552452c5713382a7f280055d3f7b8 lib/Math/PlanePath/HIndexing.pm SHA1 628ff502965930533fe5f60e32da214c32ad3b91 lib/Math/PlanePath/HeptSpiralSkewed.pm SHA1 98cda9054a1ac6229e447911ba6ad95eba5bba27 lib/Math/PlanePath/HexArms.pm SHA1 eed8d814d4945799a9d70e5ee96e1aaf3daee19d lib/Math/PlanePath/HexSpiral.pm SHA1 3d843ee102c43517118f4e93c781501916cbd2f9 lib/Math/PlanePath/HexSpiralSkewed.pm SHA1 a3b5e0ed61f284d47e512f83ee1ad0e95872f7d7 lib/Math/PlanePath/HilbertCurve.pm SHA1 8ed7a8822fb60b7948024c0f32b9edf83b248776 lib/Math/PlanePath/HilbertSides.pm SHA1 9807bd80574f5840eed83787da08e2d93dcc3326 lib/Math/PlanePath/HilbertSpiral.pm SHA1 b942ca7e9591e774601334b500ded60fda3bedee lib/Math/PlanePath/Hypot.pm SHA1 50dc6b4b1829369a7ced64cb173a0307898fc808 lib/Math/PlanePath/HypotOctant.pm SHA1 d8a2d3c91c2da0654d73b05a8ddb2beb8d570f62 lib/Math/PlanePath/ImaginaryBase.pm SHA1 88373c29fe8ac4cb14e010d1d0fe1dbc15f9a7a6 lib/Math/PlanePath/ImaginaryHalf.pm SHA1 51f1eb47c5b3c941bc4501e524a3d0a50cbbfee7 lib/Math/PlanePath/KnightSpiral.pm SHA1 b1533919f5dc26b21bef679e684ad429f2fbbab7 lib/Math/PlanePath/KochCurve.pm SHA1 6a4325f1bf5bbcc6714392ca955e3d3ba7d0f57f lib/Math/PlanePath/KochPeaks.pm SHA1 3bc8a8aee53934c2bd7ec2d063a588bed1f006c6 lib/Math/PlanePath/KochSnowflakes.pm SHA1 eb0566a74b8f6503a16da3b362ae7a50f3c020e4 lib/Math/PlanePath/KochSquareflakes.pm SHA1 34e34da35fb74351b62b581c49bea858239fa3fb lib/Math/PlanePath/KochelCurve.pm SHA1 b62ee015af85b388bf20d5cff927095a8ce8a0ad lib/Math/PlanePath/LTiling.pm SHA1 8ec68b6a19e92a7cd6e03b09f9fa4f7bb7bedf7d lib/Math/PlanePath/MPeaks.pm SHA1 02b694d67445f287c009c6764eecdd48b60eae6e lib/Math/PlanePath/MultipleRings.pm SHA1 9d60c27fe05e0ce9185deda49dd612e7dea00626 lib/Math/PlanePath/OctagramSpiral.pm SHA1 1532b2e4d6908ee6acb8b7dc632f1e5247ca2a61 lib/Math/PlanePath/PeanoCurve.pm SHA1 908e410e5ade1ee363bf03c2bb833cc72a5f35b7 lib/Math/PlanePath/PentSpiral.pm SHA1 d21af1d9c78af5f27e573b86990d81a03187b5a0 lib/Math/PlanePath/PentSpiralSkewed.pm SHA1 fa86a43bd46d7563b6049ee88e82a0aee256d837 lib/Math/PlanePath/PixelRings.pm SHA1 0941c2d4c2de761f41e0f13783f5fd6b995cbe86 lib/Math/PlanePath/PowerArray.pm SHA1 6453b17f3356efc01ed3ad6e3e93513ddb1890c0 lib/Math/PlanePath/PyramidRows.pm SHA1 292b4010c0262372f26d1c072c0b93b6a53221aa lib/Math/PlanePath/PyramidSides.pm SHA1 4747daed987cde7f885c97e0aba5ea8e47f5c23e lib/Math/PlanePath/PyramidSpiral.pm SHA1 66ea22f8fdb2bff7bacd483af32fa36275e50b55 lib/Math/PlanePath/PythagoreanTree.pm SHA1 f62ee9dd0db2518e4c656061bccc2e32d49f1fd4 lib/Math/PlanePath/QuadricCurve.pm SHA1 22060afa9fafbcf68ed1deebba17886379ca5557 lib/Math/PlanePath/QuadricIslands.pm SHA1 4c55a9647f2d754e2b4bb2d0530f4ad4b4dee6f5 lib/Math/PlanePath/QuintetCentres.pm SHA1 af3f32d96a4fe6c4ccac3dee2a0876c51735da26 lib/Math/PlanePath/QuintetCurve.pm SHA1 e3efa93a5a792c0c645be0fa8cfc5b8804db71ea lib/Math/PlanePath/QuintetReplicate.pm SHA1 f1671f1c5327c9b9ecc597848f24b51a1d9b8777 lib/Math/PlanePath/R5DragonCurve.pm SHA1 781f68f09a08f795a948c8bc6de1c7d49f227cbb lib/Math/PlanePath/R5DragonMidpoint.pm SHA1 029e12a581f7ee694fc4fd4f37e2e7d5851b2d13 lib/Math/PlanePath/RationalsTree.pm SHA1 1cf735de91747bc3b4a278099f17c0cd149049d3 lib/Math/PlanePath/Rows.pm SHA1 1f1710c74ef05557e894a72f3bfb4057eb548e37 lib/Math/PlanePath/SacksSpiral.pm SHA1 04b8f520be23bd9f155cc622b89976a2ad83ec7f lib/Math/PlanePath/SierpinskiArrowhead.pm SHA1 c4bec5e47ad61379e070bf76a491d3fa8960d335 lib/Math/PlanePath/SierpinskiArrowheadCentres.pm SHA1 360720eb0deef5047579e13a43f3a2fb2c2479c4 lib/Math/PlanePath/SierpinskiCurve.pm SHA1 2f7ce1a3b506fafed0d09584c114e0537ac823f4 lib/Math/PlanePath/SierpinskiCurveStair.pm SHA1 e669a8886affa885501eeec31c7db48ade451f6b lib/Math/PlanePath/SierpinskiTriangle.pm SHA1 6c827575b977185ff4f1699e2011fed5b4bbde1d lib/Math/PlanePath/SquareArms.pm SHA1 3dec1412ccfcb8b4931264e834cbaad8d8b19cd7 lib/Math/PlanePath/SquareReplicate.pm SHA1 7980b4d2cfb99657200d1e815ed7fb843bc43e15 lib/Math/PlanePath/SquareSpiral.pm SHA1 510554bfc7d787476ce7befa5002df2d15492945 lib/Math/PlanePath/Staircase.pm SHA1 bdb7158f5cfc0aba7e3edff92b72a361102a0aaa lib/Math/PlanePath/StaircaseAlternating.pm SHA1 fa279bddfda5906dbb9c3f69a52d3d5651ff1803 lib/Math/PlanePath/TerdragonCurve.pm SHA1 08d819a97d9491574c1f5887137764cdcca2ea91 lib/Math/PlanePath/TerdragonMidpoint.pm SHA1 d29960b1907465900e3a332249764abafe2265b0 lib/Math/PlanePath/TerdragonRounded.pm SHA1 cabe21c11fba71289ab35964cae216e3ab178eea lib/Math/PlanePath/TheodorusSpiral.pm SHA1 03c49f685aa9a9f5eebe65cfd5a107ec48427d56 lib/Math/PlanePath/TriangleSpiral.pm SHA1 cd585b1cecefa1cbabe6146c31b513657764cbfe lib/Math/PlanePath/TriangleSpiralSkewed.pm SHA1 05242b27d5d850a2187371b60fa9191386eca186 lib/Math/PlanePath/TriangularHypot.pm SHA1 60229ff5f2c328ab16759de88625ad2e5cf48321 lib/Math/PlanePath/UlamWarburton.pm SHA1 ebda57157b64b7414b49e57898813a44ebf5ea6f lib/Math/PlanePath/UlamWarburtonQuarter.pm SHA1 cca576343efe2d0a96fb87608312d06aa280bb67 lib/Math/PlanePath/VogelFloret.pm SHA1 ed0c44160114f8f355aa401f757d65fda3005bae lib/Math/PlanePath/WunderlichMeander.pm SHA1 539571a31f8c16890b247b9a37053ad9df9fb256 lib/Math/PlanePath/WunderlichSerpentine.pm SHA1 c469d3e4d20ba9e0bdf08269a5f6dd26f489a888 lib/Math/PlanePath/WythoffArray.pm SHA1 fb18d811e6113c75a29e25ba6d422326e73c96fd lib/Math/PlanePath/WythoffPreliminaryTriangle.pm SHA1 6bae084f7e8e8d389f6313f91099ff9461f14c28 lib/Math/PlanePath/ZOrderCurve.pm SHA1 162927f8f1d444cc8a61f268ece506125b72cbff t/AlternatePaper.t SHA1 359cd13d1619e0b8553109da01879a708d2e4a9b t/AlternatePaperMidpoint.t SHA1 317778fc960d62ad65ce00d6044e61537a4f230f t/AnvilSpiral.t SHA1 d277ab67dcea4ab4c472f29d9011cfa26f232a51 t/ArchimedeanChords.t SHA1 c2b159336066ec57a93f3fb8ec0ff1e6ce2acf97 t/AztecDiamondRings.t SHA1 0d88a905075d5aeb466943435927e4342e3354af t/Base-Digits.t SHA1 ded1246d76b43a5f7475d791e66e1acf8bcb15b5 t/Base-Generic.t SHA1 2872e0a89333be10f5b326722e8ab651fda0a3c0 t/BetaOmeta.t SHA1 8878b78edffec09dc21424d4692cefa9455cfad4 t/CCurve.t SHA1 bcdb47149859d4b9cbdc41bfcbac4ec3e531a7da t/CellularRule.t SHA1 c2c18788699f491aaa4e5b794ddec954b5d105f4 t/CellularRule190.t SHA1 2f3dff029ef03c5944e8895cac3ab5309cc45c92 t/CfracDigits.t SHA1 1c619c82686758349ecfc8c308b040e1e2b3d547 t/ChanTree.t SHA1 85d3bd4690844ac864b7550d929532e51a9dec53 t/CincoCurve.t SHA1 6c9497544bbc86a9daeb3b10941c9f0bc1856311 t/Columns-load.t SHA1 7c8b0d937c139627eff087e83fddcb4c575fa9e3 t/Columns.t SHA1 d8c4209ceac85d921f38096c86df0b6bed8436c5 t/ComplexMinus.t SHA1 a46f842c2a0132db5d55e76f5ada9b51cdc730dd t/ComplexPlus.t SHA1 cb6dda181526d0767464c51e98ea81ab71e8d510 t/CoprimeColumns.t SHA1 7a319fbe2499b2b672920ac4f213e760173d4d88 t/Corner.t SHA1 71db5c304fad515265b949236e3881916dbcccd2 t/CornerReplicate.t SHA1 b7eb05cbec44163c584882726d2f136b3fff38d3 t/CubicBase.t SHA1 16ae8dd151165453cc77e70d2b5826f542ad30d4 t/DekkingCurve.t SHA1 03ed360333e525b30bc3d764722445a8372bb173 t/DiagonalRationals.t SHA1 9c30d0b54bd069771d63af6204bc892392df622f t/Diagonals.t SHA1 7036425197027180c67e4f7c21d933c7cdfe1cba t/DiamondArms.t SHA1 b42d02d0c8383aa05fc7707617ffdc15440e7de8 t/DiamondSpiral.t SHA1 592d7f9d2a56902f79edca4138015b226e336bb6 t/DigitGroups.t SHA1 ed23bd02a966e48ccacccc3f8a7b22588feb7235 t/DivisibleColumns.t SHA1 0fde2229dacfeab7ff3a9adc4029146b40217a1c t/DragonCurve.t SHA1 1ba1ef2a59f8bb4d14dc552f7d98d2bb083254e3 t/DragonMidpoint.t SHA1 ae778e4901ec2274c28315727bfb5d53c265b2db t/DragonRounded.t SHA1 73cc021ea8a17560523adf660ea6e11d61f6f6d5 t/FactorRationals.t SHA1 fa5ca72e0471d90f3dfdc9cf086a93b508476de1 t/FibonacciWordFractal.t SHA1 38d1bd3d86a9c126e64bda12350ffb56f63a65d6 t/File.t SHA1 a5f19a56a8d5c46224ef89c65a4fbe483deed30a t/FilledRings.t SHA1 4091f8afcb0d2f46cc8c99e9ee1555aae5ddfafe t/Flowsnake.t SHA1 e246196ca3bf9e02e4f8f90648df9bb407405173 t/FlowsnakeCentres.t SHA1 d37054fc1a1fbbd64e1145888534a77f0ffa1edf t/GcdRationals.t SHA1 6511d286e15bdc4a745e03abfc1753c2bd18d9f8 t/GosperIslands.t SHA1 30164fcf126f517c0bc46268b7f5c7f7d6adb976 t/GosperSide.t SHA1 c0bf402ef0e3a66b8d9b23fb61e6f3e10e7f4e9f t/GrayCode.t SHA1 7b21a4c066d838d6fb3c367bbd8ed172b2988b83 t/GreekKeySpiral.t SHA1 b0b93fa7adc16824b9ac5cb771d150dc3949b3e9 t/HIndexing.t SHA1 084df4e44aa86e6a803995cbfafaa3f8e8c40cbf t/HexArms.t SHA1 8589e2dc1cde191710da58e6143891885e754556 t/HexSpiral-load.t SHA1 676229f7f8fd6ced29c2b43cab1679c4ee65cf03 t/HexSpiral.t SHA1 253cd7e616a2cf3ecb018d40eb4cf42fe932964f t/HexSpiralSkewed-unskew.t SHA1 9988f5d548fb49d75e534cb3b249329109e68802 t/HexSpiralSkewed.t SHA1 31ef4e1ed82aab4aa97ab7db7e265e648b03a06d t/HilbertCurve-load.t SHA1 deb2013b6fda4605775c2f902e6ee4c68d4f0555 t/HilbertCurve.t SHA1 cfb0d5b8ba22a68e49c28034582d735e924066ac t/HilbertSpiral.t SHA1 d6a6f34e89d4dceb33c540f34c4b1b06df751e42 t/Hypot.t SHA1 4d1a03014be4cd81096928c911a3cd71e1062d96 t/HypotOctant.t SHA1 6a65d2b2d310f7e1130656898e2802d3cb289435 t/ImaginaryBase.t SHA1 0101a98519f4b8ae2553a242980b6e058464e92c t/ImaginaryHalf.t SHA1 23aa77aabc1a786f6ee04139def067154bc6f397 t/KnightSpiral.t SHA1 84bcb11ecc49ec8d3838b106d90c73f6896f0444 t/KochCurve.t SHA1 ac6fed67107ac3cee480badf7ec4f789047a39fa t/KochPeaks.t SHA1 515de6dfffa695d5cf9729d91028909263253f08 t/KochSnowflakes.t SHA1 d9339e7e629a4bde2c5a4dabf661d19bdcd67f06 t/KochSquareflakes.t SHA1 9bb1cf1fcb2943a3ab90a07677cd98bcd02ac459 t/KochelCurve.t SHA1 e5aeaab2a0ad287fb93ebe462e1f7621f505dad4 t/LTiling.t SHA1 ea94f46a058f8fffac3bca0675e781c56822da73 t/MultipleRings.t SHA1 47ebbf1d71ebdc462f51055161de9cd0838f45dc t/MyTestHelpers.pm SHA1 3983128a33a4e3d6699049ebf3a6631a2622a1bc t/NumSeq-PlanePathCoord.t SHA1 c6839d4089248aabc6ebbc8f7e1fc1ff5d458e56 t/NumSeq-PlanePathDelta.t SHA1 5803c2810018618034a6bd93e4feb6122127cfe3 t/NumSeq-PlanePathN.t SHA1 225151da95ea409074adf539a968a65fdfdaa61e t/NumSeq-PlanePathTurn.t SHA1 f815aca97a74d40ed7e3b3e58755efe32b7b675e t/PeanoCurve-load.t SHA1 75e066fceb0857639397390955646586307889c7 t/PeanoCurve.t SHA1 c425f5d604466cf54607fe71b7b7d41ad68b6ca3 t/PixelRings.t SHA1 ef99841133e51ec9b070162469c8f1c59ca79dd0 t/PlanePath.t SHA1 5a7fab4d4ca152b11bcc429f55c6b7c321867370 t/PyramidRows.t SHA1 f3f57d743741e5fbd8c526b730dfa5a8b0165b4f t/PythagoreanTree.t SHA1 a0f55f4173d5eb846b7ca066232d8bfbfbdf227a t/QuadricCurve.t SHA1 c2d8017f5e960e055648e2a3b6d6cb8f006a0a24 t/QuadricIslands.t SHA1 72871cc7ea9eba65a9fd46fd78271090b6ecaf80 t/QuintetCentres.t SHA1 1109bbab1a7bc2a4cc8aea5cf196539b14bce4cd t/QuintetCurve.t SHA1 1fe69c5629c1ba47c9eccf97c51294e43dcd3db9 t/RationalsTree.t SHA1 4b6df766c4ea151b90526f781865e1753b12e7cb t/Rows-load.t SHA1 6a5def938d18d33629717f5d075d09e772d01198 t/Rows.t SHA1 0d7a551360e5feb810091292fb7620a95ef550eb t/SacksSpiral.t SHA1 58b6043d7a764f6aa7e1f903e5f5f59750db5272 t/SierpinskiArrowhead.t SHA1 11c12aa32afedf05d5ac62ed3b4649a6a9ba1cf9 t/SierpinskiArrowheadCentres.t SHA1 244851b05730bc6c43908d6a2a51180af75a9be1 t/SierpinskiCurve.t SHA1 3dcfdc911bd3ed964a611ae1930d7c32b5b9f559 t/SierpinskiCurveStair.t SHA1 8cd9225d1072b94afa79117dca9564ea4e08774c t/SierpinskiTriangle.t SHA1 9b3a8e650923c10eecd321738a4593c022b859d6 t/SquareSpiral.t SHA1 0e55633c870f0c23fea5fb4bab8087b73793aa18 t/Staircase.t SHA1 00848e3f690c6f27f1388a87220ddc4e944f1d63 t/StaircaseAlternating.t SHA1 c31a1a7f68544b671cae7a81dcec18c8efe0d934 t/TerdragonCurve.t SHA1 8ee3be57deaeef12653411e2e07767fb726118ec t/TerdragonMidpoint.t SHA1 a59f13a42bf606cbcb9fb8304ee29f911ec334e7 t/TheodorusSpiral.t SHA1 fa691e8bc90e32336df5912cfcf9e1e4456c18c9 t/TriangularHypot.t SHA1 01e3011b707ea716976b17cfbdf5fa267bbfe676 t/UlamWarburton.t SHA1 5f2a6973ef3e227926e3b5e0bd88a63d3a0240fb t/UlamWarburtonQuarter.t SHA1 4b6acf7ef24748496c4ade0ab5df47eba357caad t/VogelFloret.t SHA1 4b54eedbae0b6f33d55dc61c08a0e4784f300474 t/WythoffArray.t SHA1 4f9df96e15ee598ee068127457ae74cd867e6167 t/ZOrderCurve.t SHA1 ed43ba94de07078526593c73c26016a453d93739 t/bigfloat.t SHA1 20fe33be7ed9a1e4096298ea6de35afc87042c3b t/bigint-lite.t SHA1 999b6be6b9cc26670d8bfcd07f872bca0be12e43 t/bigint.t SHA1 ef44feead2f7bd2787d2745d1227d59364c43506 t/bigint_common.pm SHA1 81d2e0f68a3e560a56fb177f16c3be791cad726c t/number-fraction.t SHA1 26c440ac63362a95f75b36e764df91474d0a4cff tools/alternate-paper-dxdy.pl SHA1 68474197b7cf155f8f9fbcaec2f3d3bd24745cd5 tools/ar2w2-curve-table.pl SHA1 46d993d37dde7b8589aa2171d848bc17abd22020 tools/beta-omega-table.pl SHA1 310410fe349b3a406e4fbb2eead64b6e88c900d5 tools/cellular-rule-limits.pl SHA1 2db846a6883e7a53818d0a5bcb51eecb44860af1 tools/cinco-curve-table.pl SHA1 d0b65a40e9262040786e72c5457c549f4d532b5f tools/corner-replicate-table.pl SHA1 48afdc0c514947cd3da2ef0b58af780957cd40e7 tools/dekking-centres-table.pl SHA1 01f50e3e1494bfe42987d62bb23773f000089a29 tools/dekking-curve-table.pl SHA1 4abefdc3e2018f76a486ab3bdd1f742dcaf10e8f tools/dragon-curve-dxdy.pl SHA1 01a78e5351bfb3e0492002f327511af412058938 tools/dragon-curve-table.pl SHA1 8a050e00ccb2147ab251b5c6ca9bb5758e47266c tools/flowsnake-centres-table.pl SHA1 eed4fef5fcaf3cee04a1a039049e83e79a251855 tools/flowsnake-table.pl SHA1 fb052e3f04dbe32173cd42af01207bc00156e567 tools/gallery.pl SHA1 848f66feb046b5d675b1c801c40ab2d39d3145a2 tools/hilbert-curve-table.pl SHA1 7a3c801ae7780c6692ca9c957edaadbfeadad487 tools/hilbert-spiral-table.pl SHA1 44ac8621c473ab50197f0dc7950f093c5e613291 tools/kochel-curve-table.pl SHA1 4d685d4a8048e3b1d5d88ca47abd06d06fb34f7e tools/moore-spiral-table.pl SHA1 7ed118b655199437723dd756ce10ce321d0db865 tools/pythagorean-tree.pl SHA1 8da5705623c4e03ef8ef14eee80cc28e37cba3f5 tools/r5dragon-midpoint-offset.pl SHA1 da0c0aab3e90387e44665a73049a6cdf77677f0f tools/terdragon-midpoint-offset.pl SHA1 12241a4830fb4af2a70d7c5e5b99e40a90bb80e2 tools/wunderlich-meander-table.pl SHA1 e58d96fdc6a736494e0cc2ae34dedafa7ab1f546 tools/wythoff-array-zeck.pl SHA1 f2ceb7bb4bf68bd24728b90918e63d251fd06183 xt/0-META-read.t SHA1 c9e7a47e1f397eb5f46d7e4ef21754d9df76e36e xt/0-Test-ConsistentVersion.t SHA1 9496e4a2a2734c7093ab6961900dd7a55800976e xt/0-Test-Pod.t SHA1 083242d959c1a9242c874982e4585872a95bec93 xt/0-Test-Synopsis.t SHA1 63abbc3297914cd38081e95588fd794f9d1f0306 xt/0-Test-YAML-Meta.t SHA1 1cafa4e23b7a691e09161c0927e6920dd663e2ce xt/0-examples-xrefs.t SHA1 4a897c9b09f37290f507d151e84b6a3c8defd496 xt/0-file-is-part-of.t SHA1 aeb6f41dfae96d04459448f6c3e3dee44691e722 xt/0-no-debug-left-on.t SHA1 566e7a6b2dac7909f843a05f1cd4a1e742d619c2 xt/ChanTree-slow.t SHA1 0c702e9a54e70acd0775efc9f091c3ba7093615b xt/DragonCurve-more.t SHA1 f8365d8c9577ceadf89fe43f119d2668f1c0579b xt/GrayCode-oseq.t SHA1 bf3c949d7ce7aed8ed44ac371d8d1e8d834712cb xt/HIndexing-more.t SHA1 80a9ee6d5e05cb86c001c8de6b5036e302b3bf39 xt/KochCurve-more.t SHA1 4bedc5dbaab1778c38a7c8a60695c71401064b80 xt/MyOEIS.pm SHA1 aeb27567b48a798edba5156f6c645882063b84ea xt/PixelRings-image.t SHA1 82b99a18790a0d739b163bdd8a0e86502553b540 xt/PlanePath-subclasses.t SHA1 0b2f21ee02531468c144c220495c9fe7a0cace05 xt/bigrat.t SHA1 140882b58baf2867b366c1b52f18ac25576dbd02 xt/oeis-duplicate.t SHA1 682c76ff58bc9b8164b939aa13ab0cf8c9cda3f4 xt/oeis-xrefs.t SHA1 b43bbeb11faa43ac880a0148c9bd508e93cb536e xt/oeis/AlternatePaper-oeis.t SHA1 0aee5a6164dfece553934542c6acc5850e7f76cc xt/oeis/AlternatePaperMidpoint-oeis.t SHA1 20d352bb7dc90e7bfda8cf98884bc6236f75e051 xt/oeis/AnvilSpiral-oeis.t SHA1 a71dcb4bb088c6c3925bd6a6e2888ee89610498f xt/oeis/CCurve-oeis.t SHA1 50cef0ef15101c26bd8c33a9bebd50e99acd92a3 xt/oeis/CellularRule-oeis.t SHA1 847920716a8a5073236552f3a400d27da70b8e5c xt/oeis/CellularRule190-oeis.t SHA1 7206c7b7988523284d419d4b5fe857ea5cb8ce80 xt/oeis/CellularRule54-oeis.t SHA1 7402261978963c96614c72ab81a4518e8a86f5e2 xt/oeis/CfracDigits-oeis.t SHA1 06226ee6f4687412b2c52683747df33d03afb57f xt/oeis/ComplexMinus-oeis.t SHA1 98a3bec8568dd3f7f557f721c4b0ca25dad3b416 xt/oeis/CoprimeColumns-oeis.t SHA1 cec88f46b02241db41921425d5240c8902faa279 xt/oeis/Corner-oeis.t SHA1 4ee9f5a47799b0e2c28735b19e88cef64ad376c5 xt/oeis/CornerReplicate-oeis.t SHA1 703ce60cad3dc46ca679cc62c29bfab6f9538737 xt/oeis/DiagonalRationals-oeis.t SHA1 1a3c6dce9e353da9b76b1cbcdcda154fb0e3b9d2 xt/oeis/Diagonals-oeis.t SHA1 3aa58a208d1c5c42fb4b415a6cadcd7a279b6b48 xt/oeis/DiagonalsAlternating-oeis.t SHA1 79474bcdb866f2cc8f631388f06ba83c6f497c4e xt/oeis/DiagonalsOctant-oeis.t SHA1 93cb279cd0741c04d128e5e8f8dd1937a252ccf0 xt/oeis/DiamondSpiral-oeis.t SHA1 4b40f04dff3a00b689de7a06853511a1afe557bf xt/oeis/DigitGroups-oeis.t SHA1 bc7406dd51ae938dfadb1fb9fea3174e6ebbeb1b xt/oeis/DivisibleColumns-oeis.t SHA1 336820921b4f1b1419ec27821142ade35befbe5a xt/oeis/DragonCurve-oeis.t SHA1 8a7d189909060d33f647b23a6db6661710824561 xt/oeis/DragonMidpoint-oeis.t SHA1 981d6eda8ca70e938690735cb9f91fdf0560a6eb xt/oeis/FactorRationals-oeis.t SHA1 6e606bf15fe4d100c0b473fc91f7df87ba2a0f52 xt/oeis/FibonacciWordFractal-oeis.t SHA1 f4a3ff1675dbf50d4b87d371c5c7af36e2feaff2 xt/oeis/FilledRings-oeis.t SHA1 869fa2ef9a71068d6a4a8e7ce0b192a40e227bf4 xt/oeis/Flowsnake-oeis.t SHA1 e4a8f62ca1a7fe413898e6b5bd539afce529db37 xt/oeis/FractionsTree-oeis.t SHA1 f2cc01e62e492604cfe29d6705c0dd61e508b268 xt/oeis/GcdRationals-oeis.t SHA1 e3cbe2861a9d5d189d561337d07314d36dc40065 xt/oeis/GosperSide-oeis.t SHA1 efe719f09edf31ae4abaa3f37dfbedc6ef4c59c7 xt/oeis/GrayCode-oeis.t SHA1 8cf629392fcbfd8fdc77461f004cdf39a9713ef1 xt/oeis/HIndexing-oeis.t SHA1 597ac080058862b3ebb10c83481ba04b7934b148 xt/oeis/HeptSpiralSkewed-oeis.t SHA1 2d326ca91199139d6d94958b5ed896f29b304241 xt/oeis/HexSpiral-oeis.t SHA1 1409fd4523f038b948e175ed7de33d6da900ee7d xt/oeis/HilbertCurve-oeis.t SHA1 bca9a98673755128b1bb0956ccff6bee147f8f5f xt/oeis/HilbertSides-oeis.t SHA1 b1a7fdc9b2ad7eb0183a5306a10d74331d0c82df xt/oeis/Hypot-oeis.t SHA1 04f8f953322bc81d6c06628af7bc47e882c700cc xt/oeis/HypotOctant-oeis.t SHA1 5dc6afc275f0aa964e3df4e09ebdd2dd0b771a45 xt/oeis/ImaginaryBase-oeis.t SHA1 8d3eebb3bab8618d8fb7d8e4649b2f777eaa77fb xt/oeis/KnightSpiral-oeis.t SHA1 ee0077feee4ac6205d417fbe81ba2db682878408 xt/oeis/KochCurve-oeis.t SHA1 5681139d5b48240e52a9d183316852e9c06d116c xt/oeis/KochSnowflakes-oeis.t SHA1 263261866af49da5587029f509dca412b59fb5e5 xt/oeis/LTiling-oeis.t SHA1 cad44d126ea0bebc303f3cfe2ba9585868cdae08 xt/oeis/MPeaks-oeis.t SHA1 70dafab13d58880e95733360dfd93193a7092641 xt/oeis/MultipleRings-oeis.t SHA1 48d2263a1ea1468f85cb8b9928201e459d43d6e7 xt/oeis/NumSeq-PlanePath-oeis.t SHA1 dc9a9b888f040917e5ae515d9d8932d8fc075209 xt/oeis/OctagramSpiral-oeis.t SHA1 14ee548948a8f5fe526ba43342351efcde335f14 xt/oeis/PeanoCurve-oeis.t SHA1 4ab6708d2047147ae86508dbd58dc8a87a09ce2d xt/oeis/PentSpiral-oeis.t SHA1 e63c0f06ef6f9ea4d047d6602435b2abb0970884 xt/oeis/PentSpiralSkewed-oeis.t SHA1 31ef6ba163d24bc4550be11351d0055aa51fcf17 xt/oeis/PowerArray-oeis.t SHA1 4c0e2727152991af52af3f04c093e4ee6a27ef55 xt/oeis/PyramidRows-oeis.t SHA1 4bd34df0f576445d6746b8263fc6f002f0c147e8 xt/oeis/PyramidSides-oeis.t SHA1 948cbd6764805f92016c4d6c82556e1e2f320a30 xt/oeis/PyramidSpiral-oeis.t SHA1 ff8d92c4aaf9c8b2b976067ca29140131d2d9dbe xt/oeis/PythagoreanTree-oeis.t SHA1 5c92b4732b687eb7d61ca644b0606610205ae048 xt/oeis/QuadricCurve-oeis.t SHA1 ff1ec6ea7cd2d7db1a6cdecb47503ec48e0fda45 xt/oeis/QuintetCentres-oeis.t SHA1 19a724fb3574ef3fbeaeef12866bd1f29cd8d834 xt/oeis/R5DragonCurve-oeis.t SHA1 e68a8958b7c6ccd2cd36fef7e9355de2c82dd705 xt/oeis/RationalsTree-oeis.t SHA1 7e9d5988a7f956dbb574761b9e73a37b9b35b68d xt/oeis/SierpinskiArrowhead-oeis.t SHA1 806715baf7409616a541e8144998de370fe58554 xt/oeis/SierpinskiCurve-oeis.t SHA1 2c118430764e8df346c0b11865af7dcb26424700 xt/oeis/SierpinskiTriangle-oeis.t SHA1 0ceeb0c668ff2aa6824d2dea5f155b81cd378a29 xt/oeis/SquareSpiral-oeis.t SHA1 46b73ead6bce139f7d7c480381845f77402a37d2 xt/oeis/Staircase-oeis.t SHA1 5b42343fe8149586650b122abc3c5161ea1d2409 xt/oeis/TerdragonCurve-oeis.t SHA1 20203a3529f4b3d9bde8dcedef98a851cedc9970 xt/oeis/TheodorusSpiral-oeis.t SHA1 1cfcf5b4dd51ac17323cecdbb39e988a3bf5cfe5 xt/oeis/TriangleSpiral-oeis.t SHA1 980bb4530261475ca4291c8d1c5dd7f21055ba8c xt/oeis/TriangleSpiralSkewed-oeis.t SHA1 efcf0405289ef43d71700380b94a40134d0f6791 xt/oeis/TriangularHypot-oeis.t SHA1 2b26a7b8aa58fdedcf2efa978d70815d43001c54 xt/oeis/UlamWarburton-oeis.t SHA1 28d73683fcf6ee0d368bd66968163d2a3a550d8a xt/oeis/UlamWarburtonQuarter-oeis.t SHA1 218e4afa991be1e9c2a4f83429997495ab55ab25 xt/oeis/WythoffArray-oeis.t SHA1 48b294e6387569a642dcd035cefaac6652c09e96 xt/oeis/WythoffPreliminaryTriangle-oeis.t SHA1 824917de67feb714721319dd4014eac6ead48c82 xt/oeis/ZOrderCurve-oeis.t SHA1 f213adabf3727c2982bb1474d84975af60489696 xt/pod-lists.t SHA1 64ecd4dc8a86887874843447847b812753fa6b82 xt/slow/AlternatePaper-slow.t SHA1 6f98af7a2ff92bca83c30844ef148f7243633710 xt/slow/CCurve-slow.t SHA1 94dfe6b4dc212c78d5f84914fc6363ffcb1f8c26 xt/slow/CellularRule-slow.t SHA1 a26cb65da4d6f2f6a2cc960fab3c8e0f76572306 xt/slow/ComplexMinus-slow.t SHA1 87d2df55bfd5bb0c608c10c0239c2570f8767ff2 xt/slow/DragonCurve-slow.t SHA1 dd2f96d02517fee4b327648d3f14df98a8dc8807 xt/slow/GcdRationals-slow.t SHA1 1442b486c3adec226d6a91b1a5a9064ee1d80bbb xt/slow/HilbertCurve-slow.t SHA1 cb1b9a30583ef4c51bbb7da1b755081325a68e25 xt/slow/NumSeq-PlanePathCoord.t SHA1 9a38c31cccf1072cfa6ebbed8eeaac3b092812e2 xt/slow/R5DragonCurve-slow.t SHA1 6a81c67366f7a683911be37829c3abe5cc431215 xt/slow/TerdragonCurve-slow.t SHA1 3e97525dfd7814d9d6e977b72a957cce0bb6f192 xtools/gp-inline SHA1 51069b694ce78fde7d42ed539322fae579948d40 xtools/my-check-copyright-years.sh SHA1 b12199bcc70c569ff43f8d8e2ea1c896f587c62f xtools/my-check-spelling.sh SHA1 d76762639d4ac9a709b1de9d0ad61974aa5026b2 xtools/my-deb.sh SHA1 2db0a23176de55a8ee8f1643f239aeaa2bcf4fa1 xtools/my-diff-prev.sh SHA1 6c48137d49c3619eca39796d49f26332eea55d4a xtools/my-kwalitee.sh SHA1 e4a15f9b4bcf1d087de2d9ed8e87bf92b0469a6c xtools/my-manifest.sh SHA1 830acfc76ae9239a2c36143c437a3ec7c6736a0a xtools/my-pc.sh SHA1 7b1e948c88915689b3782d872ef9b92a2912c593 xtools/my-tags.sh SHA1 5c86dd9ca3d2ecf3ee9d90da1537b71e56c8b321 xtools/my-wunused.sh -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iD8DBQFWh0pzLFMCIV9q3ToRAnWQAJ0b6aelpbk6dKEALVGA7YJzslGa0QCgrjVd kgeqrl2kk/IVjltL602yhZs= =i796 -----END PGP SIGNATURE----- Math-PlanePath-122/MANIFEST0000644000175000017500000003466112641645163013052 0ustar ggggChanges COPYING debian/changelog debian/compat debian/control debian/copyright debian/patches/pod-examples-dir.diff debian/rules debian/source/format debian/watch devel/alternate-paper-midpoint.pl devel/alternate-paper.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-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-side.pl devel/gray.pl devel/greek-key.pl devel/grep-values.pl devel/grid.pl devel/hex-arms.pl devel/hexhypot.pl devel/hilbert-diamond.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/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/Makefile devel/mephisto-waltz.logo devel/misc.pl devel/multiple-rings.pl devel/number-fraction.pl devel/numseq.pl devel/peano.pl 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-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/staircase-alternating.pl devel/t-square.pl devel/terdragon.pl devel/theodorus.gnuplot devel/theodorus.pl devel/tree.pl devel/triangular-spiral.pl devel/ulam-warburton.pl devel/vertical.pl devel/vogel.pl devel/wythoff-array.pl 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/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/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/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/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/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/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/PixelRings.t t/PlanePath.t t/PyramidRows.t t/PythagoreanTree.t t/QuadricCurve.t t/QuadricIslands.t t/QuintetCentres.t t/QuintetCurve.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/SquareSpiral.t t/Staircase.t t/StaircaseAlternating.t t/TerdragonCurve.t t/TerdragonMidpoint.t t/TheodorusSpiral.t t/TriangularHypot.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/moore-spiral-table.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/bigrat.t xt/ChanTree-slow.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/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/CoprimeColumns-oeis.t xt/oeis/Corner-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/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/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/WythoffArray-oeis.t xt/oeis/WythoffPreliminaryTriangle-oeis.t xt/oeis/ZOrderCurve-oeis.t xt/PixelRings-image.t xt/PlanePath-subclasses.t xt/pod-lists.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 xtools/gp-inline 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-122/MANIFEST.SKIP0000644000175000017500000000620012612607144013576 0ustar gggg#!/usr/bin/perl # MANIFEST.SKIP -- Kevin's various excluded files # Copyright 2008, 2009, 2010, 2011, 2012, 2013, 2015 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$ # 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-122/debian/0002755000175000017500000000000012641645163013133 5ustar ggggMath-PlanePath-122/debian/control0000644000175000017500000000403612421373464014535 0ustar gggg# 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 . # 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: 3.9.6 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-122/debian/watch0000644000175000017500000000171411413033465014155 0ustar gggg# Web site version watch, for devscripts uscan program. # 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 . # Crib notes: # "man uscan" describes the format. # test with: uscan --report --verbose --watchfile=debian/watch version=3 http://user42.tuxfamily.org/math-planepath/index.html \ .*/Math-PlanePath-([0-9]+)\.tar\.gz Math-PlanePath-122/debian/source/0002755000175000017500000000000012641645163014433 5ustar ggggMath-PlanePath-122/debian/source/format0000644000175000017500000000000411413032707015624 0ustar gggg1.0 Math-PlanePath-122/debian/compat0000644000175000017500000000000111413032707014314 0ustar gggg5Math-PlanePath-122/debian/copyright0000644000175000017500000000056212641634406015065 0ustar ggggMath-PlanePath is Copyright 2010, 2011, 2012, 2013, 2014, 2015, 2016 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-122/debian/patches/0002755000175000017500000000000012641645162014561 5ustar ggggMath-PlanePath-122/debian/patches/pod-examples-dir.diff0000644000175000017500000000426211524616702020564 0ustar ggggdiff -Nur -x '*.orig' -x '*~' path//lib/Math/PlanePath/KnightSpiral.pm path.new//lib/Math/PlanePath/KnightSpiral.pm --- path//lib/Math/PlanePath/KnightSpiral.pm 2011-02-02 08:25:37.000000000 +1100 +++ path.new//lib/Math/PlanePath/KnightSpiral.pm 2011-02-10 10:04:03.000000000 +1100 @@ -462,7 +462,7 @@ A068614 - spiral opposite direction (Y negate) A068615 - rotate 90 degrees, spiral opp dir (X,Y transpose) -See F generating A068608. +See F generating A068608. =head1 SEE ALSO diff -Nur -x '*.orig' -x '*~' path//lib/Math/PlanePath/SquareSpiral.pm path.new//lib/Math/PlanePath/SquareSpiral.pm --- path//lib/Math/PlanePath/SquareSpiral.pm 2011-02-10 10:03:21.000000000 +1100 +++ path.new//lib/Math/PlanePath/SquareSpiral.pm 2011-02-10 10:05:05.000000000 +1100 @@ -294,7 +294,8 @@ This path is well known from Stanislaw Ulam finding interesting straight lines when plotting the prime numbers on it. See -F in the sources for a program generating that, +F +for a program generating that, or see L using this SquareSpiral to draw Ulam's pattern and more. diff -Nur -x '*.orig' -x '*~' path//lib/Math/PlanePath.pm path.new//lib/Math/PlanePath.pm --- path//lib/Math/PlanePath.pm 2011-02-02 08:25:39.000000000 +1100 +++ path.new//lib/Math/PlanePath.pm 2011-02-10 10:04:03.000000000 +1100 @@ -84,7 +84,7 @@ PyramidSides along the sides of a 45-degree pyramid The paths are object oriented to allow parameters, though only a few -subclasses have any parameters. See C for a cute way +subclasses have any parameters. See F for a cute way to print samples of all the paths. The classes are generally based on integer C<$n> positions and those @@ -269,7 +269,7 @@ L, displaying various sequences on these paths. -F in the sources to print all the paths. +F to print all the paths. =head1 HOME PAGE Math-PlanePath-122/debian/changelog0000644000175000017500000000023312641634404014775 0ustar gggglibmath-planepath-perl (122-0.1) unstable; urgency=low * Packaged version. -- Kevin Ryde Sat, 02 Jan 2016 13:41:54 +1100 Math-PlanePath-122/debian/rules0000755000175000017500000000155312374741314014213 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/*