AIMM simulator documentation¶
Last modified: 2022-11-30T09:59
Purpose¶
The AIMM simulator emulates a cellular radio system roughly following 5G concepts and channel models. The intention is to have an easy-to-use and fast system written in pure Python with minimal dependencies. It is especially designed to be suitable for interfacing to AI engines such as tensorflow
or pytorch
, and it is not a principal aim for it to be extremely accurate at the level of the radio channel. The simulator was developed for the AIMM project.
The github sources are at <https://github.com/keithbriggs/AIMM-simulator>.
Software dependencies¶
- Python 3.8 or higher.
- NumPy.
- Simpy.
- If real-time plotting is needed, matplotlib, with an appropriate backend such as PyQt5 (
pip install PyQt5
).
Installation¶
Three ways are possible:
- The simplest way, direct from PyPI:
pip install AIMM-simulator
. This will not always get the latest version. - Download the wheel, typically
dist/aimm_simulator-2.0.0-py3-none-any.whl
from github, and runpip install <wheel>
. - Alternatively, the package can be installed by downloading the complete repository (using the green
<> Code ⌄
button) as a zip, unpacking it, and then doingmake install_local
from inside the unpacked zip.
After installation, run a test with python3 examples/basic_test.py
.
Note that a plotting utility src/realtime_plotter.py
is included but not installed. If needed, this script should be placed somewhere in your python path.
A folder img
is used by the examples to save plots in png and pdf format. So in order to run the examples with plotting, this folder must be created.
The documentation can be built from the sources with make doc
.
Quick start¶
The following example will test the installation and introduce the basic concepts. This creates a simulator instance sim
, creates one cell, creates one UE and immediately attaches it to the cell, and runs for 100 seconds of simulated time (typically about 0.03 seconds of run time). There is no logger defined, which means there will be no output apart from a few set-up messages (which are sent to stderr). The code is in AIMM_simulator_example_n0.py
.
from AIMM_simulator import Sim
sim=Sim()
sim.make_cell()
sim.make_UE().attach_to_nearest_cell()
sim.run(until=100)
The basic steps to build and run a simulation are:
- Create a
Sim
instance. - Create one or more cells with
make_cell()
. Cells are given a unique index, starting from 0. - Create one or more UEs with
make_UE()
. UEs are given a unique index, starting from 0. - Attach UEs with the method
attach_to_nearest_cell()
. - Create a
Scenario
, which typically moves the UEs according to some mobility model, but in general can include any events which affect the network. - Create one or more instances of
Logger
. - Optionally create a
RIC
, possibly linking to an AI engine. - Start the simulation with
sim.run()
. - Plot or analyse the results in the logfiles.
The AIMM simulator uses a discrete event simulation framework. Internally, a queue of pending events is maintained, but this is invisible to the programmer.
All functions and classes have default arguments appropriate to the simulation of a 5G macrocell deployment at 3.5GHz. This means that setting up a simple simulation is almost trivial, but also means that care is needed to set parameters correctly for other scenarios. Subbanding is implemented on all Cell
objects, but the number of subbands may be set to 1, effectively switching off this feature.
The AIMM simulator normally operates without a graphical user interface, and just writes logfiles for subsequent analysis. The default logfile format is tab-separated columns, with purely numerical data. These files can then be easily processed with shell utilities such as cut
, head
, tail
, etc., or read into python or R scripts, or, if all else fails, even imported into spreadsheet programs. However, a custom logger can create a logfile in any desired format.
AIMM simulator blocks¶
Tutorial examples¶
Example 1¶
This example (the code is in AIMM_simulator_example_n1.py
) creates a simulator instance, creates one cell, creates four UEs and immediately attaches them to the cell, adds a default logger, and runs for 100 seconds of simulated time. UEs by default are placed randomly in a 1km square.
1 2 3 4 5 6 | from AIMM_simulator import Sim,Logger sim=Sim() cell=sim.make_cell() ues=[sim.make_UE().attach(cell) for i in range(4)] sim.add_logger(Logger(sim,logging_interval=10)) sim.run(until=100) |
Typical output follows. The locations are 3-dimensional, with the z component being the antenna height. The default logger prints 3 columns to stdout, with a row for each UE report: cell index, UE index, CQI value. We will see later how to create a custom logger.
Cell[0] is at [434.44 591.64 20. ]
UE[0] is at [602.14 403.7 2. ]
UE[1] is at [263.87 301.28 2. ]
UE[2] is at [319.12 506.63 2. ]
UE[3] is at [370.7 394.92 2. ]
Sim: starting main loop for simulation time 100 seconds...
0 0 15
0 1 15
0 2 15
0 3 15
...
Sim: finished main loop in 0.04 seconds.
Example 2 - adding a scenario¶
A scenario is created by subclassing the Scenario
class. The code is in AIMM_simulator_example_n2.py
. The subclass must implement the loop
method as in the example: it must have an infinite loop, yielding an s.sim.wait()
object, which determines the time to the next event; in this case the next change to the UE positions. In this example, an MME is also added. This handles UE handovers, and ensures that UEs are always attached to the nearest cell.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from numpy.random import standard_normal class MyScenario(Scenario): def loop(self,interval=0.1): while True: for ue in self.sim.UEs: ue.xyz[:2]+=standard_normal(2) yield self.sim.wait(interval) def example_n2(): sim=Sim() cell=sim.make_cell() for i in range(4): sim.make_UE().attach(cell) sim.add_logger(Logger(sim,logging_interval=10)) sim.add_scenario(MyScenario(sim)) sim.add_MME(MME(sim,interval=10.0)) sim.run(until=500) example_n2() |
Example 3 - adding a custom logger¶
There are two ways to create a custom logger. The simplest way is to specify a function when creating the Logger instance. This function must accept two arguments, the Sim instance and the file object to write to. Example code for this method is in AIMM_simulator_example_n3a.py
. The custom logger must format a line of output, and write it to the file object f
. Access to all simulation variables is possible; see the API documentation below. A convenience function np_array_to_str
is available, which removes square brackets and commas from normal numpy array formatting.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | from numpy.random import standard_normal,seed def scenario_func(sim): for ue in sim.UEs: ue.xyz[:2]+=standard_normal(2) def example_n3a(): def logger_func(f): for cell in sim.cells: for ue_i in cell.reports['cqi']: xy=sim.get_UE_position(ue_i)[:2] tp=np_array_to_str(cell.get_UE_throughput(ue_i)) f.write(f'{sim.env.now:.1f}\t{cell.i}\t{ue_i}\t{xy[0]:.0f}\t{xy[1]:.0f}\t{tp}\n') sim=Sim() for i in range(4): sim.make_cell() for i in range(8): sim.make_UE().attach_to_nearest_cell() sim.add_logger(Logger(sim,func=logger_func,header='#time\tcell\tUE\tx\ty\tthroughput Mb/s\n',logging_interval=1)) sim.add_scenario(Scenario(sim,func=scenario_func)) sim.add_MME(MME(sim,interval=10.0)) sim.run(until=10) seed(1) example_n3a() |
More generally, a custom logger can be created by subclassing the Logger
class. The subclass must implement the loop
method as in the example: it must have an infinite loop, yielding an s.sim.wait()
object, which determines the time to the next write to the logfile (which defaults to stdout). The custom logger must format a line of output, and write it to the file object self.f
. Example code for this method is in AIMM_simulator_example_n3.py
.
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 | from numpy.random import standard_normal,seed class MyScenario(Scenario): def loop(self,interval=0.1): while True: for ue in self.sim.UEs: ue.xyz[:2]+=standard_normal(2) yield self.sim.wait(interval) class MyLogger(Logger): def loop(self): self.f.write('#time\tcell\tUE\tx\ty\tthroughput\n') while True: for cell in self.sim.cells: for ue_i in cell.reports['cqi']: xy=self.sim.get_UE_position(ue_i)[:2] tp=np_array_to_str(cell.get_UE_throughput(ue_i)) self.f.write(f't={self.sim.env.now:.1f}\tcell={cell.i}\tUE={ue_i}\tx={xy[0]:.0f}\ty={xy[1]:.0f}\ttp={tp}Mb/s\n') yield self.sim.wait(self.logging_interval) def example_n3(): sim=Sim() for i in range(4): sim.make_cell() for i in range(8): sim.make_UE().attach_to_nearest_cell() sim.add_logger(MyLogger(sim,logging_interval=1)) sim.add_scenario(MyScenario(sim)) sim.add_MME(MME(sim,interval=10.0)) sim.run(until=10) seed(1) example_n3() |
Typical output is:
#time cell UE x y throughput Mb/s
0.0 0 5 618 694 6.63
0.0 0 7 435 549 1.17
0.0 1 1 709 593 13.26
0.0 2 0 395 405 0.98
0.0 2 2 567 266 2.65
0.0 2 3 718 496 0.61
0.0 2 4 484 346 2.65
0.0 2 6 310 377 0.61
1.0 0 5 616 694 6.63
1.0 0 7 437 548 1.17
1.0 1 1 710 592 13.26
1.0 2 0 395 406 0.98
1.0 2 2 566 264 2.05
1.0 2 3 719 497 0.61
1.0 2 4 484 347 2.65
1.0 2 6 312 377 0.61
Example 4 - adding a RIC¶
A RIC (radio intelligent controller) is an agent able to control any aspect of a cell configuration in real time. This toy example illustrates the detection of the UE with the lowest throughout (throughputs[0][1]
, after the sort), and allocates a new subband to the cell serving that UE.
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 | from numpy.random import standard_normal,seed class MyScenario(Scenario): def loop(self,interval=0.1): while True: for ue in self.sim.UEs: ue.xyz[:2]+=standard_normal(2) yield self.sim.wait(interval) class MyLogger(Logger): def loop(self): self.f.write('#time\tcell\tUE\tx\ty\tthroughput\n') while True: for cell in self.sim.cells: for ue_i in cell.reports['cqi']: xy=self.sim.get_UE_position(ue_i)[:2] tp=np_array_to_str(cell.get_UE_throughput(ue_i)) self.f.write(f't={self.sim.env.now:.1f}\tcell={cell.i}\tUE={ue_i}\tx={xy[0]:.0f}\ty={xy[1]:.0f}\ttp={tp}Mb/s\n') yield self.sim.wait(self.logging_interval) class MyRIC(RIC): def loop(self,interval=10): while True: throughputs=[(self.sim.cells[ue.serving_cell.i].get_UE_throughput(ue.i),ue.i,) for ue in self.sim.UEs] throughputs.sort() mask=self.sim.UEs[throughputs[0][1]].serving_cell.subband_mask for i,bit in enumerate(mask): if bit==0: mask[i]=1 break yield self.sim.wait(interval) def example_n4(): sim=Sim() for i in range(4): sim.make_cell() for i in range(8): sim.make_UE().attach_to_nearest_cell() sim.add_logger(MyLogger(sim,logging_interval=1)) sim.add_scenario(MyScenario(sim)) sim.add_MME(MME(sim,interval=10.0)) sim.add_ric(MyRIC(sim,interval=10.0)) sim.run(until=10) seed(1) example_n4() |
Example 5 - adding an antenna radiation pattern¶
In this example there are two cells, and one UE which is driven around a circle centred on Cell[0] by the MyScenario class. There is no MME, so no handovers occur. With omnidirectional antennas, the UE would be exactly at the cell edge at times which are multiples of 100 seconds. But with the pattern implemented for Cell[0] in line 29, the antenna has a beam pointing east, towards the interfering cell (Cell[1], which remains omnidirectional). This considerable improves the average throughput.
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 | from math import cos,sin,pi from numpy.random import seed from AIMM_simulator import Sim,Scenario,Logger,to_dB class MyScenario(Scenario): # move the UE around a circle of specified radius, period T seconds def loop(self,interval=1.0,radius=100.0,T=100.0): while True: t=self.sim.env.now for ue in self.sim.UEs: ue.xyz[:2]=radius*cos(2*pi*t/T),radius*sin(2*pi*t/T) yield self.sim.wait(interval) class MyLogger(Logger): def loop(self,ue_i=0): ' log for UE[ue_i] only, from reports sent to Cell[0]. ' cell=self.sim.cells[0] UE=self.sim.UEs[ue_i] while True: tm=self.sim.env.now # current time xy=UE.get_xyz()[:2] # current UE position tp=cell.get_UE_throughput(ue_i) # current UE throughput self.f.write(f'{tm:.1f}\t{xy[0]:.0f}\t{xy[1]:.0f}\t{tp}\n') yield self.sim.wait(self.logging_interval) def example_n5(): sim=Sim() pattern=lambda angle: 10.0+to_dB(abs(cos(0.5*angle*pi/180.0))) cell0=sim.make_cell(xyz=[ 0.0,0.0,20.0],pattern=pattern) cell1=sim.make_cell(xyz=[200.0,0.0,20.0]) ue=sim.make_UE(xyz=[100.0,0.0,2.0]) ue.attach(cell0) sim.add_logger(MyLogger(sim,logging_interval=5)) sim.add_scenario(MyScenario(sim)) sim.run(until=500) seed(1) example_n5() |
A typical command to run this using the real-time plotter would be:
python3 AIMM_simulator_example_n5.py | ./realtime_plotter_05.py -np=3 -tm=500 -ylims='{0: (-100,100), 1: (-100,100), 2: (0,45)}' -ylabels='{0: "UE[0] $x$", 1: "UE[0] $y$", 2: "UE[0] throughput"}' -fnb='img/AIMM_simulator_example_n5'
.
This generates a plot like this:
Example 6 - a hetnet (heterogeneous network) with macro and small cells¶
In this example we start with 9 macro-cells in a 3x3 grid arrangement (line 30). We then drop 50 UEs at random into the system (line 32), and start the simulation (there is no UE mobility). The logger just computes the average throughput over all UEs. The scenario has these discrete events:
- At time 20, 20 small cells at random locations are added to the system (line 8). There is a drop in average throughput because the new cells just generate interference and are not yet used as serving cells.
- At time 40, the UEs are reattached to the best cell (line 11). Now throughput improves to above the initial value, because some UEs are now served by a nearby small cell.
- At time 60, subband masks are applied to make the macro and small cells non-interfering. Macro cells are given 1/4 of the channel bandwidth (line 15), and the small cells have 3/4 of the channel bandwidth (line 17). Now throughput improves further. As this subband allocation will not be optimal, further improvement will still be possible.
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 | from sys import stderr from numpy.random import seed from AIMM_simulator import Sim,Logger,Scenario class MyScenario(Scenario): def loop(self): yield self.sim.wait(20) # wait 20 seconds for i in range(20): self.sim.make_cell(power_dBm=10.0,n_subbands=4) print(f'small cells added at t={self.sim.env.now:.2f}',file=stderr) yield self.sim.wait(20) # wait 20 seconds for UE in self.sim.UEs: UE.attach_to_strongest_cell_simple_pathloss_model() print(f'UEs reattached at t={self.sim.env.now:.2f}',file=stderr) yield self.sim.wait(20) # wait 20 seconds for i in range(9): # set subband mask for macro cells self.sim.cells[i].set_subband_mask([1,0,0,0]) for i in range(9,29): # set subband mask for small cells self.sim.cells[i].set_subband_mask([0,1,1,1]) print(f'cells masked at t={self.sim.env.now:.2f}',file=stderr) class MyLogger(Logger): def loop(self): while True: # log average throughput over all UEs ave=self.sim.get_average_throughput() self.f.write(f'{self.sim.env.now:.2f}\t{ave:.4}\n') yield self.sim.wait(self.logging_interval) def hetnet(): sim=Sim() for i in range(9): sim.make_cell(xyz=(1000.0*(i//3),1000.0*(i%3),20.0),power_dBm=30.0,n_subbands=4) for i in range(50): sim.make_UE().attach_to_strongest_cell_simple_pathloss_model() sim.add_logger(MyLogger(sim,logging_interval=1.0e-1)) sim.add_scenario(MyScenario(sim)) sim.run(until=100) seed(3) hetnet() |
The command
python3 AIMM_simulator_example_n6.py | ./realtime_plotter_03.py -np=1 -tm=100 -ylims='(0,0.25)' -ylabels='{0: "average downlink throughput over all UEs"}' -fnb='img/AIMM_simulator_example_n6'
then generates a plot like this:
Example 7 - a hetnet with mobility¶
This is similar to example 6, but now an MME is added to perform handovers for the mobile UEs.
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 | from numpy.random import seed,standard_normal from AIMM_simulator import Sim,Logger,Scenario,MME,np_array_to_str class MyScenario(Scenario): def loop(self,interval=10): while True: for ue in self.sim.UEs: ue.xyz[:2]+=20*standard_normal(2) yield self.sim.wait(interval) class MyLogger(Logger): # throughput of UE[0], UE[0] position, serving cell index def loop(self): while True: sc=self.sim.UEs[0].serving_cell.i tp=self.sim.cells[sc].get_UE_throughput(0) xy0=np_array_to_str(self.sim.UEs[0].xyz[:2]) self.f.write(f'{self.sim.env.now:.2f}\t{tp:.4f}\t{xy0}\t{sc}\n') yield self.sim.wait(self.logging_interval) def hetnet(n_subbands=1): sim=Sim() for i in range(9): # macros sim.make_cell(xyz=(500.0*(i//3),500.0*(i%3),20.0),power_dBm=30.0,n_subbands=n_subbands) for i in range(10): # small cells sim.make_cell(power_dBm=10.0,n_subbands=n_subbands) for i in range(20): sim.make_UE().attach_to_strongest_cell_simple_pathloss_model() sim.UEs[0].set_xyz([500.0,500.0,2.0]) for UE in sim.UEs: UE.attach_to_strongest_cell_simple_pathloss_model() sim.add_logger(MyLogger(sim,logging_interval=1.0)) sim.add_scenario(MyScenario(sim)) sim.add_MME(MME(sim,verbosity=0,interval=50.0)) sim.run(until=2000) seed(3) hetnet() |
The command
python3 AIMM_simulator_example_n7.py | ./realtime_plotter_03.py -np=4 -tm=2000 -ylims='{0: (0,10), 1: (0,1000), 2: (0,1000), 3: (0,30)}' -ylabels='{0: "UE[0] throughput", 1: "UE[0] $x$", 2: "UE[0] $y$", 3: "UE[0] serving cell"}' -fnb='img/AIMM_simulator_example_n7'
then generates a plot like this:
Example 8 - estimating CQI distribution¶
This is similar to example 7, but now we create a histogram at the end of the simulation, rather than using real-time plotting. A MIMO gain boost is added half-way through the simulation, in order to observe the effect on the CQI values at UE[0]. This example also illustrates the use of the finalize
function, to create the histograms after the simulation has finished.
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 | from math import cos,sin,pi import numpy as np from numpy.random import seed,standard_normal from AIMM_simulator import Sim,Logger,Scenario,MME class MyScenario(Scenario): def loop(self,interval=1,radius=100.0,T=100.0,circle=False): while True: for i,ue in enumerate(self.sim.UEs): if circle and i==0: # walk UE[0] around a circle t=self.sim.env.now ue.xyz[:2]=500+radius*cos(2*pi*t/T),500+radius*sin(2*pi*t/T) else: # random walk, mean speed=1 ue.xyz[:2]+=standard_normal(2)/1.414 yield self.sim.wait(interval) class Histogram_Logger(Logger): # CQI histogram for UE[0] h_cqi0=np.zeros(16) h_cqi1=np.zeros(16) def loop(self): ue0=self.sim.UEs[0] while self.sim.env.now<0.5*self.sim.until: sc=ue0.get_serving_cell() cqi=ue0.get_CQI() if cqi is not None: self.h_cqi0[cqi[0]]+=1 yield self.sim.wait(self.logging_interval) # half-time break - boost MIMO gain of all cells for cell in self.sim.cells: cell.set_MIMO_gain(6.0) while True: sc=ue0.get_serving_cell() cqi=ue0.get_CQI() if cqi is not None: self.h_cqi1[cqi[0]]+=1 yield self.sim.wait(self.logging_interval) def finalize(s): hs=(s.h_cqi0/np.sum(s.h_cqi0),s.h_cqi1/np.sum(s.h_cqi1)) plot_histograms(hs) def plot_histograms(hs,fn='examples/img/AIMM_simulator_example_n8'): import matplotlib.pyplot as plt from matplotlib.patches import Rectangle from matplotlib.collections import PatchCollection from fig_timestamp import fig_timestamp ymax=max(max(h) for h in hs) fig=plt.figure(figsize=(8,8)) ax=fig.add_subplot(1,1,1) ax.grid(linewidth=1,color='gray',alpha=0.25) ax.set_xlabel('CQI for UE[0]'); ax.set_ylabel('relative frequency') ax.set_xlim(0,15); ax.set_ylim(0,1.1*ymax) for h,fc,x in zip(hs,('b','r'),(-0.1,0.1),): ax.add_collection(PatchCollection([Rectangle((i+x,0),0.2,hi) for i,hi in enumerate(h)],facecolor=fc,alpha=0.8)) ax.annotate('blue: normal\nred: after 6dB MIMO gain boost',(7,0.97*ymax),color='k',fontsize=14,bbox=dict(facecolor='w',edgecolor='k',boxstyle='round,pad=1')) fig_timestamp(fig,author='Keith Briggs',fontsize=8) for h,fc in zip(hs,('b','r'),): mean=sum(i*hi for i,hi in enumerate(h))/np.sum(h) ax.text(mean,-0.04,'mean',ha='center',va='center',rotation=90,size=8,bbox=dict(boxstyle='rarrow,pad=0.1',fc=fc,ec=fc,lw=1)) fig.savefig(fn+'.pdf') fig.savefig(fn+'.png') def example_n8(): sim=Sim() for i in range(9): # cells sim.make_cell(xyz=(300+200.0*(i//3),300+200.0*(i%3),10.0),power_dBm=10.0) for i in range(9): # UEs sim.make_UE(verbosity=1).attach_to_strongest_cell_simple_pathloss_model() sim.UEs[0].set_xyz([503.0,507.0,2.0]) sim.UEs[0].attach_to_strongest_cell_simple_pathloss_model() logger=Histogram_Logger(sim,logging_interval=1.0) sim.add_logger(logger) sim.add_scenario(MyScenario(sim)) sim.add_MME(MME(sim,verbosity=0,interval=20.0)) sim.run(until=2*5000) if __name__=='__main__': seed(1) example_n8() |
The command
python3 AIMM_simulator_example_n8.py
then generates a plot like this:
Using the geometry_3d module¶
This module is provided for running indoor simulations, and takes account of wall adsorptions, but not reflections or diffraction. It is not a full ray-tracing code. The main task it performs is to compute intersections of signal paths with walls, and it is optimized to be fast for this application. An example of usage is below.
More details on usage to be added here.
from geometry_3d import block,Panel,Building,Ray,draw_building
blk0=block(np.array([0, 0,0]),np.array([5,10,3]))
blk1=block(np.array([0,10,0]),np.array([6,12,2]))
blk2=block(np.array([0,12,0]),np.array([6,14,2]))
blk3=block(np.array([0,14,0]),np.array([6,16,2]))
blk4=block(np.array([0,16.5,0]),np.array([6,17,2]))
fence=Panel([Triangle((8,0,0),(8,15,0),(8,15,1)),
Triangle((8,0,1),(8, 0,0),(8,15,1))])
b=Building(blk0+blk1+blk2+blk3+blk4+(fence,))
ray0=Ray((0.3,0.3,2.0),(0.1,1,-0.01))
line_segments=[(8,8),(18,18),(0,4)] # [xs,ys,zs]
draw_building(b,rays=[ray0],line_segments=line_segments,color='y',limits=[(0,10),(0,20),(0,4)],labels=['$x$','$y$','$z$'],fontsize=6,show=True,pdffn='building0.pdf')
This constructs a building like this:
Using the real-time plotting utility¶
As an aid to development and debugging, a stand-alone python script realtime_plotter_05.py
for real-time plotting is included. This reads stdin and plots in a window as the data is generated. By default, png and pdf images are saved when all data has been read. It is configured with these command-line arguments:
-np number of plots (default 1)
-tm t_max (maximum time on x-axis, default 10)
-xl x-axis label (default 'time')
-fst final sleep time (before closing the window and saving the images)
-fnb filename base
-ylims y-axis limits (a python dictionary)
-ylabels y-axis labels (a python dictionary
-title figure title
-lw line width (default 2)
If -fnb
is specifed, the final plot is saved as png and pdf figures.
Typical usage would be in a bash script like this:
1 2 3 4 5 6 7 8 9 10 | python3 AIMM_simulator_RIC_example_01.py | \ ./realtime_plotter.py \ -author='Keith Briggs' \ -nplots=7 \ -tmax=10000 \ -fst=30 \ -fnb='img/AIMM_simulator_RIC_example' \ -ylims='{0: (-100,100), 1: (-100,100), 2: (-1,16), 3: (-1,16), 4: (-1,16), 5: (0,50), 6: (0,9)}' \ -ylabels='{0: "$x$", 1: "$y$", 2: "CQI$_0$", 3: "CQI$_1$", 4: "CQI$_2$", 5: "throughput", 6: "serving cell"}' \ -title 'UE$_0$' |
This generates a plot like this:
Simulator module API reference¶
AIMM simulator¶
-
class
AIMM_simulator.
Sim
(params={'fc_GHz': 3.5, 'h_BS': 20.0, 'h_UT': 2.0}, show_params=True, rng_seed=0)[source]¶ Class representing the complete simulation.
Parameters: params (dict) – A dictionary of additional global parameters which need to be accessible to downstream functions. In the instance, these parameters will be available as sim.params
. Ifparams['profile']
is set to a non-empty string, then a code profile will be performed and the results saved to the filename given by the string. There will be some execution time overhead when profiling.-
get_best_rsrp_cell
(ue_i, dbg=False)[source]¶ Return the index of the cell delivering the highest RSRP at UE[i]. Relies on UE reports, and
None
is returned if there are not enough reports (yet) to determine the desired output.
-
get_nearest_cell
(xy)[source]¶ Return the index of the geographical nearest cell (in 2 dimensions) to the point xy.
-
get_strongest_cell_simple_pathloss_model
(xyz, alpha=3.5)[source]¶ Return the index of the cell delivering the strongest signal at the point xyz (in 3 dimensions), with pathloss exponent alpha. Note: antenna pattern is not used, so this function is deprecated, but is adequate for initial UE attachment.
-
make_UE
(**kwargs)[source]¶ Convenience function: make a new UE instance and add it to the simulation; parameters as for the UE class. Return the new UE instance.
-
-
class
AIMM_simulator.
Cell
(sim, interval=10.0, bw_MHz=10.0, n_subbands=1, xyz=None, h_BS=20.0, power_dBm=30.0, MIMO_gain_dB=0.0, pattern=None, f_callback=None, f_callback_kwargs={}, verbosity=0)[source]¶ Class representing a single Cell (gNB). As instances are created, the are automatically given indices starting from 0. This index is available as the data member
cell.i
. The variableCell.i
is always the current number of cells.Parameters: - sim (Sim) – Simulator instance which will manage this Cell.
- interval (float) – Time interval between Cell updates.
- bw_MHz (float) – Channel bandwidth in MHz.
- n_subbands (int) – Number of subbands.
- xyz ([float, float, float]) – Position of cell in metres, and antenna height.
- h_BS (float) – Antenna height in metres; only used if xyz is not provided.
- power_dBm (float) – Transmit power in dBm.
- MIMO_gain_dB (float) – Effective power gain from MIMO in dB. This is no more than a crude way to estimate the performance gain from using MIMO. A typical value might be 3dB for 2x2 MIMO.
- pattern (array or function) – If an array, then a 360-element array giving the antenna gain in dB in 1-degree increments (0=east, then counterclockwise). Otherwise, a function giving the antenna gain in dB in the direction theta=(180/pi)*atan2(y,x).
- f_callback – A function with signature
f_callback(self,kwargs)
, which will be called at each iteration of the main loop. - verbosity (int) – Level of debugging output (0=none).
-
boost_power_dBm
(p, mn=None, mx=None)[source]¶ Increase or decrease (if p<0) the transmit power in dBm to be used by this cell. If mn is not
None
, then the power will not be set if it falls below mn. If mx is notNone
, then the power will not be set if it exceeds mx. Return the new power.
-
get_RSRP_reports
()[source]¶ Return the current RSRP reports to this cell, as a list of tuples (ue.i, rsrp).
-
get_RSRP_reports_dict
()[source]¶ Return the current RSRP reports to this cell, as a dictionary ue.i: rsrp.
-
get_UE_CQI
(ue_i)[source]¶ Return the current CQI of UE[i] in the simulation, as an array across all subbands. An array of NaNs is returned if there is no report.
-
get_UE_throughput
(ue_i)[source]¶ Return the total current throughput in Mb/s of UE[i] in the simulation.
-
class
AIMM_simulator.
UE
(sim, xyz=None, reporting_interval=1.0, pathloss_model=None, h_UT=2.0, f_callback=None, f_callback_kwargs={}, verbosity=0)[source]¶ Represents a single UE. As instances are created, the are automatically given indices starting from 0. This index is available as the data member
ue.i
. The static (class-level) variableUE.i
is always the current number of UEs.Parameters: - sim (Sim) – The Sim instance which will manage this UE.
- xyz ([float, float, float]) – Position of UE in metres, and antenna height.
- h_UT (float) – Antenna height of user terminal in metres; only used if xyz is not provided.
- reporting_interval (float) – Time interval between UE reports being sent to the serving cell.
- f_callback – A function with signature
f_callback(self,kwargs)
, which will be called at each iteration of the main loop. - f_callback_kwargs – kwargs for previous function.
- pathloss_model – An instance of a pathloss model. This must be a callable object which
takes two arguments, each a 3-vector. The first represent the transmitter
location, and the second the receiver location. It must return the
pathloss in dB along this signal path.
If set to
None
(the default), a standard urban macrocell model is used. See furtherNR_5G_standard_functions_00.py
.
-
attach_to_nearest_cell
()[source]¶ Attach this UE to the geographically nearest Cell instance. Intended for initial attachment only.
-
attach_to_strongest_cell_simple_pathloss_model
()[source]¶ Attach to the cell delivering the strongest signal at the current UE position. Intended for initial attachment only. Uses only a simple power-law pathloss model. For proper handover behaviour, use the MME module.
-
get_serving_cell
()[source]¶ Return the current serving Cell object (not index) for this UE instance.
-
send_rsrp_reports
(threshold=-120.0)[source]¶ Send RSRP reports in dBm to all cells for which it is over the threshold. Subbands not handled.
-
send_subband_cqi_report
()[source]¶ For this UE, send an array of CQI reports, one for each subband; and a total throughput report, to the serving cell. What is sent is a 2-tuple (current time, array of reports). For RSRP reports, use the function
send_rsrp_reports
. Also save the CQI[1]s in s.cqi, and return the throughput value.
-
class
AIMM_simulator.
Scenario
(sim, func=None, interval=1.0, verbosity=0)[source]¶ Base class for a simulation scenario. The default does nothing.
Parameters: - sim (Sim) – Simulator instance which will manage this Scenario.
- func (function) – Function called to perform actions.
- interval (float) – Time interval between actions.
- verbosity (int) – Level of debugging output (0=none).
-
class
AIMM_simulator.
MME
(sim, interval=10.0, strategy='strongest_cell_simple_pathloss_model', anti_pingpong=30.0, verbosity=0)[source]¶ Represents a MME, for handling UE handovers.
Parameters: - sim (Sim) – Sim instance which will manage this Scenario.
- interval (float) – Time interval between checks for handover actions.
- verbosity (int) – Level of debugging output (0=none).
- strategy (str) – Handover strategy; possible values are
strongest_cell_simple_pathloss_model
(default), orbest_rsrp_cell
. - anti_pingpong (float) – If greater than zero, then a handover pattern x->y->x between cells x and y is not allowed within this number of seconds. Default is 0.0, meaning pingponging is not suppressed.
-
do_handovers
()[source]¶ Check whether handovers are required, and do them if so. Normally called from loop(), but can be called manually if required.
-
class
AIMM_simulator.
RIC
(sim, interval=10, verbosity=0)[source]¶ Base class for a RIC, for hosting xApps. The default does nothing.
Parameters: - sim (Sim) – Simulator instance which will manage this Scenario.
- interval (float) – Time interval between RIC actions.
- verbosity (int) – Level of debugging output (0=none).
-
class
AIMM_simulator.
Logger
(sim, func=None, header='', f=<_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>, logging_interval=10, np_array_to_str=<function np_array_to_str>)[source]¶ Represents a simulation logger. Multiple loggers (each with their own file) can be used if desired.
Parameters: - sim (Sim) – The Sim instance which will manage this Logger.
- func (function) – Function called to perform logginf action.
- header (str) – Arbitrary text to write to the top of the logfile.
- f (file object) – An open file object which will be written or appended to.
- logging_interval (float) – Time interval between logging actions.
NR 5G standard functions¶
UMa pathloss model¶
-
class
UMa_pathloss_model.
UMa_pathloss
(fc_GHz=3.5, h_UT=2.0, h_BS=25.0, LOS=True, h=20.0, W=20.0)[source]¶ Urban macrocell dual-slope pathloss model, from 3GPP standard 36.873, Table 7.2-1.
The model is defined in 36873-c70.doc from https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=2574.
This code covers the cases 3D-UMa LOS and NLOS. 3D-UMa = three dimensional urban macrocell model. LOS = line-of-sight. NLOS = non-line-of-sight.
-
__call__
(xyz_cell, xyz_UE)[source]¶ Return the pathloss between 3-dimensional positions xyz_cell and xyz_UE (in metres). Note that the distances, building heights, etc. are not checked to ensure that this pathloss model is actually applicable.
-
__init__
(fc_GHz=3.5, h_UT=2.0, h_BS=25.0, LOS=True, h=20.0, W=20.0)[source]¶ Initialize a pathloss model instance.
Parameters: - fc_GHz (float) – Centre frequency in GigaHertz (default 3.5).
- h_UT (float) – Height of User Terminal (=UE) in metres (default 2).
- h_BS (float) – Height of Base Station in metres (default 25).
- LOS (bool) – Whether line-of-sight model is to be used (default True).
- h (float) – Average building height (default 20, used in NLOS case only)
- W (float) – Street width (default 20, used in NLOS case only)
-
UMi pathloss model¶
-
class
UMi_pathloss_model.
UMi_streetcanyon_pathloss
(fc_GHz=3.5, h_UT=2.0, h_BS=10.0, LOS=True)[source]¶ Urban microcell dual-slope pathloss model, from 3GPP standard 36.873, Table 7.2-1. The model is defined in 36873-c70.doc from https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=2574. This code covers the cases 3D-UMi LOS and NLOS. 3D-UMi = three-dimensional urban street canyon model. LOS = line-of-sight. NLOS = non-line-of-sight.
-
__call__
(xyz_cell, xyz_UE)[source]¶ Return the pathloss between 3-dimensional positions xyz_cell and xyz_UE (in metres). Note that the distances, building heights, etc. are not checked to ensure that this pathloss model is actually applicable.
-
__init__
(fc_GHz=3.5, h_UT=2.0, h_BS=10.0, LOS=True)[source]¶ Initialize a pathloss model instance.
Parameters: - fc_GHz (float) – Centre frequency in GigaHertz (default 3.5).
- h_UT (float) – Height of User Terminal (=UE) in metres (default 2).
- h_BS (float) – Height of Base Station in metres (default 10 for UMi).
- LOS (bool) – Whether line-of-sight model is to be used (default True).
-
InH pathloss model¶
-
class
InH_pathloss_model.
InH_pathloss
(fc_GHz=3.5, h_UT=2.0, h_BS=4.0, LOS=True)[source]¶ 3D-InH indoor pathloss model, from 3GPP standard 36.873, Table 7.2-1. Indoor Hotspot cell with high (indoor) UE density.
The model is defined in 36873-c70.doc from https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=2574.
LOS = line-of-sight. NLOS = non-line-of-sight.
-
__call__
(xyz_cell, xyz_UE)[source]¶ Return the pathloss between 3-dimensional positions xyz_cell and xyz_UE (in metres). Note that the distances, heights, etc. are not checked to ensure that this pathloss model is actually applicable.
-
__init__
(fc_GHz=3.5, h_UT=2.0, h_BS=4.0, LOS=True)[source]¶ Initialize a pathloss model instance.
Parameters: - fc_GHz (float) – Centre frequency in GigaHertz (default 3.5).
- h_UT (float) – Height of User Terminal (=UE) in metres (default 2).
- h_BS (float) – Height of Base Station in metres (default 25).
-
geometry_3d module¶
-
class
geometry_3d.
Panel
(triangles)[source]¶ Represents a collection of triangles (which must be parallel) making up a single flat wall panel.
-
class
geometry_3d.
Plane
(point, normal)[source]¶ Represents an infinite plane, defined by a point on the plane and a normal vector
-
class
geometry_3d.
Ray
(tail, dv)[source]¶ Represents a ray, defined by a tail (starting point) and a direction vector
-
class
geometry_3d.
Triangle
(p0, p1, p2)[source]¶ Represents a planar triangle in 3d space, defined by three points. Unoriented.
-
plot
(ax, color='y', alpha=0.5, drawedges=True)[source]¶ Plots the triangle in 3d. For kwargs, see https://matplotlib.org/stable/api/collections_api.html#matplotlib.collections.Collection
-
-
geometry_3d.
block
(c0, c1)[source]¶ Represents a rectangular block with opposite corners c0 and c1, with each face a rectangular Panel
Real-time plotting utility¶
This is independent of the main simulator code. It reads output from the Logger via a pipe from stdin, and plots it using a matplotlib animation. It is driven with command-line options, which can be seen by running realtime_plotter.py --help
.
-h, --help show this help message and exit
--selftest self-test
-naxes NAXES number of axes
-nplots NPLOTS number of plots
-tmax TMAX t_max
-xlabel XLABEL x axis label
-fst FST final sleep time
-fnb FNB filename base
-ylims YLIMS y limits (dict)
-ylabels YLABELS ylabels (dict)
-title TITLE figure title
-lw LW plot linewidth
-author AUTHOR author name for plot bottom margin
-extra EXTRA extra features to be added to the plot; raw python code
-inputfile INPUTFILE file to read input from instead of stdin; in this case the plot is not displayed, but written to an mp4 file
-column_to_axis_map COLUMN_TO_AXIS_MAP column_to_axis_map (dict)
Last modified: 2022-11-30T09:59