def _signal_electrons_in_well_dbe(self, model): E_sensor = model.energy_at_sensor_dbj E_pixel = E_sensor - pylink.to_db(model.spatial_channels) P_r = E_pixel + model.sensor_quantum_efficiency_db lambda_dbm = pylink.to_db(model.lambda_nm) - 90 h_db = pylink.to_db(model.plancks_constant) c_db = pylink.to_db(model.speed_of_light_m_s) return P_r + lambda_dbm - h_db - c_db
def _power_flux_at_lens_dbw_m2(self, model): # Approximate the pixel as a point source and apply spreading loss # The approsimate transmission gain is 3dB as lambertian reflectors D = model.slant_range_km * 1000 spreading_loss_db = pylink.to_db(math.pi * 2 * D**2) return (0.0 + model.reflected_power_dbw - spreading_loss_db - model.atmospheric_loss_db)
def _received_power_dbw(self, model): # Incident Power Flux Pi = model.power_flux_at_lens_dbw_m2 # Collecting Aperture Area A = pylink.to_db(math.pi * model.lens_radius_m**2) return Pi + A
def _energy_at_sensor_dbj(self, model): # Energy striking the primary lens P_lens = model.received_power_dbw + pylink.to_db(model.shutter_time_s) # Optical losses associated with inefficiencies # Losses associated with the focal plane sensor P_sensor = P_lens - model.optical_loss_db return P_sensor
def _atmospheric_loss_db(self, model): # Power incident on the atmosphere Pi = _interpolate(model.lambda_nm, model.orbital_solar_irradiance_w, 1) Pi = pylink.to_db(Pi) # Power transmitted through the atmosphere Pt = model.incident_power_flux_density_dbw_m2_nm return Pi - Pt
def test_spreading_loss_db(self): assert abs(pylink.spreading_loss_db(0.5e-3) - pylink.to_db(math.pi)) < 1e-3
def test_to_db(self): assert abs(pylink.to_db(10) - 10) < 1e-6 assert abs(pylink.to_db(2) - 3) < 0.1
] fig = plt.figure() ax = fig.add_subplot(1, 1, 1) x = [p[0] for p in restrictions] y = [p[1] for p in restrictions] plt.plot(x, y, color='r', linewidth=2, label='Limit') ax.set_xlabel('Elevation Angle (degrees)') ax.set_ylabel('Boresight PFD (dBW/m^2/%d%s)' % pylink.human_hz(4000)) x = np.linspace(10.0, 90.0) y = np.linspace(10.0, 90.0) for i in range(len(x)): m.override(e.min_elevation_deg, x[i]) pfd = m.pfd_dbw_per_m2_per_hz + pylink.to_db(4000) y[i] = pfd low = min(min(y), min([v[1] for v in restrictions])) hi = max(max(y), max([v[1] for v in restrictions])) delta = (hi - low) * 0.1 low -= delta hi += delta ax.set_ylim(low, hi) plt.plot(x, y) d = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'export') f = os.path.join(d, 'pfd.png')
def main(): m = linkBudgetConstants.DOWNLINK return m.max_bitrate_hz * m.allocation_hz # The same here with 'e' instead of 'enum'...'e' also doesn't # collide with the 'enum' package name. e = m.enum # First, we create the report object. Most of the interesting # stuff happens in the to_latex method. r = pylink.Report(m) # This example is designed to place the result in the export directory output_dir = os.path.join(os.getcwd(), 'export') output_path = os.path.join(output_dir, 'interference_analysis.tex') if not os.path.exists(output_dir): os.makedirs(output_dir) # We use a LaTeX import directive to include a complicated # introduction tex document, so we want to make sure we copy it # into the output directoy so that pdflatex will pick it up. # ITU-R SF.358-5 Lays out the maximum permissible PFD values for # various bands. limits = [(0, -115), (5, -115), (25, -105), (90, -105)] # The Canonical PFD figure is the 4kHz BW (though that is tunable # if you so desire) peak PFD at the Earth's surface (ie max xmit # power, peak antenna gain) as a function of elevation angle, # ignoring things like rain fade, since this figure is mostly # useful for compliance. title = 'Peak PFD at Surface (1MHz BW)' canonical_pfd = pylink.CanonicalPFDFigure(m, title=title, pfd_limits=limits) # Expected PFD is more complicated, as it assumes normal satellite # operations (ie Nadir pointing for example), and real antenna # patterns instead of peak gain, etc. title = 'Expected PFD at Surface (1kHz BW)' expected_pfd = pylink.ExpectedPFDFigure(m, title=title, pfd_limits=limits) # In some cases, it can be useful to produce graphs of peak PFD vs # bandwidth to show compliance with various limits. That's where # this class comes in handy. title = 'Peak PFD vs Bandwidth at Potential Victim GS' rx_bw_pfd = pylink.PFDvsBWFigure(m, start_hz=1e3, end_hz=5e3, is_gso=False, pfd_limits=None, title=title) title = 'Peak PFD vs Bandwidth at Potential Victim GSO' gso_bw_pfd = pylink.PFDvsBWFigure(m, start_hz=1e3, end_hz=5e3, is_gso=True, pfd_limits=None, title=title) # To make things clear to the regulators for whom this report is # being prepared, we're going to want to call out, specifically, # the PFD limit, as well as the max potential interference we # could cause. # # And before you say it, I know, the format for these added # sections aren't terribly clean...I'll add a ticket to clean this # up at some point. In the meantime, you have a working example # below. elevation = m.min_elevation_deg m.override(e.min_elevation_deg, 90) peak_pf = m.peak_pfd_dbw_per_m2_per_hz m.override(e.min_elevation_deg, elevation) extra_interference_sections = [('Hey Regulators, Look In this Section', [ ('Lowest Permissible PFD (4kHz BW) at Receiver', -150, 'dBW/m^2'), ('Peak Possible PFD (4kHz BW) at Receiver', peak_pf + pylink.to_db(4e3), 'dBW/m^2'), ])] # It may also be worth adding a couple of other random sections to # call out something necessary to the business case, or some other # computation. Note the way we escape the ampersand in the title # below. That's because, at the end of the day, a LaTeX file is # generated, and & is a reserved character. sub_ttc = ('Bitrate Requirements for TT\\&C', [ ('Number of Log Files', 1000, ''), ('Size of Each Log File', 16, 'kB'), ('Minimum Average Bitrate', 9600, 'kbps'), ]) sub_images = ('Bitrate Requirements for Images', [ ('Number of Images', 1000, ''), ('Size of Each Image', 32, 'MB'), ('Minimum Average Bitrate', 32, 'mbps'), ]) extra_regular_sections = [ ('Bitrates (Custom Section Example)', [sub_ttc, sub_images]), ] # Now that we've lined everything up, it's time to build some LaTeX r.to_latex( output_path, author='Tate Walker', intro='Link Budget Report for PPE Module of Gateway', expo='So long, and thanks for all the Fish!', rx_pfd_bws=[1e3, 4e3, 1e6], gso_pfd_bws=[1e3, 4e3, 1e6], bitrate_figure=pylink.BitrateFigure(m, 'Max Bitrate'), added_sections=extra_regular_sections, added_interference_sections=extra_interference_sections, )
# # And before you say it, I know, the format for these added # sections aren't terribly clean...I'll add a ticket to clean this # up at some point. In the meantime, you have a working example # below. elevation = m.min_elevation_deg m.override(e.min_elevation_deg, 90) peak_pf = m.peak_pfd_dbw_per_m2_per_hz m.override(e.min_elevation_deg, elevation) extra_interference_sections = [ ('Hey Regulators, Look In this Section', [('Lowest Permissible PFD (4kHz BW) at Receiver', -150, 'dBW/m^2'), ('Peak Possible PFD (4kHz BW) at Receiver', peak_pf + pylink.to_db(4e3), 'dBW/m^2'), ])] # It may also be worth adding a couple of other random sections to # call out something necessary to the business case, or some other # computation. Note the way we escape the ampersand in the title # below. That's because, at the end of the day, a LaTeX file is # generated, and & is a reserved character. sub_ttc = ( 'Bitrate Requirements for TT\\&C', [ ('Number of Log Files', 1000, ''), ('Size of Each Log File', 16, 'kB'), ('Minimum Average Bitrate', 9600, 'kbps'), ]
def __init__(self, orbital_solar_irradiance, ground_solar_irradiance, reflectance_db=pylink.to_db(0.5), fore_optics_efficiency_db=pylink.to_db(0.95), focusing_optics_efficiency_db=pylink.to_db(0.95), grating_efficiency_db=pylink.to_db(0.8), sensor_quantum_efficiency_db=pylink.to_db(0.8), read_out_noise_e=50, pixel_pitch_um=15, fwhm_nm=10, gsd_m=15, gmc=1, lambda_nm=1990, lens_radius_m=0.3, bits_per_sample=12, spatial_channels=640): self.tribute = { # Constants 'orbital_solar_irradiance_w': orbital_solar_irradiance, 'ground_solar_irradiance_w': ground_solar_irradiance, 'speed_of_light_m_s': 299792458, 'plancks_constant': 6.626068 * 10**-34, 'grav_const': 6.67 * 10**-11, 'earth_mass_kg': 5.98 * 10**24, 'reflectance_db': reflectance_db, 'fore_optics_efficiency_db': fore_optics_efficiency_db, 'grating_efficiency_db': grating_efficiency_db, 'focusing_optics_efficiency_db': focusing_optics_efficiency_db, 'sensor_quantum_efficiency_db': sensor_quantum_efficiency_db, 'read_out_noise_e': read_out_noise_e, 'pixel_pitch_um': pixel_pitch_um, 'fwhm_nm': fwhm_nm, 'gsd_m': gsd_m, 'gmc': gmc, 'lambda_nm': lambda_nm, 'lens_radius_m': lens_radius_m, 'bits_per_sample': bits_per_sample, 'spatial_channels': spatial_channels, # Calculators 'incident_power_flux_density_dbw_m2_nm': self._incident_power_flux_density_dbw_m2_nm, 'reflected_power_flux_density_dbw_m2_nm': self._reflected_power_flux_density_dbw_m2_nm, 'reflected_power_density_dbw_nm': self._reflected_power_density_dbw_nm, 'reflected_power_dbw': self._reflected_power_dbw, 'power_flux_at_lens_dbw_m2': self._power_flux_at_lens_dbw_m2, 'received_power_dbw': self._received_power_dbw, 'orbital_velocity_km_per_s': self._orbital_velocity_km_per_s, 'orbital_period_s': self._orbital_period_s, 'shutter_time_s': self._shutter_time_s, 'energy_at_sensor_dbj': self._energy_at_sensor_dbj, 'signal_electrons_in_well_dbe': self._signal_electrons_in_well_dbe, 'noise_electrons_dbe': self._noise_electrons_dbe, 'snr_db': self._snr_db, 'ground_area_dbm2': self._ground_area_dbm2, 'optical_loss_db': self._optical_loss_db, 'atmospheric_loss_db': self._atmospheric_loss_db, }
def _noise_electrons_dbe(self, model): N = (0.0 + model.read_out_noise_e + pylink.from_db(model.signal_electrons_in_well_dbe / 2)) return pylink.to_db(N)
def _reflected_power_dbw(self, model): return (1.0 + model.reflected_power_density_dbw_nm + pylink.to_db(model.fwhm_nm))
def _ground_area_dbm2(self, model): return pylink.to_db(model.spatial_channels * model.gsd_m**2)
def _incident_power_flux_density_dbw_m2_nm(self, model): PFD = max( _interpolate(model.lambda_nm, model.ground_solar_irradiance_w, 1), 1e-10) return pylink.to_db(PFD)