Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

edwardoughton / globalsat / 137

7 Jul 2022 - 1:19 coverage: 99.286% (+4.2%) from 95.105%
137

Pull #33

travis-ci-com

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
Merge 92bc3e6b3 into 3928da1fb
Pull Request #33: Build out test coverage

11 of 12 new or added lines in 1 file covered. (91.67%)

139 of 140 relevant lines covered (99.29%)

0.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.29
/src/globalsat/sim.py
1
"""
2
Globalsat simulation model.
3

4
Developed by Bonface Osaro and Ed Oughton.
5

6
December 2022
7

8
"""
9
import math
1×
10
import numpy as np
1×
11
from itertools import tee
1×
12
from collections import OrderedDict
1×
13

14

15
def system_capacity(constellation, number_of_satellites, params, lut):
1×
16
    """
17
    Find the system capacity.
18

19
    Parameters
20
    ----------
21
    constellation : string
22
        Consetellation selected for assessment.
23
    number_of_satellites : int
24
        Number of satellites in the contellation being simulated.
25
    params : dict
26
        Contains all simulation parameters.
27
    lut : list of tuples
28
        Lookup table for CNR to spectral efficiency.
29

30
    Returns
31
    -------
32
    results : list of dicts
33
        System capacity results generated by the simulation.
34

35
    """
36
    results = []
1×
37

38
    distance, satellite_coverage_area_km = calc_geographic_metrics(
1×
39
        number_of_satellites, params
40
        )
41

42
    random_variations = generate_log_normal_dist_value(
1×
43
            params['dl_frequency'],
44
            params['mu'],
45
            params['sigma'],
46
            params['seed_value'],
47
            params['iterations']
48
        )
49

50
    for i in range(0, params['iterations']):
1×
51

52
        path_loss, random_variation = calc_free_space_path_loss(
1×
53
            distance, params, i, random_variations
54
        )
55

56
        antenna_gain = calc_antenna_gain(
1×
57
            params['speed_of_light'],
58
            params['antenna_diameter'],
59
            params['dl_frequency'],
60
            params['antenna_efficiency']
61
        )
62

63
        eirp = calc_eirp(params['power'], antenna_gain)
1×
64

65
        losses = calc_losses(params['earth_atmospheric_losses'], params['all_other_losses'])
1×
66

67
        noise = calc_noise()
1×
68

69
        received_power = calc_received_power(eirp, path_loss, params['receiver_gain'], losses)
1×
70

71
        cnr = calc_cnr(received_power, noise)
1×
72

73
        spectral_efficiency = calc_spectral_efficiency(cnr, lut)
1×
74

75
        channel_capacity = calc_capacity(spectral_efficiency, params['dl_bandwidth'])
1×
76

77
        agg_capacity = calc_agg_capacity(channel_capacity, params['number_of_channels'],
1×
78
                       params['polarization'])
79

80
        sat_capacity = single_satellite_capacity(params['dl_bandwidth'],
1×
81
                       spectral_efficiency, params['number_of_channels'],
82
                       params['polarization'])
83

84
        results.append({
1×
85
            'constellation': constellation,
86
            'number_of_satellites': number_of_satellites,
87
            'distance': distance,
88
            'satellite_coverage_area': satellite_coverage_area_km,
89
            'iteration': i,
90
            'path_loss': path_loss,
91
            'random_variation': random_variation,
92
            'antenna_gain': antenna_gain,
93
            'eirp': eirp,
94
            'received_power': received_power,
95
            'noise': noise,
96
            'cnr': cnr,
97
            'spectral_efficiency': spectral_efficiency,
98
            'channel_capacity': channel_capacity,
99
            'aggregate_capacity': agg_capacity,
100
            'capacity_kmsq': agg_capacity / satellite_coverage_area_km,
101
            'capacity_per_single_satellite': sat_capacity,
102
        })
103

104
    return results
1×
105

106

