This documentation is for Python version 1Other versions

Citing

If you use the software, please consider citing pylayers.

This page

1. What is PyLayers about ?

PyLayers is an ongoing endeavour to build a versatile, intuitive and efficient open-source platform for cross layer studies, deeply rooted in the physics of wireless propagation.

In the short term, the major targeted applications are in the field of wireless indoor radio localization and navigation. Further evolutions of the PyLayers platform could also help for tackling other related problems as designing energy efficient wireless 5G networks, smart grids and cities in a cooperative, parcimonious and resiliant way.

PyLayers owes immensly to two former FP7 European Projects (WHERE1 and WHERE2 ) which run between 2008 and 2013. Several examples provided and currently studied comes out from those 2 projects.

PyLayers is currently developped in the frame of the CORMORAN French ANR project.

An other purpose of PyLayers is engineering education. So, if you are a curious student who can see the fun of surfing radio waves with python, please have a look inside and join !

Bernard Uguen

Professor @ University of Rennes, ESIR, IETR CNRS, Brittany Region, France

1.1. Keywords

  • Python scientific framework
  • Computational Electromagnetism
  • Site specific Channel Modeling
  • Ray Tracing
  • Indoor Radio Propagation Modelling
  • Impulse Radio Ultra Wide Band IR-UWB (IEEE 802.15.4a)
  • Efficient handling of antenna patterns for UWB scenario
  • Indoor Localisation
  • Exploiting Multi Path Components (MPC) for positioning
  • Cooperative Positioning
  • Robust Geometric Positioning Algorithm (RGPA)
  • Radio channel modeling (site specific and statistical)
  • Channel Sounding
  • MIMO channel
  • PHY/MAC/NET
  • Wireless Body Area Network (IEEE 802.15.6)
  • Human mobility simulation
  • Motion capture

2. Warnings

