.:: Antenna Evolver ::.
Useing simulated annealing algorithm to evolve an antenna
Program that uses simulated annealing to evolve optimized antennas
Simulated annealing is a computer algorithm for solving multi-variable problems using mutation, random walk algorithms and simulated natural selection. The alternative is the brute force method where you try every possible variable value. This however is a costly process and very slow compared to simulated annealing. Annealing is a term borrowed from metal working. Specifically heat treating. When you heat a piece of metal and then let it slowly cool down, the inner structures align themselves automatically to the most “relaxed” state, making the metal softer and easier to machine. When performing simulated annealing algorithm, you must randomly tweak variables and keep score of the overall system performance. If you come across a very good result, better than the previous best, you store all the input variables, as well as a numeric score for that trial. This process is then performed over and over until you feel the results are good enough. There is no way to tell if your result is perfect, but it will be very good. One problem of simulated annealing is that a program can get stuck with a solution (local maximum) while there is an even better solution (global maximum). When using simulated annealing it is good practice to use multiple threads or multiple generations. The reason for this is because the algorithm might get stuck with a very good result (local maximum), and the random walk algorithm doesn’t reach out far enough to find the better solution (global maximum). Having multiple threads or generations lets the program start fresh and take on a different path, perhaps getting close to the global maximum. I decided to generate antennas made out of bent wire, made out of three segments. Any more segments would require much more computing power.
My project is a proof of concept where I demonstrate how simulated annealing can be applied to antenna design evolution. I decided to not use simulated evolutionary algorithm because in that system there is a reproduction step. This is where two successful results are mated in hope that something better emerges. With antennas this is not a good idea. If you mate two antenna results together the performance will be far from optimal. You may as well generate a random antenna.
My strongest programming language is C#, but chose to use Python to write the simulation. This is mostly because there are antenna simulation modules available for Python that aren’t available for C#. There is also a C++ version of the library available for those who prefer that language. My simulation uses the PyNEC module to put together and test the radiation pattern of the antenna being evaluated. This module is actually a wrapper for the C++ NEC library. When evolving a system using simulated annealing, you must be able to evaluate each attempt. There should be a way to score a trial run in order to compare its result with previous trials. In my simulation program I am attempting to maximize the antenna gain and directionality. This means that I need to have a function that gives a score value based on the current mutated values in question. The way I decided to score my antennas is by storing the maximum gain value from my 360 degree polar gain array, and then divide the value by the average gain of the entire 360 of the antenna. This selection process promotes a final radiation pattern with a very pronounced primary lobe and smaller lobes around the rest of it. If you wanted to place more value on evolving just gain, you would just score the maximum gain value within the entire radiation pattern and leave out the average gain value.
In conclusion, since antenna design is a very complex process you can rely on already existing templates, or you can try every possible combination. There is an infinite amount of possible antennas though. Using simulated annealing you can continuously evolve an antenna and arrive at a good result in a short period of time.
The ground plane has been disabled and the antenna operates in free space.
This program using a Python module called, PyNEC. You can find the documentaton for it here...
http://tmolteno.github.io/necpp/libnecpp_8h.html#a9cf02620e0d0365cbdd24071a7ebeae9
NEC2 Documentation PDF for C++ (same function parameters as in Python)
You can get the PyNEC Python module here... https://pypi.org/project/PyNEC/
py -m pip install PyNEC==1.7.3.4
Python modules used in programs...
pip install matplotlib
pip install matplotlib.pyplot
pip install numpy
pip install mpl_toolkits
https://visualstudio.microsoft.com/visual-cpp-build-tools/
pip install PyNEC==1.7.3.4
You will also need this before installing PyNEC module...
https://visualstudio.microsoft.com/visual-cpp-build-tools/

Animated screen shot. ↑

Live view of evolution... ↑

Final optimal antenna radiation pattern...↑