107
def calc_geographic_metrics(number_of_satellites, params):
1×
108
    """
109
    Calculate geographic metrics, including (i) the distance between the transmitter
110
    and reciever, and (ii) the coverage area for each satellite.
111

112
    Parameters
113
    ----------
114
    number_of_satellites : int
115
        Number of satellites in the contellation being simulated.
116
    params : dict
117
        Contains all simulation parameters.
118

119
    Returns
120
    -------
121
    distance : float
122
        The distance between the transmitter and reciever in km.
123
    satellite_coverage_area_km : float
124
        The area which each satellite covers on Earth's surface in km.
125

126
    """
127
    area_of_earth_covered = params['total_area_earth_km_sq']
1×
128

129
    network_density = number_of_satellites / area_of_earth_covered
1×
130

131
    satellite_coverage_area_km = (area_of_earth_covered / number_of_satellites) #/ 1000
1×
132

133
    mean_distance_between_assets = math.sqrt((1 / network_density)) / 2
1×
134

135
    distance = math.sqrt(((mean_distance_between_assets)**2) + ((params['altitude_km'])**2))
1×
136

137
    return distance, satellite_coverage_area_km
1×
138

139

140
def calc_free_space_path_loss(distance, params, i, random_variations):
1×
141
    """
142
    Calculate the free space path loss in decibels.
143

144
    FSPL(dB) = 20log(d) + 20log(f) + 32.44
145

146
    Where distance (d) is in km and frequency (f) is MHz.
147

148
    Parameters
149
    ----------
150
    distance : float
151
        Distance between transmitter and receiver in metres.
152
    params : dict
153
        Contains all simulation parameters.
154
    i : int
155
        Iteration number.
156
    random_variation : list
157
        List of random variation components.
158

159
    Returns
160
    -------
161
    path_loss : float
162
        The free space path loss over the given distance.
163
    random_variation : float
164
        Stochastic component.
165
    """
166
    frequency_MHz = params['dl_frequency'] / 1e6
1×
167

168
    path_loss = 20*math.log10(distance) + 20*math.log10(frequency_MHz) + 32.44
1×
169

170
    random_variation = random_variations[i]
1×
171

172
    return path_loss + random_variation, random_variation
1×
173

174

175
def generate_log_normal_dist_value(frequency, mu, sigma, seed_value, draws):
1×
176
    """
177
    Generates random values using a lognormal distribution, given a specific mean (mu)
178
    and standard deviation (sigma).
179

180
    Original function in pysim5G/path_loss.py.
181

182
    The parameters mu and sigma in np.random.lognormal are not the mean and STD of the
183
    lognormal distribution. They are the mean and STD of the underlying normal distribution.
184

185
    Parameters
186
    ----------
187
    frequency : float
188
        Carrier frequency value in Hertz.
189
    mu : int
190
        Mean of the desired distribution.
191
    sigma : int
192
        Standard deviation of the desired distribution.
193
    seed_value : int
194
        Starting point for pseudo-random number generator.
195
    draws : int
196
        Number of required values.
197

198
    Returns
199
    -------
200
    random_variation : float
201
        Mean of the random variation over the specified itations.
202

203
    """
204
    if seed_value == None:
1×
205
        pass
1×
206
    else:
207
        frequency_seed_value = seed_value * frequency * 100
1×
208
        np.random.seed(int(str(frequency_seed_value)[:2]))
1×
209

210
    normal_std = np.sqrt(np.log10(1 + (sigma/mu)**2))
1×
211
    normal_mean = np.log10(mu) - normal_std**2 / 2
1×
212

213
    random_variation  = np.random.lognormal(normal_mean, normal_std, draws)
1×
214

215
    return random_variation
1×
216

217

218
def calc_antenna_gain(c, d, f, n):
1×
219
    """
220
    Calculates the antenna gain.
221

222
    Parameters
223
    ----------
224
    c : float
225
        Speed of light in meters per second (m/s).
226
    d : float
227
        Antenna diameter in meters.
228
    f : int
229
        Carrier frequency in Hertz.
230
    n : float
231
        Antenna efficiency.
232

233
    Returns
234
    -------
235
    antenna_gain : float
236
        Antenna gain in dB.
237

238
    """
239
    #Define signal wavelength
240
    lambda_wavelength = c / f
1×
241

242
    #Calculate antenna_gain
