├── LICENSE ├── README.md └── phased_array.py /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tutorial-phased-array 2 | Generating phased array animations in python 3 | https://www.youtube.com/watch?v=E0uDhl6X3vg&t=0s&ab_channel=Dave%27sSpace 4 | -------------------------------------------------------------------------------- /phased_array.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from matplotlib.animation import FuncAnimation 4 | 5 | plt.rcParams["figure.figsize"] = (8, 8) 6 | plt.rcParams["font.size"] = 14 7 | plt.style.use('dark_background') # dark theme 8 | 9 | 10 | def CalculatePhaseFromFocus(x, y, e): 11 | return np.sqrt(np.sum((e.r-np.array([x, y]))**2))*(2*np.pi/e.lambda0) 12 | 13 | 14 | class Emitter(): 15 | 16 | def __init__(self, x, y, c, f, phi, rMax=100, color="tab:blue", alpha=0.6): 17 | self.r, self.c, self.f, self.rMax, self.alpha = np.array( 18 | [x, y]), c, f, rMax, alpha 19 | self.color = color 20 | self.SetUp() 21 | self.SetPhase(phi) 22 | 23 | def Increment(self, dt): 24 | self.t += dt 25 | if self.t < self.t0: 26 | return 27 | for i, circle in enumerate(self.circles): 28 | r = i*self.lambda0 + self.Wrap(self.lambda0*self.phi/(2*np.pi) + 29 | self.c * self.t, self.lambda0) 30 | circle.set_height(2*r) 31 | circle.set_width(2*r) 32 | circle.set_alpha(self.alpha if i < ((self.t-self.t0)/self.T) else 0) 33 | 34 | def SetPhase(self, phi): 35 | self.phi = self.Wrap(phi, 2*np.pi) 36 | self.t0 = self.T*(1-self.phi/(2*np.pi)) 37 | self.t = 0 38 | 39 | def SetUp(self): 40 | self.lambda0 = self.c/self.f 41 | self.T = 1./self.f 42 | self.N = np.int(np.ceil(self.rMax/self.lambda0)) 43 | self.circles = [plt.Circle(xy=tuple(self.r), fill=False, lw=2, 44 | radius=0, alpha=self.alpha, 45 | color=self.color) 46 | for i in range(self.N)] 47 | 48 | def Wrap(self, x, x_max): 49 | if x >= 0: 50 | return x - np.floor(x/x_max) * x_max 51 | if x < 0: 52 | return x_max - (-x - np.floor(-x/x_max) * x_max) 53 | 54 | 55 | class EmitterArray(): 56 | 57 | def __init__(self): 58 | self.emitters = [] 59 | 60 | def AddEmitter(self, e): 61 | self.emitters.append(e) 62 | 63 | def Increment(self, dt): 64 | for emitter in self.emitters: 65 | emitter.Increment(dt) 66 | 67 | def GetCircles(self): 68 | """Get all the circles from all the emitters""" 69 | circles = [] 70 | for emitter in self.emitters: 71 | circles.extend(emitter.circles) 72 | return circles 73 | 74 | def RemoveOffset(self): 75 | """Only run this one time after all emitters have been added""" 76 | offsets = [] 77 | for emitter in self.emitters: 78 | offsets.append(emitter.t0) 79 | offset_min = np.min(offsets) 80 | for emitter in self.emitters: 81 | emitter.Increment(offset_min) 82 | 83 | @property 84 | def circles(self): 85 | return self.GetCircles() 86 | 87 | 88 | FPS = 30 89 | X, Y = 100, 100 90 | c, f = 3, 0.2 91 | lambda0 = c/f 92 | 93 | N = 10 94 | 95 | emitter_array = EmitterArray() 96 | 97 | # ######################################################## 98 | # # DEMO 1 - Linear Array of Emitters 99 | # xs = np.linspace(-lambda0/4, lambda0/4, N) 100 | # ys = np.zeros_like(xs) 101 | # phi = np.linspace(0,np.pi/2,N) 102 | # for i in range(N): 103 | # e = Emitter(xs[i], ys[i], c, f, phi[i]) 104 | # emitter_array.AddEmitter(e) 105 | # ####################################################### 106 | 107 | # ######################################################## 108 | # # DEMO 2 - Linear Array of Emitters 109 | # r = np.linspace(-lambda0/4, lambda0/4, N) 110 | # angle = np.pi/4 111 | # xs = r*np.cos(angle) 112 | # ys = r*np.sin(angle) 113 | # phi = np.linspace(0 , np.pi/2, N) 114 | # for i in range(N): 115 | # e = Emitter(xs[i], ys[i], c, f, phi[i]) 116 | # emitter_array.AddEmitter(e) 117 | # ####################################################### 118 | 119 | # ######################################################## 120 | # # DEMO 3 - Focussed Array 121 | xs = np.linspace(-lambda0, lambda0, N) 122 | ys = np.zeros_like(xs) 123 | for i in range(N): 124 | e = Emitter(xs[i], ys[i], c, f, 0) 125 | phase = CalculatePhaseFromFocus(0, 20, e) 126 | e.SetPhase(phase) 127 | emitter_array.AddEmitter(e) 128 | # ####################################################### 129 | 130 | # ######################################################## 131 | # # DEMO 4 - Dual Frequency Emitters 132 | # xs = np.linspace(-lambda0/4, lambda0/4, N) 133 | # ys = np.zeros_like(xs) 134 | # phi = np.linspace(0,np.pi/2,N) 135 | # for i in range(N): 136 | # e = Emitter(xs[i], ys[i], c, f, phi[i]) 137 | # emitter_array.AddEmitter(e) 138 | 139 | # for i in range(N): 140 | # e = Emitter(xs[i], ys[i], c, 0.5*f, -phi[i], color = "red") 141 | # emitter_array.AddEmitter(e) 142 | # ####################################################### 143 | 144 | # # ######################################################## 145 | # # # DEMO 5 - Focussed Array 146 | # xs = np.linspace(-lambda0, lambda0, N) 147 | # ys = np.zeros_like(xs) 148 | # for i in range(N): 149 | # e = Emitter(xs[i], ys[i], c, f, 0) 150 | # phase = CalculatePhaseFromFocus(0, 20, e) 151 | # e.SetPhase(phase) 152 | # emitter_array.AddEmitter(e) 153 | 154 | # for i in range(N): 155 | # e = Emitter(xs[i], ys[i], c, 0.8*f, 0, color = "red") 156 | # phase = CalculatePhaseFromFocus(-20, 30, e) 157 | # e.SetPhase(phase) 158 | # emitter_array.AddEmitter(e) 159 | # # ####################################################### 160 | 161 | # # # ######################################################## 162 | # # # # DEMO 6 - Focussed Array Random 163 | # xs = np.random.uniform(-lambda0/2, lambda0/2, N) 164 | # ys = np.random.uniform(-lambda0/2, lambda0/2, N) 165 | # for i in range(N): 166 | # e = Emitter(xs[i], ys[i], c, f, 0) 167 | # phase = CalculatePhaseFromFocus(0, 20, e) 168 | # e.SetPhase(phase) 169 | # emitter_array.AddEmitter(e) 170 | # # # ####################################################### 171 | 172 | # # # ######################################################## 173 | # # # # DEMO 7 - Focussed Array Random 174 | # N = 20 175 | # xs = np.random.uniform(-lambda0*2, lambda0*2, N) 176 | # ys = np.random.uniform(-lambda0*2, lambda0*2, N) 177 | # for i in range(N): 178 | # e = Emitter(xs[i], ys[i], c, f, 0) 179 | # phase = CalculatePhaseFromFocus(0, 20, e) 180 | # e.SetPhase(phase) 181 | # emitter_array.AddEmitter(e) 182 | # # # ####################################################### 183 | 184 | 185 | emitter_array.RemoveOffset() 186 | 187 | fig, ax = plt.subplots() 188 | ax.set_xlim([-X/2, Y/2]) 189 | ax.set_ylim([-X/2, Y/2]) 190 | ax.set_aspect(1) 191 | ax.grid(alpha=0.2) 192 | fig.tight_layout() 193 | 194 | for circle in emitter_array.circles: 195 | ax.add_patch(circle) 196 | 197 | for emitter in emitter_array.emitters: 198 | ax.add_patch(plt.Circle(tuple(emitter.r), 0.4, color="purple")) 199 | 200 | 201 | def init(): 202 | return tuple(emitter_array.circles) 203 | 204 | 205 | def update(frame_number): 206 | emitter_array.Increment(1/FPS) 207 | return tuple(emitter_array.circles) 208 | 209 | 210 | if __name__ == "__main__": 211 | ani = FuncAnimation(fig, update, init_func=init, 212 | interval=1000/FPS, blit=True) 213 | plt.show() 214 | --------------------------------------------------------------------------------