Evolver output. Red star is the RF input node. The blue symbol is the tip of the antenna. ↑
PYTHON CODE: ANTENNA SIMULATION (OPTIMIZES BOTH GAIN AND DIRECTIONALITY):
Configuration variables in code...
antenna_thickness = Antenna thickness
max_length = Limit of antenna geometry
random_walk = Maximum random step size
mutations = How many times to mutate per generation
generations = How many generations to do
frequency = Antenna input frequency in MHz
DOWNLOAD PYTHON CODE #001
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 | #pip install matplotlib #pip install matplotlib.pyplot #pip install numpy #pip install mpl_toolkits #https://visualstudio.microsoft.com/visual-cpp-build-tools/ #pip install PyNEC==1.7.3.4 from PyNEC import * import numpy as np import random import time from datetime import datetime from mpl_toolkits import mplot3d import matplotlib.pyplot as plt #WELCOME print("SIMULATED ANNEALING ANTENNA EVOLVER (MUTLIPLE GENERATIONS) - ROSTISLAV PERSION") print() #track best antenna_thickness = 0.001 max_length = 1 random_walk = 0.1 mutations = 1000 generations = 10 frequency = 2400 #reset randomly best values total_best_x1 = 0.0 total_best_y1 = 0.0 total_best_z1 = 0.001 total_best_x2 = 0.0 total_best_y2 = 0.0 total_best_z2 = 0.002 total_best_x3 = 0.0 total_best_y3 = 0.0 total_best_z3 = 0.003 total_best_gain = 0 total_bestscore = 0 #start graph plt.ion() #set up graph fig = plt.figure(num='ROSTISLAV PERSION - ANTENNA EVOLVER') ax = plt.subplot(111, polar=True) ax.grid(True) #start evolution for j in range(1,generations + 1): #reset randomly best values bestgain = 0 lowegain = 0 bestscore = 0 best_x1 = 0.0 best_y1 = 0.0 best_z1 = 0.001 best_x2 = 0.0 best_y2 = 0.0 best_z2 = 0.002 best_x3 = 0.0 best_y3 = 0.0 best_z3 = 0.003 for i in range(0, mutations): #creation of a nec context context=nec_context() #get the associated geometry geo = context.get_geometry() #random number 1.... rnd1 = random.uniform(-random_walk, random_walk) x1 = best_x1 + rnd1 rnd1 = random.uniform(-random_walk, random_walk) y1 = best_y1 + rnd1 rnd1 = random.uniform(-random_walk, random_walk) z1 = best_z1 + rnd1 rnd1 = random.uniform(-random_walk, random_walk) x2 = best_x2 + rnd1 rnd1 = random.uniform(-random_walk, random_walk) y2 = best_y2 + rnd1 rnd1 = random.uniform(-random_walk, random_walk) z2 = best_z2 + rnd1 rnd1 = random.uniform(-random_walk, random_walk) x3 = best_x3 + rnd1 rnd1 = random.uniform(-random_walk, random_walk) y3 = best_y3 + rnd1 rnd1 = random.uniform(-random_walk, random_walk) z3 = best_z3 + rnd1 #node boundaries if x1 > max_length: x1 = max_length if x1 < -max_length: x1 = -max_length if y1 > max_length: y1 = max_length if y1 < -max_length: y1 = -max_length if z1 > max_length: z1 = max_length if z1 < -max_length: z1 = -max_length if x2 > max_length: x2 = max_length if x2 < -max_length: x2 = -max_length if y2 > max_length: y2 = max_length if y2 < -max_length: y2 = -max_length if z2 > max_length: z2 = max_length if z2 < -max_length: z2 = -max_length if x3 > max_length: x3 = max_length if x3 < -max_length: x3 = -max_length if y3 > max_length: y3 = max_length if y3 < -max_length: y3 = -max_length if z3 > max_length: z3 = max_length if z3 < -max_length: z3 = -max_length try: #add wires to the geometry geo.wire(1, 9, 0, 0, 0, x1, y1, z1, antenna_thickness, 1.0, 1.0) geo.wire(2, 9, x1, y1, z1, x2, y2, z2, antenna_thickness, 1.0, 1.0) geo.wire(3, 9, x2, y2, z2, x3, y3, z3, antenna_thickness, 1.0, 1.0) context.geometry_complete(0) #free space, no ground plane context.gn_card(-1, 0, 0, 0, 0, 0, 0, 0) #add a "ex" card to specify an excitation context.ex_card(0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0) #add a "fr" card to specify the frequency context.fr_card(0, 1, frequency, 100.0e6) #add a "rp" card to specify radiation pattern sampling parameters and to cause program execution context.rp_card(0, 91, 1, 0, 5, 0, 0, 0.0, 45.0, 4.0, 2.0, 1.0, 0.0) #get the radiation_pattern rp = context.get_radiation_pattern(0) # Gains are in decibels gains_db = rp.get_gain() gains = 10.0**(gains_db / 10.0) thetas = rp.get_theta_angles() * 3.1415 / 180.0 phis = rp.get_phi_angles() * 3.1415 / 180.0 #log the graph gains = np.sqrt(gains) #messing with numbers maxgain = (max(gains[ : , 0 ])) score = max(gains[ : , 0 ]) / (sum(gains[ : , 0 ]) / len(gains[ : , 0 ])) #check score if score > bestscore: bestscore = score bestgain = maxgain bestgain2 = (max(gains_db[ : , 0 ])) best_x1 = x1 best_y1 = y1 best_z1 = z1 best_x2 = x2 best_y2 = y2 best_z2 = z2 best_x3 = x3 best_y3 = y3 best_z3 = z3 best_thetas = thetas best_gains = gains best_gains2 = gains_db #update graph ax.clear() #draw previous global best, but only after the first evolution if j > 1: ax.plot(total_best_thetas, total_best_gains[:,0], color='#AAAAAA', linewidth=3) ax.plot(best_thetas, best_gains[:,0], color='#000000', linewidth=3) ax.set_title("LAST BEST IN GENERATION: " + str(j) + ", BEST GENERATION SCORE: " + str(np.round(score, 3)) + ", BEST TOTAL SCORE: " + str(np.round(total_bestscore,3)), va='bottom', fontsize=8) # re-drawing the figure fig.canvas.draw() # to flush the GUI events fig.canvas.flush_events() #time.sleep(0.1) except Exception as e: #print("PASSIVE ERROR: " + str(e)) pass #store global best if bestscore > total_bestscore: total_bestscore = bestscore total_best_gain = bestgain total_best_gain2 = bestgain2 total_best_x1 = best_x1 total_best_y1 = best_y1 total_best_z1 = best_z1 total_best_x2 = best_x2 total_best_y2 = best_y2 total_best_z2 = best_z2 total_best_x3 = best_x3 total_best_y3 = best_y3 total_best_z3 = best_z3 total_best_thetas = best_thetas total_best_gains = best_gains #output print("END OF EVOLUTION ITTERATION: " + str(j) + " OF " + str(generations)) now = datetime.now() date_time = now.strftime("%m/%d/%Y, %H:%M:%S") print("BEST GAIN AT HIGH SCORE IN THIS GENERATION: " + str(maxgain) + " EVOLUTION SCORE: " + str(bestscore) + " TIME: " + date_time) print("TOTAL BEST SCORE: " + str(total_bestscore)) print() #END OF generations RESULTS print("END OF EVOLUTION SIMULATION.") print() print("EVOLUTION COUNT: " + str(generations)) print("EVOLUTION SCORE: " + str(total_bestscore)) print("BEST GAIN FOUND: " + str(total_best_gain)) print() print("BEST X0: 0") print("BEST Y0: 0") print("BEST Z0: 0") print() print("BEST X1: " + str(total_best_x1)) print("BEST Y1: " + str(total_best_y1)) print("BEST Z1: " + str(total_best_z1)) print() print("BEST X2: " + str(total_best_x2)) print("BEST Y2: " + str(total_best_y2)) print("BEST Z2: " + str(total_best_z2)) print() print("BEST X3: " + str(total_best_x3)) print("BEST Y3: " + str(total_best_y3)) print("BEST Z3: " + str(total_best_z3)) print() #Final Graph ax.clear() ax.plot(total_best_thetas, total_best_gains[:,0], color='#FF0000', linewidth=3) ax.set_title("45 DEGREE ELEVATION... BEST TOTAL SCORE: " + str(np.round(total_bestscore,3)) + ", BEST MAX GAIN: " + str(np.round(total_best_gain2,3)), va='bottom', fontsize=8) fig.canvas.draw() fig.canvas.flush_events() #PLOT ANTENNA WIRES plt.ioff() fig = plt.figure() ax = fig.add_subplot(111, projection='3d') xpoints = np.array([0,total_best_x1,total_best_x2,total_best_x3]) ypoints = np.array([0,total_best_y1,total_best_y2,total_best_y3]) zpoints = np.array([0,total_best_z1,total_best_z2,total_best_z3]) ax.plot(xpoints, ypoints, zpoints) ax.scatter(0, 0, 0, c='red', marker='*', s=800) ax.scatter(total_best_x3, total_best_y3, total_best_z3, c='blue', marker='1', s=300) plt.show() |