243
    antenna_gain = 10 * (math.log10(n*((np.pi*d) / lambda_wavelength)**2))
1×
244

245
    return antenna_gain
1×
246

247

248
def calc_eirp(power, antenna_gain):
1×
249
    """
250
    Calculate the Equivalent Isotropically Radiated Power.
251

252
    Equivalent Isotropically Radiated Power (EIRP) = (
253
        Power + Gain
254
    )
255

256
    Parameters
257
    ----------
258
    power : float
259
        Transmitter power in watts.
260
    antenna_gain : float
261
        Antenna gain in dB.
262
    losses : float
263
        Antenna losses in dB.
264

265
    Returns
266
    -------
267
    eirp : float
268
        eirp in dB.
269

270
    """
271
    eirp = power + antenna_gain
1×
272

273
    return eirp
1×
274

275

276
def calc_losses(earth_atmospheric_losses, all_other_losses):
1×
277
    """
278
    Estimates the transmission signal losses.
279

280
    Parameters
281
    ----------
282
    earth_atmospheric_losses : int
283
        Signal losses from rain attenuation.
284
    all_other_losses : float
285
        All other signal losses.
286

287
    Returns
288
    -------
289
    losses : float
290
        The estimated transmission signal losses.
291

292
    """
293
    losses = earth_atmospheric_losses + all_other_losses
1×
294

295
    return losses
1×
296

297

298
def calc_received_power(eirp, path_loss, receiver_gain, losses):
1×
299
    """
300
    Calculates the power received at the User Equipment (UE).
301

302
    Parameters
303
    ----------
304
    eirp : float
305
        The Equivalent Isotropically Radiated Power in dB.
306
    path_loss : float
307
        The free space path loss over the given distance.
308
    receiver_gain : float
309
        Antenna gain at the receiver.
310
    losses : float
311
        Transmission signal losses.
312

313
    Returns
314
    -------
315
    received_power : float
316
        The received power at the receiver in dB.
317

318
    """
319
    received_power = eirp + receiver_gain - path_loss - losses
1×
320

321
    return received_power
1×
322

323

324
def calc_noise():
1×
325
    """
326
    Estimates the potential noise.
327

328
    Terminal noise can be calculated as:
329

330
    “K (Boltzmann constant) x T (290K) x bandwidth”.
331

332
    The bandwidth depends on bit rate, which defines the number
333
    of resource blocks. We assume 50 resource blocks, equal 9 MHz,
334
    transmission for 1 Mbps downlink.
335

336
    Required SNR (dB)
337
    Detection bandwidth (BW) (Hz)
338
    k = Boltzmann constant
339
    T = Temperature (Kelvins) (290 Kelvin = ~16 degrees celcius)
340
    NF = Receiver noise figure (dB)
341

342
    NoiseFloor (dBm) = 10log10(k * T * 1000) + NF + 10log10BW
343

344
    NoiseFloor (dBm) = (
345
        10log10(1.38 x 10e-23 * 290 * 1x10e3) + 1.5 + 10log10(10 x 10e6)
346
    )
347

348
    Parameters
349
    ----------
350
    bandwidth : int
351
        The bandwidth of the carrier frequency (MHz).
352

353
    Returns
354
    -------
355
    noise : float
356
        Received noise at the UE receiver in dB.
357

358
    """
359
    k = 1.38e-23 #Boltzmann's constant k = 1.38×10−23 joules per kelvin
1×
360
    t = 290 #Temperature of the receiver system T0 in kelvins
1×
361
    b = 0.25 #Detection bandwidth (BW) in Hz
1×
362

363
    noise = (10*(math.log10((k*t*1000)))) + (10*(math.log10(b*10**9)))
1×
364

365
    return noise
1×
366

367

368
def calc_cnr(received_power, noise):
1×
369
    """
370
    Calculate the Carrier-to-Noise Ratio (CNR).
371

372
    Returns
373
    -------
374
    received_power : float
375
        The received signal power at the receiver in dB.
376
    noise : float
377
        Received noise at the UE receiver in dB.
378

379
    Returns
380
    -------
381
    cnr : float
382
        Carrier-to-Noise Ratio (CNR) in dB.
383

384
    """
