├── Kronig-Penny Model Solution.ipynb ├── LICENSE ├── README.md ├── fourier_series.py ├── fourier_transform.py ├── golden_ratio_flower.py ├── lava_dome_conduit_flow.py ├── legendre_polynomial.py ├── montecarlo_parallel.py ├── montecarlomethods.py ├── polezero.py ├── random_matrices.py ├── rungekutta4.py ├── schrodinger1D.py ├── schrodinger2D.py ├── schrodinger3D.py ├── sompi_analysis.py └── transfer_matrix.py /Kronig-Penny Model Solution.ipynb: -------------------------------------------------------------------------------- 1 | {"nbformat":4,"nbformat_minor":0,"metadata":{"colab":{"name":"placeholder.ipynb","provenance":[],"collapsed_sections":[],"authorship_tag":"ABX9TyM756XYvzkypZtrqvZw0VnR"},"kernelspec":{"name":"python3","display_name":"Python 3"},"language_info":{"name":"python"}},"cells":[{"cell_type":"code","metadata":{"id":"uZmsocgH6CJY","colab":{"base_uri":"https://localhost:8080/","height":321},"executionInfo":{"status":"ok","timestamp":1617525766976,"user_tz":-540,"elapsed":2770,"user":{"displayName":"Yuki Natsume","photoUrl":"","userId":"14807480849120006303"}},"outputId":"c5c78e0e-1eb3-4f30-8cb8-d70d46aa3b13"},"source":["import numpy as np\n","import matplotlib.pyplot as plt\n","from scipy.optimize import fsolve\n","\n","a = 1\n","h = 1\n","m = 1\n","K0 = 2 * np.pi / a\n","k = 0.2 * K0 * 0\n","N = 10000\n","factor = -np.linspace(0.1, 10, N)\n","K = np.arange(-100 * np.pi, 100 * np.pi + K0, K0)\n","y = np.zeros(N)\n","x = np.zeros(N)\n","Ek = 1 / (2 * m) * h**2 * (k-K)**2\n","\n","def func(E, U0, Ek):\n"," V = U0 * np.sum(1 / (Ek - E)) + 1\n"," return V\n","\n","for i in range(N):\n"," U0 = factor[i]\n","\n"," result = fsolve(func, np.array([1]), args = (U0, Ek))\n"," y[i] = result\n"," x[i] = -1 / U0\n","\n","plt.figure(figsize = (15, 5))\n","plt.plot(x, y)\n","plt.grid(True)\n","plt.show()"],"execution_count":7,"outputs":[{"output_type":"display_data","data":{"image/png":"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\n","text/plain":["
"]},"metadata":{"tags":[],"needs_background":"light"}}]},{"cell_type":"code","metadata":{"id":"T5Ej2WWXC2KT"},"source":[""],"execution_count":null,"outputs":[]}]} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Computational Physics in Python 2 | 3 | This is a set of Python codes for performing various computational physics and numerical modelling tasks. 4 | -------------------------------------------------------------------------------- /fourier_series.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def make_fs_coeff(f, x, r): 4 | """ 5 | Calculates the sin and cos coefficients of the Fourier series 6 | 7 | Inputs: 8 | ------- 9 | f: function 10 | function to approximate with Fourier series 11 | x: np.array 12 | x axis variable of f(x) 13 | r: int 14 | order of the Fourier series 15 | 16 | Outputs: 17 | -------- 18 | coeffa: np.array 19 | np.array of cos coefficients 20 | coeffb: np.array 21 | np.array of sin coefficients 22 | """ 23 | coeffa = np.zeros(r) # coefficient of cosine terms 24 | coeffb = np.zeros(r) # coefficient of sine terms 25 | L = np.abs(x[-1] - x[0]) # 'Period' of Fourier Series 26 | 27 | for count in range(1, r+1, 1): 28 | g = f * np.cos(2 * np.pi * count * x / L) 29 | h = f * np.sin(2 * np.pi * count * x / L) 30 | coeffa[count-1] = 2.0 / L * np.trapz(g, x) 31 | coeffb[count-1] = 2.0 / L * np.trapz(h, x) 32 | 33 | return coeffa, coeffb 34 | 35 | def real_fourier_series(f, x, r = 5): 36 | """ 37 | Approximate function f with order r Fourier series 38 | 39 | Inputs: 40 | ------- 41 | f: function 42 | function to approximate 43 | x: np.array 44 | x axis variable of f(x) 45 | r: int 46 | order of the Fourier series 47 | 48 | Outputs: 49 | -------- 50 | F: np.array 51 | Fourier series approximated f 52 | """ 53 | coeffa, coeffb = make_fs_coeff(f, x, r) 54 | L = np.abs(x[-1] - x[0]) # 'Period' of Fourier Series 55 | F = np.zeros(len(x)) 56 | for count in range(1,r+1,1): 57 | F = F + coeffa[count-1] * np.cos(2 * np.pi * count * x / L) + \ 58 | coeffb[count-1] * np.sin(2 * np.pi * count * x / L) 59 | F = F + np.trapz(f, x) / L 60 | return F 61 | 62 | def make_cplx_fs_coeff(f, x, r): 63 | """ 64 | Calculates the real and imaginary coefficients of the Fourier series 65 | 66 | Inputs: 67 | ------- 68 | f: function 69 | function to approximate with Fourier series 70 | x: np.array 71 | x-axis variable of f(x) 72 | r: int 73 | order of the Fourier series 74 | 75 | Outputs: 76 | -------- 77 | coeffcr: np.array 78 | np.array of real coefficients 79 | coeffci: np.array 80 | np.array of imaginary coefficients 81 | """ 82 | # complex fourier series coefficients 83 | coeffcr = np.zeros(2*r+1) 84 | coeffci = np.zeros(2*r+1) 85 | L = np.abs(x[-1] - x[0]) # 'Period' of Fourier Series 86 | index = 0 87 | for count in range(-r, r+1, 1): 88 | C = 1.0 / L * np.trapz(f * np.exp(-2*np.pi*1j*count*x/L), x) 89 | coeffcr[index] = np.real(C) 90 | coeffci[index] = np.imag(C) 91 | index = index + 1 92 | return coeffcr, coeffci 93 | 94 | def cplx_fourier_series(f, x, r): 95 | """ 96 | Approximate function f with order r Fourier series 97 | 98 | Inputs: 99 | ------- 100 | f: function 101 | function to approximate 102 | x: np.array 103 | x-axis variable of f(x) 104 | r: int 105 | order of the Fourier series 106 | 107 | Outputs: 108 | -------- 109 | G: np.array 110 | Fourier series approximated f 111 | """ 112 | coeffcr, coeffci = make_cplx_fs_coeff(f, x, r) 113 | G = np.zeros(len(x)) 114 | L = np.abs(x[-1] - x[0]) # 'Period' of Fourier Series 115 | index = 0 116 | for count in range(-r,r+1,1): 117 | G = G+(coeffcr[index]+coeffci[index]*1j)*np.exp(2*np.pi*1j*count*x/L) 118 | index = index + 1 119 | return np.real(G) 120 | 121 | -------------------------------------------------------------------------------- /fourier_transform.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.fft import fft, ifft 3 | 4 | def nextpow2(n): 5 | """ 6 | returns next power of 2 as fft algorithms work fastest when the input data 7 | length is a power of 2 8 | 9 | Inputs 10 | ------ 11 | n: int 12 | next power of 2 to calculate for 13 | 14 | Returns 15 | ------- 16 | np.int(2 ** m_i): int 17 | next power of 2 18 | """ 19 | m_f = np.log2(n) 20 | m_i = np.ceil(m_f) 21 | return np.int(2 ** m_i) 22 | 23 | def dft(X): 24 | """ 25 | Discrete Fourier Transform. 26 | 27 | Inputs 28 | ------ 29 | X: np.array 30 | np.array of X values to be Fourier transformed. len(X) should be a power of 2 31 | 32 | Returns 33 | ------- 34 | x: np.array 35 | DFT of X 36 | """ 37 | N = len(X) 38 | x = np.zeros(N, 'complex') 39 | K = np.arange(0, N, 1) 40 | for n in range(0, N, 1): 41 | x[n] = np.dot(X, np.exp(1j * 2 * np.pi * K * n / N)) 42 | return x 43 | 44 | def idft(X): 45 | """ 46 | Inverse Discrete Fourier Transform. This is the inverse function of dft(). 47 | 48 | Inputs 49 | ------ 50 | X: np.array 51 | np.array of X values to be inverse Fourier transformed. len(X) should be a power of 2 52 | 53 | Returns 54 | ------- 55 | x: np.array 56 | DFT of X 57 | """ 58 | N = len(X) 59 | x = np.zeros(N, 'complex') 60 | 61 | K = np.arange(0, N, 1) 62 | for n in range(0, N, 1): 63 | x[n] = np.dot(X, np.exp(-1j * 2 * np.pi * K * n / N)) 64 | return x / N 65 | 66 | def FFT(y, t): 67 | """ 68 | FFT function returns the frequency range up to Nyquist frequency and absolute spectral magnitude. 69 | 70 | Inputs 71 | ------ 72 | y: np.array 73 | np.array of values to perform FFT 74 | t: np.array 75 | np.array of corresponding time values 76 | 77 | Returns 78 | ------- 79 | freq: np.array 80 | np.array of frequency values 81 | amp: np.array 82 | np.array of Fourier amplitudes 83 | """ 84 | dt = t[2] - t[1] 85 | Fs = 1.0 / dt 86 | L = len(y) 87 | Y = fft(y, L) * dt # dt should mathematically be included in the result! 88 | #amp=abs(Y)/(L/2) #FFT single sided spectrum 89 | amp = abs(Y) #or simply take the amplitude only? 90 | T = L * dt #1/T=Fs/L 91 | freq = np.arange(0, Fs / 2, 1 / T) # list frequencies up to Nyquist frequency 92 | # resize result vectors to match their lengths 93 | if len(freq) < len(amp): 94 | amp = amp[0:len(freq)] # make both vectors the same size 95 | elif len(amp) < len(freq): 96 | freq = freq[0:len(amp)] 97 | return freq, amp 98 | 99 | def CEPSTRUM(y, t): 100 | """ 101 | CEPSTRUM calculates the ceptram of a time series. The cepstrum is basically 102 | a fourier transform of a fourier transform and has units of time 103 | 104 | Inputs 105 | ------ 106 | y: np.array 107 | np.array of values to perform FFT 108 | t: np.array 109 | np.array of corresponding time values 110 | 111 | Returns 112 | ------- 113 | q: np.array 114 | np.array of quefrency values 115 | C: np.array 116 | np.array of Cepstral amplitudes 117 | """ 118 | dt = t[2] - t[1] 119 | #Fs = 1.0 / dt 120 | L = len(y) 121 | #Y = fft(y, L) 122 | #amp = np.abs(Y)/(L/2) # FFT single sided spectrum 123 | #T = L * dt #1/T=Fs/L 124 | #freq = np.arange(0, Fs / 2, 1 / T) # list frequencies up to Nyquist frequency 125 | #C=real(ifft(log(abs(fft(y))))) 126 | C = np.abs(ifft(np.log(np.abs(fft(y))**2)))**2 127 | NumUniquePts = int(np.ceil((L + 1) / 2)) 128 | C = C[0:NumUniquePts] 129 | q = np.arange(0, NumUniquePts, 1) * dt 130 | return q, C 131 | 132 | def SIDFT(X,D): 133 | """ 134 | this function demonstrates explicitly the shifted inverse DFT algorithm, but should not 135 | be used because of the extremely slow speed. Faster algorithms use the fact 136 | that FFT is symmetric. 137 | """ 138 | N=len(X) 139 | x=np.zeros(N,'complex') 140 | for n in range(0,N,1): 141 | for k in range(0,N,1): 142 | x[n]=x[n]+np.exp(-1j*2*np.pi*k*D/N)*X[k]*np.exp(1j*2*np.pi*k*n/N) 143 | return x/N 144 | 145 | def SHIFTFT(X,D): 146 | """ 147 | this function demonstrates explicitly the shifted DFT algorithm, but should not 148 | be used because of the extremely slow speed. Faster algorithms use the fact 149 | that FFT is symmetric. 150 | """ 151 | N=len(X) 152 | for k in range(N): 153 | X[k]=np.exp(-1j*2*np.pi*k*D/N)*X[k] 154 | return X 155 | -------------------------------------------------------------------------------- /golden_ratio_flower.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | phi = (1 + np.sqrt(5)) / 2.0 # golden ratio 5 | 6 | def golden_ratio_flower(NTURNS): 7 | """ 8 | Inspired by Numberphile video on the Golden Ratio: 9 | https://www.youtube.com/watch?v=sj8Sg8qnjOg&t=311s 10 | 11 | I wrote a short script to generate a "Golden Ratio Flower" as described in the 12 | Numberphile video in the youtube link. 13 | 14 | Inputs 15 | ------ 16 | NTURNS: float 17 | number of turns for each seed placement. Can be float 18 | """ 19 | theta = 360.0 / NTURNS 20 | theta = theta * np.pi / 180.0 21 | R = np.array([[np.cos(theta),-np.sin(theta)],[np.sin(theta),np.cos(theta)]]) #rotation matrix 22 | x = [] #to hold the data 23 | y = [] 24 | D = 1 25 | x.append(D) #Choose starting point to be (1,0), although theoretically 26 | y.append(0) #any starting point should work just as fine. 27 | NTRIES = int(NTURNS) * 100 #Use as many seeds as possible to make a nice dense flower. 28 | L = 1 #scaling factor because seeds cannot lie on top of each other. 29 | count = 0 30 | for i in range(NTRIES): 31 | [X,Y] = np.dot(R,np.array([x[i],y[i]])) 32 | count = count + 1 33 | if count >= NTURNS: 34 | count = 0 35 | L = L + D 36 | [X,Y] = np.dot(L*np.eye(2),np.array([X,Y])/np.sqrt(X**2 + Y**2)) 37 | x.append(X) 38 | y.append(Y) 39 | plt.plot(x,y,'k.') 40 | plt.axis('equal') 41 | plt.show() 42 | 43 | golden_ratio_flower(NTURNS = np.pi) 44 | -------------------------------------------------------------------------------- /lava_dome_conduit_flow.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | # This code solves the delay difference equations proposed by: 5 | # M. Nakanishi, T. Koyaguchi, "A stability analysis of a conduit flow model for lave dome eruptions", 6 | # Journal of Volcanology and Geothermal Research, 178, 46-57, 2008. 7 | # http://www.eri.u-tokyo.ac.jp/people/ichihara/vp2008plan/NakanishiKoyaguchi2008.pdf 8 | 9 | # The delay difference equations proposed by Nakanashi and Koyaguchi come in 2 different forms 10 | # depending on the current system conditions. 11 | 12 | def less_than_one(x, t, params, delayx): 13 | """ 14 | First set of DDE equations, when np.trapz(Q, T) <= 1 15 | 16 | Inputs 17 | ------ 18 | x: np.array 19 | variables to integrate. P and Q in this system. 20 | t: float 21 | time 22 | params: np.array 23 | [Qin, mu] DDE parameters 24 | delayx: np.array 25 | DDE function values at some previous time step 26 | Returns 27 | ------- 28 | ydot: np.array 29 | np.array of DDE values 30 | """ 31 | Qin = params[0] 32 | mu = params[1] 33 | ydot = np.zeros(len(x)) 34 | ydot[0] = Qin - x[1] # pressure P 35 | ydot[1] = x[1] / x[0] * (Qin - x[1] + (mu - 1) * (x[1] - delayx) * x[1]) # flux Q 36 | return ydot.copy() 37 | 38 | def more_than_one(x, t, params, delayx): 39 | """ 40 | Second set of DDE equations, when np.trapz(Q, T) > 1 41 | 42 | Inputs 43 | ------ 44 | x: np.array 45 | variables to integrate. P and Q in this system. 46 | t: float 47 | time step 48 | params: np.array 49 | [Qin, mu] DDE parameters 50 | delayx: np.array 51 | DDE solution values at some previous time step 52 | Returns 53 | ------- 54 | ydot: np.array 55 | np.array of DDE values 56 | """ 57 | Qin = params[0] 58 | ydot = np.zeros(len(x)) 59 | ydot[0] = Qin - x[1] # pressure P 60 | ydot[1] = Qin - x[1] # flux Q 61 | return ydot.copy() 62 | 63 | def rk4(t0, t1, y0, ydot_fun, params, delayx): 64 | """ 65 | Runge-Kutta-4 algorithm 66 | 67 | Inputs 68 | ------ 69 | t0: float 70 | start of time step 71 | t1: float 72 | end of time step 73 | y0: np.array 74 | DDE solution values at time step t0 75 | y_dot_fun: function 76 | function containing the DDE to integrate numerically 77 | params: np.array 78 | [Qin, mu] DDE parameters 79 | delayx: np.array 80 | DDE solution values at some previous time step 81 | Returns 82 | ------- 83 | y1: np.array 84 | DDE solution values at time step t1 85 | """ 86 | dt = t1 - t0 87 | k1 = dt * ydot_fun(y0, t0, params, delayx) 88 | k2 = dt * ydot_fun(y0 + 0.5 * k1, t0 + dt * 0.5, params, delayx) 89 | k3 = dt * ydot_fun(y0 + 0.5 * k2, t0 + dt * 0.5, params, delayx) 90 | k4 = dt * ydot_fun(y0 + k3, t0 + dt, params, delayx) 91 | return y0 + (k1 + 2 * k2 + 2 * k3 + k4) / 6.0 92 | 93 | def lava_dome_conduit_flow(Qin = 0.8, mu = 10, y0 = np.array([1.5, 1.5]), 94 | t_star = 1, dt = None, t_end = None, 95 | less_than_one = None, more_than_one = None, rk4 = None): 96 | """ 97 | Inputs 98 | ------ 99 | Qin: float 100 | DDE parameter for input flux 101 | mu: float 102 | DDE parameter for magma viscosity 103 | y0: np.array 104 | initial conditions for [P, Q] 105 | t_star: float 106 | time parameter. 107 | dt: np.float 108 | time step size. If set to None, dt will be calculated automatically using dt = t_star / 20000 109 | t_end: float 110 | end time. If set to None, t_end will be set automatically to 30 111 | less_than_one: function 112 | function containing the 1st set of DDEs to integrate when np.trapz(Q, T) <= 1 113 | more_than_one: function 114 | function containing the 2nd set of DDEs to integrate when np.trapz(Q, T) > 1 115 | rk4: function 116 | function containing the numerical integration algorithm 117 | 118 | Returns 119 | ------- 120 | t, Y: np.array 121 | np.arrays of time steps and corresponding integrated variables [P, Q] 122 | """ 123 | 124 | if (less_than_one is None) or (more_than_one is None) or (rk4 is None): 125 | return 126 | 127 | if dt is None: 128 | dt = t_star / 20000 # set time step size 129 | if t_end is None: 130 | t_end = 30 131 | 132 | delayindex = int(t_star / dt) # set the delay (history) index 133 | params = np.array([Qin, mu]) 134 | 135 | # length of time of the simulation 136 | t = np.arange(-t_star, t_end + dt, dt) 137 | Y = np.zeros([len(t), len(y0)]) 138 | 139 | # set initial conditions for the simulation 140 | Y[0, :] = y0.copy() 141 | 142 | # Create delay difference history (history has length of delayindex) 143 | for i in range(delayindex): 144 | y0[0] = y0[0] - 1.0 / delayindex # pressure P history 145 | y0[1] = y0[1] - 1.0 / delayindex # flux Q history 146 | Y[i+1, :] = y0.copy() 147 | 148 | # Actual RK4 loop to solve the delay differential equation 149 | for i in range(len(t) - 1 - delayindex): 150 | delayx = Y[i, 1].copy() 151 | T = t[i:i + delayindex + 1].copy() 152 | Q = Y[i:i + delayindex + 1, 1].copy() 153 | QT = np.trapz(Q, T) # check the value of the integral of Q with respect to T 154 | if QT > 1: 155 | # if np.trapz(Q, T) > 1, the DDE equation to integrate is more_than_one 156 | Y[i+1+delayindex, :] = rk4(t[i+delayindex], t[i+1+delayindex], Y[i+delayindex,:], more_than_one, params, delayx) 157 | else: 158 | # if np.trapz(Q, T) <= 1, the DDE equation to integrate is less_than_one 159 | Y[i+1+delayindex, :] = rk4(t[i+delayindex], t[i+1+delayindex], Y[i+delayindex,:], less_than_one, params, delayx) 160 | 161 | # don't forget to remove the (artificial) history from the data!!! 162 | return t[delayindex:].copy(), Y[delayindex:].copy() 163 | -------------------------------------------------------------------------------- /legendre_polynomial.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.special import factorial, binom 3 | 4 | def legendre_polynomial(n, x): 5 | """Input arguments: 6 | n: polynomial order 7 | x: array of x values over which to calculate the Legendre polynomial 8 | """ 9 | if np.remainder(n, 2)==0 and np.mod(n,1)==0 and abs(max(x)) <= 1: 10 | P = np.zeros(len(x)) 11 | p = int(n / 2.0) 12 | for m in range(p, n+1, 1): 13 | P = P + (-1)**(n-m)*binom(n,m)*factorial(2*m)/factorial(2*m-n)*x**(2*m-n) 14 | 15 | P = P / (2**n*factorial(n)) 16 | elif np.remainder(n, 2) != 0 and np.mod(n, 1) == 0 and abs(max(x)) <= 1: 17 | P = np.zeros(len(x)) 18 | p = int((n+1)/2.0) 19 | for m in range(p, n+1, 1): 20 | P = P + (-1)**(n-m)*binom(n,m)*factorial(2*m)/factorial(2*m-n)*x**(2*m-n) 21 | 22 | P = P / (2**n*factorial(n)) 23 | return P 24 | -------------------------------------------------------------------------------- /montecarlo_parallel.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from scipy import random 4 | import time 5 | random.seed() #seed the RNG. 6 | import multiprocessing as mp 7 | 8 | class Worker: 9 | def __init__(self,D, J, H, Nx, steps, warmup_steps, results): 10 | self.D = D # number of spatial dimensions. D = 2 or 3 only. 11 | self.J = J # coupling strength J 12 | self.H = H # external magnetic field H 13 | self.Nx = Nx # number of spins per dimension. We assume a cubic lattice 14 | self.steps = steps # number of MC steps 15 | self.warmup_steps = warmup_steps # number of MC warmup steps 16 | self.N = Nx ** D # total number of spins in cubic lattice 17 | self.k = 1 # Boltzmann constant kB 18 | self.results = results # results queue 19 | 20 | def __call__(self, q): 21 | """ 22 | Inputs 23 | ------ 24 | q: mp.JoinableQueue 25 | JoinableQueue of temperatures 26 | """ 27 | while True: 28 | t = q.get() 29 | if t is None: 30 | return 31 | 32 | print("Processing temperature: {}".format(t)) 33 | m = 0 34 | spin = np.ones(self.N) 35 | B = 1.0 / (self.k * t) 36 | 37 | pflip = self.calc_pflip(B) 38 | 39 | for n in range(self.warmup_steps): 40 | spin = self.isingmodel(spin, pflip) 41 | for n in range(self.steps): 42 | spin = self.isingmodel(spin, pflip) 43 | m = m + np.sum(spin) / self.N 44 | m = np.abs(m / self.steps) 45 | 46 | self.results.put([t, m]) 47 | 48 | q.task_done() 49 | 50 | def calc_pflip(self, B): 51 | """ 52 | Calculates the probability of spin flip 53 | 54 | Inputs 55 | ------ 56 | B: float 57 | 1/kT 58 | 59 | Returns 60 | ------- 61 | pflip: float 62 | probability of spin flip 63 | """ 64 | if self.D == 2: 65 | pflip = np.zeros([2, 5]) 66 | Si = 1 67 | Sj = -4 68 | for i in range(2): 69 | for j in range(5): 70 | pflip[i, j] = np.exp(2 * (self.H + self.J * Sj) * Si * -B) 71 | Sj = Sj + 2 72 | Si = -1 73 | Sj = -4 74 | elif self.D == 3: 75 | pflip = np.zeros([2, 7]) 76 | Si = 1 77 | Sj = -6 78 | for i in range(2): 79 | for j in range(7): 80 | pflip[i, j] = np.exp(2 * (self.H + self.J * Sj) * Si * -B) 81 | Sj = Sj + 2 82 | Si = -1 # "reset" Si 83 | Sj = -6 # reset Sj 84 | return pflip 85 | 86 | def isingmodel(self, spin, pflip): 87 | """ 88 | Wrapper function for the actual 2D or 3D Ising model. 89 | 90 | Inputs 91 | ------ 92 | spin: np.array 93 | np.array of spins 94 | pflip: float 95 | Probability of spin flip 96 | """ 97 | if self.D == 2: 98 | spin = self.ising2D(spin, pflip) 99 | elif self.D == 3: 100 | spin = self.ising3D(spin, pflip) 101 | return spin 102 | 103 | def ising2D(self, spin, pflip): 104 | """ 105 | Inputs 106 | ------ 107 | spin: np.array 108 | np.array of spins 109 | pflip: float 110 | Probability of spin flip 111 | """ 112 | N = self.Nx ** self.D 113 | Nx = self.Nx 114 | Ny = self.Nx 115 | r = int(random.random() * N) 116 | x = np.mod(r, Nx) 117 | y = r // Nx 118 | s0 = spin[r] 119 | s1 = spin[np.mod(x + 1, Nx) + y * Ny] 120 | s2 = spin[x + np.mod(y + 1, Ny) * Nx] 121 | s3 = spin[np.mod(x - 1 + Nx, Nx) + y * Nx] 122 | s4 = spin[x + np.mod(y - 1 + Ny, Ny) * Nx] 123 | neighbours = s1 + s2 + s3 + s4 124 | if s0 == 1: 125 | pfliprow = 0 126 | elif s0 == -1: 127 | pfliprow = 1 128 | if neighbours == -4: 129 | pflipcol = 0 130 | elif neighbours == -2: 131 | pflipcol = 1 132 | elif neighbours == 0: 133 | pflipcol = 2 134 | elif neighbours == 2: 135 | pflipcol = 3 136 | elif neighbours == 4: 137 | pflipcol = 4 138 | rand = random.random() 139 | if rand < pflip[pfliprow, pflipcol]: 140 | spin[r] = -spin[r] 141 | return spin 142 | 143 | def ising3D(self, spin, pflip): 144 | """ 145 | Inputs 146 | ------ 147 | spin: np.array 148 | np.array of spins 149 | pflip: float 150 | Probability of spin flip 151 | """ 152 | N = self.N 153 | Nx = self.Nx 154 | Ny = self.Nx 155 | Nz = self.Nx 156 | r = int(random.random() * N) 157 | x = np.mod(r, Nx) 158 | y = np.mod(r // Nx, Ny) 159 | z = r // Nx // Ny 160 | s0 = spin[r] 161 | s1 = spin[np.mod(x + 1, Nx) + y * Ny + z * Ny * Nz] 162 | s2 = spin[x + np.mod(y + 1, Ny) * Nx + z * Ny * Nz] 163 | s3 = spin[np.mod(x - 1 + Nx, Nx) + y * Nx + z * Ny * Nz] 164 | s4 = spin[x + np.mod(y - 1 + Ny, Ny) * Nx + z * Ny * Nz] 165 | s5 = spin[x + y * Ny + np.mod(z - 1, Nz) * Ny * Nz] 166 | s6 = spin[x + y * Ny + np.mod(z + 1, Nz) * Ny * Nz] 167 | neighbours = s1 + s2 + s3 + s4 + s5 + s6 168 | if s0 == 1: 169 | pfliprow = 0 170 | elif s0 == -1: 171 | pfliprow = 1 172 | if neighbours == -6: 173 | pflipcol = 0 174 | elif neighbours == -4: 175 | pflipcol = 1 176 | elif neighbours == -2: 177 | pflipcol = 2 178 | elif neighbours == 0: 179 | pflipcol = 3 180 | elif neighbours == 2: 181 | pflipcol = 4 182 | elif neighbours == 4: 183 | pflipcol = 5 184 | elif neighbours == 6: 185 | pflipcol = 6 186 | rand = random.random() 187 | if rand < pflip[pfliprow, pflipcol]: 188 | spin[r] = -spin[r] 189 | return spin 190 | 191 | 192 | def main(D=2,J=1,H=0,T=np.linspace(0.01,5,50),Nx=20,steps=100000,nprocs=2): 193 | """ 194 | Main driver function for the parallel process MC 195 | 196 | Inputs 197 | ------ 198 | D: int 199 | number of spatial dimensions 200 | J: int 201 | coupling strength 202 | H: float 203 | external magnetic field strength 204 | T: np.array 205 | np.array of temperatures to conduct the MC simulation over 206 | Nx: int 207 | number of spins on each side of the cubic crystal 208 | steps: int 209 | number of MC steps to take 210 | nprocs: int 211 | number of parallel processors to use 212 | 213 | Returns 214 | ------- 215 | T2: np.array 216 | temperatures 217 | M2: np.array 218 | magnetization 219 | """ 220 | starttime = time.time() 221 | 222 | q = mp.JoinableQueue() 223 | for t in T: 224 | q.put(t) 225 | 226 | results = mp.Queue() 227 | processes = [] 228 | for i in range(nprocs): 229 | worker=mp.Process(target=Worker(D,J,H,Nx,steps,steps,results),args=(q,),daemon=True) 230 | worker.start() 231 | processes.append(worker) 232 | q.join() 233 | 234 | T2 = [] 235 | M2 = [] 236 | for i in range(len(T)): 237 | t,m=results.get() 238 | T2.append(t) 239 | M2.append(m) 240 | T2 = np.array(T2) 241 | M2 = np.array(M2) 242 | sorted_indices = np.argsort(T2) 243 | T2 = T2[sorted_indices] 244 | M2 = M2[sorted_indices] 245 | print("Total time elapsed: {:.3f}s".format(time.time()-starttime)) 246 | 247 | return T2, M2 248 | 249 | def demo(): 250 | """ 251 | This is a demo on how to perform MC simulations using the functions above. 252 | By default, the demo demonstrates the functions using a 2D lattice. 253 | """ 254 | T, M = main(D = 2, J = 1, H = 0, T = np.linspace(0.01,10,100), Nx = 20, steps = 100000, nprocs = 2) 255 | plt.figure(figsize = (15, 5)) 256 | plt.plot(T, M) 257 | plt.xlabel("Temperature") 258 | plt.ylabel("Magnetism") 259 | plt.show() 260 | -------------------------------------------------------------------------------- /montecarlomethods.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | #from scipy import * 3 | import matplotlib.pyplot as plt 4 | #from scipy import random 5 | import time 6 | np.random.seed() # seed the RNG. 7 | 8 | # This code using the Monte-Carlo method to simulate the magnetic behavior of a 2 dimensional 9 | # crystal. 2 methods are listed here. 10 | # The first shows the Ising model solved using the Metropolis algorithm, and the second shows the 11 | # Ising model solved using the Heatbath algorithm. 12 | 13 | def ising_metropolis_2D(J, H, T, Nx, Ny, steps, warmup_steps): 14 | start = time.time() 15 | N = Nx * Ny # number of spins 16 | k = 1 # boltzmann constant 17 | M = np.zeros(len(T)) 18 | 19 | for t in range(len(T)): 20 | spin = np.ones(N) # reset the spins for each temperature 21 | print('Current temperature =', T[t]) 22 | B = 1 / (k * T[t]) 23 | pflip = np.zeros([2, 5]) # there are a total of 10 weight values, 2 for 1,-1 and 5 for -4,-2,0,2,4 24 | # for each value of T pre compute the weights so as to speed up the computing time: 25 | Si = 1 26 | Sj = -4 27 | for i in range(2): # 2 rows 28 | for j in range(5): # 5 columns 29 | pflip[i, j] = np.exp(2 * (H + J * Sj) * Si * -B) # probability of flipping the spin 30 | Sj = Sj + 2 # for -4,-2,0,2,4 31 | Si = -1 # "reset" Si to -1 for the second row: 32 | Sj = -4 # reset Sj to -4 again 33 | # pflip should have the form: 34 | 35 | # now for the warm up steps: 36 | for n in range(warmup_steps): 37 | spin = ising2D(Nx, Ny, spin, pflip) 38 | 39 | # now for the actual MC: 40 | for n in range(steps): 41 | spin = ising2D(Nx, Ny, spin, pflip) 42 | 43 | # together with the Monte Carlo steps, perform the "Measurements:" 44 | M[t] = M[t] + np.sum(spin) / N 45 | 46 | # take the average values of the measurements over all MC steps 47 | M[t] = np.abs(M[t] / steps) # take only the absolute values of M 48 | 49 | end = int(time.time() - start) 50 | print("Total time elapsed:", end) 51 | 52 | #plt.plot(T, M, '-o') 53 | #plt.xlabel('T') 54 | #plt.ylabel('M') 55 | #plt.show() 56 | return [T, M] 57 | 58 | def ising2D(Nx, Ny, spin, pflip): 59 | N = Nx * Ny 60 | r = int(np.random.random() * N) 61 | x = np.mod(r, Nx) 62 | y = r // Nx 63 | s0 = spin[r] 64 | s1 = spin[np.mod(x + 1, Nx) + y * Ny] 65 | s2 = spin[x + np.mod(y + 1, Ny) * Nx] 66 | s3 = spin[np.mod(x - 1 + Nx, Nx) + y * Nx] 67 | s4 = spin[x + np.mod(y - 1 + Ny, Ny) * Nx] 68 | neighbours = s1 + s2 + s3 + s4 69 | if s0 == 1: 70 | pfliprow = 0 71 | elif s0 == -1: 72 | pfliprow = 1 73 | if neighbours == -4: 74 | pflipcol = 0 75 | elif neighbours == -2: 76 | pflipcol = 1 77 | elif neighbours == 0: 78 | pflipcol = 2 79 | elif neighbours == 2: 80 | pflipcol = 3 81 | elif neighbours == 4: 82 | pflipcol = 4 83 | rand = np.random.random() 84 | if rand < pflip[pfliprow, pflipcol]: 85 | spin[r] = -spin[r] 86 | return spin 87 | 88 | 89 | def ising_metropolis_1D(J, H, T, N, steps, warmup_steps): 90 | start = time.time() 91 | k = 1 # boltzmann constant 92 | M = np.zeros(len(T)) 93 | 94 | for t in range(len(T)): 95 | spin = np.ones(N) # reset the spins for each temperature 96 | print('Current temperature =', T[t]) 97 | B = 1 / (k * T[t]) 98 | pflip = np.zeros([2, 3]) # there are a total of 10 weight values, 2 for 1,-1 and 5 for -2,0,2 99 | # for each value of T pre compute the weights so as to speed up the computing time: 100 | Si = 1 101 | Sj = -2 102 | for i in range(2): 103 | for j in range(3): 104 | pflip[i, j] = np.exp(2 * (H + J * Sj) * Si * -B) # probability of flipping the spin 105 | Sj = Sj + 2 # for -2,0,2 106 | Si = -1 # "reset" Si to -1 for the second row: 107 | Sj = -2 # reset Sj to -4 again 108 | # pflip should have the form: 109 | 110 | # now for the warm up steps: 111 | for n in range(warmup_steps): 112 | spin = ising1D(N, spin, pflip) 113 | 114 | # now for the actual MC: 115 | for n in range(steps): 116 | spin = ising1D(N, spin, pflip) 117 | 118 | # together with the Monte Carlo steps, perform the "Measurements:" 119 | M[t] = M[t] + np.sum(spin) / N 120 | 121 | # take the average values of the measurements over all MC steps 122 | M[t] = np.abs(M[t] / steps) # take only the absolute values of M 123 | end = int(time.time() - start) 124 | print("Total time elapsed:", end) 125 | 126 | #plt.plot(T, M, '-o') 127 | #plt.xlabel('T') 128 | #plt.ylabel('M') 129 | #plt.show() 130 | return [T,M] 131 | 132 | def ising1D(N, spin, pflip): 133 | r = int(np.random.random() * N) # randomly choose a lattice site 134 | s0 = spin[r] 135 | s1 = spin[mod(r + 1, N)] # S2 S0 S1 136 | s2 = spin[r - 1] 137 | neighbours = s1 + s2 138 | if s0 == 1: 139 | pfliprow = 0 # row 0 of pflip contains spins of 1 140 | elif s0 == -1: 141 | pfliprow = 1 # row 1 of pflip contains spins of -1 142 | if neighbours == -2: 143 | pflipcol = 0 # col 0 of pflip contains Sj = -4 144 | elif neighbours == 0: 145 | pflipcol = 1 # col 1 of pflip contains Sj = -2 146 | elif neighbours == 2: 147 | pflipcol = 2 # col 2 of pflip contains Sj = 0 148 | rand = np.random.random() # Test against weightage 149 | if rand < pflip[pfliprow, pflipcol]: 150 | spin[r] = -spin[r] # flip the spin 151 | return spin 152 | 153 | def ising_heatbath_1D(J, H, T, N, steps, warmup_steps): 154 | start = time.time() 155 | k = 1 # boltzmann constant 156 | M = np.zeros(len(T)) # magnetization vector 157 | 158 | for t in range(len(T)): 159 | spin = np.ones(N) # reset the spins to 1 for each temperature 160 | print('Current temperature =', T[t]) 161 | B = 1 / (k * T[t]) 162 | pflip = np.zeros(3) # there are a total of 3 weight values for -2,0,2 163 | # precompute the weights so as to speed up the computing time: 164 | Sj = -2 165 | for j in range(3): 166 | Hprime = (H + J * Sj) 167 | pflip[j] = np.exp(Hprime * B) / (np.exp(Hprime * B) + np.exp(Hprime * -B)) # probability of flipping spin up 168 | Sj = Sj + 2 169 | # now for the warm up steps: 170 | for n in range(warmup_steps): 171 | r = int(np.random.random() * N) # randomly choose a lattice site 172 | 173 | s0 = spin[r] 174 | s1 = spin[mod(r + 1, N)] # S2 S0 S1 175 | s2 = spin[r - 1] 176 | 177 | neighbours = s1 + s2 178 | if neighbours == -2: 179 | pflipcol = 0 # col 0 of pflip contains Sj = -4 180 | elif neighbours == 0: 181 | pflipcol = 1 # col 1 of pflip contains Sj = -2 182 | elif neighbours == 2: 183 | pflipcol = 2 # col 2 of pflip contains Sj = 0 184 | rand = np.random.random() # Test against weightage 185 | if rand < pflip[pflipcol]: 186 | spin[r] = 1 # flip the spin down 187 | else: 188 | spin[r] = -1 # flip the spin up 189 | 190 | # now for the actual MC: 191 | for n in range(steps): 192 | r = int(np.random.random() * N) # randomly choose a lattice site 193 | 194 | s0 = spin[r] 195 | s1 = spin[mod(r + 1, N)] # S2 S0 S1 196 | s2 = spin[r - 1] 197 | 198 | neighbours = s1 + s2 199 | if neighbours == -2: 200 | pflipcol = 0 # col 0 of pflip contains Sj = -4 201 | elif neighbours == 0: 202 | pflipcol = 1 # col 1 of pflip contains Sj = -2 203 | elif neighbours == 2: 204 | pflipcol = 2 # col 2 of pflip contains Sj = 0 205 | rand = np.random.random() # Test against weightage 206 | if rand < pflip[pflipcol]: 207 | spin[r] = 1 # flip the spin down 208 | else: 209 | spin[r] = -1 # flip the spin up 210 | 211 | # together with the Monte Carlo steps, perform the "Measurements:" 212 | M[t] = M[t] + np.sum(spin) / N 213 | 214 | # take the average values of the measurements over all MC steps 215 | M[t] = np.abs(M[t] / steps) 216 | 217 | end = int(time.time() - start) 218 | print("Total time elapsed:", end) 219 | 220 | #plt.plot(T, M, '-o') 221 | #plt.xlabel('T') 222 | #plt.ylabel('M') 223 | #plt.show() 224 | return [T, M] 225 | 226 | def ising_heatbath_2D(J, H, T, Nx, Ny, steps, warmup_steps): 227 | start = time.time() 228 | N = Nx * Ny # number of spins 229 | k = 1 # boltzmann constant 230 | M = np.zeros(len(T)) # magnetization vector 231 | 232 | for t in range(len(T)): 233 | spin = np.ones(N) # reset the spins to 1 for each temperature 234 | print('Current temperature =', T[t]) 235 | B = 1 / (k * T[t]) 236 | pflip = np.zeros(5) # there are a total of 5 weight values for -4,-2,0,2,4 237 | # precompute the weights so as to speed up the computing time: 238 | Sj = -4 239 | for j in range(5): 240 | Hprime = (H + J * Sj) 241 | pflip[j] = np.exp(Hprime * B) / (np.exp(Hprime * B) + np.exp(Hprime * -B)) # probability of flipping spin up 242 | Sj = Sj + 2 243 | # now for the warm up steps: 244 | for n in range(warmup_steps): 245 | r = int(np.random.random() * N) # random choose lattice site 246 | x = np.mod(r, Nx) # x-coordinate of spin i.e. column index 247 | y = r // Nx # use integer division since y is an integer i.e. row index 248 | # note that at the end of the day all the indices must be 1-d since the spins are in an array 249 | s1 = spin[np.mod(x + 1, Nx) + y * Ny] 250 | s2 = spin[x + np.mod(y + 1, Ny) * Nx] 251 | s3 = spin[np.mod(x - 1 + Nx, Nx) + y * Nx] 252 | s4 = spin[x + np.mod(y - 1 + Ny, Ny) * Nx] 253 | neighbours = s1 + s2 + s3 + s4 # sum of all the neighbouring spins 254 | if neighbours == -4: 255 | pflipcol = 0 # col 0 of pflip contains Sj = -4 256 | elif neighbours == -2: 257 | pflipcol = 1 # col 1 of pflip contains Sj = -2 258 | elif neighbours == 0: 259 | pflipcol = 2 # col 2 of pflip contains Sj = 0 260 | elif neighbours == 2: 261 | pflipcol = 3 # col 3 of pflip contains Sj = 2 262 | elif neighbours == 4: 263 | pflipcol = 4 # col 4 of pflip contains Sj = 4 264 | rand = np.random.random() # Test against weightage 265 | if rand < pflip[pflipcol]: 266 | spin[r] = 1 # flip the spin down 267 | else: 268 | spin[r] = -1 # flip the spin up 269 | 270 | # now for the actual MC: 271 | for n in range(steps): 272 | r = int(np.random.random() * N) # random choose lattice site 273 | x = np.mod(r, Nx) # x-coordinate of spin i.e. column index 274 | y = r // Nx # use integer division since y is an integer i.e. row index 275 | # note that at the end of the day all the indices must be 1-d since the spins are in an array 276 | s1 = spin[np.mod(x + 1, Nx) + y * Ny] 277 | s2 = spin[x + np.mod(y + 1, Ny) * Nx] 278 | s3 = spin[np.mod(x - 1 + Nx, Nx) + y * Nx] 279 | s4 = spin[x + np.mod(y - 1 + Ny, Ny) * Nx] 280 | neighbours = s1 + s2 + s3 + s4 281 | if neighbours == -4: 282 | pflipcol = 0 283 | elif neighbours == -2: 284 | pflipcol = 1 285 | elif neighbours == 0: 286 | pflipcol = 2 287 | elif neighbours == 2: 288 | pflipcol = 3 289 | elif neighbours == 4: 290 | pflipcol = 4 291 | rand = np.random.random() 292 | if rand < pflip[pflipcol]: 293 | spin[r] = 1 294 | else: 295 | spin[r] = -1 296 | 297 | # together with the Monte Carlo steps, perform the "Measurements:" 298 | M[t] = M[t] + np.sum(spin) / N 299 | 300 | # take the average values of the measurements over all MC steps 301 | M[t] = np.abs(M[t] / steps) 302 | end = int(time.time() - start) 303 | print("Total time elapsed:", end) 304 | 305 | #plt.plot(T, M, '-o') 306 | #plt.xlabel('T') 307 | #plt.ylabel('M') 308 | #plt.show() 309 | return [T,M] 310 | 311 | def ising_metropolis_ND(D, J, H, T, Nx, steps, warmup_steps): 312 | #D = no. of dimensions 313 | #J = spin coupling parameter 314 | #H = external field 315 | #T = temperature 316 | #Nx = no. of spins along 1 axis (this code assumes cubic periodic crystal) 317 | #steps, warmp_steps = monte carlo steps 318 | start = time.time() 319 | N = Nx ** D 320 | k = 1 321 | M = np.zeros(len(T)) 322 | if D == 1: 323 | print('One dimensional crystal') 324 | for t in range(len(T)): 325 | print('Current temperature:', t) 326 | spins = np.ones(Nx) 327 | elif D == 2: 328 | print('Two dimensional crystal') 329 | for t in range(len(T)): 330 | print('Current temperature:', T[t]) 331 | spins = np.ones((Nx, Nx)) 332 | B = 1 / (k * T[t]) 333 | pflip = np.zeros([2, 5]) # there are a total of 10 weight values, 2 for 1,-1 and 5 for -4,-2,0,2,4 334 | # for each value of T pre compute the weights so as to speed up the computing time: 335 | Si = 1 336 | Sj = -4 337 | for i in range(2): # 2 rows 338 | for j in range(5): # 5 columns 339 | pflip[i, j] = np.exp(2 * (H + J * Sj) * Si * -B) # probability of flipping the spin 340 | Sj = Sj + 2 # for -4,-2,0,2,4 341 | Si = -1 # "reset" Si to -1 for the second row: 342 | Sj = -4 # reset Sj to -4 again 343 | # pflip should have the form: 344 | for n in range(warmup_steps): 345 | x = int(np.random.random() * Nx) 346 | y = int(np.random.random() * Nx) 347 | s0 = spins[y, x] 348 | s1 = spins[y, np.mod(x+1, Nx)] 349 | s2 = spins[np.mod(y+1, Nx), x] 350 | s3 = spins[y, x - 1] 351 | s4 = spins[y - 1, x] 352 | neighbours = s1 + s2 + s3 + s4 353 | if s0 == 1: 354 | pfliprow = 0 355 | elif s0 == -1: 356 | pfliprow = 1 357 | if neighbours == -4: 358 | pflipcol = 0 359 | elif neighbours == -2: 360 | pflipcol = 1 361 | elif neighbours == 0: 362 | pflipcol = 2 363 | elif neighbours == 2: 364 | pflipcol = 3 365 | elif neighbours == 4: 366 | pflipcol = 4 367 | rand = np.random.random() 368 | if rand < pflip[pfliprow, pflipcol]: 369 | spins[y, x] = -spins[y, x] 370 | for n in range(steps): 371 | x = int(np.random.random() * Nx) 372 | y = int(np.random.random() * Nx) 373 | s0 = spins[y, x] 374 | s1 = spins[y, np.mod(x+1, Nx)] 375 | s2 = spins[np.mod(y+1, Nx), x] 376 | s3 = spins[y, x - 1] 377 | s4 = spins[y - 1, x] 378 | neighbours = s1 + s2 + s3 + s4 379 | if s0 == 1: 380 | pfliprow = 0 381 | elif s0 == -1: 382 | pfliprow = 1 383 | if neighbours == -4: 384 | pflipcol = 0 385 | elif neighbours == -2: 386 | pflipcol = 1 387 | elif neighbours == 0: 388 | pflipcol = 2 389 | elif neighbours == 2: 390 | pflipcol = 3 391 | elif neighbours == 4: 392 | pflipcol = 4 393 | rand = np.random.random() 394 | if rand < pflip[pfliprow, pflipcol]: 395 | spins[y, x] = -spins[y, x] 396 | # together with the Monte Carlo steps, perform the "Measurements:" 397 | M[t] = M[t] + np.sum(spins) / N 398 | # take the average values of the measurements over all MC steps 399 | M[t] = np.abs(M[t] / steps) 400 | end = int(time.time() - start) 401 | print("Total time elapsed:", end) 402 | return T, M 403 | elif D == 3: 404 | print('Three dimensional crystal') 405 | for t in range(len(T)): 406 | print('Current temperature:', T[t]) 407 | spins = np.ones((Nx, Nx, Nx)) 408 | B = 1 / (k * T[t]) 409 | pflip = np.zeros([2, 7]) # there are a total of 10 weight values, 2 for 1,-1 and 7 for -6,-4,-2,0,2,4,6 410 | # for each value of T pre compute the weights so as to speed up the computing time: 411 | Si = 1 412 | Sj = -6 413 | for i in range(2): 414 | for j in range(7): 415 | pflip[i, j] = np.exp(2 * (H + J * Sj) * Si * -B) # probability of flipping the spin 416 | Sj = Sj + 2 417 | Si = -1 # "reset" Si 418 | Sj = -6 # reset Sj 419 | for n in range(warmup_steps): 420 | x = int(np.random.random() * Nx) 421 | y = int(np.random.random() * Nx) 422 | z = int(np.random.random() * Nx) 423 | s0 = spins[z, y, x] 424 | s1 = spins[z, y, np.mod(x + 1, Nx)] 425 | s2 = spins[z, np.mod(y + 1, Nx), x] 426 | s3 = spins[z, y, x - 1] 427 | s4 = spins[z, y - 1, x] 428 | s5 = spins[np.mod(z + 1, Nx), y, x] 429 | s6 = spins[z - 1, y, x] 430 | neighbours = s1 + s2 + s3 + s4 + s5 + s6 431 | if s0 == 1: 432 | pfliprow = 0 433 | elif s0 == -1: 434 | pfliprow = 1 435 | if neighbours == -6: 436 | pflipcol = 0 437 | elif neighbours == -4: 438 | pflipcol = 1 439 | elif neighbours == -2: 440 | pflipcol = 2 441 | elif neighbours == 0: 442 | pflipcol = 3 443 | elif neighbours == 2: 444 | pflipcol = 4 445 | elif neighbours == 4: 446 | pflipcol = 5 447 | elif neighbours == 6: 448 | pflipcol = 6 449 | rand = np.random.random() 450 | if rand < pflip[pfliprow, pflipcol]: 451 | spins[z, y, x] = -spins[z, y, x] 452 | for n in range(steps): 453 | x = int(np.random.random() * Nx) 454 | y = int(np.random.random() * Nx) 455 | z = int(np.random.random() * Nx) 456 | s0 = spins[z, y, x] 457 | s1 = spins[z, y, np.mod(x + 1, Nx)] 458 | s2 = spins[z, np.mod(y + 1, Nx), x] 459 | s3 = spins[z, y, x - 1] 460 | s4 = spins[z, y - 1, x] 461 | s5 = spins[np.mod(z + 1, Nx), y, x] 462 | s6 = spins[z - 1, y, x] 463 | neighbours = s1 + s2 + s3 + s4 + s5 + s6 464 | if s0 == 1: 465 | pfliprow = 0 466 | elif s0 == -1: 467 | pfliprow = 1 468 | if neighbours == -6: 469 | pflipcol = 0 470 | elif neighbours == -4: 471 | pflipcol = 1 472 | elif neighbours == -2: 473 | pflipcol = 2 474 | elif neighbours == 0: 475 | pflipcol = 3 476 | elif neighbours == 2: 477 | pflipcol = 4 478 | elif neighbours == 4: 479 | pflipcol = 5 480 | elif neighbours == 6: 481 | pflipcol = 6 482 | rand = np.random.random() 483 | if rand < pflip[pfliprow, pflipcol]: 484 | spins[z, y, x] = -spins[z, y, x] 485 | M[t] = M[t] + np.sum(spins) / N 486 | M[t] = np.abs(M[t] / steps) # take only the absolute values of M 487 | print("Total time elapsed:", time.time()-start) 488 | return T, M 489 | else: 490 | print('Please choose a value of D = 1, 2 3...') 491 | return 492 | 493 | 494 | def ising_metropolis_3D(J, H, T, Nx, Ny, Nz, steps, warmup_steps): 495 | start = time.time() 496 | N = Nx * Ny * Nz # number of spins 497 | k = 1 # boltzmann constant 498 | M = np.zeros(len(T)) 499 | 500 | for t in range(len(T)): 501 | spin = np.ones(N) # reset the spins for each temperature 502 | print('Current temperature =', T[t]) 503 | B = 1 / (k * T[t]) 504 | pflip = np.zeros([2, 7]) # there are a total of 10 weight values, 2 for 1,-1 and 7 for -6,-4,-2,0,2,4,6 505 | # for each value of T pre compute the weights so as to speed up the computing time: 506 | Si = 1 507 | Sj = -6 508 | for i in range(2): 509 | for j in range(7): 510 | pflip[i, j] = np.exp(2 * (H + J * Sj) * Si * -B) # probability of flipping the spin 511 | Sj = Sj + 2 512 | Si = -1 # "reset" Si 513 | Sj = -6 # reset Sj 514 | # pflip should have the form: 515 | 516 | # now for the warm up steps: 517 | for n in range(warmup_steps): 518 | spin = ising3D(Nx, Ny, Nz, spin, pflip) 519 | 520 | # now for the actual MC: 521 | for n in range(steps): 522 | spin = ising3D(Nx, Ny, Nz, spin, pflip) 523 | 524 | # together with the Monte Carlo steps, perform the "Measurements:" 525 | M[t] = M[t] + np.sum(spin) / N 526 | 527 | # take the average values of the measurements over all MC steps 528 | M[t] = np.abs(M[t] / steps) # take only the absolute values of M 529 | end = int(time.time() - start) 530 | print("Total time elapsed:", end) 531 | 532 | #plt.plot(T, M, '-o') 533 | #plt.xlabel('T') 534 | #plt.ylabel('M') 535 | #plt.show() 536 | return [T, M] 537 | 538 | def ising3D(Nx, Ny, Nz, spin, pflip): 539 | N = Nx * Ny * Nz 540 | r = int(np.random.random() * N) 541 | x = np.mod(r, Nx) 542 | y = np.mod(r // Nx, Ny) 543 | z = r // Nx // Ny 544 | s0 = spin[r] 545 | s1 = spin[np.mod(x + 1, Nx) + y * Ny + z * Ny * Nz] 546 | s2 = spin[x + np.mod(y + 1, Ny) * Nx + z * Ny * Nz] 547 | s3 = spin[np.mod(x - 1 + Nx, Nx) + y * Nx + z * Ny * Nz] 548 | s4 = spin[x + np.mod(y - 1 + Ny, Ny) * Nx + z * Ny * Nz] 549 | s5 = spin[x + y * Ny + np.mod(z - 1, Nz) * Ny * Nz] 550 | s6 = spin[x + y * Ny + np.mod(z + 1, Nz) * Ny * Nz] 551 | neighbours = s1 + s2 + s3 + s4 + s5 + s6 552 | if s0 == 1: 553 | pfliprow = 0 554 | elif s0 == -1: 555 | pfliprow = 1 556 | if neighbours == -6: 557 | pflipcol = 0 558 | elif neighbours == -4: 559 | pflipcol = 1 560 | elif neighbours == -2: 561 | pflipcol = 2 562 | elif neighbours == 0: 563 | pflipcol = 3 564 | elif neighbours == 2: 565 | pflipcol = 4 566 | elif neighbours == 4: 567 | pflipcol = 5 568 | elif neighbours == 6: 569 | pflipcol = 6 570 | rand = np.random.random() 571 | if rand < pflip[pfliprow, pflipcol]: 572 | spin[r] = -spin[r] 573 | return spin 574 | ################################################################################################# 575 | #FUNCTION CALLS TO TEST THE FUNCTION: 576 | """ 577 | [T2, M2] = ising_metropolis_2D(1.0, 0.0, np.linspace(0.01, 5, 50), 20, 20, 100000, 100000); 578 | [TH2, MH2] = ising_heatbath_2D(1.0, 0.0, np.linspace(0.01, 5, 50), 20, 20, 100000, 100000); 579 | [TX, MX] = ising_metropolis_ND(3, 1, 0, np.linspace(0.01, 10, 100), 20, 100000, 100000); 580 | """ -------------------------------------------------------------------------------- /polezero.py: -------------------------------------------------------------------------------- 1 | #for reference this line is exactly 80 characters long######################### 2 | from __future__ import division 3 | from scipy import * 4 | import matplotlib.pyplot as plt 5 | from scipy import linalg 6 | from scipy import signal 7 | 8 | #This script loads a seismometer response file with the following format: 9 | """ 10 | POLES 11 | REAL_P1 IMAG_P1 12 | ... 13 | ZEROS 14 | REAL_Z1 IMAG_Z1 15 | CONSTANT 16 | """ 17 | #and calculates the corresponding complex seismometer response 18 | 19 | def readpzfile(pzfilename): 20 | f=open(pzfilename,'r') 21 | X=f.readlines() 22 | f.close() 23 | NPOLES=int(X[0][6]) 24 | POLES=zeros(NPOLES,'complex') 25 | for i in range(NPOLES): 26 | buf=X[i+1] 27 | buf=buf.strip().split() 28 | POLES[i]=float(buf[0])+float(buf[1])*1j 29 | NZEROS=int(X[NPOLES+1][6]) 30 | ZEROS=zeros(NZEROS,'complex') 31 | for i in range(NZEROS): 32 | buf=X[i+NPOLES+2] 33 | buf=buf.strip().split() 34 | if buf[0]=='CONSTANT': 35 | break 36 | else: 37 | ZEROS[i]=float(buf[0])+float(buf[1])*1j 38 | CONSTANT=float(X[-1].strip().split()[1]) 39 | return POLES, ZEROS, CONSTANT 40 | 41 | #pzfilename='/users/user/kirishima_invert/g3t100' 42 | #pzfilename='/users/user/kirishima_invert/tri120p' 43 | #pzfilename='/users/user/kirishima_invert/tri40' 44 | #pzfilename='/users/user/kirishima_invert/usgsdst' 45 | pzfilename='/home/user/kirishima_invert/tri120p' 46 | #pzfilename='/home/user/kirishima_invert/g3t100' 47 | #pzfilename='/home/user/kirishima_invert/none' 48 | POLES,ZEROS,CONSTANT=readpzfile(pzfilename) 49 | 50 | #apparently in some cases the poles and zeros are all in Hz, 51 | #so to convert to Rad/s multiply by 2*pi 52 | #POLES=POLES*2*pi 53 | #ZEROS=ZEROS*2*pi 54 | 55 | print "Poles:", POLES 56 | print "Zeros:", ZEROS 57 | print "Constant:", CONSTANT 58 | 59 | #calculate the gain and phase by substituting for s the value 60 | #s = i omega = i 2 pi f and then calculate the modulus of H: 61 | ds=0.01 62 | s=arange(0,100,ds)*1j #this needs to be imaginary! 63 | #s has units of Rad/s 64 | H=ones(len(s)) 65 | 66 | #multiple the zeros 67 | for i in range(len(ZEROS)): 68 | H=H*(s-ZEROS[i]) 69 | 70 | #multiple the poles: 71 | for i in range(len(POLES)): 72 | H=H/(s-POLES[i]) 73 | 74 | #obtain the final seismometer response: 75 | H=H*CONSTANT 76 | 77 | #obtain the phase and amplitude information from the 78 | #complex transfer function 79 | 80 | pha=angle(H) 81 | amp=abs(H) 82 | mag=20*log10(amp) 83 | 84 | plt.subplot(2,1,1) 85 | plt.semilogx(imag(s),mag) 86 | plt.ylabel('Amplitude') 87 | plt.grid('on') 88 | plt.subplot(2,1,2) 89 | plt.semilogx(imag(s),pha/pi*180) 90 | plt.ylabel('Phase') 91 | plt.grid('on') 92 | plt.xlabel('Frequency') 93 | plt.show() 94 | -------------------------------------------------------------------------------- /random_matrices.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | np.random.seed() 4 | 5 | # This script explores several properties of random numbers and random matrices 6 | 7 | def exp_dist(lambd = 1): 8 | """ 9 | This function samples from the exponential distribution using only the 10 | random.random() random number generator 11 | 12 | Inputs 13 | ------ 14 | lambd: float 15 | exponential distribution parameter lambda 16 | 17 | Returns 18 | ------- 19 | x: float 20 | sampled point from the exponential distribution 21 | """ 22 | # note that random.random range: 0 <= r <= 1 23 | x = -1.0 / lambd * np.log(np.random.random() / lambd) 24 | # I use the inversion technique to obtain this equation which samples 25 | # correctly from the exponential distribution 26 | return x 27 | 28 | def exp_dist_histogram(lambd = 1, n_iter = 10000, n_bins = 100): 29 | """ 30 | This function generates a histogram of multiple samples from the exponential 31 | distribution and plots the analytical solution on with the numeric solution 32 | 33 | Inputs 34 | ------ 35 | lambd: float 36 | exponential distribution parameter lambda 37 | n_iter: int 38 | number of iterations to complete 39 | n_bins: int 40 | number of bins to use in the histogram 41 | 42 | Returns 43 | ------- 44 | x: float 45 | sampled point from the exponential distribution 46 | """ 47 | result = np.linspace(1, n_iter, n_iter) 48 | for i in range(n_iter): 49 | result[i] = exp_dist(lambd) 50 | 51 | # generate line plot of p(x) 52 | x = np.linspace(0, max(result), 1000) 53 | y = lambd * np.exp(-lambd * x) 54 | 55 | plt.figure(figsize=(15, 5)) 56 | plt.hist(result, n_bins, facecolor='b', density=1) 57 | plt.plot(x, y, 'r') 58 | plt.show() 59 | 60 | def expmu_histogram(lambd = 1, n_iter = 10000, n_bins = 100): 61 | """ 62 | This function generates multiple subplots of histograms of the mean of the 63 | independent random variables sampled from the exponenential distribution. 64 | The histograms are plotted together with the analytical solution 65 | to the central limit theorem 66 | 67 | Inputs 68 | ------ 69 | lambd: float 70 | exponential distribution parameter lambda 71 | n_iter: int 72 | number of iterations to complete 73 | n_bins: int 74 | number of bins to use in the histogram 75 | 76 | Returns 77 | ------- 78 | x: float 79 | sampled point from the exponential distribution 80 | """ 81 | # number of samples: 82 | N = np.array([5, 10, 20]) 83 | # create a vector to hold the results 84 | result = np.zeros([len(N), n_iter]) 85 | for a in range(n_iter): 86 | for b in range(len(N)): 87 | for c in range(N[b]): 88 | result[b][a] = result[b][a] + exp_dist(lambd) 89 | result[b][a] = result[b][a] / float(N[b]) # prevent integer division 90 | 91 | # Plotting of the analytical result: 92 | x = np.linspace(-result.min(), result.max(), 1000) 93 | # this allows linspace to scale the values of x according to the lambd value 94 | mu = lambd ** -1 # analytical mean 95 | p = np.zeros([len(N), len(x)]) 96 | 97 | plt.figure(figsize=(15, 5)) 98 | for i in range(len(N)): 99 | sig2 = 1.0 / (N[i] * lambd ** 2) # analytical variance which depends on N 100 | p[i, :] = ((2 * np.pi * sig2) ** -0.5) * np.exp(-(x - mu) ** 2 / 2.0 * sig2 ** -1) 101 | plt.subplot(len(N), 1, i + 1) 102 | plt.hist(result[i, :], n_bins, density=1) 103 | plt.plot(x, p[i, :]) 104 | 105 | plt.show() 106 | # comments: as N gets larger and larger, the distribution tends to a 107 | # Gaussian distribution with corresponding mean and variance. This is the 108 | # essence of the central limit theorem. 109 | 110 | def goe_eigs(N = 10): 111 | """ 112 | This function generates the eigenvalues from the gaussian orthogonal ensemble 113 | random matrices 114 | 115 | Inputs 116 | ------ 117 | N: int 118 | size of random matrix 119 | 120 | Returns 121 | ------- 122 | D: np.array 123 | eigenvalues 124 | """ 125 | A = np.random.randn(N, N) 126 | B = 2 ** -0.5 * (A + A.T) 127 | D = np.linalg.eigvals(B) 128 | D = np.real(D) 129 | return D 130 | 131 | def bernoulli_eigs(N = 10): 132 | """ 133 | This function generates the eigenvalues from the symmetric bernoulli ensemble 134 | random matrices 135 | 136 | Inputs 137 | ------ 138 | N: int 139 | size of random matrix 140 | 141 | Returns 142 | ------- 143 | D: np.array 144 | eigenvalues 145 | """ 146 | B = np.zeros([N, N]) 147 | # The following fills B[i][j] and B[j][i] with either 1 or -1 with 148 | # equal probabilities. The resulting matrix is symmetric 149 | for i in range(N): 150 | for j in range(N): 151 | if i >= j & i != j: 152 | x = np.random.random() 153 | if x <= 0.5: 154 | B[i][j] = 1 155 | B[j][i] = 1 156 | else: 157 | B[i][j] = -1 158 | B[j][i] = -1 159 | D = np.linalg.eigvals(B) # D = eigenvalues vector 160 | D = np.real(D) 161 | return D 162 | 163 | def gue_eigs(N = 10): 164 | """ 165 | This function generates the eigenvalues from the gaussian unitary ensemble 166 | random matrices 167 | 168 | Inputs 169 | ------ 170 | N: int 171 | size of random matrix 172 | 173 | Returns 174 | ------- 175 | D: np.array 176 | eigenvalues 177 | """ 178 | A = np.random.randn(N, N) + 1j * np.random.randn(N, N) 179 | B = (2 ** -1) * (A + np.conj(A.T)) 180 | D = np.linalg.eigvals(B) 181 | D = np.real(D) 182 | return D 183 | 184 | def random_matrix_histogram(N = 10, fun = goe_eigs, n_iter = 1000, n_bins = 50): 185 | """ 186 | This function generates the histogram for an arbitrary random matrix ensemble 187 | 188 | Inputs 189 | ------ 190 | N: int 191 | size of the random number matrix 192 | fun: object 193 | random matrix function 194 | n_iter: int 195 | number of iterations to execute 196 | n_bins: int 197 | number of bins to use in the histogram 198 | """ 199 | b = np.zeros([n_iter, N]) 200 | for i in range(n_iter): 201 | b[i, :] = fun(N) # output vector is placed into one of the rows of the result matrix 202 | bnorm = b * N ** -0.5 # obtain the normalised eigenvalues 203 | # Before passing this matrix of eigenvalues into hist(), we need to 204 | # reshape it into a vector: 205 | breshape = bnorm.reshape(np.size(bnorm), 1) # reshape into column vector 206 | 207 | # To obtain eig spacing: 208 | DELTA = np.sort(bnorm) # sort works row-wise i.e. sorts elements in each row only 209 | DELTA = np.diff(DELTA) # diff works on the elements on each row only 210 | DELTA = DELTA.reshape(1, np.size(DELTA)) # reshape into a row vector 211 | mDELTA = np.mean(DELTA) # obtain mean of DELTA i.e. eig spacing 212 | # I realise mean works faster on row rather than column vectors 213 | DELTA = DELTA * (mDELTA ** -1) # divide by the mean value 214 | DELTA = DELTA.reshape(np.size(DELTA), 1) # reshape back to column vector 215 | # for some reason hist does not accept row vectors as input, hence the need 216 | # for column vectors in the input of hist (at least on my computer) 217 | 218 | plt.subplot(2, 1, 1) # two plots - Wigner's Semicircle and Surmise 219 | plt.hist(breshape, n_bins, density=1) 220 | x = np.linspace(-2, 2, 1000) 221 | pw = 1.0 * (2 * np.pi) ** -1 * (4 - x ** 2) ** 0.5 # Wigner Semi Circle Law 222 | plt.plot(x, pw, 'r') 223 | delta = np.linspace(0, 3, 1000) 224 | fw = np.pi * delta * 0.5 * np.exp((-np.pi * delta ** 2) * 0.25) # Wigner Surmise 225 | # For the GUE ensemble, the following Surmise should be used instead: 226 | # c.f. Wigner surmise for mixed symmetry classes in random matrix theory 227 | # Sebastian Schierenberg, Falk Bruckmann, and Tilo Wettig 228 | # fw=32*pi**-2*delta**2*exp(-4*pi**-1*delta**2) 229 | plt.subplot(2, 1, 2) 230 | plt.hist(DELTA, n_bins, density=1) 231 | plt.plot(delta, fw, 'r') 232 | plt.ylim([0, 1]) 233 | plt.xlim([0, 5]) 234 | plt.show() 235 | -------------------------------------------------------------------------------- /rungekutta4.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | # This script demonstrates explicitly the Runge-Kutta 4 algorithm to perform 5 | # numerical integration of differential equations. 6 | # Some well known nonlinear systems are included as demonstrators. 7 | # For actual industrial/scientific research, specialised libraries should 8 | # be used instead! 9 | # For example scipy.integrate 10 | 11 | def ode_steps(ydot_fun, y0, t, params, step_fun): 12 | """ 13 | This function uses a for loop to loop over all time steps for numerical 14 | integration, calling the rk4 function for the actual integration steps 15 | 16 | Inputs: 17 | ------- 18 | ydot_fun: function 19 | ODE function to integrate 20 | y0: np.array 21 | np.array of initial values 22 | t: np.array 23 | np.array of time values 24 | params: np.array 25 | np.array of ydot_fun parameters 26 | step_fun: function 27 | discretised step function to use for numerical integration 28 | 29 | Returns: 30 | -------- 31 | Y: np.array 32 | np.array of numerically integrated results 33 | """ 34 | 35 | Y = np.zeros([len(t), len(y0)]) 36 | Y[0, :] = y0 37 | 38 | for i in range(len(t) - 1): 39 | Y[i + 1, :] = step_fun(t[i], t[i + 1], Y[i, :], ydot_fun, params) 40 | 41 | return Y 42 | 43 | def rk4(t0, t1, y0, xdot_fun, params): 44 | """ 45 | This function contains the actual Runge-Kutta 4th order integration steps 46 | 47 | Inputs: 48 | ------- 49 | t0: float 50 | time at start of time step 51 | t1: float 52 | time at end of time step 53 | y0: np.array 54 | y values at the start of the time step 55 | xdot_fun: function 56 | ODE function to integrate numerically 57 | params: np.array 58 | np.array of parameters of xdot_fun 59 | 60 | Returns: 61 | -------- 62 | y1: np.array 63 | y vaues at the end of the time step 64 | """ 65 | dt = t1 - t0 66 | k1 = (dt) * xdot_fun(y0, t0, params) 67 | k2 = (dt) * xdot_fun(y0 + 0.5 * k1, t0 + (dt) * 0.5, params) 68 | k3 = (dt) * xdot_fun(y0 + 0.5 * k2, t0 + (dt) * 0.5, params) 69 | k4 = (dt) * xdot_fun(y0 + k3, t0 + (dt), params) 70 | y1 = y0 + (k1 + 2 * k2 + 2 * k3 + k4) / 6.0 71 | return y1 72 | 73 | def plot(Y, x_ax, y_ax): 74 | """ 75 | Plot the numerical integration results 76 | 77 | Inputs 78 | ------ 79 | Y: np.array 80 | np.array of values to plot 81 | x_ax: 0, 1 or 2 82 | which axis to plot on the graph x axis 83 | y_ax: 0, 1 or 2 84 | which axis to plot on the graph y axis 85 | """ 86 | plt.figure(figsize=(8, 8)) 87 | plt.plot(Y[:, x_ax], Y[:, y_ax]) 88 | 89 | if x_ax == 0: 90 | x_ax = 'x' 91 | elif x_ax == 1: 92 | x_ax = 'y' 93 | elif x_ax == 2: 94 | x_ax = 'z' 95 | if y_ax == 0: 96 | y_ax = 'x' 97 | elif y_ax == 1: 98 | y_ax = 'y' 99 | elif y_ax == 2: 100 | y_ax = 'z' 101 | 102 | plt.xlabel(x_ax) 103 | plt.ylabel(y_ax) 104 | plt.grid('on') 105 | 106 | plt.show() 107 | 108 | def lorentz_plot_demo(x_ax = 0, y_ax = 2): 109 | """ 110 | Lorenz, E. N. (1963) Journal of the Atmospheric Sciences. 20 (2): 130–141 111 | 112 | Inputs 113 | ------ 114 | x_ax: 0, 1 or 2 115 | which axis to plot on the graph x axis 116 | y_ax: 0, 1 or 2 117 | which axis to plot on the graph y axis 118 | """ 119 | x0 = np.array([1, 2, 3]) 120 | t = np.arange(0, 100, 0.001) 121 | s = 10.0 122 | B = 8.0 / 3.0 123 | p = 28.0 124 | params = np.array([s, B, p]) 125 | 126 | def xdot_fun(x, t, params): 127 | xdot = np.zeros(len(x)) 128 | xdot[0] = params[0] * (x[1] - x[0]) # x 129 | xdot[1] = x[0] * (params[2] - x[2]) - x[1] # y 130 | xdot[2] = x[0] * x[1] - params[1] * x[2] # z 131 | return xdot 132 | 133 | Y = ode_steps(xdot_fun, x0, t, params, rk4) 134 | Y = Y[int(len(Y)*0.2):] 135 | 136 | plot(Y, x_ax, y_ax) 137 | 138 | def rossler_plot_demo(x_ax = 0, y_ax = 1): 139 | """ 140 | Rossler, O. E. (1976) Physics Letters, 57A (5): 397–398 141 | 142 | Inputs 143 | ------ 144 | x_ax: 0, 1 or 2 145 | which axis to plot on the graph x axis 146 | y_ax: 0, 1 or 2 147 | which axis to plot on the graph y axis 148 | """ 149 | x0 = np.array([1, 2, 3]) 150 | t = np.arange(0, 1000, 0.01) 151 | a = 0.1 152 | b = 0.1 153 | c = 13 154 | params = np.array([a, b, c]) 155 | 156 | def xdot_fun(x, t, params): 157 | a = params[0] 158 | b = params[1] 159 | c = params[2] 160 | xdot = np.zeros(len(x)) 161 | xdot[0] = -x[1] - x[2] #x 162 | xdot[1] = x[0] + a * x[1] #y 163 | xdot[2] = b + x[2] * (x[0] - c) #z 164 | return xdot 165 | 166 | Y = ode_steps(xdot_fun, x0, t, params, rk4) 167 | Y = Y[int(len(Y)*0.75):] 168 | 169 | plot(Y, x_ax, y_ax) 170 | 171 | def julian_plot_demo(x_ax = 1, y_ax = 2): 172 | """ 173 | Julian, B. R. (1994) Volcanic tremor: Nonlinear excitation by fluid flow, 174 | J. geophys. Res., 99(B6), 11859–11877 175 | 176 | Inputs 177 | ------ 178 | x_ax: 0, 1 or 2 179 | which axis to plot on the graph x axis 180 | y_ax: 0, 1 or 2 181 | which axis to plot on the graph y axis 182 | """ 183 | x0 = np.array([1, 2, 3]) 184 | t = np.arange(0, 100, 0.001) 185 | 186 | k = 600 * 10**6 187 | M = (3 * 10**5) * 0 188 | rho = 2500 189 | eta = 50 190 | p2 = 0.1 * 10**6 191 | p1 = 18 * 10**6 192 | h0 = 1 193 | L = 10 194 | A = (10**7) * 1 195 | 196 | params = np.array([k, M, rho, eta, p2, p1, h0, L, A]) 197 | 198 | def xdot_fun(x, t, params): 199 | k = params[0] 200 | M = params[1] 201 | rho = params[2] 202 | eta = params[3] 203 | p2 = params[4] 204 | p1 = params[5] 205 | h0 = params[6] 206 | L = params[7] 207 | A = params[8] 208 | 209 | xdot = np.zeros(len(x)) 210 | effectm = M + rho * L**3 / 12 / x[1] 211 | damping = A + L**3 / 12 / x[1] * (12 * eta / x[1]**2 - rho / 2 * x[2] / x[1]) 212 | kcoeff = k * (x[1] - h0) 213 | Lcoeff = L * (p1 + p2) / 2 - L * rho * x[0]**2 / 2 214 | 215 | xdot[0] = (p1-p2)/(rho*L)-(12*eta*x[0])/(rho*x[1]**2) 216 | xdot[1] = x[2] 217 | xdot[2] = (Lcoeff-kcoeff-damping*x[2])/effectm 218 | 219 | return xdot 220 | 221 | Y = ode_steps(xdot_fun, x0, t, params, rk4) 222 | Y = Y[int(len(Y)*0.75):] 223 | 224 | plot(Y, x_ax, y_ax) 225 | -------------------------------------------------------------------------------- /schrodinger1D.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from scipy import sparse 4 | from scipy.sparse import linalg as sla 5 | 6 | 7 | def schrodinger1D(xmin, xmax, Nx, Vfun, params, neigs = 20, findpsi = False): 8 | """ 9 | Solves the 1 dimensional Schrodinger equation numerically. 10 | 11 | Inputs 12 | ------ 13 | xmin: float 14 | Minimum value of the x axis. 15 | xmax: float 16 | Maximum value of the x axis. 17 | Nx: int 18 | Number of finite elements in the x axis. 19 | Vfun: function 20 | Potential energy function. 21 | params: list 22 | List containing the parameters of Vfun. 23 | neigs: int 24 | Number of eigenvalues to find. Should be 1 or more. 25 | findpsi: bool 26 | If True, the eigen wavefunctions will be calculated and returned. 27 | If False, only the eigen energies will be found. 28 | 29 | Returns 30 | ------- 31 | evl: np.array 32 | Array of solved eigen-energies. 33 | evt: np.array 34 | Array of solved eigen-wavefunctions. 35 | x: np.array 36 | Array of corresponding x axis values. 37 | """ 38 | # For this code we are using Dirichlet Boundary Conditions. 39 | x = np.linspace(xmin, xmax, Nx) # x axis grid. 40 | dx = x[1] - x[0] # x axis step size. 41 | 42 | # Obtain the potential function values: 43 | V = Vfun(x, params) 44 | 45 | # Create the Hamiltonian matrix: 46 | H = sparse.eye(Nx, Nx, format = "lil") * 2 47 | for i in range(Nx - 1): 48 | #H[i, i] = 2 49 | H[i, i + 1] = -1 50 | H[i + 1, i] = -1 51 | #H[-1, -1] = 2 52 | H = H / (dx ** 2) 53 | 54 | # Add the potential into the Hamiltonian 55 | for i in range(Nx): 56 | H[i, i] = H[i, i] + V[i] 57 | # convert to csc matrix format 58 | H = H.tocsc() 59 | 60 | # Obtain neigs solutions from the sparse matrix 61 | [evl, evt] = sla.eigs(H, k = neigs, which = "SM") 62 | 63 | for i in range(neigs): 64 | # Normalize the eigen vectors. 65 | evt[:, i] = evt[:, i] / np.sqrt( 66 | np.trapz(np.conj( 67 | evt[:, i]) * evt[:, i], x)) 68 | # Eigen values MUST be real: 69 | evl = np.real(evl) 70 | if findpsi == False: 71 | return evl 72 | else: 73 | return evl, evt, x 74 | 75 | 76 | def eval_wavefunctions(xmin, xmax, Nx, Vfun, params, neigs, findpsi = True): 77 | """ 78 | Evaluates the wavefunctions given a particular potential energy function Vfun. 79 | 80 | Inputs 81 | ------ 82 | xmin: float 83 | Minimum value of the x axis. 84 | xmax: float 85 | Maximum value of the x axis. 86 | Nx: int 87 | Number of finite elements in the x axis. 88 | Vfun: function 89 | Potential energy function. 90 | params: list 91 | List containing the parameters of Vfun. 92 | neigs: int 93 | Number of eigenvalues to find. 94 | findpsi: bool 95 | If True, the eigen wavefunctions will be calculated and returned. 96 | If False, only the eigen energies will be found. 97 | """ 98 | H = schrodinger1D(xmin, xmax, Nx, Vfun, params, neigs, findpsi) 99 | evl = H[0] # Energy eigen values. 100 | indices = np.argsort(evl) 101 | 102 | print("Energy eigenvalues:") 103 | for i,j in enumerate(evl[indices]): 104 | print("{}: {:.2f}".format(i + 1, j)) 105 | 106 | evt = H[1] # eigen vectors 107 | x = H[2] # x dimensions 108 | i = 0 109 | 110 | plt.figure(figsize = (8, 8)) 111 | while i < neigs: 112 | n = indices[i] 113 | y = np.real(np.conj(evt[:, n]) * evt[:, n]) 114 | plt.subplot(neigs, 1, i+1) 115 | plt.plot(x, y) 116 | plt.axis('off') 117 | i = i + 1 118 | plt.show() 119 | 120 | 121 | def sho_wavefunctions_plot(xmin = -10, xmax = 10, Nx = 500, neigs = 20, params = [1]): 122 | """ 123 | Plots the 1D quantum harmonic oscillator wavefunctions. 124 | 125 | Inputs 126 | ------ 127 | xmin: float 128 | minimum value of the x axis 129 | xmax: float 130 | maximum value of the x axis 131 | Nx: int 132 | number of finite elements in the x axis 133 | neigs: int 134 | number of eigenvalues to find 135 | params: list 136 | list containing the parameters of Vfun 137 | """ 138 | def Vfun(x, params): 139 | V = params[0] * x**2 140 | return V 141 | 142 | eval_wavefunctions(xmin, xmax, Nx, Vfun, params, neigs, True) 143 | 144 | 145 | def infinite_well_wavefunctions_plot(xmin = -10, xmax = 10, Nx = 500, neigs = 20, params = 1e10): 146 | """ 147 | Plots the 1D infinite well wavefunctions. 148 | 149 | Inputs 150 | ------ 151 | xmin: float 152 | minimum value of the x axis 153 | xmax: float 154 | maximum value of the x axis 155 | Nx: int 156 | number of finite elements in the x axis 157 | neigs: int 158 | number of eigenvalues to find 159 | params: float 160 | parameter of Vfun 161 | """ 162 | def Vfun(x, params): 163 | V = x * 0 164 | V[:100]=params 165 | V[-100:]=params 166 | return V 167 | 168 | eval_wavefunctions(xmin, xmax, Nx, Vfun, params, neigs, True) 169 | 170 | 171 | def double_well_wavefunctions_plot(xmin = -10, xmax = 10, Nx = 500, neigs = 20, params = [-0.5, 0.01, 7]): 172 | """ 173 | Plots the 1D double well wavefunctions. 174 | 175 | Inputs 176 | ------ 177 | xmin: float 178 | minimum value of the x axis 179 | xmax: float 180 | maximum value of the x axis 181 | Nx: int 182 | number of finite elements in the x axis 183 | neigs: int 184 | number of eigenvalues to find 185 | params: list 186 | list of parameters of Vfun 187 | """ 188 | 189 | def Vfun(x, params): 190 | A = params[0] 191 | B = params[1] 192 | C = params[2] 193 | V = A * x ** 2 + B * x ** 4 + C 194 | return V 195 | 196 | eval_wavefunctions(xmin, xmax, Nx, Vfun, params, neigs, True) -------------------------------------------------------------------------------- /schrodinger2D.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from scipy import sparse 4 | from scipy.sparse import linalg as sla 5 | from mpl_toolkits.mplot3d import Axes3D 6 | 7 | 8 | def schrodinger2D(xmin, xmax, Nx, ymin, ymax, Ny, Vfun2D, params, neigs, E0 = 0.0, findpsi = False): 9 | """ 10 | Solves the 2 dimensional Schrodinger equation numerically. 11 | 12 | Inputs 13 | ------ 14 | xmin: float 15 | minimum value of the x axis 16 | xmax: float 17 | maximum value of the x axis 18 | Nx: int 19 | number of finite elements in the x axis 20 | ymin: float 21 | minimum value of the y axis 22 | ymax: float 23 | maximum value of the y axis 24 | Ny: int 25 | number of finite elements in the y axis 26 | Vfun2D: function 27 | potential energy function 28 | params: list 29 | list containing the parameters of Vfun 30 | neigs: int 31 | number of eigenvalues to find 32 | E0: float 33 | eigenenergy value to solve for 34 | findpsi: bool 35 | If True, the eigen wavefunctions will be calculated and returned. 36 | If False, only the eigen energies will be found. 37 | 38 | Returns 39 | ------- 40 | evl: np.array 41 | eigenvalues 42 | evt: np.array 43 | eigenvectors 44 | x: np.array 45 | x axis values 46 | y: np.array 47 | y axis values 48 | """ 49 | x = np.linspace(xmin, xmax, Nx) 50 | dx = x[1] - x[0] 51 | y = np.linspace(ymin, ymax, Ny) 52 | dy = y[1] - y[0] 53 | 54 | V = Vfun2D(x, y, params) 55 | 56 | # The following code might be problematic. 57 | ''' 58 | # create the 2D Hamiltonian matrix 59 | Hx = create_1d_hamiltonian(Nx, dx) 60 | Hy = create_1d_hamiltonian(Ny, dy) 61 | 62 | Ix = sparse.eye(Nx, Nx) 63 | Iy = sparse.eye(Ny, Ny) 64 | H = sparse.kron(Hx, Iy) + sparse.kron(Ix, Hy) 65 | ''' 66 | # Use the following instead of the above! 67 | H = create_2d_hamiltonian(Nx, dx, Ny, dy) 68 | 69 | # Convert to lil form and add potential energy function 70 | H = H.tolil() 71 | for i in range(Nx * Ny): 72 | H[i, i] = H[i, i] + V[i] 73 | 74 | # convert to csc form and solve the eigenvalue problem 75 | H = H.tocsc() 76 | [evl, evt] = sla.eigs(H, k = neigs, sigma = E0) 77 | 78 | if findpsi == False: 79 | return evl 80 | else: 81 | return evl, evt, x, y 82 | 83 | 84 | def create_1d_hamiltonian(Nx, dx): 85 | """ 86 | Creates a 1 dimensional Hamiltonian. 87 | 88 | Inputs 89 | ------ 90 | Nx: int 91 | number of elements in that axis 92 | dx: float 93 | step size 94 | 95 | Returns 96 | ------- 97 | H: np.array 98 | np.array of the Hamiltonian 99 | """ 100 | H = sparse.eye(Nx, Nx, format = "lil") * 2 101 | for i in range(Nx - 1): 102 | H[i, i + 1] = -1 103 | H[i + 1, i] = -1 104 | H = H / (dx ** 2) 105 | return H 106 | 107 | 108 | def create_2d_hamiltonian(Nx, dx, Ny = None, dy = None): 109 | """ 110 | Creates a 2D Hamiltonian matrix. 111 | 112 | Inputs 113 | ------ 114 | Nx, Ny: int 115 | Number of elements in that axis. 116 | dx, dy: float 117 | Step size. 118 | 119 | Returns 120 | ------- 121 | H: np.array 122 | Hamiltonian matrix. 123 | """ 124 | if Ny is None: 125 | Ny = Nx 126 | if dy is None: 127 | dy = dx 128 | Hx = create_1d_hamiltonian(Nx, dx) 129 | Iy = sparse.eye(Ny, Ny) 130 | Hy = create_1d_hamiltonian(Nx * Ny, dy) 131 | H = sparse.kron(Hx, Iy) + Hy 132 | return H 133 | 134 | 135 | def sho_eigenenergies(xmin = -10, xmax = 10, Nx = 201, 136 | ymin = -10, ymax = 10, Ny = 201, 137 | params = [1, 1], 138 | neigs = 10, E0 = 0): 139 | """ 140 | This function calculates the quantum simple harmonic oscillator eigenenergies. 141 | Theoretically, the eigenenergies are given by: E = hw(n + 2/2), n = nx + ny. 142 | However, as we set h = w = 1, and we scale the energies during the Hamiltonian creation 143 | by 2, the theoretical eigenenergies are given by: E = 2n + 2. 144 | 145 | Inputs 146 | ------ 147 | xmin: float 148 | minimum value of the x axis 149 | xmax: float 150 | maximum value of the x axis 151 | Nx: int 152 | number of finite elements in the x axis 153 | ymin: float 154 | minimum value of the y axis 155 | ymax: float 156 | maximum value of the y axis 157 | Ny: int 158 | number of finite elements in the y axis 159 | params: list 160 | list containing the parameters of Vfun 161 | neigs: int 162 | number of eigenvalues to find 163 | E0: float 164 | eigenenergy value to solve for 165 | 166 | Returns 167 | ------- 168 | evl: list 169 | List of eigenenergies. 170 | """ 171 | def Vfun(X, Y, params): 172 | Nx = len(X) 173 | Ny = len(Y) 174 | M = Nx * Ny 175 | V = np.zeros(M) 176 | vindex = 0 177 | for i in range(Nx): 178 | for j in range(Ny): 179 | V[vindex] = params[0] * X[i]**2 + params[1] * Y[j]**2 180 | vindex = vindex + 1 181 | return V 182 | 183 | # Only eigenvalues will be returned! 184 | evl = schrodinger2D(xmin, xmax, Nx, ymin, ymax, Ny, Vfun, params, neigs, E0, False) 185 | 186 | indices = np.argsort(evl) 187 | print("Energy eigenvalues:") 188 | for i,j in enumerate(evl[indices]): 189 | print("{}: {:.2f}".format(i + 1, np.real(j))) 190 | 191 | return sorted(evl) 192 | 193 | 194 | def eval_wavefunctions(xmin, xmax, Nx, 195 | ymin, ymax, Ny, 196 | Vfun, params, 197 | neigs, E0, findpsi): 198 | """ 199 | Evaluates and plots the 2 dimensional Schrodinger equation numerically for some potential function Vfun. 200 | The 2D wavefunctions (actually, the probabilities!) are plotted as a heatmap instead of as an actual plot. 201 | 202 | Inputs 203 | ------ 204 | xmin: float 205 | minimum value of the x axis 206 | xmax: float 207 | maximum value of the x axis 208 | Nx: int 209 | number of finite elements in the x axis 210 | ymin: float 211 | minimum value of the y axis 212 | ymax: float 213 | maximum value of the y axis 214 | Ny: int 215 | number of finite elements in the y axis 216 | Vfun: function 217 | potential energy function 218 | params: list 219 | list containing the parameters of Vfun 220 | neigs: int 221 | number of eigenvalues to find 222 | E0: float 223 | eigenenergy value to solve for 224 | findpsi: bool 225 | If True, the eigen wavefunctions will be calculated and returned. 226 | If False, only the eigen energies will be found. 227 | """ 228 | 229 | H = schrodinger2D(xmin, xmax, Nx, ymin, ymax, Ny, Vfun, params, neigs, E0, findpsi) 230 | evl = H[0] # eigenvalues 231 | indices = np.argsort(evl) 232 | print("Energy eigenvalues:") 233 | for i,j in enumerate(evl[indices]): 234 | print("{}: {:.2f}".format(i + 1, np.real(j))) 235 | 236 | evt = H[1] # eigenvectors 237 | 238 | plt.figure(figsize = (15, 15)) 239 | # unpack the vector into 2 dimensions for plotting: 240 | for n in range(neigs): 241 | psi = evt[:, n] 242 | PSI = psi.reshape(Nx, Ny) 243 | PSI = np.abs(PSI) ** 2 244 | plt.subplot(2, int(neigs / 2), n + 1) 245 | plt.pcolormesh(np.transpose(PSI), cmap = "jet") 246 | plt.axis("equal") 247 | plt.axis("off") 248 | plt.show() 249 | 250 | 251 | def sho_wavefunctions_plot(xmin = -10, xmax = 10, Nx = 250, 252 | ymin = -10, ymax = 10, Ny = 250, 253 | params = [1, 1], neigs = 6, E0 = 0, findpsi = True): 254 | """ 255 | Evaluates and plots the 2D QSHO wavefunctions. 256 | 257 | Inputs 258 | ------ 259 | xmin: float 260 | minimum value of the x axis 261 | xmax: float 262 | maximum value of the x axis 263 | Nx: int 264 | number of finite elements in the x axis 265 | ymin: float 266 | minimum value of the y axis 267 | ymax: float 268 | maximum value of the y axis 269 | Ny: int 270 | number of finite elements in the y axis 271 | params: list 272 | list containing the parameters of Vfun 273 | neigs: int 274 | number of eigenvalues to find 275 | E0: float 276 | eigenenergy value to solve for 277 | findpsi: bool 278 | If True, the eigen wavefunctions will be calculated and returned. 279 | If False, only the eigen energies will be found. 280 | """ 281 | def Vfun(X, Y, params): 282 | Nx = len(X) 283 | Ny = len(Y) 284 | M = Nx * Ny 285 | V = np.zeros(M) 286 | vindex = 0 287 | for i in range(Nx): 288 | for j in range(Ny): 289 | V[vindex] = params[0] * X[i]**2 + params[1] * Y[j]**2 290 | vindex = vindex + 1 291 | return V 292 | 293 | eval_wavefunctions(xmin, xmax, Nx, 294 | ymin, ymax, Ny, 295 | Vfun, params, neigs, E0, findpsi) 296 | 297 | 298 | def stadium_wavefunctions_plot(R = 1, L = 2, V0 = 1e6, neigs = 6, E0 = 500, Ny = 250): 299 | """ 300 | Evaluates and plots the 2D stadium potential wavefunctions. 301 | 302 | Inputs 303 | ------ 304 | R: float 305 | stadium radius 306 | L: float 307 | stadium length 308 | V0: float 309 | stadium wall potential 310 | neigs: int 311 | number of eigenvalues to solve for 312 | E0: float 313 | eigenvalue to solve for 314 | Ny: int 315 | number of elements in the y axis 316 | """ 317 | ymin = -0.5 * L - R 318 | ymax = 0.5 * L + R 319 | xmin = -R 320 | xmax = R 321 | params = [R, L, V0] 322 | print("xmin: {}, xmax: {}, ymin: {}, ymax: {}".format(xmin, xmax, ymin, ymax)) 323 | 324 | Nx = int(Ny * 2 * R / (2.0 * R + L)) 325 | print("Nx: {}, Ny: {}".format(Nx, Ny)) 326 | 327 | def Vfun2D(X, Y, params): 328 | R = params[0] # stadium radius 329 | L = params[1] # stadium length 330 | V0 = params[2] # stadium wall potential 331 | # stadium potential function 332 | Nx = len(X) 333 | Ny = len(Y) 334 | [x, y] = np.meshgrid(X, Y) 335 | F = np.zeros([Nx, Ny]) 336 | 337 | for i in range(Nx): 338 | for j in range(Ny): 339 | if abs(X[i]) == R or abs(Y[j]) == R + 0.5 * L: 340 | F[i, j] = V0 341 | if (abs(Y[j]) - 0.5 * L) > 0 and np.sqrt((abs(Y[j]) - 0.5 * L) ** 2 + X[i] ** 2) >= R: 342 | F[i, j] = V0 343 | 344 | V = F.reshape(Nx * Ny) 345 | return V 346 | 347 | eval_wavefunctions(xmin, xmax, Nx, 348 | ymin, ymax, Ny, 349 | Vfun2D, params, neigs, E0, findpsi=True) 350 | 351 | 352 | def stadium_wavefunctions_3dplot(R = 1, L = 0, V0 = 1e6, neigs = 6, E0 = 70, Ny = 250): 353 | """ 354 | Evaluates and plots the 2D stadium potential wavefunctions. 355 | Instead of plotting the wavefunctions as a heatmap, plot them as a 3D surface instead 356 | 357 | Inputs 358 | ------ 359 | R: float 360 | stadium radius 361 | L: float 362 | stadium length 363 | V0: float 364 | stadium wall potential 365 | neigs: int 366 | number of eigenvalues to solve for 367 | E0: float 368 | eigenvalue to solve for 369 | Ny: int 370 | number of elements in the y axis 371 | """ 372 | ymin = -0.5 * L - R 373 | ymax = 0.5 * L + R 374 | xmin = -R 375 | xmax = R 376 | params = [R, L, V0] 377 | print("xmin: {}, xmax: {}, ymin: {}, ymax: {}".format(xmin, xmax, ymin, ymax)) 378 | 379 | Nx = int(Ny * 2 * R / (2.0 * R + L)) 380 | print("Nx: {}, Ny: {}".format(Nx, Ny)) 381 | 382 | def Vfun2D(X, Y, params): 383 | R = params[0] # stadium radius 384 | L = params[1] # stadium length 385 | V0 = params[2] # stadium wall potential 386 | # stadium potential function 387 | Nx = len(X) 388 | Ny = len(Y) 389 | [x, y] = np.meshgrid(X, Y) 390 | F = np.zeros([Nx, Ny]) 391 | 392 | for i in range(Nx): 393 | for j in range(Ny): 394 | if abs(X[i]) == R or abs(Y[j]) == R + 0.5 * L: 395 | F[i, j] = V0 396 | if (abs(Y[j]) - 0.5 * L) > 0 and np.sqrt((abs(Y[j]) - 0.5 * L) ** 2 + X[i] ** 2) >= R: 397 | F[i, j] = V0 398 | 399 | V = F.reshape(Nx * Ny) 400 | return V 401 | 402 | H = schrodinger2D(xmin, xmax, Nx, ymin, ymax, Ny, Vfun2D, params, neigs, E0, True) 403 | evl = H[0] # eigenvalues 404 | indices = np.argsort(evl) 405 | print("Energy eigenvalues:") 406 | for i,j in enumerate(evl[indices]): 407 | print("{}: {:.2f}".format(i + 1, np.real(j))) 408 | evt = H[1] # eigenvectors 409 | 410 | # unpack the vector into 2 dimensions for plotting: 411 | for n in range(evt.shape[1]): 412 | psi = evt[:, n] 413 | PSI = psi.reshape(Nx, Ny) 414 | PSI = np.abs(PSI)**2 415 | fig = plt.figure(figsize = (10, 10)) 416 | ax = fig.add_subplot(111, projection = "3d") 417 | X, Y = np.meshgrid(H[2], H[3]) 418 | ax.plot_surface(X, Y , np.transpose(PSI), cmap = "jet") 419 | ax.axis("off") 420 | plt.show() -------------------------------------------------------------------------------- /schrodinger3D.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy import sparse 3 | from scipy.sparse import linalg as sla 4 | 5 | 6 | def schrodinger3D(xmin, xmax, Nx, 7 | ymin, ymax, Ny, 8 | zmin, zmax, Nz, 9 | Vfun3D, params, neigs, E0 = 0.0, findpsi = False): 10 | """ 11 | Solves the 3 dimensional Schrodinger equation numerically 12 | 13 | Inputs 14 | ------ 15 | xmin: float 16 | minimum value of the x axis 17 | xmax: float 18 | maximum value of the x axis 19 | Nx: int 20 | number of finite elements in the x axis 21 | ymin: float 22 | minimum value of the y axis 23 | ymax: float 24 | maximum value of the y axis 25 | Ny: int 26 | number of finite elements in the y axis 27 | zmin: float 28 | minimum value of the z axis 29 | zmax: float 30 | maximum value of the z axis 31 | Nz: int 32 | number of finite elements in the z axis 33 | Vfun3D: function 34 | potential energy function 35 | params: list 36 | list containing the parameters of Vfun 37 | neigs: int 38 | number of eigenvalues to find 39 | E0: float 40 | eigenenergy value to solve for 41 | findpsi: bool 42 | If True, the eigen wavefunctions will be calculated and returned. 43 | If False, only the eigen energies will be found. 44 | 45 | Returns 46 | ------- 47 | evl: np.array 48 | eigenvalues 49 | evt: np.array 50 | eigenvectors 51 | x: np.array 52 | x axis values 53 | y: np.array 54 | y axis values 55 | z: np.array 56 | z axis values 57 | """ 58 | x = np.linspace(xmin, xmax, Nx) 59 | dx = x[1] - x[0] 60 | y = np.linspace(ymin, ymax, Ny) 61 | dy = y[1] - y[0] 62 | z = np.linspace(zmin, zmax, Nz) 63 | dz = z[1] - z[0] 64 | 65 | V = Vfun3D(x, y, z, params) # this is a 1D np.array 66 | 67 | # The following code might be problematic! 68 | """ 69 | # Create the 3D Hamiltonian matrix. 70 | Hx = create_1d_hamiltonian(Nx, dx) 71 | Hy = create_1d_hamiltonian(Ny, dy) 72 | Hz = create_1d_hamiltonian(Nz, dz) 73 | 74 | Ix = sparse.eye(Nx) 75 | Iy = sparse.eye(Ny) 76 | Iz = sparse.eye(Nz) 77 | 78 | # Combine the 3 individual 1 dimensional Hamiltonians using Kronecker products 79 | Hxy = sparse.kron(Hx, Iy) + sparse.kron(Ix, Hy) 80 | Ixy = sparse.kron(Ix, Iy) 81 | H = sparse.kron(Hxy, Iz) + sparse.kron(Ixy, Hz) 82 | """ 83 | # Use the following code instead! 84 | H = create_3d_hamiltonian(Nx, dx, Ny, dy, Nz, dz) 85 | 86 | # Convert to lil form and add potential energy function 87 | H = H.tolil() 88 | for i in range(Nx * Ny * Nz): 89 | H[i, i] = H[i, i] + V[i] 90 | 91 | # convert to csc form and solve the eigenvalue problem 92 | H = H.tocsc() 93 | [evl, evt] = sla.eigs(H, k = neigs, sigma = E0) 94 | 95 | if findpsi == False: 96 | return evl 97 | else: 98 | return evl, evt, x, y, z 99 | 100 | 101 | def create_1d_hamiltonian(Nx, dx): 102 | """ 103 | This function creates a 1 dimensional Hamiltonian. 104 | 105 | Inputs 106 | ------ 107 | Nx: int 108 | number of elements in the 1 spatial dimension 109 | dx: float 110 | step size of the 1 spatial dimension 111 | """ 112 | H = sparse.eye(Nx, Nx, format = "lil") * 2 113 | for i in range(Nx - 1): 114 | H[i, i + 1] = -1 115 | H[i + 1, i] = -1 116 | H = H / (dx ** 2) 117 | return H 118 | 119 | 120 | def create_2d_hamiltonian(Nx, dx, Ny = None, dy = None): 121 | """ 122 | Creates a 2D Hamiltonian matrix. 123 | 124 | Inputs 125 | ------ 126 | Nx, Ny: int 127 | Number of elements in that axis. 128 | dx, dy: float 129 | Step size. 130 | 131 | Returns 132 | ------- 133 | H: np.array 134 | Hamiltonian matrix. 135 | """ 136 | if Ny is None: 137 | Ny = Nx 138 | if dy is None: 139 | dy = dx 140 | Hx = create_1d_hamiltonian(Nx, dx) 141 | Iy = sparse.eye(Ny, Ny) 142 | Hy = create_1d_hamiltonian(Nx * Ny, dy) 143 | H = sparse.kron(Hx, Iy) + Hy 144 | return H 145 | 146 | 147 | def create_3d_hamiltonian(Nx, dx, Ny = None, dy = None, Nz = None, dz = None): 148 | """ 149 | Creates a 3D Hamiltonian matrix. 150 | 151 | Inputs 152 | ------ 153 | Nx, Ny: int 154 | Number of elements in that axis. 155 | dx, dy: float 156 | Step size. 157 | 158 | Returns 159 | ------- 160 | H: np.array 161 | Hamiltonian matrix. 162 | """ 163 | if Ny is None: 164 | Ny = Nx 165 | if dy is None: 166 | dy = dx 167 | if Nz is None: 168 | Nz = Nx 169 | if dz is None: 170 | dz = dx 171 | 172 | Hxy = create_2d_hamiltonian(Nx, dx, Ny, dy) 173 | Iz = sparse.eye(Nz) 174 | Hz = create_1d_hamiltonian(Nx * Ny * Nz, dz) 175 | H = sparse.kron(Hxy, Iz) + Hz 176 | 177 | return H 178 | 179 | 180 | def sho_eigenenergies(xmin = -5, xmax = 5, Nx = 50, 181 | ymin = -5, ymax = 5, Ny = 50, 182 | zmin = -5, zmax = 5, Nz = 50, 183 | params = [1, 1, 1], 184 | neigs = 10, E0 = 0): 185 | """ 186 | This function calculates the quantum simple harmonic oscillator eigenenergies. 187 | Theoretically, the eigenenergies are given by: E = hw(n + 3/2), n = nx + ny + nz. 188 | However, as we set h = w = 1, and we scale the energies during the Hamiltonian creation 189 | by 2, the theoretical eigenenergies are given by: E = 2n + 3. 190 | 191 | Inputs 192 | ------ 193 | xmin: float 194 | minimum value of the x axis 195 | xmax: float 196 | maximum value of the x axis 197 | Nx: int 198 | number of finite elements in the x axis 199 | ymin: float 200 | minimum value of the y axis 201 | ymax: float 202 | maximum value of the y axis 203 | Ny: int 204 | number of finite elements in the y axis 205 | zmin: float 206 | minimum value of the z axis 207 | zmax: float 208 | maximum value of the z axis 209 | Nz: int 210 | number of finite elements in the z axis 211 | params: list 212 | list containing the parameters of Vfun 213 | neigs: int 214 | number of eigenvalues to find 215 | E0: float 216 | eigenenergy value to solve for 217 | 218 | Returns 219 | ------- 220 | evl: list 221 | List of eigenenergies. 222 | """ 223 | def Vfun(X, Y, Z, params): 224 | """ 225 | This function returns the potential energies for a 3D quantum harmonic oscillator. 226 | 227 | Inputs 228 | ------ 229 | X: np.array 230 | np.array of the x axis 231 | Y: np.array 232 | np.array of the y axis 233 | Z: np.array 234 | np.array of the z axis 235 | params: list 236 | list of parameters for the potential energy function 237 | 238 | Returns 239 | ------- 240 | V: np.array 241 | np.array of the potential energy of the 3D QSHO 242 | """ 243 | Nx = len(X) 244 | Ny = len(Y) 245 | Nz = len(Z) 246 | M = Nx * Ny * Nz 247 | V = np.zeros(M) 248 | vindex = 0 249 | for i in range(Nx): 250 | for j in range(Ny): 251 | for k in range(Nz): 252 | V[vindex] = params[0]*X[i]**2 + params[1]*Y[j]**2 + params[2]*Z[k]**2 253 | vindex = vindex + 1 254 | return V 255 | 256 | # Only eigenvalues will be returned! 257 | evl = schrodinger3D(xmin, xmax, Nx, ymin, ymax, Ny, zmin, zmax, Nz, Vfun, params, neigs, E0, False) 258 | 259 | indices = np.argsort(evl) 260 | print("Energy eigenvalues:") 261 | for i,j in enumerate(evl[indices]): 262 | print("{}: {:.2f}".format(i + 1, np.real(j))) 263 | 264 | return sorted(evl) 265 | 266 | 267 | def hydrogen_eigenenergies(xmin = -10, xmax = 10, Nx = 101, 268 | ymin = -10, ymax = 10, Ny = 101, 269 | zmin = -10, zmax = -10, Nz = 101, 270 | params = [1 / np.pi / 4], 271 | neigs = 10, E0 = 0): 272 | def Vfun(X, Y, Z, params): 273 | Nx = len(X) 274 | Ny = len(Y) 275 | Nz = len(Z) 276 | M = Nx * Ny * Nz 277 | V = np.zeros(M) 278 | vindex = 0 279 | for i in range(Nx): 280 | for j in range(Ny): 281 | for k in range(Nz): 282 | V[vindex] = params[0] / np.sqrt(X[i]**2 + Y[j]**2 + Z[k]**2) 283 | vindex = vindex + 1 284 | return V 285 | 286 | evl = schrodinger3D(xmin, xmax, Nx, ymin, ymax, Ny, zmin, zmax, Nz, Vfun, params, neigs, E0, False) 287 | 288 | indices = np.argsort(evl) 289 | print("Energy eigenvalues:") 290 | for i,j in enumerate(evl[indices]): 291 | print("{}: {:.2f}".format(i + 1, np.real(j))) 292 | 293 | return sorted(evl) -------------------------------------------------------------------------------- /sompi_analysis.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from scipy import linalg 5 | from numpy.random import randn 6 | from scipy import signal 7 | 8 | # This code demonstrates the Sompi algorithm used to decompose a signal into 9 | # both real and imaginary frequency parts. In contrast to traditional Fourier 10 | # analysis techniques the Sompi method is able to give a direct interpretation 11 | # of decaying/amplifying waveforms. 12 | 13 | # BASED ON KUMAZAWA et al. 1990, A theory of spectral analysis based on the 14 | # characteristic property of a linear dynamic system 15 | 16 | def sompi(X, dt, m_order_min=4, m_order_max=40, d_m_order=1): 17 | """ 18 | Inputs: 19 | X: time series signal to analyse 20 | dt: time step size 21 | m_order_min: minimum Sompi order 22 | m_order_max: maximum Sompi order 23 | d_m_order: Sompi order step size 24 | Outputs: 25 | F, G: Sompi real and imaginary frequency components 26 | """ 27 | F = [] 28 | G = [] 29 | 30 | n_order = len(X) # total number of data points in the time series 31 | for m_order in range(m_order_min, m_order_max+d_m_order, d_m_order): 32 | P = np.zeros([m_order+1, m_order+1]) # P(k,l) matrix 33 | for k in range(0, m_order+1, 1): 34 | for l in range(0, m_order+1, 1): 35 | for t in range(m_order, n_order, 1): 36 | P[k, l] = P[k, l] + X[t-k] * X[t-l] 37 | 38 | P = P / (n_order - m_order) # TAKE THE AVERAGE! 39 | [val, vct] = linalg.eig(P) 40 | val = np.real(val) # both val and vct should be real! Drop +0j parts 41 | #vct = np.real(vct) 42 | aj = vct[:, np.argmin(val)] # smallest eigen value is the noise power 43 | Z = np.roots(aj) # obtain the m independent roots 44 | giw = np.log(Z) # Note that Z = np.exp(gamma + 1j * omega) 45 | g = np.real(giw) / (2 * np.pi) / dt 46 | f = np.imag(giw) / (2 * np.pi) / dt 47 | 48 | F.append(f) 49 | G.append(g) 50 | return F, G 51 | 52 | def demo(): 53 | """ 54 | This demo function shows how Sompi analysis is performed using a test 55 | waveform 56 | """ 57 | # Create a test wavefunction: 58 | dt = 1 59 | T = np.arange(0, 200, dt) 60 | EFF = 0.1 61 | GEE = -0.003 62 | X = np.sin(EFF*2*np.pi*T)*np.exp(GEE*2*np.pi*T)+randn(len(T))*0.03 63 | 64 | F, G = sompi(X, dt) # Corresponds to EFF and GEE 65 | # obtain the FFT as a double check 66 | [f, p] = signal.periodogram(X, 1, None, 2**12) 67 | 68 | plt.figure(figsize=(8, 8)) 69 | plt.subplot(2, 1, 1) 70 | plt.plot([EFF], [GEE], 'ro', markersize = 10) 71 | for count in range(len(F)): 72 | plt.plot(F[count], G[count], 'kx') 73 | plt.legend(["Actual","Estimated"]) 74 | plt.axis([0, 0.5, GEE * 2,0]) 75 | plt.grid('on') 76 | plt.subplot(2, 1, 2) 77 | plt.plot(f, abs(p)) 78 | plt.xlim([0, 0.5]) 79 | plt.grid('on') 80 | plt.show() 81 | -------------------------------------------------------------------------------- /transfer_matrix.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.lib.scimath import sqrt 3 | import matplotlib.pyplot as plt 4 | from scipy import linalg 5 | from scipy import optimize 6 | 7 | def transfer_matrix(E, V, L, V0): 8 | """ 9 | This code demonstrates the concept of the transfer matrix method to calculate 10 | the transmission and reflection probabilities of a wave passing through 11 | different potentials. In this case we will utilize a qunatum particle. 12 | This code generates the transfer matrix for a series of quantum potentials 13 | 14 | WARNING! THIS CODE ASSUMES V0 IS THE SAME ON BOTH SIDES OF THE POTENTIAL 15 | WARNING! THIS CODE DOES NOT WORK IF E = V!!! 16 | The program will run only if V and L have the same length! 17 | 18 | Inputs 19 | ------ 20 | E: float 21 | particle energy 22 | V: np.array 23 | np.array of potential energy steps 24 | L: np.array 25 | np.array of step lengths 26 | V0: float 27 | external potential 28 | 29 | Returns 30 | ------- 31 | transfer: np.array 32 | transfer matrix 33 | """ 34 | assert len(V) == len(L) 35 | N = len(L) 36 | V = np.append(V, V0) # Append V0 to the end of V for usage in the for loop 37 | # calculate the array of k values: 38 | k = sqrt(E - V) # note that k can be complex 39 | 40 | # calculate the first potential jump from V0 to V[0] i.e. from outside 41 | # to the first potential step using the combined matrix solved by hand: 42 | transfer = 0.5 * np.array([[1.0 + k[-1] / k[0], 1 - k[-1] / k[0]], [1 - k[-1] / k[0], 1 + k[-1] / k[0]]]) 43 | # k[-1] is the last element in array k which is sqrt(E - V0) i.e. the value 44 | # of k in the outside region 45 | 46 | # Loop over the rest of the N steps starting with a translation 47 | # across the space occupied by V1 i.e. along the length L1 48 | for n in range(N): 49 | # translate across V[n]: 50 | transl = np.array([[np.exp(1j * k[n] * L[n]), 0], [0, np.exp(-1j * k[n] * L[n])]]) 51 | # jump from V[n] to V[n+1]: 52 | jump = 0.5 * np.array([[1.0 + k[n] / k[n + 1], 1 - k[n] / k[n + 1]], [1 - k[n] / k[n + 1], 1 + k[n] / k[n + 1]]]) 53 | # now calculate the rest of the transfer matrix by "appending" 54 | # the other matrix multiplications infront of the current 55 | # transfer matrix: 56 | transfer = np.dot(transl, transfer) 57 | transfer = np.dot(jump, transfer) 58 | 59 | # return the completed transfer matrix: 60 | # check the determinant of the transfer matrix is 1 61 | # D=linalg.det(transfer) 62 | # print(abs(D)) #determinant of the transfer matrix should be 1 63 | return transfer 64 | 65 | def barrier_plot(): 66 | """ 67 | This code generates the transmittance T and reflectance R for an incoming 68 | wave impinging onto a step potential: 69 | """ 70 | E = np.linspace(1, 250, 700) # range of E to be used 71 | V = np.array([50]) # various potential steps 72 | L = np.array([1]) # potential step lengths 73 | V0 = 0 # potential outside 74 | T = np.zeros(len(E)) # transmission results 75 | R = np.zeros(len(E)) # reflection results 76 | 77 | # sample over all values of E, except the case of E=V: 78 | for i in range(len(E)): 79 | M = transfer_matrix(E[i], V, L, V0) # calculate transfer matrix for respective E 80 | R[i] = abs(M[1, 0] / M[1, 1]) ** 2 # calculate reflection 81 | T[i] = abs(linalg.det(M) / M[1, 1]) ** 2 # calculate transmission 82 | 83 | # Eigen values of particle in a box are of the form: 84 | # H=n^2 hbar^2 pi^2 / 2 m L^2 + V 85 | # we have normalised hbar^2/2 m = 1: 86 | n = np.array([1, 2, 3, 4]) 87 | H = (n ** 2) * (np.pi ** 2) + 50 88 | 89 | plt.figure(figsize=(15, 5)) 90 | plt.semilogy(E, T, 'r') 91 | plt.semilogy(E, R, 'c') 92 | plt.vlines(H, 1e-7, 1) 93 | plt.legend(["Transmission", "Reflection", "Eigenvalues"]) 94 | plt.xlabel('E') 95 | plt.ylabel('Transmission/Reflection Coefficients') 96 | plt.title('Potential Barrier Transmission and Reflections and Particle in a Box Eigenvalues') 97 | plt.show() 98 | 99 | # Generally, the transmission curve increases from a miminum to a maximum 100 | # transmission past the value of E=V=50, while the reflection remains at 101 | # the maximum value and starts to decrease past the same value. For E<50 102 | # this corresponds to the classical regime where the particle cannot 103 | # overcome the potential barrier due to it having a lower energy than the 104 | # potential barrier. Of course in QM the particle has a very small 105 | # chance that it can tunnel through the barrier unlike in the classical 106 | # regime where tunneling is impossible. 107 | 108 | # The interesting feature is that past E=50 the reflection does not decay 109 | # gradually i.e. the curve is not well behaved, there are some sharp dips 110 | # in the curve at certain values of E, and these sharp dips occurs for the 111 | # energies which are the same as those of energy eigenvalues of the particle 112 | # in a box. Additionally, these sharp dips of the reflection curve coincide 113 | # with a transmission value of 1 in the transmission curve; i.e. transmission 114 | # is 1 only for certain values of E rather than for all values of E>V as in 115 | # the classical case. 116 | 117 | # T=1 only when the width of the barrier is half-integral or full-integral of 118 | # the de Broglie wavelength of the particle within the Potential barrier: 119 | 120 | # k*L = n*pi 121 | 122 | # Working out the math leads to the analytical result that the energy of the 123 | # particle follows the form of the energy eigenvalues of a particle in a box: 124 | 125 | # E - V = n^2 * pi^2 / L^2 126 | 127 | # This effect is a result of destructive interference between reflections of 128 | # the waves at x=0 and x=L i.e. at the two edges of the potential barrier. 129 | 130 | # See: 131 | # Quantum Mechanics Vol 1, A. Messiah Pgs 88-98 132 | # Quantum Mechanics, Bransden and Joachain Pgs 154-155 133 | # Intro. to Quantum Mechanics, D. Griffiths Pg 82 134 | 135 | def resonant_barrier_plot(): 136 | """ 137 | This code generates the transmission and reflection coefficients for a more 138 | complicated potential setup 139 | """ 140 | V = np.array([250, 50, 250]) 141 | L = np.array([0.2, 1, 0.2]) 142 | E = np.linspace(1, 500, 700) 143 | V0 = 0 144 | T = np.zeros(len(E)) 145 | R = np.zeros(len(E)) 146 | 147 | for i in range(len(E)): 148 | M = transfer_matrix(E[i], V, L, V0) 149 | R[i] = abs(M[1, 0] / M[1, 1]) ** 2 # calculate Reflection 150 | T[i] = abs(linalg.det(M) / M[1, 1]) ** 2 # calculate Transmission 151 | 152 | plt.figure(figsize=(15, 5)) 153 | plt.semilogy(E, T, 'r') 154 | plt.semilogy(E, R, 'c') 155 | plt.legend(["Transmission", "Reflection"]) 156 | plt.xlabel('E') 157 | plt.ylabel('Transmission/Reflection Coefficients') 158 | plt.title('Potential Barrier Transmission and Reflections') 159 | plt.show() 160 | 161 | # The transmission resonances i.e. transmission spikes in the transmission 162 | # curve is similar in effect as observed for the barrier potential 163 | 164 | # The potential pocket in the region -0.5 < x < 0.5 results in "resonance 165 | # like tunnelling" (Resonance-like tunneling across a barrier with adjacent 166 | # wells, S MAHADEVAN , P PREMA , S K AGARWALLA, B SAHU and C S SHASTRY) at 167 | # specific particle energies which correspond to states of resonance within 168 | # the potential pocket. 169 | 170 | # i.e. what is happening is pseudo-standing waves are being formed within the 171 | # two peaks of V=250 within the "valley" region where V=50 and this potential 172 | # setup corresponds to a pseudo-bound state of a finite square well. These 173 | # pseudo-standing waves occur only for certain pseudo-resonant states for 174 | # certain energies E, at which transmission resonance occurs and the transmission 175 | # spikes up to 1. 176 | 177 | # As in the previous barrier_potential, during spikes in transmission, what is 178 | # occuring is destructive interference occuring between reflections of the 179 | # waves at the internal walls of the potential valley, and these resonances 180 | # occur only when the width of the "well" is equal to an integral or half 181 | # integral of the de Broglie wavelength of the particle inside the "well". 182 | 183 | # See: 184 | # Quantum Mechanics, Bransden and Joachain Pgs 169-170 185 | 186 | # scipy.optimize.fsolve(fun,x0) 187 | # calls fun(x), starts with x=x0 and tries to find a bunch of values near x0 188 | # until it finds fun(x)=0 and return x 189 | 190 | def find_bound_states(Emin, Emax, V, L, V0): 191 | """ 192 | Find the bound states specified by Emin, Emax, V, L and V0 193 | 194 | Inputs 195 | ------ 196 | Emin: float 197 | minimum energy 198 | Emax: float 199 | maximum energy 200 | V: np.array 201 | np.array of potential energies 202 | L: np.array 203 | np.array of the step lengths 204 | V0: float 205 | external potential energy value 206 | 207 | Returns 208 | ------- 209 | R: np.array 210 | energies of the bound states 211 | """ 212 | E = np.linspace(Emin, Emax, 700) 213 | H = np.array([]) 214 | R = np.array([]) 215 | 216 | def M22(E, V, L, V0): 217 | # this function returns the last element of the transfer matrix 218 | # i.e. M[1,1] in 0-index or M(2,2) in standard index 219 | Q = transfer_matrix(E, V, L, V0)[1, 1] 220 | return Q # Q = M[1,1] i.e. Q is a float and not a matrix 221 | 222 | for i in range(len(E)): 223 | 224 | # obtain the optimized value of E which returns M22=0 225 | M = optimize.fsolve(M22, E[i], args=(V, L, V0)) 226 | 227 | if Emin <= M and M <= Emax and abs(M22(M, V, L, V0)) <= 1e-10 and M not in H: 228 | # For some stupid reason, optimize.fsolve was giving out nonsense 229 | # for certain values of E which were outside the range of Emin and 230 | # Emax, so restrict the solution of M only to within the range 231 | # allowed by Emin and Emax. Additionally some values of M output 232 | # by fsolve did not produce M22=0 so restrict only those values 233 | # which wil give M22=0. Also prevent duplicate entries of M being 234 | # input into H so as to make further filtering more efficient 235 | H = np.append(H, M) 236 | 237 | # Even with the first filter, still there remains elements which are 238 | # essentially duplicates of each other but with numeric differences of 239 | # order say 1e-14 etc. Hence we need to filter out these duplicates from 240 | # the final solution. I place this filter here because checking the value 241 | # of M and the elements in H within the for loop is not efficient, especially 242 | # when H is not yet sorted which makes filtering even harder. Better 243 | # to do the filtering outside the for loop: 244 | H = np.sort(H) 245 | R = np.append(R, H[0]) 246 | for i in range(len(H) - 1): 247 | if abs(H[i + 1] - H[i]) >= 1e-10: 248 | R = np.append(R, H[i + 1]) 249 | # return the final solution R which is an array containing the energies 250 | # of which boundstates occur for this particular potential 251 | return R 252 | 253 | def resonant_barrier_plot2(): 254 | V = np.array([250, 50, 250]) 255 | L = np.array([0.2, 1, 0.2]) 256 | E = np.linspace(1, 500, 700) 257 | V0 = 0 258 | T = np.zeros(len(E)) 259 | R = np.zeros(len(E)) 260 | # Obtain the transmission and reflection coefficients 261 | for i in range(len(E)): 262 | M = transfer_matrix(E[i], V, L, V0) 263 | R[i] = abs(M[1, 0] / M[1, 1]) ** 2 # calculate reflection 264 | T[i] = abs(linalg.det(M) / M[1, 1]) ** 2 # calculate transmission 265 | 266 | Q = find_bound_states(50, 250, np.array([50]), np.array([1]), 250) 267 | print('The bound states energies are: {}'.format(Q)) 268 | 269 | plt.figure(figsize=(15, 5)) 270 | plt.semilogy(E, T, 'r') 271 | plt.semilogy(E, R, 'c') 272 | plt.vlines(Q, 1e-13, 1) 273 | plt.legend(["Transmission", "Reflection", "Bound States"]) 274 | plt.xlabel('E') 275 | plt.ylabel('Transmission/Reflection Coefficients') 276 | plt.title('Potential Barrier Transmission and Reflections') 277 | plt.show() 278 | 279 | # What is observed is that the transmission spikes correspond closely to the 280 | # bound state energies of a finite square well which is normally solved 281 | # numerically or graphically. This implies that the potential setup in problem 282 | # 2 does indeed correspond to a pseudo-bound state related to the bound state 283 | # of a finite square well. 284 | 285 | #T=transfer_matrix(1,np.array([10]),np.array([1]),0) 286 | #print(T) 287 | #barrier_plot() 288 | #resonant_barrier_plot() 289 | #resonant_barrier_plot2() 290 | --------------------------------------------------------------------------------