├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE.md ├── Notices.txt ├── README.md ├── pyproject.toml ├── rls.toml ├── src ├── algebraic_numbers.rs ├── array2d.rs ├── generate_gcd_test_cases.mac ├── interval_arithmetic.rs ├── lattice.rs ├── lib.rs ├── mod_int.rs ├── polynomial.rs ├── polynomial │ ├── add_sub.rs │ ├── distinct_degree_factorization.rs │ ├── div_rem.rs │ ├── factorization_over_integers.rs │ ├── gcd.rs │ ├── mul.rs │ └── same_degree_factorization.rs ├── prelude.rs ├── python.rs ├── quadratic_numbers.rs ├── traits.rs └── util.rs └── tests ├── __init__.py └── test_real_algebraic_number.py /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | __pycache__ 5 | *.pyc 6 | /.vscode -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ### Changed 11 | 12 | * add changes here 13 | 14 | ## [0.3.0] 15 | 16 | ### Changed 17 | 18 | * Updated dependency versions 19 | 20 | ## [0.2.0] 21 | 22 | ### Changed 23 | 24 | * Update PyO3 from v0.8.2 to v0.9.0 25 | 26 | ## [0.1.2] 27 | 28 | ### Added 29 | 30 | * Add Python bindings using PyO3 v0.8.2 31 | 32 | ## [0.1.1] 33 | 34 | ### Changed 35 | 36 | * Split common functionality out into internal function `remove_zero_from_interval` 37 | 38 | ### Added 39 | 40 | * Added functions for calculating the integer part of the base-2 log of a `RealAlgebraicNumber` 41 | 42 | ## 0.1.0 43 | 44 | ### Added 45 | 46 | * Initial release 47 | 48 | [Unreleased]: https://salsa.debian.org/Kazan-team/algebraics/-/compare/v0.2.0...master 49 | [0.2.0]: https://salsa.debian.org/Kazan-team/algebraics/-/compare/v0.1.2...v0.2.0 50 | [0.1.2]: https://salsa.debian.org/Kazan-team/algebraics/-/compare/v0.1.1...v0.1.2 51 | [0.1.1]: https://salsa.debian.org/Kazan-team/algebraics/-/compare/v0.1.0...v0.1.1 52 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-or-later 2 | # See Notices.txt for copyright information 3 | [package] 4 | name = "algebraics" 5 | version = "0.3.0" 6 | authors = ["Jacob Lifshay "] 7 | edition = "2018" 8 | license = "LGPL-2.1-or-later" 9 | description = "algebraic numbers library" 10 | keywords = ["algebraic-numbers", "arbitrary-precision", "polynomials", "real-numbers", "exact-arithmetic"] 11 | repository = "https://salsa.debian.org/Kazan-team/algebraics" 12 | readme = "README.md" 13 | categories = ["algorithms", "data-structures", "science"] 14 | 15 | [features] 16 | default = [] 17 | python = ["pyo3"] 18 | python-extension = ["python", "pyo3/extension-module"] 19 | 20 | [lib] 21 | name = "algebraics" 22 | crate-type = ["rlib", "cdylib"] 23 | 24 | [dependencies] 25 | num-traits = "0.2.14" 26 | num-bigint = "0.4.3" 27 | num-integer = "0.1.44" 28 | num-rational = "0.4.0" 29 | rand = "0.8.5" 30 | rand_pcg = "0.3.1" 31 | lazy_static = "1.4" 32 | 33 | [dependencies.pyo3] 34 | version = "0.16" 35 | optional = true 36 | features = ["num-bigint"] 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ### GNU LESSER GENERAL PUBLIC LICENSE 2 | 3 | Version 2.1, February 1999 4 | 5 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 6 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 7 | 8 | Everyone is permitted to copy and distribute verbatim copies 9 | of this license document, but changing it is not allowed. 10 | 11 | [This is the first released version of the Lesser GPL. It also counts 12 | as the successor of the GNU Library Public License, version 2, hence 13 | the version number 2.1.] 14 | 15 | ### Preamble 16 | 17 | The licenses for most software are designed to take away your freedom 18 | to share and change it. By contrast, the GNU General Public Licenses 19 | are intended to guarantee your freedom to share and change free 20 | software--to make sure the software is free for all its users. 21 | 22 | This license, the Lesser General Public License, applies to some 23 | specially designated software packages--typically libraries--of the 24 | Free Software Foundation and other authors who decide to use it. You 25 | can use it too, but we suggest you first think carefully about whether 26 | this license or the ordinary General Public License is the better 27 | strategy to use in any particular case, based on the explanations 28 | below. 29 | 30 | When we speak of free software, we are referring to freedom of use, 31 | not price. Our General Public Licenses are designed to make sure that 32 | you have the freedom to distribute copies of free software (and charge 33 | for this service if you wish); that you receive source code or can get 34 | it if you want it; that you can change the software and use pieces of 35 | it in new free programs; and that you are informed that you can do 36 | these things. 37 | 38 | To protect your rights, we need to make restrictions that forbid 39 | distributors to deny you these rights or to ask you to surrender these 40 | rights. These restrictions translate to certain responsibilities for 41 | you if you distribute copies of the library or if you modify it. 42 | 43 | For example, if you distribute copies of the library, whether gratis 44 | or for a fee, you must give the recipients all the rights that we gave 45 | you. You must make sure that they, too, receive or can get the source 46 | code. If you link other code with the library, you must provide 47 | complete object files to the recipients, so that they can relink them 48 | with the library after making changes to the library and recompiling 49 | it. And you must show them these terms so they know their rights. 50 | 51 | We protect your rights with a two-step method: (1) we copyright the 52 | library, and (2) we offer you this license, which gives you legal 53 | permission to copy, distribute and/or modify the library. 54 | 55 | To protect each distributor, we want to make it very clear that there 56 | is no warranty for the free library. Also, if the library is modified 57 | by someone else and passed on, the recipients should know that what 58 | they have is not the original version, so that the original author's 59 | reputation will not be affected by problems that might be introduced 60 | by others. 61 | 62 | Finally, software patents pose a constant threat to the existence of 63 | any free program. We wish to make sure that a company cannot 64 | effectively restrict the users of a free program by obtaining a 65 | restrictive license from a patent holder. Therefore, we insist that 66 | any patent license obtained for a version of the library must be 67 | consistent with the full freedom of use specified in this license. 68 | 69 | Most GNU software, including some libraries, is covered by the 70 | ordinary GNU General Public License. This license, the GNU Lesser 71 | General Public License, applies to certain designated libraries, and 72 | is quite different from the ordinary General Public License. We use 73 | this license for certain libraries in order to permit linking those 74 | libraries into non-free programs. 75 | 76 | When a program is linked with a library, whether statically or using a 77 | shared library, the combination of the two is legally speaking a 78 | combined work, a derivative of the original library. The ordinary 79 | General Public License therefore permits such linking only if the 80 | entire combination fits its criteria of freedom. The Lesser General 81 | Public License permits more lax criteria for linking other code with 82 | the library. 83 | 84 | We call this license the "Lesser" General Public License because it 85 | does Less to protect the user's freedom than the ordinary General 86 | Public License. It also provides other free software developers Less 87 | of an advantage over competing non-free programs. These disadvantages 88 | are the reason we use the ordinary General Public License for many 89 | libraries. However, the Lesser license provides advantages in certain 90 | special circumstances. 91 | 92 | For example, on rare occasions, there may be a special need to 93 | encourage the widest possible use of a certain library, so that it 94 | becomes a de-facto standard. To achieve this, non-free programs must 95 | be allowed to use the library. A more frequent case is that a free 96 | library does the same job as widely used non-free libraries. In this 97 | case, there is little to gain by limiting the free library to free 98 | software only, so we use the Lesser General Public License. 99 | 100 | In other cases, permission to use a particular library in non-free 101 | programs enables a greater number of people to use a large body of 102 | free software. For example, permission to use the GNU C Library in 103 | non-free programs enables many more people to use the whole GNU 104 | operating system, as well as its variant, the GNU/Linux operating 105 | system. 106 | 107 | Although the Lesser General Public License is Less protective of the 108 | users' freedom, it does ensure that the user of a program that is 109 | linked with the Library has the freedom and the wherewithal to run 110 | that program using a modified version of the Library. 111 | 112 | The precise terms and conditions for copying, distribution and 113 | modification follow. Pay close attention to the difference between a 114 | "work based on the library" and a "work that uses the library". The 115 | former contains code derived from the library, whereas the latter must 116 | be combined with the library in order to run. 117 | 118 | ### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 119 | 120 | **0.** This License Agreement applies to any software library or other 121 | program which contains a notice placed by the copyright holder or 122 | other authorized party saying it may be distributed under the terms of 123 | this Lesser General Public License (also called "this License"). Each 124 | licensee is addressed as "you". 125 | 126 | A "library" means a collection of software functions and/or data 127 | prepared so as to be conveniently linked with application programs 128 | (which use some of those functions and data) to form executables. 129 | 130 | The "Library", below, refers to any such software library or work 131 | which has been distributed under these terms. A "work based on the 132 | Library" means either the Library or any derivative work under 133 | copyright law: that is to say, a work containing the Library or a 134 | portion of it, either verbatim or with modifications and/or translated 135 | straightforwardly into another language. (Hereinafter, translation is 136 | included without limitation in the term "modification".) 137 | 138 | "Source code" for a work means the preferred form of the work for 139 | making modifications to it. For a library, complete source code means 140 | all the source code for all modules it contains, plus any associated 141 | interface definition files, plus the scripts used to control 142 | compilation and installation of the library. 143 | 144 | Activities other than copying, distribution and modification are not 145 | covered by this License; they are outside its scope. The act of 146 | running a program using the Library is not restricted, and output from 147 | such a program is covered only if its contents constitute a work based 148 | on the Library (independent of the use of the Library in a tool for 149 | writing it). Whether that is true depends on what the Library does and 150 | what the program that uses the Library does. 151 | 152 | **1.** You may copy and distribute verbatim copies of the Library's 153 | complete source code as you receive it, in any medium, provided that 154 | you conspicuously and appropriately publish on each copy an 155 | appropriate copyright notice and disclaimer of warranty; keep intact 156 | all the notices that refer to this License and to the absence of any 157 | warranty; and distribute a copy of this License along with the 158 | Library. 159 | 160 | You may charge a fee for the physical act of transferring a copy, and 161 | you may at your option offer warranty protection in exchange for a 162 | fee. 163 | 164 | **2.** You may modify your copy or copies of the Library or any 165 | portion of it, thus forming a work based on the Library, and copy and 166 | distribute such modifications or work under the terms of Section 1 167 | above, provided that you also meet all of these conditions: 168 | 169 | - **a)** The modified work must itself be a software library. 170 | - **b)** You must cause the files modified to carry prominent 171 | notices stating that you changed the files and the date of 172 | any change. 173 | - **c)** You must cause the whole of the work to be licensed at no 174 | charge to all third parties under the terms of this License. 175 | - **d)** If a facility in the modified Library refers to a function 176 | or a table of data to be supplied by an application program that 177 | uses the facility, other than as an argument passed when the 178 | facility is invoked, then you must make a good faith effort to 179 | ensure that, in the event an application does not supply such 180 | function or table, the facility still operates, and performs 181 | whatever part of its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of 185 | the application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | **3.** You may opt to apply the terms of the ordinary GNU General 212 | Public License instead of this License to a given copy of the Library. 213 | To do this, you must alter all the notices that refer to this License, 214 | so that they refer to the ordinary GNU General Public License, version 215 | 2, instead of to this License. (If a newer version than version 2 of 216 | the ordinary GNU General Public License has appeared, then you can 217 | specify that version instead if you wish.) Do not make any other 218 | change in these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for that 221 | copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of the 225 | Library into a program that is not a library. 226 | 227 | **4.** You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy from 235 | a designated place, then offering equivalent access to copy the source 236 | code from the same place satisfies the requirement to distribute the 237 | source code, even though third parties are not compelled to copy the 238 | source along with the object code. 239 | 240 | **5.** A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a work, 243 | in isolation, is not a derivative work of the Library, and therefore 244 | falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. Section 250 | 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data structure 260 | layouts and accessors, and small macros and small inline functions 261 | (ten lines or less in length), then the use of the object file is 262 | unrestricted, regardless of whether it is legally a derivative work. 263 | (Executables containing this object code plus portions of the Library 264 | will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | **6.** As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a work 273 | containing portions of the Library, and distribute that work under 274 | terms of your choice, provided that the terms permit modification of 275 | the work for the customer's own use and reverse engineering for 276 | debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | - **a)** Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood that 294 | the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | - **b)** Use a suitable shared library mechanism for linking with 298 | the Library. A suitable mechanism is one that (1) uses at run time 299 | a copy of the library already present on the user's computer 300 | system, rather than copying library functions into the executable, 301 | and (2) will operate properly with a modified version of the 302 | library, if the user installs one, as long as the modified version 303 | is interface-compatible with the version that the work was 304 | made with. 305 | - **c)** Accompany the work with a written offer, valid for at least 306 | three years, to give the same user the materials specified in 307 | Subsection 6a, above, for a charge no more than the cost of 308 | performing this distribution. 309 | - **d)** If distribution of the work is made by offering access to 310 | copy from a designated place, offer equivalent access to copy the 311 | above specified materials from the same place. 312 | - **e)** Verify that the user has already received a copy of these 313 | materials or that you have already sent this user a copy. 314 | 315 | For an executable, the required form of the "work that uses the 316 | Library" must include any data and utility programs needed for 317 | reproducing the executable from it. However, as a special exception, 318 | the materials to be distributed need not include anything that is 319 | normally distributed (in either source or binary form) with the major 320 | components (compiler, kernel, and so on) of the operating system on 321 | which the executable runs, unless that component itself accompanies 322 | the executable. 323 | 324 | It may happen that this requirement contradicts the license 325 | restrictions of other proprietary libraries that do not normally 326 | accompany the operating system. Such a contradiction means you cannot 327 | use both them and the Library together in an executable that you 328 | distribute. 329 | 330 | **7.** You may place library facilities that are a work based on the 331 | Library side-by-side in a single library together with other library 332 | facilities not covered by this License, and distribute such a combined 333 | library, provided that the separate distribution of the work based on 334 | the Library and of the other library facilities is otherwise 335 | permitted, and provided that you do these two things: 336 | 337 | - **a)** Accompany the combined library with a copy of the same work 338 | based on the Library, uncombined with any other 339 | library facilities. This must be distributed under the terms of 340 | the Sections above. 341 | - **b)** Give prominent notice with the combined library of the fact 342 | that part of it is a work based on the Library, and explaining 343 | where to find the accompanying uncombined form of the same work. 344 | 345 | **8.** You may not copy, modify, sublicense, link with, or distribute 346 | the Library except as expressly provided under this License. Any 347 | attempt otherwise to copy, modify, sublicense, link with, or 348 | distribute the Library is void, and will automatically terminate your 349 | rights under this License. However, parties who have received copies, 350 | or rights, from you under this License will not have their licenses 351 | terminated so long as such parties remain in full compliance. 352 | 353 | **9.** You are not required to accept this License, since you have not 354 | signed it. However, nothing else grants you permission to modify or 355 | distribute the Library or its derivative works. These actions are 356 | prohibited by law if you do not accept this License. Therefore, by 357 | modifying or distributing the Library (or any work based on the 358 | Library), you indicate your acceptance of this License to do so, and 359 | all its terms and conditions for copying, distributing or modifying 360 | the Library or works based on it. 361 | 362 | **10.** Each time you redistribute the Library (or any work based on 363 | the Library), the recipient automatically receives a license from the 364 | original licensor to copy, distribute, link with or modify the Library 365 | subject to these terms and conditions. You may not impose any further 366 | restrictions on the recipients' exercise of the rights granted herein. 367 | You are not responsible for enforcing compliance by third parties with 368 | this License. 369 | 370 | **11.** If, as a consequence of a court judgment or allegation of 371 | patent infringement or for any other reason (not limited to patent 372 | issues), conditions are imposed on you (whether by court order, 373 | agreement or otherwise) that contradict the conditions of this 374 | License, they do not excuse you from the conditions of this License. 375 | If you cannot distribute so as to satisfy simultaneously your 376 | obligations under this License and any other pertinent obligations, 377 | then as a consequence you may not distribute the Library at all. For 378 | example, if a patent license would not permit royalty-free 379 | redistribution of the Library by all those who receive copies directly 380 | or indirectly through you, then the only way you could satisfy both it 381 | and this License would be to refrain entirely from distribution of the 382 | Library. 383 | 384 | If any portion of this section is held invalid or unenforceable under 385 | any particular circumstance, the balance of the section is intended to 386 | apply, and the section as a whole is intended to apply in other 387 | circumstances. 388 | 389 | It is not the purpose of this section to induce you to infringe any 390 | patents or other property right claims or to contest validity of any 391 | such claims; this section has the sole purpose of protecting the 392 | integrity of the free software distribution system which is 393 | implemented by public license practices. Many people have made 394 | generous contributions to the wide range of software distributed 395 | through that system in reliance on consistent application of that 396 | system; it is up to the author/donor to decide if he or she is willing 397 | to distribute software through any other system and a licensee cannot 398 | impose that choice. 399 | 400 | This section is intended to make thoroughly clear what is believed to 401 | be a consequence of the rest of this License. 402 | 403 | **12.** If the distribution and/or use of the Library is restricted in 404 | certain countries either by patents or by copyrighted interfaces, the 405 | original copyright holder who places the Library under this License 406 | may add an explicit geographical distribution limitation excluding 407 | those countries, so that distribution is permitted only in or among 408 | countries not thus excluded. In such case, this License incorporates 409 | the limitation as if written in the body of this License. 410 | 411 | **13.** The Free Software Foundation may publish revised and/or new 412 | versions of the Lesser General Public License from time to time. Such 413 | new versions will be similar in spirit to the present version, but may 414 | differ in detail to address new problems or concerns. 415 | 416 | Each version is given a distinguishing version number. If the Library 417 | specifies a version number of this License which applies to it and 418 | "any later version", you have the option of following the terms and 419 | conditions either of that version or of any later version published by 420 | the Free Software Foundation. If the Library does not specify a 421 | license version number, you may choose any version ever published by 422 | the Free Software Foundation. 423 | 424 | **14.** If you wish to incorporate parts of the Library into other 425 | free programs whose distribution conditions are incompatible with 426 | these, write to the author to ask for permission. For software which 427 | is copyrighted by the Free Software Foundation, write to the Free 428 | Software Foundation; we sometimes make exceptions for this. Our 429 | decision will be guided by the two goals of preserving the free status 430 | of all derivatives of our free software and of promoting the sharing 431 | and reuse of software generally. 432 | 433 | **NO WARRANTY** 434 | 435 | **15.** BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 436 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 437 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 438 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 439 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 440 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 441 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 442 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 443 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 444 | 445 | **16.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 446 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 447 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 448 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 449 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 450 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 451 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 452 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 453 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 454 | DAMAGES. 455 | 456 | ### END OF TERMS AND CONDITIONS 457 | 458 | ### How to Apply These Terms to Your New Libraries 459 | 460 | If you develop a new library, and you want it to be of the greatest 461 | possible use to the public, we recommend making it free software that 462 | everyone can redistribute and change. You can do so by permitting 463 | redistribution under these terms (or, alternatively, under the terms 464 | of the ordinary General Public License). 465 | 466 | To apply these terms, attach the following notices to the library. It 467 | is safest to attach them to the start of each source file to most 468 | effectively convey the exclusion of warranty; and each file should 469 | have at least the "copyright" line and a pointer to where the full 470 | notice is found. 471 | 472 | one line to give the library's name and an idea of what it does. 473 | Copyright (C) year name of author 474 | 475 | This library is free software; you can redistribute it and/or 476 | modify it under the terms of the GNU Lesser General Public 477 | License as published by the Free Software Foundation; either 478 | version 2.1 of the License, or (at your option) any later version. 479 | 480 | This library is distributed in the hope that it will be useful, 481 | but WITHOUT ANY WARRANTY; without even the implied warranty of 482 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 483 | Lesser General Public License for more details. 484 | 485 | You should have received a copy of the GNU Lesser General Public 486 | License along with this library; if not, write to the Free Software 487 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 488 | 489 | Also add information on how to contact you by electronic and paper 490 | mail. 491 | 492 | You should also get your employer (if you work as a programmer) or 493 | your school, if any, to sign a "copyright disclaimer" for the library, 494 | if necessary. Here is a sample; alter the names: 495 | 496 | Yoyodyne, Inc., hereby disclaims all copyright interest in 497 | the library `Frob' (a library for tweaking knobs) written 498 | by James Random Hacker. 499 | 500 | signature of Ty Coon, 1 April 1990 501 | Ty Coon, President of Vice 502 | 503 | That's all there is to it! 504 | -------------------------------------------------------------------------------- /Notices.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018-2019 Jacob Lifshay 2 | 3 | This file is part of Algebraics. 4 | 5 | Algebraics is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU Lesser General Public License as published by 7 | the Free Software Foundation, either version 2.1 of the License, or 8 | (at your option) any later version. 9 | 10 | Algebraics is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with Algebraics. If not, see . 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## [Algebraic Numbers](https://en.wikipedia.org/wiki/Algebraic_number) Library 2 | 3 | Use when you need exact arithmetic, speed is not critical, and rational numbers aren't good enough. 4 | 5 | ## Example: 6 | 7 | ```rust 8 | use algebraics::prelude::*; 9 | use algebraics::RealAlgebraicNumber as Number; 10 | 11 | let two = Number::from(2); 12 | 13 | // 2 is a rational number 14 | assert!(two.is_rational()); 15 | 16 | // 1/2 is the reciprocal of 2 17 | let one_half = two.recip(); 18 | 19 | // 1/2 is also a rational number 20 | assert!(one_half.is_rational()); 21 | 22 | // 2^(1/4) 23 | let root = (&two).pow((1, 4)); 24 | 25 | // we can use all the standard comparison operators 26 | assert!(root != Number::from(3)); 27 | assert!(root < Number::from(2)); 28 | assert!(root > Number::from(1)); 29 | 30 | // we can use all of add, subtract, multiply, divide, and remainder 31 | let sum = &root + &root; 32 | let difference = &root - Number::from(47); 33 | let product = &root * &one_half; 34 | let quotient = &one_half / &root; 35 | let remainder = &root % &one_half; 36 | 37 | // root is not a rational number 38 | assert!(!root.is_rational()); 39 | 40 | // the calculations are always exact 41 | assert_eq!((&root).pow(4), two); 42 | 43 | // lets compute 30 decimal places of root 44 | let scale = Number::from(10).pow(30); 45 | let scaled = &root * scale; 46 | let digits = scaled.into_integer_trunc(); 47 | assert_eq!( 48 | digits.to_string(), 49 | 1_18920_71150_02721_06671_74999_70560u128.to_string() 50 | ); 51 | 52 | // get the minimal polynomial 53 | let other_number = root + two.pow((1, 2)); 54 | assert_eq!( 55 | &other_number.minimal_polynomial().to_string(), 56 | "2 + -8*X + -4*X^2 + 0*X^3 + 1*X^4" 57 | ); 58 | 59 | // works with really big numbers 60 | let really_big = Number::from(1_00000_00000i64).pow(20) + Number::from(23); 61 | assert_eq!( 62 | &really_big.to_integer_floor().to_string(), 63 | "100000000000000000000000000000000000000000000\ 64 | 000000000000000000000000000000000000000000000\ 65 | 000000000000000000000000000000000000000000000\ 66 | 000000000000000000000000000000000000000000000\ 67 | 000000000000000000023" 68 | ) 69 | ``` 70 | 71 | ## Python support 72 | 73 | Using algebraics from Python: 74 | 75 | ```bash 76 | python3 -m pip install algebraics 77 | ``` 78 | 79 | ```python 80 | from algebraics import RealAlgebraicNumber 81 | sqrt_2 = 2 ** (RealAlgebraicNumber(1) / 2) 82 | assert sqrt_2 * sqrt_2 == 2 83 | ``` 84 | 85 | Using algebraics in your own Rust project: 86 | 87 | ```toml 88 | [dependencies.algebraics] 89 | version = "0.3" 90 | ``` 91 | 92 | Developing algebraics: 93 | 94 | ```bash 95 | cargo install maturin 96 | maturin develop --cargo-extra-args="--features python-extension" 97 | ``` 98 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-or-later 2 | # See Notices.txt for copyright information 3 | [build-system] 4 | requires = ["maturin>=0.11,<0.12"] 5 | build-backend = "maturin" 6 | 7 | [tool.maturin] 8 | bindings = "pyo3" 9 | cargo-extra-args = "--features python-extension" 10 | -------------------------------------------------------------------------------- /rls.toml: -------------------------------------------------------------------------------- 1 | features = ["python-extension"] 2 | -------------------------------------------------------------------------------- /src/array2d.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-2.1-or-later 2 | // See Notices.txt for copyright information 3 | 4 | // FIXME: remove when module made public again 5 | #![allow(dead_code)] 6 | 7 | use std::{ 8 | borrow::{Borrow, BorrowMut}, 9 | fmt, 10 | iter::FusedIterator, 11 | ops::{ 12 | Bound, Index, IndexMut, Range, RangeBounds, RangeFrom, RangeFull, RangeInclusive, RangeTo, 13 | RangeToInclusive, 14 | }, 15 | slice, vec, 16 | }; 17 | 18 | mod private { 19 | pub(crate) trait SealedData {} 20 | 21 | impl SealedData for Vec {} 22 | impl<'a, T> SealedData for &'a [T] {} 23 | impl<'a, T> SealedData for &'a mut [T] {} 24 | } 25 | 26 | pub(crate) trait Array2DData: 27 | Sized + private::SealedData + Borrow<[::Element]> 28 | { 29 | type Element: Sized; 30 | type StrideType: Copy + Default; 31 | type IntoIter: Iterator; 32 | fn read_stride(y_size: usize, stride: Self::StrideType) -> usize; 33 | fn make_stride(y_size: usize) -> Self::StrideType; 34 | fn into_iter(this: Array2DBase) -> Self::IntoIter; 35 | } 36 | 37 | impl Array2DData for Vec { 38 | type Element = T; 39 | type StrideType = (); 40 | type IntoIter = IntoIter; 41 | fn read_stride(y_size: usize, _stride: Self::StrideType) -> usize { 42 | y_size 43 | } 44 | fn make_stride(_y_size: usize) -> Self::StrideType { 45 | Default::default() 46 | } 47 | fn into_iter(this: Array2DBase) -> Self::IntoIter { 48 | IntoIter { 49 | positions: this.positions(), 50 | data: IterOwnedData { 51 | stride: this.stride(), 52 | offset: 0, 53 | iter: this.data.into_iter(), 54 | }, 55 | } 56 | } 57 | } 58 | 59 | impl<'a, T: Sized> Array2DData for &'a [T] { 60 | type Element = T; 61 | type StrideType = usize; 62 | type IntoIter = Iter<'a, T>; 63 | fn read_stride(_y_size: usize, stride: Self::StrideType) -> usize { 64 | stride 65 | } 66 | fn make_stride(y_size: usize) -> Self::StrideType { 67 | y_size 68 | } 69 | fn into_iter(this: Array2DBase) -> Self::IntoIter { 70 | Iter { 71 | positions: this.positions(), 72 | stride: this.stride(), 73 | data: this.data.borrow(), 74 | } 75 | } 76 | } 77 | 78 | impl<'a, T: Sized> Array2DData for &'a mut [T] { 79 | type Element = T; 80 | type StrideType = usize; 81 | type IntoIter = IterMut<'a, T>; 82 | fn read_stride(_y_size: usize, stride: Self::StrideType) -> usize { 83 | stride 84 | } 85 | fn make_stride(y_size: usize) -> Self::StrideType { 86 | y_size 87 | } 88 | fn into_iter(this: Array2DBase) -> Self::IntoIter { 89 | IterMut { 90 | positions: this.positions(), 91 | data: IterOwnedData { 92 | stride: this.stride(), 93 | offset: 0, 94 | iter: this.data.borrow_mut().iter_mut(), 95 | }, 96 | } 97 | } 98 | } 99 | 100 | /// column-major 2D array 101 | /// 102 | /// The alternate display format (using `"{:#}"`) is a reStructuredText table 103 | /// 104 | /// Examples: 105 | /// ```ignored 106 | /// # use algebraics::array2d::Array2DOwned; 107 | /// // Note: using strings to demonstrate `Display`, numbers are normally used 108 | /// let mut array = Array2DOwned::from_array( 109 | /// 3, 110 | /// 3, 111 | /// vec!["0", "1", "2", "10", "11", "12", "20", "21", "22"], 112 | /// ); 113 | /// assert_eq!( 114 | /// format!("{}", array), 115 | /// "[ 0 10 20 ]\n\ 116 | /// [ 1 11 21 ]\n\ 117 | /// [ 2 12 22 ]" 118 | /// ); 119 | /// assert_eq!( 120 | /// format!("{:#}", array), 121 | /// "+---+----+----+\n\ 122 | /// | 0 | 10 | 20 |\n\ 123 | /// +---+----+----+\n\ 124 | /// | 1 | 11 | 21 |\n\ 125 | /// +---+----+----+\n\ 126 | /// | 2 | 12 | 22 |\n\ 127 | /// +---+----+----+" 128 | /// ); 129 | /// // change the value at x=0, y=2 to a multi-line str 130 | /// array[(0, 2)] = "line 1\nline 2"; 131 | /// 132 | /// assert_eq!( 133 | /// format!("{}", array), 134 | /// "[ 0 10 20 ]\n\ 135 | /// [ ]\n\ 136 | /// [ 1 11 21 ]\n\ 137 | /// [ ]\n\ 138 | /// [ line 1 12 22 ]\n\ 139 | /// [ line 2 ]" 140 | /// ); 141 | /// assert_eq!( 142 | /// format!("{:#}", array), 143 | /// "+--------+----+----+\n\ 144 | /// | 0 | 10 | 20 |\n\ 145 | /// +--------+----+----+\n\ 146 | /// | 1 | 11 | 21 |\n\ 147 | /// +--------+----+----+\n\ 148 | /// | line 1 | 12 | 22 |\n\ 149 | /// | line 2 | | |\n\ 150 | /// +--------+----+----+" 151 | /// ); 152 | /// ``` 153 | // FIXME: unignore doctest when pub 154 | #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] 155 | pub(crate) struct Array2DBase { 156 | x_size: usize, 157 | y_size: usize, 158 | stride: Data::StrideType, 159 | data: Data, 160 | } 161 | 162 | struct Array2DSliceData { 163 | x_size: usize, 164 | y_size: usize, 165 | data_offset: usize, 166 | } 167 | 168 | fn get_index_unchecked(stride: usize, x: usize, y: usize) -> usize { 169 | x * stride + y 170 | } 171 | 172 | pub(crate) trait Array2DSliceBound { 173 | fn to_slice_bound(self) -> (Bound, Bound); 174 | } 175 | 176 | impl Array2DSliceBound for usize { 177 | fn to_slice_bound(self) -> (Bound, Bound) { 178 | (Bound::Included(self), Bound::Included(self)) 179 | } 180 | } 181 | 182 | fn bound_cloned(bound: Bound<&T>) -> Bound { 183 | match bound { 184 | Bound::Unbounded => Bound::Unbounded, 185 | Bound::Excluded(v) => Bound::Excluded(v.clone()), 186 | Bound::Included(v) => Bound::Included(v.clone()), 187 | } 188 | } 189 | 190 | macro_rules! impl_array2d_slice_bound { 191 | ($t:ty) => { 192 | impl Array2DSliceBound for $t { 193 | fn to_slice_bound(self) -> (Bound, Bound) { 194 | ( 195 | bound_cloned(self.start_bound()), 196 | bound_cloned(self.end_bound()), 197 | ) 198 | } 199 | } 200 | }; 201 | } 202 | 203 | impl_array2d_slice_bound!(RangeFull); 204 | impl_array2d_slice_bound!(Range); 205 | impl_array2d_slice_bound!(RangeFrom); 206 | impl_array2d_slice_bound!(RangeTo); 207 | impl_array2d_slice_bound!(RangeInclusive); 208 | impl_array2d_slice_bound!(RangeToInclusive); 209 | impl_array2d_slice_bound!((Bound, Bound)); 210 | impl_array2d_slice_bound!(Range<&usize>); 211 | impl_array2d_slice_bound!(RangeFrom<&usize>); 212 | impl_array2d_slice_bound!(RangeTo<&usize>); 213 | impl_array2d_slice_bound!(RangeInclusive<&usize>); 214 | impl_array2d_slice_bound!(RangeToInclusive<&usize>); 215 | impl_array2d_slice_bound!((Bound<&usize>, Bound<&usize>)); 216 | 217 | impl Array2DBase { 218 | /// data is a column-major 2D array 219 | pub(crate) fn from_array(x_size: usize, y_size: usize, data: Data) -> Self { 220 | assert_eq!(x_size * y_size, data.borrow().len()); 221 | Self { 222 | x_size, 223 | y_size, 224 | stride: Data::make_stride(y_size), 225 | data, 226 | } 227 | } 228 | pub(crate) fn x_size(&self) -> usize { 229 | self.x_size 230 | } 231 | pub(crate) fn y_size(&self) -> usize { 232 | self.y_size 233 | } 234 | pub(crate) fn size(&self) -> (usize, usize) { 235 | (self.x_size, self.y_size) 236 | } 237 | fn stride(&self) -> usize { 238 | Data::read_stride(self.y_size, self.stride) 239 | } 240 | fn get_index(&self, x: usize, y: usize) -> usize { 241 | assert!(x < self.x_size); 242 | assert!(y < self.y_size); 243 | get_index_unchecked(self.stride(), x, y) 244 | } 245 | fn do_slice( 246 | &self, 247 | x_bound: XB, 248 | y_bound: YB, 249 | ) -> Array2DSliceData { 250 | fn start_and_end( 251 | (start_bound, end_bound): (Bound, Bound), 252 | size: usize, 253 | ) -> (usize, usize) { 254 | let start = match start_bound { 255 | Bound::Unbounded => 0, 256 | Bound::Excluded(v) => v + 1, 257 | Bound::Included(v) => v, 258 | }; 259 | let end = match end_bound { 260 | Bound::Unbounded => size, 261 | Bound::Excluded(v) => v, 262 | Bound::Included(v) => v + 1, 263 | }; 264 | assert!(start <= end); 265 | assert!(end <= size); 266 | (start, end) 267 | } 268 | let (x_start, x_end) = start_and_end(x_bound.to_slice_bound(), self.x_size); 269 | let (y_start, y_end) = start_and_end(y_bound.to_slice_bound(), self.y_size); 270 | Array2DSliceData { 271 | x_size: x_end - x_start, 272 | y_size: y_end - y_start, 273 | data_offset: get_index_unchecked(self.stride(), x_start, y_start), 274 | } 275 | } 276 | pub(crate) fn slice( 277 | &self, 278 | x_bound: XB, 279 | y_bound: YB, 280 | ) -> Array2DSlice { 281 | let Array2DSliceData { 282 | x_size, 283 | y_size, 284 | data_offset, 285 | } = self.do_slice(x_bound, y_bound); 286 | Array2DBase { 287 | x_size, 288 | y_size, 289 | stride: self.stride(), 290 | data: &self.data.borrow()[data_offset..], 291 | } 292 | } 293 | pub(crate) fn slice_mut( 294 | &mut self, 295 | x_bound: XB, 296 | y_bound: YB, 297 | ) -> Array2DMutSlice 298 | where 299 | Data: BorrowMut<[::Element]>, 300 | { 301 | let Array2DSliceData { 302 | x_size, 303 | y_size, 304 | data_offset, 305 | } = self.do_slice(x_bound, y_bound); 306 | Array2DBase { 307 | x_size, 308 | y_size, 309 | stride: self.stride(), 310 | data: &mut self.data.borrow_mut()[data_offset..], 311 | } 312 | } 313 | pub(crate) fn positions(&self) -> Positions { 314 | Positions::new(self.x_size, self.y_size) 315 | } 316 | pub(crate) fn iter(&self) -> Iter { 317 | Iter { 318 | positions: self.positions(), 319 | stride: self.stride(), 320 | data: self.data.borrow(), 321 | } 322 | } 323 | pub(crate) fn iter_mut(&mut self) -> IterMut 324 | where 325 | Data: BorrowMut<[::Element]>, 326 | { 327 | IterMut { 328 | positions: self.positions(), 329 | data: IterOwnedData { 330 | stride: self.stride(), 331 | offset: 0, 332 | iter: self.data.borrow_mut().iter_mut(), 333 | }, 334 | } 335 | } 336 | pub(crate) fn to_owned(&self) -> Array2DOwned 337 | where 338 | Data::Element: Clone, 339 | { 340 | Array2DBase::from_array(self.x_size, self.y_size, self.iter().cloned().collect()) 341 | } 342 | pub(crate) fn swap_elements(&mut self, (x1, y1): (usize, usize), (x2, y2): (usize, usize)) 343 | where 344 | Data: BorrowMut<[::Element]>, 345 | { 346 | let index1 = self.get_index(x1, y1); 347 | let index2 = self.get_index(x2, y2); 348 | self.data.borrow_mut().swap(index1, index2); 349 | } 350 | } 351 | 352 | impl Array2DBase> { 353 | pub(crate) fn new_with_positions T>( 354 | x_size: usize, 355 | y_size: usize, 356 | mut f: F, 357 | ) -> Self { 358 | Self::from_array( 359 | x_size, 360 | y_size, 361 | Positions::new(x_size, y_size) 362 | .map(move |(x, y)| f(x, y)) 363 | .collect(), 364 | ) 365 | } 366 | pub(crate) fn new_with T>(x_size: usize, y_size: usize, f: F) -> Self { 367 | let len = x_size * y_size; 368 | let mut data = Vec::with_capacity(len); 369 | data.resize_with(len, f); 370 | Self::from_array(x_size, y_size, data) 371 | } 372 | pub(crate) fn new(x_size: usize, y_size: usize, value: T) -> Self 373 | where 374 | T: Clone, 375 | { 376 | let len = x_size * y_size; 377 | let mut data = Vec::with_capacity(len); 378 | data.resize(len, value); 379 | Self::from_array(x_size, y_size, data) 380 | } 381 | pub(crate) fn into_data(self) -> Vec { 382 | self.data 383 | } 384 | pub(crate) fn data(&self) -> &[T] { 385 | &*self.data 386 | } 387 | pub(crate) fn data_mut(&mut self) -> &mut [T] { 388 | &mut *self.data 389 | } 390 | } 391 | 392 | impl Index<(usize, usize)> for Array2DBase { 393 | type Output = Data::Element; 394 | fn index(&self, (x, y): (usize, usize)) -> &Self::Output { 395 | let index = self.get_index(x, y); 396 | &self.data.borrow()[index] 397 | } 398 | } 399 | 400 | impl::Element]>> IndexMut<(usize, usize)> 401 | for Array2DBase 402 | { 403 | fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Self::Output { 404 | let index = self.get_index(x, y); 405 | &mut self.data.borrow_mut()[index] 406 | } 407 | } 408 | 409 | impl> fmt::Display for Array2DBase { 410 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 411 | if self.x_size() == 0 || self.y_size() == 0 { 412 | return writeln!(f, "[x_size={}, y_size={}]", self.x_size(), self.y_size()); 413 | } 414 | let mut cells: Vec> = Vec::new(); 415 | let mut row_heights = vec![0usize; self.y_size()]; 416 | let mut col_widths = vec![0usize; self.x_size()]; 417 | cells.resize(self.y_size(), Vec::with_capacity(self.x_size())); 418 | let mut any_multiline_cells = false; 419 | for y in 0..self.y_size() { 420 | for x in 0..self.x_size() { 421 | let cell_str = format!("{}", self[(x, y)]); 422 | let mut width = 1; 423 | let mut height = 0; 424 | for line in cell_str.lines() { 425 | height += 1; 426 | width = width.max(line.len()); 427 | } 428 | height = height.max(1); 429 | if height > 1 { 430 | any_multiline_cells = true; 431 | } 432 | row_heights[y] = row_heights[y].max(height); 433 | col_widths[x] = col_widths[x].max(width); 434 | cells[y].push(cell_str); 435 | } 436 | } 437 | let write_separator_line = |f: &mut fmt::Formatter| { 438 | for &col_width in &col_widths { 439 | write!(f, "+-")?; 440 | for _ in 0..col_width { 441 | write!(f, "-")?; 442 | } 443 | write!(f, "-")?; 444 | } 445 | write!(f, "+") 446 | }; 447 | if f.alternate() { 448 | write_separator_line(f)?; 449 | writeln!(f)?; 450 | } 451 | for y in 0..self.y_size() { 452 | let is_last_row = y == self.y_size() - 1; 453 | let mut line_iters: Vec<_> = cells[y].iter().map(|cell| cell.lines()).collect(); 454 | let mut height = row_heights[y]; 455 | if !f.alternate() && any_multiline_cells && !is_last_row { 456 | height += 1; 457 | } 458 | for cell_row in 0..height { 459 | if !f.alternate() { 460 | write!(f, "[")?; 461 | } 462 | for x in 0..self.x_size() { 463 | let cell_line = line_iters[x].next().unwrap_or(""); 464 | if f.alternate() { 465 | write!(f, "|")?; 466 | } 467 | write!( 468 | f, 469 | " {cell_line:width$} ", 470 | cell_line = cell_line, 471 | width = col_widths[x] 472 | )?; 473 | } 474 | if f.alternate() { 475 | write!(f, "|")?; 476 | } else { 477 | write!(f, "]")?; 478 | } 479 | if cell_row != height - 1 { 480 | writeln!(f)?; 481 | } 482 | } 483 | if f.alternate() { 484 | writeln!(f)?; 485 | write_separator_line(f)?; 486 | } 487 | if !is_last_row { 488 | writeln!(f)?; 489 | } 490 | } 491 | Ok(()) 492 | } 493 | } 494 | 495 | pub(crate) type Array2DOwned = Array2DBase>; 496 | pub(crate) type Array2DSlice<'a, T> = Array2DBase<&'a [T]>; 497 | pub(crate) type Array2DMutSlice<'a, T> = Array2DBase<&'a mut [T]>; 498 | 499 | /// column-major 2D positions iterator 500 | #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] 501 | pub(crate) struct Positions { 502 | x: usize, 503 | y: usize, 504 | y_size: usize, 505 | rev_x: usize, 506 | after_rev_y: usize, 507 | } 508 | 509 | impl Default for Positions { 510 | fn default() -> Self { 511 | Self { 512 | x: 0, 513 | y: 0, 514 | y_size: 0, 515 | rev_x: 0, 516 | after_rev_y: 0, 517 | } 518 | } 519 | } 520 | 521 | impl Positions { 522 | pub(crate) fn new(x_size: usize, y_size: usize) -> Self { 523 | if x_size == 0 || y_size == 0 { 524 | Self::default() 525 | } else { 526 | Self { 527 | x: 0, 528 | y: 0, 529 | y_size, 530 | rev_x: x_size - 1, 531 | after_rev_y: y_size, 532 | } 533 | } 534 | } 535 | pub(crate) fn is_finished(&self) -> bool { 536 | self.x == self.rev_x && self.y == self.after_rev_y 537 | } 538 | pub(crate) fn pos(&self) -> Option<(usize, usize)> { 539 | if self.is_finished() { 540 | return None; 541 | } 542 | Some((self.x, self.y)) 543 | } 544 | pub(crate) fn rev_pos(&self) -> Option<(usize, usize)> { 545 | if self.is_finished() { 546 | return None; 547 | } 548 | Some((self.rev_x, self.after_rev_y - 1)) 549 | } 550 | } 551 | 552 | impl Iterator for Positions { 553 | type Item = (usize, usize); 554 | fn next(&mut self) -> Option<(usize, usize)> { 555 | let pos = self.pos()?; 556 | self.y += 1; 557 | if self.y == self.y_size && self.rev_x != self.x { 558 | self.y = 0; 559 | self.x += 1; 560 | } 561 | Some(pos) 562 | } 563 | fn size_hint(&self) -> (usize, Option) { 564 | let len = self.len(); 565 | (len, Some(len)) 566 | } 567 | fn count(self) -> usize { 568 | self.len() 569 | } 570 | fn last(mut self) -> Option<(usize, usize)> { 571 | self.next_back() 572 | } 573 | } 574 | 575 | impl DoubleEndedIterator for Positions { 576 | fn next_back(&mut self) -> Option<(usize, usize)> { 577 | let pos = self.rev_pos()?; 578 | self.after_rev_y -= 1; 579 | if self.after_rev_y == 0 && self.rev_x != self.x { 580 | self.after_rev_y = self.y_size; 581 | self.rev_x -= 1; 582 | } 583 | Some(pos) 584 | } 585 | } 586 | 587 | impl ExactSizeIterator for Positions { 588 | fn len(&self) -> usize { 589 | self.y_size * (self.rev_x - self.x) + self.after_rev_y - self.y 590 | } 591 | } 592 | 593 | impl FusedIterator for Positions {} 594 | 595 | macro_rules! impl_positions_wrapper { 596 | (($($trait_args:tt),*), $wrapper:ident, $item:ty) => { 597 | impl<$($trait_args),*> Iterator for $wrapper<$($trait_args),*> { 598 | type Item = $item; 599 | fn next(&mut self) -> Option<$item> { 600 | let (x, y) = self.positions.next()?; 601 | Some(self.get_unchecked(x, y)) 602 | } 603 | fn size_hint(&self) -> (usize, Option) { 604 | self.positions.size_hint() 605 | } 606 | fn count(self) -> usize { 607 | self.positions.count() 608 | } 609 | fn last(mut self) -> Option<$item> { 610 | self.next_back() 611 | } 612 | } 613 | 614 | impl<$($trait_args),*> DoubleEndedIterator for $wrapper<$($trait_args),*> { 615 | fn next_back(&mut self) -> Option<$item> { 616 | let (x, y) = self.positions.next_back()?; 617 | Some(self.get_unchecked_back(x, y)) 618 | } 619 | } 620 | 621 | impl<$($trait_args),*> ExactSizeIterator for $wrapper<$($trait_args),*> { 622 | fn len(&self) -> usize { 623 | self.positions.len() 624 | } 625 | } 626 | 627 | impl<$($trait_args),*> FusedIterator for $wrapper<$($trait_args),*> {} 628 | }; 629 | } 630 | 631 | #[derive(Copy, Clone, Debug)] 632 | pub(crate) struct IterWithPositions<'a, T> { 633 | positions: Positions, 634 | stride: usize, 635 | data: &'a [T], 636 | } 637 | 638 | impl<'a, T> IterWithPositions<'a, T> { 639 | pub(crate) fn positions(&self) -> Positions { 640 | self.positions 641 | } 642 | pub(crate) fn without_positions(self) -> Iter<'a, T> { 643 | let IterWithPositions { 644 | positions, 645 | stride, 646 | data, 647 | } = self; 648 | Iter { 649 | positions, 650 | stride, 651 | data, 652 | } 653 | } 654 | fn get_unchecked(&self, x: usize, y: usize) -> ((usize, usize), &'a T) { 655 | ((x, y), &self.data[get_index_unchecked(self.stride, x, y)]) 656 | } 657 | fn get_unchecked_back(&self, x: usize, y: usize) -> ((usize, usize), &'a T) { 658 | self.get_unchecked(x, y) 659 | } 660 | } 661 | 662 | impl_positions_wrapper!(('a, T), IterWithPositions, ((usize, usize), &'a T)); 663 | 664 | #[derive(Debug)] 665 | pub(crate) struct IterMutWithPositions<'a, T: 'a> { 666 | positions: Positions, 667 | data: IterOwnedData>, 668 | } 669 | 670 | impl<'a, T: 'a> IterMutWithPositions<'a, T> { 671 | pub(crate) fn positions(&self) -> Positions { 672 | self.positions 673 | } 674 | pub(crate) fn without_positions(self) -> IterMut<'a, T> { 675 | let IterMutWithPositions { positions, data } = self; 676 | IterMut { positions, data } 677 | } 678 | fn get_unchecked(&mut self, x: usize, y: usize) -> ((usize, usize), &'a mut T) { 679 | ((x, y), self.data.get_unchecked(x, y)) 680 | } 681 | fn get_unchecked_back(&mut self, x: usize, y: usize) -> ((usize, usize), &'a mut T) { 682 | ((x, y), self.data.get_unchecked_back(x, y)) 683 | } 684 | } 685 | 686 | impl_positions_wrapper!(('a, T), IterMutWithPositions, ((usize, usize), &'a mut T)); 687 | 688 | #[derive(Copy, Clone, Debug)] 689 | pub(crate) struct Iter<'a, T> { 690 | positions: Positions, 691 | stride: usize, 692 | data: &'a [T], 693 | } 694 | 695 | impl<'a, T> Iter<'a, T> { 696 | pub(crate) fn positions(&self) -> Positions { 697 | self.positions 698 | } 699 | pub(crate) fn with_positions(self) -> IterWithPositions<'a, T> { 700 | let Iter { 701 | positions, 702 | stride, 703 | data, 704 | } = self; 705 | IterWithPositions { 706 | positions, 707 | stride, 708 | data, 709 | } 710 | } 711 | fn get_unchecked(&self, x: usize, y: usize) -> &'a T { 712 | &self.data[get_index_unchecked(self.stride, x, y)] 713 | } 714 | fn get_unchecked_back(&self, x: usize, y: usize) -> &'a T { 715 | self.get_unchecked(x, y) 716 | } 717 | } 718 | 719 | impl_positions_wrapper!(('a, T), Iter, &'a T); 720 | 721 | #[derive(Debug)] 722 | struct IterOwnedData { 723 | stride: usize, 724 | offset: usize, 725 | iter: Iter, 726 | } 727 | 728 | impl IterOwnedData { 729 | fn get_unchecked(&mut self, x: usize, y: usize) -> Iter::Item { 730 | let index = get_index_unchecked(self.stride, x, y) - self.offset; 731 | self.offset += index + 1; 732 | self.iter.nth(index).expect("data shouldn't be empty") 733 | } 734 | fn get_unchecked_back(&mut self, x: usize, y: usize) -> Iter::Item { 735 | let index = get_index_unchecked(self.stride, x, y) - self.offset; 736 | self.iter 737 | .nth_back(self.iter.len() - dbg!(index) - 1) 738 | .expect("data shouldn't be empty") 739 | } 740 | } 741 | 742 | #[derive(Debug)] 743 | pub(crate) struct IterMut<'a, T: 'a> { 744 | positions: Positions, 745 | data: IterOwnedData>, 746 | } 747 | 748 | impl<'a, T: 'a> IterMut<'a, T> { 749 | pub(crate) fn positions(&self) -> Positions { 750 | self.positions 751 | } 752 | pub(crate) fn with_positions(self) -> IterMutWithPositions<'a, T> { 753 | let IterMut { positions, data } = self; 754 | IterMutWithPositions { positions, data } 755 | } 756 | fn get_unchecked(&mut self, x: usize, y: usize) -> &'a mut T { 757 | self.data.get_unchecked(x, y) 758 | } 759 | fn get_unchecked_back(&mut self, x: usize, y: usize) -> &'a mut T { 760 | self.data.get_unchecked_back(x, y) 761 | } 762 | } 763 | 764 | impl_positions_wrapper!(('a, T), IterMut, &'a mut T); 765 | 766 | #[derive(Debug)] 767 | pub(crate) struct IntoIterWithPositions { 768 | positions: Positions, 769 | data: IterOwnedData>, 770 | } 771 | 772 | impl IntoIterWithPositions { 773 | pub(crate) fn positions(&self) -> Positions { 774 | self.positions 775 | } 776 | pub(crate) fn without_positions(self) -> IntoIter { 777 | let IntoIterWithPositions { positions, data } = self; 778 | IntoIter { positions, data } 779 | } 780 | fn get_unchecked(&mut self, x: usize, y: usize) -> ((usize, usize), T) { 781 | ((x, y), self.data.get_unchecked(x, y)) 782 | } 783 | fn get_unchecked_back(&mut self, x: usize, y: usize) -> ((usize, usize), T) { 784 | ((x, y), self.data.get_unchecked_back(x, y)) 785 | } 786 | } 787 | 788 | impl_positions_wrapper!((T), IntoIterWithPositions, ((usize, usize), T)); 789 | 790 | #[derive(Debug)] 791 | pub(crate) struct IntoIter { 792 | positions: Positions, 793 | data: IterOwnedData>, 794 | } 795 | 796 | impl IntoIter { 797 | pub(crate) fn positions(&self) -> Positions { 798 | self.positions 799 | } 800 | pub(crate) fn with_positions(self) -> IntoIterWithPositions { 801 | let IntoIter { positions, data } = self; 802 | IntoIterWithPositions { positions, data } 803 | } 804 | fn get_unchecked(&mut self, x: usize, y: usize) -> T { 805 | self.data.get_unchecked(x, y) 806 | } 807 | fn get_unchecked_back(&mut self, x: usize, y: usize) -> T { 808 | self.data.get_unchecked_back(x, y) 809 | } 810 | } 811 | 812 | impl_positions_wrapper!((T), IntoIter, T); 813 | 814 | impl<'a, Data: Array2DData> IntoIterator for &'a Array2DBase { 815 | type Item = &'a Data::Element; 816 | type IntoIter = Iter<'a, Data::Element>; 817 | fn into_iter(self) -> Self::IntoIter { 818 | self.iter() 819 | } 820 | } 821 | 822 | impl<'a, T: 'a, Data: Array2DData + BorrowMut<[T]>> IntoIterator 823 | for &'a mut Array2DBase 824 | { 825 | type Item = &'a mut Data::Element; 826 | type IntoIter = IterMut<'a, Data::Element>; 827 | fn into_iter(self) -> Self::IntoIter { 828 | self.iter_mut() 829 | } 830 | } 831 | 832 | impl IntoIterator for Array2DBase { 833 | type Item = ::Item; 834 | type IntoIter = Data::IntoIter; 835 | fn into_iter(self) -> Self::IntoIter { 836 | Data::into_iter(self) 837 | } 838 | } 839 | 840 | #[cfg(test)] 841 | mod tests { 842 | use super::*; 843 | 844 | #[test] 845 | fn test_positions() { 846 | let expected_positions_list = &[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]; 847 | for operation_mask in 0..(1 << expected_positions_list.len()) { 848 | println!(); 849 | let mut positions = Positions::new(2, 3); 850 | let mut expected_positions = expected_positions_list.iter().copied(); 851 | dbg!(positions); 852 | assert_eq!(positions.len(), expected_positions.len()); 853 | for operation_index in 0..expected_positions_list.len() { 854 | if (operation_mask & (1 << operation_index)) != 0 { 855 | let position = dbg!(positions.next()); 856 | let expected_position = expected_positions.next(); 857 | assert_eq!(position, expected_position); 858 | } else { 859 | let position = dbg!(positions.next_back()); 860 | let expected_position = expected_positions.next_back(); 861 | assert_eq!(position, expected_position); 862 | } 863 | dbg!(positions); 864 | assert_eq!(positions.len(), expected_positions.len()); 865 | } 866 | } 867 | } 868 | 869 | #[test] 870 | fn test_iter_mut() { 871 | let mut array = Array2DOwned::new_with_positions(2, 3, |x, y| x * 10 + y); 872 | let expected_list: Vec<_> = array.data().iter().copied().collect(); 873 | for operation_mask in 0..(1 << expected_list.len()) { 874 | println!(); 875 | let mut expected = expected_list.iter().copied(); 876 | let mut iter_mut = array.iter_mut(); 877 | dbg!(&iter_mut); 878 | assert_eq!(expected.len(), iter_mut.len()); 879 | for operation_index in 0..expected_list.len() { 880 | if (operation_mask & (1 << operation_index)) != 0 { 881 | let value = dbg!(iter_mut.next()).copied(); 882 | let expected_value = expected.next(); 883 | assert_eq!(value, expected_value); 884 | } else { 885 | let value = dbg!(iter_mut.next_back()).copied(); 886 | let expected_value = expected.next_back(); 887 | assert_eq!(value, expected_value); 888 | } 889 | dbg!(&iter_mut); 890 | assert_eq!(expected.len(), iter_mut.len()); 891 | } 892 | } 893 | } 894 | } 895 | -------------------------------------------------------------------------------- /src/generate_gcd_test_cases.mac: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later 2 | * See Notices.txt for copyright information 3 | * 4 | * Maxima program to generate the test cases for Polynomial::>::gcd_lcm 5 | * 6 | * run by using: 7 | * maxima -b generate_gcd_test_cases.mac 8 | */ 9 | 10 | number_to_text(v):=block( 11 | [n, d], 12 | n:num(v), 13 | d:denom(v), 14 | if d=1 then return(concat("ri(", n, ")")), 15 | concat("r(", n, ", ", d, ")") 16 | )$ 17 | 18 | poly_to_text(p):=block( 19 | [p:ratexpand(p),l], 20 | if p=0 then return("Zero::zero()"), 21 | if hipow(p, x)=0 then ( 22 | return(concat(number_to_text(p), ".into()")) 23 | ), 24 | l:makelist( 25 | [number_to_text(ratcoef(p, x, i)), ", "], 26 | i, 27 | 0, 28 | hipow(p, x)), 29 | l:reverse(rest(reverse(flatten(l)))), 30 | apply(concat, append(["vec!["], l, ["].into()"])) 31 | )$ 32 | 33 | print_test_case(a, b, g, l):=( 34 | printf( 35 | true, 36 | "test_case(~% ~a,~% ~a,~% ~a,~% ~a,~%);~%", 37 | poly_to_text(a), 38 | poly_to_text(b), 39 | poly_to_text(g), 40 | poly_to_text(l) 41 | ) 42 | )$ 43 | 44 | n:200$ 45 | ( 46 | thru n do block( 47 | [a, b, g, l], 48 | a:sum(random(3)*x^i, i, 0, 3)/(random(3)+1), 49 | b:sum(random(3)*x^i, i, 0, 3)/(random(3)+1), 50 | g:ratsimp(content(gcd(a, b), x)[2]), 51 | l:a*b, 52 | if g#0 then l:l/g, 53 | l:ratsimp(l), 54 | if g#1 or random(n) < 10 then print_test_case(a, b, g, l) 55 | ), 56 | print_test_case(0, 0, 0, 0) 57 | )$ 58 | -------------------------------------------------------------------------------- /src/lattice.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-2.1-or-later 2 | // See Notices.txt for copyright information 3 | 4 | // FIXME: remove when module made public again 5 | #![allow(dead_code)] 6 | 7 | use crate::array2d::{Array2DOwned, Array2DSlice}; 8 | use num_integer::Integer; 9 | use num_rational::Ratio; 10 | use num_traits::{FromPrimitive, NumAssign, NumRef, Signed, Zero}; 11 | use std::{ 12 | cmp::Ordering, 13 | ops::{Add, Mul, RangeTo}, 14 | }; 15 | 16 | pub(crate) fn inner_product<'a, L, R, T>(a: Array2DSlice<'a, L>, b: Array2DSlice<'a, R>) -> T 17 | where 18 | T: Zero + Add, 19 | for<'l, 'r> &'l L: Mul<&'r R, Output = T>, 20 | { 21 | assert_eq!(a.size(), b.size()); 22 | let mut retval = None; 23 | for (a, b) in a.into_iter().zip(b) { 24 | let product = a * b; 25 | retval = Some(match retval { 26 | None => product, 27 | Some(v) => v + product, 28 | }); 29 | } 30 | retval.unwrap_or_else(Zero::zero) 31 | } 32 | 33 | pub(crate) fn gram_schmidt_calculate_column( 34 | input_basis: Array2DSlice, 35 | basis: &mut Array2DOwned>, 36 | column: usize, 37 | ) where 38 | T: Clone + Integer + NumAssign, 39 | { 40 | assert_eq!(input_basis.size(), basis.size()); 41 | assert!(column < basis.x_size()); 42 | for y in 0..basis.y_size() { 43 | basis[(column, y)] = Ratio::from_integer(input_basis[(column, y)].clone()); 44 | } 45 | for j in 0..column { 46 | let n = inner_product(basis.slice(column, ..), input_basis.slice(j, ..)); 47 | let d = inner_product(basis.slice(j, ..), basis.slice(j, ..)); 48 | if d.is_zero() { 49 | assert!(n.is_zero()); 50 | continue; 51 | } 52 | let factor = n / d; 53 | for y in 0..basis.y_size() { 54 | let v = &basis[(j, y)] * &factor; 55 | basis[(column, y)] -= v; 56 | } 57 | } 58 | } 59 | 60 | pub(crate) fn gram_schmidt(input_basis: Array2DSlice) -> Array2DOwned> 61 | where 62 | T: Clone + Integer + NumAssign, 63 | { 64 | let mut basis = Array2DOwned::new_with(input_basis.x_size(), input_basis.y_size(), Zero::zero); 65 | for i in 0..basis.x_size() { 66 | gram_schmidt_calculate_column(input_basis, &mut basis, i); 67 | } 68 | basis 69 | } 70 | 71 | struct LLLState { 72 | basis: Array2DOwned, 73 | orthogonal_basis: Array2DOwned>, 74 | orthogonal_basis_valid_columns: RangeTo, 75 | } 76 | 77 | impl LLLState 78 | where 79 | T: Clone + Integer + NumAssign, 80 | { 81 | fn new(basis: Array2DOwned) -> Self { 82 | LLLState { 83 | orthogonal_basis: Array2DOwned::new_with(basis.x_size(), basis.y_size(), Zero::zero), 84 | basis, 85 | orthogonal_basis_valid_columns: ..0, 86 | } 87 | } 88 | fn invalidate_column(&mut self, column: usize) { 89 | self.orthogonal_basis_valid_columns = 90 | ..(self.orthogonal_basis_valid_columns.end.min(column)); 91 | } 92 | fn calculate_column(&mut self, column: usize) { 93 | while !self.orthogonal_basis_valid_columns.contains(&column) { 94 | gram_schmidt_calculate_column( 95 | self.basis.slice(.., ..), 96 | &mut self.orthogonal_basis, 97 | self.orthogonal_basis_valid_columns.end, 98 | ); 99 | self.orthogonal_basis_valid_columns.end += 1; 100 | } 101 | } 102 | fn mu(&mut self, i: usize, j: usize) -> Ratio { 103 | self.calculate_column(j); 104 | inner_product(self.orthogonal_basis.slice(j, ..), self.basis.slice(i, ..)) 105 | / inner_product( 106 | self.orthogonal_basis.slice(j, ..), 107 | self.orthogonal_basis.slice(j, ..), 108 | ) 109 | } 110 | } 111 | 112 | pub(crate) fn lll_reduce_with_delta(basis: Array2DOwned, delta: Ratio) -> Array2DOwned 113 | where 114 | T: Clone + Integer + NumAssign + NumRef + FromPrimitive + Signed, 115 | { 116 | // algorithm from https://en.wikipedia.org/wiki/Lenstra–Lenstra–Lovász_lattice_basis_reduction_algorithm#LLL_algorithm 117 | 118 | let one_half = Ratio::new(T::one(), T::from_i32(2).expect("can't convert 2 to T")); 119 | let mut state = LLLState::new(basis); 120 | let mut k = 1; 121 | while k < state.basis.x_size() { 122 | for j in (0..k).rev() { 123 | let mu_k_j = state.mu(k, j); 124 | if mu_k_j.clone().abs() > one_half { 125 | let mut rounded_mu_k_j = mu_k_j.clone().floor().to_integer(); 126 | match (mu_k_j - rounded_mu_k_j.clone()).cmp(&one_half) { 127 | Ordering::Equal => { 128 | if rounded_mu_k_j.is_odd() { 129 | rounded_mu_k_j += T::one(); 130 | } 131 | } 132 | Ordering::Greater => rounded_mu_k_j += T::one(), 133 | Ordering::Less => {} 134 | } 135 | state.invalidate_column(k); 136 | for y in 0..state.basis.y_size() { 137 | let v = state.basis[(j, y)].clone() * &rounded_mu_k_j; 138 | state.basis[(k, y)] -= v; 139 | } 140 | } 141 | } 142 | let mu_k_km1 = state.mu(k, k - 1); 143 | state.calculate_column(k - 1); 144 | let km1_inner_product = inner_product( 145 | state.orthogonal_basis.slice(k - 1, ..), 146 | state.orthogonal_basis.slice(k - 1, ..), 147 | ); 148 | state.calculate_column(k); 149 | let k_inner_product = inner_product( 150 | state.orthogonal_basis.slice(k, ..), 151 | state.orthogonal_basis.slice(k, ..), 152 | ); 153 | if k_inner_product >= (delta.clone() - mu_k_km1.clone() * mu_k_km1) * km1_inner_product { 154 | k += 1; 155 | } else { 156 | state.invalidate_column(k); 157 | state.invalidate_column(k - 1); 158 | for y in 0..state.basis.y_size() { 159 | state.basis.swap_elements((k, y), (k - 1, y)); 160 | } 161 | k = 1.max(k - 1); 162 | } 163 | } 164 | state.basis 165 | } 166 | 167 | pub(crate) fn lll_reduce(basis: Array2DOwned) -> Array2DOwned 168 | where 169 | T: Clone + Integer + NumAssign + NumRef + FromPrimitive + Signed, 170 | { 171 | lll_reduce_with_delta( 172 | basis, 173 | Ratio::new( 174 | T::from_i32(3).expect("can't convert 3 to T"), 175 | T::from_i32(4).expect("can't convert 4 to T"), 176 | ), 177 | ) 178 | } 179 | 180 | #[cfg(test)] 181 | mod tests { 182 | use super::*; 183 | use num_bigint::BigInt; 184 | use num_traits::One; 185 | 186 | #[test] 187 | fn test_gram_schmidt() { 188 | let i = |v: i64| BigInt::from(v); 189 | let r = |n: i64, d: i64| Ratio::::new(n.into(), d.into()); 190 | 191 | let input = Array2DOwned::from_array( 192 | 3, 193 | 4, 194 | vec![ 195 | i(1), 196 | i(2), 197 | i(3), 198 | i(4), 199 | i(23), 200 | i(34), 201 | i(456), 202 | i(0), 203 | i(23), 204 | i(36), 205 | i(15), 206 | i(2), 207 | ], 208 | ); 209 | println!("input:\n{:#}", input); 210 | let expected = Array2DOwned::from_array( 211 | 3, 212 | 4, 213 | vec![ 214 | r(1, 1), 215 | r(2, 1), 216 | r(3, 1), 217 | r(4, 1), 218 | r(-769, 30), 219 | r(-949, 15), 220 | r(3101, 10), 221 | r(-2918, 15), 222 | r(76_229_372, 4_159_949), 223 | r(111_361_550, 4_159_949), 224 | r(-12_148_176, 4_159_949), 225 | r(-65_626_986, 4_159_949), 226 | ], 227 | ); 228 | println!("expected:\n{:#}", expected); 229 | let output = gram_schmidt(input.slice(.., ..)); 230 | println!("output:\n{:#}", output); 231 | assert!(output == expected); 232 | 233 | let input = Array2DOwned::from_array( 234 | 4, 235 | 4, 236 | vec![ 237 | i(-243), 238 | i(-234), 239 | i(-2), 240 | i(-5), 241 | i(235), 242 | i(2), 243 | i(4), 244 | i(6), 245 | i(0), 246 | i(36), 247 | i(-5), 248 | i(2), 249 | i(1), 250 | i(-1), 251 | i(1), 252 | i(-1), 253 | ], 254 | ); 255 | println!("input:\n{:#}", input); 256 | let expected = Array2DOwned::from_array( 257 | 4, 258 | 4, 259 | vec![ 260 | r(-243, 1), 261 | r(-234, 1), 262 | r(-2, 1), 263 | r(-5, 1), 264 | r(12_751_517, 113_834), 265 | r(-6_626_653, 56917), 266 | r(170_057, 56917), 267 | r(394_949, 113_834), 268 | r(70_966_960, 2_973_830_033), 269 | r(-94_068_796, 2_973_830_033), 270 | r(-13_881_031_493, 2_973_830_033), 271 | r(6_505_837_994, 2_973_830_033), 272 | r(1_249_570_482, 79_030_356_797), 273 | r(-353_698_923, 79_030_356_797), 274 | r(-2_489_783_076, 11_290_050_971), 275 | r(-1_958_138_064, 4_159_492_463), 276 | ], 277 | ); 278 | println!("expected:\n{:#}", expected); 279 | let output = gram_schmidt(input.slice(.., ..)); 280 | println!("output:\n{:#}", output); 281 | assert!(output == expected); 282 | } 283 | 284 | #[test] 285 | fn test_lll_reduce() { 286 | let ints = |v: &[i64]| -> Vec { v.iter().copied().map(BigInt::from).collect() }; 287 | 288 | let input = Array2DOwned::from_array( 289 | 4, 290 | 4, 291 | ints(&[1, 99, 91, 8, 12, 91, 87, 85, 69, 74, 96, 31, 56, 35, 13, 60]), 292 | ); 293 | println!("input:\n{:#}", input); 294 | let expected = Array2DOwned::from_array( 295 | 4, 296 | 4, 297 | ints(&[ 298 | 11, -8, -4, 77, 68, -25, 5, 23, 45, 43, 17, -17, -32, -4, 66, -12, 299 | ]), 300 | ); 301 | println!("expected:\n{:#}", expected); 302 | let output = lll_reduce(input); 303 | println!("output:\n{:#}", output); 304 | assert!(output == expected); 305 | 306 | let input = 307 | Array2DOwned::from_array(3, 3, ints(&[27, 301, 408, 926, 155, 210, 814, 336, 94])); 308 | println!("input:\n{:#}", input); 309 | let expected = 310 | Array2DOwned::from_array(3, 3, ints(&[-112, 181, -116, 27, 301, 408, 675, 216, -430])); 311 | println!("expected:\n{:#}", expected); 312 | let output = lll_reduce(input); 313 | println!("output:\n{:#}", output); 314 | assert!(output == expected); 315 | 316 | // find the minimal polynomial of sin(pi / 7) 317 | let multiplier = BigInt::one() << 48i32; 318 | // approximation to 1024 fractional bits 319 | let sin_pi_7_approximation: Ratio = 320 | "97498727392503287796421964844598099607650972550809391824625445149289352\ 321 | 685085470974709559364481363509368111361275396392010311843690916990724483522132\ 322 | 640931028212023467353916861241362846244259728556581827622758966595936283678031\ 323 | 989009141225359110201687206674081123626214905851516178176527528516219901977479\ 324 | 11\ 325 | /\ 326 | 224711641857789488466163148848628091702247122367788321591787601447165844756\ 327 | 876203915885596653009420026400142349839241697073487211018020778116059288299342\ 328 | 655472209866781081856595377774501557617649316353690106257211047688352928078601\ 329 | 84239138817603404645418813835573287279993405742309964538104419541203028017152" 330 | .parse() 331 | .unwrap(); 332 | let degree = 7; 333 | let input = Array2DOwned::new_with_positions(degree, degree + 1, |x, y| { 334 | if y < degree { 335 | if x == y { 336 | BigInt::one() 337 | } else { 338 | BigInt::zero() 339 | } 340 | } else { 341 | -(&sin_pi_7_approximation) 342 | .pow(x as i32) 343 | .mul(&multiplier) 344 | .round() 345 | .to_integer() 346 | } 347 | }); 348 | println!("input:\n{:#}", input); 349 | let expected = Array2DOwned::from_array( 350 | 7, 351 | 8, 352 | ints(&[ 353 | 5, -5, 1, -40, -1, 51, -75, -11, // 354 | -20, 41, 44, -31, -57, -98, -2, 8, // 355 | 3, 49, -106, -45, -8, -16, -15, 2, // 356 | // 357 | // basis vector with the minimal polynomial: 358 | // -7 + 56*x^2 - 112*x^4 + 64*x^6 359 | 7, 0, -56, 0, 112, 0, -64, 40, // 360 | -17, 26, 3, 89, -28, -47, -60, 10, // 361 | -6, 20, -12, 11, -6, -51, -48, -103, // 362 | -55, 94, 71, -19, 65, -8, 33, -33, // 363 | ]), 364 | ); 365 | println!("expected:\n{:#}", expected); 366 | let output = lll_reduce(input); 367 | println!("output:\n{:#}", output); 368 | assert!(output == expected); 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-2.1-or-later 2 | // See Notices.txt for copyright information 3 | 4 | #[macro_use] 5 | extern crate lazy_static; 6 | 7 | pub mod algebraic_numbers; 8 | pub(crate) mod array2d; 9 | pub mod interval_arithmetic; 10 | pub(crate) mod lattice; 11 | pub mod mod_int; 12 | pub mod polynomial; 13 | pub mod prelude; 14 | pub mod python; 15 | pub(crate) mod quadratic_numbers; 16 | pub mod traits; 17 | pub mod util; 18 | 19 | pub use algebraic_numbers::RealAlgebraicNumber; 20 | 21 | macro_rules! doctest { 22 | ($x:expr) => { 23 | #[allow(unused_doc_comments)] 24 | #[doc = $x] 25 | extern "C" {} 26 | }; 27 | } 28 | 29 | doctest!(include_str!("../README.md")); 30 | -------------------------------------------------------------------------------- /src/polynomial/add_sub.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-2.1-or-later 2 | // See Notices.txt for copyright information 3 | 4 | // use super::util::{pairwise_op_eq_move, pairwise_op_eq_ref, pairwise_op_ref_ref}; 5 | use crate::{ 6 | polynomial::{Polynomial, PolynomialCoefficient}, 7 | traits::{ExactDiv, GCDAndLCM, GCD}, 8 | }; 9 | use num_traits::{CheckedAdd, CheckedSub, One, Zero}; 10 | use std::{ 11 | borrow::Cow, 12 | mem, 13 | ops::{Add, AddAssign, Neg, Sub, SubAssign}, 14 | }; 15 | 16 | fn add_sub_assign( 17 | lhs: &mut Polynomial, 18 | rhs: &Polynomial, 19 | add_sub_assign: AddSubAssign, 20 | ) { 21 | let source_element = match lhs.elements.first().or_else(|| rhs.elements.first()) { 22 | Some(v) => v, 23 | None => return, 24 | }; 25 | let GCDAndLCM { gcd, lcm: divisor } = lhs.divisor.gcd_lcm(&rhs.divisor); 26 | let lhs_divisor = mem::replace(&mut lhs.divisor, divisor); 27 | let lhs_multiplier = T::divisor_to_element( 28 | Cow::Owned(rhs.divisor.clone().exact_div(&gcd)), 29 | Cow::Borrowed(source_element), 30 | ); 31 | let rhs_multiplier = T::divisor_to_element( 32 | Cow::Owned(ExactDiv::::exact_div(lhs_divisor, gcd)), 33 | Cow::Borrowed(source_element), 34 | ); 35 | while lhs.len() < rhs.len() { 36 | lhs.elements 37 | .push(T::make_zero_element(Cow::Borrowed(&rhs.elements[0]))); 38 | } 39 | for (index, lhs_element) in lhs.elements.iter_mut().enumerate() { 40 | *lhs_element *= &lhs_multiplier; 41 | if let Some(rhs_element) = rhs.elements.get(index) { 42 | add_sub_assign(lhs_element, rhs_element.clone() * &rhs_multiplier); 43 | } 44 | } 45 | lhs.normalize(); 46 | } 47 | 48 | fn add_sub_assign_single< 49 | T: PolynomialCoefficient, 50 | AddSubAssign: Fn(&mut T::Element, T::Element), 51 | >( 52 | lhs: &mut Polynomial, 53 | rhs: Cow, 54 | add_sub_assign: AddSubAssign, 55 | ) { 56 | let (rhs_numerator, rhs_divisor) = T::coefficient_to_element(rhs); 57 | let GCDAndLCM { gcd, lcm: divisor } = lhs.divisor.gcd_lcm(&rhs_divisor); 58 | let lhs_divisor = mem::replace(&mut lhs.divisor, divisor); 59 | let lhs_multiplier = T::divisor_to_element( 60 | Cow::Owned(lhs_divisor * &gcd), 61 | Cow::Borrowed(&rhs_numerator), 62 | ); 63 | let rhs_multiplier = T::divisor_to_element( 64 | Cow::Owned(gcd * &rhs_divisor), 65 | Cow::Borrowed(&rhs_numerator), 66 | ); 67 | if lhs.is_empty() { 68 | lhs.elements 69 | .push(T::make_zero_element(Cow::Borrowed(&rhs_numerator))); 70 | } 71 | lhs.elements[0] *= &lhs_multiplier; 72 | add_sub_assign(&mut lhs.elements[0], rhs_numerator * &rhs_multiplier); 73 | lhs.normalize(); 74 | } 75 | 76 | impl AddAssign for Polynomial { 77 | fn add_assign(&mut self, rhs: Polynomial) { 78 | add_sub_assign(self, &rhs, AddAssign::::add_assign); 79 | } 80 | } 81 | 82 | impl<'a, T: PolynomialCoefficient> AddAssign<&'a Polynomial> for Polynomial { 83 | fn add_assign(&mut self, rhs: &Polynomial) { 84 | add_sub_assign(self, rhs, AddAssign::::add_assign); 85 | } 86 | } 87 | 88 | impl AddAssign for Polynomial { 89 | fn add_assign(&mut self, rhs: T) { 90 | add_sub_assign_single(self, Cow::Owned(rhs), AddAssign::::add_assign); 91 | } 92 | } 93 | 94 | impl<'a, T: PolynomialCoefficient> AddAssign<&'a T> for Polynomial { 95 | fn add_assign(&mut self, rhs: &T) { 96 | add_sub_assign_single( 97 | self, 98 | Cow::Borrowed(rhs), 99 | AddAssign::::add_assign, 100 | ); 101 | } 102 | } 103 | 104 | impl Add for Polynomial { 105 | type Output = Polynomial; 106 | fn add(mut self, rhs: Polynomial) -> Self::Output { 107 | self += rhs; 108 | self 109 | } 110 | } 111 | 112 | impl<'a, T: PolynomialCoefficient> Add<&'a Polynomial> for Polynomial { 113 | type Output = Polynomial; 114 | fn add(mut self, rhs: &Polynomial) -> Self::Output { 115 | self += rhs; 116 | self 117 | } 118 | } 119 | 120 | impl<'a, T: PolynomialCoefficient> Add> for &'a Polynomial { 121 | type Output = Polynomial; 122 | fn add(self, mut rhs: Polynomial) -> Self::Output { 123 | rhs += self; 124 | rhs 125 | } 126 | } 127 | 128 | impl<'a, T: PolynomialCoefficient> Add for &'a Polynomial { 129 | type Output = Polynomial; 130 | fn add(self, rhs: Self) -> Self::Output { 131 | let mut retval = self.clone(); 132 | retval += rhs; 133 | retval 134 | } 135 | } 136 | 137 | impl Add for Polynomial { 138 | type Output = Polynomial; 139 | fn add(mut self, rhs: T) -> Self::Output { 140 | self += rhs; 141 | self 142 | } 143 | } 144 | 145 | impl<'a, T: PolynomialCoefficient> Add<&'a T> for Polynomial { 146 | type Output = Polynomial; 147 | fn add(mut self, rhs: &T) -> Self::Output { 148 | self += rhs; 149 | self 150 | } 151 | } 152 | 153 | impl<'a, T: PolynomialCoefficient> Add for &'a Polynomial { 154 | type Output = Polynomial; 155 | fn add(self, rhs: T) -> Self::Output { 156 | let mut retval = self.clone(); 157 | retval += rhs; 158 | retval 159 | } 160 | } 161 | 162 | impl<'a, T: PolynomialCoefficient> Add<&'a T> for &'a Polynomial { 163 | type Output = Polynomial; 164 | fn add(self, rhs: &T) -> Self::Output { 165 | let mut retval = self.clone(); 166 | retval += rhs; 167 | retval 168 | } 169 | } 170 | 171 | impl CheckedAdd for Polynomial { 172 | fn checked_add(&self, rhs: &Self) -> Option { 173 | Some(self + rhs) 174 | } 175 | } 176 | 177 | impl Zero for Polynomial { 178 | fn zero() -> Self { 179 | Default::default() 180 | } 181 | fn set_zero(&mut self) { 182 | self.elements.clear(); 183 | self.divisor.set_one(); 184 | } 185 | fn is_zero(&self) -> bool { 186 | self.is_empty() 187 | } 188 | } 189 | 190 | impl SubAssign for Polynomial { 191 | fn sub_assign(&mut self, rhs: Polynomial) { 192 | add_sub_assign(self, &rhs, SubAssign::::sub_assign); 193 | } 194 | } 195 | 196 | impl<'a, T: PolynomialCoefficient> SubAssign<&'a Polynomial> for Polynomial { 197 | fn sub_assign(&mut self, rhs: &Polynomial) { 198 | add_sub_assign(self, rhs, SubAssign::::sub_assign); 199 | } 200 | } 201 | 202 | impl SubAssign for Polynomial { 203 | fn sub_assign(&mut self, rhs: T) { 204 | add_sub_assign_single(self, Cow::Owned(rhs), SubAssign::::sub_assign); 205 | } 206 | } 207 | 208 | impl<'a, T: PolynomialCoefficient> SubAssign<&'a T> for Polynomial { 209 | fn sub_assign(&mut self, rhs: &T) { 210 | add_sub_assign_single( 211 | self, 212 | Cow::Borrowed(rhs), 213 | SubAssign::::sub_assign, 214 | ); 215 | } 216 | } 217 | 218 | impl Sub for Polynomial { 219 | type Output = Polynomial; 220 | fn sub(mut self, rhs: Polynomial) -> Self::Output { 221 | self -= rhs; 222 | self 223 | } 224 | } 225 | 226 | impl<'a, T: PolynomialCoefficient> Sub<&'a Polynomial> for Polynomial { 227 | type Output = Polynomial; 228 | fn sub(mut self, rhs: &Polynomial) -> Self::Output { 229 | self -= rhs; 230 | self 231 | } 232 | } 233 | 234 | impl<'a, T: PolynomialCoefficient> Sub> for &'a Polynomial { 235 | type Output = Polynomial; 236 | fn sub(self, rhs: Polynomial) -> Self::Output { 237 | let mut lhs = self.clone(); 238 | lhs -= rhs; 239 | lhs 240 | } 241 | } 242 | 243 | impl<'a, T: PolynomialCoefficient> Sub for &'a Polynomial { 244 | type Output = Polynomial; 245 | fn sub(self, rhs: Self) -> Self::Output { 246 | let mut lhs = self.clone(); 247 | lhs -= rhs; 248 | lhs 249 | } 250 | } 251 | 252 | impl Sub for Polynomial { 253 | type Output = Polynomial; 254 | fn sub(mut self, rhs: T) -> Self::Output { 255 | self -= rhs; 256 | self 257 | } 258 | } 259 | 260 | impl<'a, T: PolynomialCoefficient> Sub<&'a T> for Polynomial { 261 | type Output = Polynomial; 262 | fn sub(mut self, rhs: &T) -> Self::Output { 263 | self -= rhs; 264 | self 265 | } 266 | } 267 | 268 | impl<'a, T: PolynomialCoefficient> Sub for &'a Polynomial { 269 | type Output = Polynomial; 270 | fn sub(self, rhs: T) -> Self::Output { 271 | let mut lhs = self.clone(); 272 | lhs -= rhs; 273 | lhs 274 | } 275 | } 276 | 277 | impl<'a, T: PolynomialCoefficient> Sub<&'a T> for &'a Polynomial { 278 | type Output = Polynomial; 279 | fn sub(self, rhs: &T) -> Self::Output { 280 | let mut lhs = self.clone(); 281 | lhs -= rhs; 282 | lhs 283 | } 284 | } 285 | 286 | impl CheckedSub for Polynomial { 287 | fn checked_sub(&self, rhs: &Self) -> Option { 288 | Some(self - rhs) 289 | } 290 | } 291 | 292 | impl Neg for Polynomial { 293 | type Output = Polynomial; 294 | fn neg(self) -> Polynomial { 295 | self.into_iter().map(Neg::neg).collect::>().into() 296 | } 297 | } 298 | 299 | impl Neg for &'_ Polynomial { 300 | type Output = Polynomial; 301 | fn neg(self) -> Polynomial { 302 | self.into_iter().map(Neg::neg).collect::>().into() 303 | } 304 | } 305 | 306 | #[cfg(test)] 307 | mod tests { 308 | use super::*; 309 | use crate::util::tests::test_op_helper; 310 | use num_rational::Ratio; 311 | #[test] 312 | fn test_add() { 313 | let test = |l: Polynomial, r: Polynomial, expected: &Polynomial| { 314 | test_op_helper( 315 | l, 316 | r, 317 | expected, 318 | |l, r| *l += r, 319 | |l, r| *l += r, 320 | |l, r| l + r, 321 | |l, r| l + r, 322 | |l, r| l + r, 323 | |l, r| l + r, 324 | ); 325 | }; 326 | test( 327 | vec![1, 2, 3, 4].into(), 328 | vec![5, 6, 7, 8].into(), 329 | &vec![6, 8, 10, 12].into(), 330 | ); 331 | test( 332 | vec![1, 2, 3, 4, -1].into(), 333 | vec![5, 6, 7, 8, 1].into(), 334 | &vec![6, 8, 10, 12].into(), 335 | ); 336 | } 337 | 338 | #[test] 339 | fn test_add_ratio() { 340 | let test = |l: Polynomial>, 341 | r: Polynomial>, 342 | expected: &Polynomial>| { 343 | test_op_helper( 344 | l, 345 | r, 346 | expected, 347 | |l, r| *l += r, 348 | |l, r| *l += r, 349 | |l, r| l + r, 350 | |l, r| l + r, 351 | |l, r| l + r, 352 | |l, r| l + r, 353 | ); 354 | }; 355 | let r = |n: i64, d: i64| Ratio::new(n, d); 356 | test( 357 | vec![r(1, 3), r(2, 3), r(3, 3), r(4, 3)].into(), 358 | vec![r(8, 5), r(7, 5), r(6, 5), r(5, 5)].into(), 359 | &vec![r(29, 15), r(31, 15), r(11, 5), r(7, 3)].into(), 360 | ); 361 | test( 362 | vec![r(1, 6), r(2, 6), r(3, 6), r(4, 6), r(10, 6)].into(), 363 | vec![r(8, 8), r(7, 8), r(6, 8), r(5, 8), r(10, 8)].into(), 364 | &vec![r(7, 6), r(29, 24), r(5, 4), r(31, 24), r(35, 12)].into(), 365 | ); 366 | } 367 | 368 | #[test] 369 | fn test_sub() { 370 | let test = |l: Polynomial, r: Polynomial, expected: &Polynomial| { 371 | test_op_helper( 372 | l, 373 | r, 374 | expected, 375 | |l, r| *l -= r, 376 | |l, r| *l -= r, 377 | |l, r| l - r, 378 | |l, r| l - r, 379 | |l, r| l - r, 380 | |l, r| l - r, 381 | ); 382 | }; 383 | test( 384 | vec![1, 2, 3, 4].into(), 385 | vec![8, 7, 6, 5].into(), 386 | &vec![-7, -5, -3, -1].into(), 387 | ); 388 | test( 389 | vec![1, 2, 3, 4, 10].into(), 390 | vec![8, 7, 6, 5, 10].into(), 391 | &vec![-7, -5, -3, -1].into(), 392 | ); 393 | } 394 | 395 | #[test] 396 | fn test_sub_ratio() { 397 | let test = |l: Polynomial>, 398 | r: Polynomial>, 399 | expected: &Polynomial>| { 400 | test_op_helper( 401 | l, 402 | r, 403 | expected, 404 | |l, r| *l -= r, 405 | |l, r| *l -= r, 406 | |l, r| l - r, 407 | |l, r| l - r, 408 | |l, r| l - r, 409 | |l, r| l - r, 410 | ); 411 | }; 412 | let r = |n: i64, d: i64| Ratio::new(n, d); 413 | test( 414 | vec![r(1, 3), r(2, 3), r(3, 3), r(4, 3)].into(), 415 | vec![r(8, 5), r(7, 5), r(6, 5), r(5, 5)].into(), 416 | &vec![r(-19, 15), r(-11, 15), r(-1, 5), r(1, 3)].into(), 417 | ); 418 | test( 419 | vec![r(1, 6), r(2, 6), r(3, 6), r(4, 6), r(10, 6)].into(), 420 | vec![r(8, 8), r(7, 8), r(6, 8), r(5, 8), r(10, 8)].into(), 421 | &vec![r(-5, 6), r(-13, 24), r(-1, 4), r(1, 24), r(5, 12)].into(), 422 | ); 423 | } 424 | } 425 | -------------------------------------------------------------------------------- /src/polynomial/distinct_degree_factorization.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-2.1-or-later 2 | // See Notices.txt for copyright information 3 | 4 | use crate::{ 5 | mod_int::{ModularInteger, ModularReducePow, Modulus, PrimeModulus}, 6 | polynomial::Polynomial, 7 | traits::{ExtendedGCD, GCD}, 8 | }; 9 | use num_integer::Integer; 10 | use num_traits::Zero; 11 | use std::{fmt, hash::Hash}; 12 | 13 | impl Polynomial> 14 | where 15 | V: ModularReducePow + Integer + GCD + ExtendedGCD + fmt::Debug + Hash, 16 | M: PrimeModulus + fmt::Debug + Hash, 17 | { 18 | pub(crate) fn distinct_degree_factorization(mut self) -> Vec>> { 19 | let nonzero_highest_power_coefficient = match self.nonzero_highest_power_coefficient() { 20 | None => return vec![Polynomial::zero()], 21 | Some(v) => v, 22 | }; 23 | let one_coefficient = ModularInteger::new( 24 | V::one(), 25 | nonzero_highest_power_coefficient.modulus().clone(), 26 | ); 27 | let x = Polynomial::make_monomial(one_coefficient, 1); 28 | let characteristic = nonzero_highest_power_coefficient.modulus().into_modulus(); 29 | self /= &nonzero_highest_power_coefficient; // convert to monic 30 | let mut retval = vec![nonzero_highest_power_coefficient.into()]; 31 | let mut power = x.powmod(characteristic.clone(), &self); 32 | for _ in 0..self.len() { 33 | if self.len() <= 1 { 34 | return retval; 35 | } 36 | let next_factor = (&power - &x).gcd(&self); 37 | self /= &next_factor; 38 | retval.push(next_factor); 39 | power = power.powmod(characteristic.clone(), &self); 40 | } 41 | unreachable!("failed to find all factors") 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | use crate::mod_int::KnownPrime; 49 | 50 | #[test] 51 | fn test_distinct_degree_factorization() { 52 | fn make_poly( 53 | poly: &[i32], 54 | modulus: KnownPrime, 55 | ) -> Polynomial>> { 56 | poly.iter() 57 | .map(|&coefficient| ModularInteger::new(coefficient, modulus)) 58 | .collect() 59 | } 60 | fn test_case(poly: &[i32], expected_factors: &[&[i32]], modulus: KnownPrime) { 61 | let poly = make_poly(poly, modulus); 62 | let expected_factors: Vec<_> = expected_factors 63 | .iter() 64 | .map(|poly| make_poly(*poly, modulus)) 65 | .collect(); 66 | println!("poly: {}", poly); 67 | println!("expected_factors:"); 68 | for factor in &expected_factors { 69 | println!(" {}", factor); 70 | } 71 | let factors = poly.distinct_degree_factorization(); 72 | println!("factors:"); 73 | for factor in &factors { 74 | println!(" {}", factor); 75 | } 76 | assert!(expected_factors == factors); 77 | } 78 | test_case( 79 | &[5, 0, 6, 6, 0, 0, 6, 0, 4, 0, 5, 6, 3, 3], 80 | &[ 81 | &[3], 82 | &[6, 4, 6, 1], 83 | &[6, 3, 2, 4, 1], 84 | &[4, 0, 0, 2, 6, 5, 1], 85 | ], 86 | KnownPrime::new_unsafe(7), 87 | ); 88 | test_case( 89 | &[4, 2, 0, 0, 2, 2, 4, 6, 0, 5, 1, 3, 6, 3], 90 | &[ 91 | &[3], 92 | &[1], 93 | &[1], 94 | &[1], 95 | &[1], 96 | &[1], 97 | &[1], 98 | &[1], 99 | &[1], 100 | &[1], 101 | &[1], 102 | &[1], 103 | &[1], 104 | &[6, 3, 0, 0, 3, 3, 6, 2, 0, 4, 5, 1, 2, 1], 105 | ], 106 | KnownPrime::new_unsafe(7), 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/polynomial/factorization_over_integers.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-2.1-or-later 2 | // See Notices.txt for copyright information 3 | 4 | use crate::{ 5 | mod_int::{KnownOddPrime, ModularInteger, Modulus}, 6 | polynomial::{ 7 | Polynomial, PolynomialCoefficient, PolynomialDivSupported, PolynomialFactor, 8 | PolynomialFactors, PolynomialReducingFactorSupported, 9 | }, 10 | traits::{ExactDiv, ExtendedGCD, ExtendedGCDResult}, 11 | util::{ 12 | for_subsets_of_size, next_prime_i32, ContinueBreak, LeafOrNodePair, PrintTree, 13 | PrintTreeData, 14 | }, 15 | }; 16 | use num_bigint::BigInt; 17 | use num_integer::Integer; 18 | use num_rational::Ratio; 19 | use num_traits::{One, Signed}; 20 | use rand::{Rng, SeedableRng}; 21 | use rand_pcg::Pcg64Mcg; 22 | use std::{ 23 | cmp::Ordering, 24 | collections::BinaryHeap, 25 | fmt, mem, 26 | ops::{Add, AddAssign}, 27 | }; 28 | 29 | struct FactorTreeInteriorNode { 30 | left: FactorTreeNode, 31 | right: FactorTreeNode, 32 | total_degree: usize, 33 | product: Polynomial, 34 | left_bezout_coefficient: Polynomial, 35 | right_bezout_coefficient: Polynomial, 36 | } 37 | 38 | impl FactorTreeInteriorNode { 39 | fn new(left: FactorTreeNode, right: FactorTreeNode) -> Self 40 | where 41 | T: PolynomialDivSupported + PolynomialReducingFactorSupported, 42 | { 43 | let total_degree = left.total_degree() + right.total_degree(); 44 | let left_product = left.product(); 45 | let right_product = right.product(); 46 | let product = left_product * right_product; 47 | let ExtendedGCDResult { 48 | gcd, 49 | x: left_bezout_coefficient, 50 | y: right_bezout_coefficient, 51 | } = left_product.extended_gcd(right_product); 52 | assert!(gcd.is_one()); 53 | assert!(left_bezout_coefficient.len() < right_product.len()); 54 | assert!(right_bezout_coefficient.len() < left_product.len()); 55 | Self { 56 | left, 57 | right, 58 | total_degree, 59 | product, 60 | left_bezout_coefficient, 61 | right_bezout_coefficient, 62 | } 63 | } 64 | fn map_coefficients O>( 65 | &self, 66 | f: &mut F, 67 | ) -> FactorTreeInteriorNode { 68 | FactorTreeInteriorNode { 69 | left: self.left.map_coefficients(f), 70 | right: self.right.map_coefficients(f), 71 | total_degree: self.total_degree, 72 | product: self.product.iter().map(&mut *f).collect(), 73 | left_bezout_coefficient: self.left_bezout_coefficient.iter().map(&mut *f).collect(), 74 | right_bezout_coefficient: self.right_bezout_coefficient.iter().map(&mut *f).collect(), 75 | } 76 | } 77 | fn into_leaves(self, leaves: &mut Vec>) { 78 | self.left.into_leaves(leaves); 79 | self.right.into_leaves(leaves); 80 | } 81 | } 82 | 83 | struct FactorTreeLeafNode { 84 | factor: Polynomial, 85 | } 86 | 87 | impl FactorTreeLeafNode { 88 | fn total_degree(&self) -> usize { 89 | self.factor.degree().expect("non-zero factor") 90 | } 91 | fn map_coefficients O>( 92 | &self, 93 | f: &mut F, 94 | ) -> FactorTreeLeafNode { 95 | FactorTreeLeafNode { 96 | factor: self.factor.iter().map(f).collect(), 97 | } 98 | } 99 | fn into_leaves(self, leaves: &mut Vec>) { 100 | leaves.push(self.factor); 101 | } 102 | } 103 | 104 | enum FactorTreeNode { 105 | Interior(Box>), 106 | Leaf(FactorTreeLeafNode), 107 | } 108 | 109 | impl FactorTreeNode { 110 | fn total_degree(&self) -> usize { 111 | match self { 112 | Self::Interior(node) => node.total_degree, 113 | Self::Leaf(node) => node.total_degree(), 114 | } 115 | } 116 | fn product(&self) -> &Polynomial { 117 | match self { 118 | Self::Interior(node) => &node.product, 119 | Self::Leaf(node) => &node.factor, 120 | } 121 | } 122 | fn map_coefficients O>( 123 | &self, 124 | f: &mut F, 125 | ) -> FactorTreeNode { 126 | match self { 127 | FactorTreeNode::Interior(node) => { 128 | FactorTreeNode::Interior(node.map_coefficients(f).into()) 129 | } 130 | FactorTreeNode::Leaf(node) => FactorTreeNode::Leaf(node.map_coefficients(f)), 131 | } 132 | } 133 | fn into_leaves(self, leaves: &mut Vec>) { 134 | match self { 135 | Self::Interior(node) => node.into_leaves(leaves), 136 | Self::Leaf(node) => node.into_leaves(leaves), 137 | } 138 | } 139 | } 140 | 141 | impl fmt::Display for FactorTreeLeafNode { 142 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 143 | let FactorTreeLeafNode { factor } = self; 144 | write!(f, "({})", factor) 145 | } 146 | } 147 | 148 | impl fmt::Display for FactorTreeInteriorNode { 149 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 150 | let FactorTreeInteriorNode { 151 | left: _left, 152 | right: _right, 153 | total_degree, 154 | product, 155 | left_bezout_coefficient, 156 | right_bezout_coefficient, 157 | } = self; 158 | write!( 159 | f, 160 | r"FactorTreeInteriorNode {{ 161 | left:.., right:.., 162 | total_degree:{total_degree}, 163 | product:({product}), 164 | left_bezout_coefficient:({left_bezout_coefficient}), 165 | right_bezout_coefficient:({right_bezout_coefficient}) 166 | }}", 167 | total_degree = total_degree, 168 | product = product, 169 | left_bezout_coefficient = left_bezout_coefficient, 170 | right_bezout_coefficient = right_bezout_coefficient, 171 | ) 172 | } 173 | } 174 | 175 | impl<'a, T: 'a + PolynomialCoefficient + fmt::Display> PrintTreeData<'a> for FactorTreeNode { 176 | type Leaf = &'a FactorTreeLeafNode; 177 | type NodeData = &'a FactorTreeInteriorNode; 178 | } 179 | 180 | impl PrintTree for FactorTreeNode { 181 | fn to_leaf_or_node_pair( 182 | &self, 183 | ) -> LeafOrNodePair<&FactorTreeLeafNode, &Self, &FactorTreeInteriorNode> { 184 | match self { 185 | Self::Interior(node) => LeafOrNodePair::NodePair(&node.left, &**node, &node.right), 186 | Self::Leaf(node) => LeafOrNodePair::Leaf(node), 187 | } 188 | } 189 | } 190 | 191 | impl Eq for FactorTreeNode {} 192 | 193 | impl PartialEq for FactorTreeNode { 194 | fn eq(&self, rhs: &Self) -> bool { 195 | self.cmp(rhs) == Ordering::Equal 196 | } 197 | } 198 | 199 | impl PartialOrd for FactorTreeNode { 200 | fn partial_cmp(&self, rhs: &Self) -> Option { 201 | Some(self.cmp(rhs)) 202 | } 203 | } 204 | 205 | impl Ord for FactorTreeNode { 206 | fn cmp(&self, rhs: &Self) -> Ordering { 207 | self.total_degree().cmp(&rhs.total_degree()).reverse() 208 | } 209 | } 210 | 211 | impl FactorTreeLeafNode> { 212 | fn hensel_lift_step( 213 | &mut self, 214 | _old_modulus: &BigInt, 215 | _new_modulus: &BigInt, 216 | new_product: Polynomial>, 217 | ) { 218 | self.factor = new_product; 219 | } 220 | } 221 | 222 | impl FactorTreeInteriorNode> { 223 | fn hensel_lift_step( 224 | &mut self, 225 | old_modulus: &BigInt, 226 | new_modulus: &BigInt, 227 | new_product: Polynomial>, 228 | ) { 229 | fn set_modulus>>( 230 | value: T, 231 | new_modulus: &BigInt, 232 | ) -> Polynomial> { 233 | value 234 | .into_iter() 235 | .map(Into::into) 236 | .map(|(value, _modulus)| ModularInteger::new(value, new_modulus.clone())) 237 | .collect() 238 | } 239 | let old_left_product = set_modulus(self.left.product(), new_modulus); 240 | let old_right_product = set_modulus(self.right.product(), new_modulus); 241 | let old_left_bezout_coefficient = 242 | set_modulus(mem::take(&mut self.left_bezout_coefficient), new_modulus); 243 | let old_right_bezout_coefficient = 244 | set_modulus(mem::take(&mut self.right_bezout_coefficient), new_modulus); 245 | let error = &new_product - &old_left_product * &old_right_product; 246 | let (quotient, remainder) = 247 | (&old_left_bezout_coefficient * &error).div_rem(&old_right_product); 248 | let left_product = 249 | &old_left_product + &old_right_bezout_coefficient * error + quotient * old_left_product; 250 | let right_product = old_right_product + remainder; 251 | let bezout_error = &old_left_bezout_coefficient * &left_product 252 | + &old_right_bezout_coefficient * &right_product 253 | - ModularInteger::new(BigInt::one(), new_modulus.clone()); 254 | let (quotient, remainder) = 255 | (&old_left_bezout_coefficient * &bezout_error).div_rem(&right_product); 256 | let new_left_bezout_coefficient = old_left_bezout_coefficient - remainder; 257 | let orbc_mul_bezout_error = &old_right_bezout_coefficient * bezout_error; 258 | let new_right_bezout_coefficient = 259 | old_right_bezout_coefficient - orbc_mul_bezout_error - quotient * &left_product; 260 | // println!("left_product: ({})", left_product); 261 | // println!("right_product: ({})", right_product); 262 | // println!("self.left.product(): ({})", self.left.product()); 263 | // println!("self.right.product(): ({})", self.right.product()); 264 | debug_assert!(&set_modulus(left_product.iter(), old_modulus) == self.left.product()); 265 | debug_assert!(&set_modulus(right_product.iter(), old_modulus) == self.right.product()); 266 | let check_bezout_coefficients = || { 267 | let bezout_identity = &new_left_bezout_coefficient * &left_product 268 | + &new_right_bezout_coefficient * &right_product; 269 | // println!("bezout_identity: ({})", bezout_identity); 270 | bezout_identity.is_one() 271 | }; 272 | debug_assert!(check_bezout_coefficients()); 273 | debug_assert!(&left_product * &right_product == new_product); 274 | self.product = new_product; 275 | self.left_bezout_coefficient = new_left_bezout_coefficient; 276 | self.right_bezout_coefficient = new_right_bezout_coefficient; 277 | self.left 278 | .hensel_lift_step(old_modulus, new_modulus, left_product); 279 | self.right 280 | .hensel_lift_step(old_modulus, new_modulus, right_product); 281 | self.total_degree = self.left.total_degree() + self.right.total_degree(); 282 | } 283 | } 284 | 285 | impl FactorTreeNode> { 286 | fn hensel_lift_step( 287 | &mut self, 288 | old_modulus: &BigInt, 289 | new_modulus: &BigInt, 290 | new_product: Polynomial>, 291 | ) { 292 | match self { 293 | Self::Leaf(node) => node.hensel_lift_step(old_modulus, new_modulus, new_product), 294 | Self::Interior(node) => node.hensel_lift_step(old_modulus, new_modulus, new_product), 295 | } 296 | } 297 | } 298 | 299 | #[derive(Clone, Debug)] 300 | enum ExactInexactInt { 301 | Exact { 302 | value: BigInt, 303 | }, 304 | 305 | /// represents the range lower_bound < x < lower_bound + 1 306 | Inexact { 307 | lower_bound: BigInt, 308 | }, 309 | } 310 | 311 | impl ExactInexactInt { 312 | fn new(value: BigInt) -> Self { 313 | Self::Exact { value } 314 | } 315 | fn sqrt(&self) -> Self { 316 | match self { 317 | Self::Exact { value } => { 318 | let sqrt = value.sqrt(); 319 | if &sqrt * &sqrt == *value { 320 | Self::Exact { value: sqrt } 321 | } else { 322 | Self::Inexact { lower_bound: sqrt } 323 | } 324 | } 325 | Self::Inexact { lower_bound } => Self::Inexact { 326 | lower_bound: lower_bound.sqrt(), 327 | }, 328 | } 329 | } 330 | } 331 | 332 | impl AddAssign for ExactInexactInt { 333 | fn add_assign(&mut self, rhs: i32) { 334 | match self { 335 | Self::Exact { value } => *value += rhs, 336 | Self::Inexact { lower_bound } => *lower_bound += rhs, 337 | } 338 | } 339 | } 340 | 341 | impl Add for ExactInexactInt { 342 | type Output = ExactInexactInt; 343 | fn add(mut self, rhs: i32) -> Self::Output { 344 | self += rhs; 345 | self 346 | } 347 | } 348 | 349 | impl Add for &'_ ExactInexactInt { 350 | type Output = ExactInexactInt; 351 | fn add(self, rhs: i32) -> Self::Output { 352 | let mut retval = self.clone(); 353 | retval += rhs; 354 | retval 355 | } 356 | } 357 | 358 | impl PartialEq for ExactInexactInt { 359 | fn eq(&self, rhs: &BigInt) -> bool { 360 | self.partial_cmp(rhs) == Some(Ordering::Equal) 361 | } 362 | } 363 | 364 | impl PartialEq for BigInt { 365 | fn eq(&self, rhs: &ExactInexactInt) -> bool { 366 | self.partial_cmp(rhs) == Some(Ordering::Equal) 367 | } 368 | } 369 | 370 | impl PartialOrd for ExactInexactInt { 371 | fn partial_cmp(&self, rhs: &BigInt) -> Option { 372 | match self { 373 | Self::Exact { value } => Some(value.cmp(rhs)), 374 | Self::Inexact { lower_bound } => { 375 | if *lower_bound >= *rhs { 376 | Some(Ordering::Greater) 377 | } else { 378 | Some(Ordering::Less) 379 | } 380 | } 381 | } 382 | } 383 | } 384 | 385 | impl PartialOrd for BigInt { 386 | fn partial_cmp(&self, rhs: &ExactInexactInt) -> Option { 387 | rhs.partial_cmp(self).map(Ordering::reverse) 388 | } 389 | } 390 | 391 | impl fmt::Display for ExactInexactInt { 392 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 393 | match self { 394 | ExactInexactInt::Exact { value } => write!(f, "{}", value), 395 | ExactInexactInt::Inexact { lower_bound } => { 396 | write!(f, "({}, {})", lower_bound, lower_bound + 1i32) 397 | } 398 | } 399 | } 400 | } 401 | 402 | impl Polynomial { 403 | fn factor_square_free_polynomial_with_rng( 404 | &self, 405 | rng: &mut R, 406 | ) -> Vec> { 407 | let degree = match self.degree() { 408 | None | Some(0) | Some(1) => return vec![self.clone()], 409 | Some(degree) => degree, 410 | }; 411 | let content = self.content(); 412 | let mut prime = 2; 413 | let (modular_polynomial, modulus) = loop { 414 | prime = 415 | next_prime_i32(prime).expect("polynomial too big to factor using this algorithm"); 416 | if self 417 | .elements 418 | .iter() 419 | .last() 420 | .expect("known to be non-empty") 421 | .is_multiple_of(&prime.into()) 422 | { 423 | // highest power coefficient would be zero, reducing the degree 424 | continue; 425 | } 426 | let modulus = KnownOddPrime::new_odd_prime_unsafe(prime); 427 | let converted_polynomial: Polynomial<_> = self 428 | .elements 429 | .iter() 430 | .map(|coefficient| ModularInteger::::from_bigint(&coefficient, modulus)) 431 | .collect(); 432 | debug_assert_eq!(converted_polynomial.degree(), Some(degree)); 433 | if converted_polynomial.is_square_free() { 434 | break (converted_polynomial, modulus); 435 | } 436 | }; 437 | // println!("modulus: {}", modulus); 438 | // println!("modular_polynomial: {}", modular_polynomial); 439 | let modular_factors: Vec<_> = modular_polynomial 440 | .distinct_degree_factorization() 441 | .into_iter() 442 | .enumerate() 443 | .flat_map(|(factor_degree, poly)| { 444 | if poly.is_one() { 445 | vec![] 446 | } else if factor_degree == 0 { 447 | vec![poly] 448 | } else { 449 | poly.same_degree_factorization(factor_degree, rng) 450 | } 451 | }) 452 | .map(|factor| FactorTreeNode::Leaf(FactorTreeLeafNode { factor })) 453 | .collect(); 454 | 455 | // println!("modular_factors:"); 456 | // for factor in &modular_factors { 457 | // if let FactorTreeNode::Leaf(leaf) = factor { 458 | // println!(" {}", leaf.factor); 459 | // } else { 460 | // unreachable!("known to be all leaf nodes"); 461 | // } 462 | // } 463 | 464 | let modular_factors_len = modular_factors.len(); 465 | 466 | // construct factor tree 467 | let mut factor_tree_heap: BinaryHeap<_> = modular_factors.into(); 468 | 469 | let factor_tree = loop { 470 | let smallest_factor_tree = match factor_tree_heap.pop() { 471 | None => { 472 | if content.is_one() { 473 | return vec![]; 474 | } else { 475 | return vec![content.into()]; 476 | } 477 | } 478 | Some(v) => v, 479 | }; 480 | let second_smallest_factor_tree = match factor_tree_heap.pop() { 481 | None => break smallest_factor_tree, 482 | Some(v) => v, 483 | }; 484 | factor_tree_heap.push(FactorTreeNode::Interior( 485 | FactorTreeInteriorNode::new(smallest_factor_tree, second_smallest_factor_tree) 486 | .into(), 487 | )) 488 | }; 489 | mem::drop(factor_tree_heap); 490 | let mut modulus = BigInt::from(modulus.into_modulus()); 491 | let mut factor_tree = factor_tree.map_coefficients(&mut |coefficient: ModularInteger< 492 | i32, 493 | KnownOddPrime, 494 | >| { 495 | ModularInteger::::new( 496 | BigInt::from(*coefficient.value()), 497 | modulus.clone(), 498 | ) 499 | }); 500 | 501 | // factor_limit from (2) in http://web.archive.org/web/20161221120512/https://dms.umontreal.ca/~andrew/PDF/BoundCoeff.pdf 502 | // original formula: 503 | // define norm_2(p) = sqrt(sum(p[i]^2, 0 <= i <= n)) 504 | // where p == p[0] + p[1]*x + p[2]*x^2 + ... + p[n]*x^n 505 | // where n is the degree of p 506 | // 507 | // for all polynomials g, p in Z[x] if g divides p then norm_2(g) <= 2^degree(g) * norm_2(p) 508 | 509 | let max_norm = self.max_norm(); 510 | let max_norm_squared = &max_norm * &max_norm; 511 | let highest_power_coefficient = self.highest_power_coefficient(); 512 | let highest_power_coefficient_squared = 513 | &highest_power_coefficient * &highest_power_coefficient; 514 | 515 | let factor_limit_squared = 516 | ((max_norm_squared * highest_power_coefficient_squared) << (degree * 2)) * (degree + 1); 517 | // let factor_limit = ExactInexactInt::new(factor_limit_squared.clone()).sqrt(); 518 | // println!("factor_limit: {}", factor_limit); 519 | let needed_modulus = ExactInexactInt::new(factor_limit_squared * 4i32).sqrt() + 1i32; 520 | // println!("needed_modulus: {}", needed_modulus); 521 | // println!(); 522 | // factor_tree.print_tree(); 523 | // println!(); 524 | while modulus < needed_modulus { 525 | let new_modulus = &modulus * &modulus; 526 | let expected_product: Polynomial<_> = self 527 | .iter() 528 | .map(|coefficient| ModularInteger::new(coefficient, new_modulus.clone())) 529 | .collect(); 530 | factor_tree.hensel_lift_step(&modulus, &new_modulus, expected_product); 531 | modulus = new_modulus; 532 | 533 | // factor_tree.print_tree(); 534 | // println!(); 535 | } 536 | // println!(); 537 | // factor_tree.print_tree(); 538 | // println!(); 539 | 540 | let mut modular_factors = Vec::with_capacity(modular_factors_len); 541 | factor_tree.into_leaves(&mut modular_factors); 542 | 543 | modular_factors.retain(|factor| factor.degree() != Some(0)); 544 | 545 | debug_assert!(modular_factors 546 | .iter() 547 | .position(|factor| match factor.degree() { 548 | None | Some(0) => true, 549 | _ => false, 550 | }) 551 | .is_none()); 552 | 553 | // println!("modular_factors:"); 554 | // for factor in &modular_factors { 555 | // println!(" {}", factor); 556 | // } 557 | 558 | let mut factors = Vec::with_capacity(modular_factors.len()); 559 | 560 | if !content.is_one() { 561 | factors.push(content.into()); 562 | } 563 | 564 | let half_modulus = &modulus / 2i32; 565 | 566 | let mut input_polynomial = self.clone(); 567 | 568 | // FIXME: replace exponential subset search with LLL reduction 569 | 570 | let mut subset_size = 0; 571 | let mut found_factors = false; 572 | loop { 573 | if found_factors { 574 | found_factors = false; 575 | } else { 576 | subset_size += 1; 577 | } 578 | if subset_size * 2 > modular_factors.len() { 579 | break; 580 | } 581 | let range = 0..modular_factors.len(); 582 | for_subsets_of_size( 583 | |subset_indexes: &[usize]| { 584 | // println!("subset_indexes: {:?}", subset_indexes); 585 | let mut potential_factor = Polynomial::from(ModularInteger::new( 586 | input_polynomial.highest_power_coefficient(), 587 | modulus.clone(), 588 | )); 589 | for &index in subset_indexes { 590 | potential_factor *= &modular_factors[index]; 591 | } 592 | // println!("potential_factor: {}", potential_factor); 593 | let mut potential_factor: Polynomial<_> = potential_factor 594 | .into_iter() 595 | .map(Into::into) 596 | .map(|(coefficient, _modulus)| { 597 | assert!(!coefficient.is_negative()); 598 | assert!(coefficient < modulus); 599 | if coefficient > half_modulus { 600 | coefficient - &modulus 601 | } else { 602 | coefficient 603 | } 604 | }) 605 | .collect(); 606 | // println!("potential_factor: {}", potential_factor); 607 | potential_factor.primitive_part_assign(); 608 | // println!("potential_factor: {}", potential_factor); 609 | // println!("input_polynomial: {}", input_polynomial); 610 | if let Some((mut quotient, _)) = input_polynomial 611 | .clone() 612 | .checked_exact_pseudo_div(&potential_factor) 613 | { 614 | factors.push(potential_factor); 615 | // println!("found factor"); 616 | quotient.primitive_part_assign(); 617 | // println!("quotient: {}", quotient); 618 | input_polynomial = quotient; 619 | found_factors = true; 620 | for &index in subset_indexes.iter().rev() { 621 | modular_factors.remove(index); 622 | } 623 | ContinueBreak::Break(()) 624 | } else { 625 | ContinueBreak::Continue 626 | } 627 | }, 628 | subset_size, 629 | range, 630 | ); 631 | } 632 | factors.push(input_polynomial); 633 | factors 634 | } 635 | pub fn factor_with_rng(&self, rng: &mut R) -> PolynomialFactors { 636 | let content = self.content(); 637 | let rational_polynomial: Polynomial<_> = self 638 | .iter() 639 | .map(|v| Ratio::from_integer(v.exact_div(&content))) 640 | .collect(); 641 | let square_free_factors = rational_polynomial 642 | .square_free_factorization_using_yuns_algorithm() 643 | .polynomial_factors; 644 | let mut polynomial_factors = Vec::with_capacity(self.len()); 645 | for (index, square_free_factor) in square_free_factors.into_iter().enumerate() { 646 | let power = index + 1; 647 | polynomial_factors.extend( 648 | Polynomial::::from(square_free_factor.split_out_divisor().0) 649 | .factor_square_free_polynomial_with_rng(rng) 650 | .into_iter() 651 | .filter(|polynomial| !polynomial.is_one()) 652 | .map(|polynomial| PolynomialFactor { polynomial, power }), 653 | ); 654 | } 655 | PolynomialFactors { 656 | constant_factor: content, 657 | polynomial_factors, 658 | } 659 | } 660 | pub fn factor(&self) -> PolynomialFactors { 661 | let mut rng = Pcg64Mcg::seed_from_u64(0); 662 | self.factor_with_rng(&mut rng) 663 | } 664 | } 665 | 666 | #[cfg(test)] 667 | mod tests { 668 | use super::*; 669 | use num_traits::{One, Pow}; 670 | use std::{collections::HashSet, ops::Mul}; 671 | 672 | fn p(coefficients: Vec) -> Polynomial { 673 | coefficients.into_iter().map(BigInt::from).collect() 674 | } 675 | 676 | fn test_factor_square_free_polynomial_with_rng_test_case( 677 | expected_factors: Vec>, 678 | ) { 679 | let mut rng = Pcg64Mcg::seed_from_u64(0); 680 | let expected_factors: HashSet<_> = expected_factors.into_iter().collect(); 681 | let poly = expected_factors 682 | .iter() 683 | .fold(Polynomial::::one(), Mul::mul); 684 | println!("poly: {}", poly); 685 | println!("expected_factors:"); 686 | for factor in &expected_factors { 687 | println!(" {}", factor); 688 | } 689 | let factors = poly.factor_square_free_polynomial_with_rng(&mut rng); 690 | let factors: HashSet<_> = factors.into_iter().collect(); 691 | println!("factors:"); 692 | for factor in &factors { 693 | println!(" {}", factor); 694 | } 695 | assert!(expected_factors == factors); 696 | } 697 | 698 | #[test] 699 | fn test_factor_square_free_polynomial_with_rng_0() { 700 | test_factor_square_free_polynomial_with_rng_test_case(vec![ 701 | p(vec![0, 1]), 702 | p(vec![4, 4, 1, 3]), 703 | p(vec![2, 0, 3, 3]), 704 | p(vec![4, 3, 1, 1, 2]), 705 | p(vec![4, 0, 2, 3, 3]), 706 | ]); 707 | } 708 | 709 | #[test] 710 | fn test_factor_square_free_polynomial_with_rng_1() { 711 | test_factor_square_free_polynomial_with_rng_test_case(vec![ 712 | p(vec![-1]), 713 | p(vec![-0, 1]), 714 | p(vec![-4, 4, -1, 3]), 715 | p(vec![-2, 0, -3, 3]), 716 | p(vec![4, -3, 1, -1, 2]), 717 | p(vec![4, -0, 2, -3, 3]), 718 | ]); 719 | } 720 | 721 | #[test] 722 | #[ignore = "slow"] 723 | fn test_factor_square_free_polynomial_with_rng_2() { 724 | test_factor_square_free_polynomial_with_rng_test_case(vec![ 725 | p(vec![12]), 726 | p(vec![29, 19]), 727 | p(vec![185, 174]), 728 | p(vec![189, 135, 97]), 729 | p(vec![171, 134, 40, 122, 46]), 730 | p(vec![118, 103, 175, 39, 172]), 731 | p(vec![101, 149, 70, 56, 68, 79]), 732 | p(vec![186, 77, 5, 168, 148, 70, 82, 158]), 733 | p(vec![171, 146, 23, 181, 116, 106, 74, 168]), 734 | p(vec![181, 16, 97, 169, 189, 142, 69, 168, 133, 87]), 735 | p(vec![130, 82, 85, 16, 87, 165, 168, -6, 106, 89]), 736 | p(vec![152, 23, 189, 50, 21, 142, 43, 146, 106, -5, 106]), 737 | ]); 738 | } 739 | 740 | #[test] 741 | #[ignore = "slow"] 742 | fn test_factor_square_free_polynomial_with_rng_3() { 743 | test_factor_square_free_polynomial_with_rng_test_case(vec![ 744 | p(vec![36, 97, 177, 78]), 745 | p(vec![190, 184, 24, 141]), 746 | p(vec![105, 57, 21, 161]), 747 | p(vec![136, 159, 47, 45, 20]), 748 | p(vec![28, 80, 84, 27, 56]), 749 | p(vec![173, 118, 123, 108, 118, 36]), 750 | p(vec![48, -4, 120, 156, 81, 76]), 751 | p(vec![98, 179, 179, -3, 176, 100]), 752 | p(vec![104, 83, 68, 123, 166, 125, 62]), 753 | p(vec![17, 176, 132, 115, 1, 182, 95, 105, 11]), 754 | p(vec![73, 70, 12, 57, 122, 23, 23, 146, 28]), 755 | ]); 756 | } 757 | 758 | #[test] 759 | #[ignore = "slow"] 760 | fn test_factor_square_free_polynomial_with_rng_4() { 761 | test_factor_square_free_polynomial_with_rng_test_case(vec![ 762 | p(vec![286]), 763 | p(vec![94, 61]), 764 | p(vec![-37, 13, 16]), 765 | p(vec![-57, 75, 20]), 766 | p(vec![98, -43, 27]), 767 | p(vec![43, -77, -75, 98]), 768 | p(vec![43, 16, -94, -84, -67, 20]), 769 | p(vec![-65, -2, -49, -61, -57, 80]), 770 | p(vec![-30, -52, 54, 21, 21, -56, 3]), 771 | p(vec![94, 50, 97, -83, 97, -3, -51, -86, 38]), 772 | p(vec![2, -16, 50, -67, 63, -69, -31, 25, 83]), 773 | p(vec![-69, 28, -25, -25, 57, -10, -65, -19, 3, -66, 38]), 774 | ]); 775 | } 776 | 777 | #[test] 778 | fn test_factor_square_free_polynomial_with_rng_5() { 779 | test_factor_square_free_polynomial_with_rng_test_case(vec![p(vec![ 780 | -69, 28, -25, -25, 57, -10, -65, -19, 3, -66, 38, 781 | ])]); 782 | } 783 | 784 | #[test] 785 | fn test_factor_square_free_polynomial_with_rng_6() { 786 | test_factor_square_free_polynomial_with_rng_test_case(vec![p(vec![1234])]); 787 | } 788 | 789 | #[test] 790 | fn test_factor_square_free_polynomial_with_rng_7() { 791 | test_factor_square_free_polynomial_with_rng_test_case(vec![p(vec![3, 1234])]); 792 | } 793 | 794 | fn test_factor_test_case(expected: PolynomialFactors) { 795 | let expected = PolynomialFactors { 796 | constant_factor: BigInt::from(expected.constant_factor), 797 | polynomial_factors: expected 798 | .polynomial_factors 799 | .into_iter() 800 | .map(|PolynomialFactor { polynomial, power }| PolynomialFactor { 801 | polynomial: p(polynomial.into_coefficients()), 802 | power, 803 | }) 804 | .collect(), 805 | }; 806 | let expected_factors: HashSet<_> = expected.polynomial_factors.into_iter().collect(); 807 | let poly = expected_factors.iter().fold( 808 | Polynomial::from(expected.constant_factor.clone()), 809 | |poly, PolynomialFactor { polynomial, power }| poly * polynomial.pow(*power), 810 | ); 811 | println!("poly: {}", poly); 812 | println!("expected_factors:"); 813 | println!(" {}", expected.constant_factor); 814 | for factor in &expected_factors { 815 | println!(" {}", factor); 816 | } 817 | let PolynomialFactors { 818 | constant_factor, 819 | polynomial_factors, 820 | } = poly.factor(); 821 | let factors: HashSet<_> = polynomial_factors.into_iter().collect(); 822 | println!("factors:"); 823 | println!(" {}", constant_factor); 824 | for factor in &factors { 825 | println!(" {}", factor); 826 | } 827 | assert!(expected.constant_factor == constant_factor); 828 | assert!(expected_factors == factors); 829 | } 830 | 831 | #[test] 832 | fn test_factor_0() { 833 | test_factor_test_case(PolynomialFactors { 834 | constant_factor: -6, 835 | polynomial_factors: vec![ 836 | PolynomialFactor { 837 | polynomial: vec![-9, 13].into(), 838 | power: 1, 839 | }, 840 | PolynomialFactor { 841 | polynomial: vec![-88, -53, 55].into(), 842 | power: 4, 843 | }, 844 | PolynomialFactor { 845 | polynomial: vec![-39, -7, 62].into(), 846 | power: 5, 847 | }, 848 | PolynomialFactor { 849 | polynomial: vec![-4, 26, -72, 91].into(), 850 | power: 1, 851 | }, 852 | PolynomialFactor { 853 | polynomial: vec![-74, 9, -68, 92].into(), 854 | power: 1, 855 | }, 856 | PolynomialFactor { 857 | polynomial: vec![-74, 5, -46, 82, -19, 63].into(), 858 | power: 6, 859 | }, 860 | ], 861 | }); 862 | } 863 | 864 | #[test] 865 | fn test_factor_1() { 866 | test_factor_test_case(PolynomialFactors { 867 | constant_factor: -128, 868 | polynomial_factors: vec![ 869 | PolynomialFactor { 870 | polynomial: vec![-1, 25].into(), 871 | power: 3, 872 | }, 873 | PolynomialFactor { 874 | polynomial: vec![2, 37].into(), 875 | power: 1, 876 | }, 877 | PolynomialFactor { 878 | polynomial: vec![99, -19, 76, 3].into(), 879 | power: 5, 880 | }, 881 | PolynomialFactor { 882 | polynomial: vec![-43, 82, -13, 35, -22, 18].into(), 883 | power: 7, 884 | }, 885 | PolynomialFactor { 886 | polynomial: vec![100, -26, 9, -24, 32, 32].into(), 887 | power: 2, 888 | }, 889 | PolynomialFactor { 890 | polynomial: vec![57, 92, 28, -26, 10, 95].into(), 891 | power: 2, 892 | }, 893 | ], 894 | }); 895 | } 896 | } 897 | -------------------------------------------------------------------------------- /src/polynomial/mul.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-2.1-or-later 2 | // See Notices.txt for copyright information 3 | 4 | use crate::polynomial::{Polynomial, PolynomialCoefficient}; 5 | use num_integer::Integer; 6 | use num_traits::{CheckedMul, One, Pow, Zero}; 7 | use std::{ 8 | borrow::Cow, 9 | ops::{AddAssign, Mul, MulAssign}, 10 | }; 11 | 12 | impl<'a, T: PolynomialCoefficient> Mul for &'a Polynomial { 13 | type Output = Polynomial; 14 | #[allow(clippy::suspicious_arithmetic_impl)] 15 | fn mul(self, rhs: &Polynomial) -> Polynomial { 16 | if self.is_zero() || rhs.is_zero() { 17 | return Zero::zero(); 18 | } 19 | let divisor = self.divisor.clone() * &rhs.divisor; 20 | let mut elements = Vec::with_capacity(self.len() + rhs.len()); 21 | for l_index in 0..self.len() { 22 | for r_index in 0..rhs.len() { 23 | let index = l_index + r_index; 24 | if index == elements.len() { 25 | elements.push(self.elements[l_index].clone() * &rhs.elements[r_index]); 26 | } else { 27 | AddAssign::::add_assign( 28 | &mut elements[index], 29 | self.elements[l_index].clone() * &rhs.elements[r_index], 30 | ); 31 | } 32 | } 33 | } 34 | Polynomial { elements, divisor }.into_normalized() 35 | } 36 | } 37 | 38 | impl<'a, T: PolynomialCoefficient> Mul> for &'a Polynomial { 39 | type Output = Polynomial; 40 | fn mul(self, rhs: Polynomial) -> Polynomial { 41 | self * &rhs 42 | } 43 | } 44 | 45 | impl<'a, T: PolynomialCoefficient> Mul<&'a Polynomial> for Polynomial { 46 | type Output = Polynomial; 47 | fn mul(self, rhs: &Polynomial) -> Polynomial { 48 | &self * rhs 49 | } 50 | } 51 | 52 | impl Mul for Polynomial { 53 | type Output = Polynomial; 54 | fn mul(self, rhs: Polynomial) -> Polynomial { 55 | &self * &rhs 56 | } 57 | } 58 | 59 | impl MulAssign for Polynomial { 60 | fn mul_assign(&mut self, rhs: Polynomial) { 61 | *self = &*self * rhs; 62 | } 63 | } 64 | 65 | impl<'a, T: PolynomialCoefficient> MulAssign<&'a Polynomial> for Polynomial { 66 | fn mul_assign(&mut self, rhs: &Polynomial) { 67 | *self = &*self * rhs; 68 | } 69 | } 70 | 71 | impl CheckedMul for Polynomial { 72 | fn checked_mul(&self, rhs: &Self) -> Option { 73 | Some(self * rhs) 74 | } 75 | } 76 | 77 | fn mul_assign_by_element_nonnormalized( 78 | lhs: &mut Polynomial, 79 | rhs: &T::Element, 80 | ) { 81 | lhs.elements.iter_mut().for_each(|v| *v *= rhs); 82 | } 83 | 84 | fn mul_single(lhs: Cow>, rhs: Cow) -> Polynomial { 85 | let lhs = match lhs { 86 | Cow::Owned(mut lhs) => { 87 | mul_assign_single(&mut lhs, rhs); 88 | return lhs; 89 | } 90 | Cow::Borrowed(lhs) => lhs, 91 | }; 92 | let (rhs_numerator, rhs_divisor) = T::coefficient_to_element(rhs); 93 | Polynomial { 94 | elements: lhs 95 | .elements 96 | .iter() 97 | .map(|l| l.clone() * &rhs_numerator) 98 | .collect(), 99 | divisor: rhs_divisor * &lhs.divisor, 100 | } 101 | .into_normalized() 102 | } 103 | 104 | fn mul_assign_single(lhs: &mut Polynomial, rhs: Cow) { 105 | let (rhs_numerator, rhs_divisor) = T::coefficient_to_element(rhs); 106 | mul_assign_by_element_nonnormalized(lhs, &rhs_numerator); 107 | MulAssign::::mul_assign(&mut lhs.divisor, rhs_divisor); 108 | lhs.normalize(); 109 | } 110 | 111 | impl<'a, T: PolynomialCoefficient> Mul<&'a T> for &'a Polynomial { 112 | type Output = Polynomial; 113 | fn mul(self, rhs: &T) -> Polynomial { 114 | mul_single(Cow::Borrowed(self), Cow::Borrowed(rhs)) 115 | } 116 | } 117 | 118 | impl<'a, T: PolynomialCoefficient> Mul for &'a Polynomial { 119 | type Output = Polynomial; 120 | fn mul(self, rhs: T) -> Polynomial { 121 | mul_single(Cow::Borrowed(self), Cow::Owned(rhs)) 122 | } 123 | } 124 | 125 | impl<'a, T: PolynomialCoefficient> Mul<&'a T> for Polynomial { 126 | type Output = Polynomial; 127 | fn mul(self, rhs: &T) -> Polynomial { 128 | mul_single(Cow::Owned(self), Cow::Borrowed(rhs)) 129 | } 130 | } 131 | 132 | impl Mul for Polynomial { 133 | type Output = Polynomial; 134 | fn mul(self, rhs: T) -> Polynomial { 135 | mul_single(Cow::Owned(self), Cow::Owned(rhs)) 136 | } 137 | } 138 | 139 | impl MulAssign for Polynomial { 140 | fn mul_assign(&mut self, rhs: T) { 141 | mul_assign_single(self, Cow::Owned(rhs)); 142 | } 143 | } 144 | 145 | impl<'a, T: PolynomialCoefficient> MulAssign<&'a T> for Polynomial { 146 | fn mul_assign(&mut self, rhs: &T) { 147 | mul_assign_single(self, Cow::Borrowed(rhs)); 148 | } 149 | } 150 | 151 | impl Polynomial { 152 | #[inline] 153 | pub fn is_one(&self) -> bool { 154 | self.len() == 1 && self.divisor.is_one() && T::is_element_one(&self.elements[0]) 155 | } 156 | } 157 | 158 | impl One for Polynomial 159 | where 160 | T::Element: One, 161 | { 162 | fn one() -> Self { 163 | Self { 164 | elements: vec![One::one()], 165 | divisor: One::one(), 166 | } 167 | } 168 | fn set_one(&mut self) { 169 | if self.elements.is_empty() { 170 | self.elements.push(One::one()); 171 | } else { 172 | self.elements.drain(1..); 173 | self.elements[0].set_one(); 174 | } 175 | self.divisor.set_one(); 176 | } 177 | #[inline] 178 | fn is_one(&self) -> bool { 179 | match &*self.elements { 180 | [element] => element.is_one() && self.divisor.is_one(), 181 | _ => false, 182 | } 183 | } 184 | } 185 | 186 | impl Polynomial { 187 | fn checked_pow(&self, mut exponent: E) -> Option { 188 | if exponent < Zero::zero() { 189 | return None; 190 | } 191 | if exponent.is_zero() { 192 | return self.to_one_if_nonzero(); 193 | } 194 | let mut base = self.clone(); 195 | if exponent.is_one() { 196 | return Some(base); 197 | } 198 | let mut retval: Option = None; 199 | loop { 200 | if exponent.is_odd() { 201 | retval = Some(match retval.take() { 202 | None => base.clone(), 203 | Some(retval) => retval * &base, 204 | }); 205 | } 206 | let two = E::one() + E::one(); 207 | exponent = exponent / two; 208 | if exponent.is_zero() { 209 | break; 210 | } 211 | base = &base * &base; 212 | } 213 | retval 214 | } 215 | } 216 | 217 | impl Pow for &'_ Polynomial { 218 | type Output = Polynomial; 219 | fn pow(self, exponent: E) -> Polynomial { 220 | self.checked_pow(exponent).expect("checked_pow failed") 221 | } 222 | } 223 | 224 | impl Pow for Polynomial { 225 | type Output = Polynomial; 226 | fn pow(self, exponent: E) -> Polynomial { 227 | self.checked_pow(exponent).expect("checked_pow failed") 228 | } 229 | } 230 | 231 | #[cfg(test)] 232 | mod tests { 233 | use super::*; 234 | use crate::util::tests::test_op_helper; 235 | use num_rational::Ratio; 236 | #[test] 237 | fn test_mul() { 238 | let test = |l: Polynomial, r: Polynomial, expected: &Polynomial| { 239 | test_op_helper( 240 | l, 241 | r, 242 | expected, 243 | |l, r| *l *= r, 244 | |l, r| *l *= r, 245 | |l, r| l * r, 246 | |l, r| l * r, 247 | |l, r| l * r, 248 | |l, r| l * r, 249 | ); 250 | }; 251 | test( 252 | vec![10, 11, 12].into(), 253 | vec![10, -11, 3, 2, 1].into(), 254 | &vec![100, 0, 29, -79, 68, 35, 12].into(), 255 | ); 256 | } 257 | 258 | #[test] 259 | fn test_mul_rational() { 260 | let test = |l: Polynomial>, 261 | r: Polynomial>, 262 | expected: &Polynomial>| { 263 | test_op_helper( 264 | l, 265 | r, 266 | expected, 267 | |l, r| *l *= r, 268 | |l, r| *l *= r, 269 | |l, r| l * r, 270 | |l, r| l * r, 271 | |l, r| l * r, 272 | |l, r| l * r, 273 | ); 274 | }; 275 | let r = |n: i64, d: i64| Ratio::new(n, d); 276 | test( 277 | vec![r(10, 7), r(11, 7), r(12, 7)].into(), 278 | vec![r(10, 29), r(-11, 29), r(3, 29), r(2, 29), r(1, 29)].into(), 279 | &vec![ 280 | r(100, 203), 281 | r(0, 1), 282 | r(1, 7), 283 | r(-79, 203), 284 | r(68, 203), 285 | r(5, 29), 286 | r(12, 203), 287 | ] 288 | .into(), 289 | ); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /src/polynomial/same_degree_factorization.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-2.1-or-later 2 | // See Notices.txt for copyright information 3 | 4 | use crate::{ 5 | mod_int::{ModularInteger, ModularReducePow, OddPrimeModulus}, 6 | polynomial::Polynomial, 7 | traits::{ExactDivAssign, ExtendedGCD, GCD}, 8 | }; 9 | use num_bigint::ToBigInt; 10 | use num_integer::Integer; 11 | use num_traits::Pow; 12 | use rand::{ 13 | distributions::{uniform::SampleUniform, Distribution, Uniform}, 14 | Rng, 15 | }; 16 | use std::{fmt, hash::Hash, iter}; 17 | 18 | impl Polynomial> 19 | where 20 | V: ModularReducePow 21 | + Integer 22 | + GCD 23 | + ExtendedGCD 24 | + fmt::Debug 25 | + fmt::Display 26 | + Hash 27 | + SampleUniform 28 | + ToBigInt, 29 | M: OddPrimeModulus + fmt::Debug + fmt::Display + Hash, 30 | { 31 | pub(crate) fn same_degree_factorization( 32 | self, 33 | factor_degree: usize, 34 | rng: &mut R, 35 | ) -> Vec>> { 36 | assert!(factor_degree >= 1); 37 | assert!(self.len() > 1); 38 | if self.degree() == Some(factor_degree) { 39 | return vec![self]; 40 | } 41 | let nonzero_highest_power_coefficient = match self.nonzero_highest_power_coefficient() { 42 | None => return Vec::new(), 43 | Some(v) => v, 44 | }; 45 | assert!(nonzero_highest_power_coefficient.value().is_one()); 46 | let modulus = nonzero_highest_power_coefficient.modulus(); 47 | let one_coefficient = ModularInteger::new(V::one(), modulus.clone()); 48 | let characteristic = modulus.to_modulus().into_owned(); 49 | let bigint_characteristic = characteristic 50 | .to_bigint() 51 | .expect("can't convert modulus/characteristic to BigInt"); 52 | let polynomial_exponent = (bigint_characteristic.pow(factor_degree) - 1u32) / 2u32; 53 | let coefficient_uniform = Uniform::new(V::zero(), characteristic); 54 | let mut retval = Vec::new(); 55 | let mut factoring_stack = vec![self.clone()]; 56 | while let Some(mut polynomial) = factoring_stack.pop() { 57 | assert!( 58 | polynomial.degree().unwrap_or(0) >= factor_degree, 59 | "factoring failed: {}", 60 | self 61 | ); 62 | if polynomial.degree() == Some(factor_degree) { 63 | retval.push(polynomial); 64 | continue; 65 | } 66 | let factors = loop { 67 | let random_polynomial_degree = 2 * factor_degree - 1; 68 | let random_polynomial: Polynomial<_> = (0..random_polynomial_degree) 69 | .map(|_| coefficient_uniform.sample(rng)) 70 | .chain(iter::once(V::one())) 71 | .map(|v| ModularInteger::new(v, modulus.clone())) 72 | .collect(); 73 | debug_assert!(random_polynomial.degree().unwrap_or(0) >= 1); 74 | let gcd = polynomial.gcd( 75 | &(random_polynomial.powmod(polynomial_exponent.clone(), &polynomial) 76 | - &one_coefficient), 77 | ); 78 | if gcd.degree().unwrap_or(0) > 0 && gcd.len() < polynomial.len() { 79 | polynomial.exact_div_assign(&gcd); 80 | break (gcd, polynomial); 81 | } 82 | }; 83 | factoring_stack.push(factors.0); 84 | factoring_stack.push(factors.1); 85 | } 86 | retval 87 | } 88 | } 89 | 90 | // TODO: implement same_degree_factorization for modulus 2 (needs different algorithm) 91 | 92 | #[cfg(test)] 93 | mod tests { 94 | use super::*; 95 | use crate::mod_int::KnownOddPrime; 96 | use rand::SeedableRng; 97 | use rand_pcg::Pcg64Mcg; 98 | use std::collections::HashSet; 99 | 100 | #[test] 101 | pub(crate) fn test_same_degree_factorization() { 102 | fn make_poly( 103 | poly: &[i32], 104 | modulus: KnownOddPrime, 105 | ) -> Polynomial>> { 106 | poly.iter() 107 | .map(|&coefficient| ModularInteger::new(coefficient, modulus)) 108 | .collect() 109 | } 110 | fn test_case( 111 | poly: &[i32], 112 | expected_factors: &[&[i32]], 113 | factor_degree: usize, 114 | modulus: KnownOddPrime, 115 | ) { 116 | let mut rng = Pcg64Mcg::seed_from_u64(0); 117 | let poly = make_poly(poly, modulus); 118 | let expected_factors: HashSet<_> = expected_factors 119 | .iter() 120 | .map(|poly| make_poly(*poly, modulus)) 121 | .collect(); 122 | println!("poly: {}", poly); 123 | println!("factor_degree: {}", factor_degree); 124 | println!("expected_factors:"); 125 | for factor in &expected_factors { 126 | println!(" {}", factor); 127 | } 128 | let factors = poly.same_degree_factorization(factor_degree, &mut rng); 129 | let factors: HashSet<_> = factors.into_iter().collect(); 130 | println!("factors:"); 131 | for factor in &factors { 132 | println!(" {}", factor); 133 | } 134 | assert!(expected_factors == factors); 135 | } 136 | test_case( 137 | &[2, 0, 3, 1, 2, 2, 4, 3, 0, 1, 2, 4, 1, 2, 0, 0, 1], 138 | &[&[2, 3, 3, 4, 0, 0, 4, 4, 1], &[1, 1, 1, 3, 3, 0, 2, 1, 1]], 139 | 8, 140 | KnownOddPrime::new_odd_prime_unsafe(5), 141 | ); 142 | test_case( 143 | &[4, 0, 0, 0, 1], 144 | &[&[1, 1], &[2, 1], &[3, 1], &[4, 1]], 145 | 1, 146 | KnownOddPrime::new_odd_prime_unsafe(5), 147 | ); 148 | test_case( 149 | &[2, 2, 3, 1, 1], 150 | &[&[1, 1, 1], &[2, 0, 1]], 151 | 2, 152 | KnownOddPrime::new_odd_prime_unsafe(5), 153 | ); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-2.1-or-later 2 | // See Notices.txt for copyright information 3 | pub use crate::{ 4 | algebraic_numbers::RealAlgebraicNumber, 5 | traits::{ 6 | ExactDiv as _, ExactDivAssign as _, ExtendedGCD as _, IntervalUnion as _, 7 | IntervalUnionAssign as _, GCD as _, 8 | }, 9 | }; 10 | pub use num_traits::{ 11 | CheckedAdd as _, CheckedDiv as _, CheckedMul as _, CheckedRem as _, CheckedSub as _, One as _, 12 | Pow as _, Signed as _, Zero as _, 13 | }; 14 | -------------------------------------------------------------------------------- /src/python.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-2.1-or-later 2 | // See Notices.txt for copyright information 3 | 4 | #![cfg(feature = "python")] 5 | 6 | use crate::{algebraic_numbers::RealAlgebraicNumber, traits::ExactDiv}; 7 | use num_bigint::BigInt; 8 | use num_traits::{Signed, Zero}; 9 | use pyo3::{ 10 | basic::CompareOp, 11 | exceptions::{PyTypeError, PyValueError, PyZeroDivisionError}, 12 | prelude::*, 13 | types::PyAny, 14 | }; 15 | use std::{ 16 | ops::{Deref, DerefMut}, 17 | sync::Arc, 18 | }; 19 | 20 | #[derive(Clone)] 21 | struct SharedNumber(Arc); 22 | 23 | impl Deref for SharedNumber { 24 | type Target = Arc; 25 | fn deref(&self) -> &Self::Target { 26 | &self.0 27 | } 28 | } 29 | 30 | impl DerefMut for SharedNumber { 31 | fn deref_mut(&mut self) -> &mut Self::Target { 32 | &mut self.0 33 | } 34 | } 35 | 36 | impl From for SharedNumber { 37 | fn from(v: RealAlgebraicNumber) -> Self { 38 | SharedNumber(v.into()) 39 | } 40 | } 41 | 42 | impl IntoPy for SharedNumber { 43 | fn into_py(self, py: Python) -> PyObject { 44 | RealAlgebraicNumberPy2 { value: self }.into_py(py) 45 | } 46 | } 47 | 48 | impl FromPyObject<'_> for SharedNumber { 49 | fn extract(value: &PyAny) -> PyResult { 50 | if let Ok(value) = value.extract::>() { 51 | return Ok(value.value.clone()); 52 | } 53 | let value = value.extract::()?; 54 | Ok(RealAlgebraicNumber::from(value).into()) 55 | } 56 | } 57 | 58 | #[pyclass(name = "RealAlgebraicNumber", module = "algebraics")] 59 | struct RealAlgebraicNumberPy2 { 60 | value: SharedNumber, 61 | } 62 | 63 | impl From<&'_ PyCell> for SharedNumber { 64 | fn from(v: &PyCell) -> Self { 65 | v.borrow().value.clone() 66 | } 67 | } 68 | 69 | #[pymethods] 70 | impl RealAlgebraicNumberPy2 { 71 | #[new] 72 | fn pynew(value: Option) -> Self { 73 | let value = value.unwrap_or_else(|| RealAlgebraicNumber::zero().into()); 74 | RealAlgebraicNumberPy2 { value } 75 | } 76 | fn __trunc__(&self, py: Python) -> BigInt { 77 | py.allow_threads(|| self.value.to_integer_trunc()) 78 | } 79 | fn __floor__(&self, py: Python) -> BigInt { 80 | py.allow_threads(|| self.value.to_integer_floor()) 81 | } 82 | fn __ceil__(&self, py: Python) -> BigInt { 83 | py.allow_threads(|| self.value.to_integer_ceil()) 84 | } 85 | fn to_integer(&self) -> Option { 86 | self.value.to_integer() 87 | } 88 | fn to_rational(&self) -> Option<(BigInt, BigInt)> { 89 | self.value.to_rational().map(Into::into) 90 | } 91 | #[getter] 92 | fn minimal_polynomial(&self) -> Vec { 93 | self.value.minimal_polynomial().iter().collect() 94 | } 95 | #[getter] 96 | fn degree(&self) -> usize { 97 | self.value.degree() 98 | } 99 | fn is_rational(&self) -> bool { 100 | self.value.is_rational() 101 | } 102 | fn is_integer(&self) -> bool { 103 | self.value.is_integer() 104 | } 105 | fn recip(&self, py: Python) -> PyResult { 106 | py.allow_threads(|| Some(self.value.checked_recip()?.into())) 107 | .ok_or_else(get_div_by_zero_error) 108 | } 109 | /// returns `floor(log2(self))` 110 | fn floor_log2(&self, py: Python) -> PyResult { 111 | py.allow_threads(|| self.value.checked_floor_log2()) 112 | .ok_or_else(get_floor_ceil_log2_error) 113 | } 114 | /// returns `ceil(log2(self))` 115 | fn ceil_log2(&self, py: Python) -> PyResult { 116 | py.allow_threads(|| self.value.checked_ceil_log2()) 117 | .ok_or_else(get_floor_ceil_log2_error) 118 | } 119 | 120 | // Basic object methods 121 | fn __repr__(&self) -> PyResult { 122 | Ok(format!("<{:?}>", *self.value)) 123 | } 124 | fn __richcmp__(&self, py: Python, other: SharedNumber, op: CompareOp) -> PyResult { 125 | Ok(py.allow_threads(|| match op { 126 | CompareOp::Lt => *self.value < *other, 127 | CompareOp::Le => *self.value <= *other, 128 | CompareOp::Eq => *self.value == *other, 129 | CompareOp::Ne => *self.value != *other, 130 | CompareOp::Gt => *self.value > *other, 131 | CompareOp::Ge => *self.value >= *other, 132 | })) 133 | } 134 | 135 | // Numeric methods 136 | fn __add__(lhs: SharedNumber, py: Python, rhs: SharedNumber) -> PyResult { 137 | arithmetic_helper(py, lhs, rhs, |lhs, rhs| lhs + rhs) 138 | } 139 | fn __radd__(rhs: SharedNumber, py: Python, lhs: SharedNumber) -> PyResult { 140 | Self::__add__(lhs, py, rhs) 141 | } 142 | fn __sub__(lhs: SharedNumber, py: Python, rhs: SharedNumber) -> PyResult { 143 | arithmetic_helper(py, lhs, rhs, |lhs, rhs| lhs - rhs) 144 | } 145 | fn __rsub__(rhs: SharedNumber, py: Python, lhs: SharedNumber) -> PyResult { 146 | Self::__sub__(lhs, py, rhs) 147 | } 148 | fn __mul__(lhs: SharedNumber, py: Python, rhs: SharedNumber) -> PyResult { 149 | arithmetic_helper(py, lhs, rhs, |lhs, rhs| lhs * rhs) 150 | } 151 | fn __rmul__(rhs: SharedNumber, py: Python, lhs: SharedNumber) -> PyResult { 152 | Self::__mul__(lhs, py, rhs) 153 | } 154 | fn __truediv__(lhs: SharedNumber, py: Python, rhs: SharedNumber) -> PyResult { 155 | try_arithmetic_helper( 156 | py, 157 | lhs, 158 | rhs, 159 | |lhs, rhs| lhs.checked_exact_div(rhs).ok_or(()), 160 | |_| get_div_by_zero_error(), 161 | ) 162 | } 163 | fn __rtruediv__(rhs: SharedNumber, py: Python, lhs: SharedNumber) -> PyResult { 164 | Self::__truediv__(lhs, py, rhs) 165 | } 166 | fn __pow__( 167 | lhs: SharedNumber, 168 | py: Python, 169 | rhs: SharedNumber, 170 | modulus: &PyAny, 171 | ) -> PyResult { 172 | if !modulus.is_none() { 173 | return Err(PyTypeError::new_err( 174 | "3 argument pow() not allowed for RealAlgebraicNumber", 175 | )); 176 | } 177 | try_arithmetic_helper( 178 | py, 179 | lhs, 180 | rhs, 181 | |lhs, rhs| { 182 | if let Some(rhs) = rhs.to_rational() { 183 | lhs.checked_pow(rhs) 184 | .ok_or("pow() failed for RealAlgebraicNumber") 185 | } else { 186 | Err("exponent must be rational for RealAlgebraicNumber") 187 | } 188 | }, 189 | PyValueError::new_err, 190 | ) 191 | } 192 | fn __rpow__( 193 | rhs: SharedNumber, 194 | py: Python, 195 | lhs: SharedNumber, 196 | modulus: &PyAny, 197 | ) -> PyResult { 198 | Self::__pow__(lhs, py, rhs, modulus) 199 | } 200 | 201 | // Unary arithmetic 202 | fn __neg__(&self, py: Python) -> PyResult { 203 | Ok(py.allow_threads(|| (-&**self.value).into())) 204 | } 205 | fn __abs__(&self, py: Python) -> PyResult { 206 | Ok(py.allow_threads(|| self.value.abs().into())) 207 | } 208 | } 209 | 210 | fn get_div_by_zero_error() -> PyErr { 211 | PyZeroDivisionError::new_err("can't divide RealAlgebraicNumber by zero") 212 | } 213 | 214 | fn get_floor_ceil_log2_error() -> PyErr { 215 | PyValueError::new_err("can't extract base-2 logarithm of zero or negative RealAlgebraicNumber") 216 | } 217 | 218 | fn try_arithmetic_helper< 219 | E: Send, 220 | F: Send + FnOnce(&RealAlgebraicNumber, &RealAlgebraicNumber) -> Result, 221 | MapErr: FnOnce(E) -> PyErr, 222 | >( 223 | py: Python, 224 | lhs: SharedNumber, 225 | rhs: SharedNumber, 226 | f: F, 227 | map_err: MapErr, 228 | ) -> PyResult { 229 | py.allow_threads(|| Ok(f(&lhs, &rhs)?.into())) 230 | .map_err(map_err) 231 | } 232 | 233 | fn arithmetic_helper< 234 | F: Send + FnOnce(&RealAlgebraicNumber, &RealAlgebraicNumber) -> RealAlgebraicNumber, 235 | >( 236 | py: Python, 237 | lhs: SharedNumber, 238 | rhs: SharedNumber, 239 | f: F, 240 | ) -> PyResult { 241 | enum Uninhabited {} 242 | try_arithmetic_helper( 243 | py, 244 | lhs, 245 | rhs, 246 | |lhs, rhs| Ok(f(lhs, rhs)), 247 | |v: Uninhabited| match v {}, 248 | ) 249 | } 250 | 251 | #[pymodule] 252 | fn algebraics(_py: Python, m: &PyModule) -> PyResult<()> { 253 | m.add_class::()?; 254 | Ok(()) 255 | } 256 | 257 | #[cfg(test)] 258 | mod tests { 259 | use super::*; 260 | 261 | #[test] 262 | fn conversion_compile_test() { 263 | #![allow(dead_code)] 264 | 265 | #[pyfunction] 266 | fn identity_result(v: SharedNumber) -> PyResult { 267 | Ok(v) 268 | } 269 | 270 | #[pyfunction] 271 | fn identity(v: SharedNumber) -> SharedNumber { 272 | v 273 | } 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /src/traits.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: LGPL-2.1-or-later 2 | // See Notices.txt for copyright information 3 | use crate::polynomial::{Polynomial, PolynomialCoefficient}; 4 | use num_bigint::{BigInt, BigUint}; 5 | use num_integer::Integer; 6 | use num_rational::Ratio; 7 | use num_traits::{CheckedDiv, CheckedMul, CheckedRem, NumAssign, One, Signed, ToPrimitive, Zero}; 8 | use std::{ 9 | convert::TryInto, 10 | fmt, 11 | ops::{Add, Div, DivAssign, Mul}, 12 | }; 13 | 14 | /// GCD and LCM 15 | /// 16 | /// ```no_build 17 | /// let result = GCD::gcd_lcm(a, b); 18 | /// assert_eq!(result.gcd * result.lcm, a * b); 19 | /// ``` 20 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 21 | pub struct GCDAndLCM { 22 | pub gcd: T, 23 | pub lcm: T, 24 | } 25 | 26 | /// GCD and Bézout coefficients 27 | /// 28 | /// ```no_build 29 | /// let result = ExtendedGCD::extended_gcd(a, b); 30 | /// assert_eq!(result.x * a + result.y * b, result.gcd); 31 | /// ``` 32 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 33 | pub struct ExtendedGCDResult { 34 | pub gcd: T, 35 | pub x: T, 36 | pub y: T, 37 | } 38 | 39 | /// GCD, LCM, and Bézout coefficients 40 | /// 41 | /// ```no_build 42 | /// let result = ExtendedGCD::extended_gcd_lcm(a, b); 43 | /// assert_eq!(result.x * a + result.y * b, result.gcd); 44 | /// assert_eq!(result.gcd * result.lcm, a * b); 45 | /// ``` 46 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 47 | pub struct ExtendedGCDAndLCM { 48 | pub gcd: T, 49 | pub x: T, 50 | pub y: T, 51 | pub lcm: T, 52 | } 53 | 54 | pub trait GCD { 55 | type Output; 56 | #[must_use] 57 | fn gcd(&self, rhs: &Rhs) -> Self::Output { 58 | self.gcd_lcm(rhs).gcd 59 | } 60 | #[must_use] 61 | fn lcm(&self, rhs: &Rhs) -> Self::Output { 62 | self.gcd_lcm(rhs).lcm 63 | } 64 | #[must_use] 65 | fn gcd_lcm(&self, rhs: &Rhs) -> GCDAndLCM; 66 | } 67 | 68 | pub trait ExtendedGCD: GCD { 69 | #[must_use] 70 | fn extended_gcd(&self, rhs: &Rhs) -> ExtendedGCDResult { 71 | let ExtendedGCDAndLCM { gcd, x, y, .. } = self.extended_gcd_lcm(rhs); 72 | ExtendedGCDResult { gcd, x, y } 73 | } 74 | #[must_use] 75 | fn extended_gcd_lcm(&self, rhs: &Rhs) -> ExtendedGCDAndLCM; 76 | } 77 | 78 | macro_rules! impl_gcd_for_int { 79 | ($t:ty) => { 80 | impl GCD for $t { 81 | type Output = $t; 82 | fn gcd(&self, rhs: &$t) -> $t { 83 | Integer::gcd(self, rhs) 84 | } 85 | fn lcm(&self, rhs: &$t) -> $t { 86 | Integer::lcm(self, rhs) 87 | } 88 | fn gcd_lcm(&self, rhs: &$t) -> GCDAndLCM<$t> { 89 | let (gcd, lcm) = Integer::gcd_lcm(self, rhs); 90 | GCDAndLCM { gcd, lcm } 91 | } 92 | } 93 | }; 94 | } 95 | 96 | macro_rules! impl_gcd_for_signed_int { 97 | ($t:ty) => { 98 | impl_gcd_for_int!($t); 99 | 100 | impl ExtendedGCD for $t { 101 | fn extended_gcd(&self, rhs: &$t) -> ExtendedGCDResult<$t> { 102 | let num_integer::ExtendedGcd { gcd, x, y, .. } = Integer::extended_gcd(self, rhs); 103 | ExtendedGCDResult { gcd, x, y } 104 | } 105 | fn extended_gcd_lcm(&self, rhs: &$t) -> ExtendedGCDAndLCM<$t> { 106 | let (num_integer::ExtendedGcd { gcd, x, y, .. }, lcm) = 107 | Integer::extended_gcd_lcm(self, rhs); 108 | ExtendedGCDAndLCM { gcd, x, y, lcm } 109 | } 110 | } 111 | }; 112 | } 113 | 114 | impl_gcd_for_int!(u8); 115 | impl_gcd_for_signed_int!(i8); 116 | impl_gcd_for_int!(u16); 117 | impl_gcd_for_signed_int!(i16); 118 | impl_gcd_for_int!(u32); 119 | impl_gcd_for_signed_int!(i32); 120 | impl_gcd_for_int!(u64); 121 | impl_gcd_for_signed_int!(i64); 122 | impl_gcd_for_int!(u128); 123 | impl_gcd_for_signed_int!(i128); 124 | impl_gcd_for_int!(usize); 125 | impl_gcd_for_signed_int!(isize); 126 | impl_gcd_for_int!(BigUint); 127 | impl_gcd_for_signed_int!(BigInt); 128 | 129 | impl Mul<&'a T, Output = T>> GCD for Ratio { 130 | type Output = Self; 131 | fn gcd_lcm(&self, rhs: &Self) -> GCDAndLCM { 132 | if self.is_zero() { 133 | return GCDAndLCM { 134 | gcd: rhs.clone(), 135 | lcm: Zero::zero(), 136 | }; 137 | } 138 | if rhs.is_zero() { 139 | return GCDAndLCM { 140 | gcd: self.clone(), 141 | lcm: Zero::zero(), 142 | }; 143 | } 144 | let (gcd_numer, lcm_numer) = 145 | (self.numer().clone() * rhs.denom()).gcd_lcm(&(rhs.numer().clone() * self.denom())); 146 | let denom: T = self.denom().clone() * rhs.denom(); 147 | GCDAndLCM { 148 | gcd: Ratio::new(gcd_numer, denom.clone()), 149 | lcm: Ratio::new(lcm_numer, denom), 150 | } 151 | } 152 | } 153 | 154 | impl Mul<&'a T, Output = T>> ExtendedGCD for Ratio { 155 | fn extended_gcd_lcm(&self, rhs: &Self) -> ExtendedGCDAndLCM { 156 | let (gcd_numer, lcm_numer) = (self.numer().clone() * rhs.denom()) 157 | .extended_gcd_lcm(&(rhs.numer().clone() * self.denom())); 158 | let denom: T = self.denom().clone() * rhs.denom(); 159 | ExtendedGCDAndLCM { 160 | gcd: Ratio::new(gcd_numer.gcd, denom.clone()), 161 | x: gcd_numer.x.into(), 162 | y: gcd_numer.y.into(), 163 | lcm: Ratio::new(lcm_numer, denom), 164 | } 165 | } 166 | } 167 | 168 | /// Division with Remainder where division returns the Nearest representable result. 169 | /// 170 | /// Unsigned Integer Examples: 171 | /// ```ignored 172 | /// # use algebraics::traits::DivRemNearest; 173 | /// // Division by zero 174 | /// assert_eq!((0u32).checked_div_rem_nearest(&0), None); 175 | /// // Quotient is rounded down since remainder can't be negative 176 | /// assert_eq!((5u32).div_rem_nearest(&8), (0, 5)); 177 | /// assert_eq!((8u32).div_rem_nearest(&5), (1, 3)); 178 | /// ``` 179 | /// 180 | /// Signed Integer Examples: 181 | /// ```ignored 182 | /// # use algebraics::traits::DivRemNearest; 183 | /// // Division by zero 184 | /// assert_eq!((0i32).checked_div_rem_nearest(&0), None); 185 | /// // Overflow 186 | /// assert_eq!((-0x80i8).checked_div_rem_nearest(&-1), None); 187 | /// // Quotient is rounded to nearest 188 | /// assert_eq!((4i32).div_rem_nearest(&9), (0, 4)); 189 | /// assert_eq!((5i32).div_rem_nearest(&9), (1, -4)); 190 | /// // Halfway cases are rounded towards zero 191 | /// assert_eq!((4i32).div_rem_nearest(&8), (0, 4)); 192 | /// assert_eq!((-15i32).div_rem_nearest(&10), (-1, -5)); 193 | /// ``` 194 | /// 195 | /// Rational Examples: 196 | /// ```ignored 197 | /// # use algebraics::traits::DivRemNearest; 198 | /// # use num_rational::{BigRational, ParseRatioError}; 199 | /// # use num_traits::Zero; 200 | /// let ratio = str::parse::; 201 | /// // Division by zero 202 | /// assert_eq!(ratio("0")?.checked_div_rem_nearest(&ratio("0")?), None); 203 | /// // All divisions produce a zero remainder 204 | /// assert_eq!(ratio("3/4")?.div_rem_nearest(&ratio("-5/7")?), (ratio("-21/20")?, ratio("0")?)); 205 | /// # Ok::<(), ParseRatioError>(()) 206 | /// ``` 207 | // FIXME: unignore doctest when pub 208 | pub(crate) trait DivRemNearest: Sized { 209 | type DivOutput; 210 | type RemOutput; 211 | fn checked_div_rem_nearest(&self, rhs: &Rhs) -> Option<(Self::DivOutput, Self::RemOutput)>; 212 | fn div_rem_nearest(&self, rhs: &Rhs) -> (Self::DivOutput, Self::RemOutput) { 213 | self.checked_div_rem_nearest(rhs).expect("division by zero") 214 | } 215 | } 216 | 217 | impl DivRemNearest for Ratio { 218 | type DivOutput = Ratio; 219 | type RemOutput = Ratio; 220 | fn checked_div_rem_nearest(&self, rhs: &Self) -> Option<(Self, Self)> { 221 | Some((self.checked_div(rhs)?, Zero::zero())) 222 | } 223 | } 224 | 225 | macro_rules! impl_div_rem_signed_int { 226 | ($t:ty, $u:ty) => { 227 | impl DivRemNearest for $t { 228 | type DivOutput = $t; 229 | type RemOutput = $t; 230 | fn checked_div_rem_nearest(&self, rhs: &$t) -> Option<($t, $t)> { 231 | let lhs = *self; 232 | let rhs = *rhs; 233 | if rhs == 0 { 234 | return None; // division by zero 235 | } 236 | if rhs == -1 && lhs == <$t>::min_value() { 237 | return None; // overflow 238 | } 239 | let mut quotient = lhs / rhs; 240 | let mut remainder = lhs % rhs; 241 | let rhs_magnitude = rhs.wrapping_abs() as $u; 242 | let remainder_magnitude = remainder.wrapping_abs() as $u; 243 | if rhs != <$t>::min_value() && remainder_magnitude > rhs_magnitude / 2 { 244 | let (quotient_offset, remainder_offset) = 245 | if rhs < 0 { (-1, rhs) } else { (1, -rhs) }; 246 | quotient = quotient.checked_add(quotient_offset)?; 247 | remainder = remainder.checked_add(remainder_offset)?; 248 | } 249 | Some((quotient, remainder)) 250 | } 251 | } 252 | }; 253 | } 254 | 255 | macro_rules! impl_div_rem_unsigned_int { 256 | ($t:ty) => { 257 | impl DivRemNearest for $t { 258 | type DivOutput = $t; 259 | type RemOutput = $t; 260 | fn checked_div_rem_nearest(&self, rhs: &$t) -> Option<($t, $t)> { 261 | if *rhs == 0 { 262 | return None; 263 | } 264 | Some((self / rhs, self % rhs)) 265 | } 266 | } 267 | }; 268 | } 269 | 270 | impl_div_rem_unsigned_int!(u8); 271 | impl_div_rem_unsigned_int!(u16); 272 | impl_div_rem_unsigned_int!(u32); 273 | impl_div_rem_unsigned_int!(u64); 274 | impl_div_rem_unsigned_int!(u128); 275 | impl_div_rem_unsigned_int!(usize); 276 | impl_div_rem_signed_int!(i8, u8); 277 | impl_div_rem_signed_int!(i16, u16); 278 | impl_div_rem_signed_int!(i32, u32); 279 | impl_div_rem_signed_int!(i64, u64); 280 | impl_div_rem_signed_int!(i128, u128); 281 | impl_div_rem_signed_int!(isize, usize); 282 | 283 | impl DivRemNearest for BigInt { 284 | type DivOutput = BigInt; 285 | type RemOutput = BigInt; 286 | fn checked_div_rem_nearest(&self, rhs: &BigInt) -> Option<(BigInt, BigInt)> { 287 | if rhs.is_zero() { 288 | return None; 289 | } 290 | let (mut quotient, mut remainder) = self.div_rem(rhs); 291 | let rhs_magnitude = rhs.abs(); 292 | let remainder_magnitude = remainder.abs(); 293 | if &rhs_magnitude - &remainder_magnitude > remainder_magnitude { 294 | if rhs.is_negative() { 295 | quotient -= 1; 296 | remainder -= rhs_magnitude; 297 | } else { 298 | quotient += 1; 299 | remainder += rhs_magnitude; 300 | } 301 | } 302 | Some((quotient, remainder)) 303 | } 304 | } 305 | 306 | impl DivRemNearest for BigUint { 307 | type DivOutput = BigUint; 308 | type RemOutput = BigUint; 309 | fn checked_div_rem_nearest(&self, rhs: &BigUint) -> Option<(BigUint, BigUint)> { 310 | if rhs.is_zero() { 311 | return None; 312 | } 313 | Some(self.div_rem(rhs)) 314 | } 315 | } 316 | 317 | pub trait IntegerBits { 318 | const BITS: usize; 319 | /// returns Self::BITS 320 | fn type_bits(&self) -> usize { 321 | Self::BITS 322 | } 323 | } 324 | 325 | macro_rules! impl_integer_bits { 326 | ($($t:ty),*) => { 327 | $( 328 | impl IntegerBits for $t { 329 | const BITS: usize = (0 as $t).trailing_zeros() as usize; 330 | } 331 | )* 332 | }; 333 | } 334 | 335 | impl_integer_bits!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize); 336 | 337 | pub trait FloorLog2: Integer { 338 | /// returns floor(log2(self)) if self is positive 339 | /// returns `None` for zero or negative 340 | fn floor_log2(&self) -> Option; 341 | } 342 | 343 | pub trait CeilLog2: Integer { 344 | /// returns ceil(log2(self)) if self is positive 345 | /// returns `None` for zero or negative 346 | fn ceil_log2(&self) -> Option; 347 | } 348 | 349 | pub trait TrailingZeros: Integer { 350 | /// returns number of trailing zero bits in the two's complement representation of `Self` 351 | /// returns `None` for zero 352 | fn trailing_zeros(&self) -> Option; 353 | } 354 | 355 | macro_rules! impl_prim_trailing_zeros_and_log2 { 356 | ($t:ty) => { 357 | impl FloorLog2 for $t { 358 | fn floor_log2(&self) -> Option { 359 | if *self <= 0 { 360 | None 361 | } else { 362 | Some(::BITS - self.leading_zeros() as usize - 1) 363 | } 364 | } 365 | } 366 | impl CeilLog2 for $t { 367 | fn ceil_log2(&self) -> Option { 368 | if *self <= 0 { 369 | None 370 | } else { 371 | Some(::BITS - (*self - 1).leading_zeros() as usize) 372 | } 373 | } 374 | } 375 | impl TrailingZeros for $t { 376 | fn trailing_zeros(&self) -> Option { 377 | if *self == 0 { 378 | None 379 | } else { 380 | Some((*self).trailing_zeros() as usize) 381 | } 382 | } 383 | } 384 | }; 385 | } 386 | 387 | impl_prim_trailing_zeros_and_log2!(u8); 388 | impl_prim_trailing_zeros_and_log2!(u16); 389 | impl_prim_trailing_zeros_and_log2!(u32); 390 | impl_prim_trailing_zeros_and_log2!(u64); 391 | impl_prim_trailing_zeros_and_log2!(u128); 392 | impl_prim_trailing_zeros_and_log2!(usize); 393 | impl_prim_trailing_zeros_and_log2!(i8); 394 | impl_prim_trailing_zeros_and_log2!(i16); 395 | impl_prim_trailing_zeros_and_log2!(i32); 396 | impl_prim_trailing_zeros_and_log2!(i64); 397 | impl_prim_trailing_zeros_and_log2!(i128); 398 | impl_prim_trailing_zeros_and_log2!(isize); 399 | 400 | impl CeilLog2 for BigUint { 401 | fn ceil_log2(&self) -> Option { 402 | if self.is_zero() { 403 | None 404 | } else { 405 | Some((self - 1u32).bits().try_into().unwrap()) 406 | } 407 | } 408 | } 409 | 410 | impl FloorLog2 for BigUint { 411 | fn floor_log2(&self) -> Option { 412 | if self.is_zero() { 413 | None 414 | } else { 415 | Some((self.bits() - 1).try_into().unwrap()) 416 | } 417 | } 418 | } 419 | 420 | impl TrailingZeros for BigUint { 421 | fn trailing_zeros(&self) -> Option { 422 | let ceil_log2 = self.ceil_log2(); 423 | // handle self == 0 by returning None 424 | let ceil_log2 = ceil_log2?; 425 | if let Some(v) = self.to_u32() { 426 | return Some(v.trailing_zeros() as usize); 427 | } 428 | let mut bit = 1; 429 | while bit < ceil_log2 { 430 | bit <<= 1; 431 | } 432 | let mut retval = 0; 433 | let mut value = self.clone(); 434 | while bit != 0 { 435 | let shifted_right = &value >> bit; 436 | if &shifted_right << bit == value { 437 | retval |= bit; 438 | value = shifted_right; 439 | } 440 | bit >>= 1; 441 | } 442 | Some(retval) 443 | } 444 | } 445 | 446 | impl CeilLog2 for BigInt { 447 | fn ceil_log2(&self) -> Option { 448 | if self.is_positive() { 449 | Some((self - 1u32).bits().try_into().unwrap()) 450 | } else { 451 | None 452 | } 453 | } 454 | } 455 | 456 | impl FloorLog2 for BigInt { 457 | fn floor_log2(&self) -> Option { 458 | if self.is_positive() { 459 | Some((self.bits() - 1).try_into().unwrap()) 460 | } else { 461 | None 462 | } 463 | } 464 | } 465 | 466 | impl TrailingZeros for BigInt { 467 | fn trailing_zeros(&self) -> Option { 468 | TrailingZeros::trailing_zeros( 469 | &self 470 | .abs() 471 | .to_biguint() 472 | .expect("abs should return non-negative values"), 473 | ) 474 | } 475 | } 476 | 477 | pub(crate) trait IsolatedRealRoot { 478 | fn root_polynomial(&self) -> &Polynomial; 479 | fn multiplicity(&self) -> usize; 480 | fn lower_bound(&self) -> &Ratio; 481 | fn upper_bound(&self) -> &Ratio; 482 | } 483 | 484 | #[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Debug)] 485 | pub struct CharacteristicZero; 486 | 487 | impl Add for CharacteristicZero { 488 | type Output = Self; 489 | fn add(self, _rhs: Self) -> Self { 490 | CharacteristicZero 491 | } 492 | } 493 | 494 | impl Zero for CharacteristicZero { 495 | fn zero() -> Self { 496 | CharacteristicZero 497 | } 498 | fn is_zero(&self) -> bool { 499 | true 500 | } 501 | } 502 | 503 | pub trait RingCharacteristic: Zero + One { 504 | type Type: Zero + fmt::Debug; 505 | fn characteristic(&self) -> Self::Type; 506 | } 507 | 508 | impl + Clone + Integer> RingCharacteristic 509 | for Ratio 510 | { 511 | type Type = CharacteristicZero; 512 | fn characteristic(&self) -> CharacteristicZero { 513 | CharacteristicZero 514 | } 515 | } 516 | 517 | macro_rules! impl_characteristic_zero { 518 | ($t:ty) => { 519 | impl RingCharacteristic for $t { 520 | type Type = CharacteristicZero; 521 | fn characteristic(&self) -> CharacteristicZero { 522 | CharacteristicZero 523 | } 524 | } 525 | }; 526 | } 527 | 528 | impl_characteristic_zero!(u8); 529 | impl_characteristic_zero!(i8); 530 | impl_characteristic_zero!(u16); 531 | impl_characteristic_zero!(i16); 532 | impl_characteristic_zero!(u32); 533 | impl_characteristic_zero!(i32); 534 | impl_characteristic_zero!(u64); 535 | impl_characteristic_zero!(i64); 536 | impl_characteristic_zero!(u128); 537 | impl_characteristic_zero!(i128); 538 | impl_characteristic_zero!(usize); 539 | impl_characteristic_zero!(isize); 540 | impl_characteristic_zero!(BigUint); 541 | impl_characteristic_zero!(BigInt); 542 | 543 | pub trait ExactDiv: Sized { 544 | type Output; 545 | #[must_use] 546 | fn exact_div(self, rhs: Rhs) -> Self::Output { 547 | self.checked_exact_div(rhs).expect("exact division failed") 548 | } 549 | #[must_use] 550 | fn checked_exact_div(self, rhs: Rhs) -> Option; 551 | } 552 | 553 | pub trait ExactDivAssign: ExactDiv { 554 | fn exact_div_assign(&mut self, rhs: Rhs) { 555 | if let Err(()) = self.checked_exact_div_assign(rhs) { 556 | panic!("exact division failed"); 557 | } 558 | } 559 | fn checked_exact_div_assign(&mut self, rhs: Rhs) -> Result<(), ()>; 560 | } 561 | 562 | /// division always produces exact results except for division by zero, overflow, or similar 563 | pub trait AlwaysExactDiv: 564 | ExactDiv + Div>::Output*/> 565 | { 566 | } 567 | 568 | /// division always produces exact results except for division by zero, overflow, or similar 569 | pub trait AlwaysExactDivAssign: 570 | AlwaysExactDiv 571 | + ExactDivAssign 572 | + DivAssign 573 | + ExactDiv 574 | + Div 575 | { 576 | } 577 | 578 | macro_rules! impl_exact_div_for_ratio { 579 | (($($lifetimes:tt),*), $t:ident, $lhs:ty, $rhs:ty) => { 580 | impl<$($lifetimes,)* $t> ExactDiv<$rhs> for $lhs 581 | where 582 | $t: Clone + Integer, 583 | { 584 | type Output = Ratio<$t>; 585 | fn exact_div(self, rhs: $rhs) -> Self::Output { 586 | self.div(rhs) 587 | } 588 | fn checked_exact_div(self, rhs: $rhs) -> Option { 589 | if rhs.is_zero() { 590 | None 591 | } else { 592 | Some(self.div(rhs)) 593 | } 594 | } 595 | } 596 | 597 | impl<$($lifetimes,)* $t> AlwaysExactDiv<$rhs> for $lhs 598 | where 599 | $t: Clone + Integer, 600 | { 601 | } 602 | }; 603 | (assign ($($lifetimes:tt),*), $t:ident, $lhs:ty, $rhs:ty) => { 604 | impl_exact_div_for_ratio!(($($lifetimes),*), $t, $lhs, $rhs); 605 | 606 | impl<$($lifetimes,)* $t> ExactDivAssign<$rhs> for $lhs 607 | where 608 | $t: Clone + Integer + NumAssign, 609 | { 610 | fn exact_div_assign(&mut self, rhs: $rhs) { 611 | self.div_assign(rhs); 612 | } 613 | fn checked_exact_div_assign(&mut self, rhs: $rhs) -> Result<(), ()> { 614 | if rhs.is_zero() { 615 | Err(()) 616 | } else { 617 | self.div_assign(rhs); 618 | Ok(()) 619 | } 620 | } 621 | } 622 | 623 | impl<$($lifetimes,)* $t> AlwaysExactDivAssign<$rhs> for $lhs 624 | where 625 | $t: Clone + Integer + NumAssign, 626 | { 627 | } 628 | }; 629 | } 630 | 631 | impl_exact_div_for_ratio!(assign(), T, Ratio, T); 632 | impl_exact_div_for_ratio!(assign(), T, Ratio, Ratio); 633 | impl_exact_div_for_ratio!(assign ('r), T, Ratio, &'r T); 634 | impl_exact_div_for_ratio!(assign ('r), T, Ratio, &'r Ratio); 635 | 636 | impl_exact_div_for_ratio!(('l), T, &'l Ratio, T); 637 | impl_exact_div_for_ratio!(('l), T, &'l Ratio, Ratio); 638 | impl_exact_div_for_ratio!(('l, 'r), T, &'l Ratio, &'r T); 639 | impl_exact_div_for_ratio!(('l, 'r), T, &'l Ratio, &'r Ratio); 640 | 641 | fn checked_div_rem(lhs: &T, rhs: &T) -> Option<(T, T)> { 642 | Some((lhs.checked_div(rhs)?, lhs.checked_rem(rhs)?)) 643 | } 644 | 645 | fn bigint_checked_div_rem(lhs: &T, rhs: &T) -> Option<(T, T)> { 646 | if rhs.is_zero() { 647 | None 648 | } else { 649 | Some(lhs.div_rem(rhs)) 650 | } 651 | } 652 | 653 | macro_rules! impl_exact_div_for_int { 654 | ($t:ident, $checked_div_rem:ident) => { 655 | impl<'l, 'r> ExactDiv<&'r $t> for &'l $t { 656 | type Output = $t; 657 | fn exact_div(self, rhs: &$t) -> $t { 658 | let (retval, remainder) = self.div_rem(rhs); 659 | assert!(remainder.is_zero(), "inexact division"); 660 | retval 661 | } 662 | fn checked_exact_div(self, rhs: &$t) -> Option<$t> { 663 | let (retval, remainder) = $checked_div_rem(self, rhs)?; 664 | if remainder.is_zero() { 665 | Some(retval) 666 | } else { 667 | None 668 | } 669 | } 670 | } 671 | 672 | impl ExactDiv<&'_ $t> for $t { 673 | type Output = $t; 674 | fn exact_div(self, rhs: &$t) -> $t { 675 | (&self).exact_div(rhs) 676 | } 677 | fn checked_exact_div(self, rhs: &$t) -> Option { 678 | (&self).checked_exact_div(rhs) 679 | } 680 | } 681 | 682 | impl ExactDiv<$t> for &'_ $t { 683 | type Output = $t; 684 | fn exact_div(self, rhs: $t) -> $t { 685 | self.exact_div(&rhs) 686 | } 687 | fn checked_exact_div(self, rhs: $t) -> Option { 688 | self.checked_exact_div(&rhs) 689 | } 690 | } 691 | 692 | impl ExactDiv<$t> for $t { 693 | type Output = $t; 694 | fn exact_div(self, rhs: $t) -> $t { 695 | (&self).exact_div(&rhs) 696 | } 697 | fn checked_exact_div(self, rhs: $t) -> Option { 698 | (&self).checked_exact_div(&rhs) 699 | } 700 | } 701 | 702 | impl ExactDivAssign<&'_ $t> for $t { 703 | fn exact_div_assign(&mut self, rhs: &$t) { 704 | *self = (&*self).exact_div(rhs); 705 | } 706 | fn checked_exact_div_assign(&mut self, rhs: &$t) -> Result<(), ()> { 707 | (&*self) 708 | .checked_exact_div(rhs) 709 | .map(|v| { 710 | *self = v; 711 | }) 712 | .ok_or(()) 713 | } 714 | } 715 | 716 | impl ExactDivAssign<$t> for $t { 717 | fn exact_div_assign(&mut self, rhs: $t) { 718 | *self = (&*self).exact_div(rhs); 719 | } 720 | fn checked_exact_div_assign(&mut self, rhs: $t) -> Result<(), ()> { 721 | (&*self) 722 | .checked_exact_div(rhs) 723 | .map(|v| { 724 | *self = v; 725 | }) 726 | .ok_or(()) 727 | } 728 | } 729 | }; 730 | } 731 | 732 | impl_exact_div_for_int!(u8, checked_div_rem); 733 | impl_exact_div_for_int!(i8, checked_div_rem); 734 | impl_exact_div_for_int!(u16, checked_div_rem); 735 | impl_exact_div_for_int!(i16, checked_div_rem); 736 | impl_exact_div_for_int!(u32, checked_div_rem); 737 | impl_exact_div_for_int!(i32, checked_div_rem); 738 | impl_exact_div_for_int!(u64, checked_div_rem); 739 | impl_exact_div_for_int!(i64, checked_div_rem); 740 | impl_exact_div_for_int!(u128, checked_div_rem); 741 | impl_exact_div_for_int!(i128, checked_div_rem); 742 | impl_exact_div_for_int!(usize, checked_div_rem); 743 | impl_exact_div_for_int!(isize, checked_div_rem); 744 | 745 | impl_exact_div_for_int!(BigInt, bigint_checked_div_rem); 746 | impl_exact_div_for_int!(BigUint, bigint_checked_div_rem); 747 | 748 | pub trait IntervalUnion { 749 | type Output; 750 | fn interval_union(self, rhs: Rhs) -> Self::Output; 751 | } 752 | 753 | pub trait IntervalUnionAssign { 754 | fn interval_union_assign(&mut self, rhs: Rhs); 755 | } 756 | 757 | #[cfg(test)] 758 | mod tests { 759 | use super::*; 760 | use num_traits::One; 761 | 762 | #[test] 763 | fn test_gcd_lcm() { 764 | let a = Ratio::new(12, 621_934i128); 765 | let b = Ratio::new(48, 12934); 766 | assert_eq!(a.gcd(&b), Ratio::new(6, 69_345_641)); 767 | assert_eq!(a.lcm(&b), Ratio::new(24, 29)); 768 | } 769 | 770 | #[test] 771 | fn test_trailing_zeros() { 772 | let one = BigUint::one(); 773 | for i in 0..=256u64 { 774 | for j in 0..=i { 775 | let v = (&one << dbg!(i)) | (&one << dbg!(j)); 776 | assert_eq!(v.trailing_zeros(), Some(j)); 777 | } 778 | } 779 | } 780 | 781 | #[test] 782 | fn test_ceil_log2() { 783 | assert_eq!(BigUint::zero().ceil_log2(), None); 784 | assert_eq!(BigInt::zero().ceil_log2(), None); 785 | assert_eq!(0i32.ceil_log2(), None); 786 | let one = BigUint::one(); 787 | assert_eq!(one.ceil_log2(), Some(0)); 788 | assert_eq!(BigInt::one().ceil_log2(), Some(0)); 789 | assert_eq!(1i32.ceil_log2(), Some(0)); 790 | for i in 0..=256usize { 791 | for j in 0..=i { 792 | let v = (&one << dbg!(i)) + (&one << dbg!(j)); 793 | assert_eq!(v.ceil_log2(), Some(i + 1)); 794 | assert_eq!(BigInt::from(v).ceil_log2(), Some(i + 1)); 795 | if i < 32 { 796 | if let Some(v) = (1u32 << i).checked_add(1 << j) { 797 | assert_eq!(v.ceil_log2(), Some(i + 1)); 798 | } 799 | } 800 | } 801 | } 802 | } 803 | 804 | #[test] 805 | fn test_floor_log2() { 806 | assert_eq!(BigUint::zero().floor_log2(), None); 807 | assert_eq!(BigInt::zero().floor_log2(), None); 808 | assert_eq!(0i32.floor_log2(), None); 809 | let one = BigUint::one(); 810 | assert_eq!(one.floor_log2(), Some(0)); 811 | assert_eq!(BigInt::one().floor_log2(), Some(0)); 812 | assert_eq!(1i32.floor_log2(), Some(0)); 813 | for i in 0..=256usize { 814 | for j in 0..=i { 815 | let v = (&one << dbg!(i)) | (&one << dbg!(j)); 816 | assert_eq!(v.floor_log2(), Some(i)); 817 | assert_eq!(BigInt::from(v).floor_log2(), Some(i)); 818 | if i < 32 { 819 | let v = (1u32 << i) | (1 << j); 820 | assert_eq!(v.floor_log2(), Some(i), "{:#x}", v); 821 | } 822 | } 823 | } 824 | } 825 | } 826 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-or-later 2 | # See Notices.txt for copyright information 3 | -------------------------------------------------------------------------------- /tests/test_real_algebraic_number.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: LGPL-2.1-or-later 2 | # See Notices.txt for copyright information 3 | 4 | from algebraics import RealAlgebraicNumber 5 | import unittest 6 | import math 7 | 8 | 9 | class TestRealAlgebraicNumber(unittest.TestCase): 10 | def test_construct(self): 11 | self.assertEqual(repr(RealAlgebraicNumber()), 12 | "") 16 | self.assertEqual(repr(RealAlgebraicNumber(42)), 17 | "") 22 | self.assertEqual(repr(RealAlgebraicNumber(-5)), 23 | "") 28 | self.assertEqual(repr(RealAlgebraicNumber(RealAlgebraicNumber(-5))), 29 | "") 34 | 35 | def test_trunc(self): 36 | self.assertEqual(math.trunc(RealAlgebraicNumber(123)), 123) 37 | self.assertEqual(math.trunc(RealAlgebraicNumber(123) / 10), 12) 38 | self.assertEqual(math.trunc(RealAlgebraicNumber(128) / 10), 12) 39 | self.assertEqual(math.trunc(RealAlgebraicNumber(123) 40 | ** (RealAlgebraicNumber(1) / 2)), 11) 41 | self.assertEqual(math.trunc(RealAlgebraicNumber(-123)), -123) 42 | self.assertEqual(math.trunc(RealAlgebraicNumber(-123) / 10), -12) 43 | self.assertEqual(math.trunc(RealAlgebraicNumber(-128) / 10), -12) 44 | self.assertEqual(math.trunc(-(RealAlgebraicNumber(123) 45 | ** (RealAlgebraicNumber(1) / 2))), -11) 46 | 47 | def test_floor(self): 48 | self.assertEqual(math.floor(RealAlgebraicNumber(123)), 123) 49 | self.assertEqual(math.floor(RealAlgebraicNumber(123) / 10), 12) 50 | self.assertEqual(math.floor(RealAlgebraicNumber(128) / 10), 12) 51 | self.assertEqual(math.floor(RealAlgebraicNumber(123) 52 | ** (RealAlgebraicNumber(1) / 2)), 11) 53 | self.assertEqual(math.floor(RealAlgebraicNumber(-123)), -123) 54 | self.assertEqual(math.floor(RealAlgebraicNumber(-123) / 10), -13) 55 | self.assertEqual(math.floor(RealAlgebraicNumber(-128) / 10), -13) 56 | self.assertEqual(math.floor(-(RealAlgebraicNumber(123) 57 | ** (RealAlgebraicNumber(1) / 2))), -12) 58 | 59 | def test_ceil(self): 60 | self.assertEqual(math.ceil(RealAlgebraicNumber(123)), 123) 61 | self.assertEqual(math.ceil(RealAlgebraicNumber(123) / 10), 13) 62 | self.assertEqual(math.ceil(RealAlgebraicNumber(128) / 10), 13) 63 | self.assertEqual(math.ceil(RealAlgebraicNumber(123) 64 | ** (RealAlgebraicNumber(1) / 2)), 12) 65 | self.assertEqual(math.ceil(RealAlgebraicNumber(-123)), -123) 66 | self.assertEqual(math.ceil(RealAlgebraicNumber(-123) / 10), -12) 67 | self.assertEqual(math.ceil(RealAlgebraicNumber(-128) / 10), -12) 68 | self.assertEqual(math.ceil(-(RealAlgebraicNumber(123) 69 | ** (RealAlgebraicNumber(1) / 2))), -11) 70 | 71 | def test_to_integer(self): 72 | self.assertEqual(RealAlgebraicNumber(123).to_integer(), 123) 73 | self.assertEqual((RealAlgebraicNumber(123) / 10).to_integer(), None) 74 | self.assertEqual((RealAlgebraicNumber(128) / 10).to_integer(), None) 75 | self.assertEqual((RealAlgebraicNumber(123) 76 | ** (RealAlgebraicNumber(1) / 2)).to_integer(), None) 77 | self.assertEqual(RealAlgebraicNumber(-123).to_integer(), -123) 78 | self.assertEqual((RealAlgebraicNumber(-123) / 10).to_integer(), None) 79 | self.assertEqual((RealAlgebraicNumber(-128) / 10).to_integer(), None) 80 | self.assertEqual((-(RealAlgebraicNumber(123) 81 | ** (RealAlgebraicNumber(1) / 2)) 82 | ).to_integer(), None) 83 | 84 | def test_to_rational(self): 85 | self.assertEqual(RealAlgebraicNumber(123).to_rational(), (123, 1)) 86 | self.assertEqual((RealAlgebraicNumber(123) / 10).to_rational(), 87 | (123, 10)) 88 | self.assertEqual((RealAlgebraicNumber(128) / 10).to_rational(), 89 | (64, 5)) 90 | self.assertEqual((RealAlgebraicNumber(123) 91 | ** (RealAlgebraicNumber(1) / 2)).to_rational(), None) 92 | self.assertEqual(RealAlgebraicNumber(-123).to_rational(), (-123, 1)) 93 | self.assertEqual((RealAlgebraicNumber(-123) / 10).to_rational(), 94 | (-123, 10)) 95 | self.assertEqual((RealAlgebraicNumber(-128) / 10).to_rational(), 96 | (-64, 5)) 97 | self.assertEqual((-(RealAlgebraicNumber(123) 98 | ** (RealAlgebraicNumber(1) / 2)) 99 | ).to_rational(), None) 100 | 101 | def test_minimal_polynomial(self): 102 | self.assertEqual(RealAlgebraicNumber(123).minimal_polynomial, 103 | [-123, 1]) 104 | self.assertEqual((RealAlgebraicNumber(123) / 10).minimal_polynomial, 105 | [-123, 10]) 106 | self.assertEqual((RealAlgebraicNumber(128) / 10).minimal_polynomial, 107 | [-64, 5]) 108 | self.assertEqual((RealAlgebraicNumber(123) 109 | ** (RealAlgebraicNumber(1) / 2)).minimal_polynomial, 110 | [-123, 0, 1]) 111 | self.assertEqual(RealAlgebraicNumber(-123).minimal_polynomial, 112 | [123, 1]) 113 | self.assertEqual((RealAlgebraicNumber(-123) / 10).minimal_polynomial, 114 | [123, 10]) 115 | self.assertEqual((RealAlgebraicNumber(-128) / 10).minimal_polynomial, 116 | [64, 5]) 117 | self.assertEqual((-(RealAlgebraicNumber(123) 118 | ** (RealAlgebraicNumber(1) / 2)) 119 | ).minimal_polynomial, 120 | [-123, 0, 1]) 121 | 122 | def test_degree(self): 123 | self.assertEqual(RealAlgebraicNumber(123).degree, 1) 124 | self.assertEqual((RealAlgebraicNumber(123) / 10).degree, 1) 125 | self.assertEqual((RealAlgebraicNumber(128) / 10).degree, 1) 126 | self.assertEqual((RealAlgebraicNumber(123) 127 | ** (RealAlgebraicNumber(1) / 2)).degree, 2) 128 | self.assertEqual(RealAlgebraicNumber(-123).degree, 1) 129 | self.assertEqual((RealAlgebraicNumber(-123) / 10).degree, 1) 130 | self.assertEqual((RealAlgebraicNumber(-128) / 10).degree, 1) 131 | self.assertEqual((-(RealAlgebraicNumber(123) 132 | ** (RealAlgebraicNumber(1) / 2)) 133 | ).degree, 2) 134 | self.assertEqual((-(RealAlgebraicNumber(123) 135 | ** (RealAlgebraicNumber(1) / 3)) 136 | ).degree, 3) 137 | self.assertEqual((-(RealAlgebraicNumber(123) 138 | ** (RealAlgebraicNumber(1) / 4)) 139 | ).degree, 4) 140 | 141 | def test_is_integer(self): 142 | self.assertEqual(RealAlgebraicNumber(123).is_integer(), True) 143 | self.assertEqual((RealAlgebraicNumber(123) / 10).is_integer(), False) 144 | self.assertEqual((RealAlgebraicNumber(128) / 10).is_integer(), False) 145 | self.assertEqual((RealAlgebraicNumber(123) 146 | ** (RealAlgebraicNumber(1) / 2)).is_integer(), False) 147 | self.assertEqual(RealAlgebraicNumber(-123).is_integer(), True) 148 | self.assertEqual((RealAlgebraicNumber(-123) / 10).is_integer(), False) 149 | self.assertEqual((RealAlgebraicNumber(-128) / 10).is_integer(), False) 150 | self.assertEqual((-(RealAlgebraicNumber(123) 151 | ** (RealAlgebraicNumber(1) / 2)) 152 | ).is_integer(), False) 153 | 154 | def test_is_rational(self): 155 | self.assertEqual(RealAlgebraicNumber(123).is_rational(), True) 156 | self.assertEqual((RealAlgebraicNumber(123) / 10).is_rational(), True) 157 | self.assertEqual((RealAlgebraicNumber(128) / 10).is_rational(), True) 158 | self.assertEqual((RealAlgebraicNumber(123) 159 | ** (RealAlgebraicNumber(1) / 2)).is_rational(), 160 | False) 161 | self.assertEqual(RealAlgebraicNumber(-123).is_rational(), True) 162 | self.assertEqual((RealAlgebraicNumber(-123) / 10).is_rational(), True) 163 | self.assertEqual((RealAlgebraicNumber(-128) / 10).is_rational(), True) 164 | self.assertEqual((-(RealAlgebraicNumber(123) 165 | ** (RealAlgebraicNumber(1) / 2)) 166 | ).is_rational(), False) 167 | 168 | def test_recip(self): 169 | self.assertEqual(RealAlgebraicNumber(123).recip().minimal_polynomial, 170 | [-1, 123]) 171 | self.assertEqual((RealAlgebraicNumber(123) / 10 172 | ).recip().minimal_polynomial, 173 | [-10, 123]) 174 | self.assertEqual((RealAlgebraicNumber(128) / 10 175 | ).recip().minimal_polynomial, 176 | [-5, 64]) 177 | self.assertEqual((-(RealAlgebraicNumber(123) 178 | ** (RealAlgebraicNumber(1) / 2)) 179 | ).recip().minimal_polynomial, 180 | [1, 0, -123]) 181 | 182 | def test_recip_zero(self): 183 | with self.assertRaises(ZeroDivisionError): 184 | RealAlgebraicNumber(0).recip() 185 | 186 | def test_add(self): 187 | self.assertEqual(RealAlgebraicNumber(1) + 2, 3) 188 | self.assertEqual(1 + RealAlgebraicNumber(2), 3) 189 | self.assertEqual(RealAlgebraicNumber(1) + RealAlgebraicNumber(2), 3) 190 | 191 | def test_sub(self): 192 | self.assertEqual(RealAlgebraicNumber(1) - 2, -1) 193 | self.assertEqual(1 - RealAlgebraicNumber(2), -1) 194 | self.assertEqual(RealAlgebraicNumber(1) - RealAlgebraicNumber(2), -1) 195 | 196 | def test_mul(self): 197 | self.assertEqual(RealAlgebraicNumber(1) * 2, 2) 198 | self.assertEqual(1 * RealAlgebraicNumber(2), 2) 199 | self.assertEqual(RealAlgebraicNumber(1) * RealAlgebraicNumber(2), 2) 200 | 201 | def test_div(self): 202 | self.assertEqual(RealAlgebraicNumber(1) / 2, 203 | RealAlgebraicNumber(1) / 2) 204 | self.assertEqual(1 / RealAlgebraicNumber(2), 205 | RealAlgebraicNumber(1) / 2) 206 | self.assertEqual(RealAlgebraicNumber(1) / RealAlgebraicNumber(2), 207 | RealAlgebraicNumber(1) / 2) 208 | 209 | def test_div_zero(self): 210 | with self.assertRaises(ZeroDivisionError): 211 | RealAlgebraicNumber(1) / 0 212 | with self.assertRaises(ZeroDivisionError): 213 | RealAlgebraicNumber(-1) / 0 214 | with self.assertRaises(ZeroDivisionError): 215 | RealAlgebraicNumber(0) / 0 216 | 217 | def test_pow(self): 218 | self.assertEqual(RealAlgebraicNumber(1) ** 2, 1) 219 | self.assertEqual(1 ** RealAlgebraicNumber(2), 1) 220 | self.assertEqual(RealAlgebraicNumber(1) ** RealAlgebraicNumber(2), 1) 221 | 222 | def test_neg(self): 223 | self.assertEqual(-RealAlgebraicNumber(1), -1) 224 | self.assertEqual(-RealAlgebraicNumber(-2), 2) 225 | 226 | def test_abs(self): 227 | self.assertEqual(abs(RealAlgebraicNumber(1)), 1) 228 | self.assertEqual(abs(RealAlgebraicNumber(-2)), 2) 229 | 230 | def test_floor_ceil_log2(self): 231 | with self.assertRaises(ValueError): 232 | RealAlgebraicNumber(0).floor_log2() 233 | with self.assertRaises(ValueError): 234 | RealAlgebraicNumber(0).ceil_log2() 235 | with self.assertRaises(ValueError): 236 | RealAlgebraicNumber(-1).floor_log2() 237 | with self.assertRaises(ValueError): 238 | RealAlgebraicNumber(-1).ceil_log2() 239 | self.assertEqual(RealAlgebraicNumber(1).floor_log2(), 0) 240 | self.assertEqual(RealAlgebraicNumber(1).ceil_log2(), 0) 241 | self.assertEqual(RealAlgebraicNumber(2).floor_log2(), 1) 242 | self.assertEqual(RealAlgebraicNumber(2).ceil_log2(), 1) 243 | self.assertEqual(RealAlgebraicNumber(3).floor_log2(), 1) 244 | self.assertEqual(RealAlgebraicNumber(3).ceil_log2(), 2) 245 | self.assertEqual(RealAlgebraicNumber(4).floor_log2(), 2) 246 | self.assertEqual(RealAlgebraicNumber(4).ceil_log2(), 2) 247 | self.assertEqual((RealAlgebraicNumber(1) / 4).floor_log2(), -2) 248 | self.assertEqual((RealAlgebraicNumber(1) / 4).ceil_log2(), -2) 249 | self.assertEqual((RealAlgebraicNumber(1) / 3).floor_log2(), -2) 250 | self.assertEqual((RealAlgebraicNumber(1) / 3).ceil_log2(), -1) 251 | 252 | 253 | if __name__ == '__main__': 254 | unittest.main() 255 | --------------------------------------------------------------------------------