385
    cnr = received_power - noise
1×
386

387
    return cnr
1×
388

389

390
def calc_spectral_efficiency(cnr, lut):
1×
391
    """
392
    Given a cnr, find the spectral efficiency.
393

394
    Parameters
395
    ----------
396
    cnr : float
397
        Carrier-to-Noise Ratio (CNR) in dB.
398
    lut : list of tuples
399
        Lookup table for CNR to spectral efficiency.
400

401
    Returns
402
    -------
403
    spectral_efficiency : float
404
        The number of bits per Hertz able to be transmitted.
405

406
    """
407
    for lower, upper in pairwise(lut):
1×
408

409
        lower_cnr, lower_se  = lower
1×
410
        upper_cnr, upper_se  = upper
1×
411

412
        if cnr >= lower_cnr and cnr < upper_cnr:
1×
413
            spectral_efficiency = lower_se
1×
414
            return spectral_efficiency
1×
415

416
        highest_value = lut[-1]
1×
417

418
        if cnr >= highest_value[0]:
1×
419
            spectral_efficiency = highest_value[1]
1×
420
            return spectral_efficiency
1×
421

422
        lowest_value = lut[0]
1×
423

424
        if cnr < lowest_value[0]:
1×
425
            spectral_efficiency = lowest_value[1]
1×
426
            return spectral_efficiency
1×
427

428

429
def calc_capacity(spectral_efficiency, dl_bandwidth):
1×
430
    """
431
    Calculate the channel capacity.
432

433
    Parameters
434
    ----------
435
    spectral_efficiency : float
436
        The number of bits per Hertz able to be transmitted.
437
    dl_bandwidth: float
438
        The channel bandwidth in Hetz.
439

440
    Returns
441
    -------
442
    channel_capacity : float
443
        The channel capacity in Mbps.
444

445
    """
446
    channel_capacity = spectral_efficiency * dl_bandwidth / (10**6)
1×
447

448
    return channel_capacity
1×
449

450

451
def single_satellite_capacity(dl_bandwidth, spectral_efficiency,
1×
452
    number_of_channels, polarization):
453
    """
454
    Calculate the capacity of each satellite.
455

456
    Parameters
457
    ----------
458
    dl_bandwidth :
459
        Bandwidth in MHz.
460
    spectral_efficiency :
461
        Spectral efficiency 64QAM equivalent to 5.1152,
462
        assuming every constellation uses 64QAM
463
    number_of_channels :
464
        ...
465
    number_of_channels :
466
        ...
467

468
    Returns
469
    -------
470
    sat_capacity : ...
471
        Satellite capacity.
472

473
    """
474
    sat_capacity = (
1×
475
        (dl_bandwidth/1000000) *
476
        spectral_efficiency *
477
        number_of_channels *
478
        polarization
479
    )
480

481
    return sat_capacity
1×
482

483

484
def calc_agg_capacity(channel_capacity, number_of_channels, polarization):
1×
485
    """
486
    Calculate the aggregate capacity.
487

488
    Parameters
489
    ----------
490
    channel_capacity : float
491
        The channel capacity in Mbps.
492
    number_of_channels : int
493
        The number of user channels per satellite.
494

495
    Returns
496
    -------
497
    agg_capacity : float
498
        The aggregate capacity in Mbps.
499

500
    """
501
    agg_capacity = channel_capacity * number_of_channels * polarization
1×
502

503
    return agg_capacity
1×
504

505

506
def pairwise(iterable):
1×
507
    """
508
    Return iterable of 2-tuples in a sliding window.
509

510
    Parameters
511
    ----------
512
    iterable: list
513
        Sliding window
514

515
    Returns
516
    -------
517
    list of tuple
518
        Iterable of 2-tuples
519

520
    Example
521
    -------
522
    >>> list(pairwise([1,2,3,4]))
523
        [(1,2),(2,3),(3,4)]
524

525
    """
526
    a, b = tee(iterable)
1×
527
    next(b, None)
1×
528

529
    return zip(a, b)
1×
530

531

