This repository is a fundamental toolbox of OTFS modulation crossing matlab
and python
. If you use our work, you should use the following citation to refer it:
X. Qu, A. Kosasih, W. Hardjawana, V. Onasis and B. Vucetic, "Bayesian-based Symbol Detector for Orthogonal Time Frequency Space Modulation Systems," 2021 IEEE 32nd Annual International Symposium on Personal, Indoor and Mobile Radio Communications (PIMRC), Helsinki, Finland, 2021, pp. 1154-1159, doi: 10.1109/PIMRC50174.2021.9569353.
A. Kosasih, X. Qu, W. Hardjawana, C. Yue and B. Vucetic, "Bayesian Neural Network Detector for an Orthogonal Time Frequency Space Modulation," in IEEE Wireless Communications Letters, vol. 11, no. 12, pp. 2570-2574, Dec. 2022, doi: 10.1109/LWC.2022.3209076.
H. Chang, A. Kosasih, W. Hardjawana, X. Qu and B. Vucetic, "Untrained Neural Network based Bayesian Detector for OTFS Modulation Systems," IEEE INFOCOM 2023 - IEEE Conference on Computer Communications Workshops (INFOCOM WKSHPS), Hoboken, NJ, USA, 2023, pp. 1-6, doi: 10.1109/INFOCOMWKSHPS57453.2023.10225901.
- Install through
Matlab
HOME/Add-Ons/Get Add-Ons
: searchwhatshow_toolbox
and install it.HOME/Add-Ons/Get Add-Ons
: searchwhatshow_phy_mod_otfs
and install it.
- Install through
pip
pip install whatshow-toolbox pip install whatshow-phy-mod-otfs
- import this module
from whatshow_phy_mod_otfs import OTFS, OTFSResGrid, OTFSDetector
- import this module
All codes are uniform in matlab and python in three class.
- OTFSResGrid: this class provides the resource grid
- OTFSResGrid()
@in1
: 1st input, a scalar for subcarrier number or the content directly
@in2
: only if 1st input is scalar, this input is thenTimeslotNum
@zp_len(opt)
: zero padding length@batch_size(opt):
the batch size (only used in python)rg = OTFSResGrid(M, N); // build a RG (give subcarrier number and timeslot number) rg = OTFSResGrid(X_DD); // build a RG (give X_DD matrix directly) // user zero padding rg = OTFSResGrid(X_DD, "zp_len", zp_len); rg = OTFSResGrid(X_DD, zp_len=zp_len);
- Set the pulse type
rg.setPulse2Ideal(); // use the ideal pulse rg.setPulse2Recta(); // use the rectangular pulse
- Set pilot type
rg.setPilot2Embed(); // use embedded pilots rg.setPilot2SuperImposed(); // use superimposed pilots
- Set the location of pilots (in the center by default)
@pl_len
: pilot length on the delay
@pk_len
: pilot length on the doppler
@pl1
: pilot location on the delay
@pk1
: pilot location on the dopplerrg.pilot2center(pl_len, pk_len); // pilots in the center of the entire OTFS frame rg.pilot2zp(pl_len, pk_len); // pilots in the center of zero-padding area rg.setPilot2Flex(pl_len, pk_len, pl1, pk1); // pilots in user-defined area
- setGuard(): set the guard range
@in1
: (1,2) negative guard on the delay (3) negative guard on the Doppler
@in2
: (1,2) positive guard on the delay (3) positive guard on the Doppler
@in3
: (1) negative guard on the Doppler
@in4
: (1) positive guard on the Doppler
@guard_delay_full(opt)
: full guard on delay (if set true, ignore the number setting)
@guard_doppl_full(opt)
: full guard on Doppler (if set true, ignore the number setting)// set guard range manually rg.setGuard(guard_delay_num_neg, guard_delay_num_pos, guard_doppl_num_neg, guard_doppl_num_pos); // full guard rg.setGuard(guard_delay_num_neg, guard_delay_num_pos, 'guard_doppl_full', true); rg.setGuard(guard_delay_num_neg, guard_delay_num_pos, guard_doppl_full=True);
- map():
symbols
is mandatory,pilots_pow
andpilots_pow
must use one if you want to use pilots. Other parameters define the length of pilots and the guards area around the pilots. If the guard area is oversize, you are suggested to use full guard on that axis so that the channel estimation result will be more accurate.
@symbols
: OTFS symbols
@pilots(opt)
: a vector of your pilots (if givenpilots_pow
won't be used)
@pilots_pow(opt)
: pilot power to generate random pilots - setAreaCE(): set the channel estimation area manually. You should only call this function when you disagree with our channel estimation area.
@ce_l_beg
: CE delay beginning
@ce_l_end
: CE delay ending
@ce_k_beg
: CE Doppler beginning
@ce_k_end
: CE Doppler ending - demap()
@isData(opt)
: whether give the data (true by default)
@isCE(opt)
: whether give the channel estimation result(true by default)
@threshold(opt)
: the threshold to estimate the channel[y, his_est, lis_est, kis_est] = rg_rx.demap("threshold", 1e-10); y, his_est, lis_est, kis_est = rg_rx.demap(threshold=1e-10);
- clone(): clone this resource grid
- isZP(): return zero padding length
- isPG(): check whether use pilots & guards
- Check whether your coordinate is in a certain area
@pos_doppl
: the position on the Doppler axis. Not given means the position is for a Doppler-delay vector
@pos_delay
: the position on the delay axis for matrix or th position on the Doppler-delay axis for the vector// is in pilots and guards area rg.isInAreaPG(k, l); rg.isInAreaPG(n); // you coordinate is from a vector // is in zero padding area rg.isInAreaZP(k, l); rg.isInAreaZP(n); // you coordinate is from a vector // is in data area rg.isInAreaDA(k, l); rg.isInAreaDA(n); // you coordinate is from a vector // is in channel estimation area rg.isInAreaCE(k, l); rg.isInAreaCE(n); // you coordinate is from a vector
- getAreaPG(): get the area of pilots and guards
[pg_num, pg_delay_beg, pg_delay_end, pg_doppl_beg, pg_doppl_end] = rg.getAreaPG(); pg_num, pg_delay_beg, pg_delay_end, pg_doppl_beg, pg_doppl_end = rg.getAreaPG();
- getAreaCE(): get the area of channel estimation
[ce_num, ce_delay_beg, ce_delay_end, ce_doppl_beg, ce_doppl_end] = rg.getAreaCE(); ce_num, ce_delay_beg, ce_delay_end, ce_doppl_beg, ce_doppl_end = rg.getAreaCE();
- check pulse type
rg.isPulseIdeal(); // use ideal pulse or not rg.isPulseRecta(); // use rectangula pulse or not
- getPilotsMat(): return a DD domain matrix only containing pilots
- setContent()
@content
: a 2D matrix containing pilots, guards and data (if used)rg.setContent(content);
- getContentSize(): get content size
[nSubcarNum, nTimeslotNum] = rg.getContentSize(); nSubcarNum, nTimeslotNum = rg.getContentSize();
- getContent(): get the content
@isVector(opt)
: if true, the returned result is a vectorrg.getContent(); // vectorized results rg.getContent("isVector", true); rg.getContent(isVector=True);
- getContentCE(): get the channel estimation area content (return a matrix)
- getContentNoCE(): get the content except CE area (return a vector)
- getContentZeroPG(): get the content of zero PG area (return a vector)
- getContentZeroCE(): get the content of zero CE area (return a vector)
- getContentDataLocsMat(): get Data locations in content (return a logical matrix)
- OTFSResGrid()
- OTFS: this class provides the entire process of OTFS from Tx to Rx
- OTFS()
@batch_size(opt)
: the batch size (only used in python) - Pulse settings: if you want to input delay-Doppler domain matrix directly into
OTFS
, you need to set the pulse type beforemodulate
.otfs.setPulse2Ideal(); // use ideal pulses otfs.setPulse2Recta(); // use rectangular pulses
- modulate():modulate (use fast method by default)
@in1
: an OTFS resource grid or a 2D matrix [(batch_size), Doppler, delay]
@isFast(opt)
: DD domain -> TD domain (no X_TF)// fast modulate otfs.modulate(rg); // slow modulate otfs.modulate(rg, "isFast", false); otfs.modulate(rg, isFast=False);
- setChannel(): this methods has multiple kinds of inputs.
- set a fixed chanel (at least two paths, if you want to add one fixed path, call
setChannelExtra
)@in1->his
: the path gains
@in2->lis
: the delays
@in3->kis
: the doppler shifts - set a random channel (overwritten the channel setting; use Rayleigh fading if not select channel model)
@in1->p
: the path number
@in2->lmax
: the maxmimal delay index
@in3->kmax
: the maximal Doppler index (can be fractional)
@force_frac(opt)
: use fractional Doppler (force)
@isAWGN(opt)
: use awgn
@isRician(opt)
: use Rician fading
otfs.setChannel(p,lmax,kmax); // optional inputs otfs.setChannel(p,lmax,kmax, "force_frac", true); otfs.setChannel(p,lmax,kmax, force_frac=True); otfs.setChannel(p,lmax,kmax, "isAWGN", true); otfs.setChannel(p,lmax,kmax, isAWGN=True); otfs.setChannel(p,lmax,kmax, "isRician", true); otfs.setChannel(p,lmax,kmax, isRician=True);
- set a fixed chanel (at least two paths, if you want to add one fixed path, call
- setChannelExtra(): add a path to the channel (this does not influence other existing paths)
@hi
: the path gain (linear gain)
@li
: the delay
@ki
: the Doppler shift - passChannel()
@No
: linear noise power (a scalar) or a given noise vectorotfs.passChannel(No);
- demodulate(): demodulate (use fast method by default)
@isFast(opt)
: TD domain -> DD domain (no Y_TF)rg = otfs.demodulate(); // if the modulation uses a resource grid Y_DD = otfs.demodulate(); // if the modulation uses a delay-Doppler domain matrix
- getChannel(): return the channel on delay Doppler domain. If not given
his
,lis
,kis
, use the current channel.
@his
: the channel gains
@lis
: the channel delays
@kis
: the channel Dopplers
@data_only(opt)
: whether the channel is only for data (by default true). If you want to get the entire H_DD when using pilos and/or guards, you should manullay set it to false.// the channel only for data Hdd = otfs.getChannel("data_only", true); Hdd = otfs.getChannel(data_only=True);
- getCSI(): get the channel state information
@sort_by_gain(opt)
: sort axis, false by defaut
@sort_by_delay_doppler(opt)
: sort axes, false by defaut
@sort_by_doppler_delay(opt)
: sort axes, false by defaut
@descend(opt)
: sort direction, false by defautotfs.getCSI("sort_by_delay_doppler", true); otfs.getCSI(sort_by_delay_doppler=True);
- getXTF(): get the signal in the TF domain
- getXT(): get the signal in the time domains
@fft_size(opt)
: the size of fft
- OTFS()
- OTFSDetect: this class provides dedicated OTFS detectors
- OTFSDetector()
@constel
: the constellation (a vector)od = OTFSDetector(sympool);
- Select a detector
- useMPBase(): set detector types - MP Base
@n_ite(opt)
: the iteration number (200 by default)
@delta_fra(opt)
: the percentage for taking the values in the current iterationod.useMPBase(); od.useMPBase("n_ite", 10, "delta_fra", 0.9); od.useMPBase(n_ite=10, delta_fra=0.9);
- useMPBase(): set detector types - MP Base
- detect(): return the estimated symbols
@y
: the received signal from the resource grid (after demapping) or just a resource grid
@csi_info1
: (1) a vector of path gains (2) a matrix of HDD
@csi_info2
: (1) the delay indices (2) the noise power
@csi_info3
: (1) the Doppler indices
@csi_info4
: (1) the noise powerxDD_est = od.detect(rg_rx, his, lis, kis, No); xDD_est = od.detect(rg_rx, Hdd, No);
- OTFSDetector()
Before running any sample code, please make sure you are at the root path of this repository. Also, Matlab codes require running init
in the command window first to load directories.
Viterbo_MP_2018
: this code is fromEmanuele Viterbo
. You can download his original code in this page.Viterbo_MP_Embed
: examples showing how MP detects symbols using embedded pilots.WaveForm
: this is to observe the waveform of OTFS.
./Modular_OTFS
: testOTFS
classcase000
: modular tests on each functionscase001:
test the light mode (integer and fractional Dopplers. ideal and rectangular pulses)
./Modular_OTFSDetector
: testOTFSResGrid
class./Modular_OTFSResGrid
: testOTFSResGrid
class./Whole_CE
: examples showing how to estimate channel (using OTFSResourceGrid)../Whole_CE_Detect
: examples showing how to estimate channel (using OTFSResourceGrid) and detect symbols../Whole_Joint
case001
: ideal/recta pulse + full guards + single pilot; NMSEcase002
: ideal/recta pulse + full guards + single pilot; SER(mp base)
OTFS
- getCSI(): python need to sort
- removeNoDAChannel(): need to remove zero padding area
OTFSDetect
detectMPBase
: support when batch_size is greater than 1
Raviteja, P., Phan, K. T., Hong, Y., & Viterbo, E. (2018). Interference cancellation and iterative detection for orthogonal time frequency space modulation. IEEE transactions on wireless communications, 17(10), 6501-6515.
Raviteja, P., Phan, K. T., & Hong, Y. (2019). Embedded pilot-aided channel estimation for OTFS in delay–Doppler channels. IEEE transactions on vehicular technology, 68(5), 4906-4917.