| |
| """code for ray marching and signal rendering |
| """ |
| import torch |
| import numpy as np |
| import torch.nn.functional as F |
| import scipy.constants as sc |
| from einops import rearrange, repeat |
|
|
|
|
| class Renderer(): |
|
|
| def __init__(self, networks_fn, **kwargs) -> None: |
| """ |
| Parameters |
| ----------------- |
| near : float. The near bound of the rays |
| far : float. The far bound of the rays |
| n_samples: int. num of samples per ray |
| """ |
|
|
| |
| self.network_fn = networks_fn |
| self.n_samples = kwargs['n_samples'] |
| self.near = kwargs['near'] |
| self.far = kwargs['far'] |
|
|
|
|
| def sample_points(self, rays_o, rays_d): |
| """sample points along rays |
| |
| Parameters |
| ---------- |
| rays_o : tensor. [n_rays, 3]. The origin of rays |
| rays_d : tensor. [n_rays, 3]. The direction of rays |
| |
| Returns |
| ------- |
| pts : tensor. [n_rays, n_samples, 3]. The sampled points along rays |
| t_vals : tensor. [n_rays, n_samples]. The distance from origin to each sampled point |
| """ |
| shape = list(rays_o.shape) |
| shape[-1] = 1 |
| near, far = torch.full(shape, self.near), torch.full(shape, self.far) |
| t_vals = torch.linspace(0., 1., steps=self.n_samples) * (far - near) + near |
| t_vals = t_vals.to(rays_o.device) |
| pts = rays_o[...,None,:] + rays_d[...,None,:] * t_vals[...,:,None] |
|
|
| return pts, t_vals |
|
|
|
|
|
|
|
|
| class Renderer_spectrum(Renderer): |
| """Renderer for spectrum (integral from single direction) |
| """ |
|
|
| def __init__(self, networks_fn, **kwargs) -> None: |
| """ |
| Parameters |
| ----------------- |
| near : float. The near bound of the rays |
| far : float. The far bound of the rays |
| n_samples: int. num of samples per ray |
| """ |
| super().__init__(networks_fn, **kwargs) |
|
|
|
|
|
|
| def render_ss(self, tx, rays_o, rays_d): |
| """render the signal strength of each ray |
| |
| Parameters |
| ---------- |
| tx: tensor. [batchsize, 3]. The position of the transmitter |
| rays_o : tensor. [batchsize, 3]. The origin of rays |
| rays_d : tensor. [batchsize, 3]. The direction of rays |
| """ |
|
|
| |
| pts, t_vals = self.sample_points(rays_o, rays_d) |
|
|
| |
| view = rays_d[:, None].expand(pts.shape) |
| tx = tx[:, None].expand(pts.shape) |
|
|
| |
| raw = self.network_fn(pts, view, tx) |
| receive_ss = self.raw2outputs(raw, t_vals, rays_d) |
|
|
| return receive_ss |
|
|
|
|
|
|
| def raw2outputs(self, raw, r_vals, rays_d): |
| """Transforms model's predictions to semantically meaningful values. (core part) |
| |
| Parameters |
| ---------- |
| raw : [batchsize, n_samples, 4]. Prediction from model. |
| r_vals : [batchsize, n_samples]. Integration distance. |
| rays_d : [batchsize, 3]. Direction of each ray |
| |
| Return: |
| ---------- |
| receive_signal : [batchsize]. abs(singal of each ray) |
| """ |
|
|
| raw2alpha = lambda raw, dists: 1.-torch.exp(-raw*dists) |
| |
| raw2phase = lambda raw, dists: raw*dists |
|
|
| dists = r_vals[...,1:] - r_vals[...,:-1] |
| dists = torch.cat([dists, torch.Tensor([1e10]).cuda().expand(dists[...,:1].shape)], -1) |
| dists = dists * torch.norm(rays_d[...,None,:], dim=-1) |
|
|
| att_a, att_p, s_a, s_p = raw[...,0], raw[...,1], raw[...,2], raw[...,3] |
| att_p, s_p = torch.sigmoid(att_p)*np.pi*2, torch.sigmoid(s_p)*np.pi*2 |
| att_a, s_a = abs(F.leaky_relu(att_a)), abs(F.leaky_relu(s_a)) |
| |
|
|
| alpha = raw2alpha(att_a, dists) |
| phase = raw2phase(att_p, dists) |
|
|
| att_i = torch.cumprod(torch.cat([torch.ones((alpha.shape[0], 1)).cuda(), 1.-alpha + 1e-10], -1), -1)[:, :-1] |
| path = torch.cat([r_vals[...,1:], torch.Tensor([1e10]).cuda().expand(r_vals[...,:1].shape)], -1) |
| path_loss = 0.025 / path |
| |
| phase_i = torch.cumsum(torch.cat([torch.ones((alpha.shape[0], 1)).cuda(), phase], -1), -1)[:, :-1] |
| phase_i = torch.exp(1j*phase_i) |
|
|
|
|
| receive_signal = torch.sum(s_a*torch.exp(1j*s_p)*att_i*phase_i*path_loss, -1) |
| receive_signal = abs(receive_signal) |
|
|
| return receive_signal |
|
|
|
|
|
|
|
|
| class Renderer_RSSI(Renderer): |
| """Renderer for RSSI (integral from all directions) |
| """ |
|
|
| def __init__(self, networks_fn, **kwargs) -> None: |
| """ |
| Parameters |
| ----------------- |
| near : float. The near bound of the rays |
| far : float. The far bound of the rays |
| n_samples: int. num of samples per ray |
| """ |
| super().__init__(networks_fn, **kwargs) |
|
|
|
|
| def render_rssi(self, tx, rays_o, rays_d): |
| """render the RSSI for each gateway. To avoid OOM, we split the rays into chunks |
| |
| Parameters |
| ---------- |
| tx: tensor. [batchsize, 3]. The position of the transmitter |
| rays_o : tensor. [batchsize, 3]. The origin of rays |
| rays_d : tensor. [batchsize, 9x36x3]. The direction of rays |
| """ |
|
|
| batchsize, _ = tx.shape |
| rays_d = torch.reshape(rays_d, (batchsize, -1, 3)) |
| chunks = 36 |
| chunks_num = 36 // chunks |
| rays_o_chunk = rays_o.expand(chunks, -1, -1).permute(1,0,2) |
| tags_chunk = tx.expand(chunks, -1, -1).permute(1,0,2) |
| recv_signal = torch.zeros(batchsize).cuda() |
| for i in range(chunks_num): |
| rays_d_chunk = rays_d[:,i*chunks:(i+1)*chunks, :] |
| pts, t_vals = self.sample_points(rays_o_chunk, rays_d_chunk) |
| views_chunk = rays_d_chunk[..., None, :].expand(pts.shape) |
| tx_chunk = tags_chunk[..., None, :].expand(pts.shape) |
|
|
| |
| raw = self.network_fn(pts, views_chunk, tx_chunk) |
| recv_signal_chunks = self.raw2outputs_signal(raw, t_vals, rays_d_chunk) |
| recv_signal += recv_signal_chunks |
|
|
| return recv_signal |
|
|
|
|
| def raw2outputs_signal(self, raw, r_vals, rays_d): |
| """Transforms model's predictions to semantically meaningful values. |
| |
| Parameters |
| ---------- |
| raw : [batchsize, chunks,n_samples, 4]. Prediction from model. |
| r_vals : [batchsize, chunks, n_samples]. Integration distance. |
| rays_d : [batchsize,chunks, 3]. Direction of each ray |
| |
| Return: |
| ---------- |
| receive_signal : [batchsize]. abs(singal of each ray) |
| """ |
| wavelength = sc.c / 2.4e9 |
| |
| |
| raw2phase = lambda raw, dists: raw + 2*np.pi*dists/wavelength |
| raw2amp = lambda raw, dists: -raw*dists |
|
|
| dists = r_vals[...,1:] - r_vals[...,:-1] |
| dists = torch.cat([dists, torch.Tensor([1e10]).cuda().expand(dists[...,:1].shape)], -1) |
| dists = dists * torch.norm(rays_d[...,None,:], dim=-1) |
|
|
| att_a, att_p, s_a, s_p = raw[...,0], raw[...,1], raw[...,2], raw[...,3] |
| att_p, s_p = torch.sigmoid(att_p)*np.pi*2-np.pi, torch.sigmoid(s_p)*np.pi*2-np.pi |
| att_a, s_a = abs(F.leaky_relu(att_a)), abs(F.leaky_relu(s_a)) |
|
|
| amp = raw2amp(att_a, dists) |
| phase = raw2phase(att_p, dists) |
|
|
| |
| |
| amp_i = torch.exp(torch.cumsum(amp, -1)) |
| phase_i = torch.exp(1j*torch.cumsum(phase, -1)) |
|
|
| recv_signal = torch.sum(s_a*torch.exp(1j*s_p)*amp_i*phase_i, -1) |
| recv_signal = torch.sum(recv_signal, -1) |
|
|
| return abs(recv_signal) |
|
|
|
|
|
|
|
|
| class Renderer_CSI(Renderer): |
| """Renderer for CSI (integral from all directions) |
| """ |
|
|
| def __init__(self, networks_fn, **kwargs) -> None: |
| """ |
| Parameters |
| ----------------- |
| near : float. The near bound of the rays |
| far : float. The far bound of the rays |
| n_samples: int. num of samples per ray |
| """ |
| super().__init__(networks_fn, **kwargs) |
|
|
|
|
|
|
| def render_csi(self, uplink, rays_o, rays_d): |
| """render the RSSI for each gateway. |
| |
| Parameters |
| ---------- |
| uplink: tensor. [batchsize, 52]. The uplink CSI (26 real + 26 imag) |
| rays_o : tensor. [batchsize, 3]. The origin of rays |
| rays_d : tensor. [batchsize, 9x36x3]. The direction of rays |
| """ |
|
|
| rays_d = rearrange(rays_d, 'b (v d) -> b v d', d=3) |
| batchsize, viewsize, _ = rays_d.shape |
| rays_o = repeat(rays_o, 'b d -> b v d', v=viewsize) |
| uplink = repeat(uplink, 'b d -> b v d', v=viewsize) |
|
|
| pts, t_vals = self.sample_points(rays_o, rays_d) |
| views = repeat(rays_d, 'b v d -> b v p d', p=self.n_samples) |
| uplink = repeat(uplink, 'b v d -> b v p d', p=self.n_samples) |
|
|
| |
| raw = self.network_fn(pts, views, uplink) |
| recv_signal = self.raw2outputs_signal(raw, t_vals, rays_d) |
|
|
| return recv_signal |
|
|
|
|
| def raw2outputs_signal(self, raw, r_vals, rays_d): |
| """Transforms model's predictions to semantically meaningful values. |
| |
| Parameters |
| ---------- |
| raw : [batchsize, chunks,n_samples, 4]. Prediction from model. |
| r_vals : [batchsize, chunks, n_samples]. Integration distance. |
| rays_d : [batchsize,chunks, 3]. Direction of each ray |
| |
| Return: |
| ---------- |
| receive_signal : [batchsize, 26]. OFDM singal of each ray |
| """ |
| wavelength = sc.c / 2.4e9 |
| |
| |
| raw2phase = lambda raw, dists: raw + 2*np.pi*dists/wavelength |
| raw2amp = lambda raw, dists: -raw*dists |
|
|
| dists = r_vals[...,1:] - r_vals[...,:-1] |
| dists = torch.cat([dists, torch.Tensor([1e10]).cuda().expand(dists[...,:1].shape)], -1) |
| dists = dists * torch.norm(rays_d[...,None,:], dim=-1) |
|
|
| att_a, att_p, s_a, s_p = raw[...,:26], raw[...,26:52], raw[...,52:78], raw[...,78:104] |
| att_p, s_p = torch.sigmoid(att_p)*np.pi*2-np.pi, torch.sigmoid(s_p)*np.pi*2-np.pi |
| att_a, s_a = abs(F.leaky_relu(att_a)), abs(F.leaky_relu(s_a)) |
|
|
| dists = dists.unsqueeze(-1) |
|
|
| amp = raw2amp(att_a, dists) |
| phase = raw2phase(att_p, dists) |
|
|
| |
| |
| amp_i = torch.exp(torch.cumsum(amp, -2)) |
| phase_i = torch.exp(1j*torch.cumsum(phase, -2)) |
|
|
| recv_signal = torch.sum(s_a*torch.exp(1j*s_p)*amp_i*phase_i, -2) |
| recv_signal = torch.sum(recv_signal, 1) |
|
|
| return recv_signal |
|
|
|
|
|
|
|
|
| renderer_dict = {"spectrum": Renderer_spectrum, "rssi": Renderer_RSSI, "csi": Renderer_CSI} |