532
def calc_per_sat_emission(name, fuel_mass, fuel_mass_1, fuel_mass_2, fuel_mass_3):
1×
533
    """
534
    calculate the emissions of the 6 compounds for each of the satellites
535
    of the three constellations based on the rocket vehicle used.
536

537
    Parameters
538
    ----------
539
    name: string
540
        Name of the constellation.
541
    fuel_mass: int
542
        mass of kerosene used by the rockets in kilograms.
543
    fuel_mass_1: int
544
        mass of hypergolic fuel used by the rockets in kilograms.
545
    fuel_mass_2: int
546
        mass of solid fuel used by the rockets in kilogram.
547
    fuel_mass_3: int
548
        mass of cryogenic fuel used by the rockets in kilogram.
549

550
    Returns
551
    -------
552
    al, sul, cb, cfc, pm, phc: dict.
553
    """
554

555
    if name == 'Starlink':
1×
556
        emission_dict = falcon_9(fuel_mass)  # Emission per satellite
1×
557

558
    elif name == 'Kuiper':
1×
559
        fm_hyp, fm_sod, fm_cry = fuel_mass_1, fuel_mass_2, fuel_mass_3
1×
560
        emission_dict = ariane(fm_hyp, fm_sod, fm_cry)
1×
561

562
    elif name == 'OneWeb':
1×
563
        fm_hyp, fm_ker = fuel_mass_1, fuel_mass_2
1×
564
        emission_dict = soyuz_fg(fm_hyp, fm_ker)
1×
565

566
    else:
NEW
567
        print('Invalid Constellation name')
!
568

569
    return emission_dict
1×
570

571

572
def soyuz_fg(hypergolic, kerosene):
1×
573
    """
574
    Calculate the emissions of the 6 compounds for Soyuz FG rocket vehicle.
575

576
    Parameters
577
    ----------
578
    hypergolic : float
579
        Hypergolic fuel used by the rocket in kilograms.
580
    kerosene : float
581
        Kerosene fuel used by the rocket in kilograms.
582

583
    Returns
584
    -------
585
    my_dict : dict
586
        A dict containing all estimated emissions.
587

588
    """
589
    emissions_dict = {}
1×
590

591
    emissions_dict['alumina_emission'] = (hypergolic*1*0.001) + (kerosene*1*0.05)
1×
592

593
    emissions_dict['sulphur_emission'] = (hypergolic*0.7*0.001) + (kerosene*0.7*0.001)
1×
594

595
    emissions_dict['carbon_emission'] = (hypergolic*0.252*1) + (kerosene*0.352*1)
1×
596

597
    emissions_dict['cfc_gases'] = (hypergolic*0.016*0.7) + (kerosene*0.016*0.7) \
1×
598
                                  + (hypergolic*0.003*0.7) + (kerosene*0.003*0.7) \
599
                                  + (hypergolic*0.001*0.7) + (kerosene*0.001*0.7)
600

601
    emissions_dict['particulate_matter'] = (hypergolic*0.001*0.22) + (kerosene *0.001*0.22) \
1×
602
                                           + (hypergolic*0.001*1) + (kerosene*0.05*1)
603

604
    emissions_dict['photo_oxidation'] = (hypergolic*0.378*0.0456) + (kerosene *0.528*0.0456) \
1×
605
                                        + (hypergolic*0.001*1) + (kerosene*0.001*1)
606

607
    return emissions_dict
1×
608

609

610
def falcon_9(kerosene):
1×
611
    """
612
    calculate the emissions of the 6 compounds for Falcon 9 rocket vehicle.
613

614
    Parameters
615
    ----------
616
    kerosene: float
617
        Kerosene fuel used by the rocket in kilograms.
618

619
    Returns
620
    -------
621
    alumina_emission, sulphur_emission, carbon_emission, cfc,gases,
622
        particulate_matter, photo_oxidation: list.
623

624
    """
625
    emission_dict = {}
1×
626

627
    emission_dict['alumina_emission'] = (kerosene*0.05)
1×
628

629
    emission_dict['sulphur_emission'] = (kerosene*0.001*0.7)
1×
630