This documentation is generated automatically with sphinx from the PyLayers source code files on github (https://github.com/pylayers/pylayers).

The collection of .s in the doc directory is used as a complementary battery of tests using runipy, it is used to track regressions and broken functionalities as it happens on a regular basis in a living project.

It is expected that the magic of git will gradually improves this document and the code at the same time.

Nothing in PyLayers is guarantee, nothing should be taken for granted. This documention is by far not sufficiently peer reviewed at that time to have reach a sufficient level of quality. It is expected that there are engineers and scientists out there, interested by the PyLayers endeavour curious to have a try, which will provide feed back and will contribute to the necessary continuous improvement of the PyLayers platform.

So, if you see any wrong assertion or have any suggestion please do not hesitate to provide a feed back on the issues page of the project

If you find a bug, please keep track of it in a . and gist it and send the link on the issues page.

Any feedback from users is more than welcome for any suggestions of improvement about the source code.

If you have any interest to be involved and to participate to the PyLayers project, the best way is to open a github account and to fork the project on your own machine. Use PyLayers for your own needs, and if you wish provide feedback to the project with a pull request.

3. Installation

PyLayers is developped on Linux platform but should also work on windows platform. If you are working on Windows and are not familiar with the Python ecosystem, a good idea is to install first Anaconda. It provides you most of the required dependencies.

3.1. Dependencies

Before to install pylayers you need to install the following dependencies


numpy>=1.6.1
scipy>=0.10.1
networkx>=1.7
matplotlib>=1.1.0
shapely>=1.2.14
descartes>=1.0
SimPy>=2.2
PIL>=1.1.5
bitstring>=3.0.2
pyintervall>=1.0
pandas >=0.12.0

The installation of matplotlib basemap requires installation of libraries GEOS
and PROJ4. The installation procedure is well described in

http://matplotlib.org/basemap/users/installing.html

For supporting the osm format PyLayers is relying on imposm. The installation
of imposm is not straighforward it requires to install first Tokyo-Cabinet
and Google-Protobuf library. PyLayers can work without imposm you need to
comment the only import to the module in pylayers/gis/osmparser.py.

http://imposm.org/docs/imposm/latest/install.html#id1

Most of these dependencies are handled in the setup.py script.

python setup.py install

3.2. Setting of environment variables $BASENAME and $PYLAYERS

It is required to define the two enviroment variables $PYLAYERS and $BASENAME

in your .bashrc add those 2 lines

#export PYLAYERS = <path to the directory where pylayers is installed>

for example if you install PyLyers in your home directory

export PYLAYERS=~/pylayers
export BASENAME=~/plproject

3.3. Testing

make test-code

4. Description of Antennas

PyLayers accepts various descriptions of the antenna radiation pattern. It goes from a simple antenna gain formula to a full polarization description.

  • Full description on a grid (.mat,.trx)
  • Scalar Harmonics decomposition (.sh2,.sh3)
  • Vector Harmonics decomposition (.vsh2,.vsh3)
  • Closed form formula for simplified antenna models (Omni,Gauss,WirePlate,...)

The following examples illustrates some features of the Antenna class.

from pylayers.antprop.antenna import *
/usr/local/lib/python2.7/dist-packages/pkg_resources.py:991: UserWarning: /home/uguen/.python-eggs is writable by group/others and vulnerable to attack when used with get_resource_filename. Consider a more secure location (set with .set_extraction_path or the PYTHON_EGG_CACHE environment variable).
  warnings.warn(msg, UserWarning)
<matplotlib.figure.Figure at 0x79a8990>

Lets start by loading an antenna from a vsh3 file.

A = Antenna('S1R1.vsh3')
A
FileName : S1R1.vsh3
-----------------------
fmin : 0.80GHz
fmax : 5.95GHz
step : 50.00MHz
Nf : 104
Not evaluated

In fact this is not a void antenna. There is a default file which has been loaded and there is an antenna down there. The only thing we know at that point is the name of the file which has extension ‘.vsh3’ which means vector spherical harmonics in shape 3. To list all the availble antenna files in the dedicated directory of the project it i possible to invoke the ls() method.

A.ls('sh3')
['S1R1.sh3', 'S2R2.sh3']
A.ls('vsh3')
['S1R1.vsh3',
 'S1R10.vsh3',
 'S1R11.vsh3',
 'S1R12.vsh3',
 'S1R13.vsh3']

At that point the radiation pattern of the antenna has not yet been evaluated. For a shape 3 pattern the method to calculate the pattern is Fsynth3() with the pattern option set to true.

A.Fsynth3(pattern=True)

The polar() method allows to superpose different pattern for a list o frequecies fGHz + If phd is precised the diagram is given as a function of + If thd is precised the diagram is given as a function of

f = plt.figure(figsize=(15,15))
a1 = f.add_subplot(121,polar=True)
f1,a1 = A.polar(fGHz=[3,4,5],phd=0,GmaxdB=0,fig=f,ax=a1)
a2 = f.add_subplot(122,polar=True)
f2,a2 = A.polar(fGHz=[3,4,5],thd=90,GmaxdB=5,fig=f,ax=a2)
./AP-Antenna1_files/AP-Antenna1_11_0.png
A
FileName : S1R1.vsh3
-----------------------
fmin : 0.80GHz
fmax : 5.95GHz
step : 50.00MHz
Nf : 104
-----------------------
Ntheta : 45
Nphi : 90
GmaxDB : 2.23 dB
   f = 5.60 GHz
   theta = 69.55 (degrees)
   phi = 271.01  (degrees)
A.C.show(typ='s3')
fig = gcf()
plt.tight_layout()
./AP-Antenna1_files/AP-Antenna1_13_0.png

4.1. Defining Antenna gain from closed form formulas

An antenna can be defined from closed-form expressions.

A = Antenna('Omni')
A.Fpatt(pattern=True)
A.polar()
(<matplotlib.figure.Figure at 0x7f991c9aa0d0>,
 <matplotlib.projections.polar.PolarAxes at 0x7f995bc67450>)
./AP-Antenna1_files/AP-Antenna1_18_1.png

4.2. Description With Scalar Spherical Harmonics

from pylayers.antprop.antenna import *
from pylayers.antprop.antssh import *
<matplotlib.figure.Figure at 0x7936310>
A = Antenna('S1R1.mat',directory='ant/UWBAN/Matfile')
A
FileName : S1R1.mat
-----------------------
fmin : 0.80GHz
fmax : 5.95GHz
step : 50.00MHz
Nf : 104
-----------------------
Ntheta : 91
Nphi : 180
GmaxDB : 2.23 dB
   f = 5.60 GHz
   theta = 70.00 (degrees)
   phi = 272.00  (degrees)
antenna name : Th1
date : 04/12/12
time : 15:55
Notes : Mohamed at the log

Serie : 1
Run : 1
Nb theta (lat) : 91
Nb phi (lon) :180

To calculate scalar spherical harmonics coefficient the method ssh(A,L) is used.

L = 5
A = ssh(A,L=5)
A
FileName : S1R1.mat
-----------------------
fmin : 0.80GHz
fmax : 5.95GHz
step : 50.00MHz
Nf : 104
-----------------------
Ntheta : 91
Nphi : 180
GmaxDB : 2.23 dB
   f = 5.60 GHz
   theta = 70.00 (degrees)
   phi = 272.00  (degrees)
antenna name : Th1
date : 04/12/12
time : 15:55
Notes : Mohamed at the log

Serie : 1
Run : 1
Nb theta (lat) : 91
Nb phi (lon) :180
plot(abs(A.S.Cx.s2[0]))
[<matplotlib.lines.Line2D at 0x83090d0>]
./AP-AntennaSSH_files/AP-AntennaSSH_6_1.png
A.savesh2()
/home/uguen/Bureau/P1/ant/S1R1.sh2  already exist
A.loadsh2()
plot(abs(A.S.Cx.s2[0]))
[<matplotlib.lines.Line2D at 0x84115d0>]
./AP-AntennaSSH_files/AP-AntennaSSH_9_1.png
A.S.s2tos3()
plot(abs(A.S.Cx.s3[0]))
[<matplotlib.lines.Line2D at 0x87e8550>]
./AP-AntennaSSH_files/AP-AntennaSSH_11_1.png
A.S.Cx.ind2.shape
(36, 2)
A.savesh3()
/home/uguen/Bureau/P1/ant/S1R1.sh3  already exist
plot(abs(A.S.Cx.s2[0]))
[<matplotlib.lines.Line2D at 0x8a0fb90>]
./AP-AntennaSSH_files/AP-AntennaSSH_14_1.png
A.loadsh3()
plot(abs(A.S.Cx.s3[100]))
[<matplotlib.lines.Line2D at 0x8a45210>]
./AP-AntennaSSH_files/AP-AntennaSSH_16_1.png
plot(abs(A.S.Cx.s2[100]))
[<matplotlib.lines.Line2D at 0x8cf9b50>]
./AP-AntennaSSH_files/AP-AntennaSSH_17_1.png
A.__dict__.keys()
['Nf',
 'PhotoFile',
 'Np',
 'SqG',
 'Run',
 'Nt',
 'Serie',
 'Ftheta',
 'theta',
 'fromfile',
 'phi',
 'Fphi',
 'Notes',
 'Date',
 'S',
 'AntennaName',
 'typ',
 'DataFile',
 'fa',
 'evaluated',
 'StartTime',
 '_filename']
A.S.Cx.__dict__.keys()
['k2', 'ind3', 'ind2', 'fmax', 's2', 'Nf', 's3', 'lmax', 'fmin']
A.S.Cx
Nf   : 104
fmin (GHz) : 0.8
fmax (GHz) : 5.95
NCoeff s2  : 36
Ncoeff s3 : 32

4.3. Description of Antennas with Vector Spherical Harmonics

from pylayers.antprop.antenna import *
from pylayers.antprop.antvsh import *
<matplotlib.figure.Figure at 0x85a9310>

Read a trx file. This file has no header, it contains 7 columns :

A = Antenna('S1R1.mat',directory='ant/UWBAN/Matfile')
#A = Antenna('trx1','UWB_CEA.trx')

The shape of the functions indicates :

  • :math:`N_{phi} = 180 `
figsize(5,5)
shape(A.Fphi)
(104, 91, 180)

The frequency ramp is converted in . In practice we always handles frequency expressed in and delays expressed in

fGHz = A.fa.reshape(104,1,1)/1e9

Then an electrical delay of is applied on the

tau=4.185
I = A.Ftheta[:,:,:]*exp(-2*1j*pi*fGHz*tau)
imshow(unwrap(angle(I[:,45,:])))
title(r'Unwrapped phase of $F_{\theta}$ w.r.t frequency and phi for $\theta=\frac{pi}{2}$')
ylabel('f index')
colorbar()
figure()
plot(fGHz[:,0,0],unwrap(angle(I[:,45,85])))
[<matplotlib.lines.Line2D at 0x9782b50>]
./AP-AntennaVSH_files/AP-AntennaVSH_9_1.png ./AP-AntennaVSH_files/AP-AntennaVSH_9_2.png

Display of the radiation pattern for all frequencies

for nf in range(104):
    polar(A.phi,abs(A.Ftheta[nf,45,:]))
./AP-AntennaVSH_files/AP-AntennaVSH_11_0.png

4.4. Evaluation of Vector Spherical Harmonics Coefficients

At that stage we compute the Vector Spherical Harmonics coefficients

A=vsh(A)
A.info()
S1R1.mat
type :  mat
S1R1
Th1
04/12/12
15:55
Mohamed at the log

1
1
Nb theta (lat) : 91
Nb phi (lon) : 180
No vsh coefficient calculated yet
A.C.s1tos2(30)
A.C
Br
-------------
N1  : 90
M1  : 89
Ncoeff s1 8010
NCoeff s2  : 495

Bi
-------------
N1  : 90
M1  : 89
Ncoeff s1 8010
NCoeff s2  : 495

Cr
-------------
N1  : 90
M1  : 89
Ncoeff s1 8010
NCoeff s2  : 495

Ci
-------------
N1  : 90
M1  : 89
Ncoeff s1 8010
NCoeff s2  : 495
figsize(10,10)
A.C.show('s2',k=300)
./AP-AntennaVSH_files/AP-AntennaVSH_19_0.png
A.C.s2tos3()
A.C
Br
-------------
N1  : 90
M1  : 89
Ncoeff s1 8010
NCoeff s2  : 495
Ncoeff s3 : 86

Bi
-------------
N1  : 90
M1  : 89
Ncoeff s1 8010
NCoeff s2  : 495
Ncoeff s3 : 86

Cr
-------------
N1  : 90
M1  : 89
Ncoeff s1 8010
NCoeff s2  : 495
Ncoeff s3 : 86

Ci
-------------
N1  : 90
M1  : 89
Ncoeff s1 8010
NCoeff s2  : 495
Ncoeff s3 : 86
A.C.show('s3')
./AP-AntennaVSH_files/AP-AntennaVSH_22_0.png

5. Slabs and Materials

Before introducing any model, lets start by introducing two major classes of the PyLayers platform on which is relying the electromagnetic field calculation.

A Slab is a set of several layers of materials with specified thickness. Slabs are used to describe properties of the different constitutive elements of a building such as wall, windows ,...

In practice when describing a specific building, we specify a set of different slabs with different dielectric characteristics.

The structure which gathers Slab is SlabDB. If no file argument is given, this structure is initialized with the default file:

slabDB.ini

We illustrates below some features of the pylayers.antprop.slab module.

from pylayers.antprop.slab import *
fig = plt.figure(figsize=(10,10))

The Class SlabDB contains a dictionnary of all available Slab. This information is read in the file slabDB.ini of the current project pointed by environment variable $BASENAME

A __repr__ method provides a visual indication of the thichness and materials of a Slab

S = SlabDB()
S.keys()
['WINDOW_GLASS',
 'PLASTERBOARD_7CM',
 'WALL',
 'AIR',
 'WINDOW',
 'METALIC',
 'PLASTERBOARD_14CM',
 'DOOR',
 'FLOOR',
 'METAL',
 'PARTITION',
 'CONCRETE_20CM3D',
 'PLASTERBOARD_10CM',
 'CEIL',
 'CONCRETE_6CM3D',
 'CONCRETE_15CM3D',
 '3D_WINDOW_GLASS',
 'WALLS',
 'WOOD',
 'CONCRETE_7CM3D',
 'PILLAR',
 'ABSORBENT']

5.1. Defining a New Slab and a New Material

S.mat.add(name='wall2',typ='reim',cval=2.6-0.026*1j,fGHz=4)
S.add('slab2',['wall2'],[0.15])
S.mat['wall2']
{'epr': array(2.6),
 'epr2': array(-0.026),
 'epsr': (2.6-0.026j),
 'fGHz': 4,
 'index': 11,
 'mur': 1,
 'n': (1.612-0.00806j),
 'name': 'wall2',
 'roughness': 0,
 'sigma': 0.0057}
S['slab2']['lmatname']
['wall2']

ev method is used to evaluate a Slab for a given range of frequencies and angles

fGHz= np.arange(3,5,0.01)
theta = np.arange(0,np.pi/2,0.01)
S['slab2'].ev(fGHz,theta)

pcolor() display the magnitude or phase of reflection ant transmission coefficients w.r.t angle anf frequency.

S['slab2'].pcolor()
./AP-SlabsMaterials_files/AP-SlabsMaterials_15_0.png
A=S['slab2']
R=A.R

5.2. Slab Information

Each slab contains informations about the electromagnetic properties of its constitutive materials.

Below is presented an example for a simple slab, constituted with a single material. The slab ‘WOOD’ is defined as a layer of 4cm ‘WOOD’ material.

lmatname list of material names

S['WOOD']['lmatname']
['WOOD']

lithickname list of slab thickness

S['WOOD']['lthick']
[0.04]

There is also a color attribute

S['WOOD']['color']
'maroon'
S['WOOD']['linewidth']
2

Multi layers Slab, using different stacks of materials can be easily defined using the two lists lmatname and lthick.

Notice the adopted convention naming lists starting with letter ‘l’ and dictionnaries starting with letter ‘d’
S['3D_WINDOW_GLASS']['lmatname']
['GLASS', 'AIR', 'GLASS']
S['3D_WINDOW_GLASS']['lthick']
[0.005, 0.005, 0.005]

For each constitutive material of a slab, their electromagnetic properties can be obtained as:

S['3D_WINDOW_GLASS']['lmat']
[{'epr': (3.79999995232+0j),
  'index': 4,
  'mur': (1+0j),
  'name': 'GLASS',
  'roughness': 0.0,
  'sigma': 0.0},
 {'epr': (1+0j),
  'index': 1,
  'mur': (1+0j),
  'name': 'AIR',
  'roughness': 0.0,
  'sigma': 0.0},
 {'epr': (3.79999995232+0j),
  'index': 4,
  'mur': (1+0j),
  'name': 'GLASS',
  'roughness': 0.0,
  'sigma': 0.0}]

5.3. Slab evaluation

Each Slab can be evaluated to obtain the Transmission and Reflexion coefficients for

  • a given frequency range
  • a given incidence angle range ()
fGHz = np.arange(3,5,0.01)
theta = np.arange(0,pi/2,0.01)

S['WOOD'].ev(fGHz,theta,compensate=True)
sR = np.shape(S['WOOD'].R)
print '\nHere, slab is evaluted for',sR[0],'frequency(ies)', 'and',sR[1], 'angle(s)\n'
Here, slab is evaluted for 200 frequency(ies) and 158 angle(s)

5.4. Transmission and Reflexion coefficients

Relexion and transmission coefficient are computed for the given frequency range and theta range

ifreq=1
ithet=10

print '\nReflection coefficient @',fGHz[ifreq],'GHz and theta=',theta[ithet],':\n\n R=',S['WOOD'].R[0,0]
print '\nTransmission coefficient @',fGHz[ifreq],'GHz and theta=',theta[ithet],':\n\n T=',S['WOOD'].T[0,0],'\n'
Reflection coefficient @ 3.01 GHz and theta= 0.1 :

 R= [[-0.39396205-0.17289585j  0.00000000+0.j        ]
 [ 0.00000000+0.j          0.39396205+0.17289585j]]

Transmission coefficient @ 3.01 GHz and theta= 0.1 :

 T= [[-0.17594898-0.86927604j -0.00000000+0.j        ]
 [-0.00000000+0.j         -0.17594898-0.86927604j]]

5.4.1. Ploting Coefficients

plotwrt (plot with respect to)

Parameters
----------

 kv  : int
    variable index
 polar: string
        'po', # po | p | o   (parallel+ortho | parallel | ortogonal)
 coeff: string
        'RT', # RT | R | T   (Reflexion & Transmission ) | Reflexion | Transmission
 var:  string
        'a',    # a | f       angle | frequency
 types : string
        'm' | 'r' | 'd' | 'l20'
        mod   rad    deg   dB
fig,ax = S['WOOD'].plotwrt(var='f',coeff='R',polar='p')
./AP-SlabsMaterials_files/AP-SlabsMaterials_36_0.png

with respect to angle

fig = plt.figure(figsize=(20,20))
fGHz= np.array([2.4])
S['WOOD'].ev(fGHz,theta)
fig,ax = S['WOOD'].plotwrt(var='a',coeff='R',fig=fig)
plt.tight_layout()
./AP-SlabsMaterials_files/AP-SlabsMaterials_38_1.png

wrt to angle and frequency

plt.figure(figsize=(10,10))
fGHz= np.arange(0.7,5.2,0.1)
S['WOOD'].ev(fGHz,theta)
S['WOOD'].pcolor()
./AP-SlabsMaterials_files/AP-SlabsMaterials_40_0.png
theta = np.arange(0,np.pi/2,0.01)
fGHz = np.arange(0.1,10,0.2)
sl = SlabDB('matDB.ini','slabDB.ini')
mat   = sl.mat
lmat  = [mat['AIR'],mat['WOOD']]
II    = MatInterface(lmat,0,fGHz,theta)
II.RT()
fig,ax = II.plotwrt(var='a',kv=10,typ=['m'])
tight_layout()
air = mat['AIR']
brick  = mat['BRICK']
II  = MatInterface([air,brick],0,fGHz,theta)
II.RT()
fig,ax = II.plotwrt(var='f',color='k',typ=['m'])
tight_layout()
./AP-SlabsMaterials_files/AP-SlabsMaterials_41_0.png ./AP-SlabsMaterials_files/AP-SlabsMaterials_41_1.png
## Adding new materials
sl.mat.add(name='TESS-p50',cval=3+0j,sigma=0.06,typ='epsr')

sl.add(name='TESS-p50-5cm',lmatname=['TESS-p50'],lthick=[0.05])
sl.add(name='TESS-p50-10cm',lmatname=['TESS-p50'],lthick=[0.10])
sl.add(name='TESS-p50-15cm',lmatname=['TESS-p50'],lthick=[0.15])
fGHz=4
theta = np.arange(0,np.pi/2,0.01)
#figure(figsize=(8,8))
# These Tessereau page 50

sl['TESS-p50-5cm'].ev(fGHz,theta,compensate=True)
sl['TESS-p50-10cm'].ev(fGHz,theta,compensate=True)
sl['TESS-p50-15cm'].ev(fGHz,theta,compensate=True)

# by default var='a' and kv = 0

fig,ax = sl['TESS-p50-5cm'].plotwrt(color='k',labels=[''])
fig,ax = sl['TESS-p50-10cm'].plotwrt(color='k',labels=[''],linestyle='dashed',fig=fig,ax=ax)
fig,ax = sl['TESS-p50-15cm'].plotwrt(color='k',labels=[''],linestyle='dashdot',fig=fig,ax=ax)
plt.tight_layout()
/usr/local/lib/python2.7/dist-packages/matplotlib/axes.py:4747: UserWarning: No labeled objects found. Use label='...' kwarg on individual plots.
  warnings.warn("No labeled objects found. "
./AP-SlabsMaterials_files/AP-SlabsMaterials_43_1.png

5.5. Evaluation without phase compensation

fGHz = np.arange(2,16,0.1)
theta = 0

sl['TESS-p50-5cm'].ev(fGHz,theta,compensate=False)
sl['TESS-p50-10cm'].ev(fGHz,theta,compensate=False)
sl['TESS-p50-15cm'].ev(fGHz,theta,compensate=False)

fig,ax = sl['TESS-p50-5cm'].plotwrt('f',coeff='T',typ=['ru'],labels=[''],color='k')
print ax
#fig,ax = sl['TESS-p50-10cm'].plotwrt('f',coeff='T',types=['ru'],labels=[''],color='k',linestyle='dashed',fig=fig,ax=ax)
#fig,ax = sl['TESS-p50-15cm'].plotwrt('f',coeff='T',types=['ru'],labels=[''],color='k',linestyle='dashdot')
plt.tight_layout()
./AP-SlabsMaterials_files/AP-SlabsMaterials_45_1.png
sl['TESS-p50-5cm'].ev(fGHz,theta,compensate=True)
sl['TESS-p50-10cm'].ev(fGHz,theta,compensate=True)
sl['TESS-p50-15cm'].ev(fGHz,theta,compensate=True)

fig,ax = sl['TESS-p50-5cm'].plotwrt('f',coeff='T',typ=['ru'],labels=['5cm compensated',''],color='r',fig=fig,ax=ax)
fig,ax = sl['TESS-p50-10cm'].plotwrt('f',coeff='T',typ=['ru'],labels=['10cm compensated',''],color='r',linestyle='dashed',fig=fig,ax=ax)
fig,ax = sl['TESS-p50-15cm'].plotwrt('f',coeff='T',typ=['ru'],labels=['15cm not compensated',''],color='r',linestyle='dashdot',fig=fig,ax=ax)

fig,ax = sl['TESS-p50-5cm'].plotwrt('f',coeff='T',labels=[''],color='k')
fig,ax = sl['TESS-p50-10cm'].plotwrt('f',coeff='T',labels=[''],color='k',linestyle='dashed',fig=fig,ax=ax)
fig,ax = sl['TESS-p50-15cm'].plotwrt('f',coeff='T',labels=[''],color='k',linestyle='dashdot',fig=fig,ax=ax)
./AP-SlabsMaterials_files/AP-SlabsMaterials_46_0.png

5.6. Double Glass example from litterature [1] in sub TeraHertz D-band @ 120GHz

sl.mat.add('ConcreteJc',cval=3.5,alpha_cmm1=1.9,fGHz=120,typ='THz')
sl.mat.add('GlassJc',cval=2.55,alpha_cmm1=2.4,fGHz=120,typ='THz')
sl.add('ConcreteJc',['ConcreteJc'],[0.049])

theta = np.linspace(20,60,100)*np.pi/180
sl['ConcreteJc'].ev(120,theta)
fig,ax = sl['ConcreteJc'].plotwrt('a')
./AP-SlabsMaterials_files/AP-SlabsMaterials_48_0.png
fGHz = np.linspace(110,135,50)
sl.add('DoubleGlass',['GlassJc','AIR','GlassJc'],[0.0029,0.0102,0.0029])
sl['DoubleGlass'].ev(fGHz,theta)
sl['DoubleGlass'].pcolor(dB=True)
./AP-SlabsMaterials_files/AP-SlabsMaterials_49_0.png
plt.figure(figsize=(10,10))
sl['DoubleGlass'].ev(120,theta)
fig,ax = sl['DoubleGlass'].plotwrt('a',figsize=(10,10))
tight_layout()
./AP-SlabsMaterials_files/AP-SlabsMaterials_50_1.png
freq = np.linspace(110,135,50)
sl['DoubleGlass'].ev(freq,theta)
fig,ax = sl['DoubleGlass'].plotwrt('f',figsize=(10,10))  # @20
tight_layout()
./AP-SlabsMaterials_files/AP-SlabsMaterials_51_0.png

6. The Layout class

This section explains the main features of the Layout class.

A Layout is a representation of a floorplan, it is handled by the module pylayers.gis.layout.

This module recognizes different file formats including geo-referenced files in open street map format .osm.

Using osm allows to take advantage of a mature floor plan editor JOSM with the plugin PicLayer. This is well described in http://wiki.openstreetmap.org/wiki/IndoorOSM

The pylayers.gis.osmparser module parses osm files.

See the following methods of the layout object

  • loadosm()
  • saveosm()

6.1. Structure of a Layout

A Layout is firstly described by a set of points (negative index) and a set of segments (positive index).

Both points and segments are nodes of the graph.

It is required to respect a strict non overlapping rule. No segments can recover partially or totally an other segment.

This rule allows to capture topological relations of the network which are exploited for further analysis.

6.2. Subsegments

To describe doors and windows, the concept of subsegment is introduced.

A segment has attributes :

  • name : slab name
  • z : tuple of minimum and maximum heights with respect to ground (meters)
  • transition : a boolean indicating if a human can cross this segment. For example, segments associated with a door are transition segments but we will see later that it may be judicious to split space with transparent segments which have the name ‘AIR’. Those segments are also transition=True

A subsegment belongs to a segment, it has mainly 2 attached parameters :

  • ss_name : subsegment slab name
  • ss_z : [(zmin1,zmax1),(zmin2,zmax2),...,(zminK,zmaxK))] list of minimum and maximum heights of associated subsegments (meters)

When appearing in a 3D ray a subsegment has a unique index different from the segment index.

6.3. Layout file formats

The layout format has regularly evolved over time and is going to evolve again. Currently, the different recognized file extensions are the following :

  • .str2: a ASCII file (Node list + edge list)
  • .str : a binary file which includes visibility relations between point and segments
  • .ini : an ini file which gather node list and edge list as well as the state of the current display dictionnary
  • .osm : an xml file which can be edited with JOSM
from pylayers.gis.layout import *
from pylayers.util.project import *

6.4. Reading an exiting Layout

To read an existing layout it is sufficient to create a Layout object with, as an argument, a file name with one of the recognized extension. All files are stored in the pstruc['DIRSTRUC'] directory of the project. The project root directory is defined in the $BASENAME environment variable.

print pstruc['DIRSTRUC']
struc/str

pstruc is a dictionnary which gathers all directories which are used in PyLayers

pstruc
{'DIRANT': 'ant',
 'DIRCIR': 'output',
 'DIRFUR': 'struc/furnitures',
 'DIRGEOM': 'geom',
 'DIRIMAGE': 'struc/images',
 'DIRINI': 'struc/ini',
 'DIRLCH': 'output',
 'DIRMAT': 'ini',
 'DIRMAT2': 'ini',
 'DIRMES': 'meas',
 'DIRNETSAVE': 'netsave',
 'DIROSM': 'struc/osm',
 'DIRPICKLE': 'struc/gpickle',
 'DIRR2D': 'output/r2d',
 'DIRR3D': 'output/r3d',
 'DIRSIG': 'output/sig',
 'DIRSIMUL': 'ini',
 'DIRSLAB': 'ini',
 'DIRSLAB2': 'ini',
 'DIRSTRUC': 'struc/str',
 'DIRSTRUC2': 'struc/str',
 'DIRTRA': 'output',
 'DIRTUD': 'output',
 'DIRTx': 'output/Tx001',
 'DIRWRL': 'struc/wrl'}

The structure of the .osm file is shown below

%%bash

    cd $BASENAME/struc
    ls *.osm
DLR.osm
%%bash
    cd $BASENAME/struc
    head DLR.osm
    echo '---'
    tail -17 DLR.osm
<?xml version='1.0' encoding='UTF-8'?>
<osm version='0.6' upload='false' generator='PyLayers'>
<node id='-212' action='modify' visible='true' lat='47.0100855114' lon='-1.98980710934' />
<node id='-210' action='modify' visible='true' lat='47.0100789151' lon='-1.9897910381' />
<node id='-208' action='modify' visible='true' lat='47.0100738861' lon='-1.98977878545' />
<node id='-206' action='modify' visible='true' lat='47.0100616861' lon='-1.98982814281' />
<node id='-204' action='modify' visible='true' lat='47.0101583649' lon='-1.98982436917' />
<node id='-202' action='modify' visible='true' lat='47.0101656174' lon='-1.98981796656' />
<node id='-200' action='modify' visible='true' lat='47.0101843662' lon='-1.98977935424' />
<node id='-198' action='modify' visible='true' lat='47.0101791636' lon='-1.98982426816' />
---
<tag k='transition' v='False' />
</way>
<way id='-10000123' action='modify' visible='true'>
<nd ref='-200' />
<nd ref='-100' />
<tag k='name' v='WALL' />
<tag k='z' v="('0.0', '3.0')" />
<tag k='transition' v='False' />
</way>
<way id='-10000124' action='modify' visible='true'>
<nd ref='-166' />
<nd ref='-188' />
<tag k='name' v='WALL' />
<tag k='z' v="('0.0', '3.0')" />
<tag k='transition' v='False' />
</way>
</osm>

To read a new layout in osm format :

L=Layout('DLR.osm')
fig,ax=L.showGs()
./GIS-editor_files/GIS-editor_20_0.png
L.info()
filestr :  DLR.osm
filematini :  matDB.ini
fileslabini :  slabDB.ini
filegeom :  DLR.off
boundaries  (758.35005883924691, 794.77088532257221, 1111.8980005947324, 1138.9865273726507)
number of Points : 105
number of Segments : 124
number of Sub-Segments : 30
Gs Nodes :  229
Gs Edges :  248
Gt Nodes :  0
Gt Edges :  0
vnodes = Gt.node[Nc]['cycles'].cycle
poly = Gt.node[Nc]['cycle'].polyg
Gr Nodes    : 0
Gr Edges    : 0
Nc  = Gr.node[nroom]['cycles']

The different graphs associated with the layout are then built

L.build()

The topological graph or graph of non overlapping cycles.

f,a=L.showG('t')
b=plt.axis('off')
./GIS-editor_files/GIS-editor_25_0.png

The graph of room . Two rooms which share at least a wall are connected. Two rooms which share only a corner (punctual connection) are not connected

f,a=L.showG('r')
b=plt.axis('off')
./GIS-editor_files/GIS-editor_27_0.png

The graph of waypath . This graph is used for agent mobility. This allows to determine the shortest path between 2 rooms. This information could be included in the osm file. This is not the case yet

f,a=L.showG('w')
b=plt.axis('off')
./GIS-editor_files/GIS-editor_29_0.png

The graph of visibility

f,a=L.showG('v')
b=plt.axis('off')
./GIS-editor_files/GIS-editor_31_0.png

The graph of interactions used to determine the ray signatures.

f=plt.figure(figsize=(15,15))
a = f.gca()
f,a=L.showG('i',fig=f,ax=a)
b= plt.axis('off')
./GIS-editor_files/GIS-editor_33_0.png

6.5. The display options dictionnary

L.info()
filestr :  DLR.osm
filematini :  matDB.ini
fileslabini :  slabDB.ini
filegeom :  DLR.off
boundaries  (758.35005883924691, 794.77088532257221, 1111.8980005947324, 1138.9865273726507)
number of Points : 105
number of Segments : 124
number of Sub-Segments : 30
Gs Nodes :  229
Gs Edges :  248
Gt Nodes :  20
Gt Edges :  48
vnodes = Gt.node[Nc]['cycles'].cycle
poly = Gt.node[Nc]['cycle'].polyg
Gr Nodes    : 16
Gr Edges    : 16
Nc  = Gr.node[nroom]['cycles']

The layout can be displayed using matplotlib ploting primitive. Several display options are specified in the display dictionnary. Those options are exploited in showGs() vizualisation method.

L.display
{'activelayer': 'WINDOW_GLASS',
 'alpha': 0.5,
 'box': (-20, 20, -10, 10),
 'clear': False,
 'edges': True,
 'edlabel': False,
 'edlblsize': 20,
 'ednodes': False,
 'fileoverlay': 'TA-Office.png',
 'fontsize': 20,
 'inverse': False,
 'layer': [],
 'layers': ['WALL', 'PARTITION', 'AIR', '3D_WINDOW_GLASS'],
 'layerset': ['WINDOW_GLASS',
  'PLASTERBOARD_7CM',
  'WALL',
  'AIR',
  'WINDOW',
  'METALIC',
  'PLASTERBOARD_14CM',
  'DOOR',
  'FLOOR',
  'METAL',
  'PARTITION',
  'CONCRETE_20CM3D',
  'PLASTERBOARD_10CM',
  'CEIL',
  'CONCRETE_6CM3D',
  'CONCRETE_15CM3D',
  '3D_WINDOW_GLASS',
  'WALLS',
  'WOOD',
  'CONCRETE_7CM3D',
  'PILLAR',
  'ABSORBENT'],
 'ndlabel': False,
 'ndlblsize': 20,
 'ndsize': 10,
 'nodes': False,
 'overlay': False,
 'scaled': True,
 'subseg': True,
 'thin': False,
 'ticksoff': True,
 'title': '',
 'visu': False}

6.6. Interactive editor

The command L.editor() launches a simple Layout interactive editor. The state machine is implemented in module pylayers.gis.selectl.py.

To have an idea of all available options, look in the `pylayers.gis.SelectL <http://pylayers.github.io/pylayers/_modules/pylayers/gis/selectl.html#SelectL.new_state>`__ module

All ergonomic improvement of this editor is welcome. Just pull request your modifications.

PyLayers comes along with a low level structure editor based on matplotlib which can be invoqued using the editor() method. This editor is more suited for modyfing constitutive properties of walls.

An alternative editing solution is to use the open street map editor JOSM

There are two different modes of edition

  • A create points mode CP
    • left clic : free point
    • right clic : same x point
    • center clic : same y point
  • A create segments mode
    • left clic : select point 1
    • left clic : select point 2
    • left clic : create a segment between point 1 and point 2

m : to switch from one mode to an other

i : to return to init state

6.6.1. Image overlay

It is useful while editing a layout to have an overlay of an image in order to help placing points. The image overlay can either be an url or a filename. In that case the file is stored in

L=Layout()
L.display['fileoverlay']='http://images.wikia.com/theoffice/images/9/9e/Layout.jpg'
L.display['overlay']=True
L.display['alpha']=1
L.display['scaled']=False
L.display['ticksoff']=False
L.display['inverse']=True
plt.figure(figsize=(10,10))
L.showGs()
./GIS-editor_files/GIS-editor_53_1.png

6.6.2. Scaling the figure overlay

Before going further it is necessary :

  • to place the global origin
  • to precise the vertical and horizontal scale of the image

This is done by the following commands :

  • ‘i’ : back to init state
  • ‘m’ : goes to CP state
  • ‘o’ : define the origin
  • ‘left click’ on the point of the figure chasen as the origin
  • ‘left click’ on a point at a known distance from the origin along x axis. Fill the dialog box with the actual distance (expressed in meters) between the two points.
  • ‘left click’ on a point at a known distance from the origin along y axis. Fill the dialog box with the actual distance (expressed in meters) between the two points.

In that sequence of operation it is useful to rescale the figure with ‘r’.

At that stage, it is possible to start creating points

'b'  : selct a segment
'l'  : select activelayer
'i'  : back to init state
'e'  : edit segment
't'  : translate  structure
'h'  : add subsegment
'd'  : delete subsegment
'r'  : refresh
'o'  : toggle overlay
'm'  : toggle mode (point or segment)
'z'  : change display parameters
'q'  : quit interactive mode
'x'  : save .str2 file
'w'  : display all layers

6.6.3. Visualisation of the layout

L = Layout('TA-Office.ini')
L.dumpr()
fig = plt.figure(figsize=(25,25))
ax = fig.gca()
fig,ax = L.showG(fig=fig,ax=ax,graph='s',labels=True,font_size=9,node_size=220,node_color='c')
a = plt.axis('off')
./GIS-editor_files/GIS-editor_59_0.png

Each node of with a negative index is a point.

Each node of with a positive index corresponds to a segment (wall,door,window,...).

The segment name is the key of the slab dictionnary.

Multi Subsegments

6.6.4. Segment and sub-segments

from pylayers.simul.simulem import *
from pylayers.antprop.rays import *
from pylayers.gis.layout import *
from pylayers.antprop.signature import *
import pylayers.signal.bsignal as bs
import pylayers.signal.waveform as wvf
from pylayers.simul.simulem import *
import matplotlib.pyplot as plt
S = Simul()
filestr = 'defstr3'
S.layout(filestr+'.ini','matDB.ini','slabDB.ini')

The studied configuration is composed of a simple 2 rooms building separated by a subsegment which has a multi subsegment attribute. The attribute of the subsegment can be changed with the method chgmss (change multisubsegment)

S.L.chgmss(1,ss_name=['WOOD','AIR','WOOD'],ss_z =[(0.0,2.7),(2.7,2.8),(2.8,3)])
S.L.build()
S.L.save()
structure saved in  defstr3.str2
structure saved in  defstr3.ini

The graph dictionnary has the following structure

S.L.Gs.node
{-8: {},
 -7: {},
 -6: {},
 -5: {},
 -4: {},
 -3: {},
 -2: {},
 -1: {},
 1: {'connect': [-8, -7],
  'name': 'PARTITION',
  'ncycles': [0, 1],
  'norm': array([-0.999982  , -0.00599989,  0.        ]),
  'ss_name': ['WOOD', 'AIR', 'WOOD'],
  'ss_z': [(0.0, 2.7), (2.7, 2.8), (2.8, 3)],
  'transition': True,
  'z': (0.0, 3.0)},
 2: {'connect': [-8, -2],
  'name': 'WALL',
  'ncycles': [0, 1],
  'norm': array([ 0.99997778,  0.00666652,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)},
 3: {'connect': [-7, -5],
  'name': 'WALL',
  'ncycles': [0, 1],
  'norm': array([-0.99997775, -0.00667097,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)},
 4: {'connect': [-6, -1],
  'name': 'WALL',
  'ncycles': [1],
  'norm': array([ 0.99997888,  0.00649986,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)},
 5: {'connect': [-6, -5],
  'name': 'WALL',
  'ncycles': [1],
  'norm': array([-0.00619988,  0.99998078,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)},
 6: {'connect': [-5, -4],
  'name': 'WALL',
  'ncycles': [0],
  'norm': array([-0.00639987,  0.99997952,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)},
 7: {'connect': [-4, -3],
  'name': 'WALL',
  'ncycles': [0],
  'norm': array([ 0.99997887,  0.00650149,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)},
 8: {'connect': [-3, -2],
  'name': 'WALL',
  'ncycles': [0],
  'norm': array([ 0.00639987, -0.99997952,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)},
 9: {'connect': [-2, -1],
  'name': 'WALL',
  'ncycles': [1],
  'norm': array([ 0.00639987, -0.99997952,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)}}
S.info()
default.ini
------------------------------------------
Layout Info :
filestr :  defstr3.ini
filematini :  matDB.ini
fileslabini :  slabDB.ini
filegeom :  defstr3.off
boundaries  (758.49, 768.516, 1111.9, 1115.963)
number of Points : 8
number of Segments : 9
number of Sub-Segments : 3
Gs Nodes :  17
Gs Edges :  18
Gt Nodes :  2
Gt Edges :  1
vnodes = Gt.node[Nc]['cycles'].cycle
poly = Gt.node[Nc]['cycle'].polyg
Gr Nodes    : 2
Gr Edges    : 1
Nc  = Gr.node[nroom]['cycles']
None
Tx Info :
npos       :  2
position   :  [[  0.     19.52 ]
 [  0.     -0.69 ]
 [  0.      1.446]]
name       :
type       :  tx
fileini    :  radiotx.ini
filespa    :  radiotx.spa
filegeom   :  radiotx.vect
fileant    :  defant.vsh3
filestr    :  defstr.str2
None
Rx Info :
npos       :  350
position   :  [[  0.      0.1     0.2   ...,  28.9    28.9    28.9  ]
 [  0.      0.      0.    ...,   5.7     5.8     5.9  ]
 [  1.275   1.275   1.275 ...,   1.275   1.275   1.275]]
name       :
type       :  rx
fileini    :  radiorx.ini
filespa    :  radiorx.spa
filegeom   :  radiorx.vect
fileant    :  defant.vsh3
filestr    :  defstr.str2
None
S.tx.clear()
S.rx.clear()

#
tx=np.array([759,1114,1.0])
rx=np.array([767,1114,1.5])
#
S.tx.point(tx)
S.rx.point(rx)
# getting cycles from tx
ctx = S.L.pt2cy(S.tx.position[:,0])
# getting cycles from rx
crx = S.L.pt2cy(S.rx.position[:,0])
f,a = S.show()
./GIS-multisubsegments_files/GIS-multisubsegments_9_1.png
fGHz=np.arange(2,6,0.5)
wav = wvf.Waveform(fcGHz=4,bandGHz=1.5)
wav.show()
./GIS-multisubsegments_files/GIS-multisubsegments_10_0.png

The different steps are :

  • determine the signatures
  • determine the 2d rays
  • determine the 3d rays
  • determine local basis on 3D rays
  • fill interactions
Si = Signatures(S.L,ctx,crx)
Si.run4(cutoff=5)
r2d = Si.rays(tx,rx)
r3d = r2d.to3D(S.L)
r3d.locbas(S.L)
r3d.fillinter(S.L)

6.7. Channel variability due to different Layout constitutive materials

r3d
Rays3D
----------
1 / 1 : [0]
2 / 6 : [1 2 3 4 5 6]
3 / 18 : [ 7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24]
4 / 38 : [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]
5 / 59 : [ 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]
6 / 80 : [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]
7 / 74 : [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 270 271 272 273
 274 275]
8 / 44 : [276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
 312 313 314 315 316 317 318 319]
-----
ni : 1864
nl : 4048
layer = ['AIR','AIR','AIR']
S.L.chgmss(1,ss_name=layer)
S.L.Gs.node[1]['ss_name']=layer
S.L.g2npy()
# graph to numpy
r3d.fillinter(S.L,append=True)
Cair = r3d.eval(fGHz)
scair = Cair.prop2tran(a='theta',b='theta')
cirair = scair.applywavB(wav.sfg)
#cirair = evcir(r3d,wav)
fig,ax = cirair.plot(typ=['v'],xmin=20,xmax=60)
title = plt.title(str(layer))
./GIS-multisubsegments_files/GIS-multisubsegments_16_1.png
type(cirair)
pylayers.signal.bsignal.Usignal
layer = ['PARTITION','PARTITION','PARTITION']
S.L.chgmss(1,ss_name=layer)
S.L.Gs.node[1]['ss_name']=layer
S.L.g2npy()
# graph to numpy
r3d.fillinter(S.L,append=True)
Cwood=r3d.eval(fGHz)
scwood=Cwood.prop2tran(a='theta',b='theta')
cirwood = scwood.applywavB(wav.sfg)
cirwood.plot(typ=['v'],xmin=20,xmax=60)
plt.title(str(layer))
<matplotlib.text.Text at 0x7fe8029fb310>
./GIS-multisubsegments_files/GIS-multisubsegments_18_1.png
layer = ['METAL','METAL','METAL']
S.L.chgmss(1,ss_name=layer)
S.L.Gs.node[1]['ss_name']=layer
# graph to numpy
S.L.g2npy()
r3d.fillinter(S.L,append=True)
Cmetal=r3d.eval(fGHz)
scmetal=Cmetal.prop2tran(a='theta',b='theta')
cirmetal = scmetal.applywavB(wav.sfg)
cirmetal.plot(typ=['v'],xmin=20,xmax=60)
plt.title(str(layer))
plt.show()
./GIS-multisubsegments_files/GIS-multisubsegments_19_0.png
#fig2=plt.figure()
f,a=cirair.plot(typ=['l20'],color='b')
plt.axis([0,120,-120,-40])
plt.title('A simple illustration of shadowing effect')
plt.legend(['air'])
f,a=cirwood.plot(typ=['l20'],color='k')
plt.axis([0,120,-120,-40])
plt.legend(['wood'])
f,a=cirmetal.plot(typ=['l20'],color='r')
plt.axis([0,120,-120,-40])
plt.legend(['metal'])
./GIS-multisubsegments_files/GIS-multisubsegments_20_1.png ./GIS-multisubsegments_files/GIS-multisubsegments_20_2.png ./GIS-multisubsegments_files/GIS-multisubsegments_20_3.png

7. Electromagnetic Simulation

7.1. Mutliwall Model

The multiwall model is an adaptation of the Motley Keenan model. It determines the LOS attenuation between Tx and Rx in adding to the free space attenuation, the aggregated losses in crossed walls takinig into account :

  • incidence angle
  • frequency,
  • polarisation
  • constitutive properties of slabs.
import time
from pylayers.util.project import *
import pylayers.util.pyutil as pyu
from pylayers.util.utilnet import str2bool
from pylayers.gis.layout import Layout
from pylayers.antprop.multiwall import *
from pylayers.antprop.coverage import *
from pylayers.network.model import *

The layout is loaded from an ini file.

L=Layout('TA-Office.ini')

The 2 extremities of the radio link are coordinates in numpy.array of transmitter and receiver. A and B are radionodes at the two termination of the radio link.

A=np.array((4,1)) # defining transmitter position
B=np.array((30,12)) # defining receiver position

The scene is plotted with the showG method of the Layout.

# figure instanciation
f = plt.figure(figsize=(25,25))
ax = f.add_subplot(111)
r = np.array((A,B))
# plotting the Layout
f,ax = L.showG(fig=f,ax=ax,graph='s',nodes=False)
# plotting the Tx and Rx
ax.plot(A[0],A[1],'ob')
ax.plot(B[0],B[1],'or')
# plotting the LOS
ax.plot(r[:,0],r[:,1])
a = plt.axis('off')
./AP-multiwallmodel_files/AP-multiwallmodel_9_0.png

The function angleonlink returns the list of intersected segments and the corresponding incidence angles (in radians) with respect to the segment normal.

data=L.angleonlink(A,B)

7.1.1. Computing the Multi-wall model

The multi-wall model computation returns losses and LOS excess delay for orthogonal and parallel polarization

fGHz = 2.4
# observation grid

r = np.array((B,B))

Lwo,Lwp,Edo,Edp = Losst(L,fGHz,r.T,A)

print 'Losses orthogonal polarization \t %g dB' %(Lwo[0][0])
print 'Losses parallel polarization \t %g  dB' % (Lwp[0][0])
print 'Excess delay orthogonal polarization  \t %g ns' %(Edo[0][0])
print 'Excess delay parallel polarization   \t %g ns' %(Edp[0][0])
Losses orthogonal polarization       27.7333 dB
Losses parallel polarization         16.0573  dB
Excess delay orthogonal polarization         2.23113 ns
Excess delay parallel polarization           2.12364 ns

7.2. The Coverage class

Sometimes it might be useful to get a fast estimate about losses in a given indoor environment. Ray tracing is generally time consuming and depending on the purpose of the study it could be relevant to proceed with a simpler approach while staying site-specific. PyLayers provides such a tool which heavily relies on the core module slab.py

The multi-wall model can also be used to perform a full coverage of a Layout given a transmitter position.

C = Coverage()
C.L  = L # set layout
C.tx = A # set the transmitter
C.creategrid()

The coverage is performed on grid. The boundaries can be specified in the coverage.ini file

C.grid
array([[  1.00000000e-02,   1.00000000e-02],
       [  1.00000000e-02,   3.94102564e-01],
       [  1.00000000e-02,   7.78205128e-01],
       ...,
       [  3.99900000e+01,   1.42217949e+01],
       [  3.99900000e+01,   1.46058974e+01],
       [  3.99900000e+01,   1.49900000e+01]])
t1=time.time()
C.cover()
t2=time.time()
print 'Coverage performed in ', t2-t1, 's'
Coverage performed in  1.34146499634 s

For Orthogonal polarization
fig1=plt.figure(figsize=(10,10))
C.showPower(polar='o',fig=fig1)

fig2=plt.figure(figsize=(10,10))
C.showEd(polar='o',fig=fig2)
./AP-multiwallmodel_files/AP-multiwallmodel_26_0.png ./AP-multiwallmodel_files/AP-multiwallmodel_26_1.png

For parallel polarization

fig1=plt.figure(figsize=(10,10))
C.showPower(polar='p',fig=fig1)

fig2=plt.figure(figsize=(10,10))
C.showEd(polar='p',fig=fig2)
./AP-multiwallmodel_files/AP-multiwallmodel_28_0.png ./AP-multiwallmodel_files/AP-multiwallmodel_28_1.png

First let’s import the coverage module

from pylayers.antprop.coverage import *
import time

Instantiate a coverage object. By defaut, TA-Office.str layout strucure is loaded.

This information can be modified in the coverage.ini file in the project directory.

Below is an example of how the content of the file looks like

!cat $BASENAME/ini/coverage.ini
[pl_model]
sigrss = 3.0
fGHz = 3.0
rssnp = 2.64
d0 = 1.0


[grid]
nx = 80
ny = 40
boundary = [20,0,30,20]
full = True

[layout]
filename = TA-Office.ini
;filename = W2PTIN.ini
;filename = Lstruc.str

[tx]
fGHz = 3.0
x = 25
y = 5
;transmitted power (dBm)
ptdbm = 0
; frame length in bytes
framelengthbytes = 50000

[rx]
sensitivity  = -80
bandwidthmhz = 3
temperaturek = 300
noisefactordb = 13

[show]
show = True
# Create a Coverage object from coverag.ini file
C = Coverage()

The coverage object has a __repr__ which summarizes the different parameters of the current coverage object

C
Layout file : TA-Office.ini

-----Tx------
tx (coord) : [25  5]
fghz : 3.0
Pt (dBm) : 0

-----Rx------
rxsens (dBm) : -80
bandwith (Mhz) : 3
temperature (K) : 300
noisefactor (dB) : 13

--- Grid ----
nx : 80
ny : 40
full grid : True
boundary (xmin,ymin,xmax,ymax) : [20, 0, 30, 20]

---- PL Model------
plm : {'fghz': '3.0', 'rssnp': '2.64', 'd0': '1.0', 'sigrss': '3.0'}
# evaluate coverage
C.cover()

Calculating Received Power Coverage

C.L.display['nodes']=False
C.L.display['ednodes']=False
C.L.showGs()
./AP-coverage_files/AP-coverage_11_1.png

The coverage calculation is launched by calling the cover() method

The shadowing map coverage results can be displayed by invoquing various functions.

  • showLoss : display the path loss
  • showPower : display the received powercover

The following examples represent the estimated received power for 3 different frequencies

C.fGHz=0.400
tic = time.time()
C.cover()
C.showPower(figsize=(10,10))
toc =time.time()
print 'Elapsed time : {0:.2f} seconds'.format(toc-tic)
./AP-coverage_files/AP-coverage_15_1.png
Elapsed time : 1.07 seconds
C.fGHz=2.4
tic = time.time()
C.cover()
C.showPower(figsize=(10,10))
toc =time.time()
print 'Elapsed time : {0:.2f} seconds'.format(toc-tic)
./AP-coverage_files/AP-coverage_16_0.png
Elapsed time : 1.07 seconds
C.fGHz=2.4
tic = time.time()
C.cover()
C.showPower(figsize=(10,10))
toc =time.time()
print 'Elapsed time : {0:.2f} seconds'.format(toc-tic)
./AP-coverage_files/AP-coverage_17_0.png
Elapsed time : 1.07 seconds

The transmitter coordinates are :

C.tx
array([25,  5])

This can be modified on the flight, and the coverage is updated accordingly

C.fGHz=100
C.tx = np.array((21,2))

%timeit
C.cover()
C.showPower(polar='o',figsize=(10,10))
C.showPower(polar='p',figsize=(10,10))
./AP-coverage_files/AP-coverage_21_0.png ./AP-coverage_files/AP-coverage_21_1.png

The excess delay due to crossing the wall can also be evaluted.

7.3. Ray Signatures

import time
from pylayers.gis.layout import *
from pylayers.antprop.signature import *
from pylayers.antprop.rays import *
L = Layout('defstr.ini')
try:
    L.dumpr()
except:
    L.build()
    L.dumpw()

Showing the graph of rooms with 2 rooms separated by a DOOR segment

L.showG('v')
a=plt.axis('off')
./AP-signatures_files/AP-signatures_4_0.png

The graph of interactions is shown below.

L.showG('i',figsize=(20,20))
a=plt.axis('off')
./AP-signatures_files/AP-signatures_6_0.png

All the interactions of a given cycle are stored as meta information in nodes of Gt

L.Gt.node[0]['inter']
['(3, 0)',
 '(4, 0)',
 '(7, 0)',
 '(7, 0, 1)',
 '(7, 1, 0)',
 '(9, 0)',
 '(9, 0, 1)',
 '(9, 1, 0)',
 '(8, 0)',
 '(8, 0, 1)',
 '(8, 1, 0)',
 '(2, 0)']

The signature is calculated with as parameters the Layout object and two cycle numbers. In example below it is 0 and 1.

Si = Signatures(L,0,1)

The cold start determination of the signature is done with a run function. The code is not in its final shape here and there is room for significant acceleration in incorporating propagation based heuristics. The mitigation of graph exploration depth is done in setting a cutoff value which limits the exploration in the interaction graph.

Si.run4(cutoff=5)

The representation method of a signature gives varous informations about the different signatures. Signatures are grouped by number of interactions.

L.Gt.pos
{0: (7.5, 0.0), 1: (2.5, -0.0)}
ptx = np.array(L.Gt.pos[0])+np.random.rand(2)
prx = np.array(L.Gt.pos[1])+np.random.rand(2)
print ptx
print prx
[ 8.35174751  0.30020702]
[ 3.43281369  0.44106546]

7.4. Synthesis of Ultra Wide Band Waveforms

Once the propagation channel has been evaluated. This is done in the pylayers.antprop.channel module. The received signal is evaluated in applying a convolution product of each ray tranfer function with a specific IR-UWB waveform. The necessary modules are

  • pylayers.signal.bsignal.
  • pylayers.signal.waveform
  • pylayers.signal.channel

The module pylayers.simul.simulem is for definition of electromagnetic simulation.

from pylayers.signal.bsignal import *
from pylayers.signal.waveform import *
from pylayers.antprop.channel import *
from pylayers.simul.simulem import *

Generation of a pulse of normalized energy

One possible manner to define an energy normalized short UWB pulse is as follows using bsignal.EnImpulse function.

The default waveform is a gaussian windowing of a sine wave of frequency . The normalization term depends on the exponential scaling factor .

where is the desired bandwidth defined at below the spectrum maximum and is the central frequency of the pulse.

fc     = 4
band   = 2
thresh = 10
fe     = 100
ip     = EnImpulse([],fc,band,thresh,fe)
ip.info()
TUsignal
--------
shx :  (343,)
shy :  (343,)
dx :   0.01
xmin : -1.71
xmax : 1.71
ymin : -1.89545539648
ymax : 2.16154131873

Verification of energy normalization in both domains

E1= sum(ip.y*ip.y)*ip.dx()
print "Integration in time",E1
Integration in time 1.0
P = ip.esd()
E2 = sum(P.y)*P.dx()
print "Integration in frequency domain ",E2
Integration in frequency domain  1.0

7.4.1. Calculation of UWB channel impulse response

We choose to load a simple floor plan.

S = Simul()
S.L = Layout('defstr3.ini')

A simulation object has an info method providing a summary of simulation informations.

S.info()
default.ini
------------------------------------------
Layout Info :
filestr :  defstr3.ini
filematini :  matDB.ini
fileslabini :  slabDB.ini
filegeom :  defstr3.off
boundaries  (758.49, 768.516, 1111.9, 1115.963)
number of Points : 8
number of Segments : 9
number of Sub-Segments : 3
Gs Nodes :  17
Gs Edges :  18
Gt Nodes :  0
Gt Edges :  0
vnodes = Gt.node[Nc]['cycles'].cycle
poly = Gt.node[Nc]['cycle'].polyg
Gr Nodes    : 0
Gr Edges    : 0
Nc  = Gr.node[nroom]['cycles']
None
Tx Info :
npos       :  1
position   :  [[  7.59000000e+02]
 [  1.11400000e+03]
 [  1.00000000e+00]]
name       :
type       :  tx
fileini    :  radiotx.ini
filespa    :  radiotx.spa
filegeom   :  radiotx.off
fileant    :  defant.vsh3
filestr    :  defstr.str2
None
Rx Info :
npos       :  1
position   :  [[  767. ]
 [ 1114. ]
 [    1.5]]
name       :
type       :  rx
fileini    :  radiorx.ini
filespa    :  radiorx.spa
filegeom   :  radiorx.off
fileant    :  defant.vsh3
filestr    :  defstr.str2
None
S.L.Gs.node
{-8: {},
 -7: {},
 -6: {},
 -5: {},
 -4: {},
 -3: {},
 -2: {},
 -1: {},
 1: {'connect': [-8, -7],
  'name': 'PARTITION',
  'ncycles': [0, 1],
  'norm': array([-0.999982  , -0.00599989,  0.        ]),
  'ss_name': ['WOOD', 'AIR', 'WOOD'],
  'ss_z': [(0.0, 2.7), (2.7, 2.8), (2.8, 3)],
  'transition': True,
  'z': (0.0, 3.0)},
 2: {'connect': [-8, -2],
  'name': 'WALL',
  'ncycles': [0, 1],
  'norm': array([ 0.99997778,  0.00666652,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)},
 3: {'connect': [-7, -5],
  'name': 'WALL',
  'ncycles': [0, 1],
  'norm': array([-0.99997775, -0.00667097,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)},
 4: {'connect': [-6, -1],
  'name': 'WALL',
  'ncycles': [1],
  'norm': array([ 0.99997888,  0.00649986,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)},
 5: {'connect': [-6, -5],
  'name': 'WALL',
  'ncycles': [1],
  'norm': array([-0.00619988,  0.99998078,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)},
 6: {'connect': [-5, -4],
  'name': 'WALL',
  'ncycles': [0],
  'norm': array([-0.00639987,  0.99997952,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)},
 7: {'connect': [-4, -3],
  'name': 'WALL',
  'ncycles': [0],
  'norm': array([ 0.99997887,  0.00650149,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)},
 8: {'connect': [-3, -2],
  'name': 'WALL',
  'ncycles': [0],
  'norm': array([ 0.00639987, -0.99997952,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)},
 9: {'connect': [-2, -1],
  'name': 'WALL',
  'ncycles': [1],
  'norm': array([-0.00639987,  0.99997952,  0.        ]),
  'transition': False,
  'z': (0.0, 3.0)}}
st = S.wav.st
sf = S.wav.sf
S.wav.info()
fcGHz  :  4.493
typ  :  generic
feGHz  :  100
Np  :  3000
twns  :  30
te  :  0.01
threshdB  :  3
bandGHz  :  0.499

The waveform associated with the simulation object is

S.wav
{'Np': 3000,
 'bandGHz': 0.499,
 'fcGHz': 4.493,
 'feGHz': 100,
 'te': 0.01,
 'threshdB': 3,
 'twns': 30,
 'typ': 'generic'}
S.wav.show()
./PHY-signal_files/PHY-signal_18_0.png

Above the waveform is a generic UWB waveform. The interested user can add easily any other mathematical expression of UWB waveform for investigation on pulse waveform modulation for example. The waveform can also comes from measurement. For now there are two versions of this waveform which has been used during the M1 measurement campaign. One is not compensated W1compensate for an extra short delay which can introduce a bias when interpreting the observed delay in terms of distance. The non compensated version is W1offset from the time origin about 0.7 ns.

The waveform class should grow for incorporating more waveforms, especially waveforms compliant with the current IEEE 802.15.4a and IEEE 802.15.6 standards.

wavmeasured = Waveform(typ='W1compensate')
wavmeasured.show()
./PHY-signal_files/PHY-signal_20_0.png
wavmeasured = Waveform(typ='W1offset')
wavmeasured.show()
./PHY-signal_files/PHY-signal_21_0.png

Here the time domain waveform is measured and the anticausal part of the signal is artificially set to 0.

To handle properly the time domain wavefom in PyLayers, it is required to center the signal in the middle of the array. The waveform has embedded in the object its frequency domain and time domain representation.

  • st member stands for signal in time domain
  • sf member stands for signal in frequency domain
print type(S.wav.sf)
print type(S.wav.st)
<class 'pylayers.signal.bsignal.FUsignal'>
<class 'pylayers.signal.bsignal.EnImpulse'>
  • FUsignal Frequency domain uniformly sampled base signal
  • TUsignal Time domain uniformly sampled base signal

7.5. Construction of the propagation channel

The following representation shows the spatial spreading of the propagation channel. On the left are scattered the intensity of rays wrt to angles of departure (in azimut and elevation). On the right is the intensity of rays wrt to angles of arrival. It misses the application between the 2 planes as well as the delay dimension of the propagation channel.

from pylayers.antprop.signature import *
from pylayers.antprop.channel import *
S.L.build()
S.L
----------------
defstr3.ini
Image('/home/uguen/Bureau/P1/struc/images/TA-Office.png')
----------------

Number of points  : 8
Number of segments  : 9
Number of sub segments  : 3
Number of cycles  : 2
Number of rooms  : 2
degree 0 : []
degree 1 : [-8 -7]
degree 2 : 4
degree 3 : 2

xrange :(758.49, 768.516)
yrange :(1111.9, 1115.963)

Useful dictionnaries
----------------
di {interaction : [nstr,typi]}
sl {slab name : slab dictionary}
name :  {slab :seglist}

Useful arrays
----------------
tsg : get segment index in Gs from tahe
isss :  sub-segment index above Nsmax
tgs : get segment index in tahe from Gs
lsss : list of segments with sub-segment
sla : list of all slab names (Nsmax+Nss+1)
degree : degree of nodes
S.L.Gt.pos
{0: (766.00300113353387, 1113.947479109665),
 1: (761.00289669547806, 1113.915769812613)}
tx=np.array([759,1114,1.0])
rx=np.array([767,1114,1.5])
ctx = S.L.pt2cy(tx)
crx = S.L.pt2cy(rx)

The sequence of command below :

  • initialize a signature between cycle ctx and cycle crx
  • evaluates the signature with a given cutoff value
  • calculates a set of 2D rays from signature and tx/rx coordinates
  • calculates a set of 3D ray from 2D rays and layout and ceil height (default H=3m)
  • calculates local basis and various geometric information out of the 3D ray and Layout
  • fill and reorganize the interactions object with proper material chararcteristics
Si = Signatures(S.L,ctx,crx)
Si.run4(cutoff=5)
r2d = Si.rays(tx,rx)
r3d = r2d.to3D(S.L)
r3d.locbas(S.L)
r3d.fillinter(S.L)

Define a frequency base in GHz.

fGHz = np.arange(2,10,0.01)

Evaluate the propagation channel . Here the meaning of tilde is that the complex value of the channel do not include the phase term due to delay along the ray.

C = r3d.eval(fGHz)
print type(C)
<class 'pylayers.antprop.channel.Ctilde'>

7.6. Construction of the transmission channel

The transmission channel is obtained from the combination of the propagation channel and the vector antenna pattern at both side of the radio link. This operation is implemented in the prop2tran method of the Ctilde class.

sc = C.prop2tran()

The ScalChannel object contains all the information about the ray transfer functions. The transmission channel is obtained by applying a vector radiation pattern using an antenna file.

In the presented case, it comes from a real antenna which has been used during the FP7 project WHERE1 measurement campaign M1.

sc
freq :2.0 9.99 800
shape  :(315, 800)
tau :26.7186992365 105.599656025
dist :8.01560977094 31.6798968075

The antenna radiation pattern is stored in a very compact way thanks to Vector Spherical Harmonics decomposition. The following gives information about the content of the antenna object.

S.tx.A.info()
defant.vsh3
type :  vsh3
No vsh coefficient calculated yet

The figure below plot on a same graph all the tansfer function in modulus and phase of the ray transfer function.

If a realistic antenna is applied it gives

sca = C.prop2tran(S.tx.A,S.rx.A)

7.7. Calculate UWB Channel Impulse Response

Once the transmission channel has been evaluated on can convolved the waveform with the channel impulse response to get the received waveform.

r = sca.applywavB(S.wav.sfg)
r.y
array([  0.00000000e+00,   0.00000000e+00,   0.00000000e+00, ...,
        -2.06305673e-12,  -2.85965052e-13,   6.80134567e-13])
fig,ax = r.plot(typ=['l20'])
plt.axis([15,90,-90,-20])
plt.title(u'Received Waveform $r(t)$')
<matplotlib.text.Text at 0x4cc2750>
./PHY-signal_files/PHY-signal_53_2.png
r.plot(typ=['v'])
plt.axis([15,60,-0.3
          ,0.3])
plt.title(u'Received Waveform $r(t)$')
<matplotlib.text.Text at 0x7f6c579f1e90>
./PHY-signal_files/PHY-signal_54_1.png

7.8. Hermitian symetry enforcment

If the number of point for the transmission channel and the waveform were the same the mathematical operation is an Hadamard-Shur product between and .

In practice this is what is done after a resampling of the time base with a reinterpolated time step.

The process which consists in going from time domain to frequency domain is delegated to a specialized class pylayers.signal.bsignal.Bsignal which maintains the proper binding between signal samples and their indexation either in time or in frequency domain.

wgam = S.wav.sfg
Y    = sc.apply(wgam)
tau  = Y.tau0
dod = Y.dod
doa = Y.doa

The transmission channel has a member data which is the time delay of each path in nano seconds. Notice that by default those delay are not sorted.

print 'tau =', tau[0:20]
tau = [ 26.71869924  27.93842436  29.10708199  29.64889324  30.03048589
  30.075433    36.72255959  30.75261837  31.12068041  31.1640552
  31.81807982  32.17395555  32.21591227  32.36081306  32.66533294
  33.05244038  34.35921355  37.6193175   37.86521462  38.49519081]
h = hist(tau,20)
./PHY-signal_files/PHY-signal_61_0.png

Direction of arrival in radians

print "doa = ", doa[1:10,:]
doa =  [[ 1.8736812  -3.14159265]
 [ 1.15838589 -3.14159265]
 [ 1.62703943  2.69865609]
 [ 1.62632401 -3.14087348]
 [ 1.62624094 -2.65738656]
 [ 1.61619728  0.01122758]
 [ 1.84520693  2.69865609]
 [ 1.84187905 -3.14087348]
 [ 1.84149225 -2.65738656]]
subplot(221)
ht = hist(doa[:,0],20)
xlabel(u'$\\theta_r$')
ylabel('#')
subplot(222)
hp = hist(doa[:,1],20)
xlabel(u'$\phi_r$')
ylabel('#')
subplot(223)
ht = hist(dod[:,0],20)
xlabel(u'$\\theta_t$')
ylabel('#')
subplot(224)
hp = hist(dod[:,1],20)
xlabel(u'$\phi_t$')
ylabel('#')
tight_layout()
./PHY-signal_files/PHY-signal_64_0.png

symHz force the Hermitian symetry of Y with as an argument here a zero padding of 500 points

UH   = Y.symHz(500)
#%psource Y.symHz
uh   = UH.ifft(1)
ips  = Y.ift(500,1)
t    = ips.x
ip0  = TUsignal(t,ips.y[0,:])
plot(UH.x,real(UH.y[0,:]),UH.x,imag(UH.y[0,:]))
legend(['Re','Im'])
U0 = FHsignal(UH.x,UH.y[0,:])
u0 = U0.ifft(1)
u1 = ifft(U0.y)
title('checking Hermitian Symetry in frequency domain fc=4.49GHz')
xlabel('f (GHz)')
<matplotlib.text.Text at 0x51fb750>
./PHY-signal_files/PHY-signal_70_1.png

The ray channel has been applied to the UWB waveform. Notice the small delay introduced by dielectrics material crossing.

plot(uh.x,uh.y[0,:],S.wav.st.x,S.wav.st.y*0.0012+0.0034)
xlim(-2,2)
ylim(-0.002,0.008)
legend(['Ray channel filtered waveform','Original Waveform *1.2e-3 +0.0034'])
xlabel('Time (ns)')
title('Inverse Fourier Transform of an FH signal')
tight_layout()
./PHY-signal_files/PHY-signal_72_0.png

8. Example of an UWB channel Ray Tracing simulation

In the following, all steps required for going from the descrpition of the radio scene until the calculation of the UWB channel impulse response is described on a simple example.

A ray-tracing simulation is controlled via a configuration file which is stored in the ini directory of the project directory. By default, a default configuration file named default.ini is loaded.

from pylayers.simul.simulem import *
from pylayers.antprop.rays import *
from pylayers.antprop.channel import *
from pylayers.antprop.signature import *
from pylayers.measures.mesuwb import *
import pylayers.util.pyutil as pyu
import pylayers.signal.bsignal as bs

A first step consists in loading a layout associated with the simulation. Here the WHERE1.ini layout is chosen along with the corresponding slabs and materials files matDB.ini and slabDB.ini.

This layout corresponds to the office building where the first WHERE1 UWB measurement campaign has been conducted.

The layout method loads those in a member layout object L of the simulation object S.

If not already available, the layout associated graphs are built.

S = Simul()
# loading a layout
filestr = 'WHERE1'
S.layout(filestr+'.ini','matDB.ini','slabDB.ini')
try:
    S.L.dumpr()
except:
    S.L.build()
    S.L.dumpw()

The layout display is fully parameterized via the embedded display dictionnary member of the Layout object.which allows to configure the showGs() method behavior.

S.L.display['ednodes']=False
S.L.display['nodes']=False
S.L.display['title']='WHERE1 Project Office Measurement Site'
S.L.display['overlay']=False
fig,ax=S.L.showGs()
./SIM-raytracing_files/SIM-raytracing_7_0.png

8.1. Adding coordinates of transmiting and receiving points

Coordinates of transmitters and receivers for the simulation are stored in .ini files. Transmitter and Receiver are instances of the class RadioNode which offers different methods for specifying nodes positions. The stucture of this .ini file presented below. The node Id is associated with the 3 coordinates separated by white spaces.

[coordinates]
1 = -12.2724 7.76319999993 1.2
2 = -18.7747 15.1779999998 1.2
3 = -4.14179999998 8.86029999983 1.2
4 = -9.09139999998 15.1899000001 1.2
S.tx = RadioNode(_fileini='w2m1rx.ini',_fileant='defant.vsh3')
S.rx = RadioNode(_fileini='w2m1tx.ini',_fileant='defant.vsh3')

The whole simulation setup can then be displayed using the show method of the Simulation object

fig,ax = S.show()
./SIM-raytracing_files/SIM-raytracing_13_1.png

The different object of the simulation cans be accessed to obtain different information. Below the number of transmitter and receiver.

print 'number of Tx :',len(S.tx.points.keys())
print 'number of rx :',len(S.rx.points.keys())
number of Tx : 302
number of rx : 4

The decomposition of the layout in a set of disjoint cycles is represented below. Not all cycles are rooms.

fig =plt.figure(figsize=(15,15))
fig,ax=S.L.showG('t',fig=fig)
plt.axis('off')
(-40.0, 40.0, 2.0, 18.0)
./SIM-raytracing_files/SIM-raytracing_17_1.png
fig,ax=S.L.showG('t',labels=True,figsize=(15,15))
plt.axis('off')
(-40.0, 40.0, 2.0, 18.0)
./SIM-raytracing_files/SIM-raytracing_18_1.png
istup = filter(lambda x: type(eval(x))==tuple,S.L.Gi.node.keys())
cy5 = filter(lambda x: eval(x)[1]==5,istup)
print cy5
['(15, 5, 76)', '(12, 5, 4)', '(13, 5, 6)', '(13, 5)', '(12, 5)', '(2, 5, 0)', '(331, 5, 6)', '(4, 5)', '(4, 5, 1)', '(156, 5)', '(15, 5)', '(2, 5)', '(5, 5)', '(156, 5, 75)', '(331, 5)', '(330, 5)', '(329, 5, 0)', '(329, 5)', '(140, 5, 78)']
nx.neighbors(S.L.Gi,'(140, 5, 78)')
['(38, 78)',
 '(14, 78)',
 '(135, 78, 22)',
 '-253',
 '-250',
 '(32, 78, 76)',
 '(42, 78, 21)',
 '(36, 78)',
 '(37, 78)',
 '(36, 78, 2)',
 '(32, 78)',
 '(14, 78, 76)',
 '(38, 78, 2)',
 '(43, 78)',
 '(42, 78)']
S.L.Gi.edge['(140, 5, 78)']['(38, 78)']
{'output': {'(135, 78, 22)': 0.35579843644453529,
  '(140, 78, 5)': 0.20141398602845637,
  '(42, 78)': 0.4427875775270072,
  '(42, 78, 21)': 0.4427875775270072}}

8.2. Signatures, rays and propagation and transmission channel

# Choose Tx and Rx coordinates
itx=10
irx=2
tx= S.tx.points[itx]
rx= S.rx.points[irx]
tx
array([-24.867 ,  12.3097,   1.2   ])

A signature is a sequence of layout objects (points and segments) which are involved in a given optical ray, relating the transmiter and the receiver. The signatutre is calculated from a layout cycle to an other layout cycle. This means that is is required first to retrieve the cycle number from point coordinates. This is done thanks to the pt2cy, point to cycle function.

ctx=S.L.pt2cy(tx)
crx=S.L.pt2cy(rx)
print 'tx point belongs to cycle ',ctx
print 'rx point belongs to cycle ',crx
tx point belongs to cycle  5
rx point belongs to cycle  4

Then the signature between cycle 5 and cycle 4 can be calculated. This is done by instantiating a Signature object with a given layout and the 2 cycle number.

The representaion of a signature object provides information about the number of signatures for each number of interactions.

Si = Signatures(S.L,ctx,crx)
Si.run4(cutoff=4,algo='old')
Si
Signatures
----------
from cycle : 5 to cycle 4
1 : 1
   [12]
   [2]
2 : 21
   [ 12  12  12  12  12  12  12  12  12  13  13 331 331 329   5   4 156  15
  13 330   2]
   [2 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1]
   [ 29  30  16 333  22 328  23 335 334  11 342  11 342  12  12  12  12  12
  12  12  12]
   [1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2]

...
#Si.run4(cutoff=5)

Once the signature has been obtained, 2D rays are calculated with the rays() method of the signature Si. The coordinates of a transmitter and a receiver should be parameters of the function. r2d object has a show and show3 method

tx
array([-24.867 ,  12.3097,   1.2   ])
figsize(20,20)
r2d = Si.rays(tx,rx)
S.L.display['ednodes']=False
r2d.show(S.L,figsize=(20,20),nodes=False)
./SIM-raytracing_files/SIM-raytracing_34_1.png
r2d
N2Drays : 42
from 24395 signatures
#Rays/#Sig: 0.00172166427547
pTx : [-24.867   12.3097   1.2   ]
pRx : [-18.7747  15.178    1.2   ]
1: [[12]]
2: [[ 12  12   5]
 [333 335  12]]
3: [[ 12  12  12   5   5   4  12  13]
 [335 335  30  12  12   5   5  12]
 [ 12  23 335 333 335  12  12  30]]
4: [[ 12  12  12   5   5 156  12   2]
 [335  30 335  12  15   5   5 330]
 [283 335 333 335  12  12  12  12]
 [335  12 342  12 335 333 335  30]]
5: [[ 12  12  12  12  12  12  12  13   5   5   5   5  15  12  12  12  12 330]
 [328  22  30 328 335  30 333   3  12  12 156 330   5   5   5  15   5  12]
 [339 339 335 335 283 335 334  13 335 335  12  12 330 331  12   5  12  30]
 [ 24 328 283  30 335 333 335  12 283 333 335 334  12   8 335  12  23  21]
 [328  24 335  12  12 342 328  30 335  11  23 335  30 342  12 335 335  30]]
...
# r2d.show3(strucname='WHERE1')

Then, the r2d object is transformed in 3D ray, taking into account the reflection on ceil and floor.

r3d=r2d.to3D(S.L)
# r3d.show3(strucname='WHERE1')

Once the 3D rays are obtained the local basis are determined

r3d.locbas(S.L)

and the the interaction matrices are filled.

r3d.fillinter(S.L)

Below ni is the number of interactions

r3d
Rays3D
----------
1 / 1 : [0]
2 / 5 : [1 2 3 4 5]
3 / 16 : [ 6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21]
4 / 30 : [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]
5 / 50 : [ 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]
6 / 54 : [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]
7 / 42 : [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]
8 / 8 : [198 199 200 201 202 203 204 205]
9 / 4 : [206 207 208 209]
-----
ni : 1147
nl : 2504
6*8+5*18+20*4+13*3+5*2+1
268

8.3. Calculating the Propagation Channel

The propagation channel is a Ctilde object. This object can be evaluated for different frequency point thanks to the eval() method with a frequency array as argument.

S.fGHz
array([  2.  ,   2.05,   2.1 ,   2.15,   2.2 ,   2.25,   2.3 ,   2.35,
         2.4 ,   2.45,   2.5 ,   2.55,   2.6 ,   2.65,   2.7 ,   2.75,
         2.8 ,   2.85,   2.9 ,   2.95,   3.  ,   3.05,   3.1 ,   3.15,
         3.2 ,   3.25,   3.3 ,   3.35,   3.4 ,   3.45,   3.5 ,   3.55,
         3.6 ,   3.65,   3.7 ,   3.75,   3.8 ,   3.85,   3.9 ,   3.95,
         4.  ,   4.05,   4.1 ,   4.15,   4.2 ,   4.25,   4.3 ,   4.35,
         4.4 ,   4.45,   4.5 ,   4.55,   4.6 ,   4.65,   4.7 ,   4.75,
         4.8 ,   4.85,   4.9 ,   4.95,   5.  ,   5.05,   5.1 ,   5.15,
         5.2 ,   5.25,   5.3 ,   5.35,   5.4 ,   5.45,   5.5 ,   5.55,
         5.6 ,   5.65,   5.7 ,   5.75,   5.8 ,   5.85,   5.9 ,   5.95,
         6.  ,   6.05,   6.1 ,   6.15,   6.2 ,   6.25,   6.3 ,   6.35,
         6.4 ,   6.45,   6.5 ,   6.55,   6.6 ,   6.65,   6.7 ,   6.75,
         6.8 ,   6.85,   6.9 ,   6.95,   7.  ,   7.05,   7.1 ,   7.15,
         7.2 ,   7.25,   7.3 ,   7.35,   7.4 ,   7.45,   7.5 ,   7.55,
         7.6 ,   7.65,   7.7 ,   7.75,   7.8 ,   7.85,   7.9 ,   7.95,
         8.  ,   8.05,   8.1 ,   8.15,   8.2 ,   8.25,   8.3 ,   8.35,
         8.4 ,   8.45,   8.5 ,   8.55,   8.6 ,   8.65,   8.7 ,   8.75,
         8.8 ,   8.85,   8.9 ,   8.95,   9.  ,   9.05,   9.1 ,   9.15,
         9.2 ,   9.25,   9.3 ,   9.35,   9.4 ,   9.45,   9.5 ,   9.55,
         9.6 ,   9.65,   9.7 ,   9.75,   9.8 ,   9.85,   9.9 ,   9.95,
        10.  ,  10.05,  10.1 ,  10.15,  10.2 ,  10.25,  10.3 ,  10.35,
        10.4 ,  10.45,  10.5 ,  10.55,  10.6 ,  10.65,  10.7 ,  10.75,
        10.8 ,  10.85,  10.9 ,  10.95,  11.  ])
Ct = r3d.eval(fGHz=S.fGHz)
print "fmin : ",S.fGHz.min()
print "fmax : ",S.fGHz.max()
print "Nf : ", len(S.fGHz)
fmin :  2.0
fmax :  11.0
Nf :  181
r3d
Rays3D
----------
1 / 1 : [0]
2 / 5 : [1 2 3 4 5]
3 / 16 : [ 6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21]
4 / 30 : [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]
5 / 50 : [ 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]
6 / 54 : [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]
7 / 42 : [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]
8 / 8 : [198 199 200 201 202 203 204 205]
9 / 4 : [206 207 208 209]
-----
ni : 1147
nl : 2504
Ectt,Ecpp,Ectp,Ecpt = Ct.energy()
Eco = Ectt+Ecpp
Ecross = Ectp+Ecpt
plt.plot(Ct.tauk,10*np.log10(Eco),'ob')
plt.plot(Ct.tauk,10*np.log10(Ecross),'or')
plt.xlabel('delay(ns)')
plt.ylabel('path energy (dB)')
plt.legend(('Co-pol','X-pol'))
plt.axis((0,180,-60,20))
(0, 180, -60, 20)
./SIM-raytracing_files/SIM-raytracing_54_1.png

The multipath doa/dod diagram can be obtained via the method doadod. The colorbar corresponds to the total energy of the path.

Ct.doadod(phi=(-180,180))
./SIM-raytracing_files/SIM-raytracing_56_0.png
Ct.info()
Nfreq  : 181
Nray : 210
shape Ctt : (210, 181)
shape Ctp : (210, 181)
shape Cpt : (210, 181)
shape Cpp : (210, 181)

8.4. Apply waveform

Once the propagation channel is obtained the transmission channel is calculated with the method prop2tran

Ct.freq = S.fGHz
sco = Ct.prop2tran(a='theta',b='theta')
sca = Ct.prop2tran(a=S.tx.A,b=S.rx.A)

The applied waveform which is here loaded from a measurement file, and compensated for a small time shift. It is important for the latter treatment for the applied waveform to be centered on the middle of the array as it is illustrated below.

print mesdir
/home/uguen/data/WHERE1/measures
wav = wvf.Waveform(typ='generic',fcGHz=6,bandGHz=4)
wav.show()
./SIM-raytracing_files/SIM-raytracing_63_0.png

Finally, the received UWB waveform can be synthesize in applyng the waveform to the transmission channel.

ro = sco.applywavB(wav.sfg)
ra = sca.applywavB(wav.sfg)
ro.plot(typ=['v'])
plt.xlabel('delay(ns)')
plt.ylabel('voltage (V)')
plt.title('without antenna')
#plt.axis((0,180,-0.006,0.006))

ra.plot(typ=['v'])
plt.xlabel('delay(ns)')
plt.ylabel('voltage (V)')
plt.title('with antenna')
#plt.axis((0,180,-0.1,0.1))
./SIM-raytracing_files/SIM-raytracing_66_2.png ./SIM-raytracing_files/SIM-raytracing_66_3.png

9. Simulation Creation

A ray-tracing simulation is controlled via a configuration file which is stored in the ini directory of the project directory. By default, there is a default configuration file named default.ini.

from pylayers.simul.simulem import *
from pylayers.antprop.rays import *
from pylayers.antprop.channel import *
from pylayers.antprop.signature import *
from pylayers.measures.mesuwb import *
import pylayers.util.pyutil as pyu
import pylayers.signal.bsignal as bs

A first step consists in loading a layout associated with the simulation. Here the WHERE1.ini layout is chosen along with the corresponding slabs and materials.

This layout corresponds to the office building where the first WHERE1 UWB measurement campaign has been conducted.

The layout method loads those in a member layout object L of the simulation object S.

If not already available, the layout associated graphs are built.

S = Simul()
# loading a layout
filestr = 'WHERE1'
S.layout(filestr+'.ini','matDB.ini','slabDB.ini')
try:
    S.L.dumpr()
except:
    S.L.build()
    S.L.dumpw()
S.L.showG('i',figsize=(20,20))
./SIM-where1_files/SIM-where1_5_1.png
figsize(20,20)
S.L.showGi(en=7)
axis('off')
int0 :  (309, 15, 46)
int1 :  (202, 46, 59)
 output  {'(137, 59, 58)': 0.64242282168494413, '(230, 59, 67)': 0.35757717831506564, '(230, 59)': 0.35757717831506564}
 Sum pR :  0.357577178315
 Sum pT :  0.642422821685
lseg [137 230]
(-31.123950000000001, 34.74295, 3.6289500000000001, 17.468049999999998)
./SIM-where1_files/SIM-where1_6_2.png

The layout display is fully parameterized via the embedded display dictionnary member of the Layout object.

figsize(10,10)
S.L.display['ednodes']=False
S.L.display['nodes']=False
S.L.display['title']='WHERE1 Project Office Measurement Site'
S.L.display['overlay']=False
fig,ax=S.L.showGs()
./SIM-where1_files/SIM-where1_8_0.png
S.L.Gi.edges()[0]
('(309, 15, 46)', '(148, 46, 44)')

9.1. Adding coordinates of transmiting and receiving points

Coordinates of transmitters and receivers for the simulation to be perform are stotred in .ini files. Transmitter and Receiver are instances of the class RadioNode which offers different methods for specifying nodes positions. The stucture of this .ini file presented below. The node Id is associated with the 3 coordinates separated by white spaces.

[coordinates]
1 = -12.2724 7.76319999993 1.2
2 = -18.7747 15.1779999998 1.2
3 = -4.14179999998 8.86029999983 1.2
4 = -9.09139999998 15.1899000001 1.2
S.tx = RadioNode(_fileini='w2m1rx.ini',_fileant='defant.vsh3')
S.rx = RadioNode(_fileini='w2m1tx.ini',_fileant='defant.vsh3')

The whole simulation setup can then be displayed using the show method of the Simulation object

fig,ax = S.show()
./SIM-where1_files/SIM-where1_15_1.png

Select Tx and Rx positions

map={1: 1,2: 2, 3: 3, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10,
10: 11, 11: 12, 12: 13, 13: 14, 14: 15, 15: 16, 16: 17, 17: 18, 18: 19, 19: 20,
20: 21, 21: 22, 22: 23, 23: 24, 24: 25, 25: 26, 26: 27,
27: 28, 28: 29, 29: 30, 30: 32, 31: 33, 32: 34, 33: 35, 34: 36, 35: 37, 36: 38,
      37: 39, 38: 40, 39: 41, 40: 42, 41: 43, 42: 44, 43: 45, 44: 46, 45: 47,
      46: 48, 47: 49, 48: 50, 49: 51, 50: 52, 51: 53, 52: 54, 53: 55, 54: 56,
      55: 57, 56: 58, 57: 59, 58: 60, 59: 61, 60: 62, 61: 63, 62: 64, 63: 65,
      64: 66, 65: 67, 66: 68, 67: 69, 68: 70, 69: 71, 70: 72, 71: 73, 72: 74,
      73: 75, 74: 76, 75: 77, 76: 78, 77: 79, 78: 80, 79: 81, 80: 82, 81: 83,
      82: 84, 83: 85, 84: 89, 85: 90, 86: 91, 87: 92, 88: 93, 89: 94, 90: 95,
      91: 96, 92: 97, 93: 98, 94: 99, 95: 100, 96: 101, 97: 103, 98: 104, 99:
      105, 100: 106, 101: 107, 102: 108, 103: 109, 104: 110, 105: 111, 106:
      113, 107: 114, 108: 116, 109: 117, 110: 119, 111: 120, 112: 122, 113:
      123, 114: 124, 115: 125, 116: 126, 117: 127, 118: 128, 119: 129, 120:
      133, 121: 134, 122: 136, 123: 137, 124: 138, 125: 139, 126: 140, 127:
      141, 128: 142, 129: 143, 130: 144, 131: 145, 132: 146, 133: 147, 134:
      162, 135: 163, 136: 164, 137: 165, 138: 166, 139: 167, 140: 168, 141:
      169, 142: 170, 143: 171, 144: 172, 145: 173, 146: 174, 147: 175, 148:
      176, 149: 177, 150: 179, 151: 180, 152: 181, 153: 182, 154: 183, 155:
      184, 156: 185, 157: 186, 158: 188, 159: 189, 160: 199, 161: 200, 162:
      201, 163: 202, 164: 203, 165: 204, 166: 205, 167: 206, 168: 207, 169:
      208, 170: 209, 171: 210, 172: 211, 173: 212, 174: 213, 175: 214, 176:
      215, 177: 216, 178: 217, 179: 218, 180: 219, 181: 220, 182: 221, 183:
      222, 184: 223, 185: 227, 186: 228, 187: 229, 188: 230, 189: 231, 190:
      232, 191: 233, 192: 234, 193: 235, 194: 236, 195: 237, 196: 238, 197:
      239, 198: 240, 199: 241, 200: 242, 201: 243, 202: 244, 203: 245, 204:
      246, 205: 247, 206: 248, 207: 249, 208: 250, 209: 251, 210: 252, 211:
      253, 212: 258, 213: 259, 214: 266, 215: 267, 216: 268, 217: 269, 218:
      270, 219: 271, 220: 272, 221: 273, 222: 274, 223: 275, 224: 276, 225:
      277, 226: 278, 227: 279, 228: 297, 229: 298, 230: 299, 231: 300, 232:
      301, 233: 302, 234: 303, 235: 304, 236: 305, 237: 306, 238: 307, 239:
      308, 240: 309, 241: 310, 242: 311, 243: 312, 244: 313, 245: 314, 246:
      315, 247: 316, 248: 317, 249: 318, 250: 319, 251: 320, 252: 321, 253:
      322, 254: 323, 255: 324, 256: 325, 257: 326, 258: 327, 259: 328, 260:
      329, 261: 330, 262: 332, 263: 333, 264: 334, 265: 335, 266: 336, 267:
      337, 268: 338, 269: 339, 270: 340, 271: 341, 272: 342, 273: 343, 274:
      344, 275: 345, 276: 346, 277: 347, 278: 348, 279: 349, 280: 350, 281:
      351, 282: 352, 283: 353, 284: 354, 285: 355, 286: 356, 287: 360, 288:
      361, 289: 362, 290: 363, 291: 364, 292: 365, 293: 366, 294: 367, 295:
      368, 296: 369, 297: 370, 298: 371, 299: 372, 300: 373, 301: 374, 302:
      375}
print 'number of Tx :',len(S.tx.points.keys())
print 'number of rx :',len(S.rx.points.keys())
number of Tx : 302
number of rx : 4

Choose measurement points

# Chose used points here

itx=10
irx=2


# check points
tx= S.tx.points[itx]
rx= S.rx.points[irx]
M = UWBMesure(map[itx])
txm = M.tx
rxm = M.rx[irx]
print tx,txm
print rx,rxm

if (tx[0] - txm[0] > 0.001) or (tx[1] - txm[1] > 0.001):
    print 'Tx and Txm Are not the same !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'
else :
    print 'Txs OK'
if (rx[0] - rxm[0] > 0.001) or (rx[1] - rxm[1] > 0.001):
    print 'Rx and Rxm Are not the same !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'
else :
    print 'Rxs OK'
[-24.867   12.3097   1.2   ] [-24.867   12.3097   1.2   ]
[-18.7747  15.178    1.2   ] [-18.7747  15.178    1.2   ]
Txs OK
Rxs OK
fig =plt.figure(figsize=(20,20))
fig,ax=S.L.showG('t',fig=fig,labels=True)
./SIM-where1_files/SIM-where1_21_0.png
ax.plot(M.tx[0],M.tx[1],'or',label='tx')
ax.plot(M.rx[irx][0],M.rx[irx][1],'ob',label='rx')
ax.legend()
<matplotlib.legend.Legend at 0x7fd83411d910>

9.2. Signatures, Rays and channel

A signature is a sequence of layout objects (points and segments) which are involved in a given optical ray joinint the transmiter and the receiver. The signatutre is calculated from a layout cycle to an other layout cycle. This means that is is required first to retrieve the cycle number from point coordinates. This is done thanks to the pt2cy, point to cycle function.

ctx=S.L.pt2cy(tx)
crx=S.L.pt2cy(rx)
print 'tx point belongs to cycle ',ctx
print 'rx point belongs to cycle ',crx
tx point belongs to cycle  5
rx point belongs to cycle  4

Then the signature between cycle 5 and cycle 4 can be calculated. This is done by instantiating a Signature object with a given layout and the 2 cycle number.

The representaion of a signature obje

Si = Signatures(S.L,ctx,crx)
Si.run4(cutoff=3)
tx[2]=1.5
r2d = Si.rays(tx,rx)
r3d=r2d.to3D(S.L)
r2d.show(S.L,figsize=(20,10),nodes=False)
./SIM-where1_files/SIM-where1_30_1.png
r2d.show(S.L,figsize=(20,10))
./SIM-where1_files/SIM-where1_31_1.png
r3d.locbas(S.L)
r3d.fillinter(S.L)
r3d
Rays3D
----------
1 / 1 : [0]
2 / 5 : [1 2 3 4 5]
3 / 16 : [ 6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21]
4 / 31 : [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]
5 / 34 : [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]
6 / 18 : [ 87  88  89  90  91  92  93  94  95  96  97  98  99 100 101 102 103 104]
-----
ni : 461
nl : 1027
r3d
Rays3D
----------
1 / 1 : [0]
2 / 5 : [1 2 3 4 5]
3 / 16 : [ 6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21]
4 / 31 : [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]
5 / 34 : [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]
6 / 18 : [ 87  88  89  90  91  92  93  94  95  96  97  98  99 100 101 102 103 104]
-----
ni : 461
nl : 1027
S.freq()
array([  2.  ,   2.05,   2.1 ,   2.15,   2.2 ,   2.25,   2.3 ,   2.35,
         2.4 ,   2.45,   2.5 ,   2.55,   2.6 ,   2.65,   2.7 ,   2.75,
         2.8 ,   2.85,   2.9 ,   2.95,   3.  ,   3.05,   3.1 ,   3.15,
         3.2 ,   3.25,   3.3 ,   3.35,   3.4 ,   3.45,   3.5 ,   3.55,
         3.6 ,   3.65,   3.7 ,   3.75,   3.8 ,   3.85,   3.9 ,   3.95,
         4.  ,   4.05,   4.1 ,   4.15,   4.2 ,   4.25,   4.3 ,   4.35,
         4.4 ,   4.45,   4.5 ,   4.55,   4.6 ,   4.65,   4.7 ,   4.75,
         4.8 ,   4.85,   4.9 ,   4.95,   5.  ,   5.05,   5.1 ,   5.15,
         5.2 ,   5.25,   5.3 ,   5.35,   5.4 ,   5.45,   5.5 ,   5.55,
         5.6 ,   5.65,   5.7 ,   5.75,   5.8 ,   5.85,   5.9 ,   5.95,
         6.  ,   6.05,   6.1 ,   6.15,   6.2 ,   6.25,   6.3 ,   6.35,
         6.4 ,   6.45,   6.5 ,   6.55,   6.6 ,   6.65,   6.7 ,   6.75,
         6.8 ,   6.85,   6.9 ,   6.95,   7.  ,   7.05,   7.1 ,   7.15,
         7.2 ,   7.25,   7.3 ,   7.35,   7.4 ,   7.45,   7.5 ,   7.55,
         7.6 ,   7.65,   7.7 ,   7.75,   7.8 ,   7.85,   7.9 ,   7.95,
         8.  ,   8.05,   8.1 ,   8.15,   8.2 ,   8.25,   8.3 ,   8.35,
         8.4 ,   8.45,   8.5 ,   8.55,   8.6 ,   8.65,   8.7 ,   8.75,
         8.8 ,   8.85,   8.9 ,   8.95,   9.  ,   9.05,   9.1 ,   9.15,
         9.2 ,   9.25,   9.3 ,   9.35,   9.4 ,   9.45,   9.5 ,   9.55,
         9.6 ,   9.65,   9.7 ,   9.75,   9.8 ,   9.85,   9.9 ,   9.95,
        10.  ,  10.05,  10.1 ,  10.15,  10.2 ,  10.25,  10.3 ,  10.35,
        10.4 ,  10.45,  10.5 ,  10.55,  10.6 ,  10.65,  10.7 ,  10.75,
        10.8 ,  10.85,  10.9 ,  10.95,  11.  ])
Ct = r3d.eval(S.freq())

The energy method calculates the energy hoeld by each ray

Ct.energy()
(array([  1.50717866e+00,   8.72383299e-01,   7.43948129e-01,
          5.88602827e-02,   6.49517588e-02,   6.82830760e-02,
          3.54534653e-02,   3.03303816e-02,   8.53310210e-03,
          9.71372168e-03,   1.34329862e-02,   5.11977619e-02,
          1.04359516e-01,   1.37194630e-04,   2.24881173e-04,
          5.56901244e-02,   1.07063235e-03,   5.29425486e-02,
          1.13768704e-03,   8.71270328e-03,   5.41494202e-03,
          9.58557439e-03,   2.79649101e-04,   6.10714650e-04,
          1.10020382e-02,   1.05690665e-02,   1.04372661e-05,
          1.25286783e-04,   6.33948908e-05,   5.87987972e-03,
          1.79390141e-04,   2.29893535e-02,   4.29389287e-05,
          1.00415449e-03,   1.02078797e-03,   6.65003548e-05,
          1.75349689e-06,   1.06828491e-02,   1.18747946e-02,
          1.51969989e-04,   7.50277086e-06,   2.22448129e-04,
          1.01132975e-05,   2.45454222e-04,   1.58291561e-04,
          3.33823329e-04,   2.11439212e-05,   1.09192028e-03,
          2.22112301e-04,   8.56044825e-04,   1.31056176e-03,
          1.02549588e-03,   2.00009116e-03,   1.72815139e-03,
          1.23562630e-03,   5.02095787e-05,   1.14052380e-04,
          8.05219626e-05,   3.28328138e-05,   6.44649760e-05,
          2.29734777e-04,   1.15802974e-04,   1.54155145e-05,
          1.97470536e-04,   1.19146330e-04,   2.16602036e-05,
          1.11553925e-04,   9.69270565e-03,   1.93607601e-03,
          5.93875349e-05,   7.12439444e-03,   9.68028356e-05,
          8.54211894e-05,   3.55405355e-05,   1.24790236e-03,
          2.21022661e-04,   1.75148699e-04,   6.50288223e-05,
          2.91193002e-04,   2.16224248e-03,   2.47123465e-03,
          1.18362263e-03,   5.75388808e-04,   2.91758477e-04,
          5.51046523e-04,   5.98355765e-04,   4.69812489e-04,
          1.51177319e-06,   1.39066087e-05,   2.77314071e-05,
          1.83960091e-04,   2.56338584e-04,   5.32258431e-06,
          5.95554013e-06,   1.32305177e-05,   5.32176083e-05,
          1.11130239e-03,   9.15109095e-05,   1.05832357e-03,
          8.60677065e-04,   8.27197200e-04,   1.54930324e-03,
          6.79953409e-04,   1.30889427e-03,   1.24607050e-03]),
 array([  1.29040779e+00,   8.48255132e-02,   1.17476989e-01,
          2.88212748e-01,   6.78736126e-02,   6.91178518e-02,
          1.30831387e-02,   1.71412387e-02,   9.18240805e-03,
          1.26936235e-02,   1.11250612e-02,   3.86908768e-03,
          4.72544843e-03,   3.28379115e-02,   2.59571206e-02,
          6.02854066e-03,   4.65726984e-02,   3.84255807e-03,
          3.70510573e-02,   9.23267268e-03,   5.60971604e-03,
          9.79610884e-03,   3.38476331e-03,   3.30074709e-03,
          4.37291852e-04,   3.28904147e-04,   7.73214589e-04,
          5.37997318e-04,   6.60788727e-04,   2.59344403e-04,
          4.77613852e-04,   6.52446705e-04,   1.19686862e-02,
          4.23869732e-03,   2.67919046e-03,   8.53586068e-04,
          2.59480959e-04,   6.82970439e-05,   1.11026500e-04,
          3.63094187e-03,   7.02005464e-06,   9.49443735e-06,
          6.91037039e-06,   1.98917235e-05,   4.15443320e-03,
          2.83134828e-03,   1.04707561e-05,   7.79799266e-03,
          2.74116760e-05,   9.02423326e-04,   1.35817604e-03,
          1.05432999e-03,   1.98147495e-03,   2.25382366e-04,
          3.13550015e-04,   1.16041899e-06,   3.01617274e-04,
          1.07050148e-03,   2.50402676e-03,   5.86416144e-03,
          2.21046915e-04,   6.98223402e-06,   4.34058789e-05,
          7.46483719e-06,   1.55312397e-04,   4.89785548e-05,
          1.05834613e-04,   3.18005296e-05,   1.39731044e-05,
          3.13832066e-04,   1.65210326e-05,   3.25523335e-04,
          1.47167228e-04,   4.79520767e-05,   7.06827360e-06,
          3.25336068e-04,   7.68029101e-04,   6.99467316e-05,
          1.54317487e-03,   7.57409296e-06,   8.83989381e-06,
          3.15505498e-04,   2.55271187e-03,   9.36639388e-04,
          2.03632807e-03,   1.80256361e-03,   1.72411395e-03,
          1.14694826e-04,   1.07381489e-04,   1.21995072e-06,
          5.13847570e-06,   1.24833836e-05,   1.84052608e-07,
          8.04727851e-07,   2.43500618e-07,   9.40367287e-07,
          2.80292080e-06,   1.50728522e-06,   2.21253510e-06,
          3.99573156e-06,   3.05226608e-06,   1.09471995e-05,
          3.38765449e-06,   1.32603318e-05,   8.71220302e-06]),
 array([  5.34880264e-02,   4.60803734e-03,   5.98041544e-03,
          1.48683145e-04,   4.28468974e-07,   1.39167132e-07,
          3.44648117e-04,   4.32988222e-04,   8.24459987e-02,
          7.54576478e-02,   1.63685453e-05,   6.92871619e-05,
          2.63244563e-04,   9.49397775e-06,   2.89314257e-04,
          9.73902592e-06,   1.38493859e-07,   1.72308550e-05,
          2.96402702e-05,   4.06921123e-04,   2.53204898e-04,
          4.45457011e-04,   3.00919230e-03,   2.74214745e-03,
          1.27043571e-03,   1.77472645e-03,   2.52976615e-05,
          3.19583383e-05,   1.26081852e-02,   1.66933899e-03,
          3.01657589e-02,   1.10767500e-05,   1.24368554e-06,
          4.61618831e-05,   7.68465623e-05,   1.24749021e-04,
          9.08952232e-05,   2.04096397e-03,   2.18918090e-03,
          7.27851376e-06,   7.75188889e-03,   7.27374331e-03,
          4.97618361e-03,   1.06649843e-02,   4.76817452e-05,
          5.32268958e-06,   8.83456410e-03,   1.52744749e-04,
          1.34937336e-03,   9.42567084e-09,   7.88070925e-09,
          2.19793421e-09,   1.33811600e-09,   7.65163723e-06,
          1.67915149e-05,   2.31902822e-05,   8.08145175e-06,
          4.54018611e-04,   1.42571737e-04,   4.03665450e-04,
          9.09425612e-06,   1.78502197e-03,   5.39685583e-05,
          2.08133755e-03,   2.09301490e-04,   6.67154780e-05,
          2.47958981e-04,   1.38657691e-05,   1.64801603e-04,
          2.14662346e-03,   6.08350344e-06,   5.80254804e-03,
          2.21523284e-04,   4.03132781e-05,   1.02701914e-04,
          5.50400930e-03,   3.64017129e-07,   1.02301766e-03,
          1.83143792e-05,   2.37637808e-04,   1.98007641e-04,
          3.21570896e-07,   2.47035172e-05,   1.97385201e-07,
          1.87117892e-05,   1.08760771e-06,   1.22245058e-05,
          2.88923701e-05,   1.22816094e-05,   6.13166338e-05,
          2.26194468e-05,   7.90626055e-06,   1.37317342e-05,
          1.22449783e-04,   1.31706875e-05,   2.52453549e-04,
          8.59336275e-07,   2.19348695e-04,   1.04655208e-06,
          2.10670000e-07,   2.22683794e-07,   3.94088500e-07,
          1.90411407e-07,   8.05127406e-08,   9.03272187e-08]),
 array([  5.34880264e-02,   4.70253324e-04,   9.53453096e-04,
          3.25606682e-04,   4.28490165e-07,   1.39166488e-07,
          6.58052608e-03,   7.65589396e-03,   8.24459987e-02,
          7.54576478e-02,   8.15320469e-06,   1.70511440e-05,
          5.41592552e-05,   4.98194314e-04,   8.32929767e-06,
          1.97219406e-05,   2.06240611e-06,   3.16796161e-05,
          5.92109220e-04,   4.06915344e-04,   2.53194360e-04,
          4.45457563e-04,   1.01712501e-02,   1.06245290e-02,
          1.12214708e-04,   1.19484728e-04,   1.72028890e-02,
          4.14885260e-02,   2.95876839e-05,   2.65130124e-03,
          4.03814948e-05,   1.04625183e-05,   1.07969229e-06,
          1.20402997e-05,   1.19188861e-05,   2.30644740e-02,
          2.59905782e-02,   1.69084386e-03,   1.70711486e-03,
          1.03146050e-06,   1.02227094e-03,   6.39981720e-04,
          1.01244884e-03,   1.59704915e-03,   1.82803331e-06,
          4.31836935e-05,   1.98386471e-03,   2.05740738e-05,
          8.38930939e-03,   5.47689702e-09,   3.34939470e-09,
          2.87072999e-09,   1.43614814e-09,   7.67176734e-06,
          6.33927227e-05,   1.36598438e-03,   4.69152228e-04,
          3.65816261e-05,   1.96997168e-04,   5.35532355e-04,
          2.97339170e-05,   6.06628498e-05,   1.44729065e-03,
          6.98300846e-05,   4.57851393e-05,   1.71312609e-03,
          5.91728217e-05,   4.27614714e-07,   2.67767172e-04,
          1.85128730e-05,   8.93871167e-08,   3.84234643e-05,
          3.75284889e-04,   2.04557194e-03,   1.05410482e-04,
          1.79143739e-04,   1.80964837e-07,   9.14758218e-04,
          2.13176317e-05,   2.26183135e-04,   1.74356891e-04,
          1.54182553e-07,   2.84784687e-05,   2.85562209e-07,
          1.67352836e-05,   1.50085136e-06,   1.04969768e-05,
          1.76917818e-05,   2.62365597e-05,   8.81455569e-06,
          8.02351625e-07,   2.43210557e-08,   9.31884630e-05,
          7.37771985e-06,   8.43374338e-05,   3.79100648e-05,
          2.51109657e-07,   3.08731683e-05,   3.16445653e-07,
          1.95783318e-07,   2.12930276e-07,   2.53116911e-07,
          1.11909010e-07,   8.11390058e-08,   9.07842766e-08]))

9.3. Apply waveform

Ct.freq = S.freq
sco= Ct.prop2tran(a='theta',b='theta')
sca= Ct.prop2tran(a=S.tx.A,b=S.rx.A)
wav = wvf.Waveform(typ='W1offset')
#wav = wvf.Waveform({'type' : 'generic','band': 0.499,'fc': 4.493, 'fe': 100, 'thresh': 3, 'tw': 30})
wav.show()
./SIM-where1_files/SIM-where1_40_0.png
ciro = sco.applywavB(wav.sfg)
cira = sca.applywavB(wav.sfg)
ciro.plot(typ='v')
title(u'received waveform without antenna $\\theta\\theta$')
./SIM-where1_files/SIM-where1_42_2.png
cira.plot(typ='v')
title('received waveform with antenna')
<matplotlib.text.Text at 0xfe125d0>
./SIM-where1_files/SIM-where1_43_1.png
#dchan={i:'ch'+str(i) for i in range(1,5)}
dchan={}
dchan[1]='ch3'
dchan[2]='ch4'
dchan[3]='ch1'
dchan[4]='ch2'
fig = plt.figure()
ax1 = fig.add_subplot(311,title="Measurements")
cmd='M.tdd.' + str(dchan[irx]) + '.plot(ax=ax1)'
eval(cmd)
title('WHERE1 measurement')
#M.tdd.ch2.plot()
# align for plotting
#ciro.x=ciro.x-ciro.x[0]
ax2 = fig.add_subplot(312,title="Simulation-with antenna",sharex=ax1, sharey=ax1)
plt.xlim(20,70)
plt.ylim(-75,-50)
u = cira.plot(ax=ax2)
title('Simulation-with antenna - without noise')
plt.tight_layout()
#ax3 = fig.add_subplot(313,title="Simulation-without antenna",sharex=ax1, sharey=ax1)
#ciro.plot()
./SIM-where1_files/SIM-where1_45_0.png
v = tx-rx
d = sqrt(dot(v,v))

print 'real delay between tx and rx',d/0.3
print 'simulated first path delay',r3d.delays[0]
real delay between tx and rx 22.4680672606
simulated first path delay 22.4680672606
r3d.delays
array([  22.46806726,   24.18292882,   24.99628065,   25.87437157,
         42.95559158,   51.79056452,   27.37668906,   28.09774198,
         29.40772086,   30.73782761,   41.71071752,   43.87690563,
         44.3303829 ,   44.83132695,   45.78534275,   52.55723141,
         52.83546805,   52.93640121,   53.35513533,   57.24533827,
         73.04881379,   77.74128409,   32.08555912,   33.30890428,
         42.65892586,   43.12521253,   45.714854  ,   46.15027493,
         46.65080504,   46.95937444,   47.07757014,   47.80358614,
         48.77327119,   53.58718769,   53.95912049,   54.09963462,
         54.46806832,   55.15670923,   55.87720978,   56.27546336,
         57.93987188,   58.28403515,   73.59435574,   73.8656158 ,
         74.74796992,   75.18892262,   78.25411972,   78.44138572,
         78.50928131,   78.54342153,   87.50257849,   99.12940895,
        104.69401317,   45.82339965,   46.68815648,   48.68108335,
         49.49593798,   49.56104933,   49.58661092,   49.98831846,
         50.36166807,   56.13899433,   56.62835391,   56.8470464 ,
         56.9818197 ,   57.33036252,   57.33173446,   60.30778352,
         60.96744011,   75.28119956,   75.47270498,   75.54640301,
         75.71904704,   75.98272228,   76.00084997,   78.9496738 ,
         79.05105354,   79.20259461,   79.30365102,   80.02316697,
         80.52147075,   87.95852001,   88.18560678,   99.53210396,
         99.73284172,  105.07538433,  105.26555179,   52.33385121,
         53.09267353,   59.38794302,   60.05770372,   77.11847384,
         77.54594822,   77.63542367,   78.06006716,   80.70347572,
         80.80265506,   81.19760461,   81.29618112,   89.53603321,
         89.98167171,  100.92888446,  101.32442804,  106.39941914,
        106.77469922])
r3d.info(0)
-------------------------
Informations of ray # 0
-------------------------

Index , type, slab      , th(rad), alpha     , gamma2
    0 , B0  , -         , -      , -         , -
    0 , T   , PARTITION ,    0.43,  (0.35+0j),  (0.88+0j)
    0 , B   , -         , -      , -         , -

----------------------------------------
 Matrix of ray # 0 at f= 2.0
----------------------------------------
rotation matrix# type: B0
[[-0.99533359 -0.09649373]
 [ 0.09649373 -0.99533359]]
interaction # 0 type: T
[[ 0.29697185+0.47269683j  0.00000000+0.j        ]
 [ 0.00000000+0.j          0.34443500+0.50490218j]]
rotation matrix# [0] type: B
[[ 0.99533359  0.09649373]
 [-0.09649373  0.99533359]]
Ct.doadod(phi=(-180,180))
./SIM-where1_files/SIM-where1_49_0.png

9.4. Example of a trajectory synthesis in DLR WHERE2 environment

from pylayers.simul.simulem import *
from pylayers.antprop.rays import *
from pylayers.antprop.channel import *
from pylayers.antprop.signature import *
import pylayers.util.pyutil as pyu
from pylayers.gis.layout import *
from pylayers.util.project import *
import pylayers.signal.bsignal as bs
from datetime import datetime
import time
import pdb
import pickle
import numpy as np
import matplotlib.pyplot as plt

This function has to be moved in simulem module. It is a temporary implementation. Signatures can be handled much more efficiently here. It run a full simulation and returns a list of channel impulse response.

def evalcir(S,wav,cutoff=4):
    """
    Parameters
    ----------

    S
    tx
    rx
    wav
    cutoff

    """

    crxp =-1
    ctxp =-1
    tcir = {}
    tx = S.tx.position
    Ntx = len(tx[0])
    rx = S.rx.position
    Nrx = len(rx[0])

    #for kt in range(1,Ntx-1):
    #print kt+1
    kt=0
    tcir[kt+1] = {}
    t = np.array([S.tx.position[0,kt+1],S.tx.position[1,kt+1],S.tx.position[2,kt+1]])
    for kr in range(Nrx-1):
        if (mod(kr,10)==0):
            print kr+1
        r = np.array([S.rx.position[0,kr+1],S.rx.position[1,kr+1],S.rx.position[2,kr+1]])
        ctx = S.L.pt2cy(t)
        crx = S.L.pt2cy(r)
        if (ctx<>ctxp)|(crx<>crxp):
            Si  = Signatures(S.L,ctx,crx)
            ctxp = ctx
            crxp = crx
            Si.run4(cutoff=cutoff,algo='old')
        r2d = Si.rays(t,r)
        #r2d.show(S.L)

        r3d = r2d.to3D(S.L)
        r3d.locbas(S.L)
        r3d.fillinter(S.L)
        Ct  = r3d.eval(S.fGHz)
        sca = Ct.prop2tran(S.tx.A,S.rx.A)
        cir = sca.applywavB(wav.sfg)
        tcir[kt+1][kr+1]=cir
    return(tcir)
S = Simul()
filestr = 'DLR2'
S.layout(filestr+'.ini','matDB.ini','slabDB.ini')
try:
    S.L.dumpr()
except:
    S.L.build()
    S.L.dumpw()
S.L.display['ednodes']=False
S.L.display['nodes']=False
S.L.display['title']='DLR WP4 WHERE2 measurement site'
S.L.display['overlay']=False
fig,ax = S.L.showGs()
./SIM-trajectory_files/SIM-trajectory_6_0.png

We have a list of static Anchor Nodes. Those values correspond to the actual anchor nodes coordinates of the WHERE2 project DLR measurement campaign.

AnchorNodes = {390:{'name':'MT_ACO_05','coord':[6,0.81,1.64]},
               386:{'name':'MT_ACO_08','coord':[30.574,2.8,1.291]},
               391:{'name':'MT_ACO_07','coord':[11.78,-5.553,1.5]},
               385:{'name': 'MT_ACO_01','coord':[19.52,-0.69,1.446]},
               387:{'name':'MT_ACO_03','coord':[28.606,-0.74,1.467]},
               400:{'name':'MT_ACO_02','coord':[30.574,2.8,1.291]},
               1:{'name':'MT_DLR_RTDSlave','coord':[0.85,0,1.18]}
              }
S.tx.clear()
S.rx.clear()
S.tx.filant='def.vsh3'
S.rx.filant='def.vsh3'
da ={}
dm ={}

Vizualization of the simulated scenario

fig,ax=S.L.showG('s',nodes=False)
plt.axis('off')
#
# add new points in tx and rx
#
#for c,k in enumerate(AnchorNodes):
c = 0 # first anchor nodes
k = AnchorNodes.keys()[c]
pta = array([AnchorNodes[k]['coord'][0],AnchorNodes[k]['coord'][1],AnchorNodes[k]['coord'][2]]).reshape(3,1)
#
# To add a point
#
S.tx.point(pta,mode="add")
da[c]=k
plt.plot(pta[0,:],pta[1,:],'or')
[<matplotlib.lines.Line2D at 0x7fa00e25bcd0>]
./SIM-trajectory_files/SIM-trajectory_11_1.png

In the following a trajectory for the receiver is defined.

linevect function allows to define a linear trajectory from ptt along direction vec.

S.rx.linevect(npt=290, step=0.1, ptt=[0, 0, 1.275], vec=[1, 0, 0], mode='subst')
ps = S.rx.position[:,-1]
S.rx.linevect(npt=60, step=0.1, ptt=ps,vec=[0,1,0],mode='append')

Looking what is does

S.L.display['ednodes']=False
S.L.display['edges']=True
S.L.display['nodes']=False
S.L.display['title']='Trajectory to be simulated'
S.show(s=20)
./SIM-trajectory_files/SIM-trajectory_15_2.png

Choosing a UWB waveform for the simulation

wav = wvf.Waveform(type='W1compensate')
wav.show()
./SIM-trajectory_files/SIM-trajectory_17_0.png

running the simulation

#tcir = evalcir(S,wav,cutoff=4)

Saving the data in pickle format

#file = open("tcir5.pickle","w")
#pickle.dump(tcir,file)
#file.close()

Reading the data from the above file

#del tcir
file=open("tcir5.pickle","r")
tcir=pickle.load(file)
file.close()
#
for i in tcir[1].keys():
    cir = tcir[1][i]
    cir.zlr(0,150)
    try:
        ttcir=np.vstack((ttcir,cir.y))
    except:
        ttcir=cir.y
plt.figure(figsize=(10,10))
dmax=150
plt.imshow(20*np.log10(ttcir+1e-20),vmax=-40,vmin=-120,origin='lower',extent=[0,dmax,1,69],interpolation='nearest')
plt.xlabel(r'delay $\times$ c (meters)',fontsize=20)
#plt.ylabel(r'distance along trajectory (meters)',fontsize=20)
plt.ylabel(r'trajectory index number',fontsize=20)
clb=plt.colorbar()
clb.set_label('level (dB)',fontsize=20)

plt.axis('tight')
(0.0, 150.0, 1.0, 69.0)
./SIM-trajectory_files/SIM-trajectory_25_1.png
tcir[1][10].plot(typ=['v'])
./SIM-trajectory_files/SIM-trajectory_26_2.png
plt.figure(figsize=(10,5))
tcir[1][1].plot(typ=['v'])
xlabel('Delay (ns)')
ylabel('Level (V)')
title('Received Waveform')
./SIM-trajectory_files/SIM-trajectory_27_2.png
tcir[1][11].plot(typ=['v'])
xlabel('Delay (ns)')
ylabel('Level (V)')
title('Received Waveform')
<matplotlib.text.Text at 0x553bd50>
./SIM-trajectory_files/SIM-trajectory_28_1.png

10. Handling Human mobility

The body mobility is described at two different level, the body level (small scale) and the trajectory level (large scale)

10.1. Body Mobility

from pylayers.mobility.body.body import *
from pylayers.mobility.trajectory import Trajectory
from IPython.display import Image
<matplotlib.figure.Figure at 0x894a310>

The body mobility is imported from motion capture files. This is the chosen manner to achieve a high degree of realism for the modeling of the human motion. Two kind of files exist :

  • c3d files are a set of point which are evolving in time
  • bvh files are a stuctured version of the motion capture.

Both type of file will be exploited in the following.

10.1.1. BodyCylinder data structure

To ease electromagnetic simulation a simplification of the motion capture data structure is necessary. Generally there is a large number of captured points, not all of them being useful for the modeling.

The body model is a restriction of key body segments which are transformed into cylinders of radius .

The chosen body model is made of 11 cylinders. 4 cylinders decribing the two arms, 4 cylinders decribing the two legs, 2 cylinders describing the trunk and 1 cylinder for the head.

The body cylinder model is handle by a dedicated Python class call Body

To create a void body, simply instantiate a Body object from the class

John = Body()

which is equivalent to :

John = Body(_filebody='John.ini',_filemocap='07_01.c3d')

The default body filename is John.ini and the default motion capture filename is ‘07_01.c3d’. The creation of a Body consists in reading a _filebody and a _filemocap

10.1.2. Description of a body file

An example of a body file is given below. It is a file in ini format with 4 sections.

  • [nodes]

This section associates a node number to a c3d fils conventional node number

NodeId = C3DNODE
  • [cylinder]

This section associates a cylinder Id to a dictionnary wich contains cylinder tail head and radius information

CylId = {'t',NodeId1,'h',NodeId2,'r',float (m),'name',}
  • [device]

This section associates a device name to a dictionnary wich contains cylinder device related information

DevId = {'typ' : {static|mobile}
          'cyl': CylId
          'l'  : length coordinate in ccs,
          'h'  : height coordinate in ccs,
          'a'  : angle coordinate in ccs,
          'file' : antenna file ,
          'T' : Rotation matrix }
#%load /home/uguen/Bureau/P1/ini/Francois.ini
            [nodes]
0 = STRN
1 = CLAV
2 = RFHD
3 =RSHO
4 =LSHO
5 =RELB
6 =LELB
7 =RWRB
8 =LWRB
9 =RFWT
10 =LFWT
11 =RKNE
12 =LKNE
13 =RANK
14 =LANK
15 =BOTT
[cylinder]
; sternum (STRN) - clavicule (CLAV)
trunku = {'t':0,'h':1,'r':0.18,'i':0}
; bottom  (BOTT) sternum (STRN)
trunkb = {'t':15,'h':0,'r':0.17,'i':10}
; clavicule (CLAV)  - tete (RFHD)
headu = {'t':1,'h':2,'r':0.12,'i':1}
; coude droit (RELB)  epaule droite (RSHO)
armr = {'t':5,'h':3,'r':0.05,'i':2}
; coude gauche (LELB)  epaule gauche (LSHO)
arml  = {'t':6,'h':4,'r':0.05,'i':3}
; poignet droit (RWRB) coude droit (RELB)
forearmr = {'t':7,'h':5,'r':0.05,'i':4}
; left wrist (LWRB)  left elbow (LELB)
forearml = {'t':8,'h':6,'r':0.05,'i':5}
; knee droit (RKNE) hanche droit (RFWT)
thighr = {'t':11,'h':9,'r':0.05,'i':6}
; knee left (LKNE)  hanche left (LFWT)
thighl = {'t':12,'h':10,'r':0.05,'i':7}
; cheville droit (RANK) genou  droit (RKNE)
calfr = {'t':13,'h':11,'r':0.05,'i':8}
; cheville droit (LANK) genou  droit (LKNE)
calfl = {'t':14,'h':12,'r':0.05,'i':9}
[device]
0 = {'typ':'static','name':'BeSpoon Phone','cyl':'trunku','l':0.1,'h':0.01,'a':0,'file':'S2R2.sh3','T':np.array([[1,0,0],[0,1,0],[0,0,1]])}
1 = {'typ':'static','name':'Movea Accel','cyl':'trunku','l':0.1,'h':0.01,'a':180,'file':'S2R2.sh3','T':np.array([[1,0,0],[0,1,0],[0,0,1]])}
2 = {'typ':'static','name':'Optivent Glass','cyl':'head','l':0.7,'h':0.01,'a':0,'file':'S2R2.sh3','T':np.array([[1,0,0],[0,1,0],[0,0,1]])}
3 = {'typ':'static','name':'Geonaute Podo','cyl':'trunkb','l':0.1,'h':0.01,'a':45,'file':'S2R2.sh3','T':np.array([[1,0,0],[0,1,0],[0,0,1]])}
4 = {'typ':'static','name':'Breizh Watch','cyl':'forearmr','l':0.2,'h':0.01,'a':0,'file':'S2R2.sh3','T':np.array([[1,0,0],[0,1,0],[0,0,1]])}
5 = {'typ':'static','name':'Breizh Watch','cyl':'forearml','l':0.2,'h':0.01,'a':0,'file':'S2R2.sh3','T':np.array([[1,0,0],[0,1,0],[0,0,1]])}
[mocap]

walk = '07_01_c3d'
John
My name is : John

I have a Galaxy Gear device on the left forearm
I have a cardio device on the upper part of trunk
I am nowhere yet

filename : 07_01.c3d
nframes : 300
Centered : True
Mocap Speed : 1.36558346484
Francois = Body(_filebody='Francois.ini',_filemocap='07_01.c3d')
Francois
My name is : Francois

I have a Movea Accel device on the upper part of trunk
I have a BeSpoon Phone device on the upper part of trunk
I have a Geonaute Podo device on the lower part of trunk
I have a Optivent Glass device hea
I have a Breizh Watch device on the left forearm
I have a Breizh Watch device on the right forearm
I am nowhere yet

filename : 07_01.c3d
nframes : 300
Centered : True
Mocap Speed : 1.36558346484

10.1.3. Loading a Motion Capture File

A .c3d motion capture file is loaded with the method loadC3D with as arguments the motion capture file and the number of frames to load.

The motion is represented as a sequence of framef stored in the d variable member.

It is possible to get the information from the C3D header by using the verbose option of the read_c3d function

# Video Frame Rate
Vrate = 120
# Inter Frame
Tframe = 1./120
# select a number of frame
nframes = 300
# Time duration of the whole selected frame sequence
Tfseq = Tframe*nframes
#
# load a .c3dmotion capture file
# this update the g.pos
#
#bc.loadC3D(filename='07_01.c3d',nframes=nframes,centered=True)

The duration of the capture is

print "Duration of the motion capture sequence", Tfseq," seconds"
Duration of the motion capture sequence 2.5  seconds

d is a MDA of shape (3,npoint,nframe). It contains all the possible configurations of the body. In general it is supposed to be a cyclic motion as an integer number of walking steps. This allows to instantiate the body configuration anywhere else in space in a given trajectory.

A specific space-time configuration of the body is called a ``topos``.

np.shape(John.d)
(3, 16, 300)

10.1.4. Defining a trajectory

A Trajectory is a class which :

  • derives from a pandas DataFrame
  • is container for time,position,velocity and acceleration.

To define a default trajectory :

traj = Trajectory()
t = traj.generate()
traj.head()
x y z vx vy vz ax ay az s
1970-01-01 00:00:00 0.000000 0.000000 -0.675722 0.061186 0.204082 0.539726 -0.000229 0 0.401367 0.000000
1970-01-01 00:00:00.204082 0.061186 0.204082 -0.135996 0.060957 0.204082 0.941093 -0.000458 0 -0.976458 0.580256
1970-01-01 00:00:00.408163 0.122143 0.408163 0.805096 0.060499 0.204082 -0.035365 -0.000684 0 0.439891 1.545150
1970-01-01 00:00:00.612245 0.182642 0.612245 0.769731 0.059815 0.204082 0.404526 -0.000909 0 -2.415204 1.760928
1970-01-01 00:00:00.816327 0.242457 0.816327 1.174257 0.058906 0.204082 -2.010679 -0.001129 0 2.788602 2.217949

5 rows 10 columns

f,a = traj.plot()
./MOB-body_files/MOB-body_26_0.png

10.1.5. settopos () method

Once the trajectory has been defined it is possible to send the body at the position corresponding to any time of the trajectory with the settopos method.

settopos takes as argument

  • A trajectory
  • A time index
traj.__repr__()
't (s) : 0.0:9.591837nd (m) : 59.4653280432nVmoy (m/s) : 6.19957658196n'
John.settopos(traj,t=5)
figure(figsize=(15,20))
for t in arange(traj.tmin+0.4,traj.tmax,0.5):
    John.settopos(traj,t=t)
    f,a=John.show(color='b',plane='yz',topos=True)
    axis('off')
./MOB-body_files/MOB-body_30_0.png
John
My centroid position is
[ 0.31956097  9.3877551 ]
filename : 07_01.c3d
nframes : 300
Centered : True
Mocap Speed : 1.36558346484
Francois.settopos(traj,t=6)
Francois
My centroid position is
[ 0.97911919  5.91836735]
filename : 07_01.c3d
nframes : 300
Centered : True
Mocap Speed : 1.36558346484
  • 3 : dimension of space
  • 16 : number of nodes
  • 300 : number of frames

The figure below shows the projection in a vertival plane of the body nodes.

In order to translate the motion in any point in space-time, a distinction is made between the real motion or topos and the centered motion capture which acts as a virtual motion.

Let denotes the center of gravity of the body in the (O,x,y) plane

a = np.hstack((John.vg,John.vg[:,-1][:,newaxis]))

is the velocity vector of the gravity center of the body.

print np.shape(John.pg)
print np.shape(John.vg)
(3, 300)
(3, 300)
print John.vg[:,145]
print John.vg[:,298]
[ 0.0114987  -0.00026335  0.        ]
[  1.08123514e-02   7.24411022e-05   0.00000000e+00]

At that point the body structure is centered.

The frame is centered in the xy plane by substracting from the configuration of points the projection of the body in the xy plane.

np.shape(John.d)
(3, 16, 300)
John.npoints
16

Each frame is centered above the origin. For example for a walk motion the effect of the centering is just like if the body was still walking but not moving forward exactly in the same manner as a walk on a conveyor belt.

pgc = np.sum(John.d[:,:,0],axis=1)/16
pg0 = John.pg[:,0]
print "True center of gravity", pg0
print "Center of gravity of the centered frame",pgc
True center of gravity [-1.74251571  0.49373077  0.        ]
Center of gravity of the centered frame [  3.74700271e-16   1.38777878e-17   8.94887363e-01]
np.shape(John.pg)
(3, 300)

The current file contains 300 frames

tframe = arange(John.nframes)
np.shape(John.pg[0:-1,:])
(2, 300)
xg = John.pg[0,:]
yg = John.pg[1,:]
zg = John.pg[2,:]
figure(figsize=(8,8))
subplot(311)
plot(tframe,xg)
title('x component')
ylabel('m')
subplot(312)
xlabel('frame index')
title('y component')
ylabel('m')
plot(tframe,yg)
subplot(313)
xlabel('frame index')
title('Motion capture centroid trajectory')
ylabel('m')
plot(xg,yg,'.b')

d = John.pg[0:-1,1:]-John.pg[0:-1,0:-1]
smocap = cumsum(sqrt(sum(d*d,axis=0)))

Vmocap = smocap[-1]/Tfseq
title('Length = '+str(smocap[-1])+' V = '+str(Vmocap*3.6)+' km/h')
axis('scaled')
axis('off')
plt.tight_layout()
./MOB-body_files/MOB-body_52_0.png
plot(smocap)
title('evolution of curvilinear abscisse from motion capture centroid trajectory')
xlabel('frame index')
ylabel('distance (meters)')
<matplotlib.text.Text at 0x7fdd45827110>
./MOB-body_files/MOB-body_53_1.png

10.1.6. Defining a large scale trajectory

A large scale trajectory is defined in the plane.

traj is a data structure (Npt,2)

v = Vmocap
print v*3.6,"Kmph"
4.91610047343 Kmph
# time in seconds
time = np.arange(0,10,0.01)
x = v*time
y = np.zeros(len(time))
z = np.zeros(len(time))
traj = Trajectory()
traj.generate(time,np.vstack((x,y,y)).T)
traj.tmax
9.97
fig ,ax = traj.plot()
traj.head()
x y z vx vy vz ax ay az s
1970-01-01 00:00:00 0.000000 0 0 0.013656 0 0 0.000000e+00 0 0 0.000000
1970-01-01 00:00:00.010000 0.013656 0 0 0.013656 0 0 0.000000e+00 0 0 0.013656
1970-01-01 00:00:00.020000 0.027312 0 0 0.013656 0 0 0.000000e+00 0 0 0.027312
1970-01-01 00:00:00.030000 0.040968 0 0 0.013656 0 0 6.938894e-18 0 0 0.040968
1970-01-01 00:00:00.040000 0.054623 0 0 0.013656 0 0 -1.387779e-17 0 0 0.054623

5 rows 10 columns

./MOB-body_files/MOB-body_58_1.png

10.1.7. Trajectory

10.2. posvel()

The posvel() function (position and velocity) takes as arguments the following parameters

  • traj a plane trajectory object.
  • time for evaluation of topos
  • duration of the periodic motion frame sequence

and returns

  • the frame index
  • the trajectory index
  • velocity unitary vector along motion capture frame
  • :math:`hat{mathbf{w}}_s = mathbf{hat{z}} times hat{mathbf{v}}_s `
  • velocity unitary vector along trajectory
  • :math:`hat{mathbf{w}}_t = mathbf{hat{z}} times hat{mathbf{v}}_t `

is the interframe time or frame sampling period, it is equal to the whole duration of the motion sequence divided by the number of frames

settopos is a method which takes as argument :

  • traj a plane trajectory (Npt,2)
  • time for evaluation of topos

In futher version of the project, this function will be modified to be able to avoid passing the whole trajectory.

John.settopos(traj=traj,t=3)

There is now a new data structure in the Body objet. This data structure is called a topos.

print np.shape(John.topos)
(3, 16)
John.topos
array([[ 4.19599817,  4.15046572,  4.13081653,  4.07294142,  4.08289287,
         4.11542251,  3.94530449,  4.26514518,  3.82039411,  4.12746908,
         4.18189918,  3.89824308,  4.28535797,  3.70884964,  4.41212222,
         4.15468413],
       [ 0.00978643,  0.01039192, -0.08586912, -0.15014439,  0.17454155,
        -0.29891008,  0.28336831, -0.31726715,  0.37578539, -0.14822518,
         0.12069953, -0.11848735,  0.13268648, -0.04485156,  0.07025805,
        -0.01376282],
       [ 1.18925612,  1.35442836,  1.57836099,  1.39037819,  1.40039528,
         1.07349379,  1.11418117,  0.83620759,  0.89701301,  0.89713179,
         0.89777028,  0.44315412,  0.43969984,  0.09704225,  0.05562085,
         0.89745103]])
John.show3()
John.settopos(traj=traj,t=1)
fig,ax=John.plot3d(topos=True)
John.settopos(traj=traj,t=4)
John.plot3d(topos=True,fig=fig,ax=ax)
John.settopos(traj=traj,t=6)
John.plot3d(topos=True,fig=fig,ax=ax)
(<matplotlib.figure.Figure at 0x7fdd458256d0>,
 <matplotlib.axes.Axes3DSubplot at 0x7fdd4582d2d0>)
./MOB-body_files/MOB-body_69_1.png

10.2.1. Definition of Several Coordinates systems

Each cylinder of the Body model bears one specific coordinate system.

One or several cylinder coordinate systems can be chosen to define the Body Local Coordinates System (BLCS) which is required for motion capture (BLCS) applications.

In general, the origin will be chosen on a position which is the most time invariant as on the chest or the back.

Those frames of references are all defined in the Global Coordinate System (GCS) of the scene.

10.2.2. Construction of the Cylinder Coordinate System (CCS)

The method setccs() is used to associate a Cylinder Coordinate System (CCS) to each cylinder of the bodyCylinder model. Notice that those cylinders coordinates systems are not known by the localization application. The localization application will define the BLCS from the position of radiating devices placed on the body surface.

Each basis is constructed with the function from geomutil.onbfromaxe() : orthonormal bases from axes. This function takes 2 sets of points and as input and provides an orthonormal basis as output.

3 unitary vectors are constructed :

Where is the unit velocity vector along actual trajectory.

The outpout of geomutil.onbframe is an MDA of unitary matrices aggregated along axis 1

To create the CCS :

John.setccs()
import scipy.linalg as la
print "ccs dimensions : ",np.shape(John.ccs)
print John.ccs[0,:,:]
print "Check determinant : ", la.det(John.ccs[0,:,:])
ccs dimensions :  (11, 3, 3)
[[ 0.94678656  0.05306765 -0.31745715]
 [-0.06834689  0.99696857 -0.03718026]
 [ 0.31452173  0.05689898  0.94754345]]
Check determinant :  1.0

Create a Wireframe body representation from the body graph model

Representation of frames associated with the cylinder

John.show3()

On the figure below the wireframe model is shown associated with the 11 CCS (Cylinder coordinates systems)

Image('CCS.png')
./MOB-body_files/MOB-body_82_0.png

10.2.3. Placing a dcs (Device Coordinate System ) on the cylinder

A DCS is refered by 4 numbers

  • Id : Cylinder Id
  • l : length along cylinder
  • h : height above cylinder generatrix
  • alpha : angle from front direction (degrees)
Id = 4 # 4 Left Arm
l  = 0.1 # Longitudinal coordinates
h  = 0.03 # height
alpha = 45 # angle degrees
John.dcyl
{'arml': 3,
 'armr': 2,
 'calfl': 9,
 'calfr': 8,
 'forearml': 5,
 'forearmr': 4,
 'head': 1,
 'thighl': 7,
 'thighr': 6,
 'trunkb': 10,
 'trunku': 0}

Rotate Matrix around z

John.settopos(traj=traj,t=6,cs=True)
John.dcyl
{'arml': 3,
 'armr': 2,
 'calfl': 9,
 'calfr': 8,
 'forearml': 5,
 'forearmr': 4,
 'head': 1,
 'thighl': 7,
 'thighr': 6,
 'trunkb': 10,
 'trunku': 0}
John.show3(topos=True,dcs=True)
John.show3(topos=True,pattern=True)
./MOB-body_files/MOB-body_93_0.png

10.3. Large Scale Mobility

11. Algebraic Localization Class

For localization in the plane it is important not to provide the z coordinates. Otherwise a singularity problem arises.

import numpy as np
from pylayers.location.algebraic.algebraic  import *
c = 0.2997924583
nodes={}
ldp={}
p1 = np.array([-1,-1])
p2 = np.array([1,-1])
p3 = np.array([0,1])
nodes['BN'] = np.array([[0],[0]])
nodes['RN_TOA']=np.vstack((p1,p2,p3)).T
ldp['TOA']=np.array([np.sqrt(2),np.sqrt(2),1])
ldp['TOA_std']=np.array([1,1,1])
Alg = algloc(nodes,ldp)
print ldp
Alg.plot()
{'TOA_std': array([1, 1, 1]), 'TOA': array([ 1.41421356,  1.41421356,  1.        ])}
./LOC-algebraic_files/LOC-algebraic_4_2.png
pest = Alg.ls_locate(toa=True,tdoa=False,rss=False)
Alg.plot()
./LOC-algebraic_files/LOC-algebraic_6_1.png
pest
array([[ 0.        ],
       [-0.22753112]])
Alg
Nodes : {'BN': array([[0],
       [0]]), 'RN_TOA': array([[-1,  1,  0],
       [-1, -1,  1]])}
LDPs :{'TOA_std': array([1, 1, 1]), 'TOA': array([ 1.41421356,  1.41421356,  1.        ])}
from pylayers.location.algebraic.algebraic import *
from pylayers.util.geomutil import dist

11.1. Example of TDOA

First, it is necesary to define Anchor nodes and associated Reference Anchor Nodes. This is important to be very specific about what exactly the TDOA is. In the example the Blin node is draw randomly as well as anchor nodes.

nodes = {}
N = 5
AN_TDOA = np.random.rand(2,N)
#AN_TDOA1=np.array([[0, 0, 1, 1],[0,1,1,0]])
#AN_TDOAr1 = np.roll(AN_TDOA,1,axis=1)
AN_TDOAr1 = np.zeros((2,1))
AN_TDOAr2 = AN_TDOA[:,-1][:,newaxis]
BN = np.array([[0.2],[0.3]])
AN_TDOA
array([[ 0.4184779 ,  0.46323775,  0.76936267,  0.03841692,  0.20877339],
       [ 0.86098336,  0.99099928,  0.10115537,  0.30007228,  0.9133354 ]])
AN_TDOAr1
array([[ 0.],
       [ 0.]])
AN_TDOAr2
array([[ 0.20877339],
       [ 0.9133354 ]])

The figure below illustrates the situation, in red the anchor nodes, the blue star is the blind node.

plot(AN_TDOA[0,:],AN_TDOA[1,:],'or')
plot(AN_TDOAr2[0,:],AN_TDOAr2[1,:],'ok')
plot(BN[0,:],BN[1,:],'*b')
axis([-1,2,-1,2])
[-1, 2, -1, 2]
./LOC-localization_files/LOC-localization_8_1.png
d = dist(AN_TDOA,BN,0)
dr1= dist(AN_TDOAr1,BN,0)
dr2= dist(AN_TDOAr2,BN,0)
tdoa1 = (d-dr1)/0.3
tdoa2 = (d-dr2)/0.3
tdoa0 = (d-roll(d,1))/0.3

print cumsum(tdoa0)
print tdoa1
print tdoa2
[-0.03790822  0.42014538 -0.03437296 -1.50605018  0.        ]
[ 0.80490184  1.26295544  0.8084371  -0.66324012  0.84281006]
[-0.03790822  0.42014538 -0.03437296 -1.50605018  0.        ]
node={}
nodes['BN']=BN
nodes['RN_TDOA']=AN_TDOA
nodes['RNr_TDOA']=AN_TDOAr2
ldp = {}
ldp['TDOA']=cumsum(tdoa0)
ldp['TDOA_std']=np.ones(N)
S=algloc(nodes,ldp)
S.info()
Nodes :  {'RN_TDOA': array([[ 0.4184779 ,  0.46323775,  0.76936267,  0.03841692,  0.20877339],
       [ 0.86098336,  0.99099928,  0.10115537,  0.30007228,  0.9133354 ]]), 'BN': array([[ 0.2],
       [ 0.3]]), 'RNr_TDOA': array([[ 0.20877339],
       [ 0.9133354 ]])}
Location dependent parameters :  {'TDOA': array([-0.03790822,  0.42014538, -0.03437296, -1.50605018,  0.        ]), 'TDOA_std': array([ 1.,  1.,  1.,  1.,  1.])}
S = algloc(nodes,ldp)
S.ls_locate(tdoa=True,toa=False,rss=False)
array([[ 0.20011097],
       [ 0.30007884]])
nodes['BN']
array([[ 0.2],
       [ 0.3]])

Robust Geometric Positioning Algorithm

from pylayers.location.geometric.constraints.cla import *
from pylayers.location.geometric.constraints.toa import *
from pylayers.location.geometric.constraints.tdoa import *
from pylayers.location.geometric.constraints.rss import *
from pylayers.network.model import *

import matplotlib.pyplot as plt

Let’s define 4 anchors in the plane.

pt1=np.array(([0,0]))
pt2=np.array(([10,15]))
pt3=np.array(([5,28]))
pt4=np.array(([-10,-10]))
# the unknown point is p
p = np.array((0,5))

Then displaying the scene with ancho nodes (in red) and blind nodes in blue

f=plt.figure()
ax=f.add_subplot(111)
ax.plot(pt1[0],pt1[1],'or',label='anchor 1')
ax.plot(pt2[0],pt2[1],'or',label='anchor 2')
ax.plot(pt3[0],pt3[1],'or',label='anchor 3')
ax.plot(pt4[0],pt4[1],'or',label='anchor 4')
ax.plot(p[0],p[1],'xb',label='p')


ax.legend(loc='best')
<matplotlib.legend.Legend at 0x48dab50>
./LOC-rgpa_files/LOC-rgpa_5_1.png

The euclidian distance betwenn blind node and anchors ans the corresponding Time of flight are evaluated.

d1=np.sqrt(np.sum((pt1-p)**2))
d2=np.sqrt(np.sum((pt2-p)**2))
d3=np.sqrt(np.sum((pt3-p)**2))
d4=np.sqrt(np.sum((pt4-p)**2))

toa1=d1/0.3
toa2=d2/0.3
toa3=d3/0.3
toa4=d4/0.3

print 'distance p-1=',d1, '/ toa1=',toa1
print 'distance p-2=',d2, '/ toa2=',toa2
print 'distance p-3=',d3, '/ toa3=',toa3
print 'distance p-4=',d4, '/ toa3=',toa4
distance p-1= 5.0 / toa1= 16.6666666667
distance p-2= 14.1421356237 / toa2= 47.1404520791
distance p-3= 23.5372045919 / toa3= 78.4573486396
distance p-4= 18.0277563773 / toa3= 60.0925212577

12. RGPA (Robust Geometric Positioning Algorithm)

12.1. Exploiting Time of Arrival (ToA) only

We call Constraint Layer Array (CLA) the object which gathers all the geometric constraints of a considered scenario.

C=CLA()

Instanciate TOA constraints, notice that their id are differents

T1=TOA(id=0,value = toa1, std = np.array([1.0]), p = pt1)
T2=TOA(id=1,value = toa2, std = np.array([1.0]), p = pt2)
T3=TOA(id=2,value = toa3, std = np.array([1.0]), p = pt3)
T4=TOA(id=3,value = toa4, std = np.array([1.0]), p = pt4)

Add TOA contraints to the CLA

C.append(T1)
C.append(T2)
C.append(T3)
C.append(T4)

All the constraints of the CLA can be listed as follows

C.c
[node | peer   |type | rat  | p              | value    | std  | runable| usable|
    0 |        |TOA  |      | [0 0]          | [ 16.667]| [ 1.]|       1|      1|,
 node | peer   |type | rat  | p              | value    | std  | runable| usable|
    1 |        |TOA  |      | [10 15]        | [ 47.14] | [ 1.]|       1|      1|,
 node | peer   |type | rat  | p              | value    | std  | runable| usable|
    2 |        |TOA  |      | [ 5 28]        | [ 78.457]| [ 1.]|       1|      1|,
 node | peer   |type | rat  | p              | value    | std  | runable| usable|
    3 |        |TOA  |      | [-10 -10]      | [ 60.093]| [ 1.]|       1|      1|]

Get information on the cla :

  • type : TOA / RSS
  • p : Position of the origin of the constraint
  • value : power ( RSS ) / time in ns ( TOA)
  • std : standard deviation of value
  • runable : does the constraint has a position p ?
  • obsolete : does the value has been obtained recently ?
  • usuable : runbale AND NOT obsolete
  • evlauated : obsolete
C.info()
type , p              , value, std  , runable, usable, obsolete, evaluated
TOA  , [0 0]          , [ 16.667], [ 1.],       1,      1,        0,         0
type , p              , value, std  , runable, usable, obsolete, evaluated
TOA  , [10 15]        , [ 47.14], [ 1.],       1,      1,        0,         0
type , p              , value, std  , runable, usable, obsolete, evaluated
TOA  , [ 5 28]        , [ 78.457], [ 1.],       1,      1,        0,         0
type , p              , value, std  , runable, usable, obsolete, evaluated
TOA  , [-10 -10]      , [ 60.093], [ 1.],       1,      1,        0,         0

Update the CLA

C.update()

Compute the cla

C.compute()
True

show the estimated position

C.pe
array([ -4.735e-03,   4.992e+00])

to be compare with the actual position value

p
array([0, 5])

12.2. RSS

The RSS is a quantity which is weakly related to distance via a parametric model. The bettet the model, better would be tthe inference ab out tthe associated distance. t To model the Path Loss shadowing model is widely used.

To define the classical path loss shadowing model widely used in this context the PLSmodel class has been defined.

M = PLSmodel(f=3.0,rssnp=2.64,d0=1.0,sigrss=3.0,method='mode')

For simulation purpose : get RSS from distances (or associated delay) with the above model

toa1
16.666666666666668
M.getPL(toa1,1)
11.262571050852122

12.3. TDOA

Td1=TDOA(id=0,value = toa1-toa2, std = np.array([1.0]), p = np.array([pt1,pt2]))
Td2=TDOA(id=1,value = toa1-toa3, std = np.array([1.0]), p = np.array([pt1,pt3]))
Td3=TDOA(id=2,value = toa1-toa4, std = np.array([1.0]), p = np.array([pt1,pt4]))
C=CLA()
C.append(Td1)
C.append(Td2)
C.append(Td3)
C.compute()
TDOA 2.0
TDOA 2.0
TDOA 2.0
TDOA 1.5
TDOA 1.5
TDOA 1.5
TDOA 1.375
TDOA 1.375
TDOA 1.375
TDOA 1.375
TDOA 1.375
TDOA 1.375
True
C.pe
array([ 0.021,  4.987])
import ConfigParser
import pylayers.util.pyutil as pyu

13. Network Simulation Configuration

PyLayers is designed to provide indoor radio channel simulation for mobile agents.

The goal is to adress mobility in indoor environement heterogeneous network, with human being carriers of a mobile User Equipement (UE) which possibly embeds several Radio Acess Technology (RAT).

Several human can be created and their motion in the environement should be as realistic as possible, because for many applications it turns out that many parameter of interest are stongly dependent of the dynamic topology of the mobile network.

In the following the configuration files for proceeding with those high level PyLayers simulation are described.

The configuration file is simulnet.ini

This file is located in $BASENAME/ini

!cat $BASENAME/ini/simulnet.ini
[Mysql]
host = localhost
user = root
passwd = sqlsql
dbname = test
dumpdb =True

[Save]
save=[]
;save=['csv','mysql','matlab','pyray','txt','ini']
savep=True

[Layout]
filename = TA-Office.ini

x_offset  = 30
y_offset = 2

the_world_width      = 65
the_world_height = 20
the_world_scale      = 20

[Mechanics]
; update time for agent movement
mecanic_update_time = 0.1
; select how agnt choose destiantion
;'random' ; file
choose_destination = 'random'


[Network]
; refresh TOA regulary 'synchro 'or with distance 'autionomous'
Communication_mode='autonomous'
; update time for refreshing network
network_update_time = 0.1
; show nodes moving & radio link
show = False
; show in ipython .
ipython_nb_show = False
; show signature ( not fully functionnal)
show_sg = False
; show 2 tables : mecanic & network
show_table = False
; show the same information but in terminal
dispinfo = False

[Localization]
; perform localization
localization = True
; time to refresh localization
localization_update_time = 1.0
; list of used methods method = ['alg','geo']
method = ['alg']



[Simulation]
; Simulation duration
duration = 10.0
; speed ratio
speedratio = 1.
; time for refreshing tk plot ( obsolete)
show_interval = 0.5
; show scene using tk renderer ( obsolete)
showtk   = False
; choose seed for random mobiliity
seed = 1
; verbose output
verbose = True
Cp = ConfigParser.ConfigParser()
Cp.read(pyu.getlong('simulnet.ini','ini'))
['/home/uguen/Bureau/P1/ini/simulnet.ini']

Simulnet.ini contains the following sections

Cp.sections()
['Mysql',
 'Save',
 'Layout',
 'Mechanics',
 'Network',
 'Localization',
 'Simulation']

This section define the save options.

dict(Cp.items('Save'))
{'save': '[]', 'savep': 'True'}

The savep boolean enable/disable saving of the simulation.

dict(Cp.items('Save'))['savep']
'True'

The log file which contains all traces from the dynamic are in $BASENAME/netsave

ls /home/Bureau/P1/netsave/
ls: impossible d'accder  /home/Bureau/P1/netsave/: Aucun fichier ou dossier de ce type

13.1. Layout

This section allow setup the layout for the simulation

dict(Cp.items('Layout'))
{'filename': 'TA-Office.ini',
 'the_world_height': '20',
 'the_world_scale': '20',
 'the_world_width': '65',
 'x_offset': '30',
 'y_offset': '2'}

Choose the used Layout for simulation

dict(Cp.items('Layout'))['filename']
'TA-Office.ini'

Setup an offset on the origin of axis

print dict(Cp.items('Layout'))['x_offset']
print dict(Cp.items('Layout'))['y_offset']
30
2

13.2. Network

dict(Cp.items('Network'))
{'communication_mode': "'autonomous'",
 'dispinfo': 'False',
 'ipython_nb_show': 'False',
 'network_update_time': '0.1',
 'show': 'False',
 'show_sg': 'False',
 'show_table': 'False'}

Setup communication mode between node:

  • autonomous : the data exchange between nodes is driven by the localization layer. If more information is required to estimate the position a communication request is send to the communication stae
  • synchro : the data exchange between nodes is periodic. LDPs are periodically refreshed at the network_update_time
dict(Cp.items('Network'))['communication_mode']
"'autonomous'"

Time step for the refresh network information

dict(Cp.items('Network'))['network_update_time']
'0.1'

Vizualization of the simulation using matplotlib

dict(Cp.items('Network'))['show']
'False'

Vizualization of a table summing up the data exchange of the nodes

dict(Cp.items('Network'))['show_table']
'False'

Vizualization of the simulation inside ipython .

dict(Cp.items('Network'))['ipython_nb_show']
'False'

13.3. Mechanics

This section allow to setup the agent movement during simulation

dict(Cp.items('Mechanics'))
{'choose_destination': "'random'", 'mecanic_update_time': '0.1'}

Setup how agent choose their target:

  • random : the agnet move into the layout randomly
  • file : the agent follow the sequence specified in /nodes_destination.ini
dict(Cp.items('Mechanics'))['choose_destination']
"'random'"

Time step for refreshing the mechanical layer (ground truth position)

dict(Cp.items('Mechanics'))['mecanic_update_time']
'0.1'

13.4. Localization

Setup Localization algorithms

dict(Cp.items('Localization'))
{'localization': 'True',
 'localization_update_time': '1.0',
 'method': "['alg']"}

enable/disable localizaiton of the agents

dict(Cp.items('Localization'))['localization']
'True'

Select localization methods :

  • Algebraic : htrogeneous localization algorithm
  • Geometric : RGPA
dict(Cp.items('Localization'))['method']
"['alg']"

Time step for localization update

dict(Cp.items('Localization'))['localization_update_time']
'1.0'

13.5. Simulation

dict(Cp.items('Simulation'))
{'duration': '10.0',
 'seed': '1',
 'show_interval': '0.5',
 'showtk': 'False',
 'speedratio': '1.',
 'verbose': 'True'}

Setup simulation duration in second

dict(Cp.items('Simulation'))['duration']
'10.0'

Setup random seed for simulation

dict(Cp.items('Simulation'))['seed']
'1'

Display messages during simulation

dict(Cp.items('Simulation'))['verbose']
'True'
import pylayers.mobility.trajectory as traj
from pylayers.mobility.body.body import *
from pylayers.gis.layout import *

trajectories can be imported from a simulnet simulation with the importsn method

L=Layout('TA-Office.ini')
t=traj.importsn()

The 2 following trajectories have been calculated with pylayers.simul.simulnet

plt.figure(figsize=(20,20))
f,a = L.showGs()
f,a = t[0].plot()
f,a = t[1].plot()
f,a = t[2].plot()
f,a = t[3].plot()
./MOB-mobility_files/MOB-mobility_7_0.png ./SIM-workflow_files/SIM-workflow_4_0.png

14. Geomutil Class

Geomutil is a module which gathers different geometrical functions used in other modeule of pylayers.

The importation is done as below. The geoutil alias is geu

from pylayers.util.geomutil import *
from pylayers.util.plotutil import *
import shapely.geometry as shg

15. Class Polygon

This class is important because it implements the visibility graph, of a Polygon.

The Polygon class is a subclass of the shapely polygon class. It allows to initialize a Polygon with different object (list,np.array,sh.MultiPoint)

points = shg.MultiPoint([(0, 0), (1, 1), (2, 0), (1, 0),(0,-2)])
poly1 = Polygon(points)
poly2 = Polygon(p=[[3,4,4,3],[1,1,2,2]])
N = 7
phi = np.linspace(0,2*np.pi,N)
x = 3*np.cos(phi)+5
y = 3*np.sin(phi)+5
nppoints  = np.vstack((x,y))
poly3  = Polygon(nppoints)

15.1. ploting polygons

fig = figure()
ax = fig.gca()
axis('off')
axis('equal')
fig,ax=poly1.plot(color='green',fig=fig,ax=ax)
fig,ax=poly2.plot(color='red',fig=fig,ax=ax)
fig,ax=poly3.plot(color='#000000',fig=fig,ax=ax)
./MISC-geomutil_files/MISC-geomutil_8_0.png

15.2. buildGv()

Dertermine visibility relationships in a Polygon. Returns a graph

This function is used for determining visibility relationships in indoor environement.

figsize(8,8)
points  = shg.MultiPoint([(0, 0), (0, 1), (2.5,1), (2.5, 2), \
                                          (2.8,2), (2.8, 1.1), (3.2, 1.1), \
                                          (3.2, 0.7), (0.4, 0.7), (0.4, 0)])
polyg   = Polygon(points)
Gv      = polyg.buildGv(show=True)
plt.axis('off')
(-0.5, 4.0, -0.5, 2.5)
./MISC-geomutil_files/MISC-geomutil_11_1.png

16. Geomview classes

16.1. GeomVect class

This class is used to interact with geomview 3D viewer.

16.1.1. geomBase

Display a base

v1 = np.array([1,0,0])
v2 = np.array([0,1,0])
v3 = np.array([0,0,1])
M  = np.vstack((v1,v2,v3))
gv = GeomVect('test')
gv.geomBase(M)
#gv.show3()

16.1.2. points

display a set of points

gv1 = GeomVect('test1')
gv1.points(rand(3,10))
#gv1.show3()

ndarray method converts a Polygon object to an ndarray

geo = Geomoff('test2')
pt  = poly3.ndarray().T
pt1 = np.hstack((pt,np.zeros((7,1))))

This class is used in module vrml2geom

polys = [[0,1,2,3,4,5,6]]
geo.polygons(pt1,polys)
#geo.show3()
poly = [0,1,2,3,4,5,6]
geo.polygon(pt1,poly)
#geo.show3()
np.zeros((7,1))
array([[ 0.],
       [ 0.],
       [ 0.],
       [ 0.],
       [ 0.],
       [ 0.],
       [ 0.]])
extrem=np.array([-2,2,-2,2,-2,2])

16.1.3. plotting a box

geo=Geomoff('test3')
geo.box()
#geo.show3()

16.2. Utility functions

16.2.1. angledir

angledir converts a 3D vector into the 2 spherical angle , expressed in radians

s = np.array([[2,0,0],[0,2,0],[0,0,1],[1,1,1]])
angledir(s)*180/pi
array([[ 90.        ,   0.        ],
       [ 90.        ,  90.        ],
       [  0.        ,   0.        ],
       [ 54.73561032,  45.        ]])

16.2.2. linet

fig = figure()
axis('off')
ax = fig.gca()
p1 = np.array([0,0])
p2 = np.array([1,0])
p3 = np.array([0,1])
p4 = np.array([1,1])
ax = linet(ax,p1,p2,al=0.7,color='red',linewidth=3)
ax = linet(ax,p2,p3,al=0.8,color='blue',linewidth=2)
ax = linet(ax,p3,p4,al=0.9,color='green',linewidth=1)
ax = linet(ax,p4,p1,al=1,color='cyan',linewidth=10)
./MISC-geomutil_files/MISC-geomutil_36_0.png

16.2.3. dptseg(p,pt,ph)

this function calculates distances between a set of points and a segment

pt = np.array([0,0])
ph = np.array([10,0])
p  = np.array([[-1,1 ,3,4,11],[8,1,2,3,3]])
d1,d2,h = dptseg(p,pt,ph)
print d1,d2,h
[[ -1.   1.   3.   4.  11.]] [[ 11.   9.   7.   6.  -1.]] [ 8.  1.  2.  3.  3.]

16.2.4. displot

axis('off')
axis('equal')
N   = 50
pt  = sp.rand(2,N)
ph  = sp.rand(2,N)
f,a = displot(pt,ph)
./MISC-geomutil_files/MISC-geomutil_42_0.png

16.2.5. ptonseg(pta,phe,pt)

used in select.py

pta = np.array([0,0])
phe = np.array([10,0])
pt = np.array([9,8])
p = ptonseg(pta,phe,pt)
print p
[ 9.  0.]

16.2.6. ptconvex

points  = shg.MultiPoint([(0, 0), (0, 1), (3.2, 1), (3.2, 0.7), (0.4, 0.7), (0.4, 0)])
N = len(points)
polyg   = Polygon(points)
tcc,n   = polyg.ptconvex()
axis('off')
axis('equal')
k = 0
polyg.plot()
for p in points:
    if tcc[k] == 1 :
        plt.plot(p.x, p.y, 'o', color='red',alpha=1)
    else:
        plt.plot(p.x, p.y, 'o', color='blue',alpha=0.3)
    k = k+1
./MISC-geomutil_files/MISC-geomutil_48_0.png ./MISC-geomutil_files/MISC-geomutil_48_1.png

16.2.7. intersect

intersect(A,B,C,D) wether or not the N segments (AB) intersects N segments (CD). The intersection is tested only for the segment of same index in the ndarray.

from pylayers.util.geomutil import *
from pylayers.util.plotutil import *
import scipy as sp
N1 = 6
N2 = 5
A = sp.rand(2,N1)
B = sp.rand(2,N1)
C = sp.rand(2,N1)
D = sp.rand(2,N1)
b1 = intersect(A,B,C,D)
b1
array([ True, False,  True, False,  True, False], dtype=bool)
pt1 = A[:,b1]
ph1 = B[:,b1]
pt2 = C[:,b1]
ph2 = D[:,b1]
pt3 = A[:,(1-b1).astype(bool)]
ph3 = B[:,(1-b1).astype(bool)]
pt4 = C[:,(1-b1).astype(bool)]
ph4 = D[:,(1-b1).astype(bool)]
f1,a1 = displot(pt1,ph1,'r')
f2,a2 = displot(pt2,ph2,'b')
f3,a3 = displot(pt3,ph3,'c')
f4,a4 = displot(pt4,ph4,'y')
ti = plt.title('test intersect')
./MISC-geomutil_files/MISC-geomutil_53_0.png
b1
array([ True, False,  True, False,  True, False], dtype=bool)
(1-b1).astype('bool')
array([False,  True, False,  True, False,  True], dtype=bool)
b1.all()
False
b1.any()
True

One want to evaluate all the intersection of a set of segments of

16.3. Useful functions

isaligned?
pts = np.array([-27.835,  10.891])
phs = np.array([-27.836,  10.926])

ptk = array([-27.833,  10.686])
phk = array([-27.835,  10.891])
isaligned(pts,phs,ptk)
True
isaligned(pts,phs,phk)
True
plot(pts[0],pts[1],'or')
plot(phs[0],phs[1],'or')
plot(ptk[0],ptk[1],'ob')
plot(phk[0],phk[1],'ob')
axis('equal')
(-27.836000000000002, -27.832500000000003, 10.65, 10.950000000000001)
./MISC-geomutil_files/MISC-geomutil_64_1.png
Previous
Next