631
    emission_dict['carbon_emission'] = (kerosene*0.352*1)
1×
632

633
    emission_dict['cfc_gases'] = (kerosene*0.016*0.7) + (kerosene*0.003*0.7) \
1×
634
                                 + (kerosene*0.001*0.7)
635

636
    emission_dict['particulate_matter'] = (kerosene*0.001*0.22) + (kerosene*0.05*1)
1×
637

638
    emission_dict['photo_oxidation'] = (kerosene*0.0456*0.528) + (kerosene*0.001*1)
1×
639

640
    return emission_dict
1×
641

642

643
def falcon_heavy(kerosene):
1×
644
    """
645
    calculate the emissions of the 6 compounds for Falcon Heavy rocket vehicle.
646

647
    Parameters
648
    ----------
649
    kerosene: float
650
        Kerosene fuel used by the rocket in kilograms.
651

652
    Returns
653
    -------
654
    alumina_emission, sulphur_emission, carbon_emission, cfc,gases,
655
        particulate_matter, photo_oxidation: list.
656

657
    """
658
    emission_dict = {}
1×
659

660
    emission_dict['alumina_emission'] = kerosene*0.05
1×
661

662
    emission_dict['sulphur_emission'] = (kerosene*0.001*0.7)
1×
663

664
    emission_dict['carbon_emission'] = (kerosene*0.352*1)
1×
665

666
    emission_dict['cfc_gases'] = (kerosene*0.016*0.7) + (kerosene*0.003*0.7) \
1×
667
                                 + (kerosene*0.001*0.7)
668

669
    emission_dict['particulate_matter'] = (kerosene*0.001*0.22) + (kerosene*0.05*1)
1×
670

671
    emission_dict['photo_oxidation'] = (kerosene*0.0456*0.528) + (kerosene*0.001*1)
1×
672

673
    return emission_dict
1×
674

675

676
def ariane(hypergolic, solid, cryogenic):
1×
677
    """
678
    calculate the emissions of the 6 compounds for Ariane 5 space.
679

680
    Parameters
681
    ----------
682
    hypergolic: float
683
        Hypergolic fuel used by the rocket in kilograms.
684
    solid: float
685
        solid fuel used by the rocket in kilograms.
686
    cryogenic: float
687
        cryogenic fuel used by the rocket in kilograms.
688

689
    Returns
690
    -------
691
    alumina_emission, sulphur_emission, carbon_emission, cfc,gases,
692
        particulate_matter, photo_oxidation: list.
693

694
    """
695
    emission_dict = {}
1×
696

697
    emission_dict['alumina_emission'] = (solid*0.33*1) + (hypergolic*0.001*1)
1×
698

699
    emission_dict['sulphur_emission'] = (solid*0.005*0.7) + (cryogenic*0.001*0.7) \
1×
700
                                        + (hypergolic*0.001*0.7)+(solid*0.15*0.88)
701

702
    emission_dict['carbon_emission'] = (solid*0.108*1) + (hypergolic*0.252)
1×
703

704
    emission_dict['cfc_gases'] = (solid*0.08*0.7) + (cryogenic*0.016*0.7) \
1×
705
                                 + (hypergolic*0.016*0.7) + (solid*0.015*0.7) \
706
                                 + (cryogenic*0.003*0.7) + (hypergolic*0.003*0.7) \
707
                                 + (solid*0.005*0.7) + (cryogenic*0.001*0.7) \
708
                                 + (hypergolic*0.001*0.7) + (solid*0.15*0.7)
709

710
    emission_dict['particulate_matter'] = (solid*0.005*0.22) + (cryogenic*0.001*0.22) \
1×
711
                                          + (hypergolic*0.001*0.22) + (solid*0.33*1) \
712
                                          + (hypergolic*0.001*1)
713

714
    emission_dict['photo_oxidation'] = (solid*0.162*0.0456) + (hypergolic*0.378*0.0456) \
1×
715
                                       + (solid*0.005*1) + (cryogenic*0.001*1) \
716
                                       + (hypergolic*0.001*1)
717

718
    return emission_dict
1×
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2023 Coveralls, Inc