350 Commits

Author SHA1 Message Date
EstherLerouzic
7ce6650109 feat: move and update documentation on equipment types
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I0f85a059e2393d2d573938bd0804fe49596bbc2d
2025-01-30 17:23:18 +01:00
EstherLerouzic
252e67a71e fix: move amp documentation to the docs folder and update it
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ie6c207e3335cbf30b1f5858c21672dff420b9c51
2025-01-30 17:23:18 +01:00
EstherLerouzic
f83869392b feat: improve documentation of the scripts options
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ic68ded41b188cd07cf87f83e31e6d4eea5af5ed9
2025-01-30 17:23:18 +01:00
EstherLerouzic
94a3714aba fix: documentation missing the worker_utils section
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I832b0f0bfdd255396e6c9809273b1171d08c9f60
2025-01-30 17:23:18 +01:00
EstherLerouzic
ccab4835fc fix: Refactor the methods to avoid returning the same value
equipment being a dict, no need to use 'return' to have the changes
applied.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ic5a4247bbaa0b4af3fca5b6cb0a74a2f434b1b6a
2025-01-30 17:23:18 +01:00
EstherLerouzic
e55f7a5d4c Define default in common parts to be used both by cli and API
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I1e9c6aa99fd2896789c73340ccf5c8adf51a5f13
2025-01-30 17:23:18 +01:00
EstherLerouzic
4fda8c6002 use explicit file arguments for additional configs
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I133bb6a2d21d573cf819e1d92b1912dfa87dbfa4
2025-01-30 17:23:18 +01:00
EstherLerouzic
8717156712 feat: Read a list of optional extra equipement files
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ic521bbacd38b3bb60da3a364a069abfd1895d337
2025-01-30 17:23:18 +01:00
EstherLerouzic
d2c0836164 Remove default_edfa_config.json dictionary and use parameters.py
But enable the user to still input its own default file with a new
'default_config_from_json' attribute useable in fixed and variable gain
amplifiers.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I773682ae6daa1025007fc051582e779986982838
2025-01-29 18:27:51 +00:00
AndreaDAmico
eac4ba80ea List of collaborative PSE publications added in the docs
Change-Id: I1db6d9fe86004cd5bc8135577421117679cb9965
2025-01-24 08:49:48 +00:00
EstherLerouzic
4ef01d54a5 fix plot bug: do not overwrite the path used for plot
The plot function failed to recognize 'path' as part
of the network due to the reuse of the 'path' variable.
This led to errors when attempting to plot.

Solution is to use a different name for the deepcopy of
path elements used to record the propagation results
'propagated_path'.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I0351c37de0d74391ebeb68e974b777b1f51572aa
2025-01-10 11:08:04 +01:00
EstherLerouzic
4b50ee0c2d fix: do not assume 0 dB default value for tilt-target
Instead keep the None value, it user has not stated anything

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I45fcff92caabbfbe514fbe30deac60426b7eb16b
2024-12-06 16:35:46 +01:00
EstherLerouzic
33a289e22b fix: restore uid info in warning logs
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I2fdd29a4461b250661b1ccaa9737836fc3fe8695
2024-12-06 16:35:46 +01:00
EstherLerouzic
e593b8c9ec fix case where there are multiple multiband amps matching the sub amp type
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ibe86499866f2f9e3dfd70b51a33b919d584b812b
2024-12-06 16:35:46 +01:00
EstherLerouzic
94a6f922cd fix typing
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I0f1621b669b4db833d0760368cd834f3186ee2db
2024-12-06 16:35:46 +01:00
EstherLerouzic
fbe387915b fix: offset was not correctly taken into account on reversed path
TODO: write a test!

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I7ac909bb9b8c9700c3841f133245f17f49ba3467
2024-12-06 16:35:46 +01:00
EstherLerouzic
fce9d1d293 chore: refactor json_io
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: If764ba7b520a060deb855c0b55e17c78fa22f841
2024-12-06 16:35:46 +01:00
EstherLerouzic
a59db8fd12 fix: cli_examples linter issues
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Id7e2f8b7282913b885062a01f1bd018bbc85e39c
2024-12-06 16:35:46 +01:00
EstherLerouzic
de509139b3 fix: linter issues on json_io
add docstrings, typing, small fixes

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I01d0fabe5e34103077ec2de829e96829e6202e1e
2024-12-06 16:35:46 +01:00
EstherLerouzic
bb77b3f4a8 fix: remove unused _automatic_spacing
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ie3c2fc226f8a549622933cbd1ba8a6b8be213f92
2024-12-06 16:35:46 +01:00
EstherLerouzic
34c7fd1b60 fix: save autodesign file after autodesign!
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: If1c82c8cb7ff9dbb8bf5c2d5c4b96beaa59dc402
2024-12-06 16:35:46 +01:00
EstherLerouzic
89a962ffaf fix remove unnecessary else after return
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I61dc58f15c8f03a686437e19a36ac0afe35904e9
2024-12-06 16:35:46 +01:00
EstherLerouzic
1722fbec13 feat: add more warnings on amplifiers
when user settings do not match library

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Id387b7781d9637f1d18c453dae75330962229902
2024-12-06 16:35:46 +01:00
EstherLerouzic
e48aa57c35 Improve error reporting by including uid of elements
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ief4125322e4db02765974c43159a014749cdab2e
2024-12-06 16:35:46 +01:00
EstherLerouzic
e3e37b1986 feat: skip path computation when path is explicit
and add tests for explicit path

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I95aaf5b56a7ea04f24153d5cb6612cd09401401c
2024-12-06 16:35:46 +01:00
EstherLerouzic
abf42afaf8 fix ci: The macOS-12 environment is deprecated
remove this check and add one for The macOS-14 instead

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ief1c990cbc67ec4d7912404c24d67f9f2fa6d96e
2024-12-06 16:35:46 +01:00
EstherLerouzic
310995045e fix: linter issues in convert and service_sheet
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I5f7490ed61b53cea8d2923a8a54d38b3fbb5fa0a
2024-12-06 16:35:43 +01:00
EstherLerouzic
c840bb1a44 Improve test coverage on ila constraint cases
explicitely check the corrections for all cases
ila defined in eqpt or not,
ila defined on the link with same direction as request or not
constraint loose or strict
several or one ila in the OMS

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I4d4b5167e7327c9aea4b13879f4e00d30e60d643
2024-11-25 17:07:36 +01:00
EstherLerouzic
4b6f4af3a5 Refactor to reduce cognitive complexity
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ia9992ebbe06498ed53b3987edd4eb139d960ff75
2024-11-25 17:07:33 +01:00
EstherLerouzic
dc68d38293 fix ila names
auto design were changed long ago and these functions did not
apply the changes. Besides there was a confusion between request_element
class where loose is a string, and PathRequest from topology.requests
where loose_list is a list of strings.
This patch corrects the naming and also the tests,
because it used the wrong class to gererate xls services

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I564b77576459d6cb47767398a2db8138ba6ad1e4
2024-11-25 16:55:25 +01:00
EstherLerouzic
defe3bee5c feat: documentation for ROADM excel sheet input
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ie662015c9cd0a90aff46c63fce47d678ffe1d4db
2024-11-25 12:09:14 +01:00
EstherLerouzic
32adc0fc53 feat: enables reading per degree impairment from xls input
- read per degre roadm-path impairment from roadm sheet
add additional optional columns: type_variety and 'from degrees'
and 'from degree to degree impairment id'
'from degrees' can contain a list of degrees separated with ' | ', then the
'from degree to degree impairment id' must contain a list of ids of the same
length.
Impairment ids are expected to be in the ROADM equipment spec and
from degree must be the previous node (no verification of user input).

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I7d326bb3d4f366835249089e9537747c7d3ec2fd
2024-11-25 12:09:09 +01:00
EstherLerouzic
4796266657 fix bug in roadm to_json: move per_degree_impairments in params
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I75ed610f201608a3cb651e8c0604de444113bc25
2024-11-25 08:55:00 +01:00
EstherLerouzic
c35104c184 Add documentation for multiband
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Iadf6d9edd8c67c1389c4a0d482466a8c52198621
2024-11-21 09:26:51 +01:00
AndreaDAmico
7b1354ee24 fix: avoid using cumtrapz from scipy
cumtrapz has been replaced with cumulative_trapezoid in the scipy version currently required by GNPy.

Change-Id: I6790f7aa8d8e8d171faa48db4b20e6a93141c471
2024-11-15 22:25:27 +00:00
AndreaDAmico
39d3f0f483 Perturbative Raman Solver
Raman effect evaluation based on a perturbative solution for faster computation

Change-Id: Ie6d4ea4d9f95d8755dc8dfd004c954d4c2c5f759
2024-11-08 19:55:20 +00:00
AndreaDAmico
bbe9ef7356 Increasing precision in Raman tests
Change-Id: I7a4de449a673d2e2ac23376d7fe64399c65e1246
2024-11-08 18:12:49 +00:00
AndreaDAmico
42a8f018cd GGN approximation formula defined
An approximated version of the GGN is implemented to reduce the computational time enabling fast multi-band transmission simulations

Change-Id: I2951a878aa04b5eb4a33ba86d626a788c4cbb100
2024-11-08 18:09:44 +00:00
EstherLerouzic
29f5dd1dc4 Add frequency dependency on ROADM impairments
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Icb88bf9c42c09deb0064e3299b78b080462fef79
2024-10-16 17:49:00 +00:00
EstherLerouzic
03da959724 insert multiband_amplifier if needed
the automatic add_missing_elements function is updated to insert
multiband booster, preamp or inline amplifiers based on the OMS
nature. If nothing is stated, then uses Edfas.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I968a2fc0a3da97aecb7b513ff211491b20cdd4f2
2024-10-16 17:47:29 +00:00
EstherLerouzic
f621ca6fe7 Add tilt computation for design targets
Compute the tilts only if raman-flag in sim_params is turned on.
Use actual input power in fiber (according to expected propagation
during design).
Creates a function that computes the expected tilt after propagation
in a span, and returns the normalized power difference at the center
frequency of each band, and the tilt experenced between lower and
upper frequency in each band.
Include the expected tilt when computing target gains of amplifiers.
Current function requires that the bands remain in the same order.
(ordering is ensured when creating the objects).

Change-Id: I28bdf13f2010153175e8b6d199fd8eea15d7b292
2024-10-16 17:46:59 +00:00
EstherLerouzic
24f4503020 Preselect multiband amplifiers based on band gain and power targets
To ensure that the multiband amplifier meets the required gain and
power targets for each band, this commit introduces a preselection
process for the amplifiers type variety. The preselection ensures
that only compatible amplifiers are considered, avoiding that
an amplifier is selected for one band that is not part of the same
multiband amplifier type variety of the amplifier selected for the
other band(s).

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I8de7e0b7c165e6edfe47d7f4cda80db23924a9c9
2024-10-16 17:46:34 +00:00
EstherLerouzic
520c3615e4 Refactor select_edfa
Objective is to reuse the functions that lists suitable type variety candidates
for each amplifier of the multiband amplifier

restrictions are processed before entering select_edfa.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I8c784cef6bfed9a2e95bc39434a32191678db81f
2024-10-16 17:40:29 +00:00
EstherLerouzic
548626a9f2 preselect amplifiers based on restrictions and bands
make sure that selected amplifiers (single or multiband) have a band
that encompasses design_band.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I8b66755efbe8413f32328b9e02099ffdedd4b7ed
2024-10-16 17:40:01 +00:00
EstherLerouzic
7a26833a5a Add some test on select_edfa
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ia54c62fe76175f16048992ca1f67439dd32c6e5e
2024-10-16 17:39:39 +00:00
EstherLerouzic
c2f6f9c6a0 Add an invocation test with multiband case
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I0a98175f0c6b4333fae6bea40dac826032a25233
2024-10-16 17:39:13 +00:00
EstherLerouzic
64a91256fc Propagate power per band during autodesign
Target setting computation is done going through each element of the OMS
and computing resulting delta power after each amplifier element. In order
to account for different delta power per band (multi band autodesign), the
computation must be made per band. The previous introduction of a standard
name for bands ("CBAND", "LBAND") ensures a stable key to index these
delta power computation.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ida4b2486ebde4f2a1fb21a44458d1fe34a788d1f
2024-10-16 17:37:35 +00:00
EstherLerouzic
bdcffc2a5e Refactor: define a separate function to compute targets
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I5698feb059f13b90c1ab3d0843fb000c4e0b6b59
2024-10-16 17:36:52 +00:00
EstherLerouzic
c384af8062 Refactor: create a function to set one single band amplifier
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I00018c38b0cc0ceefcd21d50dd0cdc639019cc70
2024-10-16 17:36:25 +00:00
EstherLerouzic
0813332adc Enable differentiated design band per OMS
Introduce a design_band parameter in ROADM and Transceiver.
- if nothing is defined, use SI band(s)
- if design band is defined in ROADM, use this one for all degrees
- if per degree design band is defined, use this one instead

unsupported case: single band OMS with default multiband design band.
Check that these definitions are consistent with actual amplifiers

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ibea4ce6e72d2b1e96ef8cf4efaf499530d24179c
2024-10-16 17:31:33 +00:00
EstherLerouzic
22fe9ead55 Introduce multi band amps
Introduce a new multi-band element that contains a list of Edfa element:
- reads multiple amps out of the element config.
- deduces frequency band from the amp in the list.

no autodesign yet: multi-band amps must have type_variety.

- checks that type variety of individual EDFAs is consistent with multiband
type variety
- demux and mux spectrum when propagate in multiband
- don't add a preamp or booster if a multiband amp is already defined.

The print of channel number is removed from equipment, since the channel number
may now depend on the path's amplifiers. This changes invocation results layout.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I44e77ff82e622cdee4021a7984d660317cb90cf9
2024-10-16 17:26:11 +00:00
EstherLerouzic
920ac30aa5 Refactor and simplify network functions
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ifa0949a8b7739036639e1a4b006ceb08804558ce
2024-10-16 17:24:52 +00:00
EstherLerouzic
ac8fd770ab Only propagates carriers that belong to Amp bandwidth
The commit introduces mux/demux functions in amps and ensures that the
propagation is only done on carriers that are in the Amp bandwitdh, ie
with all their spectrum including slot width is in bandwidth.

For consistency, default amp f_min is changed:
Objective is to use amplifiers' band to bound the possible frequencies
to be propagated. Since the current default f_min of Amp in json_io.py is
higher than the SI one, this would result in a different nb of channels
than currently used in tests, and a change in all tests. In order to
avoid this, I preferred to change this value and have consistency
between SI f_min and Amp f_min.

The commits adds a set of functions to make amps band the useable
spectrum on each OMS. Thee OMS generation is changed to use the amp band.

The commit adds filtering functions (demux and mux) to filter out spectrum
which is not in the amplifier band.

Spectrum assignment is also corrected to correctly match the amp bandwidth
constraint with guardband: center frequency index must be within the
usable part of the amp band. This changes a bit the notion of freq_index
and guardband in the functions, but this is transparent to user:
f_min, f_max represent the amp band, while self.freq_index_min/max
represent the center frequency boundary for a reference 50GHz channel.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I225b2b2dc0e1f1992c0460f6e08fa9c9bc641edf
2024-10-16 17:16:21 +00:00
EstherLerouzic
5277ae2005 Add a redesign option
redesign True means that network is redesigned using each request
as reference channel. When False it means that the design is made
once and successive propagation use the settings computed with this
design.

Default propogation is without redesign, so that path-request-script
must use the ----redesign-per-request option to behave as before this
commit.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I0084185106d14659a846136620cd17791d551a7d
2024-10-16 17:15:20 +00:00
EstherLerouzic
30ead40e76 Creates a set of functions to be called by CLI and API
Instead of copying the CLI script in API code, use functions shared
by CLI and API

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I3f9b30b8700b68237d0e80768db015d8dec3deb5
2024-10-16 17:13:25 +00:00
EstherLerouzic
ae858b911a fix: capture warning to show the ROADM uid
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ie13bf4c3436a5a0b8ec730698920eee2c7fb81e8
2024-10-16 17:10:34 +00:00
EstherLerouzic
0d236fd31e fix: remove unused invocation test file
Wrongly added by commit #4a071c5

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I337b8d4560cd7f2634fca7d242783da327127de5
2024-10-16 16:42:09 +00:00
EstherLerouzic
9a84e29433 fix: remove freq2wavelength that is already defined
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I8ef480460e689bfcb33116cd393ad92ed0e03338
2024-10-08 11:07:13 +00:00
‘Renato
143f63170e FIX: json indentation in example-data
Change-Id: If3585fc554638cb1e5f7e4564d10af29420b6159
2024-09-20 13:49:26 +00:00
EstherLerouzic
b2d7f883a1 Add documentation for topology, service and sim-params files
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I0d917b742acd91aa52403f1ac96a378bb3cd8497
2024-06-02 19:26:33 +02:00
EstherLerouzic
73dbdf3042 Add documentation for the roadm impairment feature
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ief7e79ef10edf098c49ab1a0164284e5ae604961
2024-06-02 19:26:33 +02:00
EstherLerouzic
4a071c53d7 feat: transform roadm-paths into list indexed with frequency band
to be conformed with ietf + to prepare for next multiband case

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: If71857ef7dff9eaaa4c16e3837d3500bcef2fa72
2024-06-02 19:26:33 +02:00
EstherLerouzic
dcde64a8db clean some leftover from previous refactor
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ibd6e32090155433d7b1aa5b6572f1fe07a4cbe4a
2024-06-02 19:26:33 +02:00
EstherLerouzic
38cc0e3cc5 feat: separate span power from tx power
gnpy currently uses the same parameter for tx output power and span
input power: this prevents from modelling low tx power effect.
This patch introduces a new tx-cannel-power and uses it to
propagate in ROADM.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Id3ac75e2cb617b513bdb38b51a52e05d15af46f5
2024-06-02 19:26:33 +02:00
EstherLerouzic
fb70413784 Refactor equipment and add some tests
This fixes error message for wrong trx type,  catches the case of
KeyError when trx_type is not part of the library.

removes power setting from this function: power out of transceiver or
at the input of span is nor defined in equipment Transceiver

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I15fa7cc772ab5c1a8c7637738eb83c2ddffa1219
2024-06-02 19:26:33 +02:00
EstherLerouzic
87e10c240e Add test on blocking due to PDL penalty
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I2d6a7f9ed0ff8ad6977bcfe1b78ed620bb0e848d
2024-06-02 19:26:33 +02:00
EstherLerouzic
43c1085be6 feat: apply per path_type ROADM OSNR
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I2b0838c9a217a7f918b2cf08c233aacdbf686a72
2024-06-02 19:26:33 +02:00
EstherLerouzic
4ace60bea2 Feat: apply roadm-path loss
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I87e27d5b653bdce814f43e4c9c1183fb51fbcc1e
2024-06-02 19:26:33 +02:00
EstherLerouzic
f950a6aee8 Feat: add detailed ROADM impairments per roadm-path
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I09c55dcff53ffb264609654cde0f1d8b9dc7fe9b
2024-06-02 19:26:33 +02:00
EstherLerouzic
fb4195c775 Feat: Enable multiple type_varieties for ROADM
This commit introduces the 'type_variety' attribute for ROADM elements,
allowing the use of different types of ROADM specifications instead of
being limited to the default one.

If no type variety name is provided in the eqpt_config, the 'default'
name is used for backward compatibility with libraries. Additionally,
if no type variety is defined in the ROADM element in the topology,
the default one is used for backward compatibility with topologies.

The 'type_variety' attribute is included in the 'to_json' and
'display' methods for ROADM elements.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I61a2491f994e47ad0b08cf8eaef30d6d855aa706
2024-06-02 19:26:33 +02:00
EstherLerouzic
29f42666e5 remove whites spaces and align parentheses
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I0f5f389a3cf20155ceb0e9d9f071d1334a5ad688
2024-06-02 19:26:33 +02:00
EstherLerouzic
9bf7f336e3 Update release notes of v2.9
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ib949ff81fa818886a69339117bc66290dc2685b0
2024-06-02 19:26:27 +02:00
EstherLerouzic
eed6564f11 Add power sweep functionality description in documentation
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ic8db566bc168f120ddb0016c91bcc1d24263548c
2024-06-02 19:17:07 +02:00
EstherLerouzic
fbb2f2c587 fix missing to_json export of computed_number_of_channels
The parameter was introduced in commit 9736f7c032
but the export to_json was not added.

In the next commits, it is necessary to compute Raman gain during the design
phase. The sim-params used for this computation are updated during the
design phase for speed reasons. To ensure the proper restoration of user
settings for propagation, the export must include all parameters. Therefore,
this commit adds 'computed_number_of_channels' to the JSON export. This allows
for the accurate recording of all user settings locally and enables their
restoration.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I221a6f614010edea9cf46c3a7d43c5be064ff09c
2024-06-01 19:17:50 +02:00
EstherLerouzic
44040c4d06 fix missing description of computed_number_of_channels
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I5c8d057dcdab535617eee8de3eccdd806cec403e
2024-06-01 09:11:56 +02:00
EstherLerouzic
ee9af69558 Improve doc to state when tilt is vs wavelength
add some text in the docs to explain that tilt can be expressed
vs freq or lambda depending on context:
advanced_model expresses dgt as a function of frequency,
while tilt target is still defined vs wavelength (common usage).

Change the variable to have explicit name when it is per wavelength,
or add a comment to help identifyper wavelength or per frequency
variables.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I7727f00b38244152b95954e981cc9da096bb3d1d
2024-05-24 07:11:02 +00:00
Esther Le Rouzic
ce21609fec Merge "fix: image of the script and documentation" 2024-05-22 14:24:26 +00:00
Esther Le Rouzic
a1289e6a9b Merge "feat: enable different sim_param vectors for multiple requests" 2024-05-22 14:19:35 +00:00
AndreaDAmico
138115e1d7 Frequency scaling description in release notes v2.8
Change-Id: I74f3d52a3cbc087b914ff18075869d2162b693ac
2024-05-16 01:23:27 -04:00
EstherLerouzic
ed41305f55 fix: image of the script and documentation
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ib022320efd36a2d912f7b443686d7fee137e48b1
2024-04-26 18:19:53 +02:00
EstherLerouzic
9736f7c032 feat: enable different sim_param vectors for multiple requests
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ia800a7b98b33b795cc3553500116be61c612e45c
2024-04-26 17:12:43 +02:00
EstherLerouzic
be7ae35db3 Refactor amp default in parameters
default parameters are shared between json and network function,
so it is better to have them on the parameters to avoid circular
dependency when importing modules

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ib9d41852e394586d36f74992c91f67f3330cc552
2024-04-25 17:51:56 +02:00
EstherLerouzic
2b4a4ab72c fix Raman gain estimation during design
- Replaced multiple calls to the span_loss function
  with recording the span loss result in the fiber elements,
  reducing computation time.
- Updated Raman gain estimation based on design target powers to ensure
  accurate Edfa gain calculation or gain reduction during design.
- display the computed and design Raman gain in RamanFiber string output
- do not add padding on Raman fibers
- Added to_json function to preserve user input SimParams values,
  which were previously overwritten by initializing SimParams
  with fake values during design.

Next step is to allow users to balance computation time and
target accuracy of the design by inputing their own SimParams
and ref channels design values.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I1ca4954d0621858cefb3d776a538131992cae3e3
2024-04-12 08:58:16 +02:00
EstherLerouzic
426c88432d fix: update README script animation
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I31381ddf3e372f34836416162c080a5205ef969d
2024-04-12 08:41:27 +02:00
AndreaDAmico
2a800b781f Bug fix: Raman coefficient properly scaled in non SSMF case
Before the Raman coefficient was normalized with respect the given effective area, instead of the reference.

Change-Id: I4c0547db4fbd0f823a9058022b93c1ca37d67b51
2024-04-11 01:21:02 -04:00
Jan Kundrát
8d1d3677ed docs: fix graphs on RTD
At OFC I was talking with the OpenROADM people, and it turned out that
our docs stopped rendering properly at RTD. It turned out that the build
started skipping the bindep.txt file at some (unknown) point in time.

Reported-by: Aparaajitha G L <aparaajitha.gomathinayakamlatha@utdallas.edu>
Change-Id: Ie9a4b61f36fb979fb5c109d02de06e0b2cbf270e
2024-03-29 19:23:08 +01:00
Jan Kundrát
5b6f8c60cf docs: use the default theme on ReadTheDocs.org as well
Historically, we've been using the RTD theme on the RTD site which hosts
the docs for us, and a Sphinx-default, "Alabaster" theme for other docs
builds. Doing that however started failing:

 Traceback (most recent call last):
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/builders/html/__init__.py", line 1096, in handle_page
     output = self.templates.render(templatename, ctx)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/readthedocs_ext/readthedocs.py", line 181, in rtd_render
     content = old_render(template, render_context)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/jinja2glue.py", line 194, in render
     return self.environment.get_template(template).render(context)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/jinja2/environment.py", line 1301, in render
     self.environment.handle_exception()
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/jinja2/environment.py", line 936, in handle_exception
     raise rewrite_traceback_stack(source=source)
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/themes/basic/page.html", line 10, in top-level template code
     {%- extends "layout.html" %}
     ^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/themes/classic/layout.html", line 10, in top-level template code
     {%- extends "basic/layout.html" %}
     ^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/themes/default/../basic/layout.html", line 170, in top-level template code
     {%- block content %}
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/themes/default/../basic/layout.html", line 189, in block 'content'
     {%- block sidebar2 %}{{ sidebar() }}{% endblock %}
     ^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/themes/default/../basic/layout.html", line 189, in block 'sidebar2'
     {%- block sidebar2 %}{{ sidebar() }}{% endblock %}
     ^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/jinja2/sandbox.py", line 393, in call
     return __context.call(__obj, *args, **kwargs)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/jinja2/runtime.py", line 777, in _invoke
     rv = self._func(*arguments)
          ^^^^^^^^^^^^^^^^^^^^^^
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/themes/default/../basic/layout.html", line 63, in template
     {%- include sidebartemplate %}
     ^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/jinja2glue.py", line 215, in get_source
     raise TemplateNotFound(template)
 jinja2.exceptions.TemplateNotFound: about.html

 The above exception was the direct cause of the following exception:

 Traceback (most recent call last):
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/cmd/build.py", line 281, in build_main
     app.build(args.force_all, args.filenames)
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/application.py", line 347, in build
     self.builder.build_update()
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/builders/__init__.py", line 310, in build_update
     self.build(to_build,
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/builders/__init__.py", line 376, in build
     self.write(docnames, list(updated_docnames), method)
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/builders/__init__.py", line 571, in write
     self._write_serial(sorted(docnames))
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/builders/__init__.py", line 581, in _write_serial
     self.write_doc(docname, doctree)
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/builders/html/__init__.py", line 672, in write_doc
     self.handle_page(docname, ctx, event_arg=doctree)
   File "/home/docs/checkouts/readthedocs.org/user_builds/gnpy/envs/499/lib/python3.12/site-packages/sphinx/builders/html/__init__.py", line 1103, in handle_page
     raise ThemeError(__("An error happened in rendering the page %s.\nReason: %r") %
 sphinx.errors.ThemeError: An error happened in rendering the page about-project.
 Reason: TemplateNotFound('about.html')

 Theme error:
 An error happened in rendering the page about-project.
 Reason: TemplateNotFound('about.html')

I have no clue what that means because we have never requested this
`about.html`, nor do we reference that file from anywhere. Chances are
that it's "just" some version pinning/compatibility issue, but hey --
why mess with that when there's a perfectly good default theme that
we're using for other purposes already.

As a side effect, this also solves that long-standing issue that Esther
reported where the tables have overly long lines. Apparently, it's a
theme-specific misfeature (readthedocs/sphinx_rtd_theme/#117), and the
Alabaster one doesn't suffer from that.

All hail alabaster!

Change-Id: I857890f29f14b7c0f66bca201c9a9c1b1cbf8841
2024-03-13 21:30:20 +01:00
Jan Kundrát
3a733b1fd5 docs: try to unbreak the readthedocs.io build
It was failing with a message:

  Config validation error in build.os. Value os not found.

Apparently, the v2 config file is mandatory, so let's do that.

Change-Id: I267d5314db026de532b2b6644f500d25de08e343
2024-03-13 21:30:20 +01:00
Jan Kundrát
2d68b94a46 build: specify dependencies directly in setup.cfg
In tox v4, "reuse of environments" was disabled [1]. This is then later
explained [2] to refer to exactly that thing which we were using for
inheriting the dependencies from the top-level testenv all the way to
the docs build. That's why the docs build in GitHub CI started failing.

IMHO, The Correct Way™ of specifying what dependencies are used for
which feature are the so-called "extra dependencies". Once they are in
place, it is be possible to install gnpy via, e.g., `pip install
oopt-gnpy[docs]`. However, this process is (as far as I can tell)
incompatible with `requirements.txt`; all my attempts at using the
standard dependency syntax in that file have failed for me.

So, in order to make this happen, let's move all the dependencies from a
more-or-less ad-hoc collection of files to this declarative approach
right in setup.cfg. That way, the deps are listed on a single place, in
a declarative manner, and as a result, the installation is now a trivial
pip oneliner.

As a result, one can also remove that duplication of dependencies in
docs requirements.

[1] https://tox.wiki/en/4.11.4/upgrading.html#reuse-of-environments
[2] https://tox.wiki/en/4.11.4/upgrading.html#packaging-configuration-and-inheritance

Change-Id: I34aa0c71e993b39e2b805a7de40e133b4d290318
Fixes: 47c89626 fix docs requirements
2024-03-13 21:28:01 +01:00
EstherLerouzic
bc71823bd0 docs: add a release-note section and update documentation for penalties and SI
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I12a5747df3cee6df79c24dd6261f7be17aa77fcf
2024-02-09 18:59:40 +01:00
EstherLerouzic
5481b93728 Fix frequency scaling for fiber
- wrong parameter was used in parameter
- error message could not read 0-dimensional arrey for 0 and -1 element
- add a test that makes use of the feature

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Id7f6d6766d5b91a4b9410ad23aaa5e472b8ebb6f
2024-01-16 09:10:31 +00:00
EstherLerouzic
05e301182d Change fedora-python in action
"Until version 0.4, this action always used the
latest fedora-python-tox image"
https://github.com/fedora-python/tox-github-action

So let's use one that supports python 3.8

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ibf3e0baa715da70b4c2af6e2cde6efccfab50311
2024-01-15 20:02:13 +01:00
EstherLerouzic
47c89626e3 fix docs requirements
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I9d151b842a38380b0368e099c67957ec36b78250
2024-01-15 13:53:44 +01:00
EstherLerouzic
7a032a63b5 ci change allow_whitelist which is deprecated
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ic62050d351eb5bb2f8be2b8d9e0088bd965dc71d
2024-01-15 13:12:35 +01:00
EstherLerouzic
f195d5f496 fix: use ref power on transceiver to Roadm (or transceivers) links
The recent refactor removed a default pref in case of transceivers-OMS
(amplified links starting with a transceiver).
This resulted in a mismatch between input power during design
(default 0 forced in the function) and the design ref power using SI
power_dbm.

This change ensures that the same power is used for the input power
and for the design ref power, and avoid inconsistent gain computatiion.

The code has been using the same power input (SI power_dbm) to define the
power target out of a transceiver and the target out of amplifiers
(at the input of fibers). This will be changed in a future patch.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I610c8df19039bcf156a8ba77c79114b22913a538
2023-12-08 12:27:05 +01:00
Esther Le Rouzic
56569f866f Merge changes from topics "mixed-rate", "refactor_remove_pref"
* changes:
  Add a test on EOL
  add invocation test with the 3 equalization settings
  Add a test on out_voa optimisation function
  Clean a bit, add docstrings
  Remove Pref, and move ref_carrier definition
  Remove p_span0 from SI
  Remove p_spani from Pref
  Use design delta_p and gains instead of p_spani
  restore initial power sweep behaviour
  refactor cli to use a common design function
  Parametrize verbose in autodesign
  refactor build_network: create a separate function to add elements
  Computes reference input power in fiber during design
  Computes reference input power in ROADM during design
  Add a variable to hold delta_p even if gain mode is selected
  Add frequency range in default_edfa profile
  Add a test on gain mode behaviour
  Check element setting before and after propagation
  Correct design: apply saturation in all cases
  Add more tests on amp saturation
2023-12-04 16:09:37 +00:00
Esther Le Rouzic
bf1f293043 Merge "Add test in amplifier behaviour" 2023-12-04 16:08:14 +00:00
Esther Le Rouzic
28871c6f2d Merge pull request #480 from jktjkt/python-3.12
CI: Python 3.12 and extended platform coverage
2023-11-23 17:54:02 +01:00
EstherLerouzic
d7c1a6b75e Add a test on EOL
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Iddce655a64623a42cdaeaa2e8c269e3a737dd935
2023-11-20 17:07:53 +01:00
EstherLerouzic
c69c2a3af2 add invocation test with the 3 equalization settings
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I0aee8da7bbf71991c68e163c7188efe1ddf29ff9
2023-11-20 17:07:53 +01:00
EstherLerouzic
fb29d72906 Add a test on out_voa optimisation function
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I36d71d85e5837965f6d5ae47820506d06b3cb94e
2023-11-20 17:07:53 +01:00
EstherLerouzic
30a06da6b1 Clean a bit, add docstrings
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I8639d458ebb090761846387921f9da4fc65a9f64
2023-11-20 17:07:53 +01:00
EstherLerouzic
139c8cc1e7 Remove Pref, and move ref_carrier definition
Finally, ref_carrier is not meant to change after design since
it is the carrier used for design. So let's move its definition
to networks function. Only ROADM need the ref_carrier baud rate
so let's define a dedicated variable in ROADM to hold it.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ida7e42dd534a04c8df8792b44980f3fd2165ecb6
2023-11-20 17:07:53 +01:00
EstherLerouzic
7034d4c686 Remove p_span0 from SI
reference channel is defined during design. No need to convey it
anymore during propagation.

move target_pch_out_db definition to the design phase and change
its name to be consistent with what it contains (dbm)

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I350e4557e8488a614674042de26152ab89b2d245
2023-11-20 17:07:53 +01:00
EstherLerouzic
10164495b9 Remove p_spani from Pref
next step: remove Pref

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I7cc17253a2d7ab3fb42e3d07c1665991cffa6222
2023-11-20 17:07:53 +01:00
EstherLerouzic
87211b35e9 Use design delta_p and gains instead of p_spani
Remove the visualisation of the effective_pch in amp because actual
and target are the relevant ones. effective_pch was artificially
related to a mix of reference channel and noisy channel (mixed between
on the fly redesign but using actual ROADM equalisation which includes noise
in its actual loss).

the change does no more rely on the target power (which is rounded)
but on the designed gain, which is not rounded.

Propagations are slightly changed for openroadm simulations because of that.
(I verified)

The gain of amp was estimated on the fly with p_spni also in case of
RamanFiber preceding elements. removing p_spani requies that an estimation
of Raman gain be done during design.

This commit also adds this estimation.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I960b85e99f85a7d168ac5349e325c4928fa5673b
2023-11-20 17:07:46 +01:00
EstherLerouzic
e9f9ddb4d6 restore initial power sweep behaviour
if user define a delta_p that is reduced because of saturation,
then this initial setting is still kept for power sweep to be sure
that the full amplitude of sweep is used.

SI power = 0 dBm
max power amp1 = 20 dBm,
user_defined_delta_p set by user = 3
80 channels, so pch_max = 20 - 10log10(80) = 0.96 dBm
power_sweep -> power range [-3, 0] dBm

then for initial design,
   pref = 0 dBm
   computed_delta_p =
      min(pch_max, pref + user_defined_delta_p) - pref = 0.96

but for -3 power sweep
   pref = -3 dBm
   computed_delta_p =
      min(pch_max, pref + user_defined_delta_p) - pref =
      min(0.96,    -3 + 3) - (-3) = 3
   so the user defined delta_p is applied as much as possible

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I8fd459c29aa9754ff9d4868af1d8be8642a31913
2023-11-20 11:10:07 +01:00
EstherLerouzic
8ea13bb4d6 refactor cli to use a common design function
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I029d8c7fc29b1e86e1e3b2b64933bae5da134226
2023-11-20 10:27:43 +01:00
EstherLerouzic
b45829d2df Parametrize verbose in autodesign
transmission-main-example and path-request-run functions implement an
on-the-fly redesign based on p_span_i.
Since we remove p_span_i from elements, we will need to properly call
redesign several times before each propagation, to keep the same
behaviour of these functions.

in this commit we simply enable the possibility to mute warnings.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I3aa3d8fc87325033ef69641078bdd7213e0409eb
2023-11-20 10:27:41 +01:00
EstherLerouzic
6ac3a517cf refactor build_network: create a separate function to add elements
separate function that adds element, from function that configure them

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ica332223bdf7fc599cb007d7513d7cd62d9c5f9c
2023-11-20 10:25:07 +01:00
EstherLerouzic
2f2920a716 Computes reference input power in fiber during design
input power is computed at design time: so let's record it and
use it instead of p_span_i for reference channel fiber loss computation.
Note that this loss parameter is only used for visualisation purpose.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I16bd792bd6079ce521aafadcf5e21922aa3b4c81
2023-11-20 10:23:21 +01:00
EstherLerouzic
07fd89351b Computes reference input power in ROADM during design
input power is computed at design time: so let's record it and
use it instead of p_span_i for ROADM reference channel loss computation.
Note that this loss parameter is only used for visualisation purpose.
No impact on propagation.

Since this loss is computed for the reference channel used for
design, we need to record input power based on input degrees,
and indicate this information within the call function.

Note that this will be also usefull later on to implement ROADM
parameters

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I64d510fc20df72f07158f400964d592d76dc0ce4
2023-11-20 10:23:21 +01:00
EstherLerouzic
7c60b000b5 Add a variable to hold delta_p even if gain mode is selected
Let's use a clean convention to hold values that are configured,
autodesigned or resulting from propagation.

- edfa.operational.delta_p: holds the value set by the user if any.
  This is needed in case of redesign for power sweep for example.
  It is never changed.
- edfa.delta_p:
  o if power_mode is true, records the value computed by the design.
      Applies user defined value except:
      If the user has set non possible values (eg leading to saturation),
      then the value is corrected at design phase.
      If the element is propagated for different conditions than
      design, for example leading to saturation,  then delta_p might be
      different than the value initially computed during design.
  o if power_mode is False, it is set to None
- edfa._delta_p: records the value computed during design whatever
  the power mode

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I4e130a3abe0a5e3f6c057d89360e50531c168123
2023-11-20 10:23:21 +01:00
EstherLerouzic
537eb017b5 Add frequency range in default_edfa profile
This range is the property of amps and is independant from user propagation range.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ib89f1987910aa3121a3b8c859a0a785f7d5e27eb
2023-11-20 10:23:21 +01:00
EstherLerouzic
9c514e8086 Add a test on gain mode behaviour
This test checks that setting in gain mode forces amp to the gain settings
and ignores any power requirements. Change in SI in eqpt config and change
in req power (eg power sweep) should have no effect on the propagation.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Iad826f30010fe3110d105b5206d99f502fbf98ff
2023-11-20 10:23:21 +01:00
EstherLerouzic
78efb6c650 Check element setting before and after propagation
In power mode, all elements design attributes should not change except
amplifiers' gain in case of power saturation.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I2fec00232c80dd395e4dec20ec531c9c2e127760
2023-11-20 10:23:18 +01:00
EstherLerouzic
3510d59250 Correct design: apply saturation in all cases
Previously saturation was not checked during design if amp type was set.
This commit also applies saturation for these amplifiers.

This changes some of the autodesign result (since range for selection
is changed). For example, this changes some of the gains, or type variety
of amplifier of test files.

The commit also removes one of the rounding in the design phase, and
applies rounding only for printing purpose.

It also adds minor refactor on a function

In order to keep power sweep behaviour in case of saturation, the saturation
check in amplifier element uses initial power targets set by user instead
of a possible autodesign delta_p result.

Note that gain_mode is unchanged: design in gain mode means that delta_p
is set to None during the build process, even if the user defined a value
in operational.delta_p.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Idc5cfc8263cf678473acb6ec490207d9d6ba5c0a
2023-11-20 10:21:38 +01:00
EstherLerouzic
41d9d156a6 Add more tests on amp saturation
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ibba18bed646748d59cfe906b403a9b100c58bb7e
2023-11-20 10:21:35 +01:00
EstherLerouzic
e9d5e748e4 Add test in amplifier behaviour
Check that amp correctly applies saturation, when there is tilt.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I3e7623e9d5b28bdc12eae24766588645781c2827
2023-11-20 10:21:06 +01:00
EstherLerouzic
5a5bed56c2 Add test on _check_one_request function
Add the call to the function, and creates additional test cases to raise the
different situations related to spectrum slots:
- M value too small
- total nb of channels too small
- overlapping

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I7ab3923deef2ff154ee1be21dcaeb3d9e4b84375
2023-11-17 16:26:12 +01:00
EstherLerouzic
22de1b1281 Add tests on aggregation
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I5409d847657fbe14f7963ff56546d0bedbf6c941
2023-11-17 16:26:12 +01:00
EstherLerouzic
73e1485b47 aggregate demands with defined mode and spectrum
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Id9fc2e0fe6f6ff5a3996700f6db7dfa6222dc3ca
2023-11-17 16:26:12 +01:00
EstherLerouzic
22ee05ea6f Add more tests for multiple slots spectrum assignment
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Id773f0f14cfe80b7ebcf07370170ad425faf0919
2023-11-17 16:23:48 +01:00
EstherLerouzic
31824f318d Enable multiple slots assignment
list of slot may include (N, M) values such as
(int, uint>0)
(int, None)
(None, uint>0)
(None, None)

Demands will be splitted into requested slots according to first fit strategy.
For example, if request is for 32 slots corresponding to 8 x 4slots 32Gbauds
channels, the following frequency slots will result in the following
assignments:
example 1
N = 0, 8,    16, 32           -> 0,   8,   16,   32
M = 8, None, 8,  None         -> 8,   8,    8,    8
example 2
N = 0,    8,    16, 32        -> 0,   , 16
M = None, None, 8,  None      -> 24,  , 8

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ice9bb4b5700d23bcf30db25aa4882e74853169ac
2023-11-17 16:23:48 +01:00
EstherLerouzic
b0cb604e91 Remove old commented code
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Idd2fcca0fe757eb801ab575953828c6df0521bb4
2023-11-17 16:23:48 +01:00
EstherLerouzic
79102e283a Refactor function to simplify the process
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I362d23a969c338ccd70caecc4e59e991d2a8d8a2
2023-11-17 16:23:48 +01:00
EstherLerouzic
db5e63d51b Refactor spectrum selection function
Cut some functions into smaller pieces to be easily re-used afterwards.
This step to prepare multiple slots assignment.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: If0fa2df7f6174e54405f92a57d60289d560c1166
2023-11-17 16:23:48 +01:00
EstherLerouzic
af42699133 Enable the loading of a bitmap
OMS are currently built with a brand new spectrum bitmap using f_min, f_max,
guard band and grid values. This changes enables to load an existing bitmap.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: If0547bc337c863a3510ad9e43928e6f64701d295
2023-11-17 16:23:48 +01:00
EstherLerouzic
4ba77d0a0a Change rq.N and rq.M from scalar to list
Prepare for the next step, to be able to handle lists of candidate assignment

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I2bd78606ce4502f68efb60f85892df5f76d52bb5
2023-11-17 16:23:48 +01:00
EstherLerouzic
064d3af8e0 Remove line number from invocation logs
line numbers are useful for debugging, but the benefit does not
compensate for the painful update of lines in files at each commit
that changes line numbers.
So I have removed those lines only for the test_invocation logs case.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ic1f628d80b204a9a098f3902ebdfd10b480c7613
2023-11-17 11:56:06 +01:00
AndreaDAmico
4ab5bac45f EDFA Parameters restructuring
The parameters of the EDFA are explicitely retrieved in the EDFAParams class.
All the defaults are set instead in the gnpy.tool.json_io.AMP class.
Where required, the AMP.default_values are used instead of an empty dictionary.

Change-Id: Iba80a6a56bc89feb7e959b54b9bd424ec9b0bf06
Co-authored-by: Vittorio Gatto <vittoriogatto98@gmail.com>
2023-11-17 09:08:00 +01:00
AndreaDAmico
bbe5fb7821 Chromatic Dispersion scaling along frequency
The chromatic dispersion and dispersion slope can be provided as a single values evaluated at the fiber reference frequency or in a dictionary containing the dispersion values evaluated at multiple frequencies:
"dispersion": {"value": [], "frequency": []}

Change-Id: I81429484dd373cc49bd9baf013247782ba1912fd
2023-11-17 09:04:44 +01:00
AndreaDAmico
edf1eec072 Nonlinear coefficient scaling along frequency
The nonlinear coefficient can be expressed at the reference frequency and will be scaled in frequency using the scaling rule of the effective area

Change-Id: Id103b227472702776bda17ab0a2a120ecfbf7473
2023-11-17 08:53:58 +01:00
AndreaDAmico
88ac41f721 Seprating the eta matrix evaluation in compute nli
The evaluation of the eta matrix is reintroduced for nli evaluation and validation purposes. Also, the parameters for cut and pump are separated explicitelly.

Change-Id: Id3844fa8ba41a5d4f5a72d281d758136ee983f45
2023-11-17 08:51:35 +01:00
AndreaDAmico
c20e6fb320 Effective Area and Raman Gain Coefficient Scaling
1. Effective area scaling along frequency is implemented by means of a technological model.
2. Raman gain coefficient is extended coherently, including the scaling due to the pump frequency.

Change-Id: I4e8b79697500ef0f73ba2f969713d9bdb3e9949c
Co-authored-by: Giacomo Borraccini <giacomo.borraccini@polito.it>
2023-11-17 08:51:26 +01:00
Jan Kundrát
05500c7047 CI: run tests on Apple M1 CPUs as well (64bit ARM)
Note that on GitHub, this currently targets a "runner" that's behind a
paywall. TIP does have a payment setup in place AFAICT, so we make sure
that this job does not run on forks.

Change-Id: I50c556424d86a1ce47e59911b9e39f336df34ce5
2023-11-15 20:37:26 +01:00
Jan Kundrát
86a39f4b5e packaging: mark Python 3.12 as supported
Change-Id: If3c8ea7d5a7651b71379a71e5dfde6b464aa5b4a
2023-11-15 20:06:55 +01:00
Jan Kundrát
2b25609255 CI: test on Python 3.12 and some new platforms
Change-Id: Ice5c3ca21245c4ac87cb2bf4f0fd062596615a2e
2023-11-15 20:06:55 +01:00
Jan Kundrát
7e0b95bcfd Bump all dependencies
Change-Id: Id08b7722880b992b1bb70f53ad243d4f40ffe387
2023-11-15 20:06:54 +01:00
Jan Kundrát
f0a52dcc8a tests: upgrade pandas
There are no binary wheels for Python 3.12 prior to pandas v2.1.1. Our
previous pin requested the 1.x branch, and that resulted in building
Pandas from source, which takes time. We cannot pin to 2.1.1 because
they removed support of Python 3.8 in 2.1, so 2.0.3 it is.

Change-Id: Ia9a567e54f4dda19a0a6b67d0c295a9a079892de
2023-11-15 20:05:54 +01:00
EstherLerouzic
3bea4b3c9f Feat: improve sanity check for eqpt sheet
add cases of wrong sheets that were not captured and
generate errors later in propagation:
- if the eqpt line is duplicated
- if the eqpt contains a link that is not present in Links sheet,
  but Nodes are OK
- if the same link is defined but with opposite directions
- if the ila is defined twice with opposite directions
- if the service type or mode are not in the library

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I4715886e19f07380bf02ed0e664559972bb39b71
2023-11-02 10:14:03 +01:00
EstherLerouzic
f2cc9f7225 Add more logs
and test them

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I05ffc3a75354fa8d8f3a668973ab7f4cbcfa1a98
2023-11-02 10:01:38 +01:00
Esther Le Rouzic
e79f9f51b6 Merge "Feat: add offset power option for transceivers" 2023-10-31 08:30:13 +00:00
Esther Le Rouzic
7fd7f94efe Merge "Refactor error message" 2023-10-31 08:29:58 +00:00
Esther Le Rouzic
0acdf9d9f6 Merge "docs: rename the Matrix channel" 2023-10-27 15:09:16 +00:00
EstherLerouzic
a3edb20142 Feat: add offset power option for transceivers
Offset power is used for equalization purpose to correct for the
generic equalization target set in ROADM for this particular transceiver.
This is usefull to handle exception to the general equalization rule.
For example in the case of constant power equalization, the user might
want to apply particular power offsets unrelated to slot width or baudrate.
or in constant PSW, the user might want to have a given mode equalized for
a different value than the one computed based on the request bandwidth.

For example consider that a transceiver mode is meant to be equalized with
75 GHz whatever the spacing specified in request. then the user may specify
2 flavours depending on used spacing:

  service 1 : mode 3, spacing 75GHz
  service 2 : mode 4, spacing 87.5Ghz
avec
  {
    "format": "mode 3",
    "baud_rate": 64e9,
    "OSNR": 18,
    "bit_rate": 200e9,
    "roll_off": 0.15,
    "tx_osnr": 40,
    "min_spacing": 75e9,
    "cost": 1
  }

  {
    "format": "mode 4",
    "baud_rate": 64e9,
    "OSNR": 18,
    "bit_rate": 200e9,
    "roll_off": 0.15,
    "tx_osnr": 40,
    "min_spacing": 87.5e9,
    "equalization_offset_db": -0.67,
    "cost": 1
  }

then the same target power would be considered for mode3 and mode4
despite using a different slot width

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I437f75c42f257b88b24207260ef9ec9b1ab7066e
2023-10-24 13:20:00 +02:00
EstherLerouzic
33cc11b85c Refactor error message
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ie8fc6bedcdbce26e2e80759c6c56d2c7429bf560
2023-10-24 13:20:00 +02:00
Jan Kundrát
5d079ab261 docs: rename the Matrix channel
It seems that the Matrix server at `foss.wtf` disappeared some time ago
with no details posted anywhere. I've marked the long-existing channel
alias `#oopt-gnpy:matrix.org` as the primary one, so let's adjust the
docs so that new people can actually join this channel.

This should have no consequences on people who have already joined.

Change-Id: Idee9c050ff5cb1c3926e5d4cf751002ad1541e71
2023-10-03 01:50:11 +02:00
AndreaDAmico
a3b1157e38 Fiber latency calculation
Fiber latency evaluated during propagation. The speed of ligth in fiber is evaluated as the vacuum speed of ligth  divided by the core reflective index n1.
The latency in the transceiver is evaluated in ms.

Change-Id: I7a3638c49f346aecaf1d4897cecf96d345fdb26c
2023-08-07 18:29:03 +02:00
AndreaDAmico
70731b64d6 fix: include position of lumped losses in Raman profile
In the previous version, the position of the lumped losses were not
included in the result Raman profile. As the latter is then used to
evaluate the NLI, including the lumped loss positions is required for
accurate estimations.

Change-Id: I683f48ceb7139d1a8be03d2e7ca7e3abffecbe85
2023-07-24 17:13:15 +02:00
AndreaDAmico
4ea0180caf tests: prefer pandas.read_csv over numpy.genfromtext
Change-Id: Icc9618afc4cad0c7a07f3a785c99b6b438e0c6cc
2023-07-24 17:12:25 +02:00
AndreaDAmico
eb2363a3d4 Fix: lumped losses included in total fiber loss
In previous version, the lumped losses where not included in the fiber loss, creating an inaccurate overall power balance.

Change-Id: I98a4d37b9cc0526218fe3c6f2b9318b6fa797901
2023-07-06 15:19:07 +02:00
EstherLerouzic
41b94cc888 fix: don't crash if PMD, PDL or CD penalties are missing in transceivers
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Iafc248af3ecfcd4da4c1135fd3a37da796cdfb5f
2023-05-09 10:11:26 +02:00
Jan Kundrát
1eeb6a0583 Merge changes Icd0b4fbd,I3ca81bcd,Ia33315f0
* changes:
  docs: docstring formatting
  SimParams: less boilerplate
  python: prefer isinstance(foo, Bar) over type(foo) == Bar
2023-04-18 23:01:30 +00:00
Jan Kundrát
215c20e245 Merge "fix: add missing PSW case for power computation" 2023-04-18 00:41:45 +00:00
Jan Kundrát
76e9146043 docs: docstring formatting
Let's use the pythonic indenting, quoting and structure in general as
specified in PEP 0257.

Change-Id: Icd0b4fbd94dabd9a163ae3f6887b236e76c486ab
2023-04-18 01:34:19 +02:00
Jan Kundrát
2a07eec966 SimParams: less boilerplate
The code look as if it was trying to prevent direct instantiation of the
SimParams class. However, instance *creation* in Python is actually
handled via `__new__` which was not overridden. In addition, the
`get()` accessor was invoking `SimParams.__new__()` directly, which
meant that this class was instantiated each time it was needed.

Let's cut the boilerplate by getting rid of the extra step and just use
the regular constructor.

This patch doesn't change anything in actual observable behavior. I
still do not like this implicit singleton design pattern, but nuking
that will have to wait until some other time.

Change-Id: I3ca81bcd0042e91b4f6b7581879922611f18febe
2023-04-17 23:06:31 +02:00
Jan Kundrát
cc994bf118 python: prefer isinstance(foo, Bar) over type(foo) == Bar
Use of isinstance() is more Python and it also allows inheritance to
work properly.

Change-Id: Ia33315f0e3faf6638334bec85d0fa92ea8ac81f0
2023-04-17 23:02:51 +02:00
EstherLerouzic
37e70e622c fix: add missing PSW case for power computation
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I5fa9135cdde1735ec142bc88d8fdf0aa03b13a41
2023-04-13 16:48:21 +02:00
Florian FRANK
7d9a508955 Fix 2 minor typos in docs/model.rst
Signed-off-by: Florian FRANK <florian1.frank@orange.com>
Change-Id: Ic2160f554b120b011c941aca36b69a0f032cf45f
2023-04-13 09:33:19 +02:00
Florian FRANK
185adabd77 Fix bug of comparison dimension when Raman is allowed and loss_coef is a vector instead of a scalar
Signed-off-by: Florian FRANK <florian1.frank@orange.com>
Change-Id: I0b39d102b9200ec25ed62e6f53b1e0addcc66f67
2023-04-11 17:30:24 +02:00
Jan Kundrát
8f9cf8ccc7 docs: sync the author list from git history
I don't have a script for this because it requires some manual fixups.

Change-Id: I19f36b953c98d6bc0c09040c27b964b288360c0e
2023-03-06 01:31:41 +01:00
Sami Alavi
0c797a254c simplify type annotations
PEP 484 says that `float` also implicitly allows `int`, so there's no
need to use `Union[int | float]`.

Fixes: #450
Change-Id: Ib1aeda4c13ffabd47719c1e0886e9ebcf21a64e0
2023-03-03 21:12:57 +05:00
Jan Kundrát
2cdeeabfa6 Mark Python 3.11 as supported
Change-Id: I2dedd942c92959e1f891194f6234376b9ecad6e5
2023-03-02 14:28:12 +01:00
Jan Kundrát
5e874798cb CI: GitHub: add builds on 3.11
...and also switch various jobs to use that by default.

Change-Id: I9170fc305bfd9bea6b5dde5741f912c6ed455e3e
2023-03-02 14:28:12 +01:00
Jan Kundrát
ff8f044064 Merge changes from topic "mixed-rate"
* changes:
  complete tests with the --power option tests
  Add Roadm uid when raising error
  add equalization per constant ratio power/slot_width
2023-02-14 09:59:20 +00:00
Jan Kundrát
d84ee4e76c Merge "doc: add a link to our public chat room" 2023-02-07 17:09:15 +00:00
Jan Kundrát
521d27ffac docs: fix a nasty typo
Fixes: b1067a62 docs: flexgrid
Change-Id: I44613d8ef4a27e7791db81509a56efa7ee29b4ff
2023-02-07 00:36:14 +01:00
Jan Kundrát
35e759212e doc: add a link to our public chat room
Change-Id: Id5323ad01ff0705efb9c9335e2c1f61227e5b73b
2023-02-02 17:29:19 +01:00
Jan Kundrát
f6dede2b5f docs: remove LGTM.com code-quality badge
...which no longer works since they got acquired by GitHub. Setting up
that CodeQL will need a bit more time I'm afraid.

Change-Id: I2f2bf21d31df643b25e931b2aadf60406bba683b
2023-02-02 17:28:40 +01:00
Jan Kundrát
0d0019f627 Update my e-mail address
I was informed that my TIP-specific e-mail address won't be coming back.

Change-Id: Ic2ee4986203490d90143a89dc49d7fca71a84c73
2023-02-02 17:13:23 +01:00
EstherLerouzic
06fe1c2f63 complete tests with the --power option tests
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ia7be6b86b82cc0317a5ba48086ef63f67d490990
2023-01-30 18:05:41 +01:00
EstherLerouzic
092316a9d7 Add Roadm uid when raising error
in case parameters are not correct, catch the ParameterError
and raises it again with the uid of the ROADM to ease debug

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I1f85f0e9e9226fc613d35611774c739adb2104c7
2023-01-30 18:05:37 +01:00
EstherLerouzic
48e3f96967 add equalization per constant ratio power/slot_width
Constant power per slot_width uses the slot width instead of
baud rate compared to PSD.

This is the equalization used in OpenROADM

add tests for constant power per slot width equalization

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ie350e4c15cb6b54c15e418556fe33e72486cb134
2023-01-30 18:03:58 +01:00
Jan Kundrát
e9e8956caf docs: fix the GitHub CI (actions) badge
Bug: https://github.com/badges/shields/issues/8671
Change-Id: I9cb15b762710cae7c3c37ed95d08aee2ca7b2457
2023-01-18 22:57:17 +01:00
Jan Kundrát
0ae341c2a5 tests: update to flake8 v5
flake8-html is now compatible with the v5 of this package, so let's use
it. Unfortunately, they killed the `--diff` option in v6, so we cannot
use it right now. I understand the reasoning as well as the fact that
it's easy to be broken, but I don't like broken CI that much.

Change-Id: I70dd686e097f411c39bfc53f83d519540687dd64
2023-01-18 22:25:48 +01:00
Jan Kundrát
0c2f6372f8 tests: switch to PEP517-compliant build process
...mainly to be in sync with oopt-gnpy-libyang that I've been working on
recently, and to allow us to modernize this infrastructure later on.

Change-Id: Id0ed1d7620762fc204300ebe8a190de8e42ae9df
2023-01-18 22:20:39 +01:00
Jan Kundrát
97e80b4445 Merge changes from topic "enable-multiple-slots-assignment"
* changes:
  record request_id as string, not integer
  support missing trx_mode in request instead of null value
2023-01-18 21:20:03 +00:00
Jan Kundrát
5e4c9b7d73 Merge "Respect fiber max_length when splitting fibers" 2023-01-18 21:19:39 +00:00
Jan Kundrát
e96f821cce CI: Switch to Fedora 36
...because Vexxhost pulled the plug on the F35 mirroring infrastructure,
and as a result, all jobs started failing.

Change-Id: Ib5d795397e907de3eff6cdb9c4145353400793ab
Depends-on: https://review.gerrithub.io/c/Telecominfraproject/oopt-zuul-jobs/+/548583
2023-01-18 21:35:08 +01:00
Jan Kundrát
5f7e61e255 CI: temporarily remove Fedora 35 jobs
...since the hosting provider pulled the plug on the mirroring
infrastructure. See the rest of these commits in this serie for
details.

Change-Id: Iac1d5f1c6f557458194deafe441564afc4851d94
2023-01-18 21:32:11 +01:00
Jonas Mårtensson
682b5c5691 Respect fiber max_length when splitting fibers
According to the documentation, auto-design will
"Split fiber lengths > max_length", which also makes sense based on the
name of the parameter but with the existing implementation fiber
length could still be longer than max_length after splitting. This
patch makes auto-design respect the specified max_length.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: Ifd83aa4d77206bf10796579df73632fe405e2d54
2023-01-18 11:59:06 +00:00
Jan Kundrát
11e5117505 tests: do not compare floating point numbers for equality
GitHub CI started failing with the following error:

  assert (watt2dbm(si.signal) == target - correction).all()
  assert False
   +  where False = <built-in method all of numpy.ndarray object at 0x7f01c0ca94d0>()
   +    where <built-in method all of numpy.ndarray object at 0x7f01c0ca94d0> = array([-25.5, -24.5, -22.5, -25. , -27.5]) == array([-25.5, -24.5, -22.5, -25. , -27.5])
        +array([-25.5, -24.5, -22.5, -25. , -27.5])
        -array([-25.5, -24.5, -22.5, -25. , -27.5])
        Full diff:
          array([-25.5, -24.5, -22.5, -25. , -27.5]).all

This is with code which has passed in the Zuul/Vexxhost CI.

It looks very similar to a regression that hit numpy 1.24.0, but the
GitHub action log shows that this happens with numpy 1.24.1. Weird, and
I'm not getting these differences locally, and also not on an ARM64
cloud VM.

Anyway, comparing floating point numbers for strict equality is futile,
so let's use this opportunity to use a proper check for these.

Change-Id: I05683f3116cad78d067bddde2780fe25b5caf768
2023-01-18 00:27:53 +01:00
EstherLerouzic
50603420fc ROADM: rework equalization
On a ROADM, the code would previously set the same per-carrier power
regardless of the channel spectrum width. With this patch, carriers are
equalized either by their:

- absolute power (same as before),
- power spectral density (PSD).

Also, it's possible to apply a per-channel power offset (in dB) which
will be applied to a specified channel on top of the selected
power-level or PSD strategy. The same offset can be also selected
through the `--spectrum` option via the `default_pdb` parameter.

The equalization policy can be set via the ROADM model (in the equipment
config) as well as on a per-instance basis.

The PSD is defined as the absolute power over a spectral bandwidth,
where the spectral bandwidth corresponds to the actual spectrum
occupation (without any applicable guard bands), as approximated by the
symbol rate. PSD is specified in mW/GHz. As an example, for a 32 GBaud
signal at 0.01 mW, the PSD is 0.01/32 = 3.125e-4 mW/GHz.

This has some implications on the power sweep and ROADM behavior. Same
as previously (with absolute power targets), the ROADM design determines
the power set points. Target power is usually the best (highest) power
that can be supported by the ROADMs, especially the Add/Drop and express
stages' losses, with the goal to maximize the power at the booster's
input. As such, the `--power` option (or the power sweep) doesn't
manipulate with ROADM's target output power, but only with the output
power of the amplifiers. With PSD equalization, the `--power` option is
interpreted as the power of the reference channel defined in equipment
config's `SI` container, and its PSD is used for propagation. Power
sweep is interpreted in the same way, e.g.:

      "SI":[{
            "f_min": 191.3e12,
            "baud_rate": 32e9,
            "f_max":195.1e12,
            "spacing": 50e9,
            "power_dbm": 0,
            "power_range_db": [-1,1,1],
            "roll_off": 0.15,
            "tx_osnr": 40,
            "sys_margins": 2
            }],

...and with the PSD equalization in a ROADM:

    {
      "uid": "roadm A",
      "type": "Roadm",
      "params": {
        "target_psd_out_mWperGHz": 3.125e-4,
      }
    },
    {
      "uid": "edfa in roadm A to toto",
      "type": "Edfa",
      "type_variety": "standard_medium_gain",
      "operational": {
        "gain_target": 22,
        "delta_p": 2,
        "tilt_target": 0.0,
        "out_voa": 0
      }
    },

then we use the power steps of the power_range_db to compute resulting
powers of each carrier out of the booster amp:

 power_db = psd2powerdbm(target_psd_out_mWperGHz, baud_rate)
 sweep = power_db + delta_power for delta_power in power_range_db

Assuming one 32Gbaud and one 64Gbaud carriers:

                   32 Gbaud        64 Gbaud
roadmA out power
(sig+ase+nli)      -20dBm         -17dBm

EDFA out power
range[
        -1          1dBm            4dBm
         0          2dBm            5dBm
         1          3dBm            6dBm
]

Design case:

Design is performed based on the reference channel set defined in SI
in equipment config (independantly of equalization process):

      "SI":[{
            "f_min": 191.3e12,
            "baud_rate": 32e9,
            "f_max":195.1e12,
            "spacing": 50e9,
            "power_dbm": -1,
            "power_range_db": [0,0,1],
            "roll_off": 0.15,
            "tx_osnr": 40,
            "sys_margins": 2
            }],

`delta_p` values of amps refer to this reference channel, but are applicable
for any baudrate during propagation, e.g.:

    {
      "uid": "roadm A",
      "type": "Roadm",
      "params": {
        "target_psd_out_mWperGHz": 2.717e-4,
      }
    },
    {
      "uid": "edfa in roadm A to toto",
      "type": "Edfa",
      "type_variety": "standard_medium_gain",
      "operational": {
        "gain_target": 22,
        "delta_p": 2,
        "tilt_target": 0.0,
        "out_voa": 0
      }
    },

Then the output power for a 64 Gbaud carrier will be +4 =
= lin2db(db2lin(power_dbm + delta_p)/32e9 * 64e9)
= lin2db(db2lin(power_dbm + delta_p) * 2)
= powerdbm + delta + 3 = 4 dBm

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I28bcfeb72b0e74380b087762bb92ba5d39219eb3
2023-01-17 12:26:50 +01:00
Jan Kundrát
125264f265 coding style: don't yell when using the recommended newline-vs-operator
Fun stuff -- PEP8 used to recommend against this pattern, but back in
2016 the Pythonic Truth got reversed to actually recommend the pattern
which we're using. Unfortunately, W503 is still a thing, and even though
it's supposed to be ignored, it really ain't.

Change-Id: I99f42548d236f05d1050fd78cb81b3b20a78013c
2023-01-17 12:26:50 +01:00
Jan Kundrát
b1067a6266 docs: flexgrid
Co-authored-by: Esther Lerouzic <esther.lerouzic@orange.com>
Change-Id: If38b56a39e083deec0563f25a2b575788dcedc43
2023-01-17 09:48:15 +00:00
EstherLerouzic
50d4ecd700 docs: fix power mode vs. gain mode and power sweep
Change-Id: Ibef9a49123767d6e2ce73081485833f281711e04
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Co-authored-by: Jan Kundrát <jan.kundrat@telecominfraproject.com>
2023-01-17 09:47:58 +00:00
Jan Kundrát
9f37e0371e CI: temporarily require tox 3.x
Upstream introduced some breaking changes, one of which is a different
installation process. As a result, the builds won't perform a full
package installation, which means that there are no console entry points
(shell wrappers), which means that the test suite fails.

Hotfix this by temporarily requiring an older version of tox.

Change-Id: I0466c70f2024d35d87606d9ad738284a143a574f
2023-01-17 01:31:33 +01:00
Jan Kundrát
9bd303db05 CI: github: upgrade deprecated actions
...because the GitHub infrastructure is deprecating Node 12 actions:

 https: //github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/

Change-Id: I2d4a28be37a407aa26e79a1755eb5c3b0ec36a87
2023-01-10 12:27:50 +01:00
EstherLerouzic
1bcb3ce25c JSON: ensure that node constraints use correct indexing
The program currently ignores the explicit `index` and reads the
constraints in the JSON order of the list. However in general, it is not
guaranteed that constraints are listed in order.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Icefe271f5801cf9f7b43311c6666556564587c65
Signed-off-by: Jan Kundrát <jan.kundrat@telecominfraproject.com>
2022-11-22 01:53:24 +01:00
Jan Kundrát
e381138320 move test-only dependencies from main requirements
Pandas is only used from the test suite.

Bug: https://github.com/Telecominfraproject/oopt-gnpy/issues/451
Change-Id: Iafd02c800e5b7772e180979d19b81a2eda0e588f
2022-11-15 10:01:31 +00:00
EstherLerouzic
b450677709 Minor refactor: use watt2dbm function
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I09c3e923a8e1565d2ab07596c393bb5b2dc30f6c
2022-11-09 14:39:27 +01:00
EstherLerouzic
54a3725e17 Add a -spectrum option to input external file to define spectrum
The option is only set for gnpy-transmission-main.

The spectrum file is a list of spectrum objects, each defining
f_min, f_max and spectrum attributes using the same meaning as SI
in eqpt_config.json for baud_rate, roll_off, tx_osnr. slot_width is
used for the occupation of each carrier around their central frequency,
so slot_width corresponds to spacing of SI.
Unlike SI, the frequencies are defined includint f_min and f_max.
The partitions must be contiguous not overlapping.

Pref.p_span0 object records the req_power, while
ref_carrier records info that will be useful for equalization ie baud_rate.

For now, I have not integrated the possibility to directly use
transceivers type and mode in the list.

User can define sets of contiguous channels and a label to identify
the spectrum bands. If no label are defined, the program justs uses
the index + baud rate of the spectrum bands as label.

Print results per spectrum label

If propagated spectrum has mixed rates, then prints results (GSNR and OSNR)
for each propagated spectrum type according to its label.

Print per label channel power of elements

Per channel power prints were previously only showing the noiseless
reference channel power and only an average power.
With this change, we add a new information on the print:
the average total power (signal + noise + non-linear noise).
If there are several spectrum types propagating, the average per
spectrum is displayed using the label.
For this purpose, label and total power are recorded in each element
upon propagation

Note that the difference between this total power and the existing
channel power represents the added noise for the considered OMS.
Indeed ROADMs equalize per channel total power, so that power displayed
in 'actual pch (dBm)' may contain some noise contribution accumulated
with previous propagation.
Because 'reference pch out (dBm)' is for the noiseless reference,
it is exactly set to the target power and 'actual pch (dBm)' is always
matching 'reference pch out (dBm)' in ROADM prints.

Add examples and tests for -spectrum option

initial_spectrum1.json reproduces exactly the case of SI
initial_spectrum2.json sets half of the spectrum with 50GHz 32Gbauds and
half with 75GHz 64 Gbauds. Power setting is not set for the second half,
So that equalization will depend on ROADM settings.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ibc01e59e461e5e933e95d23dacbc5289e275ccf7
2022-11-09 14:39:25 +01:00
Jan Kundrát
8889c2437a refactoring: ROADM: clarify effective_loss and improve the docs
Move the docs to a place where that variable is declared, not to the
place where it's computed.

Co-authored-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-id: I17dff12c1e81827dfb4be869e59c9be85797dba4
2022-11-03 10:24:42 +01:00
Jan Kundrát
8bf8b2947b tests: pass the reference carrier when constructing SI
Co-authored-by: EstherLerouzic <esther.lerouzic@orange.com>
Fixes: 18610fb7 Add ref_carrier to Pref and remove req_power from ReferenceCarrier
Change-Id: I8ac2a7ca7c6d866170e564771c6cb78dcf3754d8
2022-11-03 10:24:38 +01:00
EstherLerouzic
cb85b8fe2b Add a test with long propagation
Existing tests only cover short distances, and effect on accumulated
noise, especially when crossing ROADMs with equalization, are not well
reported on elements power prints.
With this long path, I can catch more printing inconsistencies.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I2d0e8ccbbd387a2cd6c645c07f4b5f75e4617c30
2022-11-02 12:05:26 +01:00
EstherLerouzic
18610fb7a9 Add ref_carrier to Pref and remove req_power from ReferenceCarrier
ref_carrier is added in Pref conveys the reference channel type
information ie the channel that was used for design (would it be
auto-design or for a given design). Other attributes (like
slot_width or roll-off) may be added here for future equalization
types.

Pref object already records the req_power, so let's remove it
from ReferenceCarrier and only use ref_carrier to record info that
will be useful for PSD equalization ie baud_rate.

This reference baud_rate is required to compute reference target power
based on spectral density values during propagation. It is thus required
because of on-the-fly evaluation of loss for p_span_i and for printing
loss and target power of ROADM during propagation.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ic7441afa12ca5273ff99dea0268e439276107257
2022-11-02 12:05:26 +01:00
EstherLerouzic
bd6b278dd1 Add tx_osnr in spectral information
This change enables to use a different tx_osnr per carrier.

If tx_osnr is defined via spectrum then use it to define a tx_osnr per
carrier in si else use the tx_osnr of request to set tx_osnr of si.

Then, the propagate function for requests is changed to update OSNR with
tx_OSNR per carrier defined in si.

TODO: The tx_osnr defined in spectrum is not yet taken into account for
the propagate_and_optimize function, because the loop that optimizes
the choice for the mode only loops on baudrate.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I0fcdf559d4f1f8f0047faa257076084ec7adcc77
2022-10-31 16:04:46 +01:00
EstherLerouzic
e143d25339 Add a user defined initial spectrum in propagation functions
A new function is added to build spectrum information based on
the actual mixture of channels to be simulated (baud rate, slot width,
power per frequency).

Propagation function is changed so that, if the user defines a
specific distribution, then it uses it, else it uses as before,
all identical channels based on the initial request. In this case,
as before this change, we assume full load, with same channel for
the spectral info and not the resulting mixt of channels after
routing.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Icf56396837b77009e98accd27fcebd2dded6d112
2022-10-31 16:03:15 +01:00
EstherLerouzic
ffc7dbc241 Change pref from a scalar to a list of per channel delta power
The idea behind this change is to reproduce the exact same behaviour as
with the scalar, but accounting for variable levels of powers.

- delete the  neq_ch: equivalent channel count in dB because with mixed
  rates and power such a value has limited utility
- instead creates a vector that records the 'user defined' distribution
  of power.

This vector is used as a reference for channel equalization out of the
ROADM. If target_power_per_channel has some channels power above
input power, then the whole target is reduced.
For example, if user specifies delta_pdb_per_channel:
   freq1: 1dB, freq2: 3dB, freq3: -3dB, and target is -20dBm out of the
ROADM, then the target power for each channel uses the specified
delta_pdb_per_channel.
   target_power_per_channel[f1, f2, f3] = -19, -17, -23
However if input_signal = -23, -16, -26, then the target can not be
applied, because -23 < -19dBm and -26 < -23dBm, and a reduction must be
applied (ROADM can not amplify).
Then the target is only applied to signals whose power is above the
threshold. others are left unchanged and unequalized.
the new target is [-23, -17, -26]
and the attenuation to apply is [-23, -16, -26] - [-23, -17, -26] = [0, 1, 0]

Important note:
This changes the previous behaviour that equalized all identical channels
based on the one that had the min power !!

TODO: in coming refactor where transmission and design will be properly
separated, the initial behaviour may be set again as a design choice.

This change corresponds to a discussion held during coders call. Please look at this document for
a reference: https://telecominfraproject.atlassian.net/wiki/spaces/OOPT/pages/669679645/PSE+Meeting+Minutes

- in amplifier: the saturation is computed based on this vector
delta_pdb_per_channel, instead of the nb of channels.
The target of the future refactor will be to use the effective
carrier's power. I prefer to have this first step, because this is
how it is implemented today (ie based on the noiseless reference),
and I would like first to add more behaviour tests before doing
this refactor (would it be needed).

- in spectralInfo class, change pref to a Pref object to enable both
p_span0 and p_spani to be conveyed during propagation of
spectral_information in elements. No refactor of them at this point.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I591027cdd08e89098330c7d77d6f50212f4d4724
2022-10-28 09:13:24 +02:00
EstherLerouzic
b842898baf Change precision of --show-channels to 5 digits
Flexgrid precision is 6.25GHz so --show-channels should be at least 5 digits

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I7de4254ab18508320133371e0d8cc8b5e08f0d2f
2022-10-28 00:38:28 +00:00
EstherLerouzic
7ea9e3b341 Fix bug when gain is not initialized
If gain is not initialized, save_networks does not work properly
trying to round a None value

Change-Id: I614859f16e0019a2f6fe680c04159398c9b1eb51
2022-10-20 15:38:12 +00:00
Jan Kundrát
fcf168b361 tests: fix flake8 and flake8-html incompatibility
Test runs (`linters-diff-ci`) end up with an error:

 Traceback (most recent call last):
   File "/home/zuul/src/gerrithub.io/Telecominfraproject/oopt-gnpy/.tox/linters-diff-ci/bin/flake8", line 8, in <module>
     sys.exit(main())
   File "/home/zuul/src/gerrithub.io/Telecominfraproject/oopt-gnpy/.tox/linters-diff-ci/lib/python3.10/site-packages/flake8/main/cli.py", line 22, in main
     app.run(argv)
   File "/home/zuul/src/gerrithub.io/Telecominfraproject/oopt-gnpy/.tox/linters-diff-ci/lib/python3.10/site-packages/flake8/main/application.py", line 336, in run
     self._run(argv)
   File "/home/zuul/src/gerrithub.io/Telecominfraproject/oopt-gnpy/.tox/linters-diff-ci/lib/python3.10/site-packages/flake8/main/application.py", line 326, in _run
     self.report()
   File "/home/zuul/src/gerrithub.io/Telecominfraproject/oopt-gnpy/.tox/linters-diff-ci/lib/python3.10/site-packages/flake8/main/application.py", line 321, in report
     self.formatter.stop()
   File "/home/zuul/src/gerrithub.io/Telecominfraproject/oopt-gnpy/.tox/linters-diff-ci/lib/python3.10/site-packages/flake8_html/plugin.py", line 245, in stop
     self.write_index()
   File "/home/zuul/src/gerrithub.io/Telecominfraproject/oopt-gnpy/.tox/linters-diff-ci/lib/python3.10/site-packages/flake8_html/plugin.py", line 281, in write_index
     versions=self.option_manager.generate_versions(),
 AttributeError: 'OptionManager' object has no attribute 'generate_versions'
 ERROR: InvocationError for command /home/zuul/src/gerrithub.io/Telecominfraproject/oopt-gnpy/.tox/linters-diff-ci/bin/flake8 --format html --htmldir linters --exit-zero (exited with code 1)

Bug: https://github.com/lordmauve/flake8-html/issues/30
Change-Id: I755877341dec2d9cd9bdcdab098e2067f783cc27
2022-10-20 15:37:12 +02:00
Jan Kundrát
a7ec7e2ed6 Merge changes from topic "mixed-rate"
* changes:
  Change saturation verification to total input power
  Prepare for Pref definition
  Add utilities
2022-09-19 09:30:59 +00:00
Jan Kundrát
00ee102b3a docs: fix RST formatting
...of a bullet list. This ain't markdown, apparently.

Change-Id: I4f7a55a4084eda6463636c1dd5c41ef43ef78921
2022-09-18 12:46:19 +02:00
EstherLerouzic
ce11524ad9 Correct dgt vector: listed in the reversed order
This was corrected for example-data but not for tests data
(in commit 3a72ce84d0, related
to issue #390)

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I929beeb034166d30aa994439a1d6a26350f5c3e9
2022-09-14 05:35:15 +00:00
EstherLerouzic
74be14562a record request_id as string, not integer
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I59416a6d69a5989d0c152461ca9e264abcf09ea8
2022-08-24 16:45:56 +02:00
EstherLerouzic
16694d0a09 support missing trx_mode in request instead of null value
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I5c05b17b0b134c7782a08e86015dc30c7c9b3713
2022-08-24 16:43:57 +02:00
EstherLerouzic
33c6038921 Change saturation verification to total input power
Previous check was made on reference channel computation.
Now we use the actual total input power to compute the actual gain.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I3e0db72fdb030a49e2b06cdcfb442b5e642c1777
2022-08-17 14:13:38 +02:00
EstherLerouzic
119c9eda90 Prepare for Pref definition
Mainly changes self.pch_out_db to self.ref_pch_out_dbm in order
to reflect real unit for the value and to remind that this value
is defined for a reference noiseless channel (whose power is recorded
in p_spani in Pref).

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: If0e008c3efc36ce73c9df01c76cf46985543d9fa
2022-08-17 14:13:38 +02:00
EstherLerouzic
b63e146bf4 Add utilities
to convert from/to watt, mW, dBm, power spectral density ...

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I9b9684c1ad096aa54d01ef3f0242ecd2dcae79aa
2022-08-17 14:13:38 +02:00
gborrach
09dba8a166 Fix: Raman pumps SRS solver
In the previous version, when the values of the counter-propagating Raman pump profiles were flipped, the pumps resulted flipped also in frequency.

Change-Id: I66f7c2aff35c72f5dcb4fb11f7a82fe1df2ee3f2
Co-authored-by: Andrea D'Amico <andrea.damico@polito.it>
2022-07-27 00:20:47 +02:00
Jan Kundrát
7f5043622b CI: GitHub: show all build failures
Change-Id: I4a5aeb123aa89a30371a36788188964ca924ca12
2022-07-07 16:32:13 +02:00
Jan Kundrát
6ad4593f41 CI: GitHub: test on Mac OS as well
Change-Id: Ifb8ba470765fc02e3367beeaf8f048da6fdad6d7
2022-07-05 13:18:37 +02:00
Jan Kundrát
706661d801 CI: GitHub: use new test-requirements
Follow-up: b86fe960 I8d2e610c91da728d72c7d19590b25bbd8713f0de tests: Easier installation of test requirements via PIP
Change-Id: I870ed27af7301829ced572214f517ca76a94a0dc
2022-07-05 13:07:18 +02:00
Jan Kundrát
a408d28911 Merge "Remove Travis-CI leftovers" 2022-07-05 11:07:08 +00:00
Jan Kundrát
b86fe96032 tests: Easier installation of test requirements via PIP
Burying these behind the tox.ini works fine on CI, but it means that
someone has to ask the users to run an extra `pip install pytest` for
the test suite. Not nice.

This will need a follow-up commit to adjust the GitHub action to use
this new simplified way. That cannot land via Gerrit due to GitHub's
permission model.

Change-Id: I8d2e610c91da728d72c7d19590b25bbd8713f0de
2022-07-05 12:45:19 +02:00
Jan Kundrát
43926518ad Remove Travis-CI leftovers
These builds have not been running for about an year. Now that we have
Zuul for day-to-day CI and GitHub actions for some auxiliary package
building, remove Travis.

Change-Id: I1dd7f70045fd24d2f73f0d2086cb49edde2093c7
2022-07-05 10:14:18 +02:00
Jan Kundrát
128a6e816b docs: better anchor for legacy JSON
Change-Id: I410a4c0cdd34dd9aa5d8e34bb859746236fffdd2
2022-07-03 01:03:26 +02:00
Jan Kundrát
44db951261 docs: show gnpy.app
Change-Id: I7ec5eec72fb0f07e277ac849da09003d376eec17
2022-04-12 11:42:06 +02:00
Jonas Mårtensson
3c3d919b77 Merge "Fix: penalties are not correctly initialized" 2022-04-11 18:55:42 +00:00
EstherLerouzic
2079d2bc5b Fix: penalties are not correctly initialized
When mode is not given and propagation is performed bidir, penalties
corresponding to automatically  selected mode are not correctly
initialized in request, and gnpy-path-request fails.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: If045624ff5dad7f0dfdec93eaa05bb5eae86e643
2022-04-06 17:17:21 +02:00
Jonas Mårtensson
062e2076ed Properly initialize power profiles for Raman calculation
numpy.empty should not be used for initializing arrays without manually
setting values since it does not initialize entries. Use numpy.zeros
instead.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I4e85eb39bdce00663c0cab9582ea7ae25eb90986
2022-03-29 21:41:57 +02:00
Jonas Mårtensson
1dd1bad273 Fix bug in Raman calculation without counterpropagating pumps
The counterpropagating power profile was initialized based on number of
copropagating frequencies, which caused simulation to crash when no
counterpropagating pumps were present.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I685e5438fda06058f0757ff51fdd67bc68aa1352
2022-03-29 21:27:18 +02:00
Jan Kundrát
5b104af296 packaging: cleanup: remove non-existing paths
The examples have lived below gnpy/example-data/ for a few releases
already, and I've checked that these files are included in release
tarballs and wheels.

Change-Id: I782fc56a171f4cbc08f6698ac5d339e4cacecbb4
2022-03-09 00:13:43 +01:00
Jan Kundrát
f170574abf CI: retire gate
We have not started using this one, so let's not pretend that it's
active.

Change-Id: I806a76e2c6e0dc1d1fb76796cfea8eb37bfa39ca
2022-02-15 13:27:24 +01:00
Jan Kundrát
a68e8ff8d2 CI: Use default VMs for Python 3.8
Since the mirror infra for Fedora 34 is now gone on Vexxhost, let's try
to use the default options.

Change-Id: I6e4cc07705287666a772c6b1b70e981b24da6670
2022-02-15 13:18:31 +01:00
AndreaDAmico
d5a52d1b2b Restructure Transceiver with new spectral information
Change-Id: Iec9a6e4a510b8020aed8804d4a594b2b0429e28d
2022-02-10 17:38:39 +01:00
AndreaDAmico
7ac6e058ec EDFA new spectral information restructuring
Change-Id: Ia30e0e9bd666e83394c7a0740b2117a2d9c9d485
2022-02-10 17:37:03 +01:00
AndreaDAmico
74ab3c1bcd Fused new spectral information restructuring
Change-Id: Ie4dd989e2fd72682820845d21c43afed177f0f2f
2022-02-10 17:36:35 +01:00
AndreaDAmico
1a2ff2d215 Roadm new spectral information restructuring
Change-Id: I5c7c615e8278bff79dc74af10810589a15cc7535
2022-02-10 17:34:28 +01:00
Giacomo Borraccini
aaf0480e9c Management of lumped losses along a fiber span
The lumped losses are used in the computation of the loss/gain profile
through the fiber whether the Raman effect is considered or not. The
computed power profile is used to calculate the related NLI impairment.

Using the 'gn_model_analytic' method, the lumped losses are taken into
account as the contribution of an additional total loss at the end of
the fiber span. In case the 'ggn_spectrally_separated' is selected, the
method uses the computed power profile according to the specified z and
frequency arrays. The lumped losses are so considered within the NLI
power evolution along the fiber.

Change-Id: I73a6baa321aca4d041cafa180f47afed824ce267
Signed-off-by: Jan Kundrát <jan.kundrat@telecominfraproject.com>
2022-02-10 17:33:34 +01:00
Jan Kundrát
5e50ffbbf6 CI: check patches on Python 3.10 as well
Depends-on: https://review.gerrithub.io/c/Telecominfraproject/oopt-zuul-jobs/+/531900
Change-Id: I975c38b2f287aa07b68cc37500c7022feb4ae3e2
2022-02-02 02:13:23 +01:00
Jan Kundrát
243b701391 docs: update dependencies
Python 3.10 builds require a fix in Sphinx, so let's update all docs
dependencies while we're at this.

Unfortunately, the myst-parser requires docutils<0.18,>=0.15 so we
cannot take the latest and greatest of that one.

After this update, sphinxcontrib-bibtex now requires an explicit
reference to the .bib files directly in the config file. Let's remove
the one in the actual docs, then.

Bug: https://github.com/sphinx-doc/sphinx/pull/9513
Change-Id: I80f62131b25f3afa23351646001f6ce381723487
2022-02-02 02:13:04 +01:00
Jan Kundrát
bdbfe76aed Mark Python 3.10 as supported
The CI is (so far) only post-merge via the GitHub CI. The Zuul CI is
pending on Fedora 35 images within nodepool; I've requested that via a
ticket at Vexxhost, our hosted CI provider.

Change-Id: I81c9867792f972db8fcb2bf902f5f8d1ed7ffeea
2022-01-20 18:51:02 +01:00
Jan Kundrát
541ec04444 GitHub CI: Python 3.10
Add a new target, and switch those builds where a specific version is
not that important to the latest and greatest.

Change-Id: I6f62a08c671a98b3663c7a8cf0099947457f1e4d
2022-01-20 18:50:54 +01:00
Jan Kundrát
bf1522b047 Merge changes Iff561600,I60f951e9
* changes:
  tests: update pytest
  Update dependencies
2022-01-20 17:50:09 +00:00
Jan Kundrát
3f4188a0fd Merge changes I79611db3,Ib0ab383b,I3745eba4,Ic19aff08,Ic255f35d
* changes:
  Add PMD and PDL in amplifiers
  Introduce PDL accumulation and penalty calculation
  Calculate CD and PMD penalty
  Set PMD for ROADMs in OpenROADM eqpt_config according to MSA spec
  Fix formatting of OpenROADM eqpt files
2022-01-20 16:48:11 +00:00
Jan Kundrát
8b387ef722 tests: update pytest
Let's switch to this new major version; it's not wise to stay on a
previous major release indefinitely. In practical terms, version 6.x is
a requirement for supporting Python 3.10.

I'm not updating the CI configuration yet because GitHub disallows
pushes via Gerrit when GitHub CI Actions are modified. The version will
have to be bumped in there as well.

Change-Id: Iff5616007b51e6098f53810a28fef5b839dadec2
2022-01-19 16:25:38 +01:00
Jan Kundrát
cad9a0f18e Update dependencies
Updating everything to the latest versions which are available on PIP --
apart from xlrd, which decided to stop supporting XLSX files in their
newest version. Since we have some XLSX files in this repo, I think we
cannot update now.

Change-Id: I60f951e9f17cc62f0dcdc6b6d0cfce0cb5f891fa
2022-01-19 16:23:18 +01:00
Jonas Mårtensson
ab84c77363 Restore RamanFiber to_json method with operational parameters
The recent commit 77925b2 removed the to_json method from RamanFiber
class, which means that operational parameters (temperature and
raman_pumps) were not included when converting a topology to json and
saving it. This patch restores it.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: Icbea349c1ffaa2f216533b84c8edf5c8b59765f9
2022-01-18 19:07:28 +01:00
Jonas Mårtensson
62fa9ab0b0 Add PMD and PDL in amplifiers
Both PMD and PDL is set to 0 by default. Values from the OpenROADM MSA
for ILAs are included in corresponding eqpt files.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I79611db3ae798e9dadc47ee39161dc1e242f2595
2022-01-18 12:35:59 +01:00
Jonas Mårtensson
14591c7a11 Introduce PDL accumulation and penalty calculation
This fixes #421

As a first step PDL is specified in the eqpt library for ROADMs only.
In a later step, PDL (as well as PMD) should be specified also for amps
and possibly for fibers. PDL values from the OpenROADM MSA for ROADMs
are included in the corresponding eqpt files.

The acculumation rule for PDL is the same as for PMD as shown in:

"The statistics of polarization-dependent loss in optical communication
systems", A. Mecozzi and M. Shtaif, IEEE Photon. Technol. Lett., vol.
14, pp. 313-315, Mar 2002.

PDL penalty is specified and calculated in the same way as for CD and
PMD, i.e. linear interpolation between impairment_value/penalty_value
pairs. This patch includes penalty specification for OpenROADM trx
modes according to the MSA.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: Ib0ab383bcaee7d7523ffc3fa9a949d76c8c86ff7
2022-01-18 12:35:59 +01:00
Jonas Mårtensson
587932290d Calculate CD and PMD penalty
The penalties are calculated and presented separately from the GSNR.

They are also taken into account when optimizing trx mode and verifying
path feasibility in path_requests_run processing.

Penalties are specified in the eqpt_config file as part of trx modes.
This patch includes specifications for OpenROADM trx modes.

Penalties are defined by a list of
impairment_value/penalty_value pairs, for example:

"penalties": [
    {
        "chromatic_dispersion": 4e3,
        "penalty_value": 0
    },
    {
        "chromatic_dispersion": 18e3,
        "penalty_value": 0.5
    },
    {
        "pmd": 10,
        "penalty_value": 0
    },
    {
        "pmd": 30,
        "penalty_value": 0.5
    }
]

- Between given pairs, penalty is linearly interpolated.
- Below min and above max up_to_boundary, transmission is considered
  not feasible.

This is in line with how penalties are specified in OpenROADM and
compatible with specifications from most other organizations and
vendors.

The implementation makes it easy to add other penalties (PDL, etc.) in
the future.

The input format is flexible such that it can easily be extended to
accept combined penalty entries (e.g. CD and PMD) in the future.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I3745eba48ca60c0e4c904839a99b59104eae9216
2022-01-18 12:35:59 +01:00
Jonas Mårtensson
82b148eb87 Set PMD for ROADMs in OpenROADM eqpt_config according to MSA spec
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: Ic19aff08ea0da51656ba81e04f34a405413f7b54
2022-01-18 12:35:59 +01:00
Jonas Mårtensson
8393daf67d Fix formatting of OpenROADM eqpt files
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: Ic255f35d32ce996724a547962efc44d7950bac4d
2022-01-18 12:35:57 +01:00
Jan Kundrát
be61dfd094 Merge changes from topic "mixed-rate"
* changes:
  Raman Solver restructuring and speed up
  Effective area included in fiber parameters
  Fiber propagation of new Spectral Information.
  Small change on Fiber parameters
2022-01-18 09:58:07 +00:00
AndreaDAmico
77925b218e Raman Solver restructuring and speed up
In this change, the RamanSolver is completely restructured in order to obtain a simplified and faster solution of the Raman equation. Additionally, the inter-channel Raman effect can be evaluated also in the standard fiber, when no Raman pumping is present. The same is true for the GGN model.

The Raman pump parameter pumps_loss_coef has been removed as it was not used. The loss coefficient value evaluated at the pump frequency can be included within the fiber loss_coef parameter.

This change induces variations in some expected test results as the Raman profile solution is calculated by a completely distinct algorithm. Nevertheless, these variations are negligible being lower than 0.1dB.

Change-Id: Iaa40fbb23c555571497e1ff3bf19dbcbfcadf96b
2022-01-12 19:37:10 +01:00
AndreaDAmico
4621ac12bf Effective area included in fiber parameters
Gamma and the raman efficiency are calculated using the effective area if not provided. Both these parameters are managed as optional in json_io.py for backward compatibility.

Change-Id: Id7f1403ae33aeeff7ec464e4c7f9c1dcfa946827
2022-01-06 12:00:00 +01:00
AndreaDAmico
09920c0af2 Fiber propagation of new Spectral Information.
Modification of the Fiber and the NliSolver in order to properly propagate the new definition of the spectral information taking advantage of the numpy array structures.

In the previous version, the propagation of the spectral information was implemented by means of for cycles over each channel, in turn.
In this change the propagation is applied directly on the newly defined spectral information attributes as numpy arrays.

Additional changes:
- Simplification of the FiberParameters and the NliParameters;
- Previous issues regarding the loss_coef definition along the frequency are solved;
- New test in test_science_utils.py verifing that the fiber propagation provides the correct values in case of a few cases of flex grid spectra.

Change-Id: Id71f36effba35fc3ed4bbf2481a3cf6566ccb51c
2022-01-06 12:00:00 +01:00
AndreaDAmico
e6a3d9ce5b Small change on Fiber parameters
Squeeze function has been replaced by asarray. Using 'get' function
instead of if condition for the dictionaries.  Frequency reference
derived from wavelength reference of 1550 nm.

Change-Id: I815ad8591c9e238f3fc9322ca0946ea469ff448f
2022-01-06 12:00:00 +01:00
Jonas Mårtensson
b9645702c8 Add fiber padding after splitting fibers
Since splitting fibers may result in fibers with loss lower than
specifed padding, the padding should be added after splitting.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: Id75ddf5d81c4c1c52f8f8becfdb005d2fe04c1f8
2021-12-19 21:20:08 +01:00
Jan Kundrát
9c2095b138 remove unused import
Change-Id: I08d31eab16f0c4195c4431f339c60ac694788d22
2021-12-07 16:49:40 +01:00
Jan Kundrát
cb42115230 LGTM: exclude more harmless "errors"
The {node.uid} pattern is used also when exporting some data to a file,
and once again it is not an antipattern for us.

Change-Id: Ib4441155b5ca42fad7dd6cf8554a0302a69ef136
2021-12-07 16:47:30 +01:00
Jan Kundrát
5909da4bbf remove an unused import
Change-Id: I4d719813b647424c20cc7fc990c36a57490b7f5b
2021-12-07 12:49:46 +01:00
Jan Kundrát
2ba1e86b28 Silence an irrelevant warning on LGTM.com
By default, this service issues a warning for strings which format data
like {uid}, flagging this pattern as related to antipatterns CWE-312,
CWE-315 and CWE-359. This warning is not relevant for us because we
never process sensitive information (if there are some NDA-covered data
in the input, well, the user already has them, we're just using these).

Silence this warning (it's currently getting us an B rating,
apparently).

See-also: https://lgtm.com/rules/1510014536001/
Change-Id: Id5cdd2c62e61a8329760d3fb79665737beb22378
2021-12-07 12:49:46 +01:00
Jan Kundrát
3358c5eeb5 Merge "docs: a beginner-friendly way of reaching out to vendors" 2021-11-25 14:53:57 +00:00
Jan Kundrát
13e4c29bc1 Merge "tests: rely on pytest's native comparisons of nested dicts" 2021-11-11 16:17:14 +00:00
Jan Kundrát
4becc9060c docs: a beginner-friendly way of reaching out to vendors
As Gert proposed on the latest call, submitting patches could be a bit
high of a barrier to go over. Let's make it clear that we're here to
help those vendors who are willing to collaborate.

Change-Id: Ieac1c91480143c553ffb25dd1c46e94022bf5ba3
2021-11-04 18:45:05 +01:00
AndreaDAmico
32d8b2a4d8 Simulation Parameters
This change siplifies the structure of the simulation parameters,
removing the gnpy.science_utils.simulation layer, provides some
documentation of the parameters and define a mock fixture for testing in
safe mode.

Jan: while I'm not thrilled by this concept of hidden global state, we
agreed to let it in as a temporary measure (so as not to hold merging of
Andrea's flexgrid/multirate patches). I've refactored this to a more
pytest-ish way of dealing with fixtures. In the end, it was also
possible to remove the MockSimParams class because it was not adding any
features on top of what SimParams can do already (and to what was
tested).

Change-Id: If5ef341e0585586127d5dae3f39dca2c232236f1
Signed-off-by: Jan Kundrát <jan.kundrat@telecominfraproject.com>
2021-10-29 13:14:22 +02:00
Jan Kundrát
399eb9700f tests: rely on pytest's native comparisons of nested dicts
In my opinion, this actually produces more useful output *if* pytest is
invoked with `-vv` (which the CI is already doing). When I deliberately
change the expected result of services like this:

 --- a/tests/data/testService_services_expected.json
 +++ b/tests/data/testService_services_expected.json
 @@ -45,7 +45,7 @@
            ],
            "spacing": 50000000000.0,
            "max-nb-of-channel": null,
 -          "output-power": 0.0012589254117941673,
 +          "output-power": 0.001258925411791673,
            "path_bandwidth": 10000000000.0
          }
        }

...the old code would just say:

 >       assert not results.requests.different
 E       AssertionError: assert not [({'bidirectional': False, 'destination': 'trx Vannes_KBE', 'dst-tp-id': 'trx Vannes_KBE', 'path-constraints': {'te-ba......}], 'max-nb-of-channel': None, 'output-power': 0.0012589254117941673, 'path_bandwidth': 10000000000.0, ...}}, ...})]
 E        +  where [({'bidirectional': False, 'destination': 'trx Vannes_KBE', 'dst-tp-id': 'trx Vannes_KBE', 'path-constraints': {'te-ba......}], 'max-nb-of-channel': None, 'output-power': 0.0012589254117941673, 'path_bandwidth': 10000000000.0, ...}}, ...})] = Results(missing=set(), extra=set(), different=[({'request-id': '1', 'source': 'trx Brest_KLA', 'destination': 'trx Van...g': 50000000000.0, 'max-nb-of-channel': 80, 'output-power': 0.0012589254117941673, 'path_bandwidth': 60000000000.0}}}}).different
 E        +    where Results(missing=set(), extra=set(), different=[({'request-id': '1', 'source': 'trx Brest_KLA', 'destination': 'trx Van...g': 50000000000.0, 'max-nb-of-channel': 80, 'output-power': 0.0012589254117941673, 'path_bandwidth': 60000000000.0}}}}) = ServicesResults(requests=Results(missing=set(), extra=set(), different=[({'request-id': '1', 'source': 'trx Brest_KLA'...': 50000000000.0, 'max-nb-of-channel': 80, 'output-power': 0.0012589254117941673, 'path_bandwidth': 60000000000.0}}}})).requests

 tests/test_parser.py:147: AssertionError
 ...
 FAILED tests/test_parser.py::test_excel_service_json_generation[xls_input1-expected_json_output1] - AssertionError: assert not [({'bidirectional': False, 'destination': 'trx Vannes_KBE', 'dst-tp-id': 'trx Vannes_KBE', 'path-constraints': {'te-ba......}], 'max-nb-of-channel': None, 'output-power': 0.0012589254117941673, 'path_bandwidth': 10000000000.0, ...}}, ...})]

With this change in place, the report becomes more useful:

 >       assert from_xls == load_json(expected_json_output)
 E       AssertionError: assert {'path-request': [{'bidirectional': False,\n                   'destination': 'trx Vannes_KBE',\n                   'dst-tp-id': 'trx Vannes_KBE',\n                   'path-constraints': {'te-bandwidth': {'effective-freq-slot': [{'M': None,\n                                                                                  'N': None}],\n                                                         'max-nb-of-channel': 80,\n                                                         'output-power': None,\n                                                         'path_bandwidth': 100000000000.0,\n                                                         'spacing': 50000000000.0,\n                                                         'technology': 'flexi-grid',\n                                                         'trx_mode': 'mode 1',\n                                                         'trx_type': 'Voyager'}},\n                   'request-id': '0',\n                   'source': 'trx Lorient_KMA',\n                   'src-tp-id': 'trx Lorient_KMA'},\n                  {'bidirectional': False,\n                   'destination': 'trx Vannes_KBE',\n                   'dst-tp-id': 'trx Vannes_KBE',\n                   'path-constraints': {'te-bandwidth': {'effective-freq-slot': [{'M': None,\n                                                                                  'N': None}],\n                                                         'max-nb-of-channel': None,\n                                                         'output-power': 0.0012589254117941673,\n                                                         'path_bandwidth': 10000000000.0,\n                                                         'spacing': 50000000000.0,\n                                                         'technology': 'flexi-grid',\n                                                         'trx_mode': 'mode 1',\n                                                         'trx_type': 'Voyager'}},\n                   'request-id': '1',\n                   'source': 'trx Brest_KLA',\n                   'src-tp-id': 'trx Brest_KLA'},\n                  {'bidirectional': False,\n                   'destination': 'trx Rennes_STA',\n                   'dst-tp-id': 'trx Rennes_STA',\n                   'path-constraints': {'te-bandwidth': {'effective-freq-slot': [{'M': None,\n                                                                                  'N': None}],\n                                                         'max-nb-of-channel': 80,\n                                                         'output-power': 0.0012589254117941673,\n                                                         'path_bandwidth': 60000000000.0,\n                                                         'spacing': 50000000000.0,\n                                                         'technology': 'flexi-grid',\n                                                         'trx_mode': 'mode 1',\n                                                         'trx_type': 'vendorA_trx-type1'}},\n                   'request-id': '3',\n                   'source': 'trx Lannion_CAS',\n                   'src-tp-id': 'trx Lannion_CAS'}]} == {'path-request': [{'bidirectional': False,\n                   'destination': 'trx Vannes_KBE',\n                   'dst-tp-id': 'trx Vannes_KBE',\n                   'path-constraints': {'te-bandwidth': {'effective-freq-slot': [{'M': None,\n                                                                                  'N': None}],\n                                                         'max-nb-of-channel': 80,\n                                                         'output-power': None,\n                                                         'path_bandwidth': 100000000000.0,\n                                                         'spacing': 50000000000.0,\n                                                         'technology': 'flexi-grid',\n                                                         'trx_mode': 'mode 1',\n                                                         'trx_type': 'Voyager'}},\n                   'request-id': '0',\n                   'source': 'trx Lorient_KMA',\n                   'src-tp-id': 'trx Lorient_KMA'},\n                  {'bidirectional': False,\n                   'destination': 'trx Vannes_KBE',\n                   'dst-tp-id': 'trx Vannes_KBE',\n                   'path-constraints': {'te-bandwidth': {'effective-freq-slot': [{'M': None,\n                                                                                  'N': None}],\n                                                         'max-nb-of-channel': None,\n                                                         'output-power': 0.001258925411791673,\n                                                         'path_bandwidth': 10000000000.0,\n                                                         'spacing': 50000000000.0,\n                                                         'technology': 'flexi-grid',\n                                                         'trx_mode': 'mode 1',\n                                                         'trx_type': 'Voyager'}},\n                   'request-id': '1',\n                   'source': 'trx Brest_KLA',\n                   'src-tp-id': 'trx Brest_KLA'},\n                  {'bidirectional': False,\n                   'destination': 'trx Rennes_STA',\n                   'dst-tp-id': 'trx Rennes_STA',\n                   'path-constraints': {'te-bandwidth': {'effective-freq-slot': [{'M': None,\n                                                                                  'N': None}],\n                                                         'max-nb-of-channel': 80,\n                                                         'output-power': 0.0012589254117941673,\n                                                         'path_bandwidth': 60000000000.0,\n                                                         'spacing': 50000000000.0,\n                                                         'technology': 'flexi-grid',\n                                                         'trx_mode': 'mode 1',\n                                                         'trx_type': 'vendorA_trx-type1'}},\n                   'request-id': '3',\n                   'source': 'trx Lannion_CAS',\n                   'src-tp-id': 'trx Lannion_CAS'}]}
 E         Differing items:
 E         {'path-request': [{'bidirectional': False, 'destination': 'trx Vannes_KBE', 'dst-tp-id': 'trx Vannes_KBE', 'path-const...[{...}], 'max-nb-of-channel': 80, 'output-power': 0.0012589254117941673, 'path_bandwidth': 60000000000.0, ...}}, ...}]} != {'path-request': [{'bidirectional': False, 'destination': 'trx Vannes_KBE', 'dst-tp-id': 'trx Vannes_KBE', 'path-const...[{...}], 'max-nb-of-channel': 80, 'output-power': 0.0012589254117941673, 'path_bandwidth': 60000000000.0, ...}}, ...}]}
 E         Full diff:
 E           {
 E            'path-request': [{'bidirectional': False,
 E                              'destination': 'trx Vannes_KBE',
 E                              'dst-tp-id': 'trx Vannes_KBE',
 E                              'path-constraints': {'te-bandwidth': {'effective-freq-slot': [{'M': None,
 E                                                                                             'N': None}],
 E                                                                    'max-nb-of-channel': 80,
 E                                                                    'output-power': None,
 E                                                                    'path_bandwidth': 100000000000.0,
 E                                                                    'spacing': 50000000000.0,
 E                                                                    'technology': 'flexi-grid',
 E                                                                    'trx_mode': 'mode 1',
 E                                                                    'trx_type': 'Voyager'}},
 E                              'request-id': '0',
 E                              'source': 'trx Lorient_KMA',
 E                              'src-tp-id': 'trx Lorient_KMA'},
 E                             {'bidirectional': False,
 E                              'destination': 'trx Vannes_KBE',
 E                              'dst-tp-id': 'trx Vannes_KBE',
 E                              'path-constraints': {'te-bandwidth': {'effective-freq-slot': [{'M': None,
 E                                                                                             'N': None}],
 E                                                                    'max-nb-of-channel': None,
 E         -                                                          'output-power': 0.001258925411791673,
 E         +                                                          'output-power': 0.0012589254117941673,
 E         ?                                                                                          +
 E                                                                    'path_bandwidth': 10000000000.0,
 E                                                                    'spacing': 50000000000.0,
 E                                                                    'technology': 'flexi-grid',
 E                                                                    'trx_mode': 'mode 1',
 E                                                                    'trx_type': 'Voyager'}},
 E                              'request-id': '1',
 E                              'source': 'trx Brest_KLA',
 E                              'src-tp-id': 'trx Brest_KLA'},
 E                             {'bidirectional': False,
 E                              'destination': 'trx Rennes_STA',
 E                              'dst-tp-id': 'trx Rennes_STA',
 E                              'path-constraints': {'te-bandwidth': {'effective-freq-slot': [{'M': None,
 E                                                                                             'N': None}],
 E                                                                    'max-nb-of-channel': 80,
 E                                                                    'output-power': 0.0012589254117941673,
 E                                                                    'path_bandwidth': 60000000000.0,
 E                                                                    'spacing': 50000000000.0,
 E                                                                    'technology': 'flexi-grid',
 E                                                                    'trx_mode': 'mode 1',
 E                                                                    'trx_type': 'vendorA_trx-type1'}},
 E                              'request-id': '3',
 E                              'source': 'trx Lannion_CAS',
 E                              'src-tp-id': 'trx Lannion_CAS'}],
 E           }

 tests/test_parser.py:140: AssertionError

Change-Id: I30eafb3c7c0f2e800fb0983371eaa5058e78b029
2021-10-28 17:16:11 +02:00
AndreaDAmico
82f83e1462 Add documentation of simulation parameters
Jan: only add those parameter which are not being removed in future
patches and which have useful documentation that the user can plausibly
act on.

Change-Id: I02173f500fed8c065a30de5d23e318bce2a90c33
Co-authored-by: Jan Kundrát <jan.kundrat@telecominfraproject.com>
2021-10-28 16:18:25 +02:00
Jonas Mårtensson
171450fa54 Update documentation of polynomial NF to match the code
This fixes #422.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I39af6767e41723b23dd61a832860fc66f21dbf17
2021-10-28 08:41:14 +02:00
AndreaDAmico
9f9f4c78fc Small change on Raman pump parameters
Getter and setter removed from the class PumpParams. The propagation
direction is cast to lower case string within the PumpParams
constructor.

Change-Id: Ice28affe8bcffbf8adcebb5cb096be8100081511
2021-10-26 16:33:35 +02:00
AndreaDAmico
c469a8d9ba New definition of spectral information
It allows the definition of an arbitrary spectral information.
It is fully back-compatible.

Change-Id: Id050e9f0a0d30780a49ecfbe8b96271fe47bcedc
2021-10-26 15:59:20 +02:00
Jonas Mårtensson
99b2a554dc Correct calculation of NF for OpenROADM amps
This fixes #420.

In order to be consistent with the OpenROADM MSA, the input power per
channel used for calculating incremental OSNR and NF should be scaled to
50 GHz slot width.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I64ca3e4cad6399f308827f4161d7c6b89be9d2ca
2021-10-19 12:02:48 +02:00
Jan Kundrát
57e98d7173 CI: linters: don't complain about non-lowercase variable names
...as agreed during today's coders call. It turned out that we do not
have a strong opinion, and it appeared that this limits us bit. The
rationale was that sometimes there's a loss of information when we
force-lowercase (such as milli- vs. mega-). So let's hope we're gonna be
smarter than a rigid set of rules :).

See-also: https://review.gerrithub.io/c/Telecominfraproject/oopt-gnpy/+/525660/2/gnpy/core/elements.py#681
Change-Id: If88abfa8383a437cc42e9196c612897fae6c96a0
2021-10-19 12:00:07 +02:00
Jan Kundrát
78b45a3958 Merge "Fix warnings for raman amp type varieties" 2021-10-11 19:32:03 +00:00
Jan Kundrát
64b6b486a9 Merge "Fix unit for max_fiber_lineic_loss_for_raman" 2021-10-11 19:15:59 +00:00
Jonas Mårtensson
65cb46f479 Fix unit for max_fiber_lineic_loss_for_raman
See GitHub issue #419.

The unit of max_fiber_lineic_loss_for_raman in the default equipment
config file is dB/km but it was compared with Fiber.params.loss_coef
which is in units of dB/m.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I27c8ab03845a72fcda97415843c007078b7b9d06
2021-10-11 21:03:17 +02:00
Jonas Mårtensson
f94d06f124 Fix warnings for raman amp type varieties
Currently, a warning about fiber lineic loss being above threshold is
printed even when the warning is triggered by that the previous node is
not a fiber, which is confusing. Additionally, when a warning about
raman is triggered, the code in the following else clause is not
executed, which means that a potential warning about gain level is
disabled. These two warnings are independent and the second one should
not be disabled by the first one.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I8ad58b4ebf6e7df1a949a77d67ed948ef385c47e
2021-09-28 13:08:57 +02:00
EstherLerouzic
e1f2c55942 Remove the representation error due to floating point
On certain values of loss_coef, the computation loss_coef * 1e-3 * 1e3
results in x.0000000000000000y values:
eg if 0.21 is provided in the topology file, then parameters.FiberParams
changes this to _loss_coef = 0.00021
and elements.Fiber.to_json prints 0.21000000000000002
This is a normal floating point behaviour, but is rather confusing.
In order to remove this unwanted decimal, I propose to round the printings
in to_json

https://docs.python.org/3/tutorial/floatingpoint.html

The change also add this round for gain, because in power mode
the gain is computed based on loss and loss may have this floating value.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ib3287a794e7da985eabf0af914c0e1ef4914e857
2021-09-16 15:41:25 +02:00
Jan Kundrát
d28c67143e docs: remove link to asciinema
This image needs changing, so let's prepare by not linking to asciinema
anymore.

Change-Id: I15567325139fdf02bcca2bf2b1f1460d689cbfa4
2021-09-15 16:21:10 +02:00
Jan Kundrát
6bb9ae8336 tests: Fix after merging two incompatible changes
Oops. We are not using gating, which means that changes are tested
against the "current tip of the branch" and might pass fine there, but
once they are merged, there can well be a conflict between them. This
has just happened.

The EDFA which reported a difference had its VOA set to 0.5. Previously,
this was not taken into assumption.

Fixes: ce51a4d1 Take explicitly set out_voa value into account in power calculation
Fixes: 280443f1 add an invocation test with power saturation
Change-Id: Icebbb16d2ef5886d2c9c04cc9a300a6aa08bf245
2021-09-15 15:31:20 +02:00
Jan Kundrát
0dc7d853ef Merge changes I7f6cc553,I0a6a8442,I34fe2dcf
* changes:
  requests: avoid TypeError
  add an invocation test with power saturation
  Update a roadm test to include more cases for power handling
2021-09-15 13:09:32 +00:00
Jan Kundrát
dec9388416 Merge changes from topic "openroadm-v5"
* changes:
  tests: add OpenROADMv5 example propagation
  OpenROADM: mark example config files as v4 explicitly
  Add an eqpt config file matching latest OpenROADM MSA version
  Add updated openroadm amp specifications to eqpt config
2021-09-15 13:08:40 +00:00
Jan Kundrát
017b35fa33 Merge changes I4e407484,Id35aeffe
* changes:
  examples: json-to-csv: fix invocation
  examples: fix JSON-to-CSV description
2021-09-15 13:06:27 +00:00
Jan Kundrát
cb0a410418 Merge "Take explicitly set out_voa value into account in power calculation" 2021-09-15 13:05:12 +00:00
Jan Kundrát
f250990a49 requests: avoid TypeError
When printing a Request which had its baud_rate set and bit_rate unset,
the code would hit a TypeError.

Fixes: https://review.gerrithub.io/c/Telecominfraproject/oopt-gnpy/+/521471
Change-Id: I7f6cc553c07fd8e7d1ef32866d9f711a32016744
2021-09-15 14:59:59 +02:00
EstherLerouzic
280443f17f add an invocation test with power saturation
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I0a6a8442326fdfb9c9922abf05aeee52cfa42090
2021-09-15 14:59:59 +02:00
EstherLerouzic
6f62251cb4 Update a roadm test to include more cases for power handling
add several power reference setting tests to the existing test

the test only checks the  power level out of roadm B.
if previous node is a preamp, power level is the one specified in
target_pch_out_db.
if previous node is a fused , power level at roadm input is below
target_pch_out_db and roadm can not increase this power (no amp).
then expected outpower is in_power, which should be equal to -22 + power_dBm
on this particular node.

nota bene
currently, no minimum losss is coded on roadm, so that the applied loss
is 0 dB, and roadm does not affect power in this case.
This behaviour is not correct and should be changed in the future.
But for now, I am only concentrating on existing behaviour tests.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I34fe2dcf2d355b291c27745ab511d3d77057dd94
2021-09-15 13:04:09 +02:00
Jan Kundrát
5ad54879b1 Merge "docs: cross-reference topology section with XLS/JSON" 2021-09-15 10:39:46 +00:00
Jan Kundrát
825d37c05c tests: add OpenROADMv5 example propagation
These numbers "appear to look sane" as per [1]. Let's make sure that the
config files are CI-tested.

[1] https://review.gerrithub.io/c/Telecominfraproject/oopt-gnpy/+/522340/1#message-b40ac2c839f138237139407374452f254c3b0b0d

Change-Id: Iad346a14ed12b984f90a40629c0339fa0823290e
2021-09-15 12:33:18 +02:00
Jan Kundrát
3ac9f90914 OpenROADM: mark example config files as v4 explicitly
The recent commit has added support for OpenROADM v5, the latest
published optical spec sheet. Given that the upstream project has
released v10 YANG files (but still just v3, v4 and v5 XLS sheets with
optical performance numbers), I think it would be rather misleading to
have both versioned and non-versioned config files -- especially when
the unversioned one refers to the oldest release, not the newest one.

Change-Id: I04109341724b51d276660d400c923dc28561aef2
2021-09-15 12:31:05 +02:00
Jan Kundrát
dbfbf115ff examples: json-to-csv: fix invocation
The two filenames are actually mandatory, not optional.

Reported-by: 张路 <luzhang@bupt.edu.cn>
Change-Id: I4e40748430a602a2c06eb35c96e0a8267519b8b3
2021-09-15 12:14:19 +02:00
Jan Kundrát
ad2590962b examples: fix JSON-to-CSV description
Change-Id: Id35aeffe4e71da663f3c91298cb166cee5646c98
2021-09-15 12:13:10 +02:00
Jan Kundrát
a9d530c776 Merge "Check for non-existing N or M values when comparing requests" 2021-09-14 14:39:12 +00:00
Jan Kundrát
f255c31f1f Merge "Add option to cli examples for disabling auto-insertion of EDFAs" 2021-09-14 13:15:14 +00:00
Jan Kundrát
80ec05f84c Merge "Limit target length when splitting fibers to max_length in eqpt config" 2021-09-07 09:29:15 +00:00
Jonas Mårtensson
22541d65e4 Check for non-existing N or M values when comparing requests
The compare_reqs function checks if N and M values of the requests are
None but these attributes may not exist e.g. if a service file does not
define an "effective-freq-slot" for a request.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I786aad97ed658cd703694f164a87525d77b51fe1
2021-08-31 22:52:10 +02:00
Jan Kundrát
26fcf0ff6e CI: docker: releases should update the latest tag
We've been accidentally not updating the `latest` tag on Docker hub
after switching from Travis CI to GitHub actions. Traditionally, we've
assumed the `latest` points to the "latest release", so this patch makes
it simple and will call any pushed "version tag" as the `latest`. This
will become a problem if/when we start maintaining multiple releases,
but I think it's a safe approach for now.

As before, there's also the `master` tag which always points to the,
well, master branch of the repo.

Change-Id: I14c08b12010986d4327bd9d685619a98cfec370f
2021-08-31 16:16:01 +02:00
Jan Kundrát
1c32e437a2 docs: README: link to pretty installation docs
The docs used to point to in-the-tree source code of the documentation
in the RST format. When navigating from the landing page on GitHub, this
led to GitHub's rendering of the RST, which is rather incomplete. Just
point to readthedocs to get a nice doc format.

Change-Id: I985a8fd1688337b4e0e47cdb09b666cf4e96cc1b
2021-08-31 12:59:01 +02:00
EstherLerouzic
718007b3de Do not agregate requests which have non None N,M spectrum values
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ib103e3292887c863e7c1cd785ffbffba629d0d02
2021-08-31 11:31:16 +02:00
EstherLerouzic
4d6c06340f Add a complementary set of tests on the N and M values
- if specified, they must be used except:
    - if N and M are not consistant (eg M smaller than the required
      spectrum for the demand)
    - if N value and M value lead to occupation outside of the band
    - if spectrum is occupied
- if any of them is None, the program uses the first fit strategy for M and
the path_bandwidth value to compute minimum required M

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I9160ffb116dd9d7d53ad80638826b609a1367446
2021-08-31 11:31:16 +02:00
EstherLerouzic
bad893bf86 Remove this test which should never happen
this sort of verification should be covered by automatic tests
since this is a verification of the correct behaviour of the
spectrum_selection function

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I76dd3bcad74085e1cd36ecb6503dad0271b61b80
2021-08-31 11:31:16 +02:00
EstherLerouzic
75e7fca8e4 change M value from 0 to None in case of blocking
Change tests based on M==0 value for response creation and use
instead the blocking_reason attribute existence
result element should have non null M value if request is not blocked.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I67e4222cf9d014201e91d3aefd3624b001264e03
2021-08-31 11:31:16 +02:00
EstherLerouzic
4e38ba98ab Change N value from 0 to None in case of NO_SPECTRUM
in case spectrum can not be assigned default value for N
was set to 0, which is not correct (N is a meaningfull value for
center frequency index). This changes replaces this default
value with None

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ibe642682e48d09f340d53e2092f172de6aa7cc90
2021-08-31 11:31:16 +02:00
EstherLerouzic
fdcdfca589 refactor spectrum assignment
use the new function compute_spectrum_slot_vs_bandwidth

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: If045fe53550dbde57c56fd4e8b99275c0757ea2b
2021-08-31 11:31:16 +02:00
EstherLerouzic
299ca10a47 Add a consistency check on request before any propagation is performed
In case user defines trx_mode, it is possible to check consistency of
nb of required slots and the total requested path_bandwidth and raise
a service error, before staring any propagation computation.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I543cab581280faef5d6072eb172da136f2542492
2021-08-31 11:31:16 +02:00
EstherLerouzic
c0b7bf714e Remove "null" entries of effective-freq-slot
before 'effective-freq-slot' was just ignored, and filled with "null" strings.
this is no longer supported

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I24d30de91b8b29d37f6ba81220d3cad5aabb6781
2021-08-31 11:31:16 +02:00
EstherLerouzic
7f7c568160 Enabling the reading of N and M value from the json request
For this commit only the first element from the {N, M} list is read
and assigned.

This is better than not reading this value at all.

the commit also updates test_files and test data files with correct
values for the effective_freq_slot attribute

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I1e60fe833ca1092b40de27c8cbfb13083810414e
2021-08-31 11:31:07 +02:00
EstherLerouzic
9bf6ed953a Add a new cause of blocking
if the user has specified a nb of slot and has not specified a mode
it may happen that the nb of slot is finally not large enough to support
the requested traffic: then blocking reason is 'NOT_ENOUGH_RESERVED_SPECTRUM'

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I8d4c4df5fa97e37aefac8d9ee0d93c901505fa55
2021-08-31 11:23:08 +02:00
EstherLerouzic
e68dc39ddd Avoid overwriting blocking reason
When a path is blocked for 'NO_FEASIBLE_MODE' reason, and bidir is true,
the request attributes are filled with the last explored mode values
(baudrate notably), and the reversed path is propagated with this last
explored mode specs. if this reversed path is also not feasible the blocking
reason was overwritten with a 'MODE_NOT_FESIBLE' reasonn, because
baudrate is filled in the request attribute.

This change ensure that the blocking reason (if it exists) is not overwritten.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: If80a37d77e2b967a327562c733a44e7f78f1c544
2021-08-31 11:23:08 +02:00
Jan Kundrát
f8007b41d1 Merge changes from topic "refactor disjunction"
* changes:
  adding another set of test for disjunction
  Minor refactor
  Refactor step4 of the compute_path_dsjctn
2021-08-31 09:19:57 +00:00
Jonas Mårtensson
228125029e Add an eqpt config file matching latest OpenROADM MSA version
This adds a separate OpenROADM eqpt config file corresponding to the
latest version of the MSA:

https://0201.nccdn.net/4_2/000/000/071/260/20210629_open-roadm_msa_specification_ver5.0.xlsx

The existing config file corresponding to the old version is kept for
backward compatibility. The new version introduces the following
changes:

* New definition of incremental OSNR for a ROADM based on polynomial
  (see also previous commit).

* ROADM add path OSNR changed from 30 dB to 33 dB

* New transceiver mode: 200 Gbit/s, 31.57 Gbaud, DP-16QAM

* Tx OSNR for transceiver mode 100 Gbit/s, 31.57 Gbaud, DP-QPSK changed
  from 35 dB to 36 dB

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: Ieb7d33bd448ed9d0cb8320ed190019c9aa94c9ef
2021-08-16 22:10:21 +02:00
Jonas Mårtensson
d185e0c241 Add updated openroadm amp specifications to eqpt config
The latest version 5.0 of the OpenROADM MSA changes the definition
of the incremental OSNR mask of a ROADM to a polynomial:

https://0201.nccdn.net/4_2/000/000/071/260/20210629_open-roadm_msa_specification_ver5.0.xlsx

A new preamp type variety corresponding to the updated specification is
added to the default eqpt config file while also keeping the type
variety corresponding to the old specification for backward
compatibility.

The updated specification includes both typical and worst case values.
These are added as separate type varieties to let the user choose which
values to use. Note that the specification with typical values is
identical to the existing OpenROADM ILA standard type variety but a new
type variety is still added for clarity.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I2de5e9db69f9ae3b218e30a3b246bd9b83cef458
2021-08-16 20:57:42 +02:00
Jonas Mårtensson
357bbec257 Limit target length when splitting fibers to max_length in eqpt config
Auto-design tries to split fibers longer than the max_length parameter
specified in the eqpt config file. When calculating new fiber lengths,
it uses a target_length parameter, which is currently hardcoded to 90
km. If the user specifies a max_length that is shorter than the
target_length and the topology includes any fiber that is longer than
the max_length but shorter than the hardcoded target_length, the
calculation crashes with a ZeroDivisionError. This patch limits the
target_length parameter so that it can't be longer than max_length.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: Id0851fcf79ab0b1a05832e22ee7e9cf63691446c
2021-08-10 14:10:25 +02:00
EstherLerouzic
d25e98c567 adding another set of test for disjunction
Previous set of tests did not correctly check the combinations of
disjunction and route constraint. This new set tests specific cases
with several demands in one synchronization vector with and without
route constraint, and the case where both disjunction constraint and
route can not be met (STRICT and LOOSE cases)

+ minor refactor on test_disjunction

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Id5a5902e6945185922ce5743ac97d15dbc777af2
2021-08-02 18:02:14 +02:00
Jan Kundrát
397411690e Merge "Nicer printing of Edfa elements when parameters are None" 2021-06-29 16:06:02 +00:00
Jonas Mårtensson
4ab6f8cb1b Nicer printing of Edfa elements when parameters are None
When delta_p or target_pch_out_db is None (resulting e.g. from
operating in gain mode) the current logic replaces a whole printed
line with 'None' which does not look very nice in the script outputs.
With this patch, the parameter information is kept in the printout
like this:

  Delta_P (dB):           None
  target pch (dBm):       None

Change-Id: Ie52ce7353a708a174cf9d769918a6136eefbf444
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Fixes: 225cafa8 (Floating point formatting of elements' operational parameters)
2021-06-29 11:37:05 +00:00
Jan Kundrát
44aff147db Merge pull request #410 from jktjkt/ci-on-windows
CI: test on Windows
2021-06-29 13:33:48 +02:00
Jan Kundrát
a36b139065 CI: GitHub: push coverage reports to codecov
Since Travis CI builds stopped, nothing was pushing updates to codecov.

Change-Id: Ia9a15a1a956c41586eda9ab85bd1f22fa798e960
2021-06-17 20:32:50 +02:00
Jan Kundrát
141fc66d47 CI: GitHub: fix conditional syntax
This is strange because the docs say that it's required [1], but during
my testing it appeared to work just fine without this wrapping. Anyway,
let's follow the docs.

[1] https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#about-contexts-and-expressions

Change-Id: I6a03f9423d7f10d03687759de71f25aed15bd172
2021-06-17 19:52:43 +02:00
Jan Kundrát
53f29957fd CI: GitHub: don't fail pushing PyPI releases from forks
...and also unify the condition with what syntax that the Docker build
job is using.

Change-Id: Ia6e3fe308093bac144441f4d9a33df93ffdca06f
2021-06-17 19:05:52 +02:00
Jan Kundrát
9f3995ee20 CI: GitHub: don't try to access Docker Hub without proper credentials
Change-Id: I9dccb2d12ad97d54fa0f5bfa0d8db63cc5deb2ec
2021-06-17 18:49:07 +02:00
Jan Kundrát
0cf45bd102 CI: test on Windows
I've been getting reports that the test suite is broken on Windows (the
usual set of problems such as CRLF line endings and backslashes in path
names), so let's make sure we have a way of reproducing this.
Unfortunately, we don't have a Windows image in Zuul, so this will be a
post-merge CI I'm afraid :(.

Change-Id: Ibd539764d6e40693b95a9b231324bd0216e4a207
2021-06-17 18:18:16 +02:00
Jan Kundrát
55932ee3e9 tests: handle Unicode properly for "expected console output"
Let's use the text mode everywhere because Unicode codepoints is what
matters. The only catch on Windows turned out to be the default file IO
encoding; forcing UTF-8 there fixes all issues in the CI (and it makes
sense because that file was written out in a UTF-8 locale, and the
system which runs the test suite might be set to something else.

This was a rather interesting debugging experience; passing logs over
the web and handling "strange" characters as utf-8 did not help.

Change-Id: I1fdbe3a115458558b27a81f9eab8e58c9605bae7
Bug: https://github.com/Telecominfraproject/oopt-gnpy/issues/358
2021-06-17 18:14:36 +02:00
Jan Kundrát
797a0856ec tests: Use a portable /dev/null file name
Bug: https://github.com/Telecominfraproject/oopt-gnpy/issues/358
Change-Id: Icbca94682ce0ded860ba6397e4445651b6a61f32
2021-06-17 16:20:49 +02:00
Jan Kundrát
3fa53adc4d Don't print file name when handling requests
We have a test which compares the raw output of GNPy against a fixed
expected output. That comparison of course chokes when forward slashes
and backslashes are used, which breaks the test suite on Windows. Let's
try to solve this by always using forward slashes if possible. The way
to go is via pathlib's as_posix(), but that one can possibly return an
absolute path -- which cannot work in a test suite, obviously. So one
can workaround that via calling a Path.relative_to(), but that one
chokes on paths which require at least one "path up" component (`..`).
I posted a patch which use brute force here, but Jonas is right, better
just don't print that output in the test suite in the first place.

Change-Id: I762ddb58a2042120c7b20414152a06a3ed72048d
Bug: https://github.com/Telecominfraproject/oopt-gnpy/issues/358
2021-06-17 16:20:38 +02:00
Jan Kundrát
bcb5e6bb60 Merge changes I60b4e3bf,I2bb3b5a0,I344c4a03
* changes:
  tests: requests: rely on pytest's own dict support
  tests: enable pytest's builtin multiline diffing
  utils: document round2float
2021-06-17 14:15:32 +00:00
Jan Kundrát
6380f8f37a Merge "CI: Zuul: Introduce Python 3.9, and switch all to Fedora 34" 2021-06-11 13:44:58 +00:00
Jan Kundrát
93869d6cb5 CI: Zuul: Introduce Python 3.9, and switch all to Fedora 34
Change-Id: I40e66ca0eca21b91809ea0f94291f3bd77bc53d2
Depends-on: https://review.gerrithub.io/c/Telecominfraproject/oopt-zuul-jobs/+/518622
2021-06-11 15:35:46 +02:00
Jonas Mårtensson
ce51a4d160 Take explicitly set out_voa value into account in power calculation
As mentioned in GitHub issue #409, an out_voa value for an EDFA
explicitly set in the topology file is not taken into account by
auto-design when calculating target power and gain. I think it is
more logical if the target power resulting from the optimization
algorithm represents the desired power into the fiber. This is also
more consistent with the behaviour for an automatically set out_voa
value when out_voa_auto is set to true.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I7e58b61d0bf30728c39d36404619dbe370c12f2b
2021-06-11 08:32:00 +02:00
Jan Kundrát
601e228bb6 tests: requests: rely on pytest's own dict support
When `pytest` is run with `-vv`, it shows a diff of multiline strings
and dict just fine. The only drawback is that there's the raw string
with newlines shown as "\n", however, *then* the nice diff pretty
printing kicks in, and the result is:

 E                 Common items:
 E                 {'response-id': '5'}
 E                 Differing items:
 E                 {'path-properties': {'path-metric': [{'accumulative-value': 21.68, 'metric-type': 'SNR-bandwidth'}, {'accumulative-val...EDFA', 'link-tp-id': 'east edfa in Rennes_STA to Stbrieuc', 'node-id': 'east edfa in Rennes_STA to Stbrieuc'}}}, ...]}} != {'path-properties': {'path-metric': [{'accumulative-value': 21.68, 'metric-type': 'SNR-bandwidth'}, {'accumulative-val...EDFA', 'link-tp-id': 'east edfa in Rennes_STA to Stbrieuc', 'node-id': 'east edfa in Rennes_STA to Stbrieuc'}}}, ...]}}
 E                 Full diff:
 E                   {
 E                    'path-properties': {'path-metric': [{'accumulative-value': 21.68,
 E                                                         'metric-type': 'SNR-bandwidth'},
 E                                                        {'accumulative-value': 28.77,
 E                                                         'metric-type': 'SNR-0.1nm'},
 E                                                        {'accumulative-value': 23.7,
 E                                                         'metric-type': 'OSNR-bandwidth'},
 E                                                        {'accumulative-value': 30.79,
 E                                                         'metric-type': 'OSNR-0.1nm'},
 E                                                        {'accumulative-value': 0.0019952623149688794,
 E                                                         'metric-type': 'reference_power'},
 E                                                        {'accumulative-value': 20000000000.0,
 E                                                         'metric-type': 'path_bandwidth'}],
 ...
 ... now, it's a bit annoying that there's too much output, but
 ... that's just for context; the offending lines will be properly
 ... marked, see --\
 ...               |
 ...               v
 ...
 E                                                               {'path-route-object': {'index': 17,
 E                 -                                                                    'num-unnum-hop': {'gnpy-node-type': 'transceiver',
 E                 ?                                                                                       ^ ^^^^^^^  - ^      ^^^^^^^^^ -
 E                 +                                                                    'num-unnum-hop': {'link-tp-id': 'trx '
 E                 ?                                                                                       ^^ ^   ^^^      ^^
 E                 -                                                                                      'link-tp-id': 'trx '
 E                                                                                                                      'Lannion_CAS',
 E                                                                                                        'node-id': 'trx '
 E                                                                                                                   'Lannion_CAS'}}},
 E                                                               {'path-route-object': {'index': 18,
 E                                                                                      'label-hop': {'M': 6,
 E                                                                                                    'N': -274}}},
 E                                                               {'path-route-object': {'index': 19,
 E                                                                                      'transponder': {'transponder-mode': 'mode '
 E                                                                                                                          '2',
 E                                                                                                      'transponder-type': 'vendorA_trx-type1'}}}]},
 E                    'response-id': '5',
 E                   }

 tests/test_parser.py:312: AssertionError

Change-Id: I60b4e3bfa432a720a381bf2c0a9f0288e989dab2
2021-06-09 21:17:21 +02:00
Jan Kundrát
3f58cbd559 tests: enable pytest's builtin multiline diffing
...because it works on strings while doesn't work on byte arrays.

Change-Id: I2bb3b5a0a3d6ad965321c58fb90a02341db66d0f
2021-06-09 21:17:21 +02:00
Jan Kundrát
2e3274ac78 utils: document round2float
Change-Id: I344c4a03e7d3e0614e0fc3307b12af359c61b882
2021-06-09 21:17:21 +02:00
Jan Kundrát
e33144f8cc Merge "refactoring: OpenROADM: store the NF model of a premp/booster" 2021-06-09 18:54:56 +00:00
Jan Kundrát
fd1e3f0f61 Merge "Do not load equipment['SI']['default'].power_range_db in the gain mode" 2021-06-09 18:31:48 +00:00
Jan Kundrát
80c41264cf CI: Docker: remove the dev- prefix
We have never used that one before, so let's not introduce it now.
Noise, noise, noise, sorry.

Change-Id: I336b3e73f7dd61b14615412ea52ec90986468fcf
2021-06-09 20:22:58 +02:00
Jan Kundrát
a051a5723b Merge tag 'v2.3.1'
GNPy 2.3.1

Just some release automation on top of v2.3. If you're already on v2.3,
there's no need to update; this release does not contain any
user-visible changes.

Change-Id: I0473a0bb43be596cf376cf18eb8a546b53aa0214
2021-06-09 20:14:11 +02:00
Jan Kundrát
72f300ab94 CI: Build and upload a PyPI package upon tagging
Change-Id: Idcd84a9f8e03ffc4ea27b37add8e5e59d1da7509
2021-06-09 20:03:57 +02:00
Jan Kundrát
2c3b0d8c82 CI: GitHub: build Docker images
Pushes to master should create a "development" tag. Tags should create
something prettier.

We need the git checkout for proper package versioning, hence that magic
`context` thing, and also that magic `echo`.

Change-Id: I79fcb800cc079e8486c3795f36aa1993676cee49
2021-06-09 19:53:53 +02:00
Jan Kundrát
11dab614a9 CI: GitHub: prevent duplicate runs
Change-Id: I09d55937d553d097b0440bb3c7d0f7dcc709abe3
2021-06-09 19:53:53 +02:00
Jan Kundrát
11d88bf09a CI: random bikeshedding about the name
Change-Id: I7ce2b32cb30f2b1a5e402c1dc999b02d2a1588de
2021-06-09 19:46:23 +02:00
Jan Kundrát
af3cc4736e setup metadata: fix description
Our landing page on PyPI was not displaying the longer version of the
README, just a one-sentence summary. It turns out that `pbr` can indeed
read the README file, but specifying the `description` overrides that
with no warning. Yay.

Change-Id: Ic03412928fe09f5edab4a7b9f4297a485a740cd0
2021-06-09 16:34:24 +02:00
Jan Kundrát
50cb82ee18 packaging: fix the long description
Fixes: e45a54c2 (README: rewrite in Markdown)
Change-Id: Ifefd78ba1008f0a37299dca07b53b5481ebeac27
2021-06-09 16:34:17 +02:00
Jan Kundrát
c80aca6696 CI: GitHub: ensure tags are present
We're using PBR, so let's make sure we don't get a package that's marked
as version 0.0.0.

Bug: https://github.com/actions/checkout/issues/217
Change-Id: Icd8264a798f9a1a404e21a9b64317c57662d53fe
2021-06-09 16:34:11 +02:00
Jan Kundrát
dfca35d4ae CI: always try to build a release wheel
This might be a wee bit controversial, I guess, because the Zuul jobs
look like there's a dedicated playbook for that
(playbooks/python/release.yaml). However, that would be one extra VM
launch, which feels wasteful. Let's waste the CPU cycles elsewhere --
during each "regular test build", produce a wheel as well.

It looks that these "wheels" are *the* format for distributing Python
packages now -- including the source code, of course. Since there's no
real support for tag review in Gerrit, I don't think I need Zuul for
release management, either, so I'll just rely on GitHub actions for
release upload, I guess. And for that, I need to "somehow" create a
wheel anyway, so let's just do this all the time to ensure that it
really works and never stops working.

Change-Id: Ib86852a386673cd4929a8059b19fa527cd4d5955
2021-06-09 16:34:02 +02:00
Jan Kundrát
1fbdaef58a CI: Run on GitHub Actions
We have Zuul, and we're happy with it; however, every now and then
there's a problem with the managed infrastructure, and there's also
people who contribute patches as GitHub PRs.

Change-Id: I405c5806ed9ad2e7f59f9b2394daf068b373e425
2021-06-09 16:33:50 +02:00
Jan Kundrát
bd025f3af4 setup metadata: fix description
Our landing page on PyPI was not displaying the longer version of the
README, just a one-sentence summary. It turns out that `pbr` can indeed
read the README file, but specifying the `description` overrides that
with no warning. Yay.

Change-Id: Ic03412928fe09f5edab4a7b9f4297a485a740cd0
2021-06-09 16:30:38 +02:00
Jan Kundrát
c3e546abe3 packaging: fix the long description
Fixes: e45a54c2 (README: rewrite in Markdown)
Change-Id: Ifefd78ba1008f0a37299dca07b53b5481ebeac27
2021-06-09 02:32:48 +02:00
Jan Kundrát
9427d0b139 CI: GitHub: ensure tags are present
We're using PBR, so let's make sure we don't get a package that's marked
as version 0.0.0.

Bug: https://github.com/actions/checkout/issues/217
Change-Id: Icd8264a798f9a1a404e21a9b64317c57662d53fe
2021-06-09 01:17:34 +02:00
Jan Kundrát
89f5b12f7e CI: always try to build a release wheel
This might be a wee bit controversial, I guess, because the Zuul jobs
look like there's a dedicated playbook for that
(playbooks/python/release.yaml). However, that would be one extra VM
launch, which feels wasteful. Let's waste the CPU cycles elsewhere --
during each "regular test build", produce a wheel as well.

It looks that these "wheels" are *the* format for distributing Python
packages now -- including the source code, of course. Since there's no
real support for tag review in Gerrit, I don't think I need Zuul for
release management, either, so I'll just rely on GitHub actions for
release upload, I guess. And for that, I need to "somehow" create a
wheel anyway, so let's just do this all the time to ensure that it
really works and never stops working.

Change-Id: Ib86852a386673cd4929a8059b19fa527cd4d5955
2021-06-09 00:49:52 +02:00
Jan Kundrát
9d2c10e267 CI: Run on GitHub Actions
We have Zuul, and we're happy with it; however, every now and then
there's a problem with the managed infrastructure, and there's also
people who contribute patches as GitHub PRs.

Change-Id: I405c5806ed9ad2e7f59f9b2394daf068b373e425
2021-06-08 22:34:00 +02:00
Jan Kundrát
305620e5dd Do not load equipment['SI']['default'].power_range_db in the gain mode
It is not used, so don't check it.

Change-Id: I309638ac8e647ed9f507016a116d9c8d0342c32d
2021-06-07 13:51:40 +00:00
Jan Kundrát
c91c5d622f Bump the minimal required Python to 3.8
We discussed this at one of the recent coder calls; the motivation
includes better mypy type hint support, especially in numpy, but also in
the language core, and of course the dataclasses.

Change-Id: I8ffee28c33f167cbcba978c85486e58a1b8c99be
2021-06-07 10:01:49 +00:00
Jonas Mårtensson
08c922a5e5 Add option to cli examples for disabling auto-insertion of EDFAs
The auto-design feature inserts EDFAs after ROADMs and fibers when they
are not already present in the input topology file. This functionality
can be locally disabled by manually adding a Fused element in the
topology. This patch adds an option to the cli example scripts,
"--no-insert-edfas", which globally disables insertion of EDFAs as well
as automatic splitting of fibers.

Change-Id: If40aa6ac6d8b47d5e7b6f8eabfe389e8258cbce6
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2021-06-01 16:18:13 +02:00
Jan Kundrát
efd7468d42 docs: cross-reference topology section with XLS/JSON
Suggested-by: Melin, Stefan M. <Stefan.Melin@teliacompany.com>
Change-Id: I01defca88e0355af39fd6f97e5a69fc1bb5f8f73
2021-02-16 18:04:21 +01:00
EstherLerouzic
902cfa11a7 Minor refactor
Correct comments

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I99874f08625bade15223cfbbd3cd933419fb5c66
2021-02-04 10:02:51 +01:00
EstherLerouzic
24c6acc027 Refactor step4 of the compute_path_dsjctn
Previous implementation selected last candidate in case both routing
and disjunction constraints could not be applied. The new implementation
elaborates an alternate list where each feasible paths satisfyng
disjunction constraint but not route constraint is recorded. The algorithm
then preferably selects a feasible path that satisfies all constraints and
if none is found and route constraint is LOOSE, the first set of paths
that satisfy disjunction is selected (instead of the last one).

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Iba44397d105006a98539494d821cc83dc3e3bbd9
2021-02-04 10:02:51 +01:00
164 changed files with 36518 additions and 9580 deletions

View File

@@ -1,47 +0,0 @@
#!/bin/bash
set -e
IMAGE_NAME=telecominfraproject/oopt-gnpy
IMAGE_TAG=$(git describe --tags)
ALREADY_FOUND=0
docker pull ${IMAGE_NAME}:${IMAGE_TAG} && ALREADY_FOUND=1
if [[ $ALREADY_FOUND == 0 ]]; then
docker build . -t ${IMAGE_NAME}
docker tag ${IMAGE_NAME} ${IMAGE_NAME}:${IMAGE_TAG}
# shared directory setup: do not clobber the real data
mkdir trash
cd trash
docker run -it --rm --volume $(pwd):/shared ${IMAGE_NAME} gnpy-transmission-example
else
echo "Image ${IMAGE_NAME}:${IMAGE_TAG} already available, will just update the other tags"
fi
docker images
do_docker_login() {
echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
}
if [[ "${TRAVIS_PULL_REQUEST}" == "false" ]]; then
if [[ "${TRAVIS_BRANCH}" == "develop" || "${TRAVIS_BRANCH}" == "docker" ]]; then
echo "Publishing latest"
docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:latest
do_docker_login
if [[ $ALREADY_FOUND == 0 ]]; then
docker push ${IMAGE_NAME}:${IMAGE_TAG}
fi
docker push ${IMAGE_NAME}:latest
elif [[ "${TRAVIS_BRANCH}" == "master" ]]; then
echo "Publishing stable"
docker tag ${IMAGE_NAME}:${IMAGE_TAG} ${IMAGE_NAME}:stable
do_docker_login
if [[ $ALREADY_FOUND == 0 ]]; then
docker push ${IMAGE_NAME}:${IMAGE_TAG}
fi
docker push ${IMAGE_NAME}:stable
fi
fi

145
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,145 @@
on:
push:
pull_request:
branches:
- master
name: CI
jobs:
build:
name: Tox test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: fedora-python/tox-github-action@v37.0
with:
tox_env: ${{ matrix.tox_env }}
dnf_install: ${{ matrix.dnf_install }}
- uses: codecov/codecov-action@v3.1.1
if: ${{ endswith(matrix.tox_env, '-cover') }}
with:
files: ${{ github.workspace }}/cover/coverage.xml
strategy:
fail-fast: false
matrix:
tox_env:
- py38
- py39
- py310
- py311
- py312-cover
include:
- tox_env: docs
dnf_install: graphviz
pypi:
needs: build
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && github.repository_owner == 'Telecominfraproject' }}
name: PyPI packaging
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-python@v4
name: Install Python
with:
python-version: '3.12'
- uses: casperdcl/deploy-pypi@bb869aafd89f657ceaafe9561d3b5584766c0f95
with:
password: ${{ secrets.PYPI_API_TOKEN }}
pip: wheel -w dist/ --no-deps .
upload: true
docker:
needs: build
if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) && github.repository_owner == 'Telecominfraproject' }}
name: Docker image
runs-on: ubuntu-latest
steps:
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
username: jktjkt
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Extract tag name
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
id: extract_pretty_git
run: echo ::set-output name=GIT_DESC::$(git describe --tags)
- name: Build and push a container
uses: docker/build-push-action@v2
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
with:
context: .
push: true
tags: |
telecominfraproject/oopt-gnpy:${{ steps.extract_pretty_git.outputs.GIT_DESC }}
telecominfraproject/oopt-gnpy:master
- name: Extract tag name
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
id: extract_tag_name
run: echo ::set-output name=GIT_DESC::${GITHUB_REF/refs\/tags\//}
- name: Build and push a container
uses: docker/build-push-action@v2
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
with:
context: .
push: true
tags: |
telecominfraproject/oopt-gnpy:${{ steps.extract_tag_name.outputs.GIT_DESC }}
telecominfraproject/oopt-gnpy:latest
other-platforms:
name: Tests on other platforms
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python_version }}
- run: |
pip install --editable .[tests]
pytest -vv
strategy:
fail-fast: false
matrix:
include:
- os: windows-2019
python_version: "3.10"
- os: windows-2022
python_version: "3.11"
- os: windows-2022
python_version: "3.12"
- os: macos-13
python_version: "3.12"
- os: macos-14
python_version: "3.12"
paywalled-platforms:
name: Tests on paywalled platforms
if: github.repository_owner == 'Telecominfraproject'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python_version }}
- run: |
pip install --editable .[tests]
pytest -vv
strategy:
fail-fast: false
matrix:
include:
- os: macos-13-xlarge # Apple M1 CPU
python_version: "3.12"

3
.lgtm.yml Normal file
View File

@@ -0,0 +1,3 @@
queries:
- exclude: py/clear-text-logging-sensitive-data
- exclude: py/clear-text-storage-sensitive-data

View File

@@ -1,5 +1,17 @@
version: 2
build: build:
image: latest os: ubuntu-22.04
tools:
python: "3.12"
apt_packages:
- graphviz
python: python:
version: 3.8 install:
requirements_file: docs/requirements.txt - method: pip
path: .
extra_requirements:
- docs
sphinx:
configuration: docs/conf.py

View File

@@ -1,29 +0,0 @@
dist: focal
os: linux
language: python
services: docker
python:
- "3.6"
- "3.7"
- "3.8"
- "3.9"
before_install:
- sudo apt-get -y install graphviz
install: skip
script:
- pip install --editable .
- pip install pytest-cov rstcheck
- pytest --cov-report=xml --cov=gnpy -v
- pip install -r docs/requirements.txt
- rstcheck --ignore-roles cite *.rst
- sphinx-build -W --keep-going docs/ x-throwaway-location
after_success:
- bash <(curl -s https://codecov.io/bash)
jobs:
include:
- stage: test
name: Docker image
script:
- git fetch --unshallow
- ./.docker-travis.sh
- docker images

View File

@@ -2,24 +2,33 @@
- project: - project:
check: check:
jobs: jobs:
- tox-py38-cover - tox-py38:
vars:
ensure_tox_version: '<4'
- tox-py39:
vars:
ensure_tox_version: '<4'
- tox-py310-cover:
vars:
ensure_tox_version: '<4'
- tox-docs-f36:
vars:
ensure_tox_version: '<4'
- coverage-diff: - coverage-diff:
voting: false voting: false
dependencies: dependencies:
- tox-py38-cover-previous - tox-py310-cover-previous
- tox-py38-cover - tox-py310-cover
vars: vars:
coverage_job_name_previous: tox-py38-cover-previous coverage_job_name_previous: tox-py310-cover-previous
coverage_job_name_current: tox-py38-cover coverage_job_name_current: tox-py310-cover
- tox-linters-diff-n-report: - tox-linters-diff-n-report:
voting: false voting: false
- tox-py36-el8 vars:
- tox-docs-f32 ensure_tox_version: '<4'
- tox-py38-cover-previous - tox-py310-cover-previous:
gate: vars:
jobs: ensure_tox_version: '<4'
- tox-py38-f32
- tox-docs-f32
tag: tag:
jobs: jobs:
- oopt-release-python: - oopt-release-python:

View File

@@ -11,18 +11,21 @@ To learn how to contribute, please see CONTRIBUTING.md
- Brian Taylor (Facebook) <briantaylor@fb.com> - Brian Taylor (Facebook) <briantaylor@fb.com>
- David Boertjes (Ciena) <dboertje@ciena.com> - David Boertjes (Ciena) <dboertje@ciena.com>
- Diego Landa (Facebook) <dlanda@fb.com> - Diego Landa (Facebook) <dlanda@fb.com>
- Emmanuelle Delfour (Orange) <WEDE7391@orange.com>
- Esther Le Rouzic (Orange) <esther.lerouzic@orange.com> - Esther Le Rouzic (Orange) <esther.lerouzic@orange.com>
- Gabriele Galimberti (Cisco) <ggalimbe@cisco.com> - Gabriele Galimberti (Cisco) <ggalimbe@cisco.com>
- Gert Grammel (Juniper Networks) <ggrammel@juniper.net> - Gert Grammel (Juniper Networks) <ggrammel@juniper.net>
- Giacomo Borraccini (Politecnico di Torino) <giacomo.borraccini@polito.it>
- Gilad Goldfarb (Facebook) <giladg@fb.com> - Gilad Goldfarb (Facebook) <giladg@fb.com>
- James Powell (Telecom Infra Project) <james.powell@telecominfraproject.com> - James Powell (Telecom Infra Project) <james.powell@telecominfraproject.com>
- Jan Kundrát (Telecom Infra Project) <jan.kundrat@telecominfraproject.com> - Jan Kundrát (Telecom Infra Project) <jkt@jankundrat.com>
- Jeanluc Augé (Orange) <jeanluc.auge@orange.com> - Jeanluc Augé (Orange) <jeanluc.auge@orange.com>
- Jonas Mårtensson (RISE) <jonas.martensson@ri.se> - Jonas Mårtensson (RISE) <jonas.martensson@ri.se>
- Mattia Cantono (Politecnico di Torino) <mattia.cantono@polito.it> - Mattia Cantono (Politecnico di Torino) <mattia.cantono@polito.it>
- Miguel Garrich (University Catalunya) <miquel.garrich@upct.es> - Miguel Garrich (University Catalunya) <miquel.garrich@upct.es>
- Raj Nagarajan (Lumentum) <raj.nagarajan@lumentum.com> - Raj Nagarajan (Lumentum) <raj.nagarajan@lumentum.com>
- Roberts Miculens (Lattelecom) <roberts.miculens@lattelecom.lv> - Roberts Miculens (Lattelecom) <roberts.miculens@lattelecom.lv>
- Sami Alavi (NUST) <sami.mansooralavi1999@gmail.com>
- Shengxiang Zhu (University of Arizona) <szhu@email.arizona.edu> - Shengxiang Zhu (University of Arizona) <szhu@email.arizona.edu>
- Stefan Melin (Telia Company) <Stefan.Melin@teliacompany.com> - Stefan Melin (Telia Company) <Stefan.Melin@teliacompany.com>
- Vittorio Curri (Politecnico di Torino) <vittorio.curri@polito.it> - Vittorio Curri (Politecnico di Torino) <vittorio.curri@polito.it>

View File

@@ -3,12 +3,12 @@
[![Install via pip](https://img.shields.io/pypi/v/gnpy)](https://pypi.org/project/gnpy/) [![Install via pip](https://img.shields.io/pypi/v/gnpy)](https://pypi.org/project/gnpy/)
[![Python versions](https://img.shields.io/pypi/pyversions/gnpy)](https://pypi.org/project/gnpy/) [![Python versions](https://img.shields.io/pypi/pyversions/gnpy)](https://pypi.org/project/gnpy/)
[![Documentation status](https://readthedocs.org/projects/gnpy/badge/?version=master)](http://gnpy.readthedocs.io/en/master/?badge=master) [![Documentation status](https://readthedocs.org/projects/gnpy/badge/?version=master)](http://gnpy.readthedocs.io/en/master/?badge=master)
[![CI](https://travis-ci.com/Telecominfraproject/oopt-gnpy.svg?branch=master)](https://travis-ci.com/Telecominfraproject/oopt-gnpy) [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/Telecominfraproject/oopt-gnpy/main.yml)](https://github.com/Telecominfraproject/oopt-gnpy/actions/workflows/main.yml)
[![Gerrit](https://img.shields.io/badge/patches-via%20Gerrit-blue)](https://review.gerrithub.io/q/project:Telecominfraproject/oopt-gnpy+is:open) [![Gerrit](https://img.shields.io/badge/patches-via%20Gerrit-blue)](https://review.gerrithub.io/q/project:Telecominfraproject/oopt-gnpy+is:open)
[![Contributors](https://img.shields.io/github/contributors-anon/Telecominfraproject/oopt-gnpy)](https://github.com/Telecominfraproject/oopt-gnpy/graphs/contributors) [![Contributors](https://img.shields.io/github/contributors-anon/Telecominfraproject/oopt-gnpy)](https://github.com/Telecominfraproject/oopt-gnpy/graphs/contributors)
[![Code Quality via LGTM.com](https://img.shields.io/lgtm/grade/python/github/Telecominfraproject/oopt-gnpy)](https://lgtm.com/projects/g/Telecominfraproject/oopt-gnpy/)
[![Code Coverage via codecov](https://img.shields.io/codecov/c/github/Telecominfraproject/oopt-gnpy)](https://codecov.io/gh/Telecominfraproject/oopt-gnpy) [![Code Coverage via codecov](https://img.shields.io/codecov/c/github/Telecominfraproject/oopt-gnpy)](https://codecov.io/gh/Telecominfraproject/oopt-gnpy)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3458319.svg)](https://doi.org/10.5281/zenodo.3458319) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3458319.svg)](https://doi.org/10.5281/zenodo.3458319)
[![Matrix chat](https://img.shields.io/matrix/oopt-gnpy:matrix.org)](https://matrix.to/#/%23oopt-gnpy%3Amatrix.org?via=matrix.org)
GNPy is an open-source, community-developed library for building route planning and optimization tools in real-world mesh optical networks. GNPy is an open-source, community-developed library for building route planning and optimization tools in real-world mesh optical networks.
We are a consortium of operators, vendors, and academic researchers sponsored via the [Telecom Infra Project](http://telecominfraproject.com)'s [OOPT/PSE](https://telecominfraproject.com/open-optical-packet-transport) working group. We are a consortium of operators, vendors, and academic researchers sponsored via the [Telecom Infra Project](http://telecominfraproject.com)'s [OOPT/PSE](https://telecominfraproject.com/open-optical-packet-transport) working group.
@@ -18,12 +18,14 @@ Together, we are building this tool for rapid development of production-grade ro
## Quick Start ## Quick Start
Install either via [Docker](docs/install.rst#install-docker), or as a [Python package](docs/install.rst#install-pip). Install either via [Docker](https://gnpy.readthedocs.io/en/master/install.html#using-prebuilt-docker-images), or as a [Python package](https://gnpy.readthedocs.io/en/master/install.html#using-python-on-your-computer).
Read our [documentation](https://gnpy.readthedocs.io/), learn from the demos, and [get in touch with us](https://github.com/Telecominfraproject/oopt-gnpy/discussions). Read our [documentation](https://gnpy.readthedocs.io/), learn from the demos, and [get in touch with us](https://github.com/Telecominfraproject/oopt-gnpy/discussions).
This example demonstrates how GNPy can be used to check the expected SNR at the end of the line by varying the channel input power: This example demonstrates how GNPy can be used to check the expected SNR at the end of the line by varying the channel input power:
[![Running a simple simulation example](https://telecominfraproject.github.io/oopt-gnpy/docs/images/transmission_main_example.svg)](https://asciinema.org/a/252295) ![Running a simple simulation example](docs/images/gnpy-transmission-example.svg)
GNPy can do much more, including acting as a Path Computation Engine, tracking bandwidth requests, or advising the SDN controller about a best possible path through a large DWDM network. GNPy can do much more, including acting as a Path Computation Engine, tracking bandwidth requests, or advising the SDN controller about a best possible path through a large DWDM network.
Learn more about this [in the documentation](https://gnpy.readthedocs.io/). Learn more about this [in the documentation](https://gnpy.readthedocs.io/), or give it a [try online at `gnpy.app`](https://gnpy.app/):
[![Path propagation at gnpy.app](docs/images/2022-04-12-gnpy-app.png)](https://gnpy.app/)

View File

@@ -7,11 +7,12 @@ There are weekly calls about our progress.
Newcomers, users and telecom operators are especially welcome there. Newcomers, users and telecom operators are especially welcome there.
We encourage all interested people outside the TIP to [join the project](https://telecominfraproject.com/apply-for-membership/) and especially to [get in touch with us](https://github.com/Telecominfraproject/oopt-gnpy/discussions). We encourage all interested people outside the TIP to [join the project](https://telecominfraproject.com/apply-for-membership/) and especially to [get in touch with us](https://github.com/Telecominfraproject/oopt-gnpy/discussions).
(contributing)=
## Contributing ## Contributing
`gnpy` is looking for additional contributors, especially those with experience planning and maintaining large-scale, real-world mesh optical networks. `gnpy` is looking for additional contributors, especially those with experience planning and maintaining large-scale, real-world mesh optical networks.
To get involved, please contact [Jan Kundrát](mailto:jan.kundrat@telecominfraproject.com) or [Gert Grammel](mailto:ggrammel@juniper.net). To get involved, please contact [Jan Kundrát](mailto:jkt@jankundrat.com) or [Gert Grammel](mailto:ggrammel@juniper.net).
`gnpy` contributions are currently limited to members of [TIP](http://telecominfraproject.com). `gnpy` contributions are currently limited to members of [TIP](http://telecominfraproject.com).
Membership is free and open to all. Membership is free and open to all.

View File

@@ -1,18 +1,19 @@
********************************************* .. _amp_models:
Amplifier models and configuration Amplifier models and configuration
********************************************* ==================================
1. Equipment configuration description 1. Equipment configuration description
####################################### --------------------------------------
Equipment description defines equipment types and parameters. Equipment description defines equipment types and parameters.
It takes place in the default **eqpt_config.json** file. It takes place in the equipment library such as **eqpt_config.json** file defined in example-data folder.
By default **gnpy-transmission-example** uses **eqpt_config.json** file and that By default **gnpy-transmission-example** uses **eqpt_config.json** file and that
can be changed with **-e** or **--equipment** command line parameter. can be changed with **-e** or **--equipment** command line parameter.
2. Amplifier parameters and subtypes 2. Amplifier parameters and subtypes
####################################### ------------------------------------
Several amplifiers can be used by GNpy, so they are defined as an array of equipment parameters in **eqpt_config.json** file. Several amplifiers can be used by GNpy, so they are defined as an array of equipment parameters in **eqpt_config.json** file.
@@ -28,9 +29,16 @@ Several amplifiers can be used by GNpy, so they are defined as an array of equip
- *"variable_gain"* - *"variable_gain"*
- *"fixed_gain"* - *"fixed_gain"*
- *"dual_stage"* - *"dual_stage"*
- *"multi_band"*
- *"openroadm"* - *"openroadm"*
*see next section for a full description of these models* *see next section for a full description of these models*
- *"default_config_from_json"*:
Use a custom per frequency dynamic gain tilt, gain and noise ripple arrays defined in the file specified with
this option, instead of the default values from GNPy.
- *"advanced_config_from_json"*: - *"advanced_config_from_json"*:
**This parameter is only applicable to the _"advanced_model"_ model** **This parameter is only applicable to the _"advanced_model"_ model**
@@ -135,7 +143,7 @@ Several amplifiers can be used by GNpy, so they are defined as an array of equip
3. Amplifier models 3. Amplifier models
####################################### -------------------
In an opensource and multi-vendor environnement, it is needed to support different use cases and context. Therefore several models are supported for amplifiers. In an opensource and multi-vendor environnement, it is needed to support different use cases and context. Therefore several models are supported for amplifiers.
@@ -179,7 +187,7 @@ In an opensource and multi-vendor environnement, it is needed to support differe
- *"variable_gain"* - *"variable_gain"*
This model is refered as an operator model because a lower level of knowledge is required. A full polynomial description of the NF cross the gain range is not required. Instead, NF_min and NF_max values are required and used by the code to model a dual stage amplifier with an internal mid stage VOA. NF_min and NF_max values are typically available from equipment suppliers data-sheet. This model is refered as an operator model because a lower level of knowledge is required. A full polynomial description of the NF cross the gain range is not required. Instead, NF_min and NF_max values are required and used by the code to model a dual stage amplifier with an internal mid stage VOA. NF_min and NF_max values are typically available from equipment suppliers data-sheet.
There is a default JSON file ”default_edfa_config.json”* to enforce 0 tilt and ripple values because GNpy core algorithm is a multi-carrier propogation. There is a default configuration to enforce 0 tilt and ripple values because GNPy core algorithm is a multi-carrier propagation.
- gain_ripple =[0,...,0] - gain_ripple =[0,...,0]
- nf_ripple = [0,...,0] - nf_ripple = [0,...,0]
- dgt = [...] generic dgt comb - dgt = [...] generic dgt comb
@@ -250,7 +258,7 @@ In an opensource and multi-vendor environnement, it is needed to support differe
- gain_min indicates to auto_design when this dual_stage should be used - gain_min indicates to auto_design when this dual_stage should be used
But unlike other models the 1st stage input will not be padded: it is always operated to its maximu gain and min NF. Therefore if gain adaptation and padding is needed it will be performed by the 2nd stage. But unlike other models the 1st stage input will not be padded: it is always operated to its maximum gain and min NF. Therefore if gain adaptation and padding is needed it will be performed by the 2nd stage.
.. code-block:: json .. code-block:: json
@@ -263,8 +271,18 @@ In an opensource and multi-vendor environnement, it is needed to support differe
"allowed_for_design": true "allowed_for_design": true
} }
- *"multiband"*
This model enables the definition of multiband amplifiers that consist of multiple single-band
amplifier elements, with each amplifier responsible for amplifying a different portion of the spectrum.
The types of single-band amplifiers that can be included in these multiband amplifiers are specified,
allowing for multiple options to be available for the same spectrum band (for instance, providing
several permitted type varieties for both the C-band and the L-band). The actual element utilizing the
type_variety must implement only one option for each band.
4. advanced_config_from_json 4. advanced_config_from_json
####################################### ----------------------------
The build_oa_json.py library in ``gnpy/example-data/edfa_model/`` can be used to build the json file required for the amplifier advanced_model type_def: The build_oa_json.py library in ``gnpy/example-data/edfa_model/`` can be used to build the json file required for the amplifier advanced_model type_def:

View File

@@ -1848,3 +1848,177 @@ month={Sept},}
title = {Telecom Infra Project}, title = {Telecom Infra Project},
url = {https://www.telecominfraproject.com}, url = {https://www.telecominfraproject.com},
} }
@ARTICLE{DAmicoJLT2022,
author={DAmico, Andrea and Correia, Bruno and London, Elliot and Virgillito,
Emanuele and Borraccini, Giacomo and Napoli, Antonio and Curri, Vittorio},
journal={Journal of Lightwave Technology},
title={Scalable and Disaggregated GGN Approximation Applied to a C+L+S Optical Network},
year={2022},
volume={40},
number={11},
pages={3499-3511},
doi={10.1109/JLT.2022.3162134}
}
@inproceedings{grammel2018physical,
title={Physical simulation environment of the telecommunications infrastructure project (TIP)},
author={Grammel, Gert and Curri, Vittorio and Auge, Jean-Luc},
booktitle={Optical Fiber Communication Conference},
pages={M1D--3},
year={2018},
organization={Optica Publishing Group}
}
@inproceedings{taylor2018towards,
title={Towards a route planning tool for open optical networks in the telecom infrastructure project},
author={Taylor, Brian D and Goldfarb, Gilad and Bandyopadhyay, Saumil and Curri, Vittorio and Schmidtke, Hans-Juergen},
booktitle={Optical Fiber Communication Conference},
pages={Tu3E--4},
year={2018},
organization={Optica Publishing Group}
}
@article{filer2018multi,
title={Multi-vendor experimental validation of an open source QoT estimator for optical networks},
author={Filer, Mark and Cantono, Mattia and Ferrari, Alessio and Grammel, Gert and Galimberti, Gabriele and Curri, Vittorio},
journal={Journal of Lightwave Technology},
volume={36},
number={15},
pages={3073--3082},
year={2018},
publisher={IEEE}
}
@inproceedings{auge2019open,
title={Open optical network planning demonstration},
author={Auge, Jean-Luc and Grammel, Gert and Le Rouzic, Esther and Curri, Vittorio and Galimberti, Gabriele and Powell, James},
booktitle={Optical Fiber Communication Conference},
pages={M3Z--9},
year={2019},
organization={Optica Publishing Group}
}
@inproceedings{kundrat2020physical,
title={Physical-layer awareness: GNPy and ONOS for end-to-end circuits in disaggregated networks},
author={Kundr{\'a}t, Jan and Campanella, Andrea and Le Rouzic, Esther and Ferrari, Alessio and Havli{\v{s}}, Ond{\v{r}}ej and Ha{\v{z}}linsk{\`y}, Michal and Grammel, Gert and Galimberti, Gabriele and Curri, Vittorio},
booktitle={2020 Optical Fiber Communications Conference and Exhibition (OFC)},
pages={1--3},
year={2020},
organization={IEEE}
}
@inproceedings{ferrari2020experimental,
title={Experimental validation of an open source quality of transmission estimator for open optical networks},
author={Ferrari, Alessio and Filer, Mark and Balasubramanian, Karthikeyan and Yin, Yawei and Le Rouzic, Esther and Kundr{\'a}t, Jan and Grammel, Gert and Galimberti, Gabriele and Curri, Vittorio},
booktitle={2020 Optical Fiber Communications Conference and Exhibition (OFC)},
pages={1--3},
year={2020},
organization={IEEE}
}
@article{ferrari2020gnpy,
title={GNPy: an open source application for physical layer aware open optical networks},
author={Ferrari, Alessio and Filer, Mark and Balasubramanian, Karthikeyan and Yin, Yawei and Le Rouzic, Esther and Kundr{\'a}t, Jan and Grammel, Gert and Galimberti, Gabriele and Curri, Vittorio},
journal={Journal of Optical Communications and Networking},
volume={12},
number={6},
pages={C31--C40},
year={2020},
publisher={Optica Publishing Group}
}
@inproceedings{ferrari2020softwarized,
title={Softwarized optical transport QoT in production optical network: a Brownfield validation},
author={Ferrari, Alessio and Balasubramanian, Karthikeyan and Filer, Mark and Yin, Yawei and Le Rouzic, Esther and Kundr{\'a}t, Jan and Grammel, Gert and Galimberti, Gabriele and Curri, Vittorio},
booktitle={2020 European Conference on Optical Communications (ECOC)},
pages={1--4},
year={2020},
organization={IEEE}
}
@article{ferrari2021assessment,
title={Assessment on the in-field lightpath QoT computation including connector loss uncertainties},
author={Ferrari, Alessio and Balasubramanian, Karthikeyan and Filer, Mark and Yin, Yawei and Le Rouzic, Esther and Kundr{\'a}t, Jan and Grammel, Gert and Galimberti, Gabriele and Curri, Vittorio},
journal={Journal of Optical Communications and Networking},
volume={13},
number={2},
pages={A156--A164},
year={2021},
publisher={Optica Publishing Group}
}
@inproceedings{kundrat2021gnpy,
title={GNPy \& YANG: open APIs for end-to-end service provisioning in optical networks},
author={Kundr{\'a}t, Jan and Le Rouzic, Esther and M{\aa}rtensson, Jonas and Campanella, Andrea and Havli{\v{s}}, Ond{\v{r}}ej and DAmico, Andrea and Grammel, Gert and Galimberti, Gabriele and Curri, Vittorio and Vojt{\v{e}}ch, Josef},
booktitle={Optical Fiber Communication Conference},
pages={M1B--6},
year={2021},
organization={Optica Publishing Group}
}
@inproceedings{d2021gnpy,
title={GNPy experimental validation on flex-grid, flex-rate WDM optical transport scenarios},
author={DAmico, Andrea and London, Elliot and Le Guyader, Bertrand and Frank, Florian and Le Rouzic, Esther and Pincemin, Erwan and Brochier, Nicolas and Curri, Vittorio},
booktitle={Optical fiber communication conference},
pages={W1G--2},
year={2021},
organization={Optica Publishing Group}
}
@inproceedings{virgillito2021testing,
title={Testing TIP open source solutions in deployed optical networks},
author={Virgillito, Emanuele and Braun, Ralf-Peter and Breuer, Dirk and Gladisch, Andreas and Curri, Vittorio and Grammel, Gert},
booktitle={Optical Fiber Communication Conference},
pages={F1C--3},
year={2021},
organization={Optica Publishing Group}
}
@article{d2022experimental,
title={Experimental validation of GNPy in a multi-vendor flex-grid flex-rate WDM optical transport scenario},
author={DAmico, Andrea and London, Elliot and Le Guyader, Bertrand and Frank, Florian and Le Rouzic, Esther and Pincemin, Erwan and Brochier, Nicolas and Curri, Vittorio},
journal={Journal of Optical Communications and Networking},
volume={14},
number={3},
pages={79--88},
year={2022},
publisher={Optica Publishing Group}
}
@inproceedings{mano2022accuracy,
title={Accuracy of nonlinear interference estimation on launch power optimization in short-reach systems with field trial},
author={Mano, Toru and DAmico, Andrea and Virgillito, Emanuele and Borraccini, Giacomo and Huang, Yue-Kai and Kitamura, Kei and Anazawa, Kazuya and Masuda, Akira and Nishizawa, Hideki and Wang, Ting and others},
booktitle={European Conference and Exhibition on Optical Communication},
pages={We3B--1},
year={2022},
organization={Optica Publishing Group}
}
@inproceedings{kundrat2022gnpy,
title={GNPy: Lessons learned and future plans},
author={Kundr{\'a}t, Jan and Le Rouzic, Esther and M{\aa}rtensson, Jonas and Melin, Stefan and DAmico, Andrea and Grammel, Gert and Galimberti, Gabriele and Curri, Vittorio},
booktitle={European Conference and Exhibition on Optical Communication},
pages={We3B--6},
year={2022},
organization={Optica Publishing Group}
}
@inproceedings{grammel2023open,
title={Open Optical Networks: the good, the bad and the ugly},
author={Grammel, Gert and Kundrat, Jan and Le Rouzic, Esther and Melin, Stefan and Curri, Vittorio and d'Amico, Andrea and Manzotti, Roberto},
booktitle={49th European Conference on Optical Communications (ECOC 2023)},
volume={2023},
pages={1585--1588},
year={2023},
organization={IET}
}
@inproceedings{d2024gnpy,
title={GNPy Experimental Validation in a C+ L Multiband Optical Multiplex Section},
author={DAmico, Andrea and Gatto, Vittorio and Nespola, Antonino and Borraccini, Giacomo and Jiang, Yanchao and Poggiolini, Pierluigi and Le Rouzic, Esther and de Lerma, Arturo Mayoral L{\'o}pez and Grammel, Gert and Manzotti, Roberto and others},
booktitle={2024 24th International Conference on Transparent Optical Networks (ICTON)},
pages={1--4},
year={2024},
organization={IEEE}
}

298
docs/cli_options.rst Normal file
View File

@@ -0,0 +1,298 @@
.. _cli-options:
Options Documentation for `gnpy-path-request` and `gnpy-transmission-example`
=============================================================================
Common options
--------------
**Option**: `--no-insert-edfas`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Purpose**: Disables the automatic insertion of EDFAs after ROADMs and fibers, as well as the splitting
of fibers during the auto-design process.
The `--no-insert-edfas` option is a command-line argument available in GNPy that allows users to control the
automatic insertion of amplifiers during the network design process. This option provides flexibility for
users who may want to manually manage amplifier placements or who have specific design requirements that
do not necessitate automatic amplification.
To use the `--no-insert-edfas` option, simply include it in the command line when running your GNPy program. For example:
.. code-block:: shell-session
gnpy-transmission-example my_network.json --no-insert-edfas
When the `--no-insert-edfas` option is specified:
1. **No Automatic Amplifiers**: The program will not automatically add EDFAs to the network topology after
ROADMs or fiber elements. This means that if the network design requires amplification, users must ensure
that amplifiers are manually defined in the network topology file. Users should be aware that disabling
automatic amplifier insertion may lead to insufficient amplification in the network if not managed properly.
It is essential to ensure that the network topology includes the necessary amplifiers to meet performance requirements.
2. **No Fiber Splitting**: The option also prevents the automatic splitting of fibers during the design process.
This is particularly useful for users who want to maintain specific fiber lengths or configurations without
the program altering them.
**Option**: `--equipment`, `-e`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Description**: Specifies the equipment library file.
**Usage**:
.. code-block:: shell-session
gnpy-transmission-example my_network.json --equipment <FILE.json>
**Default**: Uses the default equipment configuration in the example-data folder if not specified.
**Functionality**: This option allows users to load a specific equipment configuration that defines the characteristics of the network elements.
**Option**: `--extra-equipment` and `--extra-config`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The `--extra-equipment` and `--extra-config` options allow users to extend the default equipment library and configuration
settings used by the GNPy program. This feature is particularly useful for users who need to incorporate additional
equipment types or specific configurations that are not included in the standard equipment library (such as third party pluggables).
**Usage**:
.. code-block:: shell-session
--extra-equipment <file1.json> [<file2.json> ...]
**Parameters**:
- `<file1.json>`: Path to the first additional equipment file.
- `<file2.json>`: Path to any subsequent additional equipment files (optional).
**Functionality**:
- The program will merge the equipment definitions from the specified files into the main equipment library.
- If an equipment type defined in the additional files has the same name as one in the main library, the program
will issue a warning about the duplicate entry and will include ony the last definition.
- This allows for flexibility in defining equipment that may be specific to certain use cases or vendor-specific models.
**`--extra-config`**:
**Description**: This option allows users to specify additional configuration files that can override
or extend the default configuration settings used by the program. This is useful for customizing simulation
parameters or equipment settings. To set an amplifier with a specific such config, it must be defined in the
library with the keyword "default_config_from_json" filled with the file name containing the config in the case of
"variable_gain" amplifier or with the "advanced_config_from_json" for the "advanced_model" amplifier.
**Usage**:
.. code-block:: shell-session
--extra-config <file1.json> [<file2.json> ...]
**Parameters**:
- `<file1.json>`: Path to the first additional configuration file.
- `<file2.json>`: Path to any subsequent additional configuration files (optional).
**Functionality**:
The program will load the configurations from the specified files and consider them instead of the
default configurations for the amplifiers that use the "default_config_from_json" or "advanced_config_from_json" keywords.
Example
-------
To run the program with additional equipment and configuration files, you can use the following command:
.. code-block:: shell-session
gnpy-transmission-example --equipment main_equipment.json \
--extra-equipment additional_equipment1.json additional_equipment2.json \
--extra-config additional_config1.json
In this example:
- `main_equipment.json` is the primary equipment file.
- `additional_equipment1.json` and `additional_equipment2.json` are additional equipment files that will be merged into the main library.
- `additional_config1.json` is an additional configuration file that will override the default settings for the amplifiers pointing to it.
**Option**: `--save-network`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Description**: Saves the final network configuration to a specified JSON file.
**Usage**:
.. code-block:: shell-session
--save-network <FILE.json>
**Functionality**: This option allows users to save the network state after the simulation, which can be useful for future reference or analysis.
**Option**: `--save-network-before-autodesign`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Description**: Dumps the network into a JSON file prior to autodesign.
**Usage**:
.. code-block:: shell-session
gnpy-path-request my_network.json my_services.json --save-network-before-autodesign <FILE.json>
**Functionality**: This option is useful for users who want to inspect the network configuration before any automatic design adjustments are made.
**Option**: `--sim-params`
~~~~~~~~~~~~~~~~~~~~~~~~~~
**Description**: Path to the JSON file containing simulation parameters.
**Usage**:
.. code-block:: shell-session
gnpy-transmission-example my_network.json --sim-params <FILE.json>
**Functionality**: The `--sim-params` option is a command-line argument available in GNPy that allows users to specify a
JSON file containing simulation parameters. This option is crucial for customizing the behavior of the simulation:
the file ``sim_params.json`` contains the tuning parameters used within both the ``gnpy.science_utils.RamanSolver`` and
the ``gnpy.science_utils.NliSolver`` for the evaluation of the Raman profile and the NLI generation, respectively.
The tuning of the parameters is detailed here: :ref:`json input sim-params<sim-params>`.
`gnpy-transmission-example` options
-----------------------------------
**Option**: `--show-channels`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Description**: Displays the final per-channel OSNR and GSNR summary.
**Usage**:
.. code-block:: shell-session
gnpy-transmission-example my_network.json --show-channels
**Functionality**: This option provides a summary of the optical signal-to-noise ratio (OSNR)
and generalized signal-to-noise ratio (GSNR) for each channel after the simulation.
**Option**: `-pl`, `--plot`
~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Description**: Generates plots of the results.
**Usage**:
.. code-block:: shell-session
gnpy-transmission-example my_network.json -pl
**Functionality**: This option allows users to visualize the results of the simulation through graphical plots.
**Option**: `-l`, `--list-nodes`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Description**: Lists all transceiver nodes in the network.
**Usage**:
.. code-block:: shell-session
gnpy-transmission-example my_network.json -l
**Functionality**: This option provides a quick way to view all transceiver nodes present in the network topology.
**Option**: `-po`, `--power`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Description**: Specifies the reference channel power in span in dBm.
**Usage**:
.. code-block:: shell-session
gnpy-transmission-example my_network.json -po <value>
**Functionality**: This option allows users to set the input power level for the reference channel used in the simulation.
It replaces the value specified in the `SI` section of the equipment library (:ref:`power_dbm<spectral_info>`).
**Option**: `--spectrum`
~~~~~~~~~~~~~~~~~~~~~~~~
**Description**: Specifies a user-defined mixed rate spectrum JSON file for propagation.
**Usage**:
.. code-block:: shell-session
gnpy-transmission-example my_network.json --spectrum <FILE.json>
**Functionality**: This option allows users to define a custom spectrum for the simulation, which can
include varying channel rates and configurations. More details here: :ref:`mixed-rate<mixed-rate>`.
Options for `path_requests_run`
-------------------------------
The `gnpy-path-request` script provides a simple path computation function that supports routing, transceiver mode selection, and spectrum assignment.
It supports include and disjoint constraints for the path computation, but does not provide any optimisation.
It requires two mandatory arguments: network file and service file (see :ref:`XLS files<excel-service-sheet>` or :ref:`JSON files<legacy-json>`).
The `gnpy-path-request` computes:
- design network once and propagate the service requests on this design
- computes performance of each request defined in the service file independently from each other, considering full load (based on the request settings),
- assigns spectrum for each request according to the remaining spectrum, on a first arrived first served basis.
Lack of spectrum leads to blocking, but performance estimation is still returned for information.
**Option**: `-bi`, `--bidir`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Description**: Indicates that all demands are bidirectional.
**Usage**:
.. code-block:: shell-session
gnpy-path-request my_network.json my_service.json -e my_equipment.json -bi
**Functionality**: This option allows users to specify that the performance of the service requests should be
computed in both directions (source to destination and destination to source). This forces the 'bidirectional'
attribute to true in the service file, possibly affecting feasibility if one direction is not feasible.
**Option**: `-o`, `--output`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Description**: Stores computation results requests into a JSON or CSV file.
**Usage**:
.. code-block:: shell-session
gnpy-path-request my_network.json my_service.json -o <FILE.json|FILE.csv>
**Functionality**: This option allows users to save the results of the path requests into a specified output file
for further analysis.
**Option**: `--redesign-per-request`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Description**: Redesigns the network for each request using the request as the reference channel
(replaces the `SI` section of the equipment library with the request specifications).
**Usage**:
.. code-block:: shell-session
gnpy-path-request my_network.json my_services.json --redesign-per-request
**Functionality**: This option enables checking different scenarios for design.

View File

@@ -29,6 +29,7 @@ This path is directional, and all "GNPy elements" along the path match the unidi
The network topology contains not just the physical topology of the network, but also references to the :ref:`equipment library<concepts-equipment>` and a set of *operating parameters* for each entity. The network topology contains not just the physical topology of the network, but also references to the :ref:`equipment library<concepts-equipment>` and a set of *operating parameters* for each entity.
These parameters include the **fiber length** of each fiber, the connector **attenutation losses**, or an amplifier's specific **gain setting**. These parameters include the **fiber length** of each fiber, the connector **attenutation losses**, or an amplifier's specific **gain setting**.
The topology is specified via :ref:`XLS files<excel>` or via :ref:`JSON<legacy-json>`.
.. _complete-vs-incomplete: .. _complete-vs-incomplete:

View File

@@ -65,7 +65,7 @@ author = 'Telecom Infra Project - OOPT PSE Group'
# #
# This is also used if you do content translation via gettext catalogs. # This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases. # Usually you set "language" from the command line for these cases.
language = None language = 'en'
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
@@ -84,13 +84,6 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
# #
on_rtd = os.environ.get('READTHEDOCS') == 'True'
if on_rtd:
html_theme = 'default'
html_theme_options = {
'logo_only': True,
}
else:
html_theme = 'alabaster' html_theme = 'alabaster'
html_theme_options = { html_theme_options = {
'logo': 'images/GNPy-logo.png', 'logo': 'images/GNPy-logo.png',
@@ -190,3 +183,5 @@ autodoc_default_options = {
} }
graphviz_output_format = 'svg' graphviz_output_format = 'svg'
bibtex_bibfiles = ['biblio.bib']

View File

@@ -1,3 +1,5 @@
.. _excel:
Excel (XLS, XLSX) input files Excel (XLS, XLSX) input files
============================= =============================
@@ -112,10 +114,6 @@ and a fiber span from node3 to node6::
If filled they must contain strings with the same constraint as "City" names. Its value is used to differenate links having the same end points. In this case different Id should be used. Cable Ids are not meant to be unique in general. If filled they must contain strings with the same constraint as "City" names. Its value is used to differenate links having the same end points. In this case different Id should be used. Cable Ids are not meant to be unique in general.
(in progress)
.. _excel-equipment-sheet: .. _excel-equipment-sheet:
Eqpt sheet Eqpt sheet
@@ -190,7 +188,42 @@ This generates a text file meshTopologyExampleV2_eqt_sheet.txt whose content ca
- **delta_p**, in dBm, is not mandatory. If filled it is used to set the output target power per channel at the output of the amplifier, if power_mode is True. The output power is then set to power_dbm + delta_power. - **delta_p**, in dBm, is not mandatory. If filled it is used to set the output target power per channel at the output of the amplifier, if power_mode is True. The output power is then set to power_dbm + delta_power.
# to be completed #
.. _excel-roadms-sheet:
Roadms sheet
------------
The ROADM sheet (named "Roadms") is optional.
If provided, it can be used to specify:
- per channel power target on a specific ROADM degree (*per_degree_pch_out_db*),
- ROADM type variety,
- impairment ID (identifier) on a particular ROADM path (from degree - to degree).
This sheet contains six columns:
Node A ; Node Z ; per degree target power (dBm) ; type_variety ; from degrees ; from degree to degree impairment id
- **Node A** is mandatory. Name of the ROADM node (as listed in Nodes sheet).
Must be a 'ROADM' (Type attribute in Node sheet), its number of occurence may be equal to its degree.
- **Node Z** is mandatory. Egress direction from the *Node A* ROADM site. Multiple Links between the same Node A
and NodeZ is not supported.
- **per degree target power (dBm)** (optional).
If filled it must contain a value in dBm corresponding to :ref:`per_degree_pch_out_db<roadm_json_instance>` on the **Node Z** degree.
Defaults to equipment library value if not filled.
- **type_variety** (optional). Must be the same for all ROADM entries if filled,
and defined in the :ref:`equipment library<roadm>`. Defaults to 'default' if not filled.
- **from degrees** (optional): List of Node names separated by ' | '. Names must be present in Node sheet.
Together with Node Z, they define a list of internal path in ROADM for which the impairment ID applies
- **from degree to degree impairment id** (optional):List of impairment IDs separated by ' | '. Must be filled
if **from degrees** is defined.
The impairment ID must be defined in the equipment library and be of "express" type.
(in progress) (in progress)

View File

@@ -4,7 +4,7 @@ Extending GNPy with vendor-specific data
======================================== ========================================
GNPy ships with an :ref:`equipment library<concepts-equipment>` containing machine-readable datasheets of networking equipment. GNPy ships with an :ref:`equipment library<concepts-equipment>` containing machine-readable datasheets of networking equipment.
Vendors who are willing to contribute descriptions of their supported products are encouraged to `submit a patch <https://review.gerrithub.io/Documentation/intro-gerrit-walkthrough-github.html>`__. Vendors who are willing to contribute descriptions of their supported products are encouraged to `submit a patch <https://review.gerrithub.io/Documentation/intro-gerrit-walkthrough-github.html>`__ -- or just :ref:`get in touch with us directly<contributing>`.
This chapter discusses option for modeling performance of :ref:`EDFA amplifiers<extending-edfa>`, :ref:`Raman amplifiers<extending-raman>`, :ref:`transponders<extending-transponder>` and :ref:`ROADMs<extending-roadm>`. This chapter discusses option for modeling performance of :ref:`EDFA amplifiers<extending-edfa>`, :ref:`Raman amplifiers<extending-raman>`, :ref:`transponders<extending-transponder>` and :ref:`ROADMs<extending-roadm>`.
@@ -29,7 +29,7 @@ The NF is expressed as a third-degree polynomial:
f(x) &= \text{a}x^3 + \text{b}x^2 + \text{c}x + \text{d} f(x) &= \text{a}x^3 + \text{b}x^2 + \text{c}x + \text{d}
\text{NF} &= f(G_\text{max} - G) \text{NF} &= f(G - G_\text{max})
This model can be also used for fixed-gain fixed-NF amplifiers. This model can be also used for fixed-gain fixed-NF amplifiers.
In that case, use: In that case, use:
@@ -91,7 +91,8 @@ Advanced Specification
********************** **********************
The amplifier performance can be further described in terms of gain ripple, NF ripple, and the dynamic gain tilt. The amplifier performance can be further described in terms of gain ripple, NF ripple, and the dynamic gain tilt.
When provided, the amplifier characteristic is fine-tuned as a function of carrier frequency. When provided, the amplifier characteristic is fine-tuned as a function of carrier frequency. Note that in this advanced
specification tilt is defined vs frequency while tilt_target specified in EDFA instances is defined vs wavelength.
.. _extending-raman: .. _extending-raman:
@@ -100,10 +101,10 @@ Raman Amplifiers
An accurate simulation of Raman amplification requires knowledge of: An accurate simulation of Raman amplification requires knowledge of:
- the *power* and *wavelength* of all Raman pumping lasers, * the *power* and *wavelength* of all Raman pumping lasers,
- the *direction*, whether it is co-propagating or counter-propagating, * the *direction*, whether it is co-propagating or counter-propagating,
- the Raman efficiency of the fiber, * the Raman efficiency of the fiber,
- the fiber temperature. * the fiber temperature.
Under certain scenarios it is useful to be able to run a simulation without an accurate Raman description. Under certain scenarios it is useful to be able to run a simulation without an accurate Raman description.
For these purposes, it is possible to approximate a Raman amplifier via a fixed-gain EDFA with the :ref:`polynomial NF<ext-nf-model-polynomial-NF>` model using :math:`\text{a} = \text{b} = \text{c} = 0`, and a desired effective :math:`\text{d} = NF`. For these purposes, it is possible to approximate a Raman amplifier via a fixed-gain EDFA with the :ref:`polynomial NF<ext-nf-model-polynomial-NF>` model using :math:`\text{a} = \text{b} = \text{c} = 0`, and a desired effective :math:`\text{d} = NF`.
@@ -119,38 +120,32 @@ A *mode* usually refers to a particular performance point that is defined by a c
The following data are required for each mode: The following data are required for each mode:
``bit-rate`` ``bit_rate``
Data bit rate, in :math:`\text{Gbits}\times s^{-1}`. Data bit rate, in :math:`\text{bits}\times s^{-1}`.
``baud-rate`` ``baud_rate``
Symbol modulation rate, in :math:`\text{Gbaud}`. Symbol modulation rate, in :math:`\text{baud}`.
``required-osnr`` ``OSNR``
Minimal allowed OSNR for the receiver. Minimal required OSNR for the receiver. In :math:`\text{dB}`
``tx-osnr`` ``tx-osnr``
Initial OSNR at the transmitter's output. Initial OSNR at the transmitter's output. In :math:`\text{dB}`
``grid-spacing`` ``min-spacing``
Minimal grid spacing, i.e., an effective channel spectral bandwidth. Minimal grid spacing, i.e., an effective channel spectral bandwidth.
In :math:`\text{Hz}`. In :math:`\text{Hz}`.
``tx-roll-off`` ``roll-off``
Roll-off parameter (:math:`\beta`) of the TX pulse shaping filter. Roll-off parameter (:math:`\beta`) of the TX pulse shaping filter.
This assumes a raised-cosine filter. This assumes a raised-cosine filter.
``rx-power-min`` and ``rx-power-max`` ``rx-power-min`` and ``rx-power-max``
The allowed range of power at the receiver. (work in progress) The allowed range of power at the receiver.
In :math:`\text{dBm}`. In :math:`\text{dBm}`.
``cd-max`` ``penalties``
Maximal allowed Chromatic Dispersion (CD). Impairments such as Chromatic Dispersion (CD), Polarization Mode Dispersion (PMD), and Polarization Dispersion Loss (PDL)
In :math:`\text{ps}/\text{nm}`. result in penalties at the receiver. The receiver's ability to handle these impairments can be defined for each mode as
``pmd-max`` a list of {impairment: in defined units, 'penalty_value' in dB} (see `transceiver section here <json.rst#_transceiver>`).
Maximal allowed Polarization Mode Dispersion (PMD). Maximum allowed CD, maximum allowed PMD, and maximum allowed PDL should be listed there with corresponding penalties.
In :math:`\text{ps}`. Impairments experienced during propagation are linearly interpolated between given points to obtain the corresponding penalty.
``cd-penalty`` The accumulated penalties are subtracted from the path GSNR before comparing with the minimum required OSNR.
*Work-in-progress.* Impairments: PMD in :math:`\text{ps}`, CD in :math:`\text{ps/nm}`, PDL in :math:`\text{dB}`, penalty_value in :math:`\text{dB}`
Describes the increase of the requires GSNR as the :abbr:`CD (Chromatic Dispersion)` deteriorates.
``dgd-penalty``
*Work-in-progress.*
Describes the increase of the requires GSNR as the :abbr:`DGD (Differential Group Delay)` deteriorates.
``pmd-penalty``
*Work-in-progress.*
Describes the increase of the requires GSNR as the :abbr:`PMD (Polarization Mode Dispersion)` deteriorates.
GNPy does not directly track the FEC performance, so the type of chosen FEC is likely indicated in the *name* of the selected transponder mode alone. GNPy does not directly track the FEC performance, so the type of chosen FEC is likely indicated in the *name* of the selected transponder mode alone.
@@ -168,6 +163,7 @@ The set of parameters for each ROADM model therefore includes:
Per-channel target TX power towards the egress amplifier. Per-channel target TX power towards the egress amplifier.
Within GNPy, a ROADM is expected to attenuate any signal that enters the ROADM node to this level. Within GNPy, a ROADM is expected to attenuate any signal that enters the ROADM node to this level.
This can be overridden on a per-link in the network topology. This can be overridden on a per-link in the network topology.
Targets can be set using power or power spectral density (see `roadm section here <json.rst#__roadm>`)
``pmd`` ``pmd``
Polarization mode dispersion (PMD) penalty of the express path. Polarization mode dispersion (PMD) penalty of the express path.
In :math:`\text{ps}`. In :math:`\text{ps}`.

View File

@@ -7,3 +7,4 @@
.. automodule:: gnpy.tools.json_io .. automodule:: gnpy.tools.json_io
.. automodule:: gnpy.tools.plots .. automodule:: gnpy.tools.plots
.. automodule:: gnpy.tools.service_sheet .. automodule:: gnpy.tools.service_sheet
.. automodule:: gnpy.tools.worker_utils

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 478 KiB

View File

@@ -11,12 +11,17 @@ in real-world mesh optical networks. It is based on the Gaussian Noise Model.
intro intro
concepts concepts
install install
cli_options
amplifier_models_description
json json
json_instance_examples
excel excel
extending extending
about-project about-project
model model
gnpy-api gnpy-api
release-notes
publications
Indices and tables Indices and tables
================== ==================

View File

@@ -38,7 +38,7 @@ Using Python on your computer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Note**: `gnpy` supports Python 3 only. Python 2 is not supported. **Note**: `gnpy` supports Python 3 only. Python 2 is not supported.
`gnpy` requires Python ≥3.6 `gnpy` requires Python ≥3.8
**Note**: the `gnpy` maintainers strongly recommend the use of Anaconda for **Note**: the `gnpy` maintainers strongly recommend the use of Anaconda for
managing dependencies. managing dependencies.
@@ -84,7 +84,7 @@ exact version of Python you are using.
$ which python # check which Python executable is used $ which python # check which Python executable is used
/path/to/anaconda/bin/python /path/to/anaconda/bin/python
$ python -V # check your Python version $ python -V # check your Python version
Python 3.6.5 :: Anaconda, Inc. Python 3.8.0 :: Anaconda, Inc.
.. _install-pip: .. _install-pip:

View File

@@ -10,33 +10,33 @@ fully-functional programs.
**Note**: *If you are a network operator or involved in route planning and **Note**: *If you are a network operator or involved in route planning and
optimization for your organization, please contact project maintainer Jan optimization for your organization, please contact project maintainer Jan
Kundrát <jan.kundrat@telecominfraproject.com>. gnpy is looking for users with Kundrát <jkt@jankundrat.com>. gnpy is looking for users with
specific, delineated use cases to drive requirements for future specific, delineated use cases to drive requirements for future
development.* development.*
This example demonstrates how GNPy can be used to check the expected SNR at the end of the line by varying the channel input power: This example demonstrates how GNPy can be used to check the expected SNR at the end of the line by varying the channel input power,
or to run a planning script to check SNR of several services:
.. image:: https://telecominfraproject.github.io/oopt-gnpy/docs/images/transmission_main_example.svg .. image:: images/gnpy-transmission-example.svg
:width: 100% :width: 100%
:align: left :align: left
:alt: Running a simple simulation example :alt: Running a simple simulation example
:target: https://asciinema.org/a/252295
By default, this script operates on a single span network defined in By default, the gnpy-transmission-example script operates on a single span network defined in
`gnpy/example-data/edfa_example_network.json <gnpy/example-data/edfa_example_network.json>`_ `gnpy/example-data/edfa_example_network.json <../gnpy/example-data/edfa_example_network.json>`_
You can specify a different network at the command line as follows. For You can specify a different network at the command line as follows. For
example, to use the CORONET Global network defined in example, to use the CORONET Global network defined in
`gnpy/example-data/CORONET_Global_Topology.json <gnpy/example-data/CORONET_Global_Topology.json>`_: `gnpy/example-data/CORONET_Global_Topology.json <../gnpy/example-data/CORONET_Global_Topology.json>`_:
.. code-block:: shell-session .. code-block:: shell-session
$ gnpy-transmission-example $(gnpy-example-data)/CORONET_Global_Topology.json $ gnpy-transmission-example $(gnpy-example-data)/CORONET_Global_Topology.json
It is also possible to use an Excel file input (for example It is also possible to use an Excel file input (for example
`gnpy/example-data/CORONET_Global_Topology.xls <gnpy/example-data/CORONET_Global_Topology.xls>`_). `gnpy/example-data/CORONET_Global_Topology.xls <../gnpy/example-data/CORONET_Global_Topology.xls>`_).
The Excel file will be processed into a JSON file with the same prefix. The Excel file will be processed into a JSON file with the same prefix.
Further details about the Excel data structure are available `in the documentation <docs/excel.rst>`__. Further details about the Excel data structure are available `in the documentation <excel.rst>`__.
The main transmission example will calculate the average signal OSNR and SNR The main transmission example will calculate the average signal OSNR and SNR
across network elements (transceiver, ROADMs, fibers, and amplifiers) across network elements (transceiver, ROADMs, fibers, and amplifiers)
@@ -57,10 +57,10 @@ interference noise.
Further Instructions for Use Further Instructions for Use
---------------------------- ----------------------------
Simulations are driven by a set of `JSON <docs/json.rst>`__ or `XLS <docs/excel.rst>`__ files. Simulations are driven by a set of `JSON <json.rst>`__ or `XLS <excel.rst>`__ files.
The ``gnpy-transmission-example`` script propagates a spectrum of channels at 32 Gbaud, 50 GHz spacing and 0 dBm/channel. The ``gnpy-transmission-example`` script propagates a spectrum of channels at 32 Gbaud, 50 GHz spacing and 0 dBm/channel.
Launch power can be overridden by using the ``--power`` argument. Launch power in fiber spans can be overridden by using the ``--power`` argument.
Spectrum information is not yet parametrized but can be modified directly in the ``eqpt_config.json`` (via the ``SpectralInformation`` -SI- structure) to accommodate any baud rate or spacing. Spectrum information is not yet parametrized but can be modified directly in the ``eqpt_config.json`` (via the ``SpectralInformation`` -SI- structure) to accommodate any baud rate or spacing.
The number of channel is computed based on ``spacing`` and ``f_min``, ``f_max`` values. The number of channel is computed based on ``spacing`` and ``f_min``, ``f_max`` values.
@@ -72,8 +72,8 @@ An experimental support for Raman amplification is available:
$(gnpy-example-data)/raman_edfa_example_network.json \ $(gnpy-example-data)/raman_edfa_example_network.json \
--sim $(gnpy-example-data)/sim_params.json --show-channels --sim $(gnpy-example-data)/sim_params.json --show-channels
Configuration of Raman pumps (their frequencies, power and pumping direction) is done via the `RamanFiber element in the network topology <gnpy/example-data/raman_edfa_example_network.json>`_. Configuration of Raman pumps (their frequencies, power and pumping direction) is done via the `RamanFiber element in the network topology <../gnpy/example-data/raman_edfa_example_network.json>`_.
General numeric parameters for simulation control are provided in the `gnpy/example-data/sim_params.json <gnpy/example-data/sim_params.json>`_. General numeric parameters for simulation control are provided in the `gnpy/example-data/sim_params.json <../gnpy/example-data/sim_params.json>`_.
Use ``gnpy-path-request`` to request several paths at once: Use ``gnpy-path-request`` to request several paths at once:
@@ -83,7 +83,7 @@ Use ``gnpy-path-request`` to request several paths at once:
$ gnpy-path-request -o output_file.json \ $ gnpy-path-request -o output_file.json \
meshTopologyExampleV2.xls meshTopologyExampleV2_services.json meshTopologyExampleV2.xls meshTopologyExampleV2_services.json
This program operates on a network topology (`JSON <docs/json.rst>`__ or `Excel <docs/excel.rst>`__ format), processing the list of service requests (JSON or XLS again). This program operates on a network topology (`JSON <json.rst>`__ or `Excel <excel.rst>`__ format), processing the list of service requests (JSON or XLS again).
The service requests and reply formats are based on the `draft-ietf-teas-yang-path-computation-01 <https://tools.ietf.org/html/draft-ietf-teas-yang-path-computation-01>`__ with custom extensions (e.g., for transponder modes). The service requests and reply formats are based on the `draft-ietf-teas-yang-path-computation-01 <https://tools.ietf.org/html/draft-ietf-teas-yang-path-computation-01>`__ with custom extensions (e.g., for transponder modes).
An example of the JSON input is provided in file `service-template.json`, while results are shown in `path_result_template.json`. An example of the JSON input is provided in file `service-template.json`, while results are shown in `path_result_template.json`.
@@ -92,4 +92,4 @@ As a result transponder type is not part of the network info. it is related to t
The current version includes a spectrum assigment features that enables to compute a candidate spectrum assignment for each service based on a first fit policy. Spectrum is assigned based on service specified spacing value, path_bandwidth value and selected mode for the transceiver. This spectrum assignment includes a basic capacity planning capability so that the spectrum resource is limited by the frequency min and max values defined for the links. If the requested services reach the link spectrum capacity, additional services feasibility are computed but marked as blocked due to spectrum reason. The current version includes a spectrum assigment features that enables to compute a candidate spectrum assignment for each service based on a first fit policy. Spectrum is assigned based on service specified spacing value, path_bandwidth value and selected mode for the transceiver. This spectrum assignment includes a basic capacity planning capability so that the spectrum resource is limited by the frequency min and max values defined for the links. If the requested services reach the link spectrum capacity, additional services feasibility are computed but marked as blocked due to spectrum reason.
OpenROADM networks can be simulated via ``gnpy/example-data/eqpt_config_openroadm.json`` -- see ``gnpy/example-data/Sweden_OpenROADM_example_network.json`` as an example. OpenROADM networks can be simulated via ``gnpy/example-data/eqpt_config_openroadm_*.json`` -- see ``gnpy/example-data/Sweden_OpenROADM*_example_network.json`` as an example.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -126,9 +126,9 @@ that can be easily evaluated extending the FWM theory from a set of discrete
tones - the standard FWM theory introduced back in the 90s by Inoue tones - the standard FWM theory introduced back in the 90s by Inoue
:cite:`Innoue-FWM`- to a continuity of tones, possibly spectrally shaped. :cite:`Innoue-FWM`- to a continuity of tones, possibly spectrally shaped.
Signals propagating in the fiber are not equivalent to Gaussian noise, but Signals propagating in the fiber are not equivalent to Gaussian noise, but
thanks to the absence of in-line compensation for choromatic dispersion, the thanks to the absence of in-line compensation for chromatic dispersion, the
become so, over short distances. So, the Gaussian noise model with incoherent become so, over short distances. So, the Gaussian noise model with incoherent
accumulation of NLI has estensively proved to be a quick yet accurate and accumulation of NLI has extensively proved to be a quick yet accurate and
conservative tool to estimate propagation impairments of fiber propagation. conservative tool to estimate propagation impairments of fiber propagation.
Note that the GN-model has not been derived with the aim of an *exact* Note that the GN-model has not been derived with the aim of an *exact*
performance estimation, but to pursue a conservative performance prediction. performance estimation, but to pursue a conservative performance prediction.
@@ -145,4 +145,4 @@ Raman Scattering in order to give a proper estimation for all channels
:cite:`cantono2018modeling`. This will be the main upgrade required within the :cite:`cantono2018modeling`. This will be the main upgrade required within the
PSE framework. PSE framework.
.. bibliography:: biblio.bib .. bibliography::

24
docs/publications.rst Normal file
View File

@@ -0,0 +1,24 @@
.. _publications:
Publications
============
Below is a chronological list of notable publications that emerged from the PSE group's collaborative work.
These articles detail the evolution of GNPy and confirm its performance through experimental trials:
- `G. Grammel, V. Curri, and J. Auge, "Physical Simulation Environment of The Telecommunications Infrastructure Project (TIP)," in Optical Fiber Communication Conference, OSA Technical Digest (online) (Optica Publishing Group, 2018), paper M1D.3. <https://opg.optica.org/abstract.cfm?uri=OFC-2018-M1D.3>`_
- `B. D. Taylor, G. Goldfarb, S. Bandyopadhyay, V. Curri, and H. Schmidtke, "Towards a Route Planning Tool for Open Optical Networks in the Telecom Infrastructure Project," in Optical Fiber Communication Conference, OSA Technical Digest (online) (Optica Publishing Group, 2018), paper Tu3E.4. <https://opg.optica.org/abstract.cfm?uri=OFC-2018-Tu3E.4>`_
- `M. Filer, M. Cantono, A. Ferrari, G. Grammel, G. Galimberti, and V. Curri, "Multi-Vendor Experimental Validation of an Open Source QoT Estimator for Optical Networks," J. Lightwave Technol. 36, 3073-3082 (2018). <https://opg.optica.org/jlt/abstract.cfm?uri=jlt-36-15-3073>`_
- `J. Auge, G. Grammel, E. le Rouzic, V. Curri, G. Galimberti, and J. Powell, "Open optical network planning demonstration," in Optical Fiber Communication Conference (OFC) 2019, OSA Technical Digest (Optica Publishing Group, 2019), paper M3Z.9. <https://opg.optica.org/abstract.cfm?uri=OFC-2019-M3Z.9>`_
- `J. Kundrát, A. Campanella, E. Le Rouzic, A. Ferrari, O. Havliš, M. Hažlinský, G. Grammel, G. Galimberti, and V. Curri, "Physical-Layer Awareness: GNPy and ONOS for End-to-End Circuits in Disaggregated Networks," in Optical Fiber Communication Conference (OFC) 2020, OSA Technical Digest (Optica Publishing Group, 2020), paper M3Z.17. <https://opg.optica.org/abstract.cfm?uri=ofc-2020-m3z.17>`_
- `A. Ferrari, M. Filer, K. Balasubramanian, Y. Yin, E. Le Rouzic, J. Kundrát, G. Grammel, G. Galimberti, and V. Curri, "Experimental Validation of an Open Source Quality of Transmission Estimator for Open Optical Networks," in Optical Fiber Communication Conference (OFC) 2020, OSA Technical Digest (Optica Publishing Group, 2020), paper W3C.2. <https://opg.optica.org/abstract.cfm?uri=ofc-2020-W3C.2>`_
- `A. Ferrari, M. Filer, K. Balasubramanian, Y. Yin, E. Le Rouzic, J. Kundrát, G. Grammel, G. Galimberti, and V. Curri, "GNPy: an open source application for physical layer aware open optical networks," J. Opt. Commun. Netw. 12, C31-C40 (2020). <https://opg.optica.org/jocn/fulltext.cfm?uri=jocn-12-6-C31&id=429003>`_
- `A. Ferrari, K. Balasubramanian, M. Filer, Y. Yin, E. Le Rouzic, J. Kundrát, G. Grammel, G. Galimberti, and V. Curri, "Softwarized Optical Transport QoT in Production Optical Network: a Brownfield Validation," 2020 European Conference on Optical Communications (ECOC), Brussels, Belgium, 2020. <https://ieeexplore.ieee.org/document/9333280>`_
- `A. Ferrari, K. Balasubramanian, M. Filer, Y. Yin, E. Le Rouzic, J. Kundrát, G. Grammel, G. Galimberti, and V. Curri, "Assessment on the in-field lightpath QoT computation including connector loss uncertainties," in Journal of Optical Communications and Networking, vol. 13, no. 2, pp. A156-A164, February 2021. <https://ieeexplore.ieee.org/document/9308057>`_
- `J. Kundrát, E. Le Rouzic, J. Mårtensson, A. Campanella, O. Havliš, A. DAmico, G. Grammel, G. Galimberti, V. Curri, and J. Vojtěch, "GNPy & YANG: Open APIs for End-to-End Service Provisioning in Optical Networks," in Optical Fiber Communication Conference (OFC) 2021, P. Dong, J. Kani, C. Xie, R. Casellas, C. Cole, and M. Li, eds., OSA Technical Digest (Optica Publishing Group, 2021), paper M1B.6. <https://opg.optica.org/abstract.cfm?uri=ofc-2021-M1B.6>`_
- `A. DAmico, E. London, B. Le Guyader, F. Frank, E. Le Rouzic, E. Pincemin, N. Brochier, and V. Curri, "GNPy experimental validation on flex-grid, flex-rate WDM optical transport scenarios," in Optical Fiber Communication Conference (OFC) 2021, P. Dong, J. Kani, C. Xie, R. Casellas, C. Cole, and M. Li, eds., OSA Technical Digest (Optica Publishing Group, 2021), paper W1G.2. <https://opg.optica.org/abstract.cfm?uri=ofc-2021-W1G.2>`_
- `E. Virgillito, R. Braun, D. Breuer, A. Gladisch, V. Curri, and G. Grammel, "Testing TIP Open Source Solutions in Deployed Optical Networks," in Optical Fiber Communication Conference (OFC) 2021, P. Dong, J. Kani, C. Xie, R. Casellas, C. Cole, and M. Li, eds., OSA Technical Digest (Optica Publishing Group, 2021), paper F1C.3. <https://opg.optica.org/abstract.cfm?uri=ofc-2021-F1C.3>`_
- `A. DAmico, E. London, B. Le Guyader, F. Frank, E. Le Rouzic, E. Pincemin, N. Brochier, and V. Curri, "Experimental validation of GNPy in a multi-vendor flex-grid flex-rate WDM optical transport scenario," J. Opt. Commun. Netw. 14, 79-88 (2022). <https://opg.optica.org/jocn/fulltext.cfm?uri=jocn-14-3-79&id=466355>`_
- `J. Kundrát, E. Le Rouzic, J. Mårtensson, S. Melin, A. DAmico, G. Grammel, G. Galimberti, and V. Curri, "GNPy: Lessons Learned and Future Plans [Invited]," in European Conference on Optical Communication (ECOC) 2022, J. Leuthold, C. Harder, B. Offrein, and H. Limberger, eds., Technical Digest Series (Optica Publishing Group, 2022), paper We3B.6. <https://opg.optica.org/abstract.cfm?uri=ECEOC-2022-We3B.6>`_
- `G. Grammel, J. Kundrat, E. Le Rouzic, S. Melin, V. Curri, A. D'Amico, R. Manzotti, "Open Optical Networks: the good, the bad and the ugly," 49th European Conference on Optical Communications (ECOC 2023), Hybrid Conference, Glasgow, UK, 2023. <https://ieeexplore.ieee.org/document/10484723>`_
- `A. DAmico, V. Gatto, A. Nespola, G. Borraccini, Y. Jiang, P. Poggiolini, E. Le Rouzic, A. M. L. de Lerma, G. Grammel, R. Manzotti, V. Curri, "GNPy Experimental Validation in a C+L Multiband Optical Multiplex Section," 2024 24th International Conference on Transparent Optical Networks (ICTON), Bari, Italy, 2024. <https://ieeexplore.ieee.org/document/10648172>`_

471
docs/release-notes.rst Normal file
View File

@@ -0,0 +1,471 @@
.. _release-notes:
Release change log
==================
Each release introduces some changes and new features.
(prepare text for next release)
**Important Changes:**
The default values for EDFA configuration, including frequency range, gain ripple, noise figure ripple, or dynamic gain tilt
are now hardcoded in parameters.py and are no longer read from the default_edfa_config.json file (the file has been removed).
However, users can define their own custom parameters using the default_config_from_json variable, which should be populated with a file name containing the desired parameter description. This applies to both variable_gain and fixed_gain amplifier types.
This change streamlines the configuration process but requires users to explicitly set parameters through the new
model if the default values do not suit their needs.
v2.11
-----
**New feature**
A new type_def for amplifiers has been introduced: multi_band. This allows the definition of a
multiband amplifier site composed of several amplifiers per band (a typical application is C+L transmission). The
release also includes autodesign for links (Optical Multiplex Section, OMS) composed of multi_band amplifiers.
Multi_band autodesign includes basic tilt and tilt_target calculation when the Raman flag is enabled with the
--sim-params option. The spectrum is demultiplexed before propagation in the amplifier and multiplexed in the output
fiber at the amplifier output.
In the library:
.. code-block:: json
{
"type_variety": "std_medium_gain_C",
"f_min": 191.225e12,
"f_max": 196.125e12,
"type_def": "variable_gain",
"gain_flatmax": 26,
"gain_min": 15,
"p_max": 21,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": false
},
{
"type_variety": "std_medium_gain_L",
"f_min": 186.5e12,
"f_max": 190.1e12,
"type_def": "variable_gain",
"gain_flatmax": 26,
"gain_min": 15,
"p_max": 21,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_medium_gain_multiband",
"type_def": "multi_band",
"amplifiers": [
"std_medium_gain_C",
"std_medium_gain_L"
],
"allowed_for_design": false
},
In the network topology:
.. code-block:: json
{
"uid": "east edfa in Site_A to Site_B",
"type": "Multiband_amplifier",
"type_variety": "std_medium_gain_multiband",
"amplifiers": [{
"type_variety": "std_medium_gain_C",
"operational": {
"gain_target": 22.55,
"delta_p": 0.9,
"out_voa": 3.0,
"tilt_target": 0.0
}
}, {
"type_variety": "std_medium_gain_L",
"operational": {
"gain_target": 21,
"delta_p": 3.0,
"out_voa": 3.0,
"tilt_target": 0.0
}
}
]
}
**Network design**
Optionally, users can define a design target per OMS (single or multi-band), with specific frequency ranges.
Default design bands are defined in the SI.
.. code-block:: json
{
"uid": "roadm Site_A",
"type": "Roadm",
"params": {
"target_pch_out_db": -20,
"design_bands": [{"f_min": 191.3e12, "f_max": 195.1e12}]
}
}
It is possible to define a set of bands in the SI block instead of a single Spectrum Information.
In this case type_variety must be used.
Each set defines a reference channel used for design functions and autodesign.
The default design settings for the path-request-run script have been modified.
Now, design is performed once for the reference channel defined in the SI block of the eqpt_config,
and requests are propagated based on this design.
The --redesign-per-request option can be used to restore previous behaviour
(design using request channel types).
The autodesign function has been updated to insert multiband booster, preamp or inline amplifiers based on the OMS
nature. If nothing is stated (no amplifier defined in the OMS, no design_bands attribute in the ROADM), then
it uses single band Edfas.
**Propagation**
Only carriers within the amplifier bandwidth are propagated, improving system coherence. This more rigorous checking
of the spectrum to be propagated and the amplifier bandwidth may lead to changes in the total number of channels
compared to previous releases. The range can be adjusted by changing the values of ``f_min`` and ``f_max``
in the amplifier library.
``f_min`` and ``f_max`` represent the boundary frequencies of the amplification bandwidth (the entire channel must fit
within this range).
In the example below, a signal center frequency of 190.05THz with a 50GHz width cannot fit within the amplifier band.
Note that this has a different meaning in the SI or Transceiver blocks, where ``f_min`` and ``f_max`` refers to the
minimum / maximum values of the carrier center frequency.
.. code-block:: json
{
"type_variety": "std_booster_L",
"f_min": 186.55e12,
"f_max": 190.05e12,
"type_def": "fixed_gain",
"gain_flatmax": 21,
"gain_min": 20,
"p_max": 21,
"nf0": 5,
"allowed_for_design": false
}
**Display**
The CLI output for the transmission_main_example now displays the channels used for design and simulation,
as well as the tilt target of amplifiers.
.. code-block:: text
Reference used for design: (Input optical power reference in span = 0.00dBm,
spacing = 50.00GHz
nb_channels = 76)
Channels propagating: (Input optical power deviation in span = 0.00dB,
spacing = 50.00GHz,
transceiver output power = 0.00dBm,
nb_channels = 76)
The CLI output displays the settings of each amplifier:
.. code-block:: text
Multiband_amplifier east edfa in Site_A to Site_B
type_variety: std_medium_gain_multiband
type_variety: std_medium_gain_C type_variety: std_medium_gain_L
effective gain(dB): 20.90 effective gain(dB): 22.19
(before att_in and before output VOA) (before att_in and before output VOA)
tilt-target(dB) 0.00 tilt-target(dB) 0.00
noise figure (dB): 6.38 noise figure (dB): 6.19
(including att_in) (including att_in)
pad att_in (dB): 0.00 pad att_in (dB): 0.00
Power In (dBm): -1.08 Power In (dBm): -1.49
Power Out (dBm): 19.83 Power Out (dBm): 20.71
Delta_P (dB): 0.90 Delta_P (dB): 2.19
target pch (dBm): 0.90 target pch (dBm): 3.00
actual pch out (dBm): -2.09 actual pch out (dBm): -0.80
output VOA (dB): 3.00 output VOA (dB): 3.00
**New feature**
The preturbative Raman and the approximated GGN models are introduced for a faster evaluation of the Raman and
Kerr effects, respectively.
These implementation are intended to reduce the computational effort required by multiband transmission scenarios.
Both the novel models have been validated with exstensive simulations
(see `arXiv:2304.11756 <https://arxiv.org/abs/2304.11756>`_ for the new Raman model and
`jlt:9741324 <https://eeexplore.ieee.org/document/9741324>`_ for the new NLI model).
Additionally, they have been experimentally validated in a laboratory setup composed of commertial equipment
(see `icton:10648172 <https://eeexplore.ieee.org/document/10648172>`_).
v2.10
-----
ROADM impairments can be defined per degree and roadm-path type (add, drop or express).
Minimum loss when crossing a ROADM is no more 0 dB. It can be set per ROADM degree with roadm-path-impairments.
The transceiver output power, which was previously set using the same parameter as the input span power (power_dbm),
can now be set using a different parameter. It can be set as:
- for all channels, with tx_power_dbm using SI similarly to tx_osnr (gnpy-transmission-example script)
.. code-block:: json
"SI": [{
"f_min": 191.35e12,
"baud_rate": 32e9,
"f_max": 196.1e12,
"spacing": 50e9,
"power_dbm": 3,
"power_range_db": [0, 0, 1],
"roll_off": 0.15,
"tx_osnr": 40,
"tx_power_dbm": -10,
"sys_margins": 2
}
]
- for certain channels, using -spectrum option and tx_channel_power_dbm option (gnpy-transmission-example script).
.. code-block:: json
{
"spectrum": [
{
"f_min": 191.35e12,
"f_max":193.1e12,
"baud_rate": 32e9,
"slot_width": 50e9,
"power_dbm": 0,
"roll_off": 0.15,
"tx_osnr": 40
},
{
"f_min": 193.15e12,
"f_max":193.15e12,
"baud_rate": 32e9,
"slot_width": 50e9,
"power_dbm": 0,
"roll_off": 0.15,
"tx_osnr": 40,
"tx_power_dbm": -10
},
{
"f_min": 193.2e12,
"f_max":195.1e12,
"baud_rate": 32e9,
"slot_width": 50e9,
"power_dbm": 0,
"roll_off": 0.15,
"tx_osnr": 40
}
]
}
- per service using the additional parameter ``tx_power`` which similarly to ``power`` should be defined in Watt (gnpy-path-request script)
.. code-block:: json
{
"path-request": [
{
"request-id": "0",
"source": "trx SITE1",
"destination": "trx SITE2",
"src-tp-id": "trx SITE1",
"dst-tp-id": "trx SITE2",
"bidirectional": false,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"trx_mode": "mode 1",
"spacing": 50000000000.0,
"path_bandwidth": 100000000000.0
}
}
},
{
"request-id": "0 with tx_power",
"source": "trx SITE1",
"destination": "trx SITE2",
"src-tp-id": "trx SITE1",
"dst-tp-id": "trx SITE2",
"bidirectional": false,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"trx_mode": "mode 1",
"tx_power": 0.0001,
"spacing": 50000000000.0,
"path_bandwidth": 100000000000.0
}
}
}
]
}
v2.9
----
The revision introduces a major refactor that separates design and propagation. Most of these changes have no impact
on the user experience, except the following ones:
**Network design - amplifiers**: amplifier saturation is checked during design in all cases, even if type_variety is
set; amplifier gain is no more computed on the fly but only at design phase.
Before, the design did not consider amplifier power saturation during design if amplifier type_variety was stated.
With this revision, the saturation is always applied:
If design is made for a per channel power that leads to saturation, the target are properly reduced and the design
is freezed. So that when a new simulation is performed on the same network for lower levels of power per channel
the same gain target is applied. Before these were recomputed, changing the gain targets, so the simulation was
not considering the exact same working points for amplifiers in case of saturation.
Note that this case (working with saturation settings) is not recommended.
The gain of amplifiers was estimated on the fly also in case of RamanFiber preceding elements. The refactor now
requires that an estimation of Raman gain of the RamanFiber is done during design to properly compute a gain target.
The Raman gain is estimated at design for every RamanFiber span and also during propagation instead of being only
estimated at propagation stage for those Raman Fiber spans concerned with the transmission. The auto-design is more
accurate for unpropagated spans, but this results in an increase overall computation time.
This will be improved in the future.
**Network design - ROADMs**: ROADM target power settings are verified during design.
Design checks that expected power coming from every directions ingress from a ROADM are consistent with output power
targets. The checks only considers the adjacent previous hop. If the expected power at the input of this ROADM is
lower than the target power on the out-degree of the ROADM, a warning is displayed, and user is asked to review the
input network to avoid this situation. This does not change the design or propagation behaviour.
**Propagation**: amplifier gain target is no more recomputed during propagation. It is now possible to freeze
the design and propagate without automatic changes.
In previous release, gain was recomputed during propagation based on an hypothetical reference noiseless channel
propagation. It was not possible to «freeze» the autodesign, and propagate without recomputing the gain target
of amplifiers.
With this new release, the design is freezed, so that it is possible to compare performances on same basis.
**Display**: "effective pch (dbm)" is removed. Display contains the target pch which is the target power per channel
in dBm, computed based on reference channel used for design and the amplifier delta_p in dB (and before out VOA
contribution). Note that "actual pch out (dBm)" is the actual propagated total power per channel averaged per spectrum
band definition at the output of the amplifier element, including noises and out VOA contribution.
v2.8
----
**Spectrum assignment**: requests can now support multiple slots.
The definition in service file supports multiple assignments (unchanged syntax):
.. code-block:: json
"effective-freq-slot": [
{
"N": 0,
"M": 4
}, {
"N": 50,
"M": 4
}
],
But in results, label-hop is now a list of slots and center frequency index:
.. code-block:: json
{
"path-route-object": {
"index": 4,
"label-hop": [
{
"N": 0,
"M": 4
}, {
"N": 50,
"M": 4
}
]
}
},
instead of
.. code-block:: json
{
"path-route-object": {
"index": 4,
"label-hop": {
"N": 0,
"M": 4
}
}
},
**change in display**: only warnings are displayed ; information are disabled and needs the -v (verbose)
option to be displayed on standard output.
**frequency scaling**: A more accurate description of fiber parameters is implemented, including frequency scaling of
chromatic dispersion, effective area, Raman gain coefficient, and nonlinear coefficient.
In particular:
1. Chromatic dispersion can be defined with ``'dispersion'`` and ``'dispersion_slope'``, as in previous versions, or
with ``'dispersion_per_frequency'``; the latter must be defined as a dictionary with two keys, ``'value'`` and
``'frequency'`` and it has higher priority than the entries ``'dispersion'`` and ``'dispersion_slope'``.
Essential change: In previous versions, when it was not provided the ``'dispersion_slope'`` was calculated in an
involute manner to get a vanishing beta3 , and this was a mere artifact for NLI evaluation purposes (namely to evaluate
beta2 and beta3, not for total dispersion accumulation). Now, the evaluation of beta2 and beta3 is performed explicitly
in the element.py module.
2. The effective area is provided as a scalar value evaluated at the Fiber reference frequency and properly scaled
considering the Fiber refractive indices n1 and n2, and the core radius. These quantities are assumed to be fixed and
are hard coded in the parameters.py module. Essential change: The effective area is always scaled along the frequency.
3. The Raman gain coefficient is properly scaled considering the overlapping of fiber effective area values scaled at
the interacting frequencies. Essential change: In previous version the Raman gain coefficient depends only on
the frequency offset.
4. The nonlinear coefficient ``'gamma'`` is properly scaled considering the refractive index n2 and the scaling
effective area. Essential change: As the effective area, the nonlinear coefficient is always scaled along the
frequency.
**power offset**: Power equalization now enables defining a power offset in transceiver library to represent
the deviation from the general equalisation strategy defined in ROADMs.
.. code-block:: json
"mode": [{
"format": "100G",
"baud_rate": 32.0e9,
"tx_osnr": 35.0,
"min_spacing": 50.0e9,
"cost": 1,
"OSNR": 10.0,
"bit_rate": 100.0e9,
"roll_off": 0.2,
"equalization_offset_db": 0.0
}, {
"format": "200G",
"baud_rate": 64.0e9,
"tx_osnr": 35.0,
"min_spacing": 75.0e9,
"cost": 1,
"OSNR": 13.0,
"bit_rate": 200.0e9,
"roll_off": 0.2,
"equalization_offset_db": 1.76
}
]
v2.7
----

View File

@@ -1,7 +0,0 @@
alabaster>=0.7.12,<1
docutils>=0.15.2,<1
myst-parser>=0.14.0,<1
Pygments>=2.7.4,<3
rstcheck
Sphinx>=3.5.0,<4
sphinxcontrib-bibtex>=0.4.2,<1

View File

@@ -1,8 +1,8 @@
''' """
GNPy is an open-source, community-developed library for building route planning and optimization tools in real-world mesh optical networks. It is based on the Gaussian Noise Model. GNPy is an open-source, community-developed library for building route planning and optimization tools in real-world mesh optical networks. It is based on the Gaussian Noise Model.
Signal propagation is implemented in :py:mod:`.core`. Signal propagation is implemented in :py:mod:`.core`.
Path finding and spectrum assignment is in :py:mod:`.topology`. Path finding and spectrum assignment is in :py:mod:`.topology`.
Various tools and auxiliary code, including the JSON I/O handling, is in Various tools and auxiliary code, including the JSON I/O handling, is in
:py:mod:`.tools`. :py:mod:`.tools`.
''' """

View File

@@ -1,4 +1,4 @@
''' """
Simulation of signal propagation in the DWDM network Simulation of signal propagation in the DWDM network
Optical signals, as defined via :class:`.info.SpectralInformation`, enter Optical signals, as defined via :class:`.info.SpectralInformation`, enter
@@ -6,4 +6,4 @@ Optical signals, as defined via :class:`.info.SpectralInformation`, enter
through the :py:mod:`.network`. through the :py:mod:`.network`.
The simulation is controlled via :py:mod:`.parameters` and implemented mainly The simulation is controlled via :py:mod:`.parameters` and implemented mainly
via :py:mod:`.science_utils`. via :py:mod:`.science_utils`.
''' """

View File

@@ -1,12 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' """
gnpy.core.ansi_escapes gnpy.core.ansi_escapes
====================== ======================
A random subset of ANSI terminal escape codes for colored messages A random subset of ANSI terminal escape codes for colored messages
''' """
red = '\x1b[1;31;40m' red = '\x1b[1;31;40m'
blue = '\x1b[1;34;40m' blue = '\x1b[1;34;40m'

File diff suppressed because it is too large Load Diff

View File

@@ -1,73 +1,132 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' """
gnpy.core.equipment gnpy.core.equipment
=================== ===================
This module contains functionality for specifying equipment. This module contains functionality for specifying equipment.
''' """
from collections import defaultdict
from functools import reduce
from typing import List
from gnpy.core.utils import automatic_nch, db2lin from gnpy.core.exceptions import EquipmentConfigError, ConfigurationError
from gnpy.core.exceptions import EquipmentConfigError
def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=False): def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=False):
"""return the trx and SI parameters from eqpt_config for a given type_variety and mode (ie format)""" """return the trx and SI parameters from eqpt_config for a given type_variety and mode (ie format)
if the type or mode do no match an existing transceiver in the library, then the function
raises an error if error_message is True else returns a default mode based on equipment['SI']['default']
If trx_mode is None (but type is valid), it returns an undetermined mode whatever the error message:
this is a special case for automatic mode selection.
"""
trx_params = {} trx_params = {}
default_si_data = equipment['SI']['default'] default_si_data = equipment['SI']['default']
# default transponder characteristics
try: # mainly used with transmission_main_example.py
trxs = equipment['Transceiver'] default_trx_params = {
# if called from path_requests_run.py, trx_mode is filled with None when not specified by user 'f_min': default_si_data.f_min,
# if called from transmission_main.py, trx_mode is '' 'f_max': default_si_data.f_max,
if trx_mode is not None: 'baud_rate': default_si_data.baud_rate,
mode_params = next(mode for trx in trxs 'spacing': default_si_data.spacing,
if trx == trx_type_variety 'OSNR': None,
for mode in trxs[trx].mode 'penalties': {},
if mode['format'] == trx_mode) 'bit_rate': None,
trx_params = {**mode_params} 'cost': None,
# sanity check: spacing baudrate must be smaller than min spacing 'roll_off': default_si_data.roll_off,
if trx_params['baud_rate'] > trx_params['min_spacing']: 'tx_osnr': default_si_data.tx_osnr,
raise EquipmentConfigError(f'Inconsistency in equipment library:\n Transpoder "{trx_type_variety}" mode "{trx_params["format"]}" ' + 'min_spacing': None,
f'has baud rate {trx_params["baud_rate"]*1e-9} GHz greater than min_spacing {trx_params["min_spacing"]*1e-9}.') 'equalization_offset_db': 0
else: }
mode_params = {"format": "undetermined", # Undetermined transponder characteristics
# mainly used with path_request_run.py for the automatic mode computation case
undetermined_trx_params = {
"format": "undetermined",
"baud_rate": None, "baud_rate": None,
"OSNR": None, "OSNR": None,
"penalties": None,
"bit_rate": None, "bit_rate": None,
"roll_off": None, "roll_off": None,
"tx_osnr": None, "tx_osnr": None,
"min_spacing": None, "min_spacing": None,
"cost": None} "cost": None,
trx_params = {**mode_params} "equalization_offset_db": 0
trx_params['f_min'] = equipment['Transceiver'][trx_type_variety].frequency['min'] }
trx_params['f_max'] = equipment['Transceiver'][trx_type_variety].frequency['max']
# TODO: novel automatic feature maybe unwanted if spacing is specified
# trx_params['spacing'] = _automatic_spacing(trx_params['baud_rate'])
# temp = trx_params['spacing']
# print(f'spacing {temp}')
except StopIteration:
if error_message:
raise EquipmentConfigError(f'Could not find transponder "{trx_type_variety}" with mode "{trx_mode}" in equipment library')
else:
# default transponder charcteristics
# mainly used with transmission_main_example.py
trx_params['f_min'] = default_si_data.f_min
trx_params['f_max'] = default_si_data.f_max
trx_params['baud_rate'] = default_si_data.baud_rate
trx_params['spacing'] = default_si_data.spacing
trx_params['OSNR'] = None
trx_params['bit_rate'] = None
trx_params['cost'] = None
trx_params['roll_off'] = default_si_data.roll_off
trx_params['tx_osnr'] = default_si_data.tx_osnr
trx_params['min_spacing'] = None
nch = automatic_nch(trx_params['f_min'], trx_params['f_max'], trx_params['spacing'])
trx_params['nb_channel'] = nch
print(f'There are {nch} channels propagating')
trx_params['power'] = db2lin(default_si_data.power_dbm) * 1e-3
trxs = equipment['Transceiver']
if trx_type_variety in trxs:
modes = {mode['format']: mode for mode in trxs[trx_type_variety].mode}
trx_frequencies = {'f_min': trxs[trx_type_variety].frequency['min'],
'f_max': trxs[trx_type_variety].frequency['max']}
if trx_mode in modes:
# if called from transmission_main.py, trx_mode is ''
trx_params = {**modes[trx_mode], **trx_frequencies}
if trx_params['baud_rate'] > trx_params['min_spacing']:
# sanity check: baudrate must be smaller than min spacing
raise EquipmentConfigError(f'Inconsistency in equipment library:\n Transponder "{trx_type_variety}" '
+ f'mode "{trx_params["format"]}" has baud rate '
+ f'{trx_params["baud_rate"] * 1e-9:.2f} GHz greater than min_spacing '
+ f'{trx_params["min_spacing"] * 1e-9:.2f}.')
trx_params['equalization_offset_db'] = trx_params.get('equalization_offset_db', 0)
return trx_params return trx_params
if trx_mode is None:
# if called from path_requests_run.py, trx_mode is filled with None when not specified by user
trx_params = {**undetermined_trx_params, **trx_frequencies}
return trx_params
if trx_type_variety in trxs and error_message:
raise EquipmentConfigError(f'Could not find transponder "{trx_type_variety}" with mode "{trx_mode}" '
+ 'in equipment library')
if error_message:
raise EquipmentConfigError(f'Could not find transponder "{trx_type_variety}" in equipment library')
trx_params = {**default_trx_params}
return trx_params
def find_type_variety(amps: List[str], equipment: dict) -> List[str]:
"""Returns the multiband type_variety associated with a list of single band type_varieties
Args:
amps (List[str]): A list of single band type_varieties.
equipment (dict): A dictionary containing equipment information.
Returns:
str: an amplifier type variety
"""
listes = find_type_varieties(amps, equipment)
_found_type = list(reduce(lambda x, y: set(x) & set(y), listes))
# Given a list of single band amplifiers, find the multiband amplifier whose multi_band group
# matches. For example, if amps list contains ["a1_LBAND", "a2_CBAND"], with a1.multi_band = [a1_LBAND, a1_CBAND]
# and a2.multi_band = [a1_LBAND, a2_CBAND], then:
# possible_type_varieties = {"a1_LBAND": ["a1", "a2"], "a2_CBAND": ["a2"]}
# listes = [["a1", "a2"], ["a2"]]
# and _found_type = [a2]
if not _found_type:
msg = f'{amps} amps do not belong to the same amp type {listes}'
raise ConfigurationError(msg)
return _found_type
def find_type_varieties(amps: List[str], equipment: dict) -> List[List[str]]:
"""Returns the multiband list of type_varieties associated with a list of single band type_varieties
Args:
amps (List[str]): A list of single band type_varieties.
equipment (dict): A dictionary containing equipment information.
Returns:
List[List[str]]: A list of lists containing the multiband type_varieties
associated with each single band type_variety.
"""
possible_type_varieties = defaultdict(list)
for amp_name, amp in equipment['Edfa'].items():
if amp.multi_band is not None:
for elem in amp.multi_band:
# possible_type_varieties stores the list of multiband amp names that list this elem as
# a possible amplifier of the multiband group. For example, if "std_medium_gain_multiband"
# and "std_medium_gain_multiband_new" contain "std_medium_gain_C" in their "multi_band" list, then:
# possible_type_varieties["std_medium_gain_C"] =
# ["std_medium_gain_multiband", "std_medium_gain_multiband_new"]
possible_type_varieties[elem].append(amp_name)
return [possible_type_varieties[a] for a in amps]

View File

@@ -8,49 +8,421 @@ gnpy.core.info
This module contains classes for modelling :class:`SpectralInformation`. This module contains classes for modelling :class:`SpectralInformation`.
""" """
from __future__ import annotations
from collections import namedtuple from collections import namedtuple
from gnpy.core.utils import automatic_nch, lin2db from collections.abc import Iterable
from typing import Union, List, Optional
from dataclasses import dataclass
from numpy import argsort, mean, array, append, ones, ceil, any, zeros, outer, full, ndarray, asarray
from gnpy.core.utils import automatic_nch, db2lin, watt2dbm
from gnpy.core.exceptions import SpectrumError
DEFAULT_SLOT_WIDTH_STEP = 12.5e9 # Hz
"""Channels with unspecified slot width will have their slot width evaluated as the baud rate rounded up to the minimum
multiple of the DEFAULT_SLOT_WIDTH_STEP (the baud rate is extended including the roll off in this evaluation)"""
class Power(namedtuple('Power', 'signal nli ase')): class Power(namedtuple('Power', 'signal nli ase')):
"""carriers power in W""" """carriers power in W"""
class Channel(namedtuple('Channel', 'channel_number frequency baud_rate roll_off power chromatic_dispersion pmd')): class Channel(
namedtuple('Channel',
'channel_number frequency baud_rate slot_width roll_off power chromatic_dispersion pmd pdl latency')):
"""Class containing the parameters of a WDM signal. """Class containing the parameters of a WDM signal.
:param channel_number: channel number in the WDM grid :param channel_number: channel number in the WDM grid
:param frequency: central frequency of the signal (Hz) :param frequency: central frequency of the signal (Hz)
:param baud_rate: the symbol rate of the signal (Baud) :param baud_rate: the symbol rate of the signal (Baud)
:param slot_width: the slot width (Hz)
:param roll_off: the roll off of the signal. It is a pure number between 0 and 1 :param roll_off: the roll off of the signal. It is a pure number between 0 and 1
:param power (gnpy.core.info.Power): power of signal, ASE noise and NLI (W) :param power (gnpy.core.info.Power): power of signal, ASE noise and NLI (W)
:param chromatic_dispersion: chromatic dispersion (s/m) :param chromatic_dispersion: chromatic dispersion (s/m)
:param pmd: polarization mode dispersion (s) :param pmd: polarization mode dispersion (s)
:param pdl: polarization dependent loss (dB)
:param latency: propagation latency (s)
""" """
class Pref(namedtuple('Pref', 'p_span0, p_spani, neq_ch ')): class SpectralInformation(object):
"""noiseless reference power in dBm: """Class containing the parameters of the entire WDM comb.
p_span0: inital target carrier power
p_spani: carrier power after element i delta_pdb_per_channel: (per frequency) per channel delta power in dbm for the actual mix of channels"""
neq_ch: equivalent channel count in dB"""
def __init__(self, frequency: array, baud_rate: array, slot_width: array, signal: array, nli: array, ase: array,
roll_off: array, chromatic_dispersion: array, pmd: array, pdl: array, latency: array,
delta_pdb_per_channel: array, tx_osnr: array, tx_power: array, label: array):
indices = argsort(frequency)
self._frequency = frequency[indices]
self._df = outer(ones(frequency.shape), frequency) - outer(frequency, ones(frequency.shape))
self._number_of_channels = len(self._frequency)
self._channel_number = [*range(1, self._number_of_channels + 1)]
self._slot_width = slot_width[indices]
self._baud_rate = baud_rate[indices]
overlap = self._frequency[:-1] + self._slot_width[:-1] / 2 > self._frequency[1:] - self._slot_width[1:] / 2
if any(overlap):
overlap = [pair for pair in zip(overlap * self._channel_number[:-1], overlap * self._channel_number[1:])
if pair != (0, 0)]
raise SpectrumError(f'Spectrum required slot widths larger than the frequency spectral distances '
f'between channels: {overlap}.')
exceed = self._baud_rate > self._slot_width
if any(exceed):
raise SpectrumError(f'Spectrum baud rate, including the roll off, larger than the slot width for channels: '
f'{[ch for ch in exceed * self._channel_number if ch]}.')
self._signal = signal[indices]
self._nli = nli[indices]
self._ase = ase[indices]
self._roll_off = roll_off[indices]
self._chromatic_dispersion = chromatic_dispersion[indices]
self._pmd = pmd[indices]
self._pdl = pdl[indices]
self._latency = latency[indices]
self._delta_pdb_per_channel = delta_pdb_per_channel[indices]
self._tx_osnr = tx_osnr[indices]
self._tx_power = tx_power[indices]
self._label = label[indices]
@property
def frequency(self):
return self._frequency
@property
def df(self):
"""Matrix of relative frequency distances between all channels. Positive elements in the upper right side."""
return self._df
@property
def slot_width(self):
return self._slot_width
@property
def baud_rate(self):
return self._baud_rate
@property
def number_of_channels(self):
return self._number_of_channels
@property
def powers(self):
powers = zip(self.signal, self.nli, self.ase)
return [Power(*p) for p in powers]
@property
def signal(self):
return self._signal
@signal.setter
def signal(self, signal):
self._signal = signal
@property
def nli(self):
return self._nli
@nli.setter
def nli(self, nli):
self._nli = nli
@property
def ase(self):
return self._ase
@ase.setter
def ase(self, ase):
self._ase = ase
@property
def roll_off(self):
return self._roll_off
@property
def chromatic_dispersion(self):
return self._chromatic_dispersion
@chromatic_dispersion.setter
def chromatic_dispersion(self, chromatic_dispersion):
self._chromatic_dispersion = chromatic_dispersion
@property
def pmd(self):
return self._pmd
@property
def label(self):
return self._label
@pmd.setter
def pmd(self, pmd):
self._pmd = pmd
@property
def pdl(self):
return self._pdl
@pdl.setter
def pdl(self, pdl):
self._pdl = pdl
@property
def latency(self):
return self._latency
@latency.setter
def latency(self, latency):
self._latency = latency
@property
def delta_pdb_per_channel(self):
return self._delta_pdb_per_channel
@delta_pdb_per_channel.setter
def delta_pdb_per_channel(self, delta_pdb_per_channel):
self._delta_pdb_per_channel = delta_pdb_per_channel
@property
def tx_osnr(self):
return self._tx_osnr
@tx_osnr.setter
def tx_osnr(self, tx_osnr):
self._tx_osnr = tx_osnr
@property
def tx_power(self):
return self._tx_power
@tx_power.setter
def tx_power(self, tx_power):
self._tx_power = tx_power
@property
def channel_number(self):
return self._channel_number
@property
def carriers(self):
entries = zip(self.channel_number, self.frequency, self.baud_rate, self.slot_width,
self.roll_off, self.powers, self.chromatic_dispersion, self.pmd, self.pdl, self.latency)
return [Channel(*entry) for entry in entries]
def apply_attenuation_lin(self, attenuation_lin):
self.signal *= attenuation_lin
self.nli *= attenuation_lin
self.ase *= attenuation_lin
def apply_attenuation_db(self, attenuation_db):
attenuation_lin = 1 / db2lin(attenuation_db)
self.apply_attenuation_lin(attenuation_lin)
def apply_gain_lin(self, gain_lin):
self.signal *= gain_lin
self.nli *= gain_lin
self.ase *= gain_lin
def apply_gain_db(self, gain_db):
gain_lin = db2lin(gain_db)
self.apply_gain_lin(gain_lin)
def __add__(self, other: SpectralInformation):
try:
return SpectralInformation(frequency=append(self.frequency, other.frequency),
slot_width=append(self.slot_width, other.slot_width),
signal=append(self.signal, other.signal), nli=append(self.nli, other.nli),
ase=append(self.ase, other.ase),
baud_rate=append(self.baud_rate, other.baud_rate),
roll_off=append(self.roll_off, other.roll_off),
chromatic_dispersion=append(self.chromatic_dispersion,
other.chromatic_dispersion),
pmd=append(self.pmd, other.pmd),
pdl=append(self.pdl, other.pdl),
latency=append(self.latency, other.latency),
delta_pdb_per_channel=append(self.delta_pdb_per_channel,
other.delta_pdb_per_channel),
tx_osnr=append(self.tx_osnr, other.tx_osnr),
tx_power=append(self.tx_power, other.tx_power),
label=append(self.label, other.label))
except SpectrumError:
raise SpectrumError('Spectra cannot be summed: channels overlapping.')
def _replace(self, carriers):
self.chromatic_dispersion = array([c.chromatic_dispersion for c in carriers])
self.pmd = array([c.pmd for c in carriers])
self.pdl = array([c.pdl for c in carriers])
self.latency = array([c.latency for c in carriers])
self.signal = array([c.power.signal for c in carriers])
self.nli = array([c.power.nli for c in carriers])
self.ase = array([c.power.ase for c in carriers])
return self
class SpectralInformation(namedtuple('SpectralInformation', 'pref carriers')): def create_arbitrary_spectral_information(frequency: Union[ndarray, Iterable, float],
signal: Union[float, ndarray, Iterable],
def __new__(cls, pref, carriers): baud_rate: Union[float, ndarray, Iterable],
return super().__new__(cls, pref, carriers) tx_osnr: Union[float, ndarray, Iterable],
tx_power: Union[float, ndarray, Iterable] = None,
delta_pdb_per_channel: Union[float, ndarray, Iterable] = 0.,
slot_width: Union[float, ndarray, Iterable] = None,
roll_off: Union[float, ndarray, Iterable] = 0.,
chromatic_dispersion: Union[float, ndarray, Iterable] = 0.,
pmd: Union[float, ndarray, Iterable] = 0.,
pdl: Union[float, ndarray, Iterable] = 0.,
latency: Union[float, ndarray, Iterable] = 0.,
label: Union[str, ndarray, Iterable] = None):
"""This is just a wrapper around the SpectralInformation.__init__() that simplifies the creation of
a non-uniform spectral information with NLI and ASE powers set to zero."""
frequency = asarray(frequency)
number_of_channels = frequency.size
try:
signal = full(number_of_channels, signal)
baud_rate = full(number_of_channels, baud_rate)
roll_off = full(number_of_channels, roll_off)
slot_width = full(number_of_channels, slot_width) if slot_width is not None else \
ceil((1 + roll_off) * baud_rate / DEFAULT_SLOT_WIDTH_STEP) * DEFAULT_SLOT_WIDTH_STEP
chromatic_dispersion = full(number_of_channels, chromatic_dispersion)
pmd = full(number_of_channels, pmd)
pdl = full(number_of_channels, pdl)
latency = full(number_of_channels, latency)
nli = zeros(number_of_channels)
ase = zeros(number_of_channels)
delta_pdb_per_channel = full(number_of_channels, delta_pdb_per_channel)
tx_osnr = full(number_of_channels, tx_osnr)
tx_power = full(number_of_channels, tx_power)
label = full(number_of_channels, label)
return SpectralInformation(frequency=frequency, slot_width=slot_width,
signal=signal, nli=nli, ase=ase,
baud_rate=baud_rate, roll_off=roll_off,
chromatic_dispersion=chromatic_dispersion,
pmd=pmd, pdl=pdl, latency=latency,
delta_pdb_per_channel=delta_pdb_per_channel,
tx_osnr=tx_osnr, tx_power=tx_power, label=label)
except ValueError as e:
if 'could not broadcast' in str(e):
raise SpectrumError('Dimension mismatch in input fields.')
else:
raise
def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power, spacing): def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, spacing, tx_osnr, tx_power,
# pref in dB : convert power lin into power in dB delta_pdb=0):
pref = lin2db(power * 1e3) """Creates a fixed slot width spectral information with flat power.
nb_channel = automatic_nch(f_min, f_max, spacing) all arguments are scalar values"""
si = SpectralInformation( number_of_channels = automatic_nch(f_min, f_max, spacing)
pref=Pref(pref, pref, lin2db(nb_channel)), frequency = [(f_min + spacing * i) for i in range(1, number_of_channels + 1)]
carriers=[ delta_pdb_per_channel = delta_pdb * ones(number_of_channels)
Channel(f, (f_min + spacing * f), label = [f'{baud_rate * 1e-9 :.2f}G' for i in range(number_of_channels)]
baud_rate, roll_off, Power(power, 0, 0), 0, 0) for f in range(1, nb_channel + 1) return create_arbitrary_spectral_information(frequency, slot_width=spacing, signal=tx_power, baud_rate=baud_rate,
] roll_off=roll_off, delta_pdb_per_channel=delta_pdb_per_channel,
) tx_osnr=tx_osnr, tx_power=tx_power, label=label)
def is_in_band(frequency: float, band: dict) -> bool:
"""band has {"f_min": value, "f_max": value} format
"""
if frequency >= band['f_min'] and frequency <= band['f_max']:
return True
return False
def demuxed_spectral_information(input_si: SpectralInformation, band: dict) -> Optional[SpectralInformation]:
"""extract a si based on band
"""
filtered_indices = [i for i, f in enumerate(input_si.frequency)
if is_in_band(f - input_si.slot_width[i] / 2, band)
and is_in_band(f + input_si.slot_width[i] / 2, band)]
if filtered_indices:
frequency = input_si.frequency[filtered_indices]
baud_rate = input_si.baud_rate[filtered_indices]
slot_width = input_si.slot_width[filtered_indices]
signal = input_si.signal[filtered_indices]
nli = input_si.nli[filtered_indices]
ase = input_si.ase[filtered_indices]
roll_off = input_si.roll_off[filtered_indices]
chromatic_dispersion = input_si.chromatic_dispersion[filtered_indices]
pmd = input_si.pmd[filtered_indices]
pdl = input_si.pdl[filtered_indices]
latency = input_si.latency[filtered_indices]
delta_pdb_per_channel = input_si.delta_pdb_per_channel[filtered_indices]
tx_osnr = input_si.tx_osnr[filtered_indices]
tx_power = input_si.tx_power[filtered_indices]
label = input_si.label[filtered_indices]
return SpectralInformation(frequency=frequency, baud_rate=baud_rate, slot_width=slot_width, signal=signal,
nli=nli, ase=ase, roll_off=roll_off, chromatic_dispersion=chromatic_dispersion,
pmd=pmd, pdl=pdl, latency=latency, delta_pdb_per_channel=delta_pdb_per_channel,
tx_osnr=tx_osnr, tx_power=tx_power, label=label)
return None
def muxed_spectral_information(input_si_list: List[SpectralInformation]) -> SpectralInformation:
"""return the assembled spectrum
"""
if input_si_list and len(input_si_list) > 1:
si = input_si_list[0] + muxed_spectral_information(input_si_list[1:])
return si return si
elif input_si_list and len(input_si_list) == 1:
return input_si_list[0]
else:
raise ValueError('liste vide')
def carriers_to_spectral_information(initial_spectrum: dict[float, Carrier],
power: float) -> SpectralInformation:
"""Initial spectrum is a dict with key = carrier frequency, and value a Carrier object.
:param initial_spectrum: indexed by frequency in Hz, with power offset (delta_pdb), baudrate, slot width,
tx_osnr, tx_power and roll off.
:param power: power of the request
"""
frequency = list(initial_spectrum.keys())
signal = [c.tx_power for c in initial_spectrum.values()]
roll_off = [c.roll_off for c in initial_spectrum.values()]
baud_rate = [c.baud_rate for c in initial_spectrum.values()]
delta_pdb_per_channel = [c.delta_pdb for c in initial_spectrum.values()]
slot_width = [c.slot_width for c in initial_spectrum.values()]
tx_osnr = [c.tx_osnr for c in initial_spectrum.values()]
tx_power = [c.tx_power for c in initial_spectrum.values()]
label = [c.label for c in initial_spectrum.values()]
return create_arbitrary_spectral_information(frequency=frequency, signal=signal, baud_rate=baud_rate,
slot_width=slot_width, roll_off=roll_off,
delta_pdb_per_channel=delta_pdb_per_channel, tx_osnr=tx_osnr,
tx_power=tx_power, label=label)
@dataclass
class Carrier:
"""One channel in the initial mixed-type spectrum definition, each type being defined by
its delta_pdb (power offset with respect to reference power), baud rate, slot_width, roll_off
tx_power, and tx_osnr. delta_pdb offset is applied to target power out of Roadm.
Label is used to group carriers which belong to the same partition when printing results.
"""
delta_pdb: float
baud_rate: float
slot_width: float
roll_off: float
tx_osnr: float
tx_power: float
label: str
@dataclass
class ReferenceCarrier:
"""Reference channel type is used to determine target power out of ROADM for the reference channel when
constant power spectral density (PSD) equalization is set. Reference channel is the type that has been defined
in SI block and used for the initial design of the network.
Computing the power out of ROADM for the reference channel is required to correctly compute the loss
experienced by reference channel in Roadm element.
Baud rate is required to find the target power in constant PSD: power = PSD_target * baud_rate.
For example, if target PSD is 3.125e4mW/GHz and reference carrier type a 32 GBaud channel then
output power should be -20 dBm and for a 64 GBaud channel power target would need 3 dB more: -17 dBm.
Slot width is required to find the target power in constant PSW (constant power per slot width equalization):
power = PSW_target * slot_width.
For example, if target PSW is 2e4mW/GHz and reference carrier type a 32 GBaud channel in a 50GHz slot width then
output power should be -20 dBm and for a 64 GBaud channel in a 75 GHz slot width, power target would be -18.24 dBm.
Other attributes (like roll-off) may be added there for future equalization purpose.
"""
baud_rate: float
slot_width: float

File diff suppressed because it is too large Load Diff

View File

@@ -7,11 +7,13 @@ gnpy.core.parameters
This module contains all parameters to configure standard network elements. This module contains all parameters to configure standard network elements.
""" """
from collections import namedtuple
from copy import deepcopy
from dataclasses import dataclass
from scipy.constants import c, pi from scipy.constants import c, pi
from numpy import squeeze, log10, exp from numpy import asarray, array, exp, sqrt, log, outer, ones, squeeze, append, flip, linspace, full
from gnpy.core.utils import db2lin, convert_length from gnpy.core.utils import convert_length
from gnpy.core.exceptions import ParametersError from gnpy.core.exceptions import ParametersError
@@ -28,110 +30,235 @@ class Parameters:
class PumpParams(Parameters): class PumpParams(Parameters):
def __init__(self, power, frequency, propagation_direction): def __init__(self, power, frequency, propagation_direction):
self._power = power self.power = power
self._frequency = frequency self.frequency = frequency
self._propagation_direction = propagation_direction self.propagation_direction = propagation_direction.lower()
@property
def power(self):
return self._power
@property
def frequency(self):
return self._frequency
@property
def propagation_direction(self):
return self._propagation_direction
class RamanParams(Parameters): class RamanParams(Parameters):
def __init__(self, **kwargs): def __init__(self, flag=False, method='perturbative', order=2, result_spatial_resolution=10e3,
self._flag_raman = kwargs['flag_raman'] solver_spatial_resolution=10e3):
self._space_resolution = kwargs['space_resolution'] if 'space_resolution' in kwargs else None """Simulation parameters used within the Raman Solver
self._tolerance = kwargs['tolerance'] if 'tolerance' in kwargs else None
@property :params flag: boolean for enabling/disable the evaluation of the Raman power profile in frequency and position
def flag_raman(self): :params method: Raman solver method
return self._flag_raman :params order: solution order for perturbative method
:params result_spatial_resolution: spatial resolution of the evaluated Raman power profile
:params solver_spatial_resolution: spatial step for the iterative solution of the first order ode
"""
self.flag = flag
self.method = method
self.order = order
self.result_spatial_resolution = result_spatial_resolution # [m]
self.solver_spatial_resolution = solver_spatial_resolution # [m]
@property def to_json(self):
def space_resolution(self): return {"flag": self.flag,
return self._space_resolution "method": self.method,
"order": self.order,
@property "result_spatial_resolution": self.result_spatial_resolution,
def tolerance(self): "solver_spatial_resolution": self.solver_spatial_resolution}
return self._tolerance
class NLIParams(Parameters): class NLIParams(Parameters):
def __init__(self, **kwargs): def __init__(self, method='gn_model_analytic', dispersion_tolerance=4, phase_shift_tolerance=0.1,
self._nli_method_name = kwargs['nli_method_name'] computed_channels=None, computed_number_of_channels=None):
self._wdm_grid_size = kwargs['wdm_grid_size'] """Simulation parameters used within the Nli Solver
self._dispersion_tolerance = kwargs['dispersion_tolerance']
self._phase_shift_tolerance = kwargs['phase_shift_tolerance']
self._f_cut_resolution = None
self._f_pump_resolution = None
self._computed_channels = kwargs['computed_channels'] if 'computed_channels' in kwargs else None
@property :params method: formula for NLI calculation
def nli_method_name(self): :params dispersion_tolerance: tuning parameter for ggn model solution
return self._nli_method_name :params phase_shift_tolerance: tuning parameter for ggn model solution
:params computed_channels: the NLI is evaluated for these channels and extrapolated for the others
:params computed_number_of_channels: the NLI is evaluated for this number of channels equally distributed
in the spectrum and extrapolated for the others
"""
self.method = method.lower()
self.dispersion_tolerance = dispersion_tolerance
self.phase_shift_tolerance = phase_shift_tolerance
self.computed_channels = computed_channels
self.computed_number_of_channels = computed_number_of_channels
@property def to_json(self):
def wdm_grid_size(self): return {"method": self.method,
return self._wdm_grid_size "dispersion_tolerance": self.dispersion_tolerance,
"phase_shift_tolerance": self.phase_shift_tolerance,
@property "computed_channels": self.computed_channels,
def dispersion_tolerance(self): "computed_number_of_channels": self.computed_number_of_channels}
return self._dispersion_tolerance
@property
def phase_shift_tolerance(self):
return self._phase_shift_tolerance
@property
def f_cut_resolution(self):
return self._f_cut_resolution
@f_cut_resolution.setter
def f_cut_resolution(self, f_cut_resolution):
self._f_cut_resolution = f_cut_resolution
@property
def f_pump_resolution(self):
return self._f_pump_resolution
@f_pump_resolution.setter
def f_pump_resolution(self, f_pump_resolution):
self._f_pump_resolution = f_pump_resolution
@property
def computed_channels(self):
return self._computed_channels
class SimParams(Parameters): class SimParams(Parameters):
def __init__(self, **kwargs): _shared_dict = {'nli_params': NLIParams(), 'raman_params': RamanParams()}
try:
if 'nli_parameters' in kwargs: @classmethod
self._nli_params = NLIParams(**kwargs['nli_parameters']) def set_params(cls, sim_params):
else: cls._shared_dict['nli_params'] = NLIParams(**sim_params.get('nli_params', {}))
self._nli_params = None cls._shared_dict['raman_params'] = RamanParams(**sim_params.get('raman_params', {}))
if 'raman_parameters' in kwargs:
self._raman_params = RamanParams(**kwargs['raman_parameters'])
else:
self._raman_params = None
except KeyError as e:
raise ParametersError(f'Simulation parameters must include {e}. Configuration: {kwargs}')
@property @property
def nli_params(self): def nli_params(self):
return self._nli_params return self._shared_dict['nli_params']
@property @property
def raman_params(self): def raman_params(self):
return self._raman_params return self._shared_dict['raman_params']
class RoadmParams(Parameters):
def __init__(self, **kwargs):
self.target_pch_out_db = kwargs.get('target_pch_out_db')
self.target_psd_out_mWperGHz = kwargs.get('target_psd_out_mWperGHz')
self.target_out_mWperSlotWidth = kwargs.get('target_out_mWperSlotWidth')
equalisation_type = ['target_pch_out_db', 'target_psd_out_mWperGHz', 'target_out_mWperSlotWidth']
temp = [kwargs.get(k) is not None for k in equalisation_type]
if sum(temp) > 1:
raise ParametersError('ROADM config contains more than one equalisation type.'
+ 'Please choose only one', kwargs)
self.per_degree_pch_out_db = kwargs.get('per_degree_pch_out_db', {})
self.per_degree_pch_psd = kwargs.get('per_degree_psd_out_mWperGHz', {})
self.per_degree_pch_psw = kwargs.get('per_degree_psd_out_mWperSlotWidth', {})
try:
self.add_drop_osnr = kwargs['add_drop_osnr']
self.pmd = kwargs['pmd']
self.pdl = kwargs['pdl']
self.restrictions = kwargs['restrictions']
self.roadm_path_impairments = self.get_roadm_path_impairments(kwargs['roadm-path-impairments'])
except KeyError as e:
raise ParametersError(f'ROADM configurations must include {e}. Configuration: {kwargs}')
self.per_degree_impairments = kwargs.get('per_degree_impairments', [])
self.design_bands = kwargs.get('design_bands', [])
self.per_degree_design_bands = kwargs.get('per_degree_design_bands', {})
def get_roadm_path_impairments(self, path_impairments_list):
"""Get the ROADM list of profiles for impairments definition
transform the ietf model into gnpy internal model: add a path-type in the attributes
"""
if not path_impairments_list:
return {}
authorized_path_types = {
'roadm-express-path': 'express',
'roadm-add-path': 'add',
'roadm-drop-path': 'drop',
}
roadm_path_impairments = {}
for path_impairment in path_impairments_list:
index = path_impairment['roadm-path-impairments-id']
path_type = next(key for key in path_impairment if key in authorized_path_types.keys())
impairment_dict = {'path-type': authorized_path_types[path_type], 'impairment': path_impairment[path_type]}
roadm_path_impairments[index] = RoadmImpairment(impairment_dict)
return roadm_path_impairments
class RoadmPath:
def __init__(self, from_degree, to_degree, path_type, impairment_id=None, impairment=None):
"""Records roadm internal paths, types and impairment
path_type must be in "express", "add", "drop"
impairment_id must be one of the id detailed in equipement
"""
self.from_degree = from_degree
self.to_degree = to_degree
self.path_type = path_type
self.impairment_id = impairment_id
self.impairment = impairment
class RoadmImpairment:
"""Generic definition of impairments for express, add and drop"""
default_values = {
'roadm-pmd': None,
'roadm-cd': None,
'roadm-pdl': None,
'roadm-inband-crosstalk': None,
'roadm-maxloss': 0,
'roadm-osnr': None,
'roadm-pmax': None,
'roadm-noise-figure': None,
'minloss': None,
'typloss': None,
'pmin': None,
'ptyp': None
}
def __init__(self, params):
self.path_type = params.get('path-type')
self.impairments = params['impairment']
class FusedParams(Parameters):
def __init__(self, **kwargs):
self.loss = kwargs['loss'] if 'loss' in kwargs else 1
DEFAULT_RAMAN_COEFFICIENT = {
# SSMF Raman coefficient profile in terms of mode intensity (g0 * A_ff_overlap)
'gamma_raman': array(
[0.0, 8.524419934705497e-16, 2.643567866245371e-15, 4.410548410941305e-15, 6.153422961291078e-15,
7.484924703044943e-15, 8.452060808349209e-15, 9.101549322698156e-15, 9.57837595158966e-15,
1.0008642675474562e-14, 1.0865773569905647e-14, 1.1300776305865833e-14, 1.2143238647099625e-14,
1.3231065750676068e-14, 1.4624900971525384e-14, 1.6013330554840492e-14, 1.7458119359310242e-14,
1.9320241330434762e-14, 2.1720395392873534e-14, 2.4137337406734775e-14, 2.628163218460466e-14,
2.8041019963285974e-14, 2.9723155447089933e-14, 3.129353531005888e-14, 3.251796163324624e-14,
3.3198839487612773e-14, 3.329527690685666e-14, 3.313155691238456e-14, 3.289013852154548e-14,
3.2458917188506916e-14, 3.060684277937575e-14, 3.2660349473783173e-14, 2.957419109657689e-14,
2.518894321396672e-14, 1.734560485857344e-14, 9.902860761605233e-15, 7.219176385099358e-15,
6.079565990401311e-15, 5.828373065963427e-15, 7.20580801091692e-15, 7.561924351387493e-15,
7.621152352332206e-15, 6.8859886780643254e-15, 5.629181047471162e-15, 3.679727598966185e-15,
2.7555869742500355e-15, 2.4810133942597675e-15, 2.2160080532403624e-15, 2.1440626024765557e-15,
2.33873070799544e-15, 2.557317929858713e-15, 3.039839048226572e-15, 4.8337165515610065e-15,
5.4647431818257436e-15, 5.229187813711269e-15, 4.510768525811313e-15, 3.3213473130607794e-15,
2.2602577027996455e-15, 1.969576495866441e-15, 1.5179853954188527e-15, 1.2953988551200156e-15,
1.1304672156251838e-15, 9.10004390675213e-16, 8.432919922183503e-16, 7.849224069008326e-16,
7.827568196032024e-16, 9.000514440646232e-16, 1.3025926460013665e-15, 1.5444108938497558e-15,
1.8795594063060786e-15, 1.7796130169921014e-15, 1.5938159865046653e-15, 1.1585522355108287e-15,
8.507044444633358e-16, 7.625404663756823e-16, 8.14510750925789e-16, 9.047944693473188e-16,
9.636431901702084e-16, 9.298633899602105e-16, 8.349739503637023e-16, 7.482901278066085e-16,
6.240794767134268e-16, 5.00652535687506e-16, 3.553373263685851e-16, 2.0344217706119682e-16,
1.4267522642294203e-16, 8.980016576743517e-17, 2.9829068181832594e-17, 1.4861959129014824e-17,
7.404482113326137e-18]
), # m/W
# SSMF Raman coefficient profile
'g0': array(
[0.00000000e+00, 1.12351610e-05, 3.47838074e-05, 5.79356636e-05, 8.06921680e-05, 9.79845709e-05, 1.10454361e-04,
1.18735302e-04, 1.24736889e-04, 1.30110053e-04, 1.41001273e-04, 1.46383247e-04, 1.57011792e-04, 1.70765865e-04,
1.88408911e-04, 2.05914127e-04, 2.24074028e-04, 2.47508283e-04, 2.77729174e-04, 3.08044243e-04, 3.34764439e-04,
3.56481704e-04, 3.77127256e-04, 3.96269124e-04, 4.10955175e-04, 4.18718761e-04, 4.19511263e-04, 4.17025384e-04,
4.13565369e-04, 4.07726048e-04, 3.83671291e-04, 4.08564283e-04, 3.69571936e-04, 3.14442090e-04, 2.16074535e-04,
1.23097823e-04, 8.95457457e-05, 7.52470400e-05, 7.19806145e-05, 8.87961158e-05, 9.30812065e-05, 9.37058268e-05,
8.45719619e-05, 6.90585286e-05, 4.50407159e-05, 3.36521245e-05, 3.02292475e-05, 2.69376939e-05, 2.60020897e-05,
2.82958958e-05, 3.08667558e-05, 3.66024657e-05, 5.80610307e-05, 6.54797937e-05, 6.25022715e-05, 5.37806442e-05,
3.94996621e-05, 2.68120644e-05, 2.33038554e-05, 1.79140757e-05, 1.52472424e-05, 1.32707565e-05, 1.06541760e-05,
9.84649374e-06, 9.13999627e-06, 9.08971012e-06, 1.04227525e-05, 1.50419271e-05, 1.77838232e-05, 2.15810815e-05,
2.03744008e-05, 1.81939341e-05, 1.31862121e-05, 9.65352116e-06, 8.62698322e-06, 9.18688016e-06, 1.01737784e-05,
1.08017817e-05, 1.03903588e-05, 9.30040333e-06, 8.30809173e-06, 6.90650401e-06, 5.52238029e-06, 3.90648708e-06,
2.22908227e-06, 1.55796177e-06, 9.77218716e-07, 3.23477236e-07, 1.60602454e-07, 7.97306386e-08]
), # [1 / (W m)]
# Note the non-uniform spacing of this range; this is required for properly capturing the Raman peak shape.
'frequency_offset': array([
0., 0.5, 1., 1.5, 2., 2.5, 3., 3.5, 4., 4.5, 5., 5.5, 6., 6.5, 7., 7.5, 8., 8.5, 9., 9.5, 10., 10.5, 11., 11.5,
12., 12.5, 12.75, 13., 13.25, 13.5, 14., 14.5, 14.75, 15., 15.5, 16., 16.5, 17., 17.5, 18., 18.25, 18.5, 18.75,
19., 19.5, 20., 20.5, 21., 21.5, 22., 22.5, 23., 23.5, 24., 24.5, 25., 25.5, 26., 26.5, 27., 27.5, 28., 28.5,
29., 29.5, 30., 30.5, 31., 31.5, 32., 32.5, 33., 33.5, 34., 34.5, 35., 35.5, 36., 36.5, 37., 37.5, 38., 38.5,
39., 39.5, 40., 40.5, 41., 41.5, 42.]) * 1e12, # [Hz]
# Raman profile reference frequency
'reference_frequency': 206.184634112792e12, # [Hz] (1454 nm)
# Raman profile reference effective area
'reference_effective_area': 75.74659443542413e-12 # [m^2] (@1454 nm)
}
class RamanGainCoefficient(namedtuple('RamanGainCoefficient', 'normalized_gamma_raman frequency_offset')):
""" Raman Gain Coefficient Parameters
Based on:
Andrea DAmico, Bruno Correia, Elliot London, Emanuele Virgillito, Giacomo Borraccini, Antonio Napoli,
and Vittorio Curri, "Scalable and Disaggregated GGN Approximation Applied to a C+L+S Optical Network,"
J. Lightwave Technol. 40, 3499-3511 (2022)
Section III.D
"""
class FiberParams(Parameters): class FiberParams(Parameters):
@@ -139,45 +266,94 @@ class FiberParams(Parameters):
try: try:
self._length = convert_length(kwargs['length'], kwargs['length_units']) self._length = convert_length(kwargs['length'], kwargs['length_units'])
# fixed attenuator for padding # fixed attenuator for padding
self._att_in = kwargs['att_in'] if 'att_in' in kwargs else 0 self._att_in = kwargs.get('att_in', 0)
# if not defined in the network json connector loss in/out # if not defined in the network json connector loss in/out
# the None value will be updated in network.py[build_network] # the None value will be updated in network.py[build_network]
# with default values from eqpt_config.json[Spans] # with default values from eqpt_config.json[Spans]
self._con_in = kwargs['con_in'] if 'con_in' in kwargs else None self._con_in = kwargs.get('con_in')
self._con_out = kwargs['con_out'] if 'con_out' in kwargs else None self._con_out = kwargs.get('con_out')
# Reference frequency (unique for all parameters: beta2, beta3, gamma, effective_area)
if 'ref_wavelength' in kwargs: if 'ref_wavelength' in kwargs:
self._ref_wavelength = kwargs['ref_wavelength'] self._ref_wavelength = kwargs['ref_wavelength']
self._ref_frequency = c / self.ref_wavelength self._ref_frequency = c / self._ref_wavelength
elif 'ref_frequency' in kwargs: elif 'ref_frequency' in kwargs:
self._ref_frequency = kwargs['ref_frequency'] self._ref_frequency = kwargs['ref_frequency']
self._ref_wavelength = c / self.ref_frequency self._ref_wavelength = c / self._ref_frequency
else: else:
self._ref_wavelength = 1550e-9 self._ref_wavelength = 1550e-9 # conventional central C band wavelength [m]
self._ref_frequency = c / self.ref_wavelength self._ref_frequency = c / self._ref_wavelength
self._dispersion = kwargs['dispersion'] # s/m/m
self._dispersion_slope = kwargs['dispersion_slope'] if 'dispersion_slope' in kwargs else \ # Chromatic Dispersion
-2 * self._dispersion/self.ref_wavelength # s/m/m/m if 'dispersion_per_frequency' in kwargs:
self._beta2 = -(self.ref_wavelength ** 2) * self.dispersion / (2 * pi * c) # 1/(m * Hz^2) # Frequency-dependent dispersion
# Eq. (3.23) in Abramczyk, Halina. "Dispersion phenomena in optical fibers." Virtual European University self._dispersion = asarray(kwargs['dispersion_per_frequency']['value']) # s/m/m
# on Lasers. Available online: http://mitr.p.lodz.pl/evu/lectures/Abramczyk3.pdf self._f_dispersion_ref = asarray(kwargs['dispersion_per_frequency']['frequency']) # Hz
# (accessed on 25 March 2018) (2005). self._dispersion_slope = None
self._beta3 = ((self.dispersion_slope - (4*pi*c/self.ref_wavelength**3) * self.beta2) / elif 'dispersion' in kwargs:
(2*pi*c/self.ref_wavelength**2)**2) # Single value dispersion
self._dispersion = asarray(kwargs['dispersion']) # s/m/m
self._dispersion_slope = kwargs.get('dispersion_slope') # s/m/m/m
self._f_dispersion_ref = asarray(self._ref_frequency) # Hz
else:
# Default single value dispersion
self._dispersion = asarray(1.67e-05) # s/m/m
self._dispersion_slope = None
self._f_dispersion_ref = asarray(self.ref_frequency) # Hz
# Effective Area and Nonlinear Coefficient
self._effective_area = kwargs.get('effective_area') # m^2
self._n1 = 1.468
self._core_radius = 4.2e-6 # m
self._n2 = 2.6e-20 # m^2/W
if self._effective_area is not None:
default_gamma = 2 * pi * self._n2 / (self._ref_wavelength * self._effective_area)
self._gamma = kwargs.get('gamma', default_gamma) # 1/W/m
elif 'gamma' in kwargs:
self._gamma = kwargs['gamma'] # 1/W/m self._gamma = kwargs['gamma'] # 1/W/m
self._pmd_coef = kwargs['pmd_coef'] # s/sqrt(m) self._effective_area = 2 * pi * self._n2 / (self._ref_wavelength * self._gamma) # m^2
if type(kwargs['loss_coef']) == dict:
self._loss_coef = squeeze(kwargs['loss_coef']['loss_coef_power']) * 1e-3 # lineic loss dB/m
self._f_loss_ref = squeeze(kwargs['loss_coef']['frequency']) # Hz
else: else:
self._loss_coef = kwargs['loss_coef'] * 1e-3 # lineic loss dB/m self._effective_area = 83e-12 # m^2
self._f_loss_ref = 193.5e12 # Hz self._gamma = 2 * pi * self._n2 / (self._ref_wavelength * self._effective_area) # 1/W/m
self._lin_attenuation = db2lin(self.length * self.loss_coef) self._contrast = 0.5 * (c / (2 * pi * self._ref_frequency * self._core_radius * self._n1) * exp(
self._lin_loss_exp = self.loss_coef / (10 * log10(exp(1))) # linear power exponent loss Neper/m pi * self._core_radius ** 2 / self._effective_area)) ** 2
self._effective_length = (1 - exp(- self.lin_loss_exp * self.length)) / self.lin_loss_exp
self._asymptotic_length = 1 / self.lin_loss_exp # Raman Gain Coefficient
# raman parameters (not compulsory) raman_coefficient = kwargs.get('raman_coefficient')
self._raman_efficiency = kwargs['raman_efficiency'] if 'raman_efficiency' in kwargs else None if raman_coefficient is None:
self._pumps_loss_coef = kwargs['pumps_loss_coef'] if 'pumps_loss_coef' in kwargs else None self._raman_reference_frequency = DEFAULT_RAMAN_COEFFICIENT['reference_frequency']
frequency_offset = asarray(DEFAULT_RAMAN_COEFFICIENT['frequency_offset'])
gamma_raman = asarray(DEFAULT_RAMAN_COEFFICIENT['gamma_raman'])
stokes_wave = self._raman_reference_frequency - frequency_offset
normalized_gamma_raman = gamma_raman / self._raman_reference_frequency # 1 / m / W / Hz
self._g0 = gamma_raman / self.effective_area_overlap(stokes_wave, self._raman_reference_frequency)
else:
self._raman_reference_frequency = raman_coefficient['reference_frequency']
frequency_offset = asarray(raman_coefficient['frequency_offset'])
stokes_wave = self._raman_reference_frequency - frequency_offset
self._g0 = asarray(raman_coefficient['g0'])
gamma_raman = self._g0 * self.effective_area_overlap(stokes_wave, self._raman_reference_frequency)
normalized_gamma_raman = gamma_raman / self._raman_reference_frequency # 1 / m / W / Hz
# Raman gain coefficient array of the frequency offset constructed such that positive frequency values
# represent a positive power transfer from higher frequency and vice versa
frequency_offset = append(-flip(frequency_offset[1:]), frequency_offset)
normalized_gamma_raman = append(- flip(normalized_gamma_raman[1:]), normalized_gamma_raman)
self._raman_coefficient = RamanGainCoefficient(normalized_gamma_raman, frequency_offset)
# Polarization Mode Dispersion
self._pmd_coef = kwargs['pmd_coef'] # s/sqrt(m)
# Loss Coefficient
if isinstance(kwargs['loss_coef'], dict):
self._loss_coef = asarray(kwargs['loss_coef']['value']) * 1e-3 # lineic loss dB/m
self._f_loss_ref = asarray(kwargs['loss_coef']['frequency']) # Hz
else:
self._loss_coef = asarray(kwargs['loss_coef']) * 1e-3 # lineic loss dB/m
self._f_loss_ref = asarray(self._ref_frequency) # Hz
# Lumped Losses
self._lumped_losses = kwargs['lumped_losses'] if 'lumped_losses' in kwargs else array([])
self._latency = self._length / (c / self._n1) # s
except KeyError as e: except KeyError as e:
raise ParametersError(f'Fiber configurations json must include {e}. Configuration: {kwargs}') raise ParametersError(f'Fiber configurations json must include {e}. Configuration: {kwargs}')
@@ -210,6 +386,10 @@ class FiberParams(Parameters):
def con_out(self): def con_out(self):
return self._con_out return self._con_out
@property
def lumped_losses(self):
return self._lumped_losses
@con_out.setter @con_out.setter
def con_out(self, con_out): def con_out(self, con_out):
self._con_out = con_out self._con_out = con_out
@@ -218,6 +398,10 @@ class FiberParams(Parameters):
def dispersion(self): def dispersion(self):
return self._dispersion return self._dispersion
@property
def f_dispersion_ref(self):
return self._f_dispersion_ref
@property @property
def dispersion_slope(self): def dispersion_slope(self):
return self._dispersion_slope return self._dispersion_slope
@@ -226,6 +410,20 @@ class FiberParams(Parameters):
def gamma(self): def gamma(self):
return self._gamma return self._gamma
def effective_area_scaling(self, frequency):
V = 2 * pi * frequency / c * self._core_radius * self._n1 * sqrt(2 * self._contrast)
w = self._core_radius / sqrt(log(V))
return asarray(pi * w ** 2)
def effective_area_overlap(self, frequency_stokes_wave, frequency_pump):
effective_area_stokes_wave = self.effective_area_scaling(frequency_stokes_wave)
effective_area_pump = self.effective_area_scaling(frequency_pump)
return squeeze(outer(effective_area_stokes_wave, ones(effective_area_pump.size)) + outer(
ones(effective_area_stokes_wave.size), effective_area_pump)) / 2
def gamma_scaling(self, frequency):
return asarray(2 * pi * self._n2 * frequency / (c * self.effective_area_scaling(frequency)))
@property @property
def pmd_coef(self): def pmd_coef(self):
return self._pmd_coef return self._pmd_coef
@@ -238,14 +436,6 @@ class FiberParams(Parameters):
def ref_frequency(self): def ref_frequency(self):
return self._ref_frequency return self._ref_frequency
@property
def beta2(self):
return self._beta2
@property
def beta3(self):
return self._beta3
@property @property
def loss_coef(self): def loss_coef(self):
return self._loss_coef return self._loss_coef
@@ -255,31 +445,276 @@ class FiberParams(Parameters):
return self._f_loss_ref return self._f_loss_ref
@property @property
def lin_loss_exp(self): def raman_coefficient(self):
return self._lin_loss_exp return self._raman_coefficient
@property @property
def lin_attenuation(self): def latency(self):
return self._lin_attenuation return self._latency
@property
def effective_length(self):
return self._effective_length
@property
def asymptotic_length(self):
return self._asymptotic_length
@property
def raman_efficiency(self):
return self._raman_efficiency
@property
def pumps_loss_coef(self):
return self._pumps_loss_coef
def asdict(self): def asdict(self):
dictionary = super().asdict() dictionary = super().asdict()
dictionary['loss_coef'] = self.loss_coef * 1e3 dictionary['loss_coef'] = self.loss_coef * 1e3
dictionary['length_units'] = 'm' dictionary['length_units'] = 'm'
if len(self.lumped_losses) == 0:
dictionary.pop('lumped_losses')
if not self.raman_coefficient:
dictionary.pop('raman_coefficient')
else:
raman_frequency_offset = \
self.raman_coefficient.frequency_offset[self.raman_coefficient.frequency_offset >= 0]
dictionary['raman_coefficient'] = {'g0': self._g0.tolist(),
'frequency_offset': raman_frequency_offset.tolist(),
'reference_frequency': self._raman_reference_frequency}
return dictionary return dictionary
class EdfaParams:
default_values = {
'f_min': None,
'f_max': None,
'multi_band': None,
'bands': None,
'type_variety': '',
'type_def': '',
'gain_flatmax': None,
'gain_min': None,
'p_max': None,
'nf_model': None,
'dual_stage_model': None,
'preamp_variety': None,
'booster_variety': None,
'nf_min': None,
'nf_max': None,
'nf_coef': None,
'nf0': None,
'nf_fit_coeff': None,
'nf_ripple': 0,
'dgt': None,
'gain_ripple': 0,
'tilt_ripple': 0,
'f_ripple_ref': None,
'out_voa_auto': False,
'allowed_for_design': False,
'raman': False,
'pmd': 0,
'pdl': 0,
'advance_configurations_from_json': None
}
def __init__(self, **params):
try:
self.type_variety = params['type_variety']
self.type_def = params['type_def']
# Bandwidth
self.f_min = params['f_min']
self.f_max = params['f_max']
self.bandwidth = self.f_max - self.f_min if self.f_max and self.f_min else None
self.f_cent = (self.f_max + self.f_min) / 2 if self.f_max and self.f_min else None
self.f_ripple_ref = params['f_ripple_ref']
self.bands = [{'f_min': params['f_min'],
'f_max': params['f_max']}]
# Gain
self.gain_flatmax = params['gain_flatmax']
self.gain_min = params['gain_min']
gain_ripple = params['gain_ripple']
if gain_ripple == 0:
self.gain_ripple = asarray([0, 0])
self.f_ripple_ref = asarray([self.f_min, self.f_max])
else:
self.gain_ripple = asarray(gain_ripple)
if self.f_ripple_ref is not None:
if (self.f_ripple_ref[0] != self.f_min) or (self.f_ripple_ref[-1] != self.f_max):
raise ParametersError("The reference ripple frequency maximum and minimum have to coincide "
"with the EDFA frequency maximum and minimum.")
elif self.gain_ripple.size != self.f_ripple_ref.size:
raise ParametersError("The reference ripple frequency and the gain ripple must have the same "
"size.")
else:
self.f_ripple_ref = linspace(self.f_min, self.f_max, self.gain_ripple.size)
tilt_ripple = params['tilt_ripple']
if tilt_ripple == 0:
self.tilt_ripple = full(self.gain_ripple.size, 0)
else:
self.tilt_ripple = asarray(tilt_ripple)
if self.tilt_ripple.size != self.gain_ripple.size:
raise ParametersError("The tilt ripple and the gain ripple must have the same size.")
# Power
self.p_max = params['p_max']
# Noise Figure
self.nf_model = params['nf_model']
self.nf_min = params['nf_min']
self.nf_max = params['nf_max']
self.nf_coef = params['nf_coef']
self.nf0 = params['nf0']
self.nf_fit_coeff = params['nf_fit_coeff']
nf_ripple = params['nf_ripple']
if nf_ripple == 0:
self.nf_ripple = full(self.gain_ripple.size, 0)
else:
self.nf_ripple = asarray(nf_ripple)
if self.nf_ripple.size != self.gain_ripple.size:
raise ParametersError(
"The noise figure ripple and the gain ripple must have the same size. %s, %s",
self.nf_ripple.size, self.gain_ripple.size)
# VOA
self.out_voa_auto = params['out_voa_auto']
# Dual Stage
self.dual_stage_model = params['dual_stage_model']
if self.dual_stage_model is not None:
# Preamp
self.preamp_variety = params['preamp_variety']
self.preamp_type_def = params['preamp_type_def']
self.preamp_nf_model = params['preamp_nf_model']
self.preamp_nf_fit_coeff = params['preamp_nf_fit_coeff']
self.preamp_gain_min = params['preamp_gain_min']
self.preamp_gain_flatmax = params['preamp_gain_flatmax']
# Booster
self.booster_variety = params['booster_variety']
self.booster_type_def = params['booster_type_def']
self.booster_nf_model = params['booster_nf_model']
self.booster_nf_fit_coeff = params['booster_nf_fit_coeff']
self.booster_gain_min = params['booster_gain_min']
self.booster_gain_flatmax = params['booster_gain_flatmax']
# Others
self.pmd = params['pmd']
self.pdl = params['pdl']
self.raman = params['raman']
self.dgt = params['dgt']
self.advance_configurations_from_json = params['advance_configurations_from_json']
# Design
self.allowed_for_design = params['allowed_for_design']
except KeyError as e:
raise ParametersError(f'Edfa configurations json must include {e}. Configuration: {params}')
def update_params(self, kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
class EdfaOperational:
default_values = {
'gain_target': None,
'delta_p': None,
'out_voa': None,
'tilt_target': None
}
def __init__(self, **operational):
self.update_attr(operational)
def update_attr(self, kwargs):
clean_kwargs = {k: v for k, v in kwargs.items() if v != ''}
for k, v in self.default_values.items():
setattr(self, k, clean_kwargs.get(k, v))
def __repr__(self):
return (f'{type(self).__name__}('
f'gain_target={self.gain_target!r}, '
f'tilt_target={self.tilt_target!r})')
DEFAULT_EDFA_CONFIG = {
"nf_ripple": [
0.0
],
"gain_ripple": [
0.0
],
"f_min": 191.275e12,
"f_max": 196.125e12,
"dgt": [
1.0, 1.017807767853702, 1.0356155337864215, 1.0534217504465226, 1.0712204022764056, 1.0895983485572227,
1.108555289615659, 1.1280891949729075, 1.1476135933863398, 1.1672278304018044, 1.1869318618366975,
1.2067249615595257, 1.2264996957264114, 1.2428104897182262, 1.2556591482982988, 1.2650555289898042,
1.2744470198196236, 1.2838336236692311, 1.2932153453410835, 1.3040618749785347, 1.316383926863083,
1.3301807335621048, 1.3439818461440451, 1.3598972673004606, 1.3779439775587023, 1.3981208704326855,
1.418273806730323, 1.4340878115214444, 1.445565137158368, 1.45273959485914, 1.4599103316162523,
1.4670307626366115, 1.474100442252211, 1.48111939735681, 1.488134243479226, 1.495145456062699,
1.502153039909686, 1.5097346239790443, 1.5178910621476225, 1.5266220576235803, 1.5353620432989845,
1.545374152761467, 1.5566577309558969, 1.569199764184379, 1.5817353179379183, 1.5986915141218316,
1.6201194134191075, 1.6460167077689267, 1.6719047669939942, 1.6918150918099673, 1.7057507692361864,
1.7137640932265894, 1.7217732861435076, 1.7297783508684146, 1.737780757913635, 1.7459181197626403,
1.7541903672600494, 1.7625959636196327, 1.7709972329654864, 1.7793941781790852, 1.7877868031023945,
1.7961751115773796, 1.8045606557581335, 1.8139629377087627, 1.824381436842932, 1.835814081380705,
1.847275503201129, 1.862235672444246, 1.8806927939516411, 1.9026104247588487, 1.9245345552113182,
1.9482128147680253, 1.9736443063300082, 2.0008103857988204, 2.0279625371819305, 2.055100772005235,
2.082225099873648, 2.1183028432496016, 2.16337565384239, 2.2174389328192197, 2.271520771371253,
2.322373696229342, 2.3699990328716107, 2.414398437185221, 2.4587748041127506, 2.499446286796604,
2.5364027376452056, 2.5696460593920065, 2.602860350286428, 2.630396440815385, 2.6521732021128046,
2.6681935771243177, 2.6841217449620203, 2.6947834587664494, 2.705443819238505, 2.714526681131686
]
}
class MultiBandParams:
default_values = {
'bands': [],
'type_variety': '',
'type_def': None,
'allowed_for_design': False
}
def __init__(self, **params):
try:
self.update_attr(params)
except KeyError as e:
raise ParametersError(f'Multiband configurations json must include {e}. Configuration: {params}')
def update_attr(self, kwargs):
clean_kwargs = {k: v for k, v in kwargs.items() if v != ''}
for k, v in self.default_values.items():
# use deepcopy to avoid sharing same object amongst all instance when v is a list or a dict!
if isinstance(v, (list, dict)):
setattr(self, k, clean_kwargs.get(k, deepcopy(v)))
else:
setattr(self, k, clean_kwargs.get(k, v))
class TransceiverParams:
def __init__(self, **params):
self.design_bands = params.get('design_bands', [])
self.per_degree_design_bands = params.get('per_degree_design_bands', {})
@dataclass
class FrequencyBand:
"""Frequency band
"""
f_min: float
f_max: float
DEFAULT_BANDS_DEFINITION = {
"LBAND": FrequencyBand(f_min=187e12, f_max=189e12),
"CBAND": FrequencyBand(f_min=191.3e12, f_max=196.0e12)
}
# use this definition to index amplifiers'element of a multiband amplifier.
# this is not the design band
def find_band_name(band: FrequencyBand) -> str:
"""return the default band name (CBAND, LBAND, ...) that corresponds to the band frequency range
Use the band center frequency: if center frequency is inside the band then returns CBAND.
This is to flexibly encompass all kind of bands definitions.
returns the first matching band name.
"""
for band_name, frequency_range in DEFAULT_BANDS_DEFINITION.items():
center_frequency = (band.f_min + band.f_max) / 2
if center_frequency >= frequency_range.f_min and center_frequency <= frequency_range.f_max:
return band_name
return 'unknown_band'

File diff suppressed because it is too large Load Diff

View File

@@ -9,8 +9,10 @@ This module contains utility functions that are used with gnpy.
""" """
from csv import writer from csv import writer
from numpy import pi, cos, sqrt, log10, linspace, zeros, shape, where, logical_and from numpy import pi, cos, sqrt, log10, linspace, zeros, shape, where, logical_and, mean, array
from scipy import constants from scipy import constants
from copy import deepcopy
from typing import List, Union
from gnpy.core.exceptions import ConfigurationError from gnpy.core.exceptions import ConfigurationError
@@ -106,7 +108,99 @@ def db2lin(value):
return 10**(value / 10) return 10**(value / 10)
def watt2dbm(value):
"""Convert Watt units to dBm
>>> round(watt2dbm(0.001), 1)
0.0
>>> round(watt2dbm(0.02), 1)
13.0
"""
return lin2db(value * 1e3)
def dbm2watt(value):
"""Convert dBm units to Watt
>>> round(dbm2watt(0), 4)
0.001
>>> round(dbm2watt(-3), 4)
0.0005
>>> round(dbm2watt(13), 4)
0.02
"""
return db2lin(value) * 1e-3
def psd2powerdbm(psd_mwperghz, baudrate_baud):
"""computes power in dBm based on baudrate in bauds and psd in mW/GHz
>>> round(psd2powerdbm(0.031176, 64e9),3)
3.0
>>> round(psd2powerdbm(0.062352, 32e9),3)
3.0
>>> round(psd2powerdbm(0.015625, 64e9),3)
0.0
"""
return lin2db(baudrate_baud * psd_mwperghz * 1e-9)
def power_dbm_to_psd_mw_ghz(power_dbm, baudrate_baud):
"""computes power spectral density in mW/GHz based on baudrate in bauds and power in dBm
>>> power_dbm_to_psd_mw_ghz(0, 64e9)
0.015625
>>> round(power_dbm_to_psd_mw_ghz(3, 64e9), 6)
0.031176
>>> round(power_dbm_to_psd_mw_ghz(3, 32e9), 6)
0.062352
"""
return db2lin(power_dbm) / (baudrate_baud * 1e-9)
def psd_mw_per_ghz(power_watt, baudrate_baud):
"""computes power spectral density in mW/GHz based on baudrate in bauds and power in W
>>> psd_mw_per_ghz(2e-3, 32e9)
0.0625
>>> psd_mw_per_ghz(1e-3, 64e9)
0.015625
>>> psd_mw_per_ghz(0.5e-3, 32e9)
0.015625
"""
return power_watt * 1e3 / (baudrate_baud * 1e-9)
def round2float(number, step): def round2float(number, step):
"""Round a floating point number so that its "resolution" is not bigger than 'step'
The finest step is fixed at 0.01; smaller values are silently changed to 0.01.
>>> round2float(123.456, 1000)
0.0
>>> round2float(123.456, 100)
100.0
>>> round2float(123.456, 10)
120.0
>>> round2float(123.456, 1)
123.0
>>> round2float(123.456, 0.1)
123.5
>>> round2float(123.456, 0.01)
123.46
>>> round2float(123.456, 0.001)
123.46
>>> round2float(123.249, 0.5)
123.0
>>> round2float(123.250, 0.5)
123.0
>>> round2float(123.251, 0.5)
123.5
>>> round2float(123.300, 0.2)
123.2
>>> round2float(123.301, 0.2)
123.4
"""
step = round(step, 1) step = round(step, 1)
if step >= 0.01: if step >= 0.01:
number = round(number / step, 0) number = round(number / step, 0)
@@ -120,23 +214,37 @@ wavelength2freq = constants.lambda2nu
freq2wavelength = constants.nu2lambda freq2wavelength = constants.nu2lambda
def freq2wavelength(value):
""" Converts frequency units to wavelength units.
>>> round(freq2wavelength(191.35e12) * 1e9, 3)
1566.723
>>> round(freq2wavelength(196.1e12) * 1e9, 3)
1528.773
"""
return constants.c / value
def snr_sum(snr, bw, snr_added, bw_added=12.5e9): def snr_sum(snr, bw, snr_added, bw_added=12.5e9):
snr_added = snr_added - lin2db(bw / bw_added) snr_added = snr_added - lin2db(bw / bw_added)
snr = -lin2db(db2lin(-snr) + db2lin(-snr_added)) snr = -lin2db(db2lin(-snr) + db2lin(-snr_added))
return snr return snr
def per_label_average(values, labels):
"""computes the average per defined spectrum band, using labels
>>> labels = ['A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'B', 'C', 'D', 'D', 'D', 'D']
>>> values = [28.51, 28.23, 28.15, 28.17, 28.36, 28.53, 28.64, 28.68, 28.7, 28.71, 28.72, 28.73, 28.74, 28.91, 27.96, 27.85, 27.87, 28.02]
>>> per_label_average(values, labels)
{'A': 28.28, 'B': 28.68, 'C': 28.91, 'D': 27.92}
"""
label_set = sorted(set(labels))
summary = {}
for label in label_set:
vals = [val for val, lab in zip(values, labels) if lab == label]
summary[label] = round(mean(vals), 2)
return summary
def pretty_summary_print(summary):
"""Build a prettty string that shows the summary dict values per label with 2 digits"""
if len(summary) == 1:
return f'{list(summary.values())[0]:.2f}'
text = ', '.join([f'{label}: {value:.2f}' for label, value in summary.items()])
return text
def deltawl2deltaf(delta_wl, wavelength): def deltawl2deltaf(delta_wl, wavelength):
"""deltawl2deltaf(delta_wl, wavelength): """deltawl2deltaf(delta_wl, wavelength):
delta_wl is BW in wavelength units delta_wl is BW in wavelength units
@@ -156,9 +264,9 @@ def deltawl2deltaf(delta_wl, wavelength):
def deltaf2deltawl(delta_f, frequency): def deltaf2deltawl(delta_f, frequency):
""" deltawl2deltaf(delta_f, frequency): """convert delta frequency to delta wavelength
converts delta frequency to delta wavelength
units for delta_wl and wavelength must be same Units for delta_wl and wavelength must be same.
:param delta_f: delta frequency in same units as frequency :param delta_f: delta frequency in same units as frequency
:param frequency: frequency BW is relevant for :param frequency: frequency BW is relevant for
@@ -173,8 +281,7 @@ def deltaf2deltawl(delta_f, frequency):
def rrc(ffs, baud_rate, alpha): def rrc(ffs, baud_rate, alpha):
""" rrc(ffs, baud_rate, alpha): computes the root-raised cosine filter """compute the root-raised cosine filter function
function.
:param ffs: A numpy array of frequencies :param ffs: A numpy array of frequencies
:param baud_rate: The Baud Rate of the System :param baud_rate: The Baud Rate of the System
@@ -200,7 +307,7 @@ def rrc(ffs, baud_rate, alpha):
def merge_amplifier_restrictions(dict1, dict2): def merge_amplifier_restrictions(dict1, dict2):
"""Updates contents of dicts recursively """Update contents of dicts recursively
>>> d1 = {'params': {'restrictions': {'preamp_variety_list': [], 'booster_variety_list': []}}} >>> d1 = {'params': {'restrictions': {'preamp_variety_list': [], 'booster_variety_list': []}}}
>>> d2 = {'params': {'target_pch_out_db': -20}} >>> d2 = {'params': {'target_pch_out_db': -20}}
@@ -295,3 +402,159 @@ def convert_length(value, units):
return value * 1e3 return value * 1e3
else: else:
raise ConfigurationError(f'Cannot convert length in "{units}" into meters') raise ConfigurationError(f'Cannot convert length in "{units}" into meters')
def replace_none(dictionary):
""" Replaces None with inf values in a frequency slots dict
>>> replace_none({'N': 3, 'M': None})
{'N': 3, 'M': inf}
"""
for key, val in dictionary.items():
if val is None:
dictionary[key] = float('inf')
if val == float('inf'):
dictionary[key] = None
return dictionary
def order_slots(slots):
""" Order frequency slots from larger slots to smaller ones up to None
>>> l = [{'N': 3, 'M': None}, {'N': 2, 'M': 1}, {'N': None, 'M': None},{'N': 7, 'M': 2},{'N': None, 'M': 1} , {'N': None, 'M': 0}]
>>> order_slots(l)
([7, 2, None, None, 3, None], [2, 1, 1, 0, None, None], [3, 1, 4, 5, 0, 2])
"""
slots_list = deepcopy(slots)
slots_list = [replace_none(e) for e in slots_list]
for i, e in enumerate(slots_list):
e['i'] = i
slots_list = sorted(slots_list, key=lambda x: (-x['M'], x['N']) if x['M'] != float('inf') else (x['M'], x['N']))
slots_list = [replace_none(e) for e in slots_list]
return [e['N'] for e in slots_list], [e['M'] for e in slots_list], [e['i'] for e in slots_list]
def restore_order(elements, order):
""" Use order to re-order the element of the list, and ignore None values
>>> restore_order([7, 2, None, None, 3, None], [3, 1, 4, 5, 0, 2])
[3, 2, 7]
"""
return [elements[i[0]] for i in sorted(enumerate(order), key=lambda x:x[1]) if elements[i[0]] is not None]
def unique_ordered(elements):
"""
"""
unique_elements = []
for element in elements:
if element not in unique_elements:
unique_elements.append(element)
return unique_elements
def calculate_absolute_min_or_zero(x: array) -> array:
"""Calculates the element-wise absolute minimum between the x and zero.
Parameters:
x (array): The first input array.
Returns:
array: The element-wise absolute minimum between x and zero.
Example:
>>> x = array([-1, 2, -3])
>>> calculate_absolute_min_or_zero(x)
array([1., 0., 3.])
"""
return (abs(x) - x) / 2
def nice_column_str(data: List[List[str]], max_length: int = 30, padding: int = 1) -> str:
"""data is a list of rows, creates strings with nice alignment per colum and padding with spaces
letf justified
>>> table_data = [['aaa', 'b', 'c'], ['aaaaaaaa', 'bbb', 'c'], ['a', 'bbbbbbbbbb', 'c']]
>>> print(nice_column_str(table_data))
aaa b c
aaaaaaaa bbb c
a bbbbbbbbbb c
"""
# transpose data to determine size of columns
transposed_data = list(map(list, zip(*data)))
column_width = [max(len(word) for word in column) + padding for column in transposed_data]
nice_str = []
for row in data:
column = ''.join(word[0:max_length].ljust(min(width, max_length)) for width, word in zip(column_width, row))
nice_str.append(f'{column}')
return '\n'.join(nice_str)
def find_common_range(amp_bands: List[List[dict]], default_band_f_min: float, default_band_f_max: float) \
-> List[dict]:
"""Find the common frequency range of bands
If there are no amplifiers in the path, then use default band
>>> amp_bands = [[{'f_min': 191e12, 'f_max' : 195e12}, {'f_min': 186e12, 'f_max' : 190e12} ], \
[{'f_min': 185e12, 'f_max' : 189e12}, {'f_min': 192e12, 'f_max' : 196e12}], \
[{'f_min': 186e12, 'f_max': 193e12}]]
>>> find_common_range(amp_bands, 190e12, 195e12)
[{'f_min': 186000000000000.0, 'f_max': 189000000000000.0}, {'f_min': 192000000000000.0, 'f_max': 193000000000000.0}]
>>> amp_bands = [[{'f_min': 191e12, 'f_max' : 195e12}, {'f_min': 186e12, 'f_max' : 190e12} ], \
[{'f_min': 185e12, 'f_max' : 189e12}, {'f_min': 192e12, 'f_max' : 196e12}], \
[{'f_min': 186e12, 'f_max': 192e12}]]
>>> find_common_range(amp_bands, 190e12, 195e12)
[{'f_min': 186000000000000.0, 'f_max': 189000000000000.0}]
"""
_amp_bands = [sorted(amp, key=lambda x: x['f_min']) for amp in amp_bands]
_temp = []
# remove None bands
for amp in _amp_bands:
is_band = True
for band in amp:
if not (is_band and band['f_min'] and band['f_max']):
is_band = False
if is_band:
_temp.append(amp)
# remove duplicate
unique_amp_bands = []
for amp in _temp:
if amp not in unique_amp_bands:
unique_amp_bands.append(amp)
if unique_amp_bands:
common_range = unique_amp_bands[0]
else:
if default_band_f_min is None or default_band_f_max is None:
return []
common_range = [{'f_min': default_band_f_min, 'f_max': default_band_f_max}]
for bands in unique_amp_bands:
common_range = [{'f_min': max(first['f_min'], second['f_min']), 'f_max': min(first['f_max'], second['f_max'])}
for first in common_range for second in bands
if max(first['f_min'], second['f_min']) < min(first['f_max'], second['f_max'])]
return sorted(common_range, key=lambda x: x['f_min'])
def transform_data(data: str) -> Union[List[int], None]:
"""Transforms a float into an list of one integer or a string separated by "|" into a list of integers.
Args:
data (float or str): The data to transform.
Returns:
list of int: The transformed data as a list of integers.
Examples:
>>> transform_data(5.0)
[5]
>>> transform_data('1 | 2 | 3')
[1, 2, 3]
"""
if isinstance(data, float):
return [int(data)]
if isinstance(data, str):
return [int(x) for x in data.split(' | ')]
return None

File diff suppressed because it is too large Load Diff

View File

@@ -1,106 +0,0 @@
{
"nf_ripple": [
0.0
],
"gain_ripple": [
0.0
],
"dgt": [
1.0,
1.017807767853702,
1.0356155337864215,
1.0534217504465226,
1.0712204022764056,
1.0895983485572227,
1.108555289615659,
1.1280891949729075,
1.1476135933863398,
1.1672278304018044,
1.1869318618366975,
1.2067249615595257,
1.2264996957264114,
1.2428104897182262,
1.2556591482982988,
1.2650555289898042,
1.2744470198196236,
1.2838336236692311,
1.2932153453410835,
1.3040618749785347,
1.316383926863083,
1.3301807335621048,
1.3439818461440451,
1.3598972673004606,
1.3779439775587023,
1.3981208704326855,
1.418273806730323,
1.4340878115214444,
1.445565137158368,
1.45273959485914,
1.4599103316162523,
1.4670307626366115,
1.474100442252211,
1.48111939735681,
1.488134243479226,
1.495145456062699,
1.502153039909686,
1.5097346239790443,
1.5178910621476225,
1.5266220576235803,
1.5353620432989845,
1.545374152761467,
1.5566577309558969,
1.569199764184379,
1.5817353179379183,
1.5986915141218316,
1.6201194134191075,
1.6460167077689267,
1.6719047669939942,
1.6918150918099673,
1.7057507692361864,
1.7137640932265894,
1.7217732861435076,
1.7297783508684146,
1.737780757913635,
1.7459181197626403,
1.7541903672600494,
1.7625959636196327,
1.7709972329654864,
1.7793941781790852,
1.7877868031023945,
1.7961751115773796,
1.8045606557581335,
1.8139629377087627,
1.824381436842932,
1.835814081380705,
1.847275503201129,
1.862235672444246,
1.8806927939516411,
1.9026104247588487,
1.9245345552113182,
1.9482128147680253,
1.9736443063300082,
2.0008103857988204,
2.0279625371819305,
2.055100772005235,
2.082225099873648,
2.1183028432496016,
2.16337565384239,
2.2174389328192197,
2.271520771371253,
2.322373696229342,
2.3699990328716107,
2.414398437185221,
2.4587748041127506,
2.499446286796604,
2.5364027376452056,
2.5696460593920065,
2.602860350286428,
2.630396440815385,
2.6521732021128046,
2.6681935771243177,
2.6841217449620203,
2.6947834587664494,
2.705443819238505,
2.714526681131686
]
}

View File

@@ -1,6 +1,7 @@
{ {
"network_name": "EDFA Example Network - P2P", "network_name": "EDFA Example Network - P2P",
"elements": [{ "elements": [
{
"uid": "Site_A", "uid": "Site_A",
"type": "Transceiver", "type": "Transceiver",
"metadata": { "metadata": {
@@ -61,9 +62,9 @@
} }
} }
} }
], ],
"connections": [{ "connections": [
{
"from_node": "Site_A", "from_node": "Site_A",
"to_node": "Span1" "to_node": "Span1"
}, },
@@ -75,6 +76,5 @@
"from_node": "Edfa1", "from_node": "Edfa1",
"to_node": "Site_B" "to_node": "Site_B"
} }
] ]
} }

View File

@@ -1,4 +1,6 @@
{ "Edfa":[{ {
"Edfa": [
{
"type_variety": "high_detail_model_example", "type_variety": "high_detail_model_example",
"type_def": "advanced_model", "type_def": "advanced_model",
"gain_flatmax": 25, "gain_flatmax": 25,
@@ -7,7 +9,8 @@
"advanced_config_from_json": "std_medium_gain_advanced_config.json", "advanced_config_from_json": "std_medium_gain_advanced_config.json",
"out_voa_auto": false, "out_voa_auto": false,
"allowed_for_design": false "allowed_for_design": false
}, { },
{
"type_variety": "Juniper_BoosterHG", "type_variety": "Juniper_BoosterHG",
"type_def": "advanced_model", "type_def": "advanced_model",
"gain_flatmax": 25, "gain_flatmax": 25,
@@ -34,7 +37,12 @@
"gain_flatmax": 27, "gain_flatmax": 27,
"gain_min": 0, "gain_min": 0,
"p_max": 22, "p_max": 22,
"nf_coef": [-8.104e-4,-6.221e-2,-5.889e-1,37.62], "nf_coef": [
-8.104e-4,
-6.221e-2,
-5.889e-1,
37.62
],
"allowed_for_design": false "allowed_for_design": false
}, },
{ {
@@ -43,7 +51,12 @@
"gain_flatmax": 27, "gain_flatmax": 27,
"gain_min": 0, "gain_min": 0,
"p_max": 22, "p_max": 22,
"nf_coef": [-5.952e-4,-6.250e-2,-1.071,28.99], "nf_coef": [
-5.952e-4,
-6.250e-2,
-1.071,
28.99
],
"allowed_for_design": false "allowed_for_design": false
}, },
{ {
@@ -54,6 +67,34 @@
"p_max": 22, "p_max": 22,
"allowed_for_design": false "allowed_for_design": false
}, },
{
"type_variety": "openroadm_mw_mw_preamp_typical_ver5",
"type_def": "openroadm",
"gain_flatmax": 27,
"gain_min": 0,
"p_max": 22,
"nf_coef": [
-5.952e-4,
-6.250e-2,
-1.071,
28.99
],
"allowed_for_design": false
},
{
"type_variety": "openroadm_mw_mw_preamp_worstcase_ver5",
"type_def": "openroadm",
"gain_flatmax": 27,
"gain_min": 0,
"p_max": 22,
"nf_coef": [
-5.952e-4,
-6.250e-2,
-1.071,
27.99
],
"allowed_for_design": false
},
{ {
"type_variety": "openroadm_mw_mw_booster", "type_variety": "openroadm_mw_mw_booster",
"type_def": "openroadm_booster", "type_def": "openroadm_booster",
@@ -159,59 +200,42 @@
"allowed_for_design": false "allowed_for_design": false
} }
], ],
"Fiber":[{ "Fiber": [
{
"type_variety": "SSMF", "type_variety": "SSMF",
"dispersion": 1.67e-05, "dispersion": 1.67e-05,
"gamma": 0.00127, "effective_area": 83e-12,
"pmd_coef": 1.265e-15 "pmd_coef": 1.265e-15
}, },
{ {
"type_variety": "NZDF", "type_variety": "NZDF",
"dispersion": 0.5e-05, "dispersion": 0.5e-05,
"gamma": 0.00146, "effective_area": 72e-12,
"pmd_coef": 1.265e-15 "pmd_coef": 1.265e-15
}, },
{ {
"type_variety": "LOF", "type_variety": "LOF",
"dispersion": 2.2e-05, "dispersion": 2.2e-05,
"gamma": 0.000843, "effective_area": 125e-12,
"pmd_coef": 1.265e-15 "pmd_coef": 1.265e-15
} }
], ],
"RamanFiber":[{ "RamanFiber": [
{
"type_variety": "SSMF", "type_variety": "SSMF",
"dispersion": 1.67e-05, "dispersion": 1.67e-05,
"gamma": 0.00127, "effective_area": 83e-12,
"pmd_coef": 1.265e-15, "pmd_coef": 1.265e-15
"raman_efficiency": {
"cr":[
0, 9.4E-06, 2.92E-05, 4.88E-05, 6.82E-05, 8.31E-05, 9.4E-05, 0.0001014, 0.0001069, 0.0001119,
0.0001217, 0.0001268, 0.0001365, 0.000149, 0.000165, 0.000181, 0.0001977, 0.0002192, 0.0002469,
0.0002749, 0.0002999, 0.0003206, 0.0003405, 0.0003592, 0.000374, 0.0003826, 0.0003841, 0.0003826,
0.0003802, 0.0003756, 0.0003549, 0.0003795, 0.000344, 0.0002933, 0.0002024, 0.0001158, 8.46E-05,
7.14E-05, 6.86E-05, 8.5E-05, 8.93E-05, 9.01E-05, 8.15E-05, 6.67E-05, 4.37E-05, 3.28E-05, 2.96E-05,
2.65E-05, 2.57E-05, 2.81E-05, 3.08E-05, 3.67E-05, 5.85E-05, 6.63E-05, 6.36E-05, 5.5E-05, 4.06E-05,
2.77E-05, 2.42E-05, 1.87E-05, 1.6E-05, 1.4E-05, 1.13E-05, 1.05E-05, 9.8E-06, 9.8E-06, 1.13E-05,
1.64E-05, 1.95E-05, 2.38E-05, 2.26E-05, 2.03E-05, 1.48E-05, 1.09E-05, 9.8E-06, 1.05E-05, 1.17E-05,
1.25E-05, 1.21E-05, 1.09E-05, 9.8E-06, 8.2E-06, 6.6E-06, 4.7E-06, 2.7E-06, 1.9E-06, 1.2E-06, 4E-07,
2E-07, 1E-07
],
"frequency_offset":[
0, 0.5e12, 1e12, 1.5e12, 2e12, 2.5e12, 3e12, 3.5e12, 4e12, 4.5e12, 5e12, 5.5e12, 6e12, 6.5e12, 7e12,
7.5e12, 8e12, 8.5e12, 9e12, 9.5e12, 10e12, 10.5e12, 11e12, 11.5e12, 12e12, 12.5e12, 12.75e12,
13e12, 13.25e12, 13.5e12, 14e12, 14.5e12, 14.75e12, 15e12, 15.5e12, 16e12, 16.5e12, 17e12,
17.5e12, 18e12, 18.25e12, 18.5e12, 18.75e12, 19e12, 19.5e12, 20e12, 20.5e12, 21e12, 21.5e12,
22e12, 22.5e12, 23e12, 23.5e12, 24e12, 24.5e12, 25e12, 25.5e12, 26e12, 26.5e12, 27e12, 27.5e12, 28e12,
28.5e12, 29e12, 29.5e12, 30e12, 30.5e12, 31e12, 31.5e12, 32e12, 32.5e12, 33e12, 33.5e12, 34e12, 34.5e12,
35e12, 35.5e12, 36e12, 36.5e12, 37e12, 37.5e12, 38e12, 38.5e12, 39e12, 39.5e12, 40e12, 40.5e12, 41e12,
41.5e12, 42e12
]
}
} }
], ],
"Span":[{ "Span": [
{
"power_mode": true, "power_mode": true,
"delta_power_range_db": [-2,3,0.5], "delta_power_range_db": [
-2,
3,
0.5
],
"max_fiber_lineic_loss_for_raman": 0.25, "max_fiber_lineic_loss_for_raman": 0.25,
"target_extended_gain": 2.5, "target_extended_gain": 2.5,
"max_length": 150, "max_length": 150,
@@ -223,26 +247,119 @@
"con_out": 0 "con_out": 0
} }
], ],
"Roadm":[{ "Roadm": [
{
"target_pch_out_db": -20, "target_pch_out_db": -20,
"add_drop_osnr": 38, "add_drop_osnr": 38,
"pmd": 0, "pmd": 0,
"pdl": 0,
"restrictions": { "restrictions": {
"preamp_variety_list": [], "preamp_variety_list": [],
"booster_variety_list": [] "booster_variety_list": []
} }
}], },
"SI":[{ {
"type_variety": "roadm_type_1",
"target_pch_out_db": -18,
"add_drop_osnr": 35,
"pmd": 0,
"pdl": 0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
},
"roadm-path-impairments": []
},
{
"type_variety": "detailed_impairments",
"target_pch_out_db": -20,
"add_drop_osnr": 38,
"pmd": 0,
"pdl": 0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
},
"roadm-path-impairments": [
{
"roadm-path-impairments-id": 0,
"roadm-express-path": [
{
"frequency-range": {
"lower-frequency": 191.3e12,
"upper-frequency": 196.1e12
},
"roadm-pmd": 0,
"roadm-cd": 0,
"roadm-pdl": 0,
"roadm-inband-crosstalk": 0,
"roadm-maxloss": 16.5
}
]
},
{
"roadm-path-impairments-id": 1,
"roadm-add-path": [
{
"frequency-range": {
"lower-frequency": 191.3e12,
"upper-frequency": 196.1e12
},
"roadm-pmd": 0,
"roadm-cd": 0,
"roadm-pdl": 0,
"roadm-inband-crosstalk": 0,
"roadm-maxloss": 11.5,
"roadm-pmax": 2.5,
"roadm-osnr": 41,
"roadm-noise-figure": 23
}
]
},
{
"roadm-path-impairments-id": 2,
"roadm-drop-path": [
{
"frequency-range": {
"lower-frequency": 191.3e12,
"upper-frequency": 196.1e12
},
"roadm-pmd": 0,
"roadm-cd": 0,
"roadm-pdl": 0,
"roadm-inband-crosstalk": 0,
"roadm-maxloss": 11.5,
"roadm-minloss": 7.5,
"roadm-typloss": 10,
"roadm-pmin": -13.5,
"roadm-pmax": -9.5,
"roadm-ptyp": -12,
"roadm-osnr": 41,
"roadm-noise-figure": 15
}
]
}
]
}
],
"SI": [
{
"f_min": 191.3e12, "f_min": 191.3e12,
"baud_rate": 32e9, "baud_rate": 32e9,
"f_max": 195.1e12, "f_max": 195.1e12,
"spacing": 50e9, "spacing": 50e9,
"power_dbm": 0, "power_dbm": 0,
"power_range_db": [0,0,1], "power_range_db": [
0,
0,
1
],
"tx_power_dbm": 0,
"roll_off": 0.15, "roll_off": 0.15,
"tx_osnr": 40, "tx_osnr": 40,
"sys_margins": 2 "sys_margins": 2
}], }
],
"Transceiver": [ "Transceiver": [
{ {
"type_variety": "vendorA_trx-type1", "type_variety": "vendorA_trx-type1",
@@ -252,7 +369,6 @@
}, },
"mode": [ "mode": [
{ {
"format": "mode 1", "format": "mode 1",
"baud_rate": 32e9, "baud_rate": 32e9,
"OSNR": 11, "OSNR": 11,
@@ -324,5 +440,4 @@
] ]
} }
] ]
} }

View File

@@ -0,0 +1,479 @@
{
"Edfa": [
{
"type_variety": "std_high_gain",
"type_def": "variable_gain",
"gain_flatmax": 35,
"gain_min": 25,
"p_max": 21,
"nf_min": 5.5,
"nf_max": 7,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_medium_gain",
"type_def": "variable_gain",
"gain_flatmax": 26,
"gain_min": 15,
"p_max": 23,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain",
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 23,
"nf_min": 6.5,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "high_power",
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 25,
"nf_min": 9,
"nf_max": 15,
"out_voa_auto": false,
"allowed_for_design": false
},
{
"type_variety": "std_fixed_gain",
"type_def": "fixed_gain",
"gain_flatmax": 21,
"gain_min": 20,
"p_max": 21,
"nf0": 5.5,
"allowed_for_design": false
},
{
"type_variety": "4pumps_raman",
"type_def": "fixed_gain",
"gain_flatmax": 12,
"gain_min": 12,
"p_max": 21,
"nf0": -1,
"allowed_for_design": false
},
{
"type_variety": "hybrid_4pumps_lowgain",
"type_def": "dual_stage",
"raman": true,
"gain_min": 25,
"preamp_variety": "4pumps_raman",
"booster_variety": "std_low_gain",
"allowed_for_design": true
},
{
"type_variety": "hybrid_4pumps_mediumgain",
"type_def": "dual_stage",
"raman": true,
"gain_min": 25,
"preamp_variety": "4pumps_raman",
"booster_variety": "std_medium_gain",
"allowed_for_design": true
},
{
"type_variety": "medium+low_gain",
"type_def": "dual_stage",
"gain_min": 25,
"preamp_variety": "std_medium_gain",
"booster_variety": "std_low_gain",
"allowed_for_design": true
},
{
"type_variety": "medium+high_power",
"type_def": "dual_stage",
"gain_min": 25,
"preamp_variety": "std_medium_gain",
"booster_variety": "high_power",
"allowed_for_design": false
},
{
"type_variety": "std_medium_gain_C",
"f_min": 191.225e12,
"f_max": 196.125e12,
"type_def": "variable_gain",
"gain_flatmax": 26,
"gain_min": 15,
"p_max": 21,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": false
},
{
"type_variety": "std_medium_gain_L",
"f_min": 186.5e12,
"f_max": 190.1e12,
"type_def": "variable_gain",
"gain_flatmax": 26,
"gain_min": 15,
"p_max": 21,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain",
"f_min": 191.25e12,
"f_max": 196.15e12,
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 21,
"nf_min": 7,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain_reduced_band",
"f_min": 192.25e12,
"f_max": 196.15e12,
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 21,
"nf_min": 7,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain_bis",
"f_min": 191.25e12,
"f_max": 196.15e12,
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 21,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain_L_ter",
"f_min": 186.55e12,
"f_max": 190.05e12,
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 16,
"nf_min": 7,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain_L",
"f_min": 186.55e12,
"f_max": 190.05e12,
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 21,
"nf_min": 7,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain_L_reduced_band",
"f_min": 187.3e12,
"f_max": 190.05e12,
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 21,
"nf_min": 7,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "test",
"type_def": "variable_gain",
"gain_flatmax": 25,
"gain_min": 15,
"p_max": 21,
"nf_min": 5.8,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "test_fixed_gain",
"type_def": "fixed_gain",
"gain_flatmax": 21,
"gain_min": 20,
"p_max": 21,
"nf0": 5,
"allowed_for_design": true
},
{
"type_variety": "std_booster",
"type_def": "fixed_gain",
"gain_flatmax": 21,
"gain_min": 20,
"p_max": 21,
"nf0": 5,
"allowed_for_design": false
},
{
"type_variety": "std_booster_L",
"f_min": 186.55e12,
"f_max": 190.05e12,
"type_def": "fixed_gain",
"gain_flatmax": 21,
"gain_min": 20,
"p_max": 21,
"nf0": 5,
"allowed_for_design": false
},
{
"type_variety": "std_booster_multiband",
"type_def": "multi_band",
"amplifiers": [
"std_booster",
"std_booster_L"
],
"allowed_for_design": false
},
{
"type_variety": "std_medium_gain_multiband",
"type_def": "multi_band",
"amplifiers": [
"std_medium_gain_C",
"std_medium_gain_L"
],
"allowed_for_design": false
},
{
"type_variety": "std_low_gain_multiband",
"type_def": "multi_band",
"amplifiers": [
"std_low_gain",
"std_low_gain_L"
],
"allowed_for_design": false
},
{
"type_variety": "std_low_gain_multiband_ter",
"type_def": "multi_band",
"amplifiers": [
"std_low_gain",
"std_low_gain_L_ter"
],
"allowed_for_design": false
},
{
"type_variety": "std_low_gain_multiband_bis",
"type_def": "multi_band",
"amplifiers": [
"std_low_gain_bis",
"std_low_gain_L"
],
"allowed_for_design": true
},
{
"type_variety": "std_low_gain_multiband_reduced",
"type_def": "multi_band",
"amplifiers": [
"std_low_gain_reduced",
"std_low_gain_L"
],
"allowed_for_design": true
},
{
"type_variety": "std_low_gain_multiband_reduced",
"type_def": "multi_band",
"amplifiers": [
"std_low_gain_bis",
"std_low_gain_L_reduced_band"
],
"allowed_for_design": true
}
],
"Fiber": [
{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"effective_area": 83e-12,
"pmd_coef": 1.265e-15
},
{
"type_variety": "NZDF",
"dispersion": 0.5e-05,
"effective_area": 72e-12,
"pmd_coef": 1.265e-15
},
{
"type_variety": "LOF",
"dispersion": 2.2e-05,
"effective_area": 125e-12,
"pmd_coef": 1.265e-15
}
],
"RamanFiber": [
{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"effective_area": 83e-12,
"pmd_coef": 1.265e-15
}
],
"Span": [
{
"power_mode": true,
"delta_power_range_db": [
-2,
3,
0.5
],
"max_fiber_lineic_loss_for_raman": 0.25,
"target_extended_gain": 2.5,
"max_length": 150,
"length_units": "km",
"max_loss": 28,
"padding": 10,
"EOL": 0,
"con_in": 0,
"con_out": 0
}
],
"Roadm": [
{
"target_pch_out_db": -20,
"add_drop_osnr": 38,
"pmd": 0,
"pdl": 0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
}
}
],
"SI": [
{
"f_min": 191.3e12,
"baud_rate": 32e9,
"f_max": 195.1e12,
"spacing": 50e9,
"power_dbm": 0,
"power_range_db": [
0,
0,
1
],
"roll_off": 0.15,
"tx_osnr": 40,
"sys_margins": 2
},
{
"type_variety": "lband",
"f_min": 186.3e12,
"baud_rate": 32e9,
"f_max": 190.1e12,
"spacing": 50e9,
"power_dbm": 0,
"power_range_db": [
0,
0,
1
],
"roll_off": 0.15,
"tx_osnr": 40,
"sys_margins": 2
}
],
"Transceiver": [
{
"type_variety": "vendorA_trx-type1",
"frequency": {
"min": 191.35e12,
"max": 196.1e12
},
"mode": [
{
"format": "mode 1",
"baud_rate": 32e9,
"OSNR": 11,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 40,
"min_spacing": 37.5e9,
"cost": 1
},
{
"format": "mode 2",
"baud_rate": 66e9,
"OSNR": 15,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 40,
"min_spacing": 75e9,
"cost": 1
}
]
},
{
"type_variety": "Voyager",
"frequency": {
"min": 191.35e12,
"max": 196.1e12
},
"mode": [
{
"format": "mode 1",
"baud_rate": 32e9,
"OSNR": 12,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 40,
"min_spacing": 37.5e9,
"cost": 1
},
{
"format": "mode 3",
"baud_rate": 44e9,
"OSNR": 18,
"bit_rate": 300e9,
"roll_off": 0.15,
"tx_osnr": 40,
"min_spacing": 62.5e9,
"cost": 1
},
{
"format": "mode 2",
"baud_rate": 66e9,
"OSNR": 21,
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 40,
"min_spacing": 75e9,
"cost": 1
},
{
"format": "mode 4",
"baud_rate": 66e9,
"OSNR": 16,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 40,
"min_spacing": 75e9,
"cost": 1
}
]
}
]
}

View File

@@ -1,190 +0,0 @@
{ "Edfa":[
{
"type_variety": "openroadm_ila_low_noise",
"type_def": "openroadm",
"gain_flatmax": 27,
"gain_min": 0,
"p_max": 22,
"nf_coef": [-8.104e-4,-6.221e-2,-5.889e-1,37.62],
"allowed_for_design": true
},
{
"type_variety": "openroadm_ila_standard",
"type_def": "openroadm",
"gain_flatmax": 27,
"gain_min": 0,
"p_max": 22,
"nf_coef": [-5.952e-4,-6.250e-2,-1.071,28.99],
"allowed_for_design": true
},
{
"type_variety": "openroadm_mw_mw_preamp",
"type_def": "openroadm_preamp",
"gain_flatmax": 27,
"gain_min": 0,
"p_max": 22,
"allowed_for_design": false
},
{
"type_variety": "openroadm_mw_mw_booster",
"type_def": "openroadm_booster",
"gain_flatmax": 32,
"gain_min": 0,
"p_max": 22,
"allowed_for_design": false
}
],
"Fiber":[
{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"gamma": 0.00127,
"pmd_coef": 1.265e-15
},
{
"type_variety": "NZDF",
"dispersion": 0.5e-05,
"gamma": 0.00146,
"pmd_coef": 1.265e-15
},
{
"type_variety": "LOF",
"dispersion": 2.2e-05,
"gamma": 0.000843,
"pmd_coef": 1.265e-15
}
],
"RamanFiber":[
{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"gamma": 0.00127,
"pmd_coef": 1.265e-15,
"raman_efficiency": {
"cr":[
0, 9.4E-06, 2.92E-05, 4.88E-05, 6.82E-05, 8.31E-05, 9.4E-05, 0.0001014, 0.0001069, 0.0001119,
0.0001217, 0.0001268, 0.0001365, 0.000149, 0.000165, 0.000181, 0.0001977, 0.0002192, 0.0002469,
0.0002749, 0.0002999, 0.0003206, 0.0003405, 0.0003592, 0.000374, 0.0003826, 0.0003841, 0.0003826,
0.0003802, 0.0003756, 0.0003549, 0.0003795, 0.000344, 0.0002933, 0.0002024, 0.0001158, 8.46E-05,
7.14E-05, 6.86E-05, 8.5E-05, 8.93E-05, 9.01E-05, 8.15E-05, 6.67E-05, 4.37E-05, 3.28E-05, 2.96E-05,
2.65E-05, 2.57E-05, 2.81E-05, 3.08E-05, 3.67E-05, 5.85E-05, 6.63E-05, 6.36E-05, 5.5E-05, 4.06E-05,
2.77E-05, 2.42E-05, 1.87E-05, 1.6E-05, 1.4E-05, 1.13E-05, 1.05E-05, 9.8E-06, 9.8E-06, 1.13E-05,
1.64E-05, 1.95E-05, 2.38E-05, 2.26E-05, 2.03E-05, 1.48E-05, 1.09E-05, 9.8E-06, 1.05E-05, 1.17E-05,
1.25E-05, 1.21E-05, 1.09E-05, 9.8E-06, 8.2E-06, 6.6E-06, 4.7E-06, 2.7E-06, 1.9E-06, 1.2E-06, 4E-07,
2E-07, 1E-07
],
"frequency_offset":[
0, 0.5e12, 1e12, 1.5e12, 2e12, 2.5e12, 3e12, 3.5e12, 4e12, 4.5e12, 5e12, 5.5e12, 6e12, 6.5e12, 7e12,
7.5e12, 8e12, 8.5e12, 9e12, 9.5e12, 10e12, 10.5e12, 11e12, 11.5e12, 12e12, 12.5e12, 12.75e12,
13e12, 13.25e12, 13.5e12, 14e12, 14.5e12, 14.75e12, 15e12, 15.5e12, 16e12, 16.5e12, 17e12,
17.5e12, 18e12, 18.25e12, 18.5e12, 18.75e12, 19e12, 19.5e12, 20e12, 20.5e12, 21e12, 21.5e12,
22e12, 22.5e12, 23e12, 23.5e12, 24e12, 24.5e12, 25e12, 25.5e12, 26e12, 26.5e12, 27e12, 27.5e12, 28e12,
28.5e12, 29e12, 29.5e12, 30e12, 30.5e12, 31e12, 31.5e12, 32e12, 32.5e12, 33e12, 33.5e12, 34e12, 34.5e12,
35e12, 35.5e12, 36e12, 36.5e12, 37e12, 37.5e12, 38e12, 38.5e12, 39e12, 39.5e12, 40e12, 40.5e12, 41e12,
41.5e12, 42e12
]
}
}
],
"Span":[
{
"power_mode":true,
"delta_power_range_db": [0,0,0],
"max_fiber_lineic_loss_for_raman": 0.25,
"target_extended_gain": 0,
"max_length": 135,
"length_units": "km",
"max_loss": 28,
"padding": 11,
"EOL": 0,
"con_in": 0,
"con_out": 0
}
],
"Roadm":[
{
"target_pch_out_db": -20,
"add_drop_osnr": 30,
"pmd": 0,
"restrictions": {
"preamp_variety_list":["openroadm_mw_mw_preamp"],
"booster_variety_list":["openroadm_mw_mw_booster"]
}
}
],
"SI":[
{
"f_min": 191.3e12,
"baud_rate": 31.57e9,
"f_max":196.1e12,
"spacing": 50e9,
"power_dbm": 2,
"power_range_db": [0,0,1],
"roll_off": 0.15,
"tx_osnr": 35,
"sys_margins": 2
}
],
"Transceiver":[
{
"type_variety": "OpenROADM MSA ver. 4.0",
"frequency":{
"min": 191.35e12,
"max": 196.1e12
},
"mode":[
{
"format": "100 Gbit/s, 27.95 Gbaud, DP-QPSK",
"baud_rate": 27.95e9,
"OSNR": 17,
"bit_rate": 100e9,
"roll_off": null,
"tx_osnr": 33,
"min_spacing": 50e9,
"cost":1
},
{
"format": "100 Gbit/s, 31.57 Gbaud, DP-QPSK",
"baud_rate": 31.57e9,
"OSNR": 12,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 35,
"min_spacing": 50e9,
"cost":1
},
{
"format": "200 Gbit/s, DP-QPSK",
"baud_rate": 63.1e9,
"OSNR": 17,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 36,
"min_spacing": 87.5e9,
"cost":1
},
{
"format": "300 Gbit/s, DP-8QAM",
"baud_rate": 63.1e9,
"OSNR": 21,
"bit_rate": 300e9,
"roll_off": 0.15,
"tx_osnr": 36,
"min_spacing": 87.5e9,
"cost":1
},
{
"format": "400 Gbit/s, DP-16QAM",
"baud_rate": 63.1e9,
"OSNR": 24,
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 36,
"min_spacing": 87.5e9,
"cost":1
}
]
}
]
}

View File

@@ -0,0 +1,371 @@
{
"Edfa": [
{
"type_variety": "openroadm_ila_low_noise",
"type_def": "openroadm",
"gain_flatmax": 27,
"gain_min": 0,
"p_max": 22,
"nf_coef": [
-8.104e-4,
-6.221e-2,
-5.889e-1,
37.62
],
"pmd": 3e-12,
"pdl": 0.7,
"allowed_for_design": true
},
{
"type_variety": "openroadm_ila_standard",
"type_def": "openroadm",
"gain_flatmax": 27,
"gain_min": 0,
"p_max": 22,
"nf_coef": [
-5.952e-4,
-6.250e-2,
-1.071,
28.99
],
"pmd": 3e-12,
"pdl": 0.7,
"allowed_for_design": true
},
{
"type_variety": "openroadm_mw_mw_preamp",
"type_def": "openroadm_preamp",
"gain_flatmax": 27,
"gain_min": 0,
"p_max": 22,
"pmd": 0,
"pdl": 0,
"allowed_for_design": false
},
{
"type_variety": "openroadm_mw_mw_booster",
"type_def": "openroadm_booster",
"gain_flatmax": 32,
"gain_min": 0,
"p_max": 22,
"pmd": 0,
"pdl": 0,
"allowed_for_design": false
}
],
"Fiber": [
{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"effective_area": 83e-12,
"pmd_coef": 1.265e-15
},
{
"type_variety": "NZDF",
"dispersion": 0.5e-05,
"effective_area": 72e-12,
"pmd_coef": 1.265e-15
},
{
"type_variety": "LOF",
"dispersion": 2.2e-05,
"effective_area": 125e-12,
"pmd_coef": 1.265e-15
}
],
"RamanFiber": [
{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"effective_area": 83e-12,
"pmd_coef": 1.265e-15
}
],
"Span": [
{
"power_mode": true,
"delta_power_range_db": [
0,
0,
0
],
"max_fiber_lineic_loss_for_raman": 0.25,
"target_extended_gain": 0,
"max_length": 135,
"length_units": "km",
"max_loss": 28,
"padding": 11,
"EOL": 0,
"con_in": 0,
"con_out": 0
}
],
"Roadm": [
{
"target_pch_out_db": -20,
"add_drop_osnr": 30,
"pmd": 3e-12,
"pdl": 1.5,
"restrictions": {
"preamp_variety_list": [
"openroadm_mw_mw_preamp"
],
"booster_variety_list": [
"openroadm_mw_mw_booster"
]
}
}
],
"SI": [
{
"f_min": 191.3e12,
"baud_rate": 31.57e9,
"f_max": 196.1e12,
"spacing": 50e9,
"power_dbm": 2,
"power_range_db": [
0,
0,
1
],
"roll_off": 0.15,
"tx_osnr": 35,
"sys_margins": 2
}
],
"Transceiver": [
{
"type_variety": "OpenROADM MSA ver. 4.0",
"frequency": {
"min": 191.35e12,
"max": 196.1e12
},
"mode": [
{
"format": "100 Gbit/s, 27.95 Gbaud, DP-QPSK",
"baud_rate": 27.95e9,
"OSNR": 17,
"bit_rate": 100e9,
"roll_off": null,
"tx_osnr": 33,
"penalties": [
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 18e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 30,
"penalty_value": 0.5
},
{
"pdl": 1,
"penalty_value": 0.5
},
{
"pdl": 2,
"penalty_value": 1
},
{
"pdl": 4,
"penalty_value": 2.5
},
{
"pdl": 6,
"penalty_value": 4
}
],
"min_spacing": 50e9,
"cost": 1
},
{
"format": "100 Gbit/s, 31.57 Gbaud, DP-QPSK",
"baud_rate": 31.57e9,
"OSNR": 12,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 35,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 40e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 30,
"penalty_value": 0.5
},
{
"pdl": 1,
"penalty_value": 0.5
},
{
"pdl": 2,
"penalty_value": 1
},
{
"pdl": 4,
"penalty_value": 2.5
},
{
"pdl": 6,
"penalty_value": 4
}
],
"min_spacing": 50e9,
"cost": 1
},
{
"format": "200 Gbit/s, DP-QPSK",
"baud_rate": 63.1e9,
"OSNR": 17,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 24e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 25,
"penalty_value": 0.5
},
{
"pdl": 1,
"penalty_value": 0.5
},
{
"pdl": 2,
"penalty_value": 1
},
{
"pdl": 4,
"penalty_value": 2.5
}
],
"min_spacing": 87.5e9,
"cost": 1
},
{
"format": "300 Gbit/s, DP-8QAM",
"baud_rate": 63.1e9,
"OSNR": 21,
"bit_rate": 300e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 18e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 25,
"penalty_value": 0.5
},
{
"pdl": 1,
"penalty_value": 0.5
},
{
"pdl": 2,
"penalty_value": 1
},
{
"pdl": 4,
"penalty_value": 2.5
}
],
"min_spacing": 87.5e9,
"cost": 1
},
{
"format": "400 Gbit/s, DP-16QAM",
"baud_rate": 63.1e9,
"OSNR": 24,
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 12e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 20,
"penalty_value": 0.5
},
{
"pdl": 1,
"penalty_value": 0.5
},
{
"pdl": 2,
"penalty_value": 1
},
{
"pdl": 4,
"penalty_value": 2.5
}
],
"min_spacing": 87.5e9,
"cost": 1
}
]
}
]
}

View File

@@ -0,0 +1,441 @@
{
"Edfa": [
{
"type_variety": "openroadm_ila_low_noise",
"type_def": "openroadm",
"gain_flatmax": 27,
"gain_min": 0,
"p_max": 22,
"nf_coef": [
-8.104e-4,
-6.221e-2,
-5.889e-1,
37.62
],
"pmd": 3e-12,
"pdl": 0.7,
"allowed_for_design": true
},
{
"type_variety": "openroadm_ila_standard",
"type_def": "openroadm",
"gain_flatmax": 27,
"gain_min": 0,
"p_max": 22,
"nf_coef": [
-5.952e-4,
-6.250e-2,
-1.071,
28.99
],
"pmd": 3e-12,
"pdl": 0.7,
"allowed_for_design": true
},
{
"type_variety": "openroadm_mw_mw_preamp_typical_ver5",
"type_def": "openroadm",
"gain_flatmax": 27,
"gain_min": 0,
"p_max": 22,
"nf_coef": [
-5.952e-4,
-6.250e-2,
-1.071,
28.99
],
"pmd": 0,
"pdl": 0,
"allowed_for_design": false
},
{
"type_variety": "openroadm_mw_mw_preamp_worstcase_ver5",
"type_def": "openroadm",
"gain_flatmax": 27,
"gain_min": 0,
"p_max": 22,
"nf_coef": [
-5.952e-4,
-6.250e-2,
-1.071,
27.99
],
"pmd": 0,
"pdl": 0,
"allowed_for_design": false
},
{
"type_variety": "openroadm_mw_mw_booster",
"type_def": "openroadm_booster",
"gain_flatmax": 32,
"gain_min": 0,
"p_max": 22,
"pmd": 0,
"pdl": 0,
"allowed_for_design": false
}
],
"Fiber": [
{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"effective_area": 83e-12,
"pmd_coef": 1.265e-15
},
{
"type_variety": "NZDF",
"dispersion": 0.5e-05,
"effective_area": 72e-12,
"pmd_coef": 1.265e-15
},
{
"type_variety": "LOF",
"dispersion": 2.2e-05,
"effective_area": 125e-12,
"pmd_coef": 1.265e-15
}
],
"RamanFiber": [
{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"effective_area": 83e-12,
"pmd_coef": 1.265e-15
}
],
"Span": [
{
"power_mode": true,
"delta_power_range_db": [
0,
0,
0
],
"max_fiber_lineic_loss_for_raman": 0.25,
"target_extended_gain": 0,
"max_length": 135,
"length_units": "km",
"max_loss": 28,
"padding": 11,
"EOL": 0,
"con_in": 0,
"con_out": 0
}
],
"Roadm": [
{
"target_pch_out_db": -20,
"add_drop_osnr": 33,
"pmd": 3e-12,
"pdl": 1.5,
"restrictions": {
"preamp_variety_list": [
"openroadm_mw_mw_preamp_worstcase_ver5"
],
"booster_variety_list": [
"openroadm_mw_mw_booster"
]
}
}
],
"SI": [
{
"f_min": 191.3e12,
"baud_rate": 31.57e9,
"f_max": 196.1e12,
"spacing": 50e9,
"power_dbm": 2,
"power_range_db": [
0,
0,
1
],
"roll_off": 0.15,
"tx_osnr": 35,
"sys_margins": 2
}
],
"Transceiver": [
{
"type_variety": "OpenROADM MSA ver. 5.0",
"frequency": {
"min": 191.35e12,
"max": 196.1e12
},
"mode": [
{
"format": "100 Gbit/s, 27.95 Gbaud, DP-QPSK",
"baud_rate": 27.95e9,
"OSNR": 17,
"bit_rate": 100e9,
"roll_off": null,
"tx_osnr": 33,
"penalties": [
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 18e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 30,
"penalty_value": 0.5
},
{
"pdl": 1,
"penalty_value": 0.5
},
{
"pdl": 2,
"penalty_value": 1
},
{
"pdl": 4,
"penalty_value": 2.5
},
{
"pdl": 6,
"penalty_value": 4
}
],
"min_spacing": 50e9,
"cost": 1
},
{
"format": "100 Gbit/s, 31.57 Gbaud, DP-QPSK",
"baud_rate": 31.57e9,
"OSNR": 12,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 48e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 30,
"penalty_value": 0.5
},
{
"pdl": 1,
"penalty_value": 0.5
},
{
"pdl": 2,
"penalty_value": 1
},
{
"pdl": 4,
"penalty_value": 2.5
},
{
"pdl": 6,
"penalty_value": 4
}
],
"min_spacing": 50e9,
"cost": 1
},
{
"format": "200 Gbit/s, 31.57 Gbaud, DP-16QAM",
"baud_rate": 31.57e9,
"OSNR": 20.5,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 24e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 30,
"penalty_value": 0.5
},
{
"pdl": 1,
"penalty_value": 0.5
},
{
"pdl": 2,
"penalty_value": 1
},
{
"pdl": 4,
"penalty_value": 2.5
},
{
"pdl": 6,
"penalty_value": 4
}
],
"min_spacing": 50e9,
"cost": 1
},
{
"format": "200 Gbit/s, DP-QPSK",
"baud_rate": 63.1e9,
"OSNR": 17,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 24e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 25,
"penalty_value": 0.5
},
{
"pdl": 1,
"penalty_value": 0.5
},
{
"pdl": 2,
"penalty_value": 1
},
{
"pdl": 4,
"penalty_value": 2.5
}
],
"min_spacing": 87.5e9,
"cost": 1
},
{
"format": "300 Gbit/s, DP-8QAM",
"baud_rate": 63.1e9,
"OSNR": 21,
"bit_rate": 300e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 18e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 25,
"penalty_value": 0.5
},
{
"pdl": 1,
"penalty_value": 0.5
},
{
"pdl": 2,
"penalty_value": 1
},
{
"pdl": 4,
"penalty_value": 2.5
}
],
"min_spacing": 87.5e9,
"cost": 1
},
{
"format": "400 Gbit/s, DP-16QAM",
"baud_rate": 63.1e9,
"OSNR": 24,
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 36,
"penalties": [
{
"chromatic_dispersion": -1e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 4e3,
"penalty_value": 0
},
{
"chromatic_dispersion": 12e3,
"penalty_value": 0.5
},
{
"pmd": 10,
"penalty_value": 0
},
{
"pmd": 20,
"penalty_value": 0.5
},
{
"pdl": 1,
"penalty_value": 0.5
},
{
"pdl": 2,
"penalty_value": 1
},
{
"pdl": 4,
"penalty_value": 2.5
}
],
"min_spacing": 87.5e9,
"cost": 1
}
]
}
]
}

View File

@@ -0,0 +1,74 @@
{
"Edfa": [
{
"type_variety": "user_defined",
"type_def": "variable_gain",
"f_min": 192.0e12,
"f_max": 195.9e12,
"gain_flatmax": 25,
"gain_min": 15,
"p_max": 21,
"nf_min": 6,
"nf_max": 10,
"default_config_from_json": "user_edfa_config.json",
"out_voa_auto": false,
"allowed_for_design": true
}, {
"type_variety": "user_high_detail_model_example",
"type_def": "advanced_model",
"gain_flatmax": 25,
"gain_min": 15,
"p_max": 21,
"advanced_config_from_json": "std_medium_gain_advanced_config.json",
"out_voa_auto": false,
"allowed_for_design": false
}
],
"Transceiver": [
{
"type_variety": "ZR400G",
"frequency": {
"min": 191.3e12,
"max": 196.1e12
},
"mode": [
{
"format": "SFF-ID:70",
"baud_rate": 60138546798,
"OSNR": 24,
"bit_rate": 400e9,
"roll_off": 0.2,
"tx_osnr": 34,
"min_spacing": 75e9,
"penalties": [
{
"chromatic_dispersion": 20e3,
"penalty_value": 0.5
},
{
"chromatic_dispersion": 0,
"penalty_value": 0
},
{
"pmd": 20,
"penalty_value": 0.5
},
{
"pdl": 1.5,
"penalty_value": 0
},
{
"pdl": 3.5,
"penalty_value": 1.8
},
{
"pdl": 3,
"penalty_value": 1.3
}
],
"cost": 1
}
]
}
]
}

View File

@@ -0,0 +1,12 @@
{
"spectrum": [
{
"f_min": 191.35e12,
"f_max": 195.1e12,
"baud_rate": 32e9,
"slot_width": 50e9,
"roll_off": 0.15,
"tx_osnr": 40
}
]
}

View File

@@ -0,0 +1,23 @@
{
"spectrum": [
{
"f_min": 191.4e12,
"f_max": 193.1e12,
"baud_rate": 32e9,
"slot_width": 50e9,
"delta_pdb": 0,
"roll_off": 0.15,
"tx_osnr": 40,
"label": "mode_1"
},
{
"f_min": 193.1625e12,
"f_max": 195e12,
"baud_rate": 64e9,
"slot_width": 75e9,
"roll_off": 0.15,
"tx_osnr": 40,
"label": "mode_2"
}
]
}

View File

@@ -14,8 +14,8 @@
"trx_mode": null, "trx_mode": null,
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 50000000000.0, "spacing": 50000000000.0,
@@ -39,8 +39,8 @@
"trx_mode": "mode 1", "trx_mode": "mode 1",
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 50000000000.0, "spacing": 50000000000.0,
@@ -104,8 +104,8 @@
"trx_mode": "mode 1", "trx_mode": "mode 1",
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 50000000000.0, "spacing": 50000000000.0,
@@ -129,8 +129,8 @@
"trx_mode": null, "trx_mode": null,
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 75000000000.0, "spacing": 75000000000.0,
@@ -154,8 +154,8 @@
"trx_mode": "mode 2", "trx_mode": "mode 2",
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 75000000000.0, "spacing": 75000000000.0,
@@ -179,8 +179,8 @@
"trx_mode": "mode 1", "trx_mode": "mode 1",
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 50000000000.0, "spacing": 50000000000.0,
@@ -204,8 +204,8 @@
"trx_mode": "mode 1", "trx_mode": "mode 1",
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 50000000000.0, "spacing": 50000000000.0,
@@ -229,8 +229,8 @@
"trx_mode": "mode 1", "trx_mode": "mode 1",
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 75000000000.0, "spacing": 75000000000.0,

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
{
"spectrum": [
{
"f_min": 191.25e12,
"baud_rate": 32e9,
"f_max": 195.1e12,
"slot_width": 50e9,
"delta_pdb": 0,
"roll_off": 0.15,
"tx_osnr": 40,
"label": "cband"
},
{
"f_min": 186.3e12,
"baud_rate": 32e9,
"f_max": 190.1e12,
"slot_width": 50e9,
"delta_pdb": 0,
"roll_off": 0.15,
"tx_osnr": 40,
"label": "lband"
}
]
}

View File

@@ -20,12 +20,12 @@
"temperature": 283, "temperature": 283,
"raman_pumps": [ "raman_pumps": [
{ {
"power": 200e-3, "power": 224.403e-3,
"frequency": 205e12, "frequency": 205e12,
"propagation_direction": "counterprop" "propagation_direction": "counterprop"
}, },
{ {
"power": 206e-3, "power": 231.135e-3,
"frequency": 201e12, "frequency": 201e12,
"propagation_direction": "counterprop" "propagation_direction": "counterprop"
} }
@@ -49,6 +49,21 @@
} }
} }
}, },
{
"uid": "Fused1",
"type": "Fused",
"params": {
"loss": 0
},
"metadata": {
"location": {
"latitude": 1.5,
"longitude": 0,
"city": null,
"region": ""
}
}
},
{ {
"uid": "Edfa1", "uid": "Edfa1",
"type": "Edfa", "type": "Edfa",
@@ -88,6 +103,10 @@
}, },
{ {
"from_node": "Span1", "from_node": "Span1",
"to_node": "Fused1"
},
{
"from_node": "Fused1",
"to_node": "Edfa1" "to_node": "Edfa1"
}, },
{ {

View File

@@ -0,0 +1,22 @@
{
"path-request": [
{
"request-id": "0",
"source": "trx Brest_KLA",
"destination": "trx Lannion_CAS",
"src-tp-id": "trx Brest_KLA",
"dst-tp-id": "trx Lannion_CAS",
"bidirectional": false,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "ZR400G",
"trx_mode": "SFF-ID:70",
"spacing": 100000000000.0,
"tx_power": 0.0015,
"path_bandwidth": 400000000000.0
}
}
}
]
}

View File

@@ -1,14 +1,19 @@
{ {
"raman_parameters": { "raman_params": {
"flag_raman": true, "flag": true,
"space_resolution": 10e3, "result_spatial_resolution": 10e3,
"tolerance": 1e-8 "solver_spatial_resolution": 50
}, },
"nli_parameters": { "nli_params": {
"nli_method_name": "ggn_spectrally_separated", "method": "ggn_spectrally_separated",
"wdm_grid_size": 50e9,
"dispersion_tolerance": 1, "dispersion_tolerance": 1,
"phase_shift_tolerance": 0.1, "phase_shift_tolerance": 0.1,
"computed_channels": [1, 18, 37, 56, 75] "computed_channels": [
1,
18,
37,
56,
75
]
} }
} }

View File

@@ -5,8 +5,8 @@
0.0359549, 0.0359549,
5.82851 5.82851
], ],
"f_min": 191.35e12, "f_min": 191.275e12,
"f_max": 196.1e12, "f_max": 196.125e12,
"nf_ripple": [ "nf_ripple": [
0.4372876328262819, 0.4372876328262819,
0.4372876328262819, 0.4372876328262819,

View File

@@ -18,9 +18,9 @@ from gnpy.tools.json_io import load_equipment
from gnpy.topology.request import jsontocsv from gnpy.topology.request import jsontocsv
parser = ArgumentParser(description='A function that writes json path results in an excel sheet.') parser = ArgumentParser(description='Converting JSON path results into a CSV')
parser.add_argument('filename', nargs='?', type=Path) parser.add_argument('filename', type=Path)
parser.add_argument('output_filename', nargs='?', type=Path) parser.add_argument('output_filename', type=Path)
parser.add_argument('eqpt_filename', nargs='?', type=Path, default=Path(__file__).parent / 'eqpt_config.json') parser.add_argument('eqpt_filename', nargs='?', type=Path, default=Path(__file__).parent / 'eqpt_config.json')
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -1,5 +1,5 @@
''' """
Processing of data via :py:mod:`.json_io`. Processing of data via :py:mod:`.json_io`.
Utilities for Excel conversion in :py:mod:`.convert` and :py:mod:`.service_sheet`. Utilities for Excel conversion in :py:mod:`.convert` and :py:mod:`.service_sheet`.
Example code in :py:mod:`.cli_examples` and :py:mod:`.plots`. Example code in :py:mod:`.cli_examples` and :py:mod:`.plots`.
''' """

View File

@@ -1,39 +1,38 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' """
gnpy.tools.cli_examples gnpy.tools.cli_examples
======================= =======================
Common code for CLI examples Common code for CLI examples
''' """
import argparse import argparse
import logging import logging
import os.path
import sys import sys
from math import ceil
from numpy import linspace, mean
from pathlib import Path from pathlib import Path
import gnpy.core.ansi_escapes as ansi_escapes from typing import List
from math import ceil
from numpy import mean
from gnpy.core import ansi_escapes
from gnpy.core.elements import Transceiver, Fiber, RamanFiber from gnpy.core.elements import Transceiver, Fiber, RamanFiber
from gnpy.core.equipment import trx_mode_params from gnpy.core import exceptions
import gnpy.core.exceptions as exceptions
from gnpy.core.network import build_network
from gnpy.core.parameters import SimParams from gnpy.core.parameters import SimParams
from gnpy.core.science_utils import Simulation from gnpy.core.utils import lin2db, pretty_summary_print, per_label_average, watt2dbm
from gnpy.core.utils import db2lin, lin2db, automatic_nch from gnpy.topology.request import (ResultElement, jsontocsv, BLOCKING_NOPATH)
from gnpy.topology.request import (ResultElement, jsontocsv, compute_path_dsjctn, requests_aggregation, from gnpy.tools.json_io import (load_equipments_and_configs, load_network, load_json, load_requests, save_network,
BLOCKING_NOPATH, correct_json_route_list, requests_from_json, save_json, load_initial_spectrum, DEFAULT_EQPT_CONFIG)
deduplicate_disjunctions, compute_path_with_disjunction,
PathRequest, compute_constrained_path, propagate)
from gnpy.topology.spectrum_assignment import build_oms_list, pth_assign_spectrum
from gnpy.tools.json_io import load_equipment, load_network, load_json, load_requests, save_network, \
requests_from_json, disjunctions_from_json, save_json
from gnpy.tools.plots import plot_baseline, plot_results from gnpy.tools.plots import plot_baseline, plot_results
from gnpy.tools.worker_utils import designed_network, transmission_simulation, planning
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
_examples_dir = Path(__file__).parent.parent / 'example-data' _examples_dir = Path(__file__).parent.parent / 'example-data'
_default_config_files = ['example-data/std_medium_gain_advanced_config.json',
'example-data/Juniper-BoosterHG.json',
'parameters.DEFAULT_EDFA_CONFIG']
_help_footer = ''' _help_footer = '''
This program is part of GNPy, https://github.com/TelecomInfraProject/oopt-gnpy This program is part of GNPy, https://github.com/TelecomInfraProject/oopt-gnpy
@@ -48,23 +47,25 @@ def show_example_data_dir():
print(f'{_examples_dir}/') print(f'{_examples_dir}/')
def load_common_data(equipment_filename, topology_filename, simulation_filename, save_raw_network_filename): def load_common_data(equipment_filename: Path, extra_equipment_filenames: List[Path], extra_config_filenames: List[Path],
'''Load common configuration from JSON files''' topology_filename: Path, simulation_filename: Path, save_raw_network_filename: Path):
"""Load common configuration from JSON files, merging additional equipment if provided."""
try: try:
equipment = load_equipment(equipment_filename) equipment = load_equipments_and_configs(equipment_filename, extra_equipment_filenames, extra_config_filenames)
network = load_network(topology_filename, equipment) network = load_network(topology_filename, equipment)
if save_raw_network_filename is not None: if save_raw_network_filename is not None:
save_network(network, save_raw_network_filename) save_network(network, save_raw_network_filename)
print(f'{ansi_escapes.blue}Raw network (no optimizations) saved to {save_raw_network_filename}{ansi_escapes.reset}') print(f'{ansi_escapes.blue}Raw network (no optimizations) saved to {save_raw_network_filename}{ansi_escapes.reset}')
sim_params = SimParams(**load_json(simulation_filename)) if simulation_filename is not None else None if not simulation_filename:
if not sim_params: sim_params = {}
if next((node for node in network if isinstance(node, RamanFiber)), None) is not None: if next((node for node in network if isinstance(node, RamanFiber)), None) is not None:
print(f'{ansi_escapes.red}Invocation error:{ansi_escapes.reset} ' print(f'{ansi_escapes.red}Invocation error:{ansi_escapes.reset} '
f'RamanFiber requires passing simulation params via --sim-params') f'RamanFiber requires passing simulation params via --sim-params')
sys.exit(1) sys.exit(1)
else: else:
Simulation.set_params(sim_params) sim_params = load_json(simulation_filename)
SimParams.set_params(sim_params)
except exceptions.EquipmentConfigError as e: except exceptions.EquipmentConfigError as e:
print(f'{ansi_escapes.red}Configuration error in the equipment library:{ansi_escapes.reset} {e}') print(f'{ansi_escapes.red}Configuration error in the equipment library:{ansi_escapes.reset} {e}')
sys.exit(1) sys.exit(1)
@@ -85,7 +86,7 @@ def load_common_data(equipment_filename, topology_filename, simulation_filename,
def _setup_logging(args): def _setup_logging(args):
logging.basicConfig(level={2: logging.DEBUG, 1: logging.INFO, 0: logging.CRITICAL}.get(args.verbose, logging.DEBUG)) logging.basicConfig(level={2: logging.DEBUG, 1: logging.INFO, 0: logging.WARNING}.get(args.verbose, logging.DEBUG))
def _add_common_options(parser: argparse.ArgumentParser, network_default: Path): def _add_common_options(parser: argparse.ArgumentParser, network_default: Path):
@@ -95,7 +96,7 @@ def _add_common_options(parser: argparse.ArgumentParser, network_default: Path):
parser.add_argument('-v', '--verbose', action='count', default=0, parser.add_argument('-v', '--verbose', action='count', default=0,
help='Increase verbosity (can be specified several times)') help='Increase verbosity (can be specified several times)')
parser.add_argument('-e', '--equipment', type=Path, metavar=_help_fname_json, parser.add_argument('-e', '--equipment', type=Path, metavar=_help_fname_json,
default=_examples_dir / 'eqpt_config.json', help='Equipment library') default=DEFAULT_EQPT_CONFIG, help='Equipment library')
parser.add_argument('--sim-params', type=Path, metavar=_help_fname_json, parser.add_argument('--sim-params', type=Path, metavar=_help_fname_json,
default=None, help='Path to the JSON containing simulation parameters (required for Raman). ' default=None, help='Path to the JSON containing simulation parameters (required for Raman). '
f'Example: {_examples_dir / "sim_params.json"}') f'Example: {_examples_dir / "sim_params.json"}')
@@ -103,9 +104,25 @@ def _add_common_options(parser: argparse.ArgumentParser, network_default: Path):
help='Save the final network as a JSON file') help='Save the final network as a JSON file')
parser.add_argument('--save-network-before-autodesign', type=Path, metavar=_help_fname_json, parser.add_argument('--save-network-before-autodesign', type=Path, metavar=_help_fname_json,
help='Dump the network into a JSON file prior to autodesign') help='Dump the network into a JSON file prior to autodesign')
parser.add_argument('--no-insert-edfas', action='store_true',
help='Disable insertion of EDFAs after ROADMs and fibers '
'as well as splitting of fibers by auto-design.')
# Option for additional equipment files
parser.add_argument('--extra-equipment', nargs='+', type=Path,
metavar=_help_fname_json, default=None,
help='List of additional equipment files to complement the main equipment file.')
# Option for additional config files
parser.add_argument('--extra-config', nargs='+', type=Path,
metavar=_help_fname_json,
help='List of additional config files as referenced in equipment files with '
'"advanced_config_from_json" or "default_config_from_json".'
f'Existing configs:\n{_default_config_files}')
def transmission_main_example(args=None): def transmission_main_example(args=None):
"""Main script running a single simulation. It returns the detailed power across crossed elements and
average performance accross all channels.
"""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Send a full spectrum load through the network from point A to point B', description='Send a full spectrum load through the network from point A to point B',
epilog=_help_footer, epilog=_help_footer,
@@ -116,13 +133,15 @@ def transmission_main_example(args=None):
parser.add_argument('-pl', '--plot', action='store_true') parser.add_argument('-pl', '--plot', action='store_true')
parser.add_argument('-l', '--list-nodes', action='store_true', help='list all transceiver nodes') parser.add_argument('-l', '--list-nodes', action='store_true', help='list all transceiver nodes')
parser.add_argument('-po', '--power', default=0, help='channel ref power in dBm') parser.add_argument('-po', '--power', default=0, help='channel ref power in dBm')
parser.add_argument('--spectrum', type=Path, help='user defined mixed rate spectrum JSON file')
parser.add_argument('source', nargs='?', help='source node') parser.add_argument('source', nargs='?', help='source node')
parser.add_argument('destination', nargs='?', help='destination node') parser.add_argument('destination', nargs='?', help='destination node')
args = parser.parse_args(args if args is not None else sys.argv[1:]) args = parser.parse_args(args if args is not None else sys.argv[1:])
_setup_logging(args) _setup_logging(args)
(equipment, network) = load_common_data(args.equipment, args.topology, args.sim_params, args.save_network_before_autodesign) (equipment, network) = load_common_data(args.equipment, args.extra_equipment, args.extra_config, args.topology,
args.sim_params, args.save_network_before_autodesign)
if args.plot: if args.plot:
plot_baseline(network) plot_baseline(network)
@@ -140,19 +159,17 @@ def transmission_main_example(args=None):
sys.exit() sys.exit()
# First try to find exact match if source/destination provided # First try to find exact match if source/destination provided
source = None
if args.source: if args.source:
source = transceivers.pop(args.source, None) source = transceivers.pop(args.source, None)
valid_source = True if source else False valid_source = bool(source)
else:
source = None
_logger.info('No source node specified: picking random transceiver')
destination = None
nodes_list = []
loose_list = []
if args.destination: if args.destination:
destination = transceivers.pop(args.destination, None) destination = transceivers.pop(args.destination, None)
valid_destination = True if destination else False valid_destination = bool(destination)
else:
destination = None
_logger.info('No destination node specified: picking random transceiver')
# If no exact match try to find partial match # If no exact match try to find partial match
if args.source and not source: if args.source and not source:
@@ -169,79 +186,77 @@ def transmission_main_example(args=None):
if not source: if not source:
source = list(transceivers.values())[0] source = list(transceivers.values())[0]
del transceivers[source.uid] del transceivers[source.uid]
_logger.info('No source node specified: picking random transceiver')
if not destination: if not destination:
destination = list(transceivers.values())[0] destination = list(transceivers.values())[0]
nodes_list = [destination.uid]
loose_list = ['STRICT']
_logger.info('No destination node specified: picking random transceiver')
_logger.info(f'source = {args.source!r}') _logger.info(f'source = {source.uid!r}')
_logger.info(f'destination = {args.destination!r}') _logger.info(f'destination = {destination.uid!r}')
params = {}
params['request_id'] = 0
params['trx_type'] = ''
params['trx_mode'] = ''
params['source'] = source.uid
params['destination'] = destination.uid
params['bidir'] = False
params['nodes_list'] = [destination.uid]
params['loose_list'] = ['strict']
params['format'] = ''
params['path_bandwidth'] = 0
trx_params = trx_mode_params(equipment)
if args.power:
trx_params['power'] = db2lin(float(args.power)) * 1e-3
params.update(trx_params)
req = PathRequest(**params)
initial_spectrum = None
if args.spectrum:
# use the spectrum defined by user for the propagation.
# the nb of channel for design remains the one of the reference channel
initial_spectrum = load_initial_spectrum(args.spectrum)
print('User input for spectrum used for propagation instead of SI')
power_mode = equipment['Span']['default'].power_mode power_mode = equipment['Span']['default'].power_mode
print('\n'.join([f'Power mode is set to {power_mode}', print('\n'.join([f'Power mode is set to {power_mode}',
f'=> it can be modified in eqpt_config.json - Span'])) '=> it can be modified in eqpt_config.json - Span']))
pref_ch_db = lin2db(req.power * 1e3) # reference channel power / span (SL=20dB) # Simulate !
pref_total_db = pref_ch_db + lin2db(req.nb_channel) # reference total power / span (SL=20dB)
try: try:
build_network(network, equipment, pref_ch_db, pref_total_db) network, req, ref_req = designed_network(equipment, network, source.uid, destination.uid,
nodes_list=nodes_list, loose_list=loose_list,
args_power=args.power,
initial_spectrum=initial_spectrum,
no_insert_edfas=args.no_insert_edfas)
path, propagations_for_path, powers_dbm, infos = transmission_simulation(equipment, network, req, ref_req)
except exceptions.NetworkTopologyError as e: except exceptions.NetworkTopologyError as e:
print(f'{ansi_escapes.red}Invalid network definition:{ansi_escapes.reset} {e}') print(f'{ansi_escapes.red}Invalid network definition:{ansi_escapes.reset} {e}')
sys.exit(1) sys.exit(1)
except exceptions.ConfigurationError as e: except exceptions.ConfigurationError as e:
print(f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}') print(f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}')
sys.exit(1) sys.exit(1)
path = compute_constrained_path(network, req) except exceptions.ServiceError as e:
print(f'Service error: {e}')
spans = [s.params.length for s in path if isinstance(s, RamanFiber) or isinstance(s, Fiber)] sys.exit(1)
except ValueError:
sys.exit(1)
# print or export results
spans = [s.params.length for s in path if isinstance(s, (Fiber, RamanFiber))]
print(f'\nThere are {len(spans)} fiber spans over {sum(spans) / 1000:.0f} km between {source.uid} ' print(f'\nThere are {len(spans)} fiber spans over {sum(spans) / 1000:.0f} km between {source.uid} '
f'and {destination.uid}') f'and {destination.uid}')
print(f'\nNow propagating between {source.uid} and {destination.uid}:') print(f'\nNow propagating between {source.uid} and {destination.uid}:')
print(f'Reference used for design: (Input optical power reference in span = {watt2dbm(ref_req.power):.2f}dBm,\n'
try: + f' spacing = {ref_req.spacing * 1e-9:.2f}GHz\n'
p_start, p_stop, p_step = equipment['SI']['default'].power_range_db + f' nb_channels = {ref_req.nb_channel})')
p_num = abs(int(round((p_stop - p_start) / p_step))) + 1 if p_step != 0 else 1 print('\nChannels propagating: (Input optical power deviation in span = '
power_range = list(linspace(p_start, p_stop, p_num)) + f'{pretty_summary_print(per_label_average(infos.delta_pdb_per_channel, infos.label))}dB,\n'
except TypeError: + ' spacing = '
print('invalid power range definition in eqpt_config, should be power_range_db: [lower, upper, step]') + f'{pretty_summary_print(per_label_average(infos.slot_width * 1e-9, infos.label))}GHz,\n'
power_range = [0] + ' transceiver output power = '
+ f'{pretty_summary_print(per_label_average(watt2dbm(infos.tx_power), infos.label))}dBm,\n'
if not power_mode: + f' nb_channels = {infos.number_of_channels})')
# power cannot be changed in gain mode for mypath, power_dbm in zip(propagations_for_path, powers_dbm):
power_range = [0]
for dp_db in power_range:
req.power = db2lin(pref_ch_db + dp_db) * 1e-3
if power_mode: if power_mode:
print(f'\nPropagating with input power = {ansi_escapes.cyan}{lin2db(req.power*1e3):.2f} dBm{ansi_escapes.reset}:') print(f'Input optical power reference in span = {ansi_escapes.cyan}{power_dbm:.2f} '
+ f'dBm{ansi_escapes.reset}:')
else: else:
print(f'\nPropagating in {ansi_escapes.cyan}gain mode{ansi_escapes.reset}: power cannot be set manually') print('\nPropagating in {ansi_escapes.cyan}gain mode{ansi_escapes.reset}: power cannot be set manually')
infos = propagate(path, req, equipment) if len(powers_dbm) == 1:
if len(power_range) == 1: for elem in mypath:
for elem in path:
print(elem) print(elem)
if power_mode: if power_mode:
print(f'\nTransmission result for input power = {lin2db(req.power*1e3):.2f} dBm:') print(f'\nTransmission result for input optical power reference in span = {power_dbm:.2f} dBm:')
else: else:
print(f'\nTransmission results:') print('\nTransmission results:')
print(f' Final GSNR (0.1 nm): {ansi_escapes.cyan}{mean(destination.snr_01nm):.02f} dB{ansi_escapes.reset}') print(f' Final GSNR (0.1 nm): {ansi_escapes.cyan}{mean(destination.snr_01nm):.02f} dB{ansi_escapes.reset}')
else: else:
print(path[-1]) print(mypath[-1])
if args.save_network is not None: if args.save_network is not None:
save_network(network, args.save_network) save_network(network, args.save_network)
@@ -262,9 +277,9 @@ def transmission_main_example(args=None):
ch_freq = final_carrier.frequency * 1e-12 ch_freq = final_carrier.frequency * 1e-12
ch_power = lin2db(final_carrier.power.signal * 1e3) ch_power = lin2db(final_carrier.power.signal * 1e3)
print( print(
'{:5}{:26.2f}{:26.2f}{:28.2f}{:28.2f}{:28.2f}' .format( '{:5}{:26.5f}{:26.2f}{:28.2f}{:28.2f}{:28.2f}' .format(
final_carrier.channel_number, round( final_carrier.channel_number, round(
ch_freq, 2), round( ch_freq, 5), round(
ch_power, 2), round( ch_power, 2), round(
ch_osnr, 2), round( ch_osnr, 2), round(
ch_snr_nl, 2), round( ch_snr_nl, 2), round(
@@ -289,6 +304,9 @@ def _path_result_json(pathresult):
def path_requests_run(args=None): def path_requests_run(args=None):
"""Main script running several services simulations. It returns a summary of the average performance
for each service.
"""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Compute performance for a list of services provided in a json file or an excel sheet', description='Compute performance for a list of services provided in a json file or an excel sheet',
epilog=_help_footer, epilog=_help_footer,
@@ -302,85 +320,49 @@ def path_requests_run(args=None):
help='considers that all demands are bidir') help='considers that all demands are bidir')
parser.add_argument('-o', '--output', type=Path, metavar=_help_fname_json_csv, parser.add_argument('-o', '--output', type=Path, metavar=_help_fname_json_csv,
help='Store satisifed requests into a JSON or CSV file') help='Store satisifed requests into a JSON or CSV file')
parser.add_argument('--redesign-per-request', action='store_true', help='Redesign the network at each request'
+ ' computation using the request as the reference channel')
args = parser.parse_args(args if args is not None else sys.argv[1:]) args = parser.parse_args(args if args is not None else sys.argv[1:])
_setup_logging(args) _setup_logging(args)
_logger.info(f'Computing path requests {args.service_filename} into JSON format') _logger.info(f'Computing path requests {args.service_filename.name} into JSON format')
print(f'{ansi_escapes.blue}Computing path requests {os.path.relpath(args.service_filename)} into JSON format{ansi_escapes.reset}')
(equipment, network) = load_common_data(args.equipment, args.topology, args.sim_params, args.save_network_before_autodesign) (equipment, network) = \
load_common_data(args.equipment, args.extra_equipment, args.extra_config, args.topology, args.sim_params,
args.save_network_before_autodesign)
# Build the network once using the default power defined in SI in eqpt config # Build the network once using the default power defined in SI in eqpt config
# TODO power density: db2linp(ower_dbm": 0)/power_dbm": 0 * nb channels as defined by
# spacing, f_min and f_max
p_db = equipment['SI']['default'].power_dbm
p_total_db = p_db + lin2db(automatic_nch(equipment['SI']['default'].f_min,
equipment['SI']['default'].f_max, equipment['SI']['default'].spacing))
try: try:
build_network(network, equipment, p_db, p_total_db) network, _, _ = designed_network(equipment, network, no_insert_edfas=args.no_insert_edfas)
data = load_requests(args.service_filename, equipment, bidir=args.bidir,
network=network, network_filename=args.topology)
_data = requests_from_json(data, equipment)
_, propagatedpths, reversed_propagatedpths, rqs, dsjn, result = \
planning(network, equipment, data, redesign=args.redesign_per_request)
except exceptions.NetworkTopologyError as e: except exceptions.NetworkTopologyError as e:
print(f'{ansi_escapes.red}Invalid network definition:{ansi_escapes.reset} {e}') print(f'{ansi_escapes.red}Invalid network definition:{ansi_escapes.reset} {e}')
sys.exit(1) sys.exit(1)
except exceptions.ConfigurationError as e: except exceptions.ConfigurationError as e:
print(f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}') print(f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}')
sys.exit(1) sys.exit(1)
if args.save_network is not None:
save_network(network, args.save_network)
print(f'{ansi_escapes.blue}Network (after autodesign) saved to {args.save_network}{ansi_escapes.reset}')
oms_list = build_oms_list(network, equipment)
try:
data = load_requests(args.service_filename, equipment, bidir=args.bidir,
network=network, network_filename=args.topology)
rqs = requests_from_json(data, equipment)
except exceptions.ServiceError as e:
print(f'{ansi_escapes.red}Service error:{ansi_escapes.reset} {e}')
sys.exit(1)
# check that request ids are unique. Non unique ids, may
# mess the computation: better to stop the computation
all_ids = [r.request_id for r in rqs]
if len(all_ids) != len(set(all_ids)):
for item in list(set(all_ids)):
all_ids.remove(item)
msg = f'Requests id {all_ids} are not unique'
_logger.critical(msg)
sys.exit()
rqs = correct_json_route_list(network, rqs)
# pths = compute_path(network, equipment, rqs)
dsjn = disjunctions_from_json(data)
print(f'{ansi_escapes.blue}List of disjunctions{ansi_escapes.reset}')
print(dsjn)
# need to warn or correct in case of wrong disjunction form
# disjunction must not be repeated with same or different ids
dsjn = deduplicate_disjunctions(dsjn)
# Aggregate demands with same exact constraints
print(f'{ansi_escapes.blue}Aggregating similar requests{ansi_escapes.reset}')
rqs, dsjn = requests_aggregation(rqs, dsjn)
# TODO export novel set of aggregated demands in a json file
print(f'{ansi_escapes.blue}The following services have been requested:{ansi_escapes.reset}')
print(rqs)
print(f'{ansi_escapes.blue}Computing all paths with constraints{ansi_escapes.reset}')
try:
pths = compute_path_dsjctn(network, equipment, rqs, dsjn)
except exceptions.DisjunctionError as this_e: except exceptions.DisjunctionError as this_e:
print(f'{ansi_escapes.red}Disjunction error:{ansi_escapes.reset} {this_e}') print(f'{ansi_escapes.red}Disjunction error:{ansi_escapes.reset} {this_e}')
sys.exit(1) sys.exit(1)
except exceptions.ServiceError as e:
print(f'Service error: {e}')
sys.exit(1)
except ValueError:
sys.exit(1)
print(f'{ansi_escapes.blue}List of disjunctions{ansi_escapes.reset}')
print(dsjn)
print(f'{ansi_escapes.blue}The following services have been requested:{ansi_escapes.reset}')
print(_data)
print(f'{ansi_escapes.blue}Propagating on selected path{ansi_escapes.reset}') if args.save_network is not None:
propagatedpths, reversed_pths, reversed_propagatedpths = compute_path_with_disjunction(network, equipment, rqs, pths) save_network(network, args.save_network)
# Note that deepcopy used in compute_path_with_disjunction returns print(f'Network (after autodesign) saved to {args.save_network}')
# a list of nodes which are not belonging to network (they are copies of the node objects).
# so there can not be propagation on these nodes.
pth_assign_spectrum(pths, rqs, oms_list, reversed_pths)
print(f'{ansi_escapes.blue}Result summary{ansi_escapes.reset}') print(f'{ansi_escapes.blue}Result summary{ansi_escapes.reset}')
header = ['req id', ' demand', ' GSNR@bandwidth A-Z (Z-A)', ' GSNR@0.1nm A-Z (Z-A)', header = ['req id', ' demand', ' GSNR@bandwidth A-Z (Z-A)', ' GSNR@0.1nm A-Z (Z-A)',
@@ -401,12 +383,12 @@ def path_requests_run(args=None):
try: try:
if rqs[i].blocking_reason in BLOCKING_NOPATH: if rqs[i].blocking_reason in BLOCKING_NOPATH:
line = [f'{rqs[i].request_id}', f' {rqs[i].source} to {rqs[i].destination} :', line = [f'{rqs[i].request_id}', f' {rqs[i].source} to {rqs[i].destination} :',
f'-', f'-', f'-', f'{rqs[i].tsp_mode}', f'{round(rqs[i].path_bandwidth * 1e-9,2)}', '-', '-', '-', f'{rqs[i].tsp_mode}', f'{round(rqs[i].path_bandwidth * 1e-9, 2)}',
f'-', f'{rqs[i].blocking_reason}'] '-', '{rqs[i].blocking_reason}']
else: else:
line = [f'{rqs[i].request_id}', f' {rqs[i].source} to {rqs[i].destination} : ', psnrb, line = [f'{rqs[i].request_id}', f' {rqs[i].source} to {rqs[i].destination} : ', psnrb,
psnr, f'-', f'{rqs[i].tsp_mode}', f'{round(rqs[i].path_bandwidth * 1e-9, 2)}', psnr, '-', f'{rqs[i].tsp_mode}', f'{round(rqs[i].path_bandwidth * 1e-9, 2)}',
f'-', f'{rqs[i].blocking_reason}'] '-', f'{rqs[i].blocking_reason}']
except AttributeError: except AttributeError:
line = [f'{rqs[i].request_id}', f' {rqs[i].source} to {rqs[i].destination} : ', psnrb, line = [f'{rqs[i].request_id}', f' {rqs[i].source} to {rqs[i].destination} : ', psnrb,
psnr, f'{rqs[i].OSNR + equipment["SI"]["default"].sys_margins}', psnr, f'{rqs[i].OSNR + equipment["SI"]["default"].sys_margins}',

View File

@@ -20,26 +20,37 @@ In the "Links" sheet, only the first three columns ("Node A", "Node Z" and
the "east" information so that it is possible to input undirected data. the "east" information so that it is possible to input undirected data.
""" """
from xlrd import open_workbook from logging import getLogger
from argparse import ArgumentParser from argparse import ArgumentParser
from collections import namedtuple, Counter, defaultdict from collections import namedtuple, Counter, defaultdict
from itertools import chain from itertools import chain
from json import dumps from json import dumps
from pathlib import Path from pathlib import Path
from copy import copy from copy import copy
from gnpy.core import ansi_escapes from typing import Dict, List, Tuple, DefaultDict
from gnpy.core.utils import silent_remove from xlrd import open_workbook
from xlrd.biffh import XLRDError
from networkx import DiGraph
from gnpy.core.utils import silent_remove, transform_data
from gnpy.core.exceptions import NetworkTopologyError from gnpy.core.exceptions import NetworkTopologyError
from gnpy.core.elements import Edfa, Fused, Fiber from gnpy.core.elements import Edfa, Fused, Fiber
_logger = getLogger(__name__)
def all_rows(sh, start=0): def all_rows(sh, start=0):
"""Returns all rows of the xls(x) sheet starting from start row
"""
return (sh.row(x) for x in range(start, sh.nrows)) return (sh.row(x) for x in range(start, sh.nrows))
class Node(object): class Node:
"""Node data class
"""
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(Node, self).__init__() super().__init__()
self.update_attr(kwargs) self.update_attr(kwargs)
def update_attr(self, kwargs): def update_attr(self, kwargs):
@@ -61,13 +72,13 @@ class Node(object):
} }
class Link(object): class Link:
"""attribtes from west parse_ept_headers dict """attribtes from west parse_ept_headers dict
+node_a, node_z, west_fiber_con_in, east_fiber_con_in +node_a, node_z, west_fiber_con_in, east_fiber_con_in
""" """
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(Link, self).__init__() super().__init__()
self.update_attr(kwargs) self.update_attr(kwargs)
self.distance_units = 'km' self.distance_units = 'km'
@@ -76,7 +87,7 @@ class Link(object):
for k, v in self.default_values.items(): for k, v in self.default_values.items():
v = clean_kwargs.get(k, v) v = clean_kwargs.get(k, v)
setattr(self, k, v) setattr(self, k, v)
k = 'west' + k.split('east')[-1] k = 'west' + k.rsplit('east', maxsplit=1)[-1]
v = clean_kwargs.get(k, v) v = clean_kwargs.get(k, v)
setattr(self, k, v) setattr(self, k, v)
@@ -97,9 +108,11 @@ class Link(object):
} }
class Eqpt(object): class Eqpt:
"""
"""
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(Eqpt, self).__init__() super().__init__()
self.update_attr(kwargs) self.update_attr(kwargs)
def update_attr(self, kwargs): def update_attr(self, kwargs):
@@ -107,7 +120,7 @@ class Eqpt(object):
for k, v in self.default_values.items(): for k, v in self.default_values.items():
v_east = clean_kwargs.get(k, v) v_east = clean_kwargs.get(k, v)
setattr(self, k, v_east) setattr(self, k, v_east)
k = 'west' + k.split('east')[-1] k = 'west' + k.rsplit('east', maxsplit=1)[-1]
v_west = clean_kwargs.get(k, v) v_west = clean_kwargs.get(k, v)
setattr(self, k, v_west) setattr(self, k, v_west)
@@ -115,17 +128,18 @@ class Eqpt(object):
'from_city': '', 'from_city': '',
'to_city': '', 'to_city': '',
'east_amp_type': '', 'east_amp_type': '',
'east_att_in': 0,
'east_amp_gain': None, 'east_amp_gain': None,
'east_amp_dp': None, 'east_amp_dp': None,
'east_tilt': 0, 'east_tilt_vs_wavelength': None,
'east_att_out': None 'east_att_out': None
} }
class Roadm(object): class Roadm:
"""
"""
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(Roadm, self).__init__() super().__init__()
self.update_attr(kwargs) self.update_attr(kwargs)
def update_attr(self, kwargs): def update_attr(self, kwargs):
@@ -136,7 +150,10 @@ class Roadm(object):
default_values = {'from_node': '', default_values = {'from_node': '',
'to_node': '', 'to_node': '',
'target_pch_out_db': None 'target_pch_out_db': None,
'type_variety': None,
'from_degrees': None,
'impairment_ids': None
} }
@@ -149,7 +166,7 @@ def read_header(my_sheet, line, slice_):
try: try:
header = [x.value.strip() for x in my_sheet.row_slice(line, slice_[0], slice_[1])] header = [x.value.strip() for x in my_sheet.row_slice(line, slice_[0], slice_[1])]
header_i = [Param_header(header, i + slice_[0]) for i, header in enumerate(header) if header != ''] header_i = [Param_header(header, i + slice_[0]) for i, header in enumerate(header) if header != '']
except Exception: except (AttributeError, IndexError):
header_i = [] header_i = []
if header_i != [] and header_i[-1].colindex != slice_[1]: if header_i != [] and header_i[-1].colindex != slice_[1]:
header_i.append(Param_header('', slice_[1])) header_i.append(Param_header('', slice_[1]))
@@ -165,7 +182,7 @@ def read_slice(my_sheet, line, slice_, header):
try: try:
slice_range = next((h.colindex, header_i[i + 1].colindex) slice_range = next((h.colindex, header_i[i + 1].colindex)
for i, h in enumerate(header_i) if header in h.header) for i, h in enumerate(header_i) if header in h.header)
except Exception: except StopIteration:
pass pass
return slice_range return slice_range
@@ -183,76 +200,123 @@ def parse_headers(my_sheet, input_headers_dict, headers, start_line, slice_in):
slice_out = read_slice(my_sheet, start_line + iteration, slice_in, h0) slice_out = read_slice(my_sheet, start_line + iteration, slice_in, h0)
iteration += 1 iteration += 1
if slice_out == (-1, -1): if slice_out == (-1, -1):
msg = f'missing header {h0}'
if h0 in ('east', 'Node A', 'Node Z', 'City'): if h0 in ('east', 'Node A', 'Node Z', 'City'):
print(f'{ansi_escapes.red}CRITICAL{ansi_escapes.reset}: missing _{h0}_ header: EXECUTION ENDS') raise NetworkTopologyError(msg)
raise NetworkTopologyError(f'Missing _{h0}_ header') _logger.warning(msg)
else:
print(f'missing header {h0}')
elif not isinstance(input_headers_dict[h0], dict): elif not isinstance(input_headers_dict[h0], dict):
headers[slice_out[0]] = input_headers_dict[h0] headers[slice_out[0]] = input_headers_dict[h0]
else: else:
headers = parse_headers(my_sheet, input_headers_dict[h0], headers, start_line + 1, slice_out) headers = parse_headers(my_sheet, input_headers_dict[h0], headers, start_line + 1, slice_out)
if headers == {}: if headers == {}:
print(f'{ansi_escapes.red}CRITICAL ERROR{ansi_escapes.reset}: could not find any header to read _ ABORT') msg = 'CRITICAL ERROR: could not find any header to read _ ABORT'
raise NetworkTopologyError('Could not find any header to read') raise NetworkTopologyError(msg)
return headers return headers
def parse_row(row, headers): def parse_row(row, headers):
"""
"""
return {f: r.value for f, r in return {f: r.value for f, r in
zip([label for label in headers.values()], [row[i] for i in headers])} zip(list(headers.values()), [row[i] for i in headers])}
def parse_sheet(my_sheet, input_headers_dict, header_line, start_line, column): def parse_sheet(my_sheet, input_headers_dict, header_line, start_line, column):
"""
"""
headers = parse_headers(my_sheet, input_headers_dict, {}, header_line, (0, column)) headers = parse_headers(my_sheet, input_headers_dict, {}, header_line, (0, column))
for row in all_rows(my_sheet, start=start_line): for row in all_rows(my_sheet, start=start_line):
yield parse_row(row[0: column], headers) yield parse_row(row[0: column], headers)
def _format_items(items): def _format_items(items: List[str]):
"""formating utils
"""
return '\n'.join(f' - {item}' for item in items) return '\n'.join(f' - {item}' for item in items)
def sanity_check(nodes, links, nodes_by_city, links_by_city, eqpts_by_city): def sanity_check(nodes: List[Node], links: List[Link],
nodes_by_city: Dict[str, Node], links_by_city: DefaultDict[str, List[Link]],
eqpts_by_city: DefaultDict[str, List[Eqpt]]) -> Tuple[List[Node], List[Link]]:
"""Raise correct issues if xls(x) is not correct, Correct type to ROADM if more tha 2-degrees
checks duplicate links, unreferenced nodes in links, in eqpts, unreferenced link in eqpts,
duplicate items
"""
duplicate_links = [] duplicate_links = []
for l1 in links: for l1 in links:
for l2 in links: for l2 in links:
if l1 is not l2 and l1 == l2 and l2 not in duplicate_links: if l1 is not l2 and l1 == l2 and l2 not in duplicate_links:
print(f'\nWARNING\n \ _logger.warning(f'\nWARNING\n \
link {l1.from_city}-{l1.to_city} is duplicate \ link {l1.from_city}-{l1.to_city} is duplicate \
\nthe 1st duplicate link will be removed but you should check Links sheet input') \nthe 1st duplicate link will be removed but you should check Links sheet input')
duplicate_links.append(l1) duplicate_links.append(l1)
for l in duplicate_links:
links.remove(l)
links_by_city[l.from_city].remove(l)
links_by_city[l.to_city].remove(l)
if duplicate_links:
msg = 'XLS error: ' \
+ f'links {_format_items([(d.from_city, d.to_city) for d in duplicate_links])} are duplicate'
raise NetworkTopologyError(msg)
unreferenced_nodes = [n for n in nodes_by_city if n not in links_by_city] unreferenced_nodes = [n for n in nodes_by_city if n not in links_by_city]
if unreferenced_nodes: if unreferenced_nodes:
raise NetworkTopologyError(f'{ansi_escapes.red}XLS error:{ansi_escapes.reset} The following nodes are not ' msg = 'XLS error: The following nodes are not ' \
f'referenced from the {ansi_escapes.cyan}Links{ansi_escapes.reset} sheet. ' + 'referenced from the Links sheet. ' \
f'If unused, remove them from the {ansi_escapes.cyan}Nodes{ansi_escapes.reset} ' + 'If unused, remove them from the Nodes sheet:\n' \
f'sheet:\n' + _format_items(unreferenced_nodes)
+ _format_items(unreferenced_nodes)) raise NetworkTopologyError(msg)
# no need to check "Links" for invalid nodes because that's already in parse_excel() # no need to check "Links" for invalid nodes because that's already in parse_excel()
wrong_eqpt_from = [n for n in eqpts_by_city if n not in nodes_by_city] wrong_eqpt_from = [n for n in eqpts_by_city if n not in nodes_by_city]
wrong_eqpt_to = [n.to_city for destinations in eqpts_by_city.values() wrong_eqpt_to = [n.to_city for destinations in eqpts_by_city.values()
for n in destinations if n.to_city not in nodes_by_city] for n in destinations if n.to_city not in nodes_by_city]
wrong_eqpt = wrong_eqpt_from + wrong_eqpt_to wrong_eqpt = wrong_eqpt_from + wrong_eqpt_to
if wrong_eqpt: if wrong_eqpt:
raise NetworkTopologyError(f'{ansi_escapes.red}XLS error:{ansi_escapes.reset} ' msg = 'XLS error: ' \
f'The {ansi_escapes.cyan}Eqpt{ansi_escapes.reset} sheet refers to nodes that ' + 'The Eqpt sheet refers to nodes that ' \
f'are not defined in the {ansi_escapes.cyan}Nodes{ansi_escapes.reset} sheet:\n' + 'are not defined in the Nodes sheet:\n'\
+ _format_items(wrong_eqpt)) + _format_items(wrong_eqpt)
raise NetworkTopologyError(msg)
# Now check links that are not listed in Links sheet, and duplicates
bad_eqpt = []
possible_links = [f'{e.from_city}|{e.to_city}' for e in links] + [f'{e.to_city}|{e.from_city}' for e in links]
possible_eqpt = []
duplicate_eqpt = []
duplicate_ila = []
for city, eqpts in eqpts_by_city.items():
for eqpt in eqpts:
# Check that each node_A-node_Z exists in links
nodea_nodez = f'{eqpt.from_city}|{eqpt.to_city}'
nodez_nodea = f'{eqpt.to_city}|{eqpt.from_city}'
if nodea_nodez not in possible_links \
or nodez_nodea not in possible_links:
bad_eqpt.append([eqpt.from_city, eqpt.to_city])
else:
# Check that there are no duplicate lines in the Eqpt sheet
if nodea_nodez in possible_eqpt:
duplicate_eqpt.append([eqpt.from_city, eqpt.to_city])
else:
possible_eqpt.append(nodea_nodez)
# check that there are no two lines defining an ILA with different directions
if nodes_by_city[city].node_type == 'ILA' and len(eqpts) > 1:
duplicate_ila.append(city)
if bad_eqpt:
msg = 'XLS error: ' \
+ 'The Eqpt sheet references links that ' \
+ 'are not defined in the Links sheet:\n' \
+ _format_items(f'{item[0]} -> {item[1]}' for item in bad_eqpt)
raise NetworkTopologyError(msg)
if duplicate_eqpt:
msg = 'XLS error: Duplicate lines in Eqpt sheet:' \
+ _format_items(f'{item[0]} -> {item[1]}' for item in duplicate_eqpt)
raise NetworkTopologyError(msg)
if duplicate_ila:
msg = 'XLS error: Duplicate ILA eqpt definition in Eqpt sheet:' \
+ _format_items(duplicate_ila)
raise NetworkTopologyError(msg)
for city, link in links_by_city.items(): for city, link in links_by_city.items():
if nodes_by_city[city].node_type.lower() == 'ila' and len(link) != 2: if nodes_by_city[city].node_type.lower() == 'ila' and len(link) != 2:
# wrong input: ILA sites can only be Degree 2 # wrong input: ILA sites can only be Degree 2
# => correct to make it a ROADM and remove entry in links_by_city # => correct to make it a ROADM and remove entry in links_by_city
# TODO: put in log rather than print _logger.warning(f'invalid node type ({nodes_by_city[city].node_type}) '
print(f'invalid node type ({nodes_by_city[city].node_type})\ + f'specified in {city}, replaced by ROADM')
specified in {city}, replaced by ROADM')
nodes_by_city[city].node_type = 'ROADM' nodes_by_city[city].node_type = 'ROADM'
for n in nodes: for n in nodes:
if n.city == city: if n.city == city:
@@ -275,13 +339,29 @@ def create_roadm_element(node, roadms_by_city):
'booster_variety_list': silent_remove(node.booster_restriction.split(' | '), '')} 'booster_variety_list': silent_remove(node.booster_restriction.split(' | '), '')}
} }
if node.city in roadms_by_city.keys(): if node.city in roadms_by_city.keys():
if 'params' not in roadm.keys(): if 'params' not in roadm:
roadm['params'] = {} roadm['params'] = {}
roadm['params']['per_degree_pch_out_db'] = {} roadm['params']['per_degree_pch_out_db'] = {}
for elem in roadms_by_city[node.city]: for elem in roadms_by_city[node.city]:
to_node = f'east edfa in {node.city} to {elem.to_node}' to_node = f'east edfa in {node.city} to {elem.to_node}'
if elem.target_pch_out_db is not None: if elem.target_pch_out_db is not None:
roadm['params']['per_degree_pch_out_db'][to_node] = elem.target_pch_out_db roadm['params']['per_degree_pch_out_db'][to_node] = elem.target_pch_out_db
if elem.from_degrees is not None and elem.impairment_ids is not None:
# only set per degree impairment if there is an entry (reduce verbose)
if roadm['params'].get('per_degree_impairments') is None:
roadm['params']['per_degree_impairments'] = []
fromdegrees = elem.from_degrees.split(' | ')
impairment_ids = transform_data(elem.impairment_ids)
if len(fromdegrees) != len(impairment_ids):
msg = f'Roadm {node.city} per degree impairment id do not match with from degree definition'
raise NetworkTopologyError(msg)
for from_degree, impairment_id in zip(fromdegrees, impairment_ids):
from_node = f'west edfa in {node.city} to {from_degree}'
roadm['params']['per_degree_impairments'].append({'from_degree': from_node,
'to_degree': to_node,
'impairment_id': impairment_id})
if elem.type_variety is not None:
roadm['type_variety'] = elem.type_variety
roadm['metadata'] = {'location': {'city': node.city, roadm['metadata'] = {'location': {'city': node.city,
'region': node.region, 'region': node.region,
'latitude': node.latitude, 'latitude': node.latitude,
@@ -290,7 +370,7 @@ def create_roadm_element(node, roadms_by_city):
return roadm return roadm
def create_east_eqpt_element(node): def create_east_eqpt_element(node: Node, nodes_by_city: Dict[str, Node]) -> dict:
""" create amplifiers json elements for the east direction. """ create amplifiers json elements for the east direction.
this includes the case where the case of a fused element defined instead of an this includes the case where the case of a fused element defined instead of an
ILA in eqpt sheet ILA in eqpt sheet
@@ -305,13 +385,13 @@ def create_east_eqpt_element(node):
eqpt['type_variety'] = f'{node.east_amp_type}' eqpt['type_variety'] = f'{node.east_amp_type}'
eqpt['operational'] = {'gain_target': node.east_amp_gain, eqpt['operational'] = {'gain_target': node.east_amp_gain,
'delta_p': node.east_amp_dp, 'delta_p': node.east_amp_dp,
'tilt_target': node.east_tilt, 'tilt_target': node.east_tilt_vs_wavelength,
'out_voa': node.east_att_out} 'out_voa': node.east_att_out}
elif node.east_amp_type.lower() == '': elif node.east_amp_type.lower() == '':
eqpt['type'] = 'Edfa' eqpt['type'] = 'Edfa'
eqpt['operational'] = {'gain_target': node.east_amp_gain, eqpt['operational'] = {'gain_target': node.east_amp_gain,
'delta_p': node.east_amp_dp, 'delta_p': node.east_amp_dp,
'tilt_target': node.east_tilt, 'tilt_target': node.east_tilt_vs_wavelength,
'out_voa': node.east_att_out} 'out_voa': node.east_att_out}
elif node.east_amp_type.lower() == 'fused': elif node.east_amp_type.lower() == 'fused':
# fused edfa variety is a hack to indicate that there should not be # fused edfa variety is a hack to indicate that there should not be
@@ -323,7 +403,7 @@ def create_east_eqpt_element(node):
return eqpt return eqpt
def create_west_eqpt_element(node): def create_west_eqpt_element(node: Node, nodes_by_city: Dict[str, Node]) -> dict:
""" create amplifiers json elements for the west direction. """ create amplifiers json elements for the west direction.
this includes the case where the case of a fused element defined instead of an this includes the case where the case of a fused element defined instead of an
ILA in eqpt sheet ILA in eqpt sheet
@@ -338,19 +418,25 @@ def create_west_eqpt_element(node):
eqpt['type_variety'] = f'{node.west_amp_type}' eqpt['type_variety'] = f'{node.west_amp_type}'
eqpt['operational'] = {'gain_target': node.west_amp_gain, eqpt['operational'] = {'gain_target': node.west_amp_gain,
'delta_p': node.west_amp_dp, 'delta_p': node.west_amp_dp,
'tilt_target': node.west_tilt, 'tilt_target': node.west_tilt_vs_wavelength,
'out_voa': node.west_att_out} 'out_voa': node.west_att_out}
elif node.west_amp_type.lower() == '': elif node.west_amp_type.lower() == '':
eqpt['operational'] = {'gain_target': node.west_amp_gain, eqpt['operational'] = {'gain_target': node.west_amp_gain,
'delta_p': node.west_amp_dp, 'delta_p': node.west_amp_dp,
'tilt_target': node.west_tilt, 'tilt_target': node.west_tilt_vs_wavelength,
'out_voa': node.west_att_out} 'out_voa': node.west_att_out}
elif node.west_amp_type.lower() == 'fused': elif node.west_amp_type.lower() == 'fused':
eqpt['type'] = 'Fused' eqpt['type'] = 'Fused'
eqpt['params'] = {'loss': 0} eqpt['params'] = {'loss': 0}
return eqpt return eqpt
def xls_to_json_data(input_filename, filter_region=[]):
def xls_to_json_data(input_filename: Path, filter_region: List[str] = None) -> Dict:
"""Read the excel sheets and produces the json dict in GNPy format (legacy)
returns json dict
"""
if filter_region is None:
filter_region = []
nodes, links, eqpts, roadms = parse_excel(input_filename) nodes, links, eqpts, roadms = parse_excel(input_filename)
if filter_region: if filter_region:
nodes = [n for n in nodes if n.region.lower() in filter_region] nodes = [n for n in nodes if n.region.lower() in filter_region]
@@ -360,16 +446,13 @@ def xls_to_json_data(input_filename, filter_region=[]):
cities = {lnk.from_city for lnk in links} | {lnk.to_city for lnk in links} cities = {lnk.from_city for lnk in links} | {lnk.to_city for lnk in links}
nodes = [n for n in nodes if n.city in cities] nodes = [n for n in nodes if n.city in cities]
global nodes_by_city
nodes_by_city = {n.city: n for n in nodes} nodes_by_city = {n.city: n for n in nodes}
global links_by_city
links_by_city = defaultdict(list) links_by_city = defaultdict(list)
for link in links: for link in links:
links_by_city[link.from_city].append(link) links_by_city[link.from_city].append(link)
links_by_city[link.to_city].append(link) links_by_city[link.to_city].append(link)
global eqpts_by_city
eqpts_by_city = defaultdict(list) eqpts_by_city = defaultdict(list)
for eqpt in eqpts: for eqpt in eqpts:
eqpts_by_city[eqpt.from_city].append(eqpt) eqpts_by_city[eqpt.from_city].append(eqpt)
@@ -435,7 +518,7 @@ def xls_to_json_data(input_filename, filter_region=[]):
'longitude': x.longitude}}, 'longitude': x.longitude}},
'type': 'Edfa', 'type': 'Edfa',
'operational': {'gain_target': None, 'operational': {'gain_target': None,
'tilt_target': 0} 'tilt_target': None}
} for x in nodes_by_city.values() if x.node_type.lower() == 'ila' and x.city not in eqpts_by_city] + } for x in nodes_by_city.values() if x.node_type.lower() == 'ila' and x.city not in eqpts_by_city] +
[{'uid': f'east edfa in {x.city}', [{'uid': f'east edfa in {x.city}',
'metadata': {'location': {'city': x.city, 'metadata': {'location': {'city': x.city,
@@ -444,25 +527,26 @@ def xls_to_json_data(input_filename, filter_region=[]):
'longitude': x.longitude}}, 'longitude': x.longitude}},
'type': 'Edfa', 'type': 'Edfa',
'operational': {'gain_target': None, 'operational': {'gain_target': None,
'tilt_target': 0} 'tilt_target': None}
} for x in nodes_by_city.values() if x.node_type.lower() == 'ila' and x.city not in eqpts_by_city] + } for x in nodes_by_city.values() if x.node_type.lower() == 'ila' and x.city not in eqpts_by_city]
[create_east_eqpt_element(e) for e in eqpts] + + [create_east_eqpt_element(e, nodes_by_city) for e in eqpts]
[create_west_eqpt_element(e) for e in eqpts], + [create_west_eqpt_element(e, nodes_by_city) for e in eqpts],
'connections': 'connections':
list(chain.from_iterable([eqpt_connection_by_city(n.city) list(chain.from_iterable([eqpt_connection_by_city(n.city, eqpts_by_city, links_by_city, nodes_by_city)
for n in nodes])) for n in nodes]))
+ + list(chain.from_iterable(zip(
list(chain.from_iterable(zip( [{'from_node': f'trx {x.city}', 'to_node': f'roadm {x.city}'}
[{'from_node': f'trx {x.city}',
'to_node': f'roadm {x.city}'}
for x in nodes_by_city.values() if x.node_type.lower() == 'roadm'], for x in nodes_by_city.values() if x.node_type.lower() == 'roadm'],
[{'from_node': f'roadm {x.city}', [{'from_node': f'roadm {x.city}', 'to_node': f'trx {x.city}'}
'to_node': f'trx {x.city}'}
for x in nodes_by_city.values() if x.node_type.lower() == 'roadm']))) for x in nodes_by_city.values() if x.node_type.lower() == 'roadm'])))
} }
def convert_file(input_filename, filter_region=[], output_json_file_name=None): def convert_file(input_filename: Path, filter_region: List[str] = None, output_json_file_name: Path = None):
"""Save the conversion into
"""
if filter_region is None:
filter_region = []
data = xls_to_json_data(input_filename, filter_region) data = xls_to_json_data(input_filename, filter_region)
if output_json_file_name is None: if output_json_file_name is None:
output_json_file_name = input_filename.with_suffix('.json') output_json_file_name = input_filename.with_suffix('.json')
@@ -472,79 +556,77 @@ def convert_file(input_filename, filter_region=[], output_json_file_name=None):
return output_json_file_name return output_json_file_name
def corresp_names(input_filename, network): def corresp_names(input_filename: Path, network: DiGraph):
""" a function that builds the correspondance between names given in the excel, """ a function that builds the correspondance between names given in the excel,
and names used in the json, and created by the autodesign. and names used in the json, and created by the autodesign.
All names are listed All names are listed
""" """
nodes, links, eqpts, roadms = parse_excel(input_filename) nodes, links, eqpts, _ = parse_excel(input_filename)
fused = [n.uid for n in network.nodes() if isinstance(n, Fused)] fused = [n.uid for n in network.nodes() if isinstance(n, Fused)]
ila = [n.uid for n in network.nodes() if isinstance(n, Edfa)] ila = [n.uid for n in network.nodes() if isinstance(n, Edfa)]
corresp_roadm = {x.city: [f'roadm {x.city}'] for x in nodes corresp_roadm = {x.city: [f'roadm {x.city}'] for x in nodes
if x.node_type.lower() == 'roadm'} if x.node_type.lower() == 'roadm'}
corresp_fused = {x.city: [f'west fused spans in {x.city}', f'east fused spans in {x.city}'] corresp_fused = {x.city: [f'west fused spans in {x.city}', f'east fused spans in {x.city}']
for x in nodes if x.node_type.lower() == 'fused' and for x in nodes if x.node_type.lower() == 'fused'
f'west fused spans in {x.city}' in fused and and f'west fused spans in {x.city}' in fused
f'east fused spans in {x.city}' in fused} and f'east fused spans in {x.city}' in fused}
corresp_ila = defaultdict(list)
# add the special cases when an ila is changed into a fused # add the special cases when an ila is changed into a fused
for my_e in eqpts: for my_e in eqpts:
name = f'east edfa in {my_e.from_city} to {my_e.to_city}' name = f'east edfa in {my_e.from_city} to {my_e.to_city}'
if my_e.east_amp_type.lower() == 'fused' and name in fused: if my_e.east_amp_type.lower() == 'fused' and name in fused:
if my_e.from_city in corresp_fused.keys(): corresp_fused.get(my_e.from_city, []).append(name)
corresp_fused[my_e.from_city].append(name)
else:
corresp_fused[my_e.from_city] = [name]
name = f'west edfa in {my_e.from_city} to {my_e.to_city}' name = f'west edfa in {my_e.from_city} to {my_e.to_city}'
if my_e.west_amp_type.lower() == 'fused' and name in fused: if my_e.west_amp_type.lower() == 'fused' and name in fused:
if my_e.from_city in corresp_fused.keys(): corresp_fused.get(my_e.from_city, []).append(name)
corresp_fused[my_e.from_city].append(name)
else:
corresp_fused[my_e.from_city] = [name]
# build corresp ila based on eqpt sheet # build corresp ila based on eqpt sheet
# start with east direction # start with east direction
corresp_ila = {e.from_city: [f'east edfa in {e.from_city} to {e.to_city}']
for e in eqpts if f'east edfa in {e.from_city} to {e.to_city}' in ila}
# west direction, append name or create a new item in dict
for my_e in eqpts: for my_e in eqpts:
name = f'west edfa in {my_e.from_city} to {my_e.to_city}' for name in [f'east edfa in {my_e.from_city} to {my_e.to_city}',
f'west edfa in {my_e.from_city} to {my_e.to_city}']:
if name in ila: if name in ila:
if my_e.from_city in corresp_ila.keys():
corresp_ila[my_e.from_city].append(name) corresp_ila[my_e.from_city].append(name)
else:
corresp_ila[my_e.from_city] = [name]
# complete with potential autodesign names: amplifiers # complete with potential autodesign names: amplifiers
for my_l in links: for my_l in links:
name = f'Edfa0_fiber ({my_l.to_city} \u2192 {my_l.from_city})-{my_l.west_cable}' # create names whatever the type and filter them out
# from-to direction
names = [f'Edfa_preamp_roadm {my_l.from_city}_from_fiber ({my_l.to_city} \u2192 {my_l.from_city})-{my_l.west_cable}',
f'Edfa_booster_roadm {my_l.from_city}_to_fiber ({my_l.from_city} \u2192 {my_l.to_city})-{my_l.east_cable}']
for name in names:
if name in ila: if name in ila:
if my_l.from_city in corresp_ila.keys():
# "east edfa in Stbrieuc to Rennes_STA" is equivalent name as # "east edfa in Stbrieuc to Rennes_STA" is equivalent name as
# "Edfa0_fiber (Lannion_CAS → Stbrieuc)-F056" # "Edfa_booster_roadm Stbrieuc_to_fiber (Lannion_CAS → Stbrieuc)-F056"
# "west edfa in Stbrieuc to Rennes_STA" is equivalent name as # "west edfa in Stbrieuc to Rennes_STA" is equivalent name as
# "Edfa0_fiber (Rennes_STA → Stbrieuc)-F057" # "Edfa_preamp_roadm Stbrieuc_to_fiber (Rennes_STA → Stbrieuc)-F057"
# does not filter names: all types (except boosters) are created. # in case fibers are splitted the name here is a
# in case fibers are splitted the name here is a prefix
corresp_ila[my_l.from_city].append(name) corresp_ila[my_l.from_city].append(name)
else: # to-from direction
corresp_ila[my_l.from_city] = [name] names = [f'Edfa_preamp_roadm {my_l.to_city}_from_fiber ({my_l.from_city} \u2192 {my_l.to_city})-{my_l.east_cable}',
name = f'Edfa0_fiber ({my_l.from_city} \u2192 {my_l.to_city})-{my_l.east_cable}' f'Edfa_booster_roadm {my_l.to_city}_to_fiber ({my_l.to_city} \u2192 {my_l.from_city})-{my_l.west_cable}']
for name in names:
if name in ila: if name in ila:
if my_l.to_city in corresp_ila.keys():
corresp_ila[my_l.to_city].append(name) corresp_ila[my_l.to_city].append(name)
else: for node in nodes:
corresp_ila[my_l.to_city] = [name] names = [f'east edfa in {node.city}', f'west edfa in {node.city}']
for name in names:
if name in ila:
# "east edfa in Stbrieuc to Rennes_STA" (created with Eqpt) is equivalent name as
# "east edfa in Stbrieuc" or "west edfa in Stbrieuc" (created with Links sheet)
# depending on link node order
corresp_ila[node.city].append(name)
# merge fused with ila: # merge fused with ila:
for key, val in corresp_fused.items(): for key, val in corresp_fused.items():
if key in corresp_ila.keys():
corresp_ila[key].extend(val) corresp_ila[key].extend(val)
else:
corresp_ila[key] = val
# no need of roadm booster # no need of roadm booster
return corresp_roadm, corresp_fused, corresp_ila return corresp_roadm, corresp_fused, corresp_ila
def parse_excel(input_filename): def parse_excel(input_filename: Path) -> Tuple[List[Node], List[Link], List[Eqpt], List[Roadm]]:
"""reads xls(x) sheets among Nodes, Eqpts, Links, Roadms and parse the data in the sheets
into internal data structure Node, Link, Eqpt, Roadm, classes
"""
link_headers = { link_headers = {
'Node A': 'from_city', 'Node A': 'from_city',
'Node Z': 'to_city', 'Node Z': 'to_city',
@@ -583,7 +665,6 @@ def parse_excel(input_filename):
'Node Z': 'to_city', 'Node Z': 'to_city',
'east': { 'east': {
'amp type': 'east_amp_type', 'amp type': 'east_amp_type',
'att_in': 'east_att_in',
'amp gain': 'east_amp_gain', 'amp gain': 'east_amp_gain',
'delta p': 'east_amp_dp', 'delta p': 'east_amp_dp',
'tilt': 'east_tilt', 'tilt': 'east_tilt',
@@ -591,7 +672,6 @@ def parse_excel(input_filename):
}, },
'west': { 'west': {
'amp type': 'west_amp_type', 'amp type': 'west_amp_type',
'att_in': 'west_att_in',
'amp gain': 'west_amp_gain', 'amp gain': 'west_amp_gain',
'delta p': 'west_amp_dp', 'delta p': 'west_amp_dp',
'tilt': 'west_tilt', 'tilt': 'west_tilt',
@@ -600,7 +680,10 @@ def parse_excel(input_filename):
} }
roadm_headers = {'Node A': 'from_node', roadm_headers = {'Node A': 'from_node',
'Node Z': 'to_node', 'Node Z': 'to_node',
'per degree target power (dBm)': 'target_pch_out_db' 'per degree target power (dBm)': 'target_pch_out_db',
'type_variety': 'type_variety',
'from degrees': 'from_degrees',
'from degree to degree impairment id': 'impairment_ids'
} }
with open_workbook(input_filename) as wb: with open_workbook(input_filename) as wb:
@@ -608,81 +691,84 @@ def parse_excel(input_filename):
links_sheet = wb.sheet_by_name('Links') links_sheet = wb.sheet_by_name('Links')
try: try:
eqpt_sheet = wb.sheet_by_name('Eqpt') eqpt_sheet = wb.sheet_by_name('Eqpt')
except Exception: except XLRDError:
# eqpt_sheet is optional # eqpt_sheet is optional
eqpt_sheet = None eqpt_sheet = None
try: try:
roadm_sheet = wb.sheet_by_name('Roadms') roadm_sheet = wb.sheet_by_name('Roadms')
except Exception: except XLRDError:
# roadm_sheet is optional # roadm_sheet is optional
roadm_sheet = None roadm_sheet = None
nodes = [] nodes = [Node(**node) for node in parse_sheet(nodes_sheet, node_headers,
for node in parse_sheet(nodes_sheet, node_headers, NODES_LINE, NODES_LINE + 1, NODES_COLUMN): NODES_LINE, NODES_LINE + 1, NODES_COLUMN)]
nodes.append(Node(**node))
expected_node_types = {'ROADM', 'ILA', 'FUSED'} expected_node_types = {'ROADM', 'ILA', 'FUSED'}
for n in nodes: for n in nodes:
if n.node_type not in expected_node_types: if n.node_type not in expected_node_types:
n.node_type = 'ILA' n.node_type = 'ILA'
links = [] links = [Link(**link) for link in parse_sheet(links_sheet, link_headers,
for link in parse_sheet(links_sheet, link_headers, LINKS_LINE, LINKS_LINE + 2, LINKS_COLUMN): LINKS_LINE, LINKS_LINE + 2, LINKS_COLUMN)]
links.append(Link(**link))
eqpts = [] eqpts = []
if eqpt_sheet is not None: if eqpt_sheet is not None:
for eqpt in parse_sheet(eqpt_sheet, eqpt_headers, EQPTS_LINE, EQPTS_LINE + 2, EQPTS_COLUMN): eqpts = [Eqpt(**eqpt) for eqpt in parse_sheet(eqpt_sheet, eqpt_headers,
eqpts.append(Eqpt(**eqpt)) EQPTS_LINE, EQPTS_LINE + 2, EQPTS_COLUMN)]
roadms = [] roadms = []
if roadm_sheet is not None: if roadm_sheet is not None:
for roadm in parse_sheet(roadm_sheet, roadm_headers, ROADMS_LINE, ROADMS_LINE+2, ROADMS_COLUMN): roadms = [Roadm(**roadm) for roadm in parse_sheet(roadm_sheet, roadm_headers,
roadms.append(Roadm(**roadm)) ROADMS_LINE, ROADMS_LINE + 2, ROADMS_COLUMN)]
# sanity check # sanity check
all_cities = Counter(n.city for n in nodes) all_cities = Counter(n.city for n in nodes)
if len(all_cities) != len(nodes): if len(all_cities) != len(nodes):
raise ValueError(f'Duplicate city: {all_cities}') msg = f'Duplicate city: {all_cities}'
raise NetworkTopologyError(msg)
bad_links = [] bad_links = []
for lnk in links: for lnk in links:
if lnk.from_city not in all_cities or lnk.to_city not in all_cities: if lnk.from_city not in all_cities or lnk.to_city not in all_cities:
bad_links.append([lnk.from_city, lnk.to_city]) bad_links.append([lnk.from_city, lnk.to_city])
if bad_links: if bad_links:
raise NetworkTopologyError(f'{ansi_escapes.red}XLS error:{ansi_escapes.reset} ' msg = 'XLS error: ' \
f'The {ansi_escapes.cyan}Links{ansi_escapes.reset} sheet references nodes that ' + 'The Links sheet references nodes that ' \
f'are not defined in the {ansi_escapes.cyan}Nodes{ansi_escapes.reset} sheet:\n' + 'are not defined in the Nodes sheet:\n' \
+ _format_items(f'{item[0]} -> {item[1]}' for item in bad_links)) + _format_items(f'{item[0]} -> {item[1]}' for item in bad_links)
raise NetworkTopologyError(msg)
return nodes, links, eqpts, roadms return nodes, links, eqpts, roadms
def eqpt_connection_by_city(city_name): def eqpt_connection_by_city(city_name: str, eqpts_by_city: DefaultDict[str, List[Eqpt]],
other_cities = fiber_dest_from_source(city_name) links_by_city: DefaultDict[str, List[Link]], nodes_by_city: Dict[str, Node]) -> list:
"""
"""
other_cities = fiber_dest_from_source(city_name, links_by_city)
subdata = [] subdata = []
if nodes_by_city[city_name].node_type.lower() in {'ila', 'fused'}: if nodes_by_city[city_name].node_type.lower() in {'ila', 'fused'}:
# Then len(other_cities) == 2 # Then len(other_cities) == 2
direction = ['west', 'east'] direction = ['west', 'east']
for i in range(2): for i in range(2):
from_ = fiber_link(other_cities[i], city_name) from_ = fiber_link(other_cities[i], city_name, links_by_city)
in_ = eqpt_in_city_to_city(city_name, other_cities[0], direction[i]) in_ = eqpt_in_city_to_city(city_name, other_cities[0], eqpts_by_city, nodes_by_city, direction[i])
to_ = fiber_link(city_name, other_cities[1 - i]) to_ = fiber_link(city_name, other_cities[1 - i], links_by_city)
subdata += connect_eqpt(from_, in_, to_) subdata += connect_eqpt(from_, in_, to_)
elif nodes_by_city[city_name].node_type.lower() == 'roadm': elif nodes_by_city[city_name].node_type.lower() == 'roadm':
for other_city in other_cities: for other_city in other_cities:
from_ = f'roadm {city_name}' from_ = f'roadm {city_name}'
in_ = eqpt_in_city_to_city(city_name, other_city) in_ = eqpt_in_city_to_city(city_name, other_city, eqpts_by_city, nodes_by_city)
to_ = fiber_link(city_name, other_city) to_ = fiber_link(city_name, other_city, links_by_city)
subdata += connect_eqpt(from_, in_, to_) subdata += connect_eqpt(from_, in_, to_)
from_ = fiber_link(other_city, city_name) from_ = fiber_link(other_city, city_name, links_by_city)
in_ = eqpt_in_city_to_city(city_name, other_city, "west") in_ = eqpt_in_city_to_city(city_name, other_city, eqpts_by_city, nodes_by_city, "west")
to_ = f'roadm {city_name}' to_ = f'roadm {city_name}'
subdata += connect_eqpt(from_, in_, to_) subdata += connect_eqpt(from_, in_, to_)
return subdata return subdata
def connect_eqpt(from_, in_, to_): def connect_eqpt(from_: str, in_: str, to_: str) -> List[dict]:
"""Utils: create the topology connection json dict between in and to
"""
connections = [] connections = []
if in_ != '': if in_ != '':
connections = [{'from_node': from_, 'to_node': in_}, connections = [{'from_node': from_, 'to_node': in_},
@@ -692,7 +778,11 @@ def connect_eqpt(from_, in_, to_):
return connections return connections
def eqpt_in_city_to_city(in_city, to_city, direction='east'): def eqpt_in_city_to_city(in_city: str, to_city: str,
eqpts_by_city: DefaultDict[str, List[Eqpt]], nodes_by_city: Dict[str, Node],
direction: str = 'east') -> str:
"""Utils: returns the formatted dtring corresponding to in_city types and direction
"""
rev_direction = 'west' if direction == 'east' else 'east' rev_direction = 'west' if direction == 'east' else 'east'
return_eqpt = '' return_eqpt = ''
if in_city in eqpts_by_city: if in_city in eqpts_by_city:
@@ -711,22 +801,25 @@ def eqpt_in_city_to_city(in_city, to_city, direction='east'):
return return_eqpt return return_eqpt
def corresp_next_node(network, corresp_ila, corresp_roadm): def corresp_next_node(network: DiGraph, corresp_ila: dict, corresp_roadm: dict) -> Tuple[dict, dict]:
""" for each name in corresp dictionnaries find the next node in network and its name """ for each name in corresp dictionnaries find the next node in network and its name
given by user in excel. for meshTopology_exampleV2.xls: given by user in excel. for meshTopology_exampleV2.xls:
user ILA name Stbrieuc covers the two direction. convert.py creates 2 different ILA user ILA name Stbrieuc covers the two direction. convert.py creates 2 different ILA
with possible names (depending on the direction and if the eqpt was defined in eqpt with possible names (depending on the direction and if the eqpt was defined in eqpt
sheet) sheet)
for an ILA and if it is defined in eqpt:
- east edfa in Stbrieuc to Rennes_STA - east edfa in Stbrieuc to Rennes_STA
- west edfa in Stbrieuc to Rennes_STA - west edfa in Stbrieuc to Rennes_STA
- Edfa0_fiber (Lannion_CAS → Stbrieuc)-F056 for an ILA and if it is not defined in eqpt:
- Edfa0_fiber (Rennes_STA → Stbrieuc)-F057 - east edfa in Stbrieuc
- west edfa in Stbrieuc
for a roadm
"Edfa_preamp_roadm node1_from_fiber (siteE → node1)-CABLES#19"
"Edfa_booster_roadm node1_to_fiber (node1 → siteE)-CABLES#19"
next_nodes finds the user defined name of next node to be able to map the path constraints next_nodes finds the user defined name of next node to be able to map the path constraints
- east edfa in Stbrieuc to Rennes_STA next node = Rennes_STA - east edfa in Stbrieuc to Rennes_STA next node = Rennes_STA
- west edfa in Stbrieuc to Rennes_STA next node Lannion_CAS - west edfa in Stbrieuc to Rennes_STA next node Lannion_CAS
Edfa0_fiber (Lannion_CAS → Stbrieuc)-F056 and Edfa0_fiber (Rennes_STA → Stbrieuc)-F057
do not exist
the function supports fiber splitting, fused nodes and shall only be called if the function supports fiber splitting, fused nodes and shall only be called if
excel format is used for both network and service excel format is used for both network and service
""" """
@@ -737,8 +830,8 @@ def corresp_next_node(network, corresp_ila, corresp_roadm):
for ila_elem in ila_list: for ila_elem in ila_list:
# find the node with ila_elem string _in_ the node uid. 'in' is used instead of # find the node with ila_elem string _in_ the node uid. 'in' is used instead of
# '==' to find composed nodes due to fiber splitting in autodesign. # '==' to find composed nodes due to fiber splitting in autodesign.
# eg if elem_ila is 'Edfa0_fiber (Lannion_CAS → Stbrieuc)-F056', # eg if elem_ila is 'east edfa in Stbrieuc to Rennes_STA',
# node uid 'Edfa0_fiber (Lannion_CAS → Stbrieuc)-F056_(1/2)' is possible # node uid 'east edfa in Stbrieuc to Rennes_STA-_(1/2)' is possible
correct_ila_name = next(n.uid for n in network.nodes() if ila_elem in n.uid) correct_ila_name = next(n.uid for n in network.nodes() if ila_elem in n.uid)
temp.remove(ila_elem) temp.remove(ila_elem)
temp.append(correct_ila_name) temp.append(correct_ila_name)
@@ -755,7 +848,7 @@ def corresp_next_node(network, corresp_ila, corresp_roadm):
break break
# if next_nd was not already added in the dict with the previous loop, # if next_nd was not already added in the dict with the previous loop,
# add the first found correspondance in ila names # add the first found correspondance in ila names
if correct_ila_name not in next_node.keys(): if correct_ila_name not in next_node:
for key, val in corresp_ila.items(): for key, val in corresp_ila.items():
# in case of splitted fibers the ila name might not be exact match # in case of splitted fibers the ila name might not be exact match
if [e for e in val if e in next_nd.uid]: if [e for e in val if e in next_nd.uid]:
@@ -766,7 +859,9 @@ def corresp_next_node(network, corresp_ila, corresp_roadm):
return corresp_ila, next_node return corresp_ila, next_node
def fiber_dest_from_source(city_name): def fiber_dest_from_source(city_name: str, links_by_city: DefaultDict[str, List[Link]]) -> List[str]:
"""Returns the list of cities city_name is connected to
"""
destinations = [] destinations = []
links_from_city = links_by_city[city_name] links_from_city = links_by_city[city_name]
for l in links_from_city: for l in links_from_city:
@@ -777,7 +872,9 @@ def fiber_dest_from_source(city_name):
return destinations return destinations
def fiber_link(from_city, to_city): def fiber_link(from_city: str, to_city: str, links_by_city: DefaultDict[str, List[Link]]) -> str:
"""utils: returns formatted uid for fibers between from_city and to_city
"""
source_dest = (from_city, to_city) source_dest = (from_city, to_city)
links = links_by_city[from_city] links = links_by_city[from_city]
link = next(l for l in links if l.from_city in source_dest and l.to_city in source_dest) link = next(l for l in links if l.from_city in source_dest and l.to_city in source_dest)
@@ -788,7 +885,9 @@ def fiber_link(from_city, to_city):
return fiber return fiber
def midpoint(city_a, city_b): def midpoint(city_a: Node, city_b:Node) -> dict:
"""Computes mipoint coordinates
"""
lats = city_a.latitude, city_b.latitude lats = city_a.latitude, city_b.latitude
longs = city_a.longitude, city_b.longitude longs = city_a.longitude, city_b.longitude
try: try:
@@ -813,10 +912,12 @@ LINKS_LINE = 3
EQPTS_LINE = 3 EQPTS_LINE = 3
EQPTS_COLUMN = 14 EQPTS_COLUMN = 14
ROADMS_LINE = 3 ROADMS_LINE = 3
ROADMS_COLUMN = 3 ROADMS_COLUMN = 6
def _do_convert(): def _do_convert():
"""Main function for xls(x) topology conversion to JSON format
"""
parser = ArgumentParser() parser = ArgumentParser()
parser.add_argument('workbook', type=Path) parser.add_argument('workbook', type=Path)
parser.add_argument('-f', '--filter-region', action='append', default=[]) parser.add_argument('-f', '--filter-region', action='append', default=[])

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' """
gnpy.tools.plots gnpy.tools.plots
================ ================
Graphs and plots usable from a CLI application Graphs and plots usable from a CLI application
''' """
from matplotlib.pyplot import show, axis, figure, title, text from matplotlib.pyplot import show, axis, figure, title, text
from networkx import draw_networkx from networkx import draw_networkx

View File

@@ -11,109 +11,150 @@ Yang model for requesting path computation.
See: draft-ietf-teas-yang-path-computation-01.txt See: draft-ietf-teas-yang-path-computation-01.txt
""" """
from xlrd import open_workbook, XL_CELL_EMPTY
from collections import namedtuple from collections import namedtuple
from logging import getLogger from logging import getLogger
from copy import deepcopy from copy import deepcopy
from pathlib import Path
from typing import Dict, List
from networkx import DiGraph
from xlrd import open_workbook, XL_CELL_EMPTY
from gnpy.core.utils import db2lin from gnpy.core.utils import db2lin
from gnpy.core.exceptions import ServiceError from gnpy.core.exceptions import ServiceError
from gnpy.core.elements import Transceiver, Roadm, Edfa, Fiber from gnpy.core.elements import Transceiver, Roadm, Edfa, Fiber
import gnpy.core.ansi_escapes as ansi_escapes from gnpy.tools.convert import corresp_names, corresp_next_node, all_rows
from gnpy.tools.convert import corresp_names, corresp_next_node
SERVICES_COLUMN = 12 SERVICES_COLUMN = 12
def all_rows(sheet, start=0):
return (sheet.row(x) for x in range(start, sheet.nrows))
logger = getLogger(__name__) logger = getLogger(__name__)
class Request(namedtuple('Request', 'request_id source destination trx_type mode \ class Request(namedtuple('request_param', 'request_id source destination trx_type mode \
spacing power nb_channel disjoint_from nodes_list is_loose path_bandwidth')): spacing power nb_channel disjoint_from nodes_list is_loose path_bandwidth')):
def __new__(cls, request_id, source, destination, trx_type, mode=None, spacing=None, power=None, nb_channel=None, disjoint_from='', nodes_list=None, is_loose='', path_bandwidth=None): """DATA class for a request.
return super().__new__(cls, request_id, source, destination, trx_type, mode, spacing, power, nb_channel, disjoint_from, nodes_list, is_loose, path_bandwidth)
:params request_id (int): The unique identifier for the request.
:params source (str): The source node for the communication.
:params destination (str): The destination node for the communication.
:params trx_type (str): The type of transmission for the communication.
:params mode (str, optional): The mode of transmission. Defaults to None.
:params spacing (float, optional): The spacing between channels. Defaults to None.
:params power (float, optional): The power level for the communication. Defaults to None.
:params nb_channel (int, optional): The number of channels required for the communication. Defaults to None.
:params disjoint_from (str, optional): The node to be disjoint from. Defaults to ''.
:params nodes_list (list, optional): The list of nodes involved in the communication. Defaults to None.
:params is_loose (str, optional): Indicates if the communication is loose. Defaults to ''.
:params path_bandwidth (float, optional): The bandwidth required for the communication. Defaults to None.
"""
def __new__(cls, request_id, source, destination, trx_type, mode=None, spacing=None, power=None, nb_channel=None,
disjoint_from='', nodes_list=None, is_loose='', path_bandwidth=None):
return super().__new__(cls, request_id, source, destination, trx_type, mode, spacing, power, nb_channel,
disjoint_from, nodes_list, is_loose, path_bandwidth)
class Element: class Element:
"""
"""
def __init__(self, uid):
self.uid = uid
def __eq__(self, other): def __eq__(self, other):
return type(self) == type(other) and self.uid == other.uid return isinstance(other, type(self)) and self.uid == other.ui
def __hash__(self): def __hash__(self):
return hash((type(self), self.uid)) return hash((type(self), self.uid))
class Request_element(Element): class Request_element(Element):
def __init__(self, Request, equipment, bidir): """Class that generate the request in the json format
:params request_param (Request): The request object containing the information for the element.
:params equipment (dict): The equipment configuration for the communication.
:params bidir (bool): Indicates if the communication is bidirectional.
Attributes:
request_id (str): The unique identifier for the request.
source (str): The source node for the communication.
destination (str): The destination node for the communication.
srctpid (str): The source TP ID for the communication.
dsttpid (str): The destination TP ID for the communication.
bidir (bool): Indicates if the communication is bidirectional.
trx_type (str): The type of transmission for the communication.
mode (str): The mode of transmission for the communication.
spacing (float): The spacing between channels for the communication.
power (float): The power level for the communication.
nb_channel (int): The number of channels required for the communication.
disjoint_from (list): The list of nodes to be disjoint from.
nodes_list (list): The list of nodes involved in the communication.
loose (str): Indicates if the communication is loose or strict.
path_bandwidth (float): The bandwidth required for the communication.
"""
def __init__(self, request_param: Request, equipment: Dict, bidir: bool):
"""
"""
super().__init__(uid=request_param.request_id)
# request_id is str # request_id is str
# excel has automatic number formatting that adds .0 on integer values # excel has automatic number formatting that adds .0 on integer values
# the next lines recover the pure int value, assuming this .0 is unwanted # the next lines recover the pure int value, assuming this .0 is unwanted
self.request_id = correct_xlrd_int_to_str_reading(Request.request_id) self.request_id = correct_xlrd_int_to_str_reading(request_param.request_id)
self.source = f'trx {Request.source}' self.source = f'trx {request_param.source}'
self.destination = f'trx {Request.destination}' self.destination = f'trx {request_param.destination}'
# TODO: the automatic naming generated by excel parser requires that source and dest name # TODO: the automatic naming generated by excel parser requires that source and dest name
# be a string starting with 'trx' : this is manually added here. # be a string starting with 'trx' : this is manually added here.
self.srctpid = f'trx {Request.source}' self.srctpid = f'trx {request_param.source}'
self.dsttpid = f'trx {Request.destination}' self.dsttpid = f'trx {request_param.destination}'
self.bidir = bidir self.bidir = bidir
# test that trx_type belongs to eqpt_config.json # test that trx_type belongs to eqpt_config.json
# if not replace it with a default # if not replace it with a default
try: try:
if equipment['Transceiver'][Request.trx_type]: if equipment['Transceiver'][request_param.trx_type]:
self.trx_type = correct_xlrd_int_to_str_reading(Request.trx_type) self.trx_type = correct_xlrd_int_to_str_reading(request_param.trx_type)
if Request.mode is not None: if request_param.mode is not None:
Requestmode = correct_xlrd_int_to_str_reading(Request.mode) request_mode = correct_xlrd_int_to_str_reading(request_param.mode)
if [mode for mode in equipment['Transceiver'][Request.trx_type].mode if mode['format'] == Requestmode]: if [mode for mode in equipment['Transceiver'][request_param.trx_type].mode
self.mode = Requestmode if mode['format'] == request_mode]:
self.mode = request_mode
else: else:
msg = f'Request Id: {self.request_id} - could not find tsp : \'{Request.trx_type}\' with mode: \'{Requestmode}\' in eqpt library \nComputation stopped.' msg = f'Request Id: {self.request_id} - could not find tsp : \'{request_param.trx_type}\' ' \
# print(msg) + f'with mode: \'{request_mode}\' in eqpt library \nComputation stopped.'
logger.critical(msg)
raise ServiceError(msg) raise ServiceError(msg)
else: else:
Requestmode = None request_mode = None
self.mode = Request.mode self.mode = request_param.mode
except KeyError: except KeyError as e:
msg = f'Request Id: {self.request_id} - could not find tsp : \'{Request.trx_type}\' with mode: \'{Request.mode}\' in eqpt library \nComputation stopped.' msg = f'Request Id: {self.request_id} - could not find tsp : \'{request_param.trx_type}\' with mode: ' \
# print(msg) + f'\'{request_param.mode}\' in eqpt library \nComputation stopped.'
logger.critical(msg) raise ServiceError(msg) from e
raise ServiceError(msg)
# excel input are in GHz and dBm # excel input are in GHz and dBm
if Request.spacing is not None: if request_param.spacing is not None:
self.spacing = Request.spacing * 1e9 self.spacing = request_param.spacing * 1e9
else: else:
msg = f'Request {self.request_id} missing spacing: spacing is mandatory.\ncomputation stopped' msg = f'Request {self.request_id} missing spacing: spacing is mandatory.\ncomputation stopped'
logger.critical(msg)
raise ServiceError(msg) raise ServiceError(msg)
if Request.power is not None:
self.power = db2lin(Request.power) * 1e-3
else:
self.power = None self.power = None
if Request.nb_channel is not None: if request_param.power is not None:
self.nb_channel = int(Request.nb_channel) self.power = db2lin(request_param.power) * 1e-3
else:
self.nb_channel = None self.nb_channel = None
if request_param.nb_channel is not None:
self.nb_channel = int(request_param.nb_channel)
value = correct_xlrd_int_to_str_reading(Request.disjoint_from) value = correct_xlrd_int_to_str_reading(request_param.disjoint_from)
self.disjoint_from = [n for n in value.split(' | ') if value] self.disjoint_from = [n for n in value.split(' | ') if value]
self.nodes_list = [] self.nodes_list = []
if Request.nodes_list: if request_param.nodes_list:
self.nodes_list = Request.nodes_list.split(' | ') self.nodes_list = request_param.nodes_list.split(' | ')
self.loose = 'LOOSE' self.loose = 'LOOSE'
if Request.is_loose.lower() == 'no': if request_param.is_loose.lower() == 'no':
self.loose = 'STRICT' self.loose = 'STRICT'
self.path_bandwidth = None
if Request.path_bandwidth is not None:
self.path_bandwidth = Request.path_bandwidth * 1e9
else:
self.path_bandwidth = 0 self.path_bandwidth = 0
if request_param.path_bandwidth is not None:
uid = property(lambda self: repr(self)) self.path_bandwidth = request_param.path_bandwidth * 1e9
@property @property
def pathrequest(self): def pathrequest(self):
"""Creates json dictionnary for the request
"""
# Default assumption for bidir is False # Default assumption for bidir is False
req_dictionnary = { req_dictionnary = {
'request-id': self.request_id, 'request-id': self.request_id,
@@ -127,7 +168,7 @@ class Request_element(Element):
'technology': 'flexi-grid', 'technology': 'flexi-grid',
'trx_type': self.trx_type, 'trx_type': self.trx_type,
'trx_mode': self.mode, 'trx_mode': self.mode,
'effective-freq-slot': [{'N': 'null', 'M': 'null'}], 'effective-freq-slot': [{'N': None, 'M': None}],
'spacing': self.spacing, 'spacing': self.spacing,
'max-nb-of-channel': self.nb_channel, 'max-nb-of-channel': self.nb_channel,
'output-power': self.power 'output-power': self.power
@@ -156,29 +197,32 @@ class Request_element(Element):
@property @property
def pathsync(self): def pathsync(self):
"""Creates json dictionnary for disjunction list (synchronization vector)
"""
if self.disjoint_from: if self.disjoint_from:
return {'synchronization-id': self.request_id, return {'synchronization-id': self.request_id,
'svec': { 'svec': {
'relaxable': 'false', 'relaxable': 'false',
'disjointness': 'node link', 'disjointness': 'node link',
'request-id-number': [self.request_id] + [n for n in self.disjoint_from] 'request-id-number': [self.request_id] + list(self.disjoint_from)
} }
} }
else:
return None return None
# TO-DO: avoid multiple entries with same synchronisation vectors # TO-DO: avoid multiple entries with same synchronisation vectors
@property @property
def json(self): def json(self):
"""Returns the json dictionnary for requests and for synchronisation vector
"""
return self.pathrequest, self.pathsync return self.pathrequest, self.pathsync
def read_service_sheet( def read_service_sheet(
input_filename, input_filename: Path,
eqpt, eqpt: Dict,
network, network: DiGraph,
network_filename=None, network_filename: Path = None,
bidir=False): bidir: bool = False) -> Dict:
""" converts a service sheet into a json structure """ converts a service sheet into a json structure
""" """
if network_filename is None: if network_filename is None:
@@ -188,19 +232,16 @@ def read_service_sheet(
req = correct_xls_route_list(network_filename, network, req) req = correct_xls_route_list(network_filename, network, req)
# if there is no sync vector , do not write any synchronization # if there is no sync vector , do not write any synchronization
synchro = [n.json[1] for n in req if n.json[1] is not None] synchro = [n.json[1] for n in req if n.json[1] is not None]
data = {'path-request': [n.json[0] for n in req]}
if synchro: if synchro:
data = { data['synchronization'] = synchro
'path-request': [n.json[0] for n in req],
'synchronization': synchro
}
else:
data = {
'path-request': [n.json[0] for n in req]
}
return data return data
def correct_xlrd_int_to_str_reading(v): def correct_xlrd_int_to_str_reading(v):
"""Utils: ensure that int values in id are read as strings containing the int and
do not use the automatic float conversion from xlrd
"""
if not isinstance(v, str): if not isinstance(v, str):
value = str(int(v)) value = str(int(v))
if value.endswith('.0'): if value.endswith('.0'):
@@ -210,22 +251,27 @@ def correct_xlrd_int_to_str_reading(v):
return value return value
def parse_row(row, fieldnames): def parse_row(row: List, fieldnames: List[str]) -> Dict:
"""Reads each values in a row and creates a dict using field names
"""
return {f: r.value for f, r in zip(fieldnames, row[0:SERVICES_COLUMN]) return {f: r.value for f, r in zip(fieldnames, row[0:SERVICES_COLUMN])
if r.ctype != XL_CELL_EMPTY} if r.ctype != XL_CELL_EMPTY}
def parse_excel(input_filename): def parse_excel(input_filename: Path) -> List[Request]:
"""Opens xls_file and reads 'Service' sheet
Returns the list of services data in Request class
"""
with open_workbook(input_filename) as wb: with open_workbook(input_filename) as wb:
service_sheet = wb.sheet_by_name('Service') service_sheet = wb.sheet_by_name('Service')
services = list(parse_service_sheet(service_sheet)) services = list(parse_service_sheet(service_sheet))
return services return services
def parse_service_sheet(service_sheet): def parse_service_sheet(service_sheet) -> Request:
""" reads each column according to authorized fieldnames. order is not important. """ reads each column according to authorized fieldnames. order is not important.
""" """
logger.info(f'Validating headers on {service_sheet.name!r}') logger.debug('Validating headers on %r', service_sheet.name)
# add a test on field to enable the '' field case that arises when columns on the # add a test on field to enable the '' field case that arises when columns on the
# right hand side are used as comments or drawing in the excel sheet # right hand side are used as comments or drawing in the excel sheet
header = [x.value.strip() for x in service_sheet.row(4)[0:SERVICES_COLUMN] header = [x.value.strip() for x in service_sheet.row(4)[0:SERVICES_COLUMN]
@@ -243,15 +289,52 @@ def parse_service_sheet(service_sheet):
'routing: is loose?': 'is_loose', 'path bandwidth': 'path_bandwidth'} 'routing: is loose?': 'is_loose', 'path bandwidth': 'path_bandwidth'}
try: try:
service_fieldnames = [authorized_fieldnames[e] for e in header] service_fieldnames = [authorized_fieldnames[e] for e in header]
except KeyError: except KeyError as e:
msg = f'Malformed header on Service sheet: {header} field not in {authorized_fieldnames}' msg = f'Malformed header on Service sheet: {header} field not in {authorized_fieldnames}'
logger.critical(msg) raise ValueError(msg) from e
raise ValueError(msg)
for row in all_rows(service_sheet, start=5): for row in all_rows(service_sheet, start=5):
yield Request(**parse_row(row[0:SERVICES_COLUMN], service_fieldnames)) yield Request(**parse_row(row[0:SERVICES_COLUMN], service_fieldnames))
def correct_xls_route_list(network_filename, network, pathreqlist): def check_end_points(pathreq: Request_element, network: DiGraph):
"""Raise error if end point is not correct
"""
transponders = [n.uid for n in network.nodes() if isinstance(n, Transceiver)]
if pathreq.source not in transponders:
msg = f'Request: {pathreq.request_id}: could not find' +\
f' transponder source : {pathreq.source}.'
logger.critical(msg)
raise ServiceError(msg)
if pathreq.destination not in transponders:
msg = f'Request: {pathreq.request_id}: could not find' +\
f' transponder destination: {pathreq.destination}.'
logger.critical(msg)
raise ServiceError(msg)
def find_node_sugestion(n_id, corresp_roadm, corresp_fused, corresp_ila, network):
"""
"""
roadmtype = [n.uid for n in network.nodes() if isinstance(n, Roadm)]
edfatype = [n.uid for n in network.nodes() if isinstance(n, Edfa)]
# check that n_id is in the node list, if not find a correspondance name
if n_id in roadmtype + edfatype:
return [n_id]
# checks first roadm, fused, and ila in this order, because ila automatic name
# contains roadm names. If it is a fused node, next ila names might be correct
# suggestions, especially if following fibers were splitted and ila names
# created with the name of the fused node
if n_id in corresp_roadm.keys():
return corresp_roadm[n_id]
if n_id in corresp_fused.keys():
return corresp_fused[n_id] + corresp_ila[n_id]
if n_id in corresp_ila.keys():
return corresp_ila[n_id]
return []
def correct_xls_route_list(network_filename: Path, network: DiGraph,
pathreqlist: List[Request_element]) -> List[Request_element]:
""" prepares the format of route list of nodes to be consistant with nodes names: """ prepares the format of route list of nodes to be consistant with nodes names:
remove wrong names, find correct names for ila, roadm and fused if the entry was remove wrong names, find correct names for ila, roadm and fused if the entry was
xls. xls.
@@ -265,32 +348,17 @@ def correct_xls_route_list(network_filename, network, pathreqlist):
corresp_ila, next_node = corresp_next_node(network, corresp_ila, corresp_roadm) corresp_ila, next_node = corresp_next_node(network, corresp_ila, corresp_roadm)
# finally correct constraints based on these dict # finally correct constraints based on these dict
trxfibertype = [n.uid for n in network.nodes() if isinstance(n, (Transceiver, Fiber))] trxfibertype = [n.uid for n in network.nodes() if isinstance(n, (Transceiver, Fiber))]
roadmtype = [n.uid for n in network.nodes() if isinstance(n, Roadm)]
edfatype = [n.uid for n in network.nodes() if isinstance(n, Edfa)]
# TODO there is a problem of identification of fibers in case of parallel # TODO there is a problem of identification of fibers in case of parallel
# fibers between two adjacent roadms so fiber constraint is not supported # fibers between two adjacent roadms so fiber constraint is not supported
transponders = [n.uid for n in network.nodes() if isinstance(n, Transceiver)]
for pathreq in pathreqlist: for pathreq in pathreqlist:
# first check that source and dest are transceivers # first check that source and dest are transceivers
if pathreq.source not in transponders: check_end_points(pathreq, network)
msg = f'{ansi_escapes.red}Request: {pathreq.request_id}: could not find' +\
f' transponder source : {pathreq.source}.{ansi_escapes.reset}'
logger.critical(msg)
raise ServiceError(msg)
if pathreq.destination not in transponders:
msg = f'{ansi_escapes.red}Request: {pathreq.request_id}: could not find' +\
f' transponder destination: {pathreq.destination}.{ansi_escapes.reset}'
logger.critical(msg)
raise ServiceError(msg)
# silently pop source and dest nodes from the list if they were added by the user as first # silently pop source and dest nodes from the list if they were added by the user as first
# and last elem in the constraints respectively. Other positions must lead to an error # and last elem in the constraints respectively. Other positions must lead to an error
# caught later on # caught later on
if pathreq.nodes_list and pathreq.source == pathreq.nodes_list[0]: if pathreq.nodes_list and pathreq.source == pathreq.nodes_list[0]:
pathreq.loose_list.pop(0)
pathreq.nodes_list.pop(0) pathreq.nodes_list.pop(0)
if pathreq.nodes_list and pathreq.destination == pathreq.nodes_list[-1]: if pathreq.nodes_list and pathreq.destination == pathreq.nodes_list[-1]:
pathreq.loose_list.pop(-1)
pathreq.nodes_list.pop(-1) pathreq.nodes_list.pop(-1)
# Then process user defined constraints with respect to automatic namings # Then process user defined constraints with respect to automatic namings
temp = deepcopy(pathreq) temp = deepcopy(pathreq)
@@ -300,79 +368,57 @@ def correct_xls_route_list(network_filename, network, pathreqlist):
# n_id must not be a transceiver and must not be a fiber (non supported, user # n_id must not be a transceiver and must not be a fiber (non supported, user
# can not enter fiber names in excel) # can not enter fiber names in excel)
if n_id not in trxfibertype: if n_id not in trxfibertype:
# check that n_id is in the node list, if not find a correspondance name nodes_suggestion = find_node_sugestion(n_id, corresp_roadm, corresp_fused, corresp_ila, network)
if n_id in roadmtype + edfatype:
nodes_suggestion = [n_id]
else:
# checks first roadm, fused, and ila in this order, because ila automatic name
# contain roadm names. If it is a fused node, next ila names might be correct
# suggestions, especially if following fibers were splitted and ila names
# created with the name of the fused node
if n_id in corresp_roadm.keys():
nodes_suggestion = corresp_roadm[n_id]
elif n_id in corresp_fused.keys():
nodes_suggestion = corresp_fused[n_id] + corresp_ila[n_id]
elif n_id in corresp_ila.keys():
nodes_suggestion = corresp_ila[n_id]
else:
nodes_suggestion = []
if nodes_suggestion:
try: try:
if len(nodes_suggestion) > 1: if len(nodes_suggestion) > 1:
# if there is more than one suggestion, we need to choose the direction # if there is more than one suggestion, we need to choose the direction
# we rely on the next node provided by the user for this purpose # we rely on the next node provided by the user for this purpose
new_n = next(n for n in nodes_suggestion new_n = next(n for n in nodes_suggestion
if n in next_node.keys() and next_node[n] if n in next_node
in temp.nodes_list[i:] + [pathreq.destination] and and next_node[n] in temp.nodes_list[i:] + [pathreq.destination]
next_node[n] not in temp.nodes_list[:i]) and next_node[n] not in temp.nodes_list[:i])
else: elif len(nodes_suggestion) == 1:
new_n = nodes_suggestion[0] new_n = nodes_suggestion[0]
else:
if temp.loose == 'LOOSE':
# if no matching can be found in the network just ignore this constraint
# if it is a loose constraint
# warns the user that this node is not part of the topology
msg = f'{pathreq.request_id}: Invalid node specified:\n\t\'{n_id}\'' \
+ ', could not use it as constraint, skipped!'
print(msg)
logger.info(msg)
pathreq.nodes_list.remove(n_id)
continue
msg = f'{pathreq.request_id}: Could not find node:\n\t\'{n_id}\' in network' \
+ ' topology. Strict constraint can not be applied.'
raise ServiceError(msg)
if new_n != n_id: if new_n != n_id:
# warns the user when the correct name is used only in verbose mode, # warns the user when the correct name is used only in verbose mode,
# eg 'a' is a roadm and correct name is 'roadm a' or when there was # eg 'a' is a roadm and correct name is 'roadm a' or when there was
# too much ambiguity, 'b' is an ila, its name can be: # too much ambiguity, 'b' is an ila, its name can be:
# Edfa0_fiber (a → b)-xx if next node is c or # "east edfa in b to c", or "west edfa in b to a" if next node is c or
# Edfa0_fiber (c → b)-xx if next node is a # "west edfa in b to c", or "east edfa in b to a" if next node is a
msg = f'{ansi_escapes.yellow}Invalid route node specified:' +\ msg = f'{pathreq.request_id}: Invalid route node specified:' \
f'\n\t\'{n_id}\', replaced with \'{new_n}\'{ansi_escapes.reset}' + f'\n\t\'{n_id}\', replaced with \'{new_n}\''
logger.info(msg) logger.info(msg)
pathreq.nodes_list[pathreq.nodes_list.index(n_id)] = new_n pathreq.nodes_list[pathreq.nodes_list.index(n_id)] = new_n
except StopIteration: except StopIteration:
# shall not come in this case, unless requested direction does not exist # shall not come in this case, unless requested direction does not exist
msg = f'{ansi_escapes.yellow}Invalid route specified {n_id}: could' +\ msg = f'{pathreq.request_id}: Invalid route specified {n_id}: could' \
f' not decide on direction, skipped!.\nPlease add a valid' +\ + ' not decide on direction, skipped!.\nPlease add a valid' \
f' direction in constraints (next neighbour node){ansi_escapes.reset}' + ' direction in constraints (next neighbour node)'
print(msg)
logger.info(msg) logger.info(msg)
pathreq.loose_list.pop(pathreq.nodes_list.index(n_id))
pathreq.nodes_list.remove(n_id) pathreq.nodes_list.remove(n_id)
else: else:
if temp.loose_list[i] == 'LOOSE': if temp.loose == 'LOOSE':
# if no matching can be found in the network just ignore this constraint msg = f'{pathreq.request_id}: Invalid route node specified:\n\t\'{n_id}\'' \
# if it is a loose constraint + ' type is not supported as constraint with xls network input, skipped!'
# warns the user that this node is not part of the topology logger.warning(msg)
msg = f'{ansi_escapes.yellow}Invalid node specified:\n\t\'{n_id}\'' +\
f', could not use it as constraint, skipped!{ansi_escapes.reset}'
print(msg)
logger.info(msg)
pathreq.loose_list.pop(pathreq.nodes_list.index(n_id))
pathreq.nodes_list.remove(n_id) pathreq.nodes_list.remove(n_id)
else: else:
msg = f'{ansi_escapes.red}Could not find node:\n\t\'{n_id}\' in network' +\ msg = f'{pathreq.request_id}: Invalid route node specified \n\t\'{n_id}\'' \
f' topology. Strict constraint can not be applied.{ansi_escapes.reset}' + ' type is not supported as constraint with xls network input,' \
logger.critical(msg) + ', Strict constraint can not be applied.'
raise ServiceError(msg)
else:
if temp.loose_list[i] == 'LOOSE':
print(f'{ansi_escapes.yellow}Invalid route node specified:\n\t\'{n_id}\'' +
f' type is not supported as constraint with xls network input,' +
f' skipped!{ansi_escapes.reset}')
pathreq.loose_list.pop(pathreq.nodes_list.index(n_id))
pathreq.nodes_list.remove(n_id)
else:
msg = f'{ansi_escapes.red}Invalid route node specified \n\t\'{n_id}\'' +\
f' type is not supported as constraint with xls network input,' +\
f', Strict constraint can not be applied.{ansi_escapes.reset}'
logger.critical(msg)
raise ServiceError(msg) raise ServiceError(msg)
return pathreqlist return pathreqlist

248
gnpy/tools/worker_utils.py Normal file
View File

@@ -0,0 +1,248 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
gnpy.tools.worker_utils
=======================
Common code for CLI examples and API
'''
import logging
from copy import deepcopy
from typing import Union, List, Tuple
from numpy import linspace
from networkx import DiGraph
from gnpy.core.utils import automatic_nch, watt2dbm, dbm2watt, pretty_summary_print, per_label_average
from gnpy.core.equipment import trx_mode_params
from gnpy.core.network import add_missing_elements_in_network, design_network
from gnpy.core import exceptions
from gnpy.core.info import SpectralInformation
from gnpy.topology.spectrum_assignment import build_oms_list, pth_assign_spectrum, OMS
from gnpy.topology.request import correct_json_route_list, deduplicate_disjunctions, requests_aggregation, \
compute_path_dsjctn, compute_path_with_disjunction, ResultElement, PathRequest, Disjunction, \
compute_constrained_path, propagate
from gnpy.tools.json_io import requests_from_json, disjunctions_from_json
logger = logging.getLogger(__name__)
def designed_network(equipment: dict, network: DiGraph, source: str = None, destination: str = None,
nodes_list: List[str] = None, loose_list: List[str] = None,
initial_spectrum: dict = None, no_insert_edfas: bool = False,
args_power: Union[str, float, int] = None,
service_req: PathRequest = None) -> Tuple[DiGraph, PathRequest, PathRequest]:
"""Build the reference channels based on inputs and design the network for this reference channel, and build the
channel to be propagated for the single transmission script.
Reference channel (target input power in spans, nb of channels, transceiver output power) is built using
equipment['SI'] information. If indicated, with target input power in spans is updated with args_power.
Channel to be propagated is using the same channel reference, except if different settings are provided
with service_req and initial_spectrum. The service to be propagated uses specified source, destination
and list nodes_list of include nodes constraint except if the service_req is specified.
Args:
- equipment: a dictionary containing equipment information.
- network: a directed graph representing the initial network.
- no_insert_edfas: a boolean indicating whether to insert EDFAs in the network.
- args_power: the power to be used for the network design.
- service_req: the service request the user wants to propagate.
- source: the source node for the channel to be propagated if no service_req is specified.
- destination: the destination node for the channel to be propagated if no service_req is specified.
- nodes_list: a list of nodes to be included ifor the channel to be propagated if no service_req is specified.
- loose_list: a list of loose nodes to be included in the network design.
- initial_spectrum: a dictionary representing the initial spectrum to propagate.
Returns:
- The designed network.
- The channel to propagate.
- The reference channel used for the design.
"""
if loose_list is None:
loose_list = []
if nodes_list is None:
nodes_list = []
if not no_insert_edfas:
add_missing_elements_in_network(network, equipment)
if not nodes_list:
if destination:
nodes_list = [destination]
loose_list = ['STRICT']
else:
nodes_list = []
loose_list = []
params = {
'request_id': 'reference',
'trx_type': '',
'trx_mode': '',
'source': source,
'destination': destination,
'bidir': False,
'nodes_list': nodes_list,
'loose_list': loose_list,
'format': '',
'path_bandwidth': 0,
'effective_freq_slot': None,
'nb_channel': automatic_nch(equipment['SI']['default'].f_min, equipment['SI']['default'].f_max,
equipment['SI']['default'].spacing),
'power': dbm2watt(equipment['SI']['default'].power_dbm),
'tx_power': None
}
params['tx_power'] = dbm2watt(equipment['SI']['default'].power_dbm)
if equipment['SI']['default'].tx_power_dbm is not None:
# use SI tx_power if present
params['tx_power'] = dbm2watt(equipment['SI']['default'].tx_power_dbm)
trx_params = trx_mode_params(equipment)
params.update(trx_params)
# use args_power instead of si
if args_power:
params['power'] = dbm2watt(float(args_power))
if equipment['SI']['default'].tx_power_dbm is None:
params['tx_power'] = params['power']
# use si as reference channel
reference_channel = PathRequest(**params)
# temporary till multiband design feat is available: do not design for L band
reference_channel.nb_channel = min(params['nb_channel'], automatic_nch(191.2e12, 196.0e12, params['spacing']))
if service_req:
# use service_req as reference channel with si tx_power if service_req tx_power is None
if service_req.tx_power is None:
service_req.tx_power = params['tx_power']
reference_channel = service_req
design_network(reference_channel, network, equipment, set_connector_losses=True, verbose=True)
if initial_spectrum:
params['nb_channel'] = len(initial_spectrum)
req = PathRequest(**params)
if service_req:
req = service_req
req.initial_spectrum = initial_spectrum
return network, req, reference_channel
def check_request_path_ids(rqs: List[PathRequest]):
"""check that request ids are unique. Non unique ids, may
mess the computation: better to stop the computation
"""
all_ids = [r.request_id for r in rqs]
if len(all_ids) != len(set(all_ids)):
for item in list(set(all_ids)):
all_ids.remove(item)
msg = f'Requests id {all_ids} are not unique'
logger.error(msg)
raise ValueError(msg)
def planning(network: DiGraph, equipment: dict, data: dict, redesign: bool = False) \
-> Tuple[List[OMS], list, list, List[PathRequest], List[Disjunction], List[ResultElement]]:
"""Run planning
data contain the service dict from json
redesign True means that network is redesign using each request as reference channel
when False it means that the design is made once and successive propagation use the settings
computed with this design.
"""
oms_list = build_oms_list(network, equipment)
rqs = requests_from_json(data, equipment)
# check that request ids are unique.
check_request_path_ids(rqs)
rqs = correct_json_route_list(network, rqs)
dsjn = disjunctions_from_json(data)
logger.info('List of disjunctions:\n%s', dsjn)
# need to warn or correct in case of wrong disjunction form
# disjunction must not be repeated with same or different ids
dsjn = deduplicate_disjunctions(dsjn)
logger.info('Aggregating similar requests')
rqs, dsjn = requests_aggregation(rqs, dsjn)
logger.info('The following services have been requested:\n%s', rqs)
# logger.info('Computing all paths with constraints for request %s', optical_path_result_id)
pths = compute_path_dsjctn(network, equipment, rqs, dsjn)
logger.info('Propagating on selected path')
propagatedpths, reversed_pths, reversed_propagatedpths = \
compute_path_with_disjunction(network, equipment, rqs, pths, redesign=redesign)
# Note that deepcopy used in compute_path_with_disjunction returns
# a list of nodes which are not belonging to network (they are copies of the node objects).
# so there can not be propagation on these nodes.
# Allowed user_policy are first_fit and 2partition
pth_assign_spectrum(pths, rqs, oms_list, reversed_pths)
for i, rq in enumerate(rqs):
if hasattr(rq, 'OSNR') and rq.OSNR:
rq.osnr_with_sys_margin = rq.OSNR + equipment["SI"]["default"].sys_margins
# assumes that list of rqs and list of propgatedpths have same order
result = [ResultElement(rq, pth, rpth) for rq, pth, rpth in zip(rqs, propagatedpths, reversed_propagatedpths)]
return oms_list, propagatedpths, reversed_propagatedpths, rqs, dsjn, result
def transmission_simulation(equipment: dict, network: DiGraph, req: PathRequest, ref_req: PathRequest) \
-> Tuple[list, List[list], List[Union[float, int]], SpectralInformation]:
"""Run simulation and returms the propagation result for each power sweep iteration.
Args:
- equipment: a dictionary containing equipment information.
- network: network after being designed using ref_req. Any missing information (amp gain or delta_p) must have
been filled using ref_req as reference channel previuos to this function.
- req: channel to be propagated.
- ref_req: the reference channel used for filling missing information in the network.
In case of power sweep, network is redesigned using ref_req whose target input power in span is
updated with the power step.
Returns a tuple containing:
- path: last propagated path. Power sweep is not possible with gain mode (as gain targets are used)
- propagations: list of propagated path for each power iteration
- powers_dbm: list of power used for the power sweep
- infos: last propagated spectral information
"""
power_mode = equipment['Span']['default'].power_mode
logger.info('Power mode is set to %s=> it can be modified in eqpt_config.json - Span', power_mode)
# initial network is designed using ref_req. that is that any missing information (amp gain or delta_p) is filled
# using this ref_req.power, previous to any sweep requested later on.
pref_ch_db = watt2dbm(ref_req.power)
p_ch_db = watt2dbm(req.power)
path = compute_constrained_path(network, req)
power_range = [0]
if power_mode:
# power cannot be changed in gain mode
try:
p_start, p_stop, p_step = equipment['SI']['default'].power_range_db
p_num = abs(int(round((p_stop - p_start) / p_step))) + 1 if p_step != 0 else 1
power_range = list(linspace(p_start, p_stop, p_num))
except TypeError as e:
msg = 'invalid power range definition in eqpt_config, should be power_range_db: [lower, upper, step]'
logger.error(msg)
raise exceptions.EquipmentConfigError(msg) from e
logger.info('Now propagating between %s and %s', req.source, req.destination)
propagations = []
powers_dbm = []
for dp_db in power_range:
ref_req.power = dbm2watt(pref_ch_db + dp_db)
req.power = dbm2watt(p_ch_db + dp_db)
# Power sweep is made to evaluate different span input powers, so redesign is mandatory for each power,
# but no need to redesign if there are no power sweep
if len(power_range) > 1:
design_network(ref_req, network.subgraph(path), equipment, set_connector_losses=False, verbose=False)
infos = propagate(path, req, equipment)
propagations.append(deepcopy(path))
powers_dbm.append(pref_ch_db + dp_db)
logger.info('\nChannels propagating: (Input optical power deviation in span = '
+ f'{pretty_summary_print(per_label_average(infos.delta_pdb_per_channel, infos.label))}dB,\n'
+ ' spacing = '
+ f'{pretty_summary_print(per_label_average(infos.slot_width * 1e-9, infos.label))}GHz,\n'
+ ' transceiver output power = '
+ f'{pretty_summary_print(per_label_average(watt2dbm(infos.tx_power), infos.label))}dBm,\n'
+ f' nb_channels = {infos.number_of_channels})')
if not power_mode:
logger.info('\n\tPropagating using gain targets: Input optical power deviation in span ignored')
return path, propagations, powers_dbm, infos

View File

@@ -1,3 +1,3 @@
''' """
Tracking :py:mod:`.request` for spectrum and their :py:mod:`.spectrum_assignment`. Tracking :py:mod:`.request` for spectrum and their :py:mod:`.spectrum_assignment`.
''' """

View File

@@ -16,34 +16,36 @@ See: draft-ietf-teas-yang-path-computation-01.txt
""" """
from collections import namedtuple, OrderedDict from collections import namedtuple, OrderedDict
from typing import List
from logging import getLogger from logging import getLogger
from networkx import (dijkstra_path, NetworkXNoPath, from networkx import (dijkstra_path, NetworkXNoPath,
all_simple_paths, shortest_simple_paths) all_simple_paths, shortest_simple_paths)
from networkx.utils import pairwise from networkx.utils import pairwise
from numpy import mean from numpy import mean, argmin
from gnpy.core.elements import Transceiver, Roadm
from gnpy.core.utils import lin2db from gnpy.core.elements import Transceiver, Roadm, Edfa, Multiband_amplifier
from gnpy.core.info import create_input_spectral_information from gnpy.core.utils import lin2db, unique_ordered, find_common_range
from gnpy.core.info import create_input_spectral_information, carriers_to_spectral_information, \
demuxed_spectral_information, muxed_spectral_information, SpectralInformation
from gnpy.core import network as network_module
from gnpy.core.exceptions import ServiceError, DisjunctionError from gnpy.core.exceptions import ServiceError, DisjunctionError
import gnpy.core.ansi_escapes as ansi_escapes
from copy import deepcopy from copy import deepcopy
from csv import writer from csv import writer
from math import ceil from math import ceil
LOGGER = getLogger(__name__) LOGGER = getLogger(__name__)
RequestParams = namedtuple('RequestParams', 'request_id source destination bidir trx_type' + RequestParams = namedtuple('RequestParams', 'request_id source destination bidir trx_type'
' trx_mode nodes_list loose_list spacing power nb_channel f_min' + ' trx_mode nodes_list loose_list spacing power nb_channel f_min'
' f_max format baud_rate OSNR bit_rate roll_off tx_osnr' + ' f_max format baud_rate OSNR penalties bit_rate'
' min_spacing cost path_bandwidth') ' roll_off tx_osnr min_spacing cost path_bandwidth effective_freq_slot'
DisjunctionParams = namedtuple('DisjunctionParams', 'disjunction_id relaxable link' + ' equalization_offset_db, tx_power')
'_diverse node_diverse disjunctions_req') DisjunctionParams = namedtuple('DisjunctionParams', 'disjunction_id relaxable link_diverse'
' node_diverse disjunctions_req')
class PathRequest: class PathRequest:
""" the class that contains all attributes related to a request """the class that contains all attributes related to a request"""
"""
def __init__(self, *args, **params): def __init__(self, *args, **params):
params = RequestParams(**params) params = RequestParams(**params)
self.request_id = params.request_id self.request_id = params.request_id
@@ -62,12 +64,19 @@ class PathRequest:
self.f_max = params.f_max self.f_max = params.f_max
self.format = params.format self.format = params.format
self.OSNR = params.OSNR self.OSNR = params.OSNR
self.penalties = params.penalties
self.bit_rate = params.bit_rate self.bit_rate = params.bit_rate
self.roll_off = params.roll_off self.roll_off = params.roll_off
self.tx_osnr = params.tx_osnr self.tx_osnr = params.tx_osnr
self.tx_power = params.tx_power
self.min_spacing = params.min_spacing self.min_spacing = params.min_spacing
self.cost = params.cost self.cost = params.cost
self.path_bandwidth = params.path_bandwidth self.path_bandwidth = params.path_bandwidth
if params.effective_freq_slot is not None:
self.N = [s['N'] for s in params.effective_freq_slot]
self.M = [s['M'] for s in params.effective_freq_slot]
self.initial_spectrum = None
self.offset_db = params.equalization_offset_db
def __str__(self): def __str__(self):
return '\n\t'.join([f'{type(self).__name__} {self.request_id}', return '\n\t'.join([f'{type(self).__name__} {self.request_id}',
@@ -75,7 +84,7 @@ class PathRequest:
f'destination: {self.destination}']) f'destination: {self.destination}'])
def __repr__(self): def __repr__(self):
if self.baud_rate is not None: if self.baud_rate is not None and self.bit_rate is not None:
temp = self.baud_rate * 1e-9 temp = self.baud_rate * 1e-9
temp2 = self.bit_rate * 1e-9 temp2 = self.bit_rate * 1e-9
else: else:
@@ -91,6 +100,7 @@ class PathRequest:
f'bit_rate:\t{temp2} Gb/s', f'bit_rate:\t{temp2} Gb/s',
f'spacing:\t{self.spacing * 1e-9} GHz', f'spacing:\t{self.spacing * 1e-9} GHz',
f'power: \t{round(lin2db(self.power) + 30, 2)} dBm', f'power: \t{round(lin2db(self.power) + 30, 2)} dBm',
f'tx_power_dbm: \t{round(lin2db(self.tx_power) + 30, 2)} dBm',
f'nb channels: \t{self.nb_channel}', f'nb channels: \t{self.nb_channel}',
f'path_bandwidth: \t{round(self.path_bandwidth * 1e-9, 2)} Gbit/s', f'path_bandwidth: \t{round(self.path_bandwidth * 1e-9, 2)} Gbit/s',
f'nodes-list:\t{self.nodes_list}', f'nodes-list:\t{self.nodes_list}',
@@ -99,8 +109,7 @@ class PathRequest:
class Disjunction: class Disjunction:
""" the class that contains all attributes related to disjunction constraints """the class that contains all attributes related to disjunction constraints"""
"""
def __init__(self, *args, **params): def __init__(self, *args, **params):
params = DisjunctionParams(**params) params = DisjunctionParams(**params)
@@ -129,7 +138,7 @@ BLOCKING_NOPATH = ['NO_PATH', 'NO_PATH_WITH_CONSTRAINT',
'NO_FEASIBLE_BAUDRATE_WITH_SPACING', 'NO_FEASIBLE_BAUDRATE_WITH_SPACING',
'NO_COMPUTED_SNR'] 'NO_COMPUTED_SNR']
BLOCKING_NOMODE = ['NO_FEASIBLE_MODE', 'MODE_NOT_FEASIBLE'] BLOCKING_NOMODE = ['NO_FEASIBLE_MODE', 'MODE_NOT_FEASIBLE']
BLOCKING_NOSPECTRUM = 'NO_SPECTRUM' BLOCKING_NOSPECTRUM = ['NO_SPECTRUM', 'NOT_ENOUGH_RESERVED_SPECTRUM']
class ResultElement: class ResultElement:
@@ -145,8 +154,7 @@ class ResultElement:
@property @property
def detailed_path_json(self): def detailed_path_json(self):
""" a function that builds path object for normal and blocking cases """a function that builds path object for normal and blocking cases"""
"""
index = 0 index = 0
pro_list = [] pro_list = []
for element in self.computed_path: for element in self.computed_path:
@@ -162,24 +170,30 @@ class ResultElement:
} }
pro_list.append(temp) pro_list.append(temp)
index += 1 index += 1
if self.path_request.M > 0: if not hasattr(self.path_request, 'blocking_reason'):
# M and N values should not be None at this point
if self.path_request.M is None or self.path_request.N is None:
raise ServiceError('request {self.path_id} should have positive non null n and m values.')
temp = { temp = {
'path-route-object': { 'path-route-object': {
'index': index, 'index': index,
"label-hop": { "label-hop": [{
"N": self.path_request.N, "N": n,
"M": self.path_request.M "M": m
}, } for n, m in zip(self.path_request.N, self.path_request.M)],
} }
} }
pro_list.append(temp) pro_list.append(temp)
index += 1 index += 1
elif self.path_request.M == 0 and hasattr(self.path_request, 'blocking_reason'):
# if the path is blocked due to spectrum, no label object is created, but
# the json response includes a detailed path for user infromation.
pass
else: else:
raise ServiceError('request {self.path_id} should have positive path bandwidth value.') # if the path is blocked, no label object is created, but
# the json response includes a detailed path for user information.
# M and N values should be None at this point
if self.path_request.M is not None or self.path_request.N is not None:
raise ServiceError('request {self.path_id} should not have label M and N values at this point.')
if isinstance(element, Transceiver): if isinstance(element, Transceiver):
temp = { temp = {
'path-route-object': { 'path-route-object': {
@@ -196,11 +210,9 @@ class ResultElement:
@property @property
def path_properties(self): def path_properties(self):
""" a function that returns the path properties (metrics, crossed elements) into a dict """a function that returns the path properties (metrics, crossed elements) into a dict"""
"""
def path_metric(pth, req): def path_metric(pth, req):
""" creates the metrics dictionary """creates the metrics dictionary"""
"""
return [ return [
{ {
'metric-type': 'SNR-bandwidth', 'metric-type': 'SNR-bandwidth',
@@ -242,8 +254,7 @@ class ResultElement:
@property @property
def pathresult(self): def pathresult(self):
""" create the result dictionnary (response for a request) """create the result dictionnary (response for a request)"""
"""
try: try:
if self.path_request.blocking_reason in BLOCKING_NOPATH: if self.path_request.blocking_reason in BLOCKING_NOPATH:
response = { response = {
@@ -281,7 +292,6 @@ def compute_constrained_path(network, req):
# been corrected and harmonized before # been corrected and harmonized before
msg = (f'Request {req.request_id} malformed list of nodes: last node should ' msg = (f'Request {req.request_id} malformed list of nodes: last node should '
'be destination trx') 'be destination trx')
LOGGER.critical(msg)
raise ValueError() raise ValueError()
trx = [n for n in network if isinstance(n, Transceiver)] trx = [n for n in network if isinstance(n, Transceiver)]
@@ -291,15 +301,16 @@ def compute_constrained_path(network, req):
nodes_list = [] nodes_list = []
for node in req.nodes_list[:-1]: for node in req.nodes_list[:-1]:
nodes_list.append(next(el for el in network if el.uid == node)) nodes_list.append(next(el for el in network if el.uid == node))
total_path = explicit_path(nodes_list, source, destination, network)
if total_path is not None:
return total_path
try: try:
path_generator = shortest_simple_paths(network, source, destination, weight='weight') path_generator = shortest_simple_paths(network, source, destination, weight='weight')
total_path = next(path for path in path_generator if ispart(nodes_list, path)) total_path = next(path for path in path_generator if ispart(nodes_list, path))
except NetworkXNoPath: except NetworkXNoPath:
msg = (f'{ansi_escapes.yellow}Request {req.request_id} could not find a path from' msg = (f'Request {req.request_id} could not find a path from'
f' {source.uid} to node: {destination.uid} in network topology{ansi_escapes.reset}') f' {source.uid} to node: {destination.uid} in network topology')
LOGGER.critical(msg) LOGGER.critical(msg)
print(msg)
req.blocking_reason = 'NO_PATH' req.blocking_reason = 'NO_PATH'
total_path = [] total_path = []
except StopIteration: except StopIteration:
@@ -308,80 +319,117 @@ def compute_constrained_path(network, req):
# last node which is the transceiver) # last node which is the transceiver)
# if all nodes i n node_list are LOOSE constraint, skip the constraints and find # if all nodes i n node_list are LOOSE constraint, skip the constraints and find
# a path w/o constraints, else there is no possible path # a path w/o constraints, else there is no possible path
print(f'{ansi_escapes.yellow}Request {req.request_id} could not find a path crossing ' LOGGER.warning(f'Request {req.request_id} could not find a path crossing '
f'{[el.uid for el in nodes_list[:-1]]} in network topology{ansi_escapes.reset}') f'{[el.uid for el in nodes_list[:-1]]} in network topology')
if 'STRICT' not in req.loose_list[:-1]: if 'STRICT' not in req.loose_list[:-1]:
msg = (f'{ansi_escapes.yellow}Request {req.request_id} could not find a path with user_' msg = (f'Request {req.request_id} could not find a path with user_'
f'include node constraints{ansi_escapes.reset}') f'include node constraints. Constraint ignored')
LOGGER.info(msg) LOGGER.warning(msg)
print(f'constraint ignored')
total_path = dijkstra_path(network, source, destination, weight='weight') total_path = dijkstra_path(network, source, destination, weight='weight')
else: else:
# one STRICT makes the whole list STRICT # one STRICT makes the whole list STRICT
msg = (f'{ansi_escapes.yellow}Request {req.request_id} could not find a path with user ' msg = (f'Request {req.request_id} could not find a path with user '
f'include node constraints.\nNo path computed{ansi_escapes.reset}') f'include node constraints.\nNo path computed')
LOGGER.critical(msg) LOGGER.critical(msg)
print(msg)
req.blocking_reason = 'NO_PATH_WITH_CONSTRAINT' req.blocking_reason = 'NO_PATH_WITH_CONSTRAINT'
total_path = [] total_path = []
return total_path return total_path
def filter_si(path: list, equipment: dict, si: SpectralInformation) -> SpectralInformation:
"""Filter spectral information based on the amplifiers common range"""
# First retrieve f_min, f_max spectrum according to amplifiers' spectrum on the path
common_range = find_elements_common_range(path, equipment)
# filter out frequencies that should not be created
filtered_si = []
for band in common_range:
temp = demuxed_spectral_information(si, band)
if temp:
filtered_si.append(temp)
if not filtered_si:
raise ValueError('Defined propagation band does not match amplifiers band.')
return muxed_spectral_information(filtered_si)
def propagate(path, req, equipment): def propagate(path, req, equipment):
"""propagates signals in each element according to initial spectrum set by user
Spectrum is specified in request through f_min, f_max and spacing, or initial_spectrum
and amps frequency band on the path is used to filter out frequencies"""
# generates spectrum based on request
if req.initial_spectrum is not None:
si = carriers_to_spectral_information(initial_spectrum=req.initial_spectrum, power=req.power)
else:
si = create_input_spectral_information( si = create_input_spectral_information(
req.f_min, req.f_max, req.roll_off, req.baud_rate, f_min=req.f_min, f_max=req.f_max, roll_off=req.roll_off, baud_rate=req.baud_rate,
req.power, req.spacing) spacing=req.spacing, tx_osnr=req.tx_osnr, tx_power=req.tx_power, delta_pdb=req.offset_db)
# filter out frequencies that should not be created
si = filter_si(path, equipment, si)
roadm_osnr = []
for i, el in enumerate(path): for i, el in enumerate(path):
if isinstance(el, Roadm): if isinstance(el, Roadm):
si = el(si, degree=path[i+1].uid) si = el(si, degree=path[i + 1].uid, from_degree=path[i - 1].uid)
roadm_osnr.append(el.get_impairment('roadm-osnr', si.frequency,
from_degree=path[i - 1].uid, degree=path[i + 1].uid))
else: else:
si = el(si) si = el(si)
path[0].update_snr(req.tx_osnr) path[0].update_snr(si.tx_osnr)
if any(isinstance(el, Roadm) for el in path): path[0].calc_penalties(req.penalties)
path[-1].update_snr(req.tx_osnr, equipment['Roadm']['default'].add_drop_osnr) roadm_osnr.append(si.tx_osnr)
else: path[-1].update_snr(*roadm_osnr)
path[-1].update_snr(req.tx_osnr) path[-1].calc_penalties(req.penalties)
return si return si
def propagate_and_optimize_mode(path, req, equipment): def propagate_and_optimize_mode(path, req, equipment):
# if mode is unknown : loops on the modes starting from the highest baudrate fiting in the # if mode is unknown : loops on the modes starting from the highest baudrate fiting in the
# step 1: create an ordered list of modes based on baudrate # step 1: create an ordered list of modes based on baudrate and power offset
baudrate_to_explore = list(set([this_mode['baud_rate'] # order higher baudrate with higher power offset first
baudrate_offset_to_explore = list(set([(this_mode['baud_rate'], this_mode['equalization_offset_db'])
for this_mode in equipment['Transceiver'][req.tsp].mode for this_mode in equipment['Transceiver'][req.tsp].mode
if float(this_mode['min_spacing']) <= req.spacing])) if float(this_mode['min_spacing']) <= req.spacing]))
# TODO be carefull on limits cases if spacing very close to req spacing eg 50.001 50.000 # TODO be carefull on limits cases if spacing very close to req spacing eg 50.001 50.000
baudrate_to_explore = sorted(baudrate_to_explore, reverse=True) baudrate_offset_to_explore = sorted(baudrate_offset_to_explore, reverse=True)
if baudrate_to_explore: if baudrate_offset_to_explore:
# at least 1 baudrate can be tested wrt spacing # at least 1 baudrate can be tested wrt spacing
for this_br in baudrate_to_explore: for (this_br, this_offset) in baudrate_offset_to_explore:
modes_to_explore = [this_mode for this_mode in equipment['Transceiver'][req.tsp].mode modes_to_explore = [this_mode for this_mode in equipment['Transceiver'][req.tsp].mode
if this_mode['baud_rate'] == this_br and if this_mode['baud_rate'] == this_br
float(this_mode['min_spacing']) <= req.spacing] and float(this_mode['min_spacing']) <= req.spacing]
modes_to_explore = sorted(modes_to_explore, modes_to_explore = sorted(modes_to_explore,
key=lambda x: x['bit_rate'], reverse=True) key=lambda x: (x['bit_rate'], x['equalization_offset_db']), reverse=True)
# print(modes_to_explore)
# step2: computes propagation for each baudrate: stop and select the first that passes # step2: computes propagation for each baudrate: stop and select the first that passes
# TODO: the case of roll of is not included: for now use SI one # TODO: the case of roll off is not included: for now use SI one
# TODO: if the loop in mode optimization does not have a feasible path, then bugs # TODO: if the loop in mode optimization does not have a feasible path, then bugs
spc_info = create_input_spectral_information(req.f_min, req.f_max, if req.initial_spectrum is not None:
equipment['SI']['default'].roll_off, # this case is not yet handled: spectrum can not be defined for the path-request-run function
this_br, req.power, req.spacing) # and this function is only called in this case. so coming here should not be considered yet.
msg = f'Request: {req.request_id} contains a unexpected initial_spectrum.'
raise ServiceError(msg)
spc_info = create_input_spectral_information(f_min=req.f_min, f_max=req.f_max,
roll_off=equipment['SI']['default'].roll_off,
baud_rate=this_br, spacing=req.spacing,
delta_pdb=this_offset, tx_osnr=req.tx_osnr,
tx_power=req.tx_power)
spc_info = filter_si(path, equipment, spc_info)
roadm_osnr = []
for i, el in enumerate(path): for i, el in enumerate(path):
if isinstance(el, Roadm): if isinstance(el, Roadm):
spc_info = el(spc_info, degree=path[i+1].uid) spc_info = el(spc_info, degree=path[i + 1].uid, from_degree=path[i - 1].uid)
roadm_osnr.append(el.get_impairment('roadm-osnr', spc_info.frequency,
from_degree=path[i - 1].uid, degree=path[i + 1].uid))
else: else:
spc_info = el(spc_info) spc_info = el(spc_info)
for this_mode in modes_to_explore: for this_mode in modes_to_explore:
if path[-1].snr is not None: if path[-1].snr is not None:
path[0].update_snr(this_mode['tx_osnr']) path[0].update_snr(this_mode['tx_osnr'])
if any(isinstance(el, Roadm) for el in path): path[0].calc_penalties(this_mode['penalties'])
path[-1].update_snr(this_mode['tx_osnr'], equipment['Roadm']['default'].add_drop_osnr) roadm_osnr.append(this_mode['tx_osnr'])
else: path[-1].update_snr(*roadm_osnr)
path[-1].update_snr(this_mode['tx_osnr']) # remove the tx_osnr from roadm_osnr list for the next iteration
if round(min(path[-1].snr + lin2db(this_br / (12.5e9))), 2) \ del roadm_osnr[-1]
path[-1].calc_penalties(this_mode['penalties'])
if round(min(path[-1].snr_01nm - path[-1].total_penalty), 2) \
> this_mode['OSNR'] + equipment['SI']['default'].sys_margins: > this_mode['OSNR'] + equipment['SI']['default'].sys_margins:
return path, this_mode return path, this_mode
else: else:
@@ -389,27 +437,23 @@ def propagate_and_optimize_mode(path, req, equipment):
else: else:
req.blocking_reason = 'NO_COMPUTED_SNR' req.blocking_reason = 'NO_COMPUTED_SNR'
return path, None return path, None
# only get to this point if no baudrate/mode satisfies OSNR requirement # only get to this point if no baudrate/mode satisfies OSNR requirement
# returns the last propagated path and mode # returns the last propagated path and mode
msg = f'\tWarning! Request {req.request_id}: no mode satisfies path SNR requirement.\n' msg = f'\tWarning! Request {req.request_id}: no mode satisfies path SNR requirement.\n'
print(msg) LOGGER.warning(msg)
LOGGER.info(msg)
req.blocking_reason = 'NO_FEASIBLE_MODE' req.blocking_reason = 'NO_FEASIBLE_MODE'
return path, last_explored_mode return path, last_explored_mode
else: else:
# no baudrate satisfying spacing # no baudrate satisfying spacing
msg = f'\tWarning! Request {req.request_id}: no baudrate satisfies spacing requirement.\n' msg = f'\tWarning! Request {req.request_id}: no baudrate satisfies spacing requirement.\n'
print(msg) LOGGER.warning(msg)
LOGGER.info(msg)
req.blocking_reason = 'NO_FEASIBLE_BAUDRATE_WITH_SPACING' req.blocking_reason = 'NO_FEASIBLE_BAUDRATE_WITH_SPACING'
return [], None return [], None
def jsontopath_metric(path_metric): def jsontopath_metric(path_metric):
""" a functions that reads resulting metric from json string """a functions that reads resulting metric from json string"""
"""
output_snr = next(e['accumulative-value'] output_snr = next(e['accumulative-value']
for e in path_metric if e['metric-type'] == 'SNR-0.1nm') for e in path_metric if e['metric-type'] == 'SNR-0.1nm')
output_snrbandwidth = next(e['accumulative-value'] output_snrbandwidth = next(e['accumulative-value']
@@ -427,9 +471,7 @@ def jsontopath_metric(path_metric):
def jsontoparams(my_p, tsp, mode, equipment): def jsontoparams(my_p, tsp, mode, equipment):
""" a function that derives optical params from transponder type and mode """a function that derives optical params from transponder type and mode supports the no mode case"""
supports the no mode case
"""
temp = [] temp = []
for elem in my_p['path-properties']['path-route-objects']: for elem in my_p['path-properties']['path-route-objects']:
if 'num-unnum-hop' in elem['path-route-object']: if 'num-unnum-hop' in elem['path-route-object']:
@@ -439,8 +481,8 @@ def jsontoparams(my_p, tsp, mode, equipment):
temp2 = [] temp2 = []
for elem in my_p['path-properties']['path-route-objects']: for elem in my_p['path-properties']['path-route-objects']:
if 'label-hop' in elem['path-route-object'].keys(): if 'label-hop' in elem['path-route-object'].keys():
temp2.append(f'{elem["path-route-object"]["label-hop"]["N"]}, ' + temp2.append(f'{[e["N"] for e in elem["path-route-object"]["label-hop"]]}, '
f'{elem["path-route-object"]["label-hop"]["M"]}') + f'{[e["M"] for e in elem["path-route-object"]["label-hop"]]}')
# OrderedDict.fromkeys returns the unique set of strings. # OrderedDict.fromkeys returns the unique set of strings.
# TODO: if spectrum changes along the path, we should be able to give the segments # TODO: if spectrum changes along the path, we should be able to give the segments
# eg for regeneration case # eg for regeneration case
@@ -696,8 +738,8 @@ def compute_path_dsjctn(network, equipment, pathreqlist, disjunctions_list):
# in each loop, dpath is updated with a path for rq that satisfies # in each loop, dpath is updated with a path for rq that satisfies
# disjunction with each path in dpath # disjunction with each path in dpath
# for example, assume set of requests in the vector (disjunction_list) is {rq1,rq2, rq3} # for example, assume set of requests in the vector (disjunction_list) is {rq1,rq2, rq3}
# rq1 p1: abfhg # rq1 p1: aefhg
# p2: aefhg # p2: abfhg
# p3: abcg # p3: abcg
# rq2 p8: bf # rq2 p8: bf
# rq3 p4: abcgh # rq3 p4: abcgh
@@ -714,6 +756,7 @@ def compute_path_dsjctn(network, equipment, pathreqlist, disjunctions_list):
# after second loop: # after second loop:
# dpath = [ p3 p8 p6 ] # dpath = [ p3 p8 p6 ]
# since p1 and p4 are not disjoint # since p1 and p4 are not disjoint
# p1 and p6 are not disjoint
# p1 and p7 are not disjoint # p1 and p7 are not disjoint
# p3 and p4 are not disjoint # p3 and p4 are not disjoint
# p3 and p7 are not disjoint # p3 and p7 are not disjoint
@@ -737,7 +780,6 @@ def compute_path_dsjctn(network, equipment, pathreqlist, disjunctions_list):
temp.append(temp2) temp.append(temp2)
# print(f' coucou {elem1}: \t{temp}') # print(f' coucou {elem1}: \t{temp}')
dpath = temp dpath = temp
# print(dpath)
candidates[dis.disjunction_id] = dpath candidates[dis.disjunction_id] = dpath
# for i in disjunctions_list: # for i in disjunctions_list:
@@ -788,33 +830,34 @@ def compute_path_dsjctn(network, equipment, pathreqlist, disjunctions_list):
# TODO: keep a version without the loose constraint # TODO: keep a version without the loose constraint
for this_d in disjunctions_list: for this_d in disjunctions_list:
temp = [] temp = []
alternatetemp = []
for j, sol in enumerate(candidates[this_d.disjunction_id]): for j, sol in enumerate(candidates[this_d.disjunction_id]):
testispartok = True testispartok = True
testispartnokloose = True
for pth in sol: for pth in sol:
# print(f'test {allpaths[id(pth)].req.request_id}') # print(f'test {allpaths[id(pth)].req.request_id}')
# print(f'length of route {len(allpaths[id(pth)].req.nodes_list)}') # print(f'length of route {len(allpaths[id(pth)].req.nodes_list)}')
if allpaths[id(pth)].req.nodes_list: if allpaths[id(pth)].req.nodes_list:
# if pth does not containt the ordered list node, remove sol from the candidate # if any pth from sol does not contain the ordered list node,
# except if this was the last solution: then check if the constraint is loose # remove sol from the candidate, except if constraint was loose:
# or not # then keep sol as an alternate solution
if not ispart(allpaths[id(pth)].req.nodes_list, pth): if not ispart(allpaths[id(pth)].req.nodes_list, pth):
# print(f'nb of solutions {len(temp)}')
if j < len(candidates[this_d.disjunction_id]) - 1:
msg = f'removing {sol}'
LOGGER.info(msg)
testispartok = False
# break
else:
if 'LOOSE' in allpaths[id(pth)].req.loose_list:
LOGGER.info(f'Could not apply route constraint' +
f'{allpaths[id(pth)].req.nodes_list} on request' +
f' {allpaths[id(pth)].req.request_id}')
else:
LOGGER.info(f'removing last solution from candidate paths\n{sol}')
testispartok = False testispartok = False
if 'STRICT' in allpaths[id(pth)].req.loose_list:
LOGGER.debug(f'removing solution from candidate paths\n{pth}')
testispartnokloose = False
break
if testispartok: if testispartok:
temp.append(sol) temp.append(sol)
elif testispartnokloose:
LOGGER.debug(f'Adding solution as alternate solution not satisfying constraint\n{pth}')
alternatetemp.append(sol)
if temp:
candidates[this_d.disjunction_id] = temp candidates[this_d.disjunction_id] = temp
elif alternatetemp:
candidates[this_d.disjunction_id] = alternatetemp
else:
candidates[this_d.disjunction_id] = []
# step 5 select the first combination that works # step 5 select the first combination that works
pathreslist_disjoint = {} pathreslist_disjoint = {}
@@ -829,9 +872,7 @@ def compute_path_dsjctn(network, equipment, pathreqlist, disjunctions_list):
# remove duplicated candidates # remove duplicated candidates
candidates = remove_candidate(candidates, allpaths, allpaths[id(pth)].req, pth) candidates = remove_candidate(candidates, allpaths, allpaths[id(pth)].req, pth)
else: else:
msg = f'No disjoint path found with added constraint' msg = 'No disjoint path found with added constraint\nComputation stopped.'
LOGGER.critical(msg)
print(f'{msg}\nComputation stopped.')
# TODO in this case: replay step 5 with the candidate without constraints # TODO in this case: replay step 5 with the candidate without constraints
raise DisjunctionError(msg) raise DisjunctionError(msg)
@@ -852,8 +893,7 @@ def compute_path_dsjctn(network, equipment, pathreqlist, disjunctions_list):
def isdisjoint(pth1, pth2): def isdisjoint(pth1, pth2):
""" returns 0 if disjoint """returns 0 if disjoint"""
"""
edge1 = list(pairwise(pth1)) edge1 = list(pairwise(pth1))
edge2 = list(pairwise(pth2)) edge2 = list(pairwise(pth2))
for edge in edge1: for edge in edge1:
@@ -888,9 +928,8 @@ def find_reversed_path(pth):
# concatenation should be [roadma el1 el2 roadmb el3 el4 roadmc] # concatenation should be [roadma el1 el2 roadmb el3 el4 roadmc]
reversed_path = list(OrderedDict.fromkeys(reversed_path)) reversed_path = list(OrderedDict.fromkeys(reversed_path))
else: else:
msg = f'Error while handling reversed path {pth[-1].uid} to {pth[0].uid}:' +\ msg = f'Error while handling reversed path {pth[-1].uid} to {pth[0].uid}:' \
' can not handle unidir topology. TO DO.' + ' can not handle unidir topology. TO DO.'
LOGGER.critical(msg)
raise ValueError(msg) raise ValueError(msg)
reversed_path.append(pth[0]) reversed_path.append(pth[0])
@@ -898,9 +937,7 @@ def find_reversed_path(pth):
def ispart(ptha, pthb): def ispart(ptha, pthb):
""" the functions takes two paths a and b and retrns True """the functions takes two paths a and b and retrns True if all a elements are part of b and in the same order"""
if all a elements are part of b and in the same order
"""
j = 0 j = 0
for elem in ptha: for elem in ptha:
if elem in pthb: if elem in pthb:
@@ -914,8 +951,7 @@ def ispart(ptha, pthb):
def remove_candidate(candidates, allpaths, rqst, pth): def remove_candidate(candidates, allpaths, rqst, pth):
""" filter duplicate candidates """filter duplicate candidates"""
"""
# print(f'coucou {rqst.request_id}') # print(f'coucou {rqst.request_id}')
for key, candidate in candidates.items(): for key, candidate in candidates.items():
temp = candidate.copy() temp = candidate.copy()
@@ -930,8 +966,7 @@ def remove_candidate(candidates, allpaths, rqst, pth):
def compare_reqs(req1, req2, disjlist): def compare_reqs(req1, req2, disjlist):
""" compare two requests: returns True or False """compare two requests: returns True or False"""
"""
dis1 = [d for d in disjlist if req1.request_id in d.disjunctions_req] dis1 = [d for d in disjlist if req1.request_id in d.disjunctions_req]
dis2 = [d for d in disjlist if req2.request_id in d.disjunctions_req] dis2 = [d for d in disjlist if req2.request_id in d.disjunctions_req]
same_disj = False same_disj = False
@@ -964,6 +999,7 @@ def compare_reqs(req1, req2, disjlist):
req1.format == req2.format and \ req1.format == req2.format and \
req1.OSNR == req2.OSNR and \ req1.OSNR == req2.OSNR and \
req1.roll_off == req2.roll_off and \ req1.roll_off == req2.roll_off and \
req1.tx_power == req2.tx_power and \
same_disj: same_disj:
return True return True
else: else:
@@ -973,17 +1009,22 @@ def compare_reqs(req1, req2, disjlist):
def requests_aggregation(pathreqlist, disjlist): def requests_aggregation(pathreqlist, disjlist):
"""this function aggregates requests so that if several requests """this function aggregates requests so that if several requests
exist between same source and destination and with same transponder type exist between same source and destination and with same transponder type
If transponder mode is defined and identical, then also agregates demands.
""" """
# todo maybe add conditions on mode ??, spacing ... # todo maybe add conditions on mode ??, spacing ...
# currently if undefined takes the default values # currently if undefined takes the default values
local_list = pathreqlist.copy() local_list = pathreqlist.copy()
for req in pathreqlist: for req in pathreqlist:
for this_r in local_list: for this_r in local_list:
if req.request_id != this_r.request_id and compare_reqs(req, this_r, disjlist): if req.request_id != this_r.request_id and compare_reqs(req, this_r, disjlist) and\
this_r.tsp_mode is not None:
# aggregate # aggregate
this_r.path_bandwidth += req.path_bandwidth this_r.path_bandwidth += req.path_bandwidth
this_r.N = this_r.N + req.N
this_r.M = this_r.M + req.M
temp_r_id = this_r.request_id temp_r_id = this_r.request_id
this_r.request_id = ' | '.join((this_r.request_id, req.request_id)) this_r.request_id = ' | '.join((this_r.request_id, req.request_id))
# remove request from list # remove request from list
local_list.remove(req) local_list.remove(req)
# todo change also disjunction req with new demand # todo change also disjunction req with new demand
@@ -1001,6 +1042,7 @@ def requests_aggregation(pathreqlist, disjlist):
def correct_json_route_list(network, pathreqlist): def correct_json_route_list(network, pathreqlist):
"""all names in list should be exact name in the network, and there is no ambiguity """all names in list should be exact name in the network, and there is no ambiguity
This function only checks that list is correct, warns user if the name is incorrect and This function only checks that list is correct, warns user if the name is incorrect and
suppresses the constraint it it is loose or raises an error if it is strict suppresses the constraint it it is loose or raises an error if it is strict
""" """
@@ -1008,15 +1050,13 @@ def correct_json_route_list(network, pathreqlist):
transponders = [n.uid for n in network.nodes() if isinstance(n, Transceiver)] transponders = [n.uid for n in network.nodes() if isinstance(n, Transceiver)]
for pathreq in pathreqlist: for pathreq in pathreqlist:
if pathreq.source not in transponders: if pathreq.source not in transponders:
msg = f'{ansi_escapes.red}Request: {pathreq.request_id}: could not find transponder' +\ msg = f'Request: {pathreq.request_id}: could not find transponder' \
f' source : {pathreq.source}.{ansi_escapes.reset}' + f' source : {pathreq.source}.'
LOGGER.critical(msg)
raise ServiceError(msg) raise ServiceError(msg)
if pathreq.destination not in transponders: if pathreq.destination not in transponders:
msg = f'{ansi_escapes.red}Request: {pathreq.request_id}: could not find transponder' +\ msg = f'Request: {pathreq.request_id}: could not find transponder' \
f' destination : {pathreq.destination}.{ansi_escapes.reset}' + f' destination : {pathreq.destination}.'
LOGGER.critical(msg)
raise ServiceError(msg) raise ServiceError(msg)
# silently remove source and dest nodes from the list # silently remove source and dest nodes from the list
@@ -1035,24 +1075,21 @@ def correct_json_route_list(network, pathreqlist):
# if no matching can be found in the network just ignore this constraint # if no matching can be found in the network just ignore this constraint
# if it is a loose constraint # if it is a loose constraint
# warns the user that this node is not part of the topology # warns the user that this node is not part of the topology
msg = f'{ansi_escapes.yellow}invalid route node specified:\n\t\'{n_id}\',' +\ msg = f'invalid route node specified:\n\t\'{n_id}\',' \
f' could not use it as constraint, skipped!{ansi_escapes.reset}' + ' could not use it as constraint, skipped!'
print(msg) LOGGER.warning(msg)
LOGGER.info(msg)
pathreq.loose_list.pop(pathreq.nodes_list.index(n_id)) pathreq.loose_list.pop(pathreq.nodes_list.index(n_id))
pathreq.nodes_list.remove(n_id) pathreq.nodes_list.remove(n_id)
else: else:
msg = f'{ansi_escapes.red}could not find node:\n\t \'{n_id}\' in network' +\ msg = f'could not find node:\n\t \'{n_id}\' in network' \
f' topology. Strict constraint can not be applied.{ansi_escapes.reset}' + ' topology. Strict constraint can not be applied.'
LOGGER.critical(msg)
raise ServiceError(msg) raise ServiceError(msg)
return pathreqlist return pathreqlist
def deduplicate_disjunctions(disjn): def deduplicate_disjunctions(disjn):
""" clean disjunctions to remove possible repetition """clean disjunctions to remove possible repetition"""
"""
local_disjn = disjn.copy() local_disjn = disjn.copy()
for elem in local_disjn: for elem in local_disjn:
for dis_elem in local_disjn: for dis_elem in local_disjn:
@@ -1062,23 +1099,28 @@ def deduplicate_disjunctions(disjn):
return local_disjn return local_disjn
def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist): def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist, redesign=False):
"""use a list but a dictionnary might be helpful to find path based on request_id """use a list but a dictionnary might be helpful to find path based on request_id
TODO change all these req, dsjct, res lists into dict ! TODO change all these req, dsjct, res lists into dict !
""" """
path_res_list = [] path_res_list = []
reversed_path_res_list = [] reversed_path_res_list = []
propagated_reversed_path_res_list = [] propagated_reversed_path_res_list = []
total_nb_requests = len(pathreqlist)
if redesign:
LOGGER.warning('Redesign the network for each request channel, '
+ 'using the request channel as the reference channel for the design.')
for i, pathreq in enumerate(pathreqlist): for i, pathreq in enumerate(pathreqlist):
# use the power specified in requests but might be different from the one # use the power specified in requests but might be different from the one
# specified for design the power is an optional parameter for requests # specified for design the power is an optional parameter for requests
# definition if optional, use the one defines in eqt_config.json # definition if optional, use the one defines in eqt_config.json
print(f'request {pathreq.request_id}') msg = f'\n\trequest {pathreq.request_id}\n' \
print(f'Computing path from {pathreq.source} to {pathreq.destination}') + f'\tComputing path from {pathreq.source} to {pathreq.destination}\n' \
# adding first node to be clearer on the output + f'\twith path constraint: {[pathreq.source] + pathreq.nodes_list}'
print(f'with path constraint: {[pathreq.source] + pathreq.nodes_list}') # # adding first node to be clearer on the output
# pathlist[i] contains the whole path information for request i # pathlist[i] contains the whole path information for request i
# last element is a transciver and where the result of the propagation is # last element is a transciver and where the result of the propagation is
@@ -1087,8 +1129,19 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
# elements to simulate performance, several demands having the same destination # elements to simulate performance, several demands having the same destination
# may use the same transponder for the performance simulation. This is why # may use the same transponder for the performance simulation. This is why
# we use deepcopy: to ensure that each propagation is recorded and not overwritten # we use deepcopy: to ensure that each propagation is recorded and not overwritten
# reversed path is needed for correct spectrum assignment
if redesign:
# this is the legacy case where network was automatically redesigned using the
# request channel as reference (nb and power used for amplifiers total power out)
reversed_path = []
if pathlist[i]:
reversed_path = find_reversed_path(pathlist[i])
network_nodes_for_redesign = pathlist[i] + reversed_path
network_module.design_network(pathreq, network.subgraph(network_nodes_for_redesign), equipment,
set_connector_losses=False, verbose=False)
total_path = deepcopy(pathlist[i]) total_path = deepcopy(pathlist[i])
print(f'Computed path (roadms):{[e.uid for e in total_path if isinstance(e, Roadm)]}') msg = msg + f'\n\tComputed path (roadms):{[e.uid for e in total_path if isinstance(e, Roadm)]}'
LOGGER.info(msg)
# for debug # for debug
# print(f'{pathreq.baud_rate} {pathreq.power} {pathreq.spacing} {pathreq.nb_channel}') # print(f'{pathreq.baud_rate} {pathreq.power} {pathreq.spacing} {pathreq.nb_channel}')
if total_path: if total_path:
@@ -1096,13 +1149,15 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
# means that at this point the mode was entered/forced by user and thus a # means that at this point the mode was entered/forced by user and thus a
# baud_rate was defined # baud_rate was defined
propagate(total_path, pathreq, equipment) propagate(total_path, pathreq, equipment)
temp_snr01nm = round(mean(total_path[-1].snr+lin2db(pathreq.baud_rate/(12.5e9))), 2) snr01nm_with_penalty = total_path[-1].snr_01nm - total_path[-1].total_penalty
if temp_snr01nm < pathreq.OSNR + equipment['SI']['default'].sys_margins: min_ind = argmin(snr01nm_with_penalty)
msg = f'\tWarning! Request {pathreq.request_id} computed path from' +\ if round(snr01nm_with_penalty[min_ind], 2) < pathreq.OSNR + equipment['SI']['default'].sys_margins:
f' {pathreq.source} to {pathreq.destination} does not pass with' +\ msg = f'\tWarning! Request {pathreq.request_id} computed path from' \
f' {pathreq.tsp_mode}\n\tcomputedSNR in 0.1nm = {temp_snr01nm} ' +\ + f' {pathreq.source} to {pathreq.destination} does not pass with {pathreq.tsp_mode}' \
f'- required osnr {pathreq.OSNR} + {equipment["SI"]["default"].sys_margins} margin' + f'\n\tcomputed SNR in 0.1nm = {round(total_path[-1].snr_01nm[min_ind], 2)}'
print(msg) msg = _penalty_msg(total_path, msg, min_ind) \
+ f'\n\trequired osnr = {pathreq.OSNR}' \
+ f'\n\tsystem margin = {equipment["SI"]["default"].sys_margins}'
LOGGER.warning(msg) LOGGER.warning(msg)
pathreq.blocking_reason = 'MODE_NOT_FEASIBLE' pathreq.blocking_reason = 'MODE_NOT_FEASIBLE'
else: else:
@@ -1122,6 +1177,8 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
pathreq.OSNR = mode['OSNR'] pathreq.OSNR = mode['OSNR']
pathreq.tx_osnr = mode['tx_osnr'] pathreq.tx_osnr = mode['tx_osnr']
pathreq.bit_rate = mode['bit_rate'] pathreq.bit_rate = mode['bit_rate']
pathreq.penalties = mode['penalties']
pathreq.offset_db = mode['equalization_offset_db']
# other blocking reason should not appear at this point # other blocking reason should not appear at this point
except AttributeError: except AttributeError:
pathreq.baud_rate = mode['baud_rate'] pathreq.baud_rate = mode['baud_rate']
@@ -1130,35 +1187,37 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
pathreq.OSNR = mode['OSNR'] pathreq.OSNR = mode['OSNR']
pathreq.tx_osnr = mode['tx_osnr'] pathreq.tx_osnr = mode['tx_osnr']
pathreq.bit_rate = mode['bit_rate'] pathreq.bit_rate = mode['bit_rate']
pathreq.penalties = mode['penalties']
pathreq.offset_db = mode['equalization_offset_db']
# reversed path is needed for correct spectrum assignment # reversed path is needed for correct spectrum assignment
reversed_path = find_reversed_path(pathlist[i]) reversed_path = find_reversed_path(pathlist[i])
if pathreq.bidir and pathreq.baud_rate is not None: if pathreq.bidir and pathreq.baud_rate is not None:
# Both directions requested, and a feasible mode was found # Both directions requested, and a feasible mode was found
rev_p = deepcopy(reversed_path) rev_p = deepcopy(reversed_path)
msg = f'\n\tPropagating Z to A direction {pathreq.destination} to {pathreq.source}\n' \
print(f'\n\tPropagating Z to A direction {pathreq.destination} to {pathreq.source}') + f'\tPath (roadms) {[r.uid for r in rev_p if isinstance(r,Roadm)]}\n'
print(f'\tPath (roadsm) {[r.uid for r in rev_p if isinstance(r,Roadm)]}\n') LOGGER.info(msg)
propagate(rev_p, pathreq, equipment) propagate(rev_p, pathreq, equipment)
propagated_reversed_path = rev_p propagated_reversed_path = rev_p
temp_snr01nm = round(mean(propagated_reversed_path[-1].snr +\ snr01nm_with_penalty = rev_p[-1].snr_01nm - rev_p[-1].total_penalty
lin2db(pathreq.baud_rate/(12.5e9))), 2) min_ind = argmin(snr01nm_with_penalty)
if temp_snr01nm < pathreq.OSNR + equipment['SI']['default'].sys_margins: if round(snr01nm_with_penalty[min_ind], 2) < pathreq.OSNR + equipment['SI']['default'].sys_margins:
msg = f'\tWarning! Request {pathreq.request_id} computed path from' +\ msg = f'\tWarning! Request {pathreq.request_id} computed path from' \
f' {pathreq.source} to {pathreq.destination} does not pass with' +\ + f' {pathreq.destination} to {pathreq.source} does not pass with {pathreq.tsp_mode}' \
f' {pathreq.tsp_mode}\n' +\ + f'\n\tcomputed SNR in 0.1nm = {round(rev_p[-1].snr_01nm[min_ind], 2)}'
f'\tcomputedSNR in 0.1nm = {temp_snr01nm} -' \ msg = _penalty_msg(rev_p, msg, min_ind) \
f' required osnr {pathreq.OSNR} + {equipment["SI"]["default"].sys_margins} margin' + f'\n\trequired osnr = {pathreq.OSNR}' \
print(msg) + f'\n\tsystem margin = {equipment["SI"]["default"].sys_margins}'
LOGGER.warning(msg) LOGGER.warning(msg)
# TODO selection of mode should also be on reversed direction !! # TODO selection of mode should also be on reversed direction !!
if not hasattr(pathreq, 'blocking_reason'):
pathreq.blocking_reason = 'MODE_NOT_FEASIBLE' pathreq.blocking_reason = 'MODE_NOT_FEASIBLE'
else: else:
propagated_reversed_path = [] propagated_reversed_path = []
else: else:
msg = 'Total path is empty. No propagation' msg = f'Request {pathreq.request_id}: Total path is empty. No propagation'
print(msg) LOGGER.warning(msg)
LOGGER.info(msg)
reversed_path = [] reversed_path = []
propagated_reversed_path = [] propagated_reversed_path = []
@@ -1166,5 +1225,79 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
reversed_path_res_list.append(reversed_path) reversed_path_res_list.append(reversed_path)
propagated_reversed_path_res_list.append(propagated_reversed_path) propagated_reversed_path_res_list.append(propagated_reversed_path)
# print to have a nice output # print to have a nice output
print('')
return path_res_list, reversed_path_res_list, propagated_reversed_path_res_list return path_res_list, reversed_path_res_list, propagated_reversed_path_res_list
def compute_spectrum_slot_vs_bandwidth(bandwidth, spacing, bit_rate, slot_width=0.0125e12):
"""Compute the number of required wavelengths and the M value (number of consumed slots)
Each wavelength consumes one `spacing`, and the result is rounded up to consume a natural number of slots.
>>> compute_spectrum_slot_vs_bandwidth(400e9, 50e9, 200e9)
(2, 8)
"""
number_of_wavelengths = ceil(bandwidth / bit_rate)
total_number_of_slots = ceil(spacing / slot_width) * number_of_wavelengths
return number_of_wavelengths, total_number_of_slots
def _penalty_msg(total_path, msg, min_ind):
"""formatting helper for reporting unfeasible paths
The penalty info are optional, so this checks that penalty exists before creating a message."""
penalty_dict = {
'pdl': 'PDL',
'chromatic_dispersion': 'CD',
'pmd': 'PMD'}
for key, pretty in penalty_dict.items():
if key in total_path[-1].penalties:
msg += f'\n\t{pretty} penalty = {round(total_path[-1].penalties[key][min_ind], 2)}'
else:
msg += f'\n\t{pretty} penalty not evaluated'
return msg
def is_adjacent(oms1, oms2):
""" oms1's egress ROADM is oms2's ingress ROADM
"""
return oms1.el_list[-1] == oms2.el_list[0]
def explicit_path(node_list, source, destination, network):
""" if list of nodes leads to adjacent oms, then means that the path is explicit, and no need to compute
the function returns the explicit path (including source and destination ROADMs)
"""
path_oms = []
for elem in node_list:
if hasattr(elem, 'oms'):
path_oms.append(elem.oms)
if not path_oms:
return None
path_oms = unique_ordered(path_oms)
try:
next_node = next(network.successors(source))
source_roadm = next_node if isinstance(next_node, Roadm) else source
previous_node = next(network.predecessors(destination))
destination_roadm = previous_node if isinstance(previous_node, Roadm) else destination
if not (path_oms[0].el_list[0] == source_roadm and path_oms[-1].el_list[-1] == destination_roadm):
return None
except StopIteration:
return None
oms0 = path_oms[0]
path = [source] + oms0.el_list
for oms in path_oms[1:]:
if not is_adjacent(oms0, oms):
return None
oms0 = oms
path.extend(oms.el_list)
path.append(destination)
return unique_ordered(path)
def find_elements_common_range(el_list: list, equipment: dict) -> List[dict]:
"""Find the common frequency range of amps of a given list of elements (for example an OMS or a path)
If there are no amplifiers in the path, then use the SI
"""
amp_bands = [n.params.bands for n in el_list if isinstance(n, (Edfa, Multiband_amplifier))]
return find_common_range(amp_bands, equipment['SI']['default'].f_min, equipment['SI']['default'].f_max)

View File

@@ -15,28 +15,31 @@ element/oms correspondace
from collections import namedtuple from collections import namedtuple
from logging import getLogger from logging import getLogger
from math import ceil
from gnpy.core.elements import Roadm, Transceiver from gnpy.core.elements import Roadm, Transceiver, Edfa, Multiband_amplifier
from gnpy.core.exceptions import ServiceError, SpectrumError from gnpy.core.exceptions import ServiceError, SpectrumError
from gnpy.core.utils import order_slots, restore_order
from gnpy.topology.request import compute_spectrum_slot_vs_bandwidth, find_elements_common_range
LOGGER = getLogger(__name__) LOGGER = getLogger(__name__)
GUARDBAND = 25e9
class Bitmap: class Bitmap:
""" records the spectrum occupation """records the spectrum occupation"""
"""
def __init__(self, f_min, f_max, grid, guardband=0.15e12, bitmap=None): def __init__(self, f_min, f_max, grid, guardband=GUARDBAND, bitmap=None):
# n is the min index including guardband. Guardband is require to be sure # n is the min index including guardband. Guardband is required to be sure
# that a channel can be assigned with center frequency fmin (means that its # that a channel can be assigned with center frequency fmin (means that its
# slot occupation goes below freq_index_min # slot occupation goes below freq_index_min
n_min = frequency_to_n(f_min - guardband, grid) n_min = frequency_to_n(f_min, grid)
n_max = frequency_to_n(f_max + guardband, grid) - 1 n_max = frequency_to_n(f_max, grid)
self.n_min = n_min self.n_min = n_min
self.n_max = n_max self.n_max = n_max
self.freq_index_min = frequency_to_n(f_min) self.freq_index_min = frequency_to_n(f_min + guardband)
self.freq_index_max = frequency_to_n(f_max) self.freq_index_max = frequency_to_n(f_max - guardband)
self.freq_index = list(range(n_min, n_max + 1)) self.freq_index = list(range(n_min, n_max + 1))
self.guardband = guardband
if bitmap is None: if bitmap is None:
self.bitmap = [1] * (n_max - n_min + 1) self.bitmap = [1] * (n_max - n_min + 1)
elif len(bitmap) == len(self.freq_index): elif len(bitmap) == len(self.freq_index):
@@ -45,26 +48,22 @@ class Bitmap:
raise SpectrumError(f'bitmap is not consistant with f_min{f_min} - n: {n_min} and f_max{f_max}- n :{n_max}') raise SpectrumError(f'bitmap is not consistant with f_min{f_min} - n: {n_min} and f_max{f_max}- n :{n_max}')
def getn(self, i): def getn(self, i):
""" converts the n (itu grid) into a local index """converts the n (itu grid) into a local index"""
"""
return self.freq_index[i] return self.freq_index[i]
def geti(self, nvalue): def geti(self, nvalue):
""" converts the local index into n (itu grid) """converts the local index into n (itu grid)"""
"""
return self.freq_index.index(nvalue) return self.freq_index.index(nvalue)
def insert_left(self, newbitmap): def insert_left(self, newbitmap):
""" insert bitmap on the left to align oms bitmaps if their start frequencies are different """insert bitmap on the left to align oms bitmaps if their start frequencies are different"""
"""
self.bitmap = newbitmap + self.bitmap self.bitmap = newbitmap + self.bitmap
temp = list(range(self.n_min - len(newbitmap), self.n_min)) temp = list(range(self.n_min - len(newbitmap), self.n_min))
self.freq_index = temp + self.freq_index self.freq_index = temp + self.freq_index
self.n_min = self.freq_index[0] self.n_min = self.freq_index[0]
def insert_right(self, newbitmap): def insert_right(self, newbitmap):
""" insert bitmap on the right to align oms bitmaps if their stop frequencies are different """insert bitmap on the right to align oms bitmaps if their stop frequencies are different"""
"""
self.bitmap = self.bitmap + newbitmap self.bitmap = self.bitmap + newbitmap
self.freq_index = self.freq_index + list(range(self.n_max, self.n_max + len(newbitmap))) self.freq_index = self.freq_index + list(range(self.n_max, self.n_max + len(newbitmap)))
self.n_max = self.freq_index[-1] self.n_max = self.freq_index[-1]
@@ -87,7 +86,6 @@ class OMS:
self.spectrum_bitmap = [] self.spectrum_bitmap = []
self.nb_channels = 0 self.nb_channels = 0
self.service_list = [] self.service_list = []
# TODO
def __str__(self): def __str__(self):
return '\n\t'.join([f'{type(self).__name__} {self.oms_id}', return '\n\t'.join([f'{type(self).__name__} {self.oms_id}',
@@ -98,36 +96,28 @@ class OMS:
f'{self.el_id_list[0]} - {self.el_id_list[-1]}', '\n']) f'{self.el_id_list[0]} - {self.el_id_list[-1]}', '\n'])
def add_element(self, elem): def add_element(self, elem):
""" records oms elements """records oms elements"""
"""
self.el_id_list.append(elem.uid) self.el_id_list.append(elem.uid)
self.el_list.append(elem) self.el_list.append(elem)
def update_spectrum(self, f_min, f_max, guardband=0.15e12, existing_spectrum=None, def update_spectrum(self, f_min, f_max, guardband=GUARDBAND, existing_spectrum=None, grid=0.00625e12):
grid=0.00625e12): """Frequencies expressed in Hz.
""" frequencies expressed in Hz Add 150 GHz margin to enable a center channel on f_min
Use ITU-T G694.1 Flexible DWDM grid definition
For the flexible DWDM grid, the allowed frequency slots have a nominal central frequency (in THz) defined by:
193.1 + n × 0.00625 where n is a positive or negative integer including 0
and 0.00625 is the nominal central frequency granularity in THz
and a slot width defined by:
12.5 × m where m is a positive integer and 12.5 is the slot width granularity in GHz.
Any combination of frequency slots is allowed as long as no two frequency slots overlap.
If bitmap is not None, then use it: Bitmap checks its consistency with f_min f_max
else a brand new bitmap is created
""" """
if existing_spectrum is None: self.spectrum_bitmap = Bitmap(f_min=f_min, f_max=f_max, grid=grid, guardband=guardband,
# add some 150 GHz margin to enable a center channel on f_min bitmap=existing_spectrum)
# use ITU-T G694.1
# Flexible DWDM grid definition
# For the flexible DWDM grid, the allowed frequency slots have a nominal
# central frequency (in THz) defined by:
# 193.1 + n × 0.00625 where n is a positive or negative integer including 0
# and 0.00625 is the nominal central frequency granularity in THz
# and a slot width defined by:
# 12.5 × m where m is a positive integer and 12.5 is the slot width granularity in
# GHz.
# Any combination of frequency slots is allowed as long as no two frequency
# slots overlap.
# TODO : add explaination on that / parametrize ....
self.spectrum_bitmap = Bitmap(f_min, f_max, grid, guardband)
# print(len(self.spectrum_bitmap.bitmap))
def assign_spectrum(self, nvalue, mvalue): def assign_spectrum(self, nvalue, mvalue):
""" change oms spectrum to mark spectrum assigned """change oms spectrum to mark spectrum assigned"""
"""
if not isinstance(nvalue, int): if not isinstance(nvalue, int):
raise SpectrumError(f'N must be a signed integer, got {nvalue}') raise SpectrumError(f'N must be a signed integer, got {nvalue}')
if not isinstance(mvalue, int): if not isinstance(mvalue, int):
@@ -146,14 +136,14 @@ class OMS:
self.spectrum_bitmap.bitmap[self.spectrum_bitmap.geti(startn):self.spectrum_bitmap.geti(stopn) + 1] = [0] * (stopn - startn + 1) self.spectrum_bitmap.bitmap[self.spectrum_bitmap.geti(startn):self.spectrum_bitmap.geti(stopn) + 1] = [0] * (stopn - startn + 1)
def add_service(self, service_id, nb_wl): def add_service(self, service_id, nb_wl):
""" record service and mark spectrum as occupied """record service and mark spectrum as occupied"""
"""
self.service_list.append(service_id) self.service_list.append(service_id)
self.nb_channels += nb_wl self.nb_channels += nb_wl
def frequency_to_n(freq, grid=0.00625e12): def frequency_to_n(freq, grid=0.00625e12):
"""converts frequency into the n value (ITU grid) """converts frequency into the n value (ITU grid)
reference to Recommendation G.694.1 (02/12), Figure I.3 reference to Recommendation G.694.1 (02/12), Figure I.3
https://www.itu.int/rec/T-REC-G.694.1-201202-I/en https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
@@ -168,6 +158,7 @@ def frequency_to_n(freq, grid=0.00625e12):
def nvalue_to_frequency(nvalue, grid=0.00625e12): def nvalue_to_frequency(nvalue, grid=0.00625e12):
"""converts n value into a frequency """converts n value into a frequency
reference to Recommendation G.694.1 (02/12), Table 1 reference to Recommendation G.694.1 (02/12), Table 1
https://www.itu.int/rec/T-REC-G.694.1-201202-I/en https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
@@ -181,8 +172,7 @@ def nvalue_to_frequency(nvalue, grid=0.00625e12):
def mvalue_to_slots(nvalue, mvalue): def mvalue_to_slots(nvalue, mvalue):
""" convert center n an m into start and stop n """convert center n an m into start and stop n"""
"""
startn = nvalue - mvalue startn = nvalue - mvalue
stopn = nvalue + mvalue - 1 stopn = nvalue + mvalue - 1
return startn, stopn return startn, stopn
@@ -190,6 +180,7 @@ def mvalue_to_slots(nvalue, mvalue):
def slots_to_m(startn, stopn): def slots_to_m(startn, stopn):
"""converts the start and stop n values to the center n and m value """converts the start and stop n values to the center n and m value
reference to Recommendation G.694.1 (02/12), Figure I.3 reference to Recommendation G.694.1 (02/12), Figure I.3
https://www.itu.int/rec/T-REC-G.694.1-201202-I/en https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
@@ -207,6 +198,7 @@ def slots_to_m(startn, stopn):
def m_to_freq(nvalue, mvalue, grid=0.00625e12): def m_to_freq(nvalue, mvalue, grid=0.00625e12):
"""converts m into frequency range """converts m into frequency range
spectrum(13,7) is (193137500000000.0, 193225000000000.0) spectrum(13,7) is (193137500000000.0, 193225000000000.0)
reference to Recommendation G.694.1 (02/12), Figure I.3 reference to Recommendation G.694.1 (02/12), Figure I.3
https://www.itu.int/rec/T-REC-G.694.1-201202-I/en https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
@@ -225,9 +217,7 @@ def m_to_freq(nvalue, mvalue, grid=0.00625e12):
def align_grids(oms_list): def align_grids(oms_list):
""" used to apply same grid to all oms : same starting n, stop n and slot size """Used to apply same grid to all oms : same starting n, stop n and slot size. Out of grid slots are set to 0."""
out of grid slots are set to 0
"""
n_min = min([o.spectrum_bitmap.n_min for o in oms_list]) n_min = min([o.spectrum_bitmap.n_min for o in oms_list])
n_max = max([o.spectrum_bitmap.n_max for o in oms_list]) n_max = max([o.spectrum_bitmap.n_max for o in oms_list])
for this_o in oms_list: for this_o in oms_list:
@@ -238,8 +228,43 @@ def align_grids(oms_list):
return oms_list return oms_list
def find_network_freq_range(network, equipment):
"""Find the lowest freq from amps and highest freq among all amps to determine the resulting bitmap
"""
amp_bands = [band for n in network.nodes() if isinstance(n, (Edfa, Multiband_amplifier)) for band in n.params.bands]
min_frequencies = [a['f_min'] for a in amp_bands]
max_frequencies = [a['f_max'] for a in amp_bands]
return min(min_frequencies), max(max_frequencies)
def create_oms_bitmap(oms, equipment, f_min, f_max, guardband, grid):
"""Find the highest low freq from oms amps and lowest high freq among oms amps to determine
the possible bitmap window.
f_min and f_max represent the useable spectrum (not the useable center frequencies)
ie n smaller than frequency_to_n(min_freq, grid) are not useable
"""
n_min = frequency_to_n(f_min, grid)
n_max = frequency_to_n(f_max, grid) - 1
common_range = find_elements_common_range(oms.el_list, equipment)
band0 = common_range[0]
band0_n_min = frequency_to_n(band0['f_min'], grid)
band0_n_max = frequency_to_n(band0['f_max'], grid)
bitmap = [0] * (band0_n_min - n_min) + [1] * (band0_n_max - band0_n_min + 1)
i = 1
while i < len(common_range):
band = common_range[i]
band_n_min = frequency_to_n(band['f_min'], grid)
band_n_max = frequency_to_n(band['f_max'], grid)
bitmap = bitmap + [0] * (band_n_min - band0_n_max - 1) + [1] * (band_n_max - band_n_min + 1)
band0_n_max = band_n_max
i += 1
bitmap = bitmap + [0] * (n_max - band0_n_max)
return bitmap
def build_oms_list(network, equipment): def build_oms_list(network, equipment):
"""initialization of OMS list in the network """initialization of OMS list in the network
an oms is build reading all intermediate nodes between two adjacent ROADMs an oms is build reading all intermediate nodes between two adjacent ROADMs
each element within the list is being added an oms and oms_id to record the each element within the list is being added an oms and oms_id to record the
oms it belongs to. oms it belongs to.
@@ -248,7 +273,15 @@ def build_oms_list(network, equipment):
""" """
oms_id = 0 oms_id = 0
oms_list = [] oms_list = []
for node in [n for n in network.nodes() if isinstance(n, Roadm)]: # identify all vertices of OMS: of course ROADM, but aso links to external chassis transponders
oms_vertices = [n for n in network.nodes() if isinstance(n, Roadm)] +\
[n for n in network.nodes() if isinstance(n, Transceiver)
and not isinstance(next(network.successors(n)), Roadm)]
# determine the size of the bitmap common to all the omses: find min and max frequencies of all amps
# in the network. These gives the band not the center frequency. Thhen we use a reference channel
# slot width (50GHz) to set the f_min, f_max
f_min, f_max = find_network_freq_range(network, equipment)
for node in oms_vertices:
for edge in network.edges([node]): for edge in network.edges([node]):
if not isinstance(edge[1], Transceiver): if not isinstance(edge[1], Transceiver):
nd_in = edge[0] # nd_in is a Roadm nd_in = edge[0] # nd_in is a Roadm
@@ -282,8 +315,9 @@ def build_oms_list(network, equipment):
nd_out.oms_list = [] nd_out.oms_list = []
nd_out.oms_list.append(oms_id) nd_out.oms_list.append(oms_id)
oms.update_spectrum(equipment['SI']['default'].f_min, bitmap = create_oms_bitmap(oms, equipment, f_min=f_min, f_max=f_max, guardband=GUARDBAND,
equipment['SI']['default'].f_max, grid=0.00625e12) grid=0.00625e12)
oms.update_spectrum(f_min, f_max, guardband=GUARDBAND, grid=0.00625e12, existing_spectrum=bitmap)
# oms.assign_spectrum(13,7) gives back (193137500000000.0, 193225000000000.0) # oms.assign_spectrum(13,7) gives back (193137500000000.0, 193225000000000.0)
# as in the example in the standard # as in the example in the standard
# oms.assign_spectrum(13,7) # oms.assign_spectrum(13,7)
@@ -297,6 +331,7 @@ def build_oms_list(network, equipment):
def reversed_oms(oms_list): def reversed_oms(oms_list):
"""identifies reversed OMS """identifies reversed OMS
only applicable for non parallel OMS only applicable for non parallel OMS
""" """
for oms in oms_list: for oms in oms_list:
@@ -322,28 +357,42 @@ def bitmap_sum(band1, band2):
return res return res
def spectrum_selection(pth, oms_list, requested_m, requested_n=None): def build_path_oms_id_list(pth):
"""Collects spectrum availability and call the select_candidate function"""
# use indexes instead of ITU-T n values
path_oms = [] path_oms = []
for elem in pth: for elem in pth:
if not isinstance(elem, Roadm) and not isinstance(elem, Transceiver): if not isinstance(elem, Roadm) and not isinstance(elem, Transceiver):
# only edfa, fused and fibers have oms_id attribute # only edfa, fused and fibers have oms_id attribute
path_oms.append(elem.oms_id) path_oms.append(elem.oms_id)
# remove duplicate oms_id, order is not important # remove duplicate oms_id, order is not important
path_oms = list(set(path_oms)) return list(set(path_oms))
# assuming all oms have same freq index
if not path_oms:
candidate = (None, None, None)
return candidate, path_oms
freq_index = oms_list[path_oms[0]].spectrum_bitmap.freq_index
freq_index_min = oms_list[path_oms[0]].spectrum_bitmap.freq_index_min
freq_index_max = oms_list[path_oms[0]].spectrum_bitmap.freq_index_max
freq_availability = oms_list[path_oms[0]].spectrum_bitmap.bitmap
def aggregate_oms_bitmap(path_oms, oms_list):
spectrum = oms_list[path_oms[0]].spectrum_bitmap
bitmap = spectrum.bitmap
# assuming all oms have same freq indices
for oms in path_oms[1:]: for oms in path_oms[1:]:
freq_availability = bitmap_sum(oms_list[oms].spectrum_bitmap.bitmap, freq_availability) bitmap = bitmap_sum(oms_list[oms].spectrum_bitmap.bitmap, bitmap)
params = {
'oms_id': 0,
'el_id_list': 0,
'el_list': []
}
freq_min = nvalue_to_frequency(spectrum.n_min)
freq_max = nvalue_to_frequency(spectrum.n_max)
aggregate_oms = OMS(**params)
aggregate_oms.update_spectrum(freq_min, freq_max, grid=0.00625e12, guardband=spectrum.guardband,
existing_spectrum=bitmap)
return aggregate_oms
def spectrum_selection(test_oms, requested_m, requested_n=None):
"""Collects spectrum availability and call the select_candidate function"""
freq_index = test_oms.spectrum_bitmap.freq_index
freq_index_min = test_oms.spectrum_bitmap.freq_index_min
freq_index_max = test_oms.spectrum_bitmap.freq_index_max
freq_availability = test_oms.spectrum_bitmap.bitmap
if requested_n is None: if requested_n is None:
# avoid slots reserved on the edge 0.15e-12 on both sides -> 24 # avoid slots reserved on the edge 0.15e-12 on both sides -> 24
candidates = [(freq_index[i] + requested_m, freq_index[i], freq_index[i] + 2 * requested_m - 1) candidates = [(freq_index[i] + requested_m, freq_index[i], freq_index[i] + 2 * requested_m - 1)
@@ -354,29 +403,36 @@ def spectrum_selection(pth, oms_list, requested_m, requested_n=None):
candidate = select_candidate(candidates, policy='first_fit') candidate = select_candidate(candidates, policy='first_fit')
else: else:
i = oms_list[path_oms[0]].spectrum_bitmap.geti(requested_n) i = test_oms.spectrum_bitmap.geti(requested_n)
# print(f'N {requested_n} i {i}') if (freq_availability[i - requested_m:i + requested_m] == [1] * (2 * requested_m)
# print(freq_availability[i-m:i+m] ) and freq_index[i - requested_m] >= freq_index_min
# print(freq_index[i-m:i+m])
if (freq_availability[i - requested_m:i + requested_m] == [1] * (2 * requested_m) and
freq_index[i - requested_m] >= freq_index_min
and freq_index[i + requested_m - 1] <= freq_index_max): and freq_index[i + requested_m - 1] <= freq_index_max):
# candidate is the triplet center_n, startn and stopn # candidate is the triplet center_n, startn and stopn
candidate = (requested_n, requested_n - requested_m, requested_n + requested_m - 1) candidate = (requested_n, requested_n - requested_m, requested_n + requested_m - 1)
else: else:
candidate = (None, None, None) candidate = (None, None, None)
# print("coucou11") return candidate
# print(candidate)
# print(freq_availability[321:321+2*m])
# a = [i+321 for i in range(2*m)] def determine_slot_numbers(test_oms, requested_n, required_m, per_channel_m):
# print(a) """determines max availability around requested_n. requested_n should not be None"""
# print(candidate) bitmap = test_oms.spectrum_bitmap
return candidate, path_oms freq_index = bitmap.freq_index
freq_index_min = bitmap.freq_index_min
freq_index_max = bitmap.freq_index_max
freq_availability = bitmap.bitmap
center_i = bitmap.geti(requested_n)
i = per_channel_m
while (freq_availability[center_i - i:center_i + i] == [1] * (2 * i)
and freq_index[center_i - i] >= freq_index_min
and freq_index[center_i + i - 1] <= freq_index_max
and i <= required_m):
i += per_channel_m
return i - per_channel_m
def select_candidate(candidates, policy): def select_candidate(candidates, policy):
""" selects a candidate among all available spectrum """selects a candidate among all available spectrum"""
"""
if policy == 'first_fit': if policy == 'first_fit':
if candidates: if candidates:
return candidates[0] return candidates[0]
@@ -386,46 +442,112 @@ def select_candidate(candidates, policy):
raise ServiceError('Only first_fit spectrum assignment policy is implemented.') raise ServiceError('Only first_fit spectrum assignment policy is implemented.')
def compute_n_m(required_m, rq, path_oms, oms_list, per_channel_m, policy='first_fit'):
""" based on requested path_bandwidth fill in M=None values with uint values, using per_channel_m
and center frequency, with first fit strategy. The function checks the available spectrum but check
consistencies among M values of the request, but not with other requests.
For example, if request is for 32 slots corresponding to 8 x 4 slots of 32Gbauds channels,
the following frequency slots will result in the following assignment
N = 0, 8, 16, 32 -> 0, 8, 16, 32
M = 8, None, 8, None -> 8, 8, 8, 8
N = 0, 8, 16, 32 -> 0, , 16
M = None, None, 8, None -> 24, , 8
"""
selected_m = []
selected_n = []
remaining_slots_to_serve = required_m
# order slots for the computation: assign biggest m first
rq_N, rq_M, order = order_slots([{'N': n, 'M': m} for n, m in zip(rq.N, rq.M)])
# Create an oms that represents current assignments of all oms listed in path_oms, and test N and M on it.
# If M is defined, checks that proposed N, M is free
test_oms = aggregate_oms_bitmap(path_oms, oms_list)
for n, m in zip(rq_N, rq_M):
if m is not None and n is not None:
# check availabilityfor this n, m
available_slots = determine_slot_numbers(test_oms, n, m, m)
if available_slots == 0:
# if n, m are not feasible, break at this point no have non zero remaining_slots_to_serve
# in order to blocks the request (even is other N,M where feasible)
break
elif m is not None and n is None:
# find a candidate n
n, _, _ = spectrum_selection(test_oms, m, None)
if n is None:
# if no n is feasible for the m, block the request
break
elif m is None and n is not None:
# find a feasible m for this n. If None is found, then block the request
m = determine_slot_numbers(test_oms, n, remaining_slots_to_serve, per_channel_m)
if m == 0 or remaining_slots_to_serve == 0:
break
else:
# if n and m are not defined, try to find a single assignment to fits the remaining slots to serve
# (first fit strategy)
n, _, _ = spectrum_selection(test_oms, remaining_slots_to_serve, None)
if n is None or remaining_slots_to_serve == 0:
break
else:
m = remaining_slots_to_serve
selected_m.append(m)
selected_n.append(n)
test_oms.assign_spectrum(n, m)
remaining_slots_to_serve = remaining_slots_to_serve - m
# re-order selected_m and selected_n according to initial request N, M order, ignoring None values
not_selected = [None for i in range(len(rq_N) - len(selected_n))]
selected_m = restore_order(selected_m + not_selected, order)
selected_n = restore_order(selected_n + not_selected, order)
return selected_n, selected_m, remaining_slots_to_serve
def pth_assign_spectrum(pths, rqs, oms_list, rpths): def pth_assign_spectrum(pths, rqs, oms_list, rpths):
"""basic first fit assignment """basic first fit assignment
if reversed path are provided, means that occupation is bidir if reversed path are provided, means that occupation is bidir
""" """
for i, pth in enumerate(pths): for pth, rq, rpth in zip(pths, rqs, rpths):
# computes the number of channels required if hasattr(rq, 'blocking_reason'):
try: rq.N = None
if rqs[i].blocking_reason: rq.M = None
rqs[i].blocked = True
rqs[i].N = 0
rqs[i].M = 0
except AttributeError:
nb_wl = ceil(rqs[i].path_bandwidth / rqs[i].bit_rate)
# computes the total nb of slots according to requested spacing
# TODO : express superchannels
# assumes that all channels must be grouped
# TODO : enables non contiguous reservation in case of blocking
requested_m = ceil(rqs[i].spacing / 0.0125e12) * nb_wl
# concatenate all path and reversed path elements to derive slots availability
(center_n, startn, stopn), path_oms = spectrum_selection(pth + rpths[i], oms_list, requested_m,
requested_n=None)
# checks that requested_m is fitting startm and stopm
# if not None, center_n and start, stop frequencies are applicable to all oms of pth
# checks that spectrum is not None else indicate blocking reason
if center_n is not None:
# checks that requested_m is fitting startm and stopm
if 2 * requested_m > (stopn - startn + 1):
msg = f'candidate: {(center_n, startn, stopn)} is not consistant ' +\
f'with {requested_m}'
LOGGER.critical(msg)
raise ValueError(msg)
for oms_elem in path_oms:
oms_list[oms_elem].assign_spectrum(center_n, requested_m)
oms_list[oms_elem].add_service(rqs[i].request_id, nb_wl)
rqs[i].blocked = False
rqs[i].N = center_n
rqs[i].M = requested_m
else: else:
rqs[i].blocked = True # computes the number of channels required for path_bandwidth and the min required nb of slots
rqs[i].N = 0 # for one channel (corresponds to the spacing)
rqs[i].M = 0 nb_wl, required_m = compute_spectrum_slot_vs_bandwidth(rq.path_bandwidth,
rqs[i].blocking_reason = 'NO_SPECTRUM' rq.spacing, rq.bit_rate)
_, per_channel_m = compute_spectrum_slot_vs_bandwidth(rq.bit_rate,
rq.spacing, rq.bit_rate)
# find oms ids that are concerned both by pth and rpth
path_oms = build_path_oms_id_list(pth + rpth)
if getattr(rq, 'M', None) is not None and all(rq.M):
# if all M are well defined: Consistency check that the requested M are enough to carry the nb_wl:
# check that the integer number of per_channel_m carried in each M value is enough to carry nb_wl.
# if not, blocks the demand
nb_channels_of_request = sum([m // per_channel_m for m in rq.M])
# TODO: elaborate a more accurate estimate with nb_wl * min_spacing + possibly guardbands in case of
# superchannel closed packing.
if nb_wl > nb_channels_of_request:
rq.N = None
rq.M = None
rq.blocking_reason = 'NOT_ENOUGH_RESERVED_SPECTRUM'
# need to stop here for this request and not go though spectrum selection process
continue
# Use the req.M even if nb_wl and required_m are smaller.
# first fit strategy: assign as many lambda as possible in the None remaining N, M values
selected_n, selected_m, remaining_slots_to_serve = \
compute_n_m(required_m, rq, path_oms, oms_list, per_channel_m)
# if there are some remaining_slots_to_serve, this means that provided rq.M and rq.N values were
# not possible. Then do not go though spectrum assignment process and blocks the demand
if remaining_slots_to_serve > 0:
rq.N = None
rq.M = None
rq.blocking_reason = 'NO_SPECTRUM'
continue
for oms_elem in path_oms:
for this_n, this_m in zip(selected_n, selected_m):
if this_m is not None:
oms_list[oms_elem].assign_spectrum(this_n, this_m)
oms_list[oms_elem].add_service(rq.request_id, nb_wl)
rq.N = selected_n
rq.M = selected_m

View File

@@ -1,559 +0,0 @@
*********************************************
Equipment and Network description definitions
*********************************************
1. Equipment description
########################
Equipment description defines equipment types and those parameters.
Description is made in JSON file with predefined structure. By default
**gnpy-transmission-example** uses **eqpt_config.json** file and that
can be changed with **-e** or **--equipment** command line parameter.
Parsing of JSON file is made with
**gnpy.core.equipment.load_equipment(equipment_description)** and return
value is a dictionary of format **dict[equipment
type][subtype]=object**
1.1. Structure definition
*************************
1.1.1. Equipment types
*************************
Every equipment type is defined in JSON root with according name and
array of parameters as value.
.. code-block:: none
{"Edfa": [...],
"Fiber": [...]
}
1.1.2. Equipment parameters and subtypes
*****************************************
Array of parameters is a list of objects with unordered parameter name
and its value definition. In case of multiple equipment subtypes each
object contains **"type_variety":”type name”** name:value combination,
if only one subtype exists **"type_variety"** name is not mandatory and
it will be marked with **”default”** value.
.. code-block:: json
{"Edfa": [{
"type_variety": "std_medium_gain",
"type_def": "variable_gain",
"gain_flatmax": 26,
"gain_min": 15,
"p_max": 23,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain",
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 23,
"nf_min": 6.5,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
}
],
"Fiber": [{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"gamma": 0.00127
}
]
}
1.2. Equipment parameters by type
*********************************
1.2.1. EDFA element
*******************
Four types of EDFA definition are possible. Description JSON file
location is in **gnpy-transmission-example** folder:
- Advanced with JSON file describing gain/noise figure tilt and
gain/noise figure ripple. **"advanced_config_from_json"** value
contains filename.
.. code-block:: json-object
"Edfa":[{
"type_variety": "high_detail_model_example",
"gain_flatmax": 25,
"gain_min": 15,
"p_max": 21,
"advanced_config_from_json": "std_medium_gain_advanced_config.json",
"out_voa_auto": false,
"allowed_for_design": false
}
]
- Variable gain with JSON file describing gain figure tilt and gain/noise
figure ripple. **”default_edfa_config.json”** as source file.
.. code-block:: json-object
"Edfa":[{
"type_variety": "std_medium_gain",
"type_def": "variable_gain",
"gain_flatmax": 26,
"gain_min": 15,
"p_max": 23,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
}
]
- Fixed gain with JSON file describing gain figure tilt and gain/noise
figure ripple. **”default_edfa_config.json”** as source file.
.. code-block:: json-object
"Edfa":[{
"type_variety": "std_fixed_gain",
"type_def": "fixed_gain",
"gain_flatmax": 21,
"gain_min": 20,
"p_max": 21,
"nf0": 5.5,
"allowed_for_design": false
}
]
- openroadm with JSON file describing gain figure tilt and gain/noise
figure ripple. **”default_edfa_config.json”** as source file.
.. code-block:: json-object
"Edfa":[{
"type_variety": "openroadm_ila_low_noise",
"type_def": "openroadm",
"gain_flatmax": 27,
"gain_min": 12,
"p_max": 22,
"nf_coef": [-8.104e-4,-6.221e-2,-5.889e-1,37.62],
"allowed_for_design": false
}
]
1.2.2. Fiber element
********************
Fiber element with its parameters:
.. code-block:: json-object
"Fiber":[{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"gamma": 0.00127
}
]
RamanFiber element
******************
A special variant of the regular ``Fiber`` where the simulation engine accounts for the Raman effect.
The newly added parameters are nested in the ``raman_efficiency`` dictionary.
Its shape corresponds to typical properties of silica.
More details are available from :cite:`curri_merit_2016`.
The ``cr`` property is the normailzed Raman efficiency, so it is is (almost) independent of the fiber type, while the coefficient actually giving Raman gain is g_R=C_R/Aeff.
The ``frequency_offset`` represents the spectral difference between the pumping photon and the one receiving energy.
.. code-block:: json-object
"RamanFiber":[{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"gamma": 0.00127,
"raman_efficiency": {
"cr":[
0, 9.4E-06, 2.92E-05, 4.88E-05, 6.82E-05, 8.31E-05, 9.4E-05, 0.0001014, 0.0001069, 0.0001119,
0.0001217, 0.0001268, 0.0001365, 0.000149, 0.000165, 0.000181, 0.0001977, 0.0002192, 0.0002469,
0.0002749, 0.0002999, 0.0003206, 0.0003405, 0.0003592, 0.000374, 0.0003826, 0.0003841, 0.0003826,
0.0003802, 0.0003756, 0.0003549, 0.0003795, 0.000344, 0.0002933, 0.0002024, 0.0001158, 8.46E-05,
7.14E-05, 6.86E-05, 8.5E-05, 8.93E-05, 9.01E-05, 8.15E-05, 6.67E-05, 4.37E-05, 3.28E-05, 2.96E-05,
2.65E-05, 2.57E-05, 2.81E-05, 3.08E-05, 3.67E-05, 5.85E-05, 6.63E-05, 6.36E-05, 5.5E-05, 4.06E-05,
2.77E-05, 2.42E-05, 1.87E-05, 1.6E-05, 1.4E-05, 1.13E-05, 1.05E-05, 9.8E-06, 9.8E-06, 1.13E-05,
1.64E-05, 1.95E-05, 2.38E-05, 2.26E-05, 2.03E-05, 1.48E-05, 1.09E-05, 9.8E-06, 1.05E-05, 1.17E-05,
1.25E-05, 1.21E-05, 1.09E-05, 9.8E-06, 8.2E-06, 6.6E-06, 4.7E-06, 2.7E-06, 1.9E-06, 1.2E-06, 4E-07,
2E-07, 1E-07
],
"frequency_offset":[
0, 0.5e12, 1e12, 1.5e12, 2e12, 2.5e12, 3e12, 3.5e12, 4e12, 4.5e12, 5e12, 5.5e12, 6e12, 6.5e12, 7e12,
7.5e12, 8e12, 8.5e12, 9e12, 9.5e12, 10e12, 10.5e12, 11e12, 11.5e12, 12e12, 12.5e12, 12.75e12,
13e12, 13.25e12, 13.5e12, 14e12, 14.5e12, 14.75e12, 15e12, 15.5e12, 16e12, 16.5e12, 17e12,
17.5e12, 18e12, 18.25e12, 18.5e12, 18.75e12, 19e12, 19.5e12, 20e12, 20.5e12, 21e12, 21.5e12,
22e12, 22.5e12, 23e12, 23.5e12, 24e12, 24.5e12, 25e12, 25.5e12, 26e12, 26.5e12, 27e12, 27.5e12, 28e12,
28.5e12, 29e12, 29.5e12, 30e12, 30.5e12, 31e12, 31.5e12, 32e12, 32.5e12, 33e12, 33.5e12, 34e12, 34.5e12,
35e12, 35.5e12, 36e12, 36.5e12, 37e12, 37.5e12, 38e12, 38.5e12, 39e12, 39.5e12, 40e12, 40.5e12, 41e12,
41.5e12, 42e12
]
}
}
]
1.2.3 Roadm element
*******************
Roadm element with its parameters:
.. code-block:: json-object
"Roadms":[{
"gain_mode_default_loss": 20,
"power_mode_pout_target": -20,
"add_drop_osnr": 38
}
]
1.2.3. Spans element
********************
Spans element with its parameters:
.. code-block:: json-object
"Spans":[{
"power_mode":true,
"delta_power_range_db": [0,0,0.5],
"max_length": 150,
"length_units": "km",
"max_loss": 28,
"padding": 10,
"EOL": 0,
"con_in": 0,
"con_out": 0
}
]
1.2.4. Spectral Information
***************************
Spectral information with its parameters:
.. code-block:: json-object
"SI":[{
"f_min": 191.3e12,
"baud_rate": 32e9,
"f_max":195.1e12,
"spacing": 50e9,
"power_dbm": 0,
"power_range_db": [0,0,0.5],
"roll_off": 0.15,
"tx_osnr": 40,
"sys_margins": 0
}
]
1.2.5. Transceiver element
**************************
Transceiver element with its parameters. **”mode”** can contain multiple
Transceiver operation formats.
Note that ``OSNR`` parameter refers to the receiver's minimal OSNR threshold for a given mode.
.. code-block:: json-object
"Transceiver":[{
"frequency":{
"min": 191.35e12,
"max": 196.1e12
},
"mode":[
{
"format": "mode 1",
"baud_rate": 32e9,
"OSNR": 11,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 40,
"min_spacing": 37.5e9,
"cost":1
},
{
"format": "mode 2",
"baud_rate": 66e9,
"OSNR": 15,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 40,
"min_spacing": 75e9,
"cost":1
}
]
}
]
***********************
2. Network description
***********************
Network description defines network elements with additional to
equipment description parameters, metadata and elements interconnection.
Description is made in JSON file with predefined structure. By default
**gnpy-transmission-example** uses **edfa_example_network.json** file
and can be changed from command line. Parsing of JSON file is made with
**gnpy.core.network.load_network(network_description,
equipment_description)** and return value is **DiGraph** object which
mimics network description.
2.1. Structure definition
##########################
2.1.1. File root structure
***************************
Network description JSON file root consist of three unordered parts:
- network_name name of described network or service, is not used as
of now
- elements - contains array of network element objects with their
respective parameters
- connections contains array of unidirectional connection objects
.. code-block:: none
{"network_name": "Example Network",
"elements": [{...},
{...}
],
"connections": [{...},
{...}
]
}
2.1.2. Elements parameters and subtypes
****************************************
Array of network element objects consist of unordered parameter names
and those values. In case of **"type_variety"** absence
**"type_variety":”default”** name:value combination is used. As of the
moment, existence of used **"type_variety"** in equipment description is
obligatory.
2.2. Element parameters by type
*********************************
2.2.1. Transceiver element
***************************
Transceiver element with its parameters.
.. code-block:: json
{"uid": "trx Site_A",
"metadata": {
"location": {
"city": "Site_A",
"region": "",
"latitude": 0,
"longitude": 0
}
},
"type": "Transceiver"
}
2.2.2. ROADM element
*********************
ROADM element with its parameters. **“params”** is optional, if not used
default loss value of 20dB is used.
.. code-block:: json
{"uid": "roadm Site_A",
"metadata": {
"location": {
"city": "Site_A",
"region": "",
"latitude": 0,
"longitude": 0
}
},
"type": "Roadm",
"params": {
"loss": 17
}
}
2.2.3. Fused element
*********************
Fused element with its parameters. **“params”** is optional, if not used
default loss value of 1dB is used.
.. code-block:: json
{"uid": "ingress fused spans in Site_B",
"metadata": {
"location": {
"city": "Site_B",
"region": "",
"latitude": 0,
"longitude": 0
}
},
"type": "Fused",
"params": {
"loss": 0.5
}
}
2.2.4. Fiber element
*********************
Fiber element with its parameters.
.. code-block:: json
{"uid": "fiber (Site_A \\u2192 Site_B)",
"metadata": {
"location": {
"city": "",
"region": "",
"latitude": 0.0,
"longitude": 0.0
}
},
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 40.0,
"length_units": "km",
"loss_coef": 0.2
}
}
2.2.5. RamanFiber element
*************************
.. code-block:: json
{
"uid": "Span1",
"type": "RamanFiber",
"type_variety": "SSMF",
"operational": {
"temperature": 283,
"raman_pumps": [
{
"power": 200e-3,
"frequency": 205e12,
"propagation_direction": "counterprop"
},
{
"power": 206e-3,
"frequency": 201e12,
"propagation_direction": "counterprop"
}
]
},
"params": {
"type_variety": "SSMF",
"length": 80.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0,
"con_in": 0.5,
"con_out": 0.5
},
"metadata": {
"location": {
"latitude": 1,
"longitude": 0,
"city": null,
"region": ""
}
}
}
2.2.6. EDFA element
********************
EDFA element with its parameters.
.. code-block:: json
{"uid": "Edfa1",
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": 16,
"tilt_target": 0
},
"metadata": {
"location": {
"city": "Site_A",
"region": "",
"latitude": 2,
"longitude": 0
}
}
}
2.3. Connections objects
*************************
Each unidirectional connection object in connections array consist of
two unordered **”from_node”** and **”to_node”** name pair with values
corresponding to element **”uid”**
.. code-block:: json
{"from_node": "roadm Site_C",
"to_node": "trx Site_C"
}
************************
3. Simulation Parameters
************************
Additional details of the simulation are controlled via ``sim_params.json``:
.. code-block:: json
{
"raman_parameters": {
"flag_raman": true,
"space_resolution": 10e3,
"tolerance": 1e-8
},
"nli_parameters": {
"nli_method_name": "ggn_spectrally_separated",
"wdm_grid_size": 50e9,
"dispersion_tolerance": 1,
"phase_shift_tolerance": 0.1,
"computed_channels": [1, 18, 37, 56, 75]
}
}

View File

@@ -1,7 +0,0 @@
matplotlib>=3.3.3,<4
networkx>=2.5,<3
numpy>=1.19.4,<2
pandas>=1.1.5,<2
pbr>=5.5.1,<6
scipy>=1.5.4,<2
xlrd>=1.2.0,<2

View File

@@ -1,16 +1,15 @@
[metadata] [metadata]
name = gnpy name = gnpy
description = Route planning and optimization tool for mesh optical networks description-file = README.md
description-file = README.rst description-content-type = text/markdown; variant=GFM
description-content-type = text/x-rst; charset=UTF-8
author = Telecom Infra Project author = Telecom Infra Project
author-email = jan.kundrat@telecominfraproject.com author-email = jkt@jankundrat.com
license = BSD-3-Clause license = BSD-3-Clause
home-page = https://github.com/Telecominfraproject/oopt-gnpy home-page = https://github.com/Telecominfraproject/oopt-gnpy
project_urls = project_urls =
Bug Tracker = https://github.com/Telecominfraproject/oopt-gnpy/issues Bug Tracker = https://github.com/Telecominfraproject/oopt-gnpy/issues
Documentation = https://gnpy.readthedocs.io/ Documentation = https://gnpy.readthedocs.io/
python-requires = >=3.6 python-requires = >=3.8
classifier = classifier =
Development Status :: 5 - Production/Stable Development Status :: 5 - Production/Stable
Intended Audience :: Developers Intended Audience :: Developers
@@ -20,10 +19,11 @@ classifier =
Natural Language :: English Natural Language :: English
Programming Language :: Python Programming Language :: Python
Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Programming Language :: Python :: 3.12
Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: CPython
Topic :: Scientific/Engineering Topic :: Scientific/Engineering
Topic :: Scientific/Engineering :: Physics Topic :: Scientific/Engineering :: Physics
@@ -42,9 +42,6 @@ warnerrors = True
[files] [files]
packages = gnpy packages = gnpy
data_files =
examples = examples/*
# FIXME: solve example data files
[options.entry_points] [options.entry_points]
console_scripts = console_scripts =
@@ -52,3 +49,35 @@ console_scripts =
gnpy-transmission-example = gnpy.tools.cli_examples:transmission_main_example gnpy-transmission-example = gnpy.tools.cli_examples:transmission_main_example
gnpy-path-request = gnpy.tools.cli_examples:path_requests_run gnpy-path-request = gnpy.tools.cli_examples:path_requests_run
gnpy-convert-xls = gnpy.tools.convert:_do_convert gnpy-convert-xls = gnpy.tools.convert:_do_convert
[options]
install_requires =
# matplotlib 3.8 removed support for Python 3.8
matplotlib>=3.7.3,<4
# networkx 3.2 removed support for Python 3.8
networkx>=3.1,<4
# numpy 1.25 removed support for Python 3.8
numpy>=1.24.4,<2
pbr>=6.0.0,<7
# scipy 1.11 removed support for Python 3.8
scipy>=1.10.1,<2
# xlrd 2.x removed support for .xlsx, it's only .xls now
xlrd>=1.2.0,<2
[options.extras_require]
tests =
build>=1.0.3,<2
pytest>=7.4.3,<8
# pandas 2.1 removed support for Python 3.8
pandas>=2.0.3,<3
# flake v6 killed the --diff option
flake8>=5.0.4,<6
docs =
alabaster>=0.7.12,<1
docutils>=0.17.1,<1
myst-parser>=0.16.1,<1
Pygments>=2.11.2,<3
rstcheck
Sphinx>=5.3.0,<6
sphinxcontrib-bibtex>=2.4.1,<3

View File

@@ -1,135 +0,0 @@
#!/usr/bin/env python3
from json import dump
from pathlib import Path
from argparse import ArgumentParser
from collections import namedtuple
from gnpy.tools.json_io import load_json
class Results(namedtuple('Results', 'missing extra different expected actual')):
def _asdict(self):
return {'missing': self.missing,
'extra': self.extra,
'different': self.different}
def __str__(self):
rv = []
if self.missing:
rv.append('Missing: {len(self.missing)}/{len(self.expected)}')
rv.extend(f'\t{x}' for x in sorted(self.missing))
if self.extra:
rv.append('Extra: {len(self.extra)}/{len(self.expected)}')
rv.extend(f'\t{x}' for x in sorted(self.extra))
if self.different:
rv.append('Different: {len(self.different)}/{len(self.expected)}')
rv.extend(f'\tExpected: {x}\n\tActual: {y}' for x, y in self.different)
if not self.missing and not self.extra and not self.different:
rv.append('All match!')
return '\n'.join(rv)
class NetworksResults(namedtuple('NetworksResult', 'elements connections')):
def _asdict(self):
return {'elements': self.elements._asdict(),
'connections': self.connections._asdict()}
def __str__(self):
return '\n'.join([
'Elements'.center(40, '='),
str(self.elements),
'Connections'.center(40, '='),
str(self.connections),
])
class ServicesResults(namedtuple('ServicesResult', 'requests synchronizations')):
def _asdict(self):
return {'requests': self.requests.asdict(),
'synchronizations': self.synchronizations.asdict()}
def __str__(self):
return '\n'.join([
'Requests'.center(40, '='),
str(self.requests),
'Synchronizations'.center(40, '='),
str(self.synchronizations),
])
class PathsResults(namedtuple('PathsResults', 'paths')):
def _asdict(self):
return {'paths': self.paths.asdict()}
def __str__(self):
return '\n'.join([
'Paths'.center(40, '='),
str(self.paths),
])
def compare(expected, actual, key=lambda x: x):
expected = {key(el): el for el in expected}
actual = {key(el): el for el in actual}
missing = set(expected) - set(actual)
extra = set(actual) - set(expected)
different = [(expected[x], actual[x]) for
x in set(expected) & set(actual)
if expected[x] != actual[x]]
return Results(missing, extra, different, expected, actual)
def compare_networks(expected, actual):
elements = compare(expected['elements'], actual['elements'],
key=lambda el: el['uid'])
connections = compare(expected['connections'], actual['connections'],
key=lambda el: (el['from_node'], el['to_node']))
return NetworksResults(elements, connections)
def compare_services(expected, actual):
requests = compare(expected['path-request'], actual['path-request'],
key=lambda el: el['request-id'])
synchronizations = compare(expected['path-request'], expected['path-request'],
key=lambda el: el['request-id'])
if 'synchronization' in expected.keys():
synchronizations = compare(expected['synchronization'], actual['synchronization'],
key=lambda el: el['synchronization-id'])
return ServicesResults(requests, synchronizations)
def compare_paths(expected_output, actual_output):
paths = compare(expected['path'], actual['path'], key=lambda el: el['path-id'])
return PathsResults(paths)
COMPARISONS = {
'networks': compare_networks,
'services': compare_services,
'paths': compare_paths,
}
parser = ArgumentParser()
parser.add_argument('expected_output', type=Path, metavar='FILE')
parser.add_argument('actual_output', type=Path, metavar='FILE')
parser.add_argument('-o', '--output', default=None)
parser.add_argument('-c', '--comparison', choices=COMPARISONS, default='networks')
def encode_sets(obj):
if isinstance(obj, set):
return list(obj)
raise TypeError(f'{obj!r} is not JSON serializable!')
if __name__ == '__main__':
args = parser.parse_args()
expected = load_json(args.expected_output)
actual = load_json(args.actual_output)
result = COMPARISONS[args.comparison](expected, actual)
if args.output:
with open(args.output, 'w', encoding='utf-8') as f:
dump(result, f, default=encode_sets, indent=2, ensure_ascii=False)
else:
print(str(result))

13
tests/conftest.py Normal file
View File

@@ -0,0 +1,13 @@
# SPDX-License-Identifier: BSD-3-Clause
#
# Copyright (C) 2020 Telecom Infra Project and GNPy contributors
# see LICENSE.md for a list of contributors
#
import pytest
from gnpy.core.parameters import SimParams, NLIParams, RamanParams
@pytest.fixture
def set_sim_params(monkeypatch):
monkeypatch.setattr(SimParams, '_shared_dict', {'nli_params': NLIParams(), 'raman_params': RamanParams()})

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
{
"path-request": [
{
"request-id": "0",
"source": "trx Abilene",
"destination": "trx Albany",
"src-tp-id": "trx Abilene",
"dst-tp-id": "trx Albany",
"bidirectional": false,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"trx_mode": "mode 3",
"spacing": 62500000000.0,
"path_bandwidth": 100000000000.0
}
}
}
]
}

View File

@@ -0,0 +1,31 @@
{
"nf_ripple": [
0.0
],
"gain_ripple": [
0.0
],
"f_min": 191.275e12,
"f_max": 196.125e12,
"dgt": [
1.0, 1.017807767853702, 1.0356155337864215, 1.0534217504465226, 1.0712204022764056, 1.0895983485572227,
1.108555289615659, 1.1280891949729075, 1.1476135933863398, 1.1672278304018044, 1.1869318618366975,
1.2067249615595257, 1.2264996957264114, 1.2428104897182262, 1.2556591482982988, 1.2650555289898042,
1.2744470198196236, 1.2838336236692311, 1.2932153453410835, 1.3040618749785347, 1.316383926863083,
1.3301807335621048, 1.3439818461440451, 1.3598972673004606, 1.3779439775587023, 1.3981208704326855,
1.418273806730323, 1.4340878115214444, 1.445565137158368, 1.45273959485914, 1.4599103316162523,
1.4670307626366115, 1.474100442252211, 1.48111939735681, 1.488134243479226, 1.495145456062699,
1.502153039909686, 1.5097346239790443, 1.5178910621476225, 1.5266220576235803, 1.5353620432989845,
1.545374152761467, 1.5566577309558969, 1.569199764184379, 1.5817353179379183, 1.5986915141218316,
1.6201194134191075, 1.6460167077689267, 1.6719047669939942, 1.6918150918099673, 1.7057507692361864,
1.7137640932265894, 1.7217732861435076, 1.7297783508684146, 1.737780757913635, 1.7459181197626403,
1.7541903672600494, 1.7625959636196327, 1.7709972329654864, 1.7793941781790852, 1.7877868031023945,
1.7961751115773796, 1.8045606557581335, 1.8139629377087627, 1.824381436842932, 1.835814081380705,
1.847275503201129, 1.862235672444246, 1.8806927939516411, 1.9026104247588487, 1.9245345552113182,
1.9482128147680253, 1.9736443063300082, 2.0008103857988204, 2.0279625371819305, 2.055100772005235,
2.082225099873648, 2.1183028432496016, 2.16337565384239, 2.2174389328192197, 2.271520771371253,
2.322373696229342, 2.3699990328716107, 2.414398437185221, 2.4587748041127506, 2.499446286796604,
2.5364027376452056, 2.5696460593920065, 2.602860350286428, 2.630396440815385, 2.6521732021128046,
2.6681935771243177, 2.6841217449620203, 2.6947834587664494, 2.705443819238505, 2.714526681131686
]
}

View File

@@ -1,296 +0,0 @@
{
"nf_ripple": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
"gain_ripple": [
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
],
"dgt": [
2.714526681131686,
2.705443819238505,
2.6947834587664494,
2.6841217449620203,
2.6681935771243177,
2.6521732021128046,
2.630396440815385,
2.602860350286428,
2.5696460593920065,
2.5364027376452056,
2.499446286796604,
2.4587748041127506,
2.414398437185221,
2.3699990328716107,
2.322373696229342,
2.271520771371253,
2.2174389328192197,
2.16337565384239,
2.1183028432496016,
2.082225099873648,
2.055100772005235,
2.0279625371819305,
2.0008103857988204,
1.9736443063300082,
1.9482128147680253,
1.9245345552113182,
1.9026104247588487,
1.8806927939516411,
1.862235672444246,
1.847275503201129,
1.835814081380705,
1.824381436842932,
1.8139629377087627,
1.8045606557581335,
1.7961751115773796,
1.7877868031023945,
1.7793941781790852,
1.7709972329654864,
1.7625959636196327,
1.7541903672600494,
1.7459181197626403,
1.737780757913635,
1.7297783508684146,
1.7217732861435076,
1.7137640932265894,
1.7057507692361864,
1.6918150918099673,
1.6719047669939942,
1.6460167077689267,
1.6201194134191075,
1.5986915141218316,
1.5817353179379183,
1.569199764184379,
1.5566577309558969,
1.545374152761467,
1.5353620432989845,
1.5266220576235803,
1.5178910621476225,
1.5097346239790443,
1.502153039909686,
1.495145456062699,
1.488134243479226,
1.48111939735681,
1.474100442252211,
1.4670307626366115,
1.4599103316162523,
1.45273959485914,
1.445565137158368,
1.4340878115214444,
1.418273806730323,
1.3981208704326855,
1.3779439775587023,
1.3598972673004606,
1.3439818461440451,
1.3301807335621048,
1.316383926863083,
1.3040618749785347,
1.2932153453410835,
1.2838336236692311,
1.2744470198196236,
1.2650555289898042,
1.2556591482982988,
1.2428104897182262,
1.2264996957264114,
1.2067249615595257,
1.1869318618366975,
1.1672278304018044,
1.1476135933863398,
1.1280891949729075,
1.108555289615659,
1.0895983485572227,
1.0712204022764056,
1.0534217504465226,
1.0356155337864215,
1.017807767853702,
1.0
]
}

View File

@@ -1,4 +1,5 @@
{ "Edfa":[{ {
"Edfa": [{
"type_variety": "CienaDB_medium_gain", "type_variety": "CienaDB_medium_gain",
"type_def": "advanced_model", "type_def": "advanced_model",
"gain_flatmax": 25, "gain_flatmax": 25,
@@ -7,8 +8,7 @@
"advanced_config_from_json": "std_medium_gain_advanced_config.json", "advanced_config_from_json": "std_medium_gain_advanced_config.json",
"out_voa_auto": false, "out_voa_auto": false,
"allowed_for_design": true "allowed_for_design": true
}, }, {
{
"type_variety": "std_medium_gain", "type_variety": "std_medium_gain",
"type_def": "variable_gain", "type_def": "variable_gain",
"gain_flatmax": 26, "gain_flatmax": 26,
@@ -18,8 +18,7 @@
"nf_max": 10, "nf_max": 10,
"out_voa_auto": false, "out_voa_auto": false,
"allowed_for_design": true "allowed_for_design": true
}, }, {
{
"type_variety": "std_low_gain", "type_variety": "std_low_gain",
"type_def": "variable_gain", "type_def": "variable_gain",
"gain_flatmax": 16, "gain_flatmax": 16,
@@ -29,8 +28,7 @@
"nf_max": 11, "nf_max": 11,
"out_voa_auto": false, "out_voa_auto": false,
"allowed_for_design": true "allowed_for_design": true
}, }, {
{
"type_variety": "test", "type_variety": "test",
"type_def": "variable_gain", "type_def": "variable_gain",
"gain_flatmax": 25, "gain_flatmax": 25,
@@ -40,8 +38,7 @@
"nf_max": 10, "nf_max": 10,
"out_voa_auto": false, "out_voa_auto": false,
"allowed_for_design": true "allowed_for_design": true
}, }, {
{
"type_variety": "test_fixed_gain", "type_variety": "test_fixed_gain",
"type_def": "fixed_gain", "type_def": "fixed_gain",
"gain_flatmax": 21, "gain_flatmax": 21,
@@ -49,8 +46,7 @@
"p_max": 21, "p_max": 21,
"nf0": 5, "nf0": 5,
"allowed_for_design": true "allowed_for_design": true
}, }, {
{
"type_variety": "std_booster", "type_variety": "std_booster",
"type_def": "fixed_gain", "type_def": "fixed_gain",
"gain_flatmax": 21, "gain_flatmax": 21,
@@ -63,7 +59,7 @@
"Fiber": [{ "Fiber": [{
"type_variety": "SSMF", "type_variety": "SSMF",
"dispersion": 1.67e-05, "dispersion": 1.67e-05,
"gamma": 0.00127, "effective_area": 83e-12,
"pmd_coef": 1.265e-15 "pmd_coef": 1.265e-15
} }
], ],
@@ -82,15 +78,104 @@
} }
], ],
"Roadm": [{ "Roadm": [{
"type_variety": "example_test",
"target_pch_out_db": -18,
"add_drop_osnr": 35,
"pmd": 1e-12,
"pdl": 0.5,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
},
"roadm-path-impairments": []
}, {
"type_variety": "example_detailed_impairments",
"target_pch_out_db": -20,
"add_drop_osnr": 35,
"pmd": 0,
"pdl": 0,
"restrictions": {
"preamp_variety_list":[],
"booster_variety_list":[]
},
"roadm-path-impairments": [
{
"roadm-path-impairments-id": 0,
"roadm-express-path": [{
"frequency-range": {
"lower-frequency": 191.3e12,
"upper-frequency": 196.1e12
},
"roadm-pmd": 0,
"roadm-cd": 0,
"roadm-pdl": 0,
"roadm-inband-crosstalk": 0,
"roadm-maxloss": 16.5
}
]
}, {
"roadm-path-impairments-id": 1,
"roadm-add-path": [{
"frequency-range": {
"lower-frequency": 191.3e12,
"upper-frequency": 196.1e12
},
"roadm-pmd": 0,
"roadm-cd": 0,
"roadm-pdl": 0,
"roadm-inband-crosstalk": 0,
"roadm-maxloss": 11.5,
"roadm-pmax": 2.5,
"roadm-osnr": 41,
"roadm-noise-figure": 23
}, {
"frequency-range": {
"lower-frequency": 186.3e12,
"upper-frequency": 190.1e12
},
"roadm-pmd": 0,
"roadm-cd": 0,
"roadm-pdl": 0.5,
"roadm-inband-crosstalk": 0,
"roadm-maxloss": 5,
"roadm-pmax": 0,
"roadm-osnr": 35,
"roadm-noise-figure": 6
}]
}, {
"roadm-path-impairments-id": 2,
"roadm-drop-path": [{
"frequency-range": {
"lower-frequency": 191.3e12,
"upper-frequency": 196.1e12
},
"roadm-pmd": 0,
"roadm-cd": 0,
"roadm-pdl": 0,
"roadm-inband-crosstalk": 0,
"roadm-maxloss": 11.5,
"roadm-minloss": 7.5,
"roadm-typloss": 10,
"roadm-pmin": -13.5,
"roadm-pmax": -9.5,
"roadm-ptyp": -12,
"roadm-osnr": 41,
"roadm-noise-figure": 15
}]
}]
}, {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"add_drop_osnr": 38, "add_drop_osnr": 38,
"pmd": 0, "pmd": 0,
"pdl": 0,
"restrictions": { "restrictions": {
"preamp_variety_list": [], "preamp_variety_list": [],
"booster_variety_list": [] "booster_variety_list": []
} }
}], }
],
"SI": [{ "SI": [{
"type_variety": "default",
"f_min": 191.3e12, "f_min": 191.3e12,
"f_max": 196.1e12, "f_max": 196.1e12,
"baud_rate": 32e9, "baud_rate": 32e9,
@@ -108,8 +193,7 @@
"min": 191.35e12, "min": 191.35e12,
"max": 196.1e12 "max": 196.1e12
}, },
"mode":[ "mode": [{
{
"format": "PS_SP64_1", "format": "PS_SP64_1",
"baud_rate": 32e9, "baud_rate": 32e9,
"OSNR": 11, "OSNR": 11,
@@ -118,8 +202,7 @@
"tx_osnr": 100, "tx_osnr": 100,
"min_spacing": 50e9, "min_spacing": 50e9,
"cost": 1 "cost": 1
}, }, {
{
"format": "PS_SP64_2", "format": "PS_SP64_2",
"baud_rate": 64e9, "baud_rate": 64e9,
"OSNR": 15, "OSNR": 15,
@@ -128,8 +211,7 @@
"tx_osnr": 100, "tx_osnr": 100,
"min_spacing": 75e9, "min_spacing": 75e9,
"cost": 1 "cost": 1
}, }, {
{
"format": "mode 1", "format": "mode 1",
"baud_rate": 32e9, "baud_rate": 32e9,
"OSNR": 11, "OSNR": 11,
@@ -138,8 +220,7 @@
"tx_osnr": 100, "tx_osnr": 100,
"min_spacing": 50e9, "min_spacing": 50e9,
"cost": 1 "cost": 1
}, }, {
{
"format": "mode 2", "format": "mode 2",
"baud_rate": 64e9, "baud_rate": 64e9,
"OSNR": 15, "OSNR": 15,
@@ -150,15 +231,13 @@
"cost": 1 "cost": 1
} }
] ]
}, }, {
{
"type_variety": "Voyager_16QAM", "type_variety": "Voyager_16QAM",
"frequency": { "frequency": {
"min": 191.35e12, "min": 191.35e12,
"max": 196.1e12 "max": 196.1e12
}, },
"mode":[ "mode": [{
{
"format": "16QAM", "format": "16QAM",
"baud_rate": 32e9, "baud_rate": 32e9,
"OSNR": 19, "OSNR": 19,
@@ -169,15 +248,13 @@
"cost": 1 "cost": 1
} }
] ]
}, }, {
{
"type_variety": "Voyager", "type_variety": "Voyager",
"frequency": { "frequency": {
"min": 191.35e12, "min": 191.35e12,
"max": 196.1e12 "max": 196.1e12
}, },
"mode":[ "mode": [{
{
"format": "mode 1", "format": "mode 1",
"baud_rate": 32e9, "baud_rate": 32e9,
"OSNR": 12, "OSNR": 12,
@@ -186,8 +263,7 @@
"tx_osnr": 45, "tx_osnr": 45,
"min_spacing": 50e9, "min_spacing": 50e9,
"cost": 1 "cost": 1
}, }, {
{
"format": "mode 3", "format": "mode 3",
"baud_rate": 44e9, "baud_rate": 44e9,
"OSNR": 18, "OSNR": 18,
@@ -196,8 +272,7 @@
"tx_osnr": 45, "tx_osnr": 45,
"min_spacing": 62.5e9, "min_spacing": 62.5e9,
"cost": 1 "cost": 1
}, }, {
{
"format": "mode 2", "format": "mode 2",
"baud_rate": 66e9, "baud_rate": 66e9,
"OSNR": 21, "OSNR": 21,
@@ -206,8 +281,7 @@
"tx_osnr": 45, "tx_osnr": 45,
"min_spacing": 75e9, "min_spacing": 75e9,
"cost": 1 "cost": 1
}, }, {
{
"format": "mode 2 - fake", "format": "mode 2 - fake",
"baud_rate": 66e9, "baud_rate": 66e9,
"OSNR": 21, "OSNR": 21,
@@ -216,8 +290,7 @@
"tx_osnr": 45, "tx_osnr": 45,
"min_spacing": 75e9, "min_spacing": 75e9,
"cost": 1 "cost": 1
}, }, {
{
"format": "mode 4", "format": "mode 4",
"baud_rate": 66e9, "baud_rate": 66e9,
"OSNR": 16, "OSNR": 16,
@@ -230,5 +303,4 @@
] ]
} }
] ]
} }

View File

@@ -0,0 +1,396 @@
{ "Edfa":[{
"type_variety": "CienaDB_medium_gain",
"type_def": "advanced_model",
"gain_flatmax": 25,
"gain_min": 15,
"p_max": 21,
"advanced_config_from_json": "std_medium_gain_advanced_config.json",
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_medium_gain",
"f_min": 191.25e12,
"f_max": 196.15e12,
"type_def": "variable_gain",
"gain_flatmax": 26,
"gain_min": 15,
"p_max": 21,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_medium_gain_L",
"f_min": 186.55e12,
"f_max": 190.05e12,
"type_def": "variable_gain",
"gain_flatmax": 26,
"gain_min": 15,
"p_max": 21,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain",
"f_min": 191.25e12,
"f_max": 196.15e12,
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 21,
"nf_min": 7,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain_reduced_band",
"f_min": 192.25e12,
"f_max": 196.15e12,
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 21,
"nf_min": 7,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain_reduced",
"f_min": 192.25e12,
"f_max": 196.15e12,
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 21,
"nf_min": 7,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain_bis",
"f_min": 191.25e12,
"f_max": 196.15e12,
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 21,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
}, {
"type_variety": "std_low_gain_L_ter",
"f_min": 186.55e12,
"f_max": 190.05e12,
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 16,
"nf_min": 7,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain_L",
"f_min": 186.55e12,
"f_max": 190.05e12,
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 21,
"nf_min": 7,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain_L_reduced_band",
"f_min": 187.3e12,
"f_max": 190.05e12,
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 21,
"nf_min": 7,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "test",
"type_def": "variable_gain",
"gain_flatmax": 25,
"gain_min": 15,
"p_max": 21,
"nf_min": 5.8,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "test_fixed_gain",
"type_def": "fixed_gain",
"gain_flatmax": 21,
"gain_min": 20,
"p_max": 21,
"nf0": 5,
"allowed_for_design": true
},
{
"type_variety": "std_booster",
"type_def": "fixed_gain",
"gain_flatmax": 21,
"gain_min": 20,
"p_max": 21,
"nf0": 5,
"allowed_for_design": false
}, {
"type_variety": "std_booster_L",
"f_min": 186.55e12,
"f_max": 190.05e12,
"type_def": "fixed_gain",
"gain_flatmax": 21,
"gain_min": 20,
"p_max": 21,
"nf0": 5,
"allowed_for_design": false
}, {
"type_variety": "std_booster_multiband",
"type_def": "multi_band",
"amplifiers": [
"std_booster",
"std_booster_L"
],
"allowed_for_design": false
}, {
"type_variety": "std_medium_gain_multiband",
"type_def": "multi_band",
"amplifiers": [
"std_medium_gain",
"std_medium_gain_L"
],
"allowed_for_design": true
},
{
"type_variety": "std_low_gain_multiband",
"type_def": "multi_band",
"amplifiers": [
"std_low_gain",
"std_low_gain_L"
],
"allowed_for_design": false
}, {
"type_variety": "std_low_gain_multiband_ter",
"type_def": "multi_band",
"amplifiers": [
"std_low_gain",
"std_low_gain_L_ter"
],
"allowed_for_design": false
}, {
"type_variety": "std_low_gain_multiband_bis",
"type_def": "multi_band",
"amplifiers": [
"std_low_gain_bis",
"std_low_gain_L"
],
"allowed_for_design": true
}, {
"type_variety": "std_low_gain_multiband_reduced_bis",
"type_def": "multi_band",
"amplifiers": [
"std_low_gain_reduced",
"std_low_gain_L"
],
"allowed_for_design": true
}, {
"type_variety": "std_low_gain_multiband_reduced",
"type_def": "multi_band",
"amplifiers": [
"std_low_gain_bis",
"std_low_gain_L_reduced_band"
],
"allowed_for_design": true
}
],
"Fiber":[{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"effective_area": 83e-12,
"pmd_coef": 1.265e-15
}
],
"Span":[{
"power_mode":true,
"delta_power_range_db": [0,0,0.5],
"max_fiber_lineic_loss_for_raman": 0.25,
"target_extended_gain": 2.5,
"max_length": 150,
"length_units": "km",
"max_loss": 28,
"padding": 10,
"EOL": 0,
"con_in": 0,
"con_out": 0
}
],
"Roadm":[{
"target_pch_out_db": -20,
"add_drop_osnr": 38,
"pmd": 0,
"pdl": 0,
"restrictions": {
"preamp_variety_list":[],
"booster_variety_list":[]
}
}],
"SI":[{
"f_min": 191.3e12,
"f_max":196.1e12,
"baud_rate": 32e9,
"spacing": 50e9,
"power_dbm": 0,
"power_range_db": [0,0,0.5],
"roll_off": 0.15,
"tx_osnr": 100,
"sys_margins": 0
}],
"Transceiver":[
{
"type_variety": "vendorA_trx-type1",
"frequency":{
"min": 191.35e12,
"max": 196.1e12
},
"mode":[
{
"format": "PS_SP64_1",
"baud_rate": 32e9,
"OSNR": 11,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 50e9,
"cost":1
},
{
"format": "PS_SP64_2",
"baud_rate": 64e9,
"OSNR": 15,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 75e9,
"cost":1
},
{
"format": "mode 1",
"baud_rate": 32e9,
"OSNR": 11,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 50e9,
"cost":1
},
{
"format": "mode 2",
"baud_rate": 64e9,
"OSNR": 15,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 75e9,
"cost":1
}
]
},
{
"type_variety": "Voyager_16QAM",
"frequency":{
"min": 191.35e12,
"max": 196.1e12
},
"mode":[
{
"format": "16QAM",
"baud_rate": 32e9,
"OSNR": 19,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 50e9,
"cost":1
}
]
},
{
"type_variety": "Voyager",
"frequency":{
"min": 191.35e12,
"max": 196.1e12
},
"mode":[
{
"format": "mode 1",
"baud_rate": 32e9,
"OSNR": 12,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 50e9,
"cost":1
},
{
"format": "mode 3",
"baud_rate": 44e9,
"OSNR": 18,
"bit_rate": 300e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 62.5e9,
"cost":1
},
{
"format": "mode 2",
"baud_rate": 66e9,
"OSNR": 21,
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 75e9,
"cost":1
},
{
"format": "mode 2 - fake",
"baud_rate": 66e9,
"OSNR": 21,
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 75e9,
"cost":1
},
{
"format": "mode 4",
"baud_rate": 66e9,
"OSNR": 16,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 75e9,
"cost":1
}
]
}
]
}

View File

@@ -0,0 +1,220 @@
{
"Edfa": [{
"type_variety": "CienaDB_medium_gain",
"type_def": "advanced_model",
"gain_flatmax": 25,
"gain_min": 15,
"p_max": 21,
"advanced_config_from_json": "std_medium_gain_advanced_config.json",
"out_voa_auto": false,
"allowed_for_design": true
}, {
"type_variety": "std_medium_gain",
"type_def": "variable_gain",
"gain_flatmax": 26,
"gain_min": 15,
"p_max": 21,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
}, {
"type_variety": "std_low_gain",
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 21,
"nf_min": 7,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
}, {
"type_variety": "test",
"type_def": "variable_gain",
"gain_flatmax": 25,
"gain_min": 15,
"p_max": 21,
"nf_min": 5.8,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
}, {
"type_variety": "test_fixed_gain",
"type_def": "fixed_gain",
"gain_flatmax": 21,
"gain_min": 20,
"p_max": 21,
"nf0": 5,
"allowed_for_design": true
}, {
"type_variety": "std_booster",
"type_def": "fixed_gain",
"gain_flatmax": 21,
"gain_min": 20,
"p_max": 21,
"nf0": 5,
"allowed_for_design": false
}
],
"Fiber": [{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"effective_area": 83e-12,
"pmd_coef": 1.265e-15
}
],
"Span": [{
"power_mode": true,
"delta_power_range_db": [0, 0, 0.5],
"max_fiber_lineic_loss_for_raman": 0.25,
"target_extended_gain": 2.5,
"max_length": 150,
"length_units": "km",
"max_loss": 28,
"padding": 10,
"EOL": 0,
"con_in": 0,
"con_out": 0
}
],
"Roadm": [{
"target_psd_out_mWperGHz": 3.125e-4,
"add_drop_osnr": 38,
"pmd": 0,
"pdl": 0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
}
}
],
"SI": [{
"f_min": 191.35e12,
"f_max": 196.1e12,
"baud_rate": 32e9,
"spacing": 50e9,
"power_dbm": 0,
"power_range_db": [0, 0, 0.5],
"roll_off": 0.15,
"tx_osnr": 100,
"sys_margins": 0
}
],
"Transceiver": [{
"type_variety": "vendorA_trx-type1",
"frequency": {
"min": 191.4e12,
"max": 196.1e12
},
"mode": [{
"format": "PS_SP64_1",
"baud_rate": 32e9,
"OSNR": 11,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 50e9,
"cost": 1
}, {
"format": "PS_SP64_2",
"baud_rate": 64e9,
"OSNR": 15,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 75e9,
"cost": 1
}, {
"format": "mode 1",
"baud_rate": 32e9,
"OSNR": 11,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 50e9,
"cost": 1
}, {
"format": "mode 2",
"baud_rate": 64e9,
"OSNR": 15,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 75e9,
"cost": 1
}
]
}, {
"type_variety": "Voyager_16QAM",
"frequency": {
"min": 191.4e12,
"max": 196.1e12
},
"mode": [{
"format": "16QAM",
"baud_rate": 32e9,
"OSNR": 19,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 50e9,
"cost": 1
}
]
}, {
"type_variety": "Voyager",
"frequency": {
"min": 191.4e12,
"max": 196.1e12
},
"mode": [{
"format": "mode 1",
"baud_rate": 32e9,
"OSNR": 12,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 50e9,
"cost": 1
}, {
"format": "mode 3",
"baud_rate": 44e9,
"OSNR": 18,
"bit_rate": 300e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 62.5e9,
"cost": 1
}, {
"format": "mode 2",
"baud_rate": 66e9,
"OSNR": 21,
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 75e9,
"cost": 1
}, {
"format": "mode 2 - fake",
"baud_rate": 66e9,
"OSNR": 21,
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 75e9,
"cost": 1
}, {
"format": "mode 4",
"baud_rate": 66e9,
"OSNR": 16,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 75e9,
"cost": 1
}
]
}
]
}

View File

@@ -0,0 +1,220 @@
{
"Edfa": [{
"type_variety": "CienaDB_medium_gain",
"type_def": "advanced_model",
"gain_flatmax": 25,
"gain_min": 15,
"p_max": 21,
"advanced_config_from_json": "std_medium_gain_advanced_config.json",
"out_voa_auto": false,
"allowed_for_design": true
}, {
"type_variety": "std_medium_gain",
"type_def": "variable_gain",
"gain_flatmax": 26,
"gain_min": 15,
"p_max": 21,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
}, {
"type_variety": "std_low_gain",
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 21,
"nf_min": 7,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
}, {
"type_variety": "test",
"type_def": "variable_gain",
"gain_flatmax": 25,
"gain_min": 15,
"p_max": 21,
"nf_min": 5.8,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
}, {
"type_variety": "test_fixed_gain",
"type_def": "fixed_gain",
"gain_flatmax": 21,
"gain_min": 20,
"p_max": 21,
"nf0": 5,
"allowed_for_design": true
}, {
"type_variety": "std_booster",
"type_def": "fixed_gain",
"gain_flatmax": 21,
"gain_min": 20,
"p_max": 21,
"nf0": 5,
"allowed_for_design": false
}
],
"Fiber": [{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"effective_area": 83e-12,
"pmd_coef": 1.265e-15
}
],
"Span": [{
"power_mode": true,
"delta_power_range_db": [0, 0, 0.5],
"max_fiber_lineic_loss_for_raman": 0.25,
"target_extended_gain": 2.5,
"max_length": 150,
"length_units": "km",
"max_loss": 28,
"padding": 10,
"EOL": 0,
"con_in": 0,
"con_out": 0
}
],
"Roadm": [{
"target_out_mWperSlotWidth": 2.0e-4,
"add_drop_osnr": 38,
"pmd": 0,
"pdl": 0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
}
}
],
"SI": [{
"f_min": 191.35e12,
"f_max": 196.1e12,
"baud_rate": 32e9,
"spacing": 50e9,
"power_dbm": 0,
"power_range_db": [0, 0, 0.5],
"roll_off": 0.15,
"tx_osnr": 100,
"sys_margins": 0
}
],
"Transceiver": [{
"type_variety": "vendorA_trx-type1",
"frequency": {
"min": 191.4e12,
"max": 196.1e12
},
"mode": [{
"format": "PS_SP64_1",
"baud_rate": 32e9,
"OSNR": 11,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 50e9,
"cost": 1
}, {
"format": "PS_SP64_2",
"baud_rate": 64e9,
"OSNR": 15,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 75e9,
"cost": 1
}, {
"format": "mode 1",
"baud_rate": 32e9,
"OSNR": 11,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 50e9,
"cost": 1
}, {
"format": "mode 2",
"baud_rate": 64e9,
"OSNR": 15,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 75e9,
"cost": 1
}
]
}, {
"type_variety": "Voyager_16QAM",
"frequency": {
"min": 191.4e12,
"max": 196.1e12
},
"mode": [{
"format": "16QAM",
"baud_rate": 32e9,
"OSNR": 19,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 50e9,
"cost": 1
}
]
}, {
"type_variety": "Voyager",
"frequency": {
"min": 191.4e12,
"max": 196.1e12
},
"mode": [{
"format": "mode 1",
"baud_rate": 32e9,
"OSNR": 12,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 50e9,
"cost": 1
}, {
"format": "mode 3",
"baud_rate": 44e9,
"OSNR": 18,
"bit_rate": 300e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 62.5e9,
"cost": 1
}, {
"format": "mode 2",
"baud_rate": 66e9,
"OSNR": 21,
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 75e9,
"cost": 1
}, {
"format": "mode 2 - fake",
"baud_rate": 66e9,
"OSNR": 21,
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 75e9,
"cost": 1
}, {
"format": "mode 4",
"baud_rate": 66e9,
"OSNR": 16,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 75e9,
"cost": 1
}
]
}
]
}

View File

@@ -0,0 +1,238 @@
{
"Edfa": [{
"type_variety": "CienaDB_medium_gain",
"type_def": "advanced_model",
"gain_flatmax": 25,
"gain_min": 15,
"p_max": 21,
"advanced_config_from_json": "std_medium_gain_advanced_config.json",
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_medium_gain",
"type_def": "variable_gain",
"gain_flatmax": 26,
"gain_min": 15,
"p_max": 21,
"nf_min": 6,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain",
"type_def": "variable_gain",
"gain_flatmax": 16,
"gain_min": 8,
"p_max": 21,
"nf_min": 7,
"nf_max": 11,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "test",
"type_def": "variable_gain",
"gain_flatmax": 25,
"gain_min": 15,
"p_max": 21,
"nf_min": 5.8,
"nf_max": 10,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "test_fixed_gain",
"type_def": "fixed_gain",
"gain_flatmax": 21,
"gain_min": 20,
"p_max": 21,
"nf0": 5,
"allowed_for_design": true
},
{
"type_variety": "std_booster",
"type_def": "fixed_gain",
"gain_flatmax": 21,
"gain_min": 20,
"p_max": 21,
"nf0": 5,
"allowed_for_design": false
}
],
"Fiber": [{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"effective_area": 83e-12,
"pmd_coef": 1.265e-15
}
],
"Span": [{
"power_mode":true,
"delta_power_range_db": [0,0,0.5],
"max_fiber_lineic_loss_for_raman": 0.25,
"target_extended_gain": 2.5,
"max_length": 150,
"length_units": "km",
"max_loss": 28,
"padding": 10,
"EOL": 0,
"con_in": 0,
"con_out": 0
}
],
"Roadm": [{
"target_pch_out_db": -20,
"add_drop_osnr": 38,
"pmd": 0,
"pdl": 0,
"restrictions": {
"preamp_variety_list":[],
"booster_variety_list":[]
}
}
],
"SI": [{
"f_min": 191.35e12,
"f_max": 196.1e12,
"baud_rate": 32e9,
"spacing": 50e9,
"power_dbm": 0,
"power_range_db": [-6,0,0.5],
"roll_off": 0.15,
"tx_osnr": 100,
"sys_margins": 0
}
],
"Transceiver":[
{
"type_variety": "vendorA_trx-type1",
"frequency":{
"min": 191.4e12,
"max": 196.1e12
},
"mode":[
{
"format": "PS_SP64_1",
"baud_rate": 32e9,
"OSNR": 11,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 50e9,
"cost": 1
},
{
"format": "PS_SP64_2",
"baud_rate": 64e9,
"OSNR": 15,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 75e9,
"cost": 1
},
{
"format": "mode 1",
"baud_rate": 32e9,
"OSNR": 11,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 50e9,
"cost": 1
},
{
"format": "mode 2",
"baud_rate": 64e9,
"OSNR": 15,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 75e9,
"cost": 1
}
]
},
{
"type_variety": "Voyager_16QAM",
"frequency": {
"min": 191.4e12,
"max": 196.1e12
},
"mode": [
{
"format": "16QAM",
"baud_rate": 32e9,
"OSNR": 19,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 100,
"min_spacing": 50e9,
"cost": 1
}
]
},
{
"type_variety": "Voyager",
"frequency": {
"min": 191.4e12,
"max": 196.1e12
},
"mode": [
{
"format": "mode 1",
"baud_rate": 32e9,
"OSNR": 12,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 50e9,
"cost": 1
},
{
"format": "mode 3",
"baud_rate": 44e9,
"OSNR": 18,
"bit_rate": 300e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 62.5e9,
"cost": 1
},
{
"format": "mode 2",
"baud_rate": 66e9,
"OSNR": 21,
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 75e9,
"cost": 1
},
{
"format": "mode 2 - fake",
"baud_rate": 66e9,
"OSNR": 21,
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 75e9,
"cost": 1
},
{
"format": "mode 4",
"baud_rate": 66e9,
"OSNR": 16,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 45,
"min_spacing": 75e9,
"cost": 1
}
]
}
]
}

View File

@@ -0,0 +1,37 @@
{
"Transceiver": [
{
"type_variety": "ZR400G",
"frequency": {
"min": 191.35e12,
"max": 196.1e12
},
"mode": [
{
"format": "400G",
"baud_rate": 60e9,
"OSNR": 24,
"bit_rate": 400e9,
"roll_off": 0.2,
"tx_osnr": 38,
"min_spacing": 75e9,
"cost": 1
}
]
}
],
"Edfa": [
{
"type_variety": "user_defined_default_amplifier",
"type_def": "variable_gain",
"gain_flatmax": 25,
"gain_min": 15,
"p_max": 21,
"nf_min": 6,
"nf_max": 10,
"advanced_config_from_json": "default_edfa_config.json",
"out_voa_auto": false,
"allowed_for_design": false
}
]
}

BIN
tests/data/ila_constraint.xlsx Executable file

Binary file not shown.

View File

@@ -63,6 +63,7 @@
{ {
"uid": "roadm Lannion_CAS", "uid": "roadm Lannion_CAS",
"type": "Roadm", "type": "Roadm",
"type_variety": "example_detailed_impairments",
"params": { "params": {
"target_pch_out_db": -18.6, "target_pch_out_db": -18.6,
"restrictions": { "restrictions": {
@@ -73,7 +74,14 @@
"east edfa in Lannion_CAS to Corlay": -18.6, "east edfa in Lannion_CAS to Corlay": -18.6,
"east edfa in Lannion_CAS to Morlaix": -23.0, "east edfa in Lannion_CAS to Morlaix": -23.0,
"east edfa in Lannion_CAS to Stbrieuc": -20 "east edfa in Lannion_CAS to Stbrieuc": -20
},
"per_degree_impairments": [
{
"from_degree": "west edfa in Lannion_CAS to Morlaix",
"to_degree": "east edfa in Lannion_CAS to Stbrieuc",
"impairment_id": 0
} }
]
}, },
"metadata": { "metadata": {
"location": { "location": {
@@ -87,6 +95,7 @@
{ {
"uid": "roadm Lorient_KMA", "uid": "roadm Lorient_KMA",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -111,6 +120,7 @@
{ {
"uid": "roadm Vannes_KBE", "uid": "roadm Vannes_KBE",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -134,6 +144,7 @@
{ {
"uid": "roadm Rennes_STA", "uid": "roadm Rennes_STA",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -157,6 +168,7 @@
{ {
"uid": "roadm Brest_KLA", "uid": "roadm Brest_KLA",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {

View File

@@ -62,7 +62,12 @@
}, },
{ {
"uid": "roadm Lannion_CAS", "uid": "roadm Lannion_CAS",
"type_variety": "example_detailed_impairments",
"params": { "params": {
"per_degree_impairments": [{
"from_degree": "west edfa in Lannion_CAS to Morlaix",
"impairment_id": 0,
"to_degree": "east edfa in Lannion_CAS to Stbrieuc"}],
"per_degree_pch_out_db": { "per_degree_pch_out_db": {
"east edfa in Lannion_CAS to Corlay": -18.6, "east edfa in Lannion_CAS to Corlay": -18.6,
"east edfa in Lannion_CAS to Morlaix": -23.0 "east edfa in Lannion_CAS to Morlaix": -23.0
@@ -661,7 +666,7 @@
"type": "Edfa", "type": "Edfa",
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"tilt_target": 0 "tilt_target": null
} }
}, },
{ {
@@ -677,7 +682,7 @@
"type": "Edfa", "type": "Edfa",
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"tilt_target": 0 "tilt_target": null
} }
}, },
{ {
@@ -693,7 +698,7 @@
"type": "Edfa", "type": "Edfa",
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"tilt_target": 0 "tilt_target": null
} }
}, },
{ {
@@ -709,7 +714,7 @@
"type": "Edfa", "type": "Edfa",
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"tilt_target": 0 "tilt_target": null
} }
}, },
{ {
@@ -727,7 +732,7 @@
"operational": { "operational": {
"gain_target": 20.0, "gain_target": 20.0,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -761,7 +766,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -779,7 +784,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -797,7 +802,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -815,7 +820,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -833,7 +838,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -851,7 +856,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -869,7 +874,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -887,7 +892,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -905,7 +910,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -923,7 +928,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -941,7 +946,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -959,7 +964,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -978,7 +983,7 @@
"operational": { "operational": {
"gain_target": 18.0, "gain_target": 18.0,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -996,7 +1001,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1014,7 +1019,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1032,7 +1037,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
} }

View File

@@ -1,224 +0,0 @@
{
"uid": "Span1",
"params": {
"length": 80,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0,
"con_in": 0.5,
"con_out": 0.5,
"type_variety": "SSMF",
"dispersion": 0.0000167,
"gamma": 0.00127,
"pmd_coef": 1.265e-15,
"raman_efficiency": {
"cr": [
0,
0.0000094,
0.0000292,
0.0000488,
0.0000682,
0.0000831,
0.000094,
0.0001014,
0.0001069,
0.0001119,
0.0001217,
0.0001268,
0.0001365,
0.000149,
0.000165,
0.000181,
0.0001977,
0.0002192,
0.0002469,
0.0002749,
0.0002999,
0.0003206,
0.0003405,
0.0003592,
0.000374,
0.0003826,
0.0003841,
0.0003826,
0.0003802,
0.0003756,
0.0003549,
0.0003795,
0.000344,
0.0002933,
0.0002024,
0.0001158,
0.0000846,
0.0000714,
0.0000686,
0.000085,
0.0000893,
0.0000901,
0.0000815,
0.0000667,
0.0000437,
0.0000328,
0.0000296,
0.0000265,
0.0000257,
0.0000281,
0.0000308,
0.0000367,
0.0000585,
0.0000663,
0.0000636,
0.000055,
0.0000406,
0.0000277,
0.0000242,
0.0000187,
0.000016,
0.000014,
0.0000113,
0.0000105,
0.0000098,
0.0000098,
0.0000113,
0.0000164,
0.0000195,
0.0000238,
0.0000226,
0.0000203,
0.0000148,
0.0000109,
0.0000098,
0.0000105,
0.0000117,
0.0000125,
0.0000121,
0.0000109,
0.0000098,
0.0000082,
0.0000066,
0.0000047,
0.0000027,
0.0000019,
0.0000012,
4e-7,
2e-7,
1e-7
],
"frequency_offset": [
0,
500000000000,
1000000000000,
1500000000000,
2000000000000,
2500000000000,
3000000000000,
3500000000000,
4000000000000,
4500000000000,
5000000000000,
5500000000000,
6000000000000,
6500000000000,
7000000000000,
7500000000000,
8000000000000,
8500000000000,
9000000000000,
9500000000000,
10000000000000,
10500000000000,
11000000000000,
11500000000000,
12000000000000,
12500000000000,
12750000000000,
13000000000000,
13250000000000,
13500000000000,
14000000000000,
14500000000000,
14750000000000,
15000000000000,
15500000000000,
16000000000000,
16500000000000,
17000000000000,
17500000000000,
18000000000000,
18250000000000,
18500000000000,
18750000000000,
19000000000000,
19500000000000,
20000000000000,
20500000000000,
21000000000000,
21500000000000,
22000000000000,
22500000000000,
23000000000000,
23500000000000,
24000000000000,
24500000000000,
25000000000000,
25500000000000,
26000000000000,
26500000000000,
27000000000000,
27500000000000,
28000000000000,
28500000000000,
29000000000000,
29500000000000,
30000000000000,
30500000000000,
31000000000000,
31500000000000,
32000000000000,
32500000000000,
33000000000000,
33500000000000,
34000000000000,
34500000000000,
35000000000000,
35500000000000,
36000000000000,
36500000000000,
37000000000000,
37500000000000,
38000000000000,
38500000000000,
39000000000000,
39500000000000,
40000000000000,
40500000000000,
41000000000000,
41500000000000,
42000000000000
]
}
},
"operational": {
"temperature": 283,
"raman_pumps": [
{
"power": 0.2,
"frequency": 205000000000000,
"propagation_direction": "counterprop"
},
{
"power": 0.206,
"frequency": 201000000000000,
"propagation_direction": "counterprop"
}
]
},
"metadata": {
"location": {
"latitude": 1,
"longitude": 0,
"city": null,
"region": ""
}
}
}

View File

@@ -1,12 +1,11 @@
{ {
"raman_parameters": { "raman_params": {
"flag_raman": true, "flag": true,
"space_resolution": 10e3, "result_spatial_resolution": 10e3,
"tolerance": 1e-8 "solver_spatial_resolution": 10e3
}, },
"nli_parameters": { "nli_params": {
"nli_method_name": "ggn_spectrally_separated", "method": "ggn_spectrally_separated",
"wdm_grid_size": 50e9,
"dispersion_tolerance": 1, "dispersion_tolerance": 1,
"phase_shift_tolerance": 0.1, "phase_shift_tolerance": 0.1,
"computed_channels": [1, 18, 37, 56, 75] "computed_channels": [1, 18, 37, 56, 75]

View File

@@ -1,4 +1,7 @@
{ "nf_fit_coeff": [ {
"f_min": 191.275e12,
"f_max": 196.125e12,
"nf_fit_coeff": [
0.000168241, 0.000168241,
0.0469961, 0.0469961,
0.0359549, 0.0359549,

View File

@@ -14,8 +14,8 @@
"trx_mode": "mode 1", "trx_mode": "mode 1",
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 50000000000.0, "spacing": 50000000000.0,
@@ -39,8 +39,8 @@
"trx_mode": "mode 1", "trx_mode": "mode 1",
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 50000000000.0, "spacing": 50000000000.0,
@@ -64,8 +64,8 @@
"trx_mode": "mode 1", "trx_mode": "mode 1",
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 50000000000.0, "spacing": 50000000000.0,

View File

@@ -159,6 +159,7 @@
{ {
"uid": "roadm Lannion_CAS", "uid": "roadm Lannion_CAS",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -183,6 +184,7 @@
{ {
"uid": "roadm Lorient_KMA", "uid": "roadm Lorient_KMA",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -207,6 +209,7 @@
{ {
"uid": "roadm Vannes_KBE", "uid": "roadm Vannes_KBE",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -230,6 +233,7 @@
{ {
"uid": "roadm Rennes_STA", "uid": "roadm Rennes_STA",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -240,7 +244,6 @@
"east edfa in Rennes_STA to Stbrieuc": -20, "east edfa in Rennes_STA to Stbrieuc": -20,
"east edfa in Rennes_STA to Ploermel": -20 "east edfa in Rennes_STA to Ploermel": -20
} }
}, },
"metadata": { "metadata": {
"location": { "location": {
@@ -254,6 +257,7 @@
{ {
"uid": "roadm Brest_KLA", "uid": "roadm Brest_KLA",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -277,6 +281,7 @@
{ {
"uid": "roadm a", "uid": "roadm a",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -302,6 +307,7 @@
{ {
"uid": "roadm b", "uid": "roadm b",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -327,6 +333,7 @@
{ {
"uid": "roadm c", "uid": "roadm c",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -351,6 +358,7 @@
{ {
"uid": "roadm d", "uid": "roadm d",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -374,6 +382,7 @@
{ {
"uid": "roadm e", "uid": "roadm e",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -397,6 +406,7 @@
{ {
"uid": "roadm f", "uid": "roadm f",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -421,6 +431,7 @@
{ {
"uid": "roadm g", "uid": "roadm g",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -444,6 +455,7 @@
{ {
"uid": "roadm h", "uid": "roadm h",
"type": "Roadm", "type": "Roadm",
"type_variety": "default",
"params": { "params": {
"target_pch_out_db": -20, "target_pch_out_db": -20,
"restrictions": { "restrictions": {
@@ -1593,7 +1605,7 @@
"type": "Edfa", "type": "Edfa",
"type_variety": "std_medium_gain", "type_variety": "std_medium_gain",
"operational": { "operational": {
"gain_target": 18.5, "gain_target": 13.177288,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": 0,
"out_voa": 0 "out_voa": 0
@@ -2235,7 +2247,7 @@
"type": "Edfa", "type": "Edfa",
"type_variety": "std_low_gain", "type_variety": "std_low_gain",
"operational": { "operational": {
"gain_target": 6.5, "gain_target": 11.822712,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": 0,
"out_voa": 0 "out_voa": 0
@@ -2292,7 +2304,7 @@
"type": "Edfa", "type": "Edfa",
"type_variety": "std_low_gain", "type_variety": "std_low_gain",
"operational": { "operational": {
"gain_target": 13.82, "gain_target": 13.822712,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": 0,
"out_voa": 0 "out_voa": 0
@@ -2311,7 +2323,7 @@
"type": "Edfa", "type": "Edfa",
"type_variety": "test_fixed_gain", "type_variety": "test_fixed_gain",
"operational": { "operational": {
"gain_target": 15.18, "gain_target": 15.177288,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": 0,
"out_voa": 0 "out_voa": 0

View File

@@ -1171,7 +1171,7 @@
"operational": { "operational": {
"gain_target": 19.0, "gain_target": 19.0,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1190,7 +1190,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1209,7 +1209,7 @@
"operational": { "operational": {
"gain_target": 18.0, "gain_target": 18.0,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": 0.5 "out_voa": 0.5
} }
}, },
@@ -1227,7 +1227,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1245,7 +1245,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1263,7 +1263,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1281,7 +1281,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1299,7 +1299,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1318,7 +1318,7 @@
"operational": { "operational": {
"gain_target": 18.5, "gain_target": 18.5,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1336,7 +1336,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1354,7 +1354,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1372,7 +1372,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1391,7 +1391,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1409,7 +1409,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1428,7 +1428,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1446,7 +1446,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1464,7 +1464,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1482,7 +1482,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1500,7 +1500,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1518,7 +1518,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1551,7 +1551,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1569,7 +1569,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1587,7 +1587,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1605,7 +1605,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1623,7 +1623,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1641,7 +1641,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1659,7 +1659,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1677,7 +1677,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1695,7 +1695,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1713,7 +1713,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1731,7 +1731,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1749,7 +1749,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1768,7 +1768,7 @@
"operational": { "operational": {
"gain_target": 18.0, "gain_target": 18.0,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1786,7 +1786,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1805,7 +1805,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1823,7 +1823,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1841,7 +1841,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1859,7 +1859,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1877,7 +1877,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1895,7 +1895,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1913,7 +1913,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1931,7 +1931,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1949,7 +1949,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1967,7 +1967,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -1985,7 +1985,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2003,7 +2003,7 @@
"operational": { "operational": {
"gain_target": 19.0, "gain_target": 19.0,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2021,7 +2021,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2039,7 +2039,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2057,7 +2057,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2075,7 +2075,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2093,7 +2093,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2111,7 +2111,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2129,7 +2129,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2147,7 +2147,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2165,7 +2165,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2183,7 +2183,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2201,7 +2201,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2219,7 +2219,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2237,7 +2237,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2255,7 +2255,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2273,7 +2273,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2291,7 +2291,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2309,7 +2309,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2327,7 +2327,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
}, },
@@ -2345,7 +2345,7 @@
"operational": { "operational": {
"gain_target": null, "gain_target": null,
"delta_p": null, "delta_p": null,
"tilt_target": 0, "tilt_target": null,
"out_voa": null "out_voa": null
} }
} }

View File

@@ -42,10 +42,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 1, "index": 1,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -69,10 +71,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 4, "index": 4,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -87,10 +91,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 6, "index": 6,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -105,10 +111,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 8, "index": 8,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -123,10 +131,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 10, "index": 10,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -141,10 +151,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 12, "index": 12,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -159,10 +171,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 14, "index": 14,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -219,10 +233,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 1, "index": 1,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -246,10 +262,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 4, "index": 4,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -264,10 +282,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 6, "index": 6,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -282,10 +302,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 8, "index": 8,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -300,10 +322,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 10, "index": 10,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -318,10 +342,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 12, "index": 12,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -336,10 +362,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 14, "index": 14,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -354,10 +382,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 16, "index": 16,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -372,10 +402,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 18, "index": 18,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -390,10 +422,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 20, "index": 20,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -408,10 +442,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 22, "index": 22,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -426,10 +462,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 24, "index": 24,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -444,10 +482,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 26, "index": 26,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -462,10 +502,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 28, "index": 28,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -480,10 +522,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 30, "index": 30,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -498,10 +542,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 32, "index": 32,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -516,10 +562,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 34, "index": 34,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -534,10 +582,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 36, "index": 36,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -552,10 +602,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 38, "index": 38,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -570,10 +622,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 40, "index": 40,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -588,10 +642,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 42, "index": 42,
"label-hop": { "label-hop": [
{
"N": -276, "N": -276,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -648,10 +704,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 1, "index": 1,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -675,10 +733,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 4, "index": 4,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -693,10 +753,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 6, "index": 6,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -711,10 +773,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 8, "index": 8,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -729,10 +793,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 10, "index": 10,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -747,10 +813,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 12, "index": 12,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -765,10 +833,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 14, "index": 14,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -783,10 +853,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 16, "index": 16,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -801,10 +873,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 18, "index": 18,
"label-hop": { "label-hop": [
{
"N": -284, "N": -284,
"M": 4 "M": 4
} }
]
} }
}, },
{ {
@@ -829,7 +903,7 @@
}, },
{ {
"metric-type": "SNR-0.1nm", "metric-type": "SNR-0.1nm",
"accumulative-value": 22.15 "accumulative-value": 22.14
}, },
{ {
"metric-type": "OSNR-bandwidth", "metric-type": "OSNR-bandwidth",
@@ -861,10 +935,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 1, "index": 1,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -888,10 +964,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 4, "index": 4,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -906,10 +984,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 6, "index": 6,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -924,10 +1004,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 8, "index": 8,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -942,10 +1024,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 10, "index": 10,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -960,10 +1044,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 12, "index": 12,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -978,10 +1064,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 14, "index": 14,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -996,10 +1084,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 16, "index": 16,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1014,10 +1104,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 18, "index": 18,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1032,10 +1124,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 20, "index": 20,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1050,10 +1144,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 22, "index": 22,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1068,10 +1164,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 24, "index": 24,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1086,10 +1184,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 26, "index": 26,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1104,10 +1204,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 28, "index": 28,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1122,10 +1224,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 30, "index": 30,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1140,10 +1244,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 32, "index": 32,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1158,10 +1264,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 34, "index": 34,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1176,10 +1284,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 36, "index": 36,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1194,10 +1304,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 38, "index": 38,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1212,10 +1324,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 40, "index": 40,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1230,10 +1344,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 42, "index": 42,
"label-hop": { "label-hop": [
{
"N": -266, "N": -266,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1254,11 +1370,11 @@
"path-metric": [ "path-metric": [
{ {
"metric-type": "SNR-bandwidth", "metric-type": "SNR-bandwidth",
"accumulative-value": 21.68 "accumulative-value": 21.67
}, },
{ {
"metric-type": "SNR-0.1nm", "metric-type": "SNR-0.1nm",
"accumulative-value": 28.77 "accumulative-value": 28.76
}, },
{ {
"metric-type": "OSNR-bandwidth", "metric-type": "OSNR-bandwidth",
@@ -1290,10 +1406,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 1, "index": 1,
"label-hop": { "label-hop": [
{
"N": -274, "N": -274,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1317,10 +1435,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 4, "index": 4,
"label-hop": { "label-hop": [
{
"N": -274, "N": -274,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1335,10 +1455,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 6, "index": 6,
"label-hop": { "label-hop": [
{
"N": -274, "N": -274,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1353,10 +1475,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 8, "index": 8,
"label-hop": { "label-hop": [
{
"N": -274, "N": -274,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1371,10 +1495,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 10, "index": 10,
"label-hop": { "label-hop": [
{
"N": -274, "N": -274,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1389,10 +1515,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 12, "index": 12,
"label-hop": { "label-hop": [
{
"N": -274, "N": -274,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1407,10 +1535,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 14, "index": 14,
"label-hop": { "label-hop": [
{
"N": -274, "N": -274,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1425,10 +1555,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 16, "index": 16,
"label-hop": { "label-hop": [
{
"N": -274, "N": -274,
"M": 6 "M": 6
} }
]
} }
}, },
{ {
@@ -1443,10 +1575,12 @@
{ {
"path-route-object": { "path-route-object": {
"index": 18, "index": 18,
"label-hop": { "label-hop": [
{
"N": -274, "N": -274,
"M": 6 "M": 6
} }
]
} }
}, },
{ {

View File

@@ -1,7 +1,7 @@
response-id,source,destination,path_bandwidth,Pass?,nb of tsp pairs,total cost,transponder-type,transponder-mode,OSNR-0.1nm,SNR-0.1nm,SNR-bandwidth,baud rate (Gbaud),input power (dBm),path,"spectrum (N,M)",reversed path OSNR-0.1nm,reversed path SNR-0.1nm,reversed path SNR-bandwidth response-id,source,destination,path_bandwidth,Pass?,nb of tsp pairs,total cost,transponder-type,transponder-mode,OSNR-0.1nm,SNR-0.1nm,SNR-bandwidth,baud rate (Gbaud),input power (dBm),path,"spectrum (N,M)",reversed path OSNR-0.1nm,reversed path SNR-0.1nm,reversed path SNR-bandwidth
0,trx Lorient_KMA,trx Vannes_KBE,100.0,True,1,1,Voyager,mode 1,30.84,30.84,26.75,32.0,0.0,trx Lorient_KMA | roadm Lorient_KMA | east edfa in Lorient_KMA to Vannes_KBE | fiber (Lorient_KMA → Vannes_KBE)-F055 | west edfa in Vannes_KBE to Lorient_KMA | roadm Vannes_KBE | trx Vannes_KBE,"-284, 4",,, 0,trx Lorient_KMA,trx Vannes_KBE,100.0,True,1,1,Voyager,mode 1,30.84,30.84,26.75,32.0,0.0,trx Lorient_KMA | roadm Lorient_KMA | east edfa in Lorient_KMA to Vannes_KBE | fiber (Lorient_KMA → Vannes_KBE)-F055 | west edfa in Vannes_KBE to Lorient_KMA | roadm Vannes_KBE | trx Vannes_KBE,"[-284], [4]",,,
1,trx Brest_KLA,trx Vannes_KBE,10.0,True,1,1,Voyager,mode 1,22.65,22.11,18.03,32.0,1.0,trx Brest_KLA | roadm Brest_KLA | east edfa in Brest_KLA to Morlaix | fiber (Brest_KLA → Morlaix)-F060 | east fused spans in Morlaix | fiber (Morlaix → Lannion_CAS)-F059 | west edfa in Lannion_CAS to Morlaix | roadm Lannion_CAS | east edfa in Lannion_CAS to Corlay | fiber (Lannion_CAS → Corlay)-F061 | west fused spans in Corlay | fiber (Corlay → Loudeac)-F010 | west fused spans in Loudeac | fiber (Loudeac → Lorient_KMA)-F054 | west edfa in Lorient_KMA to Loudeac | roadm Lorient_KMA | east edfa in Lorient_KMA to Vannes_KBE | fiber (Lorient_KMA → Vannes_KBE)-F055 | west edfa in Vannes_KBE to Lorient_KMA | roadm Vannes_KBE | trx Vannes_KBE,"-276, 4",,, 1,trx Brest_KLA,trx Vannes_KBE,10.0,True,1,1,Voyager,mode 1,22.65,22.11,18.03,32.0,1.0,trx Brest_KLA | roadm Brest_KLA | east edfa in Brest_KLA to Morlaix | fiber (Brest_KLA → Morlaix)-F060 | east fused spans in Morlaix | fiber (Morlaix → Lannion_CAS)-F059 | west edfa in Lannion_CAS to Morlaix | roadm Lannion_CAS | east edfa in Lannion_CAS to Corlay | fiber (Lannion_CAS → Corlay)-F061 | west fused spans in Corlay | fiber (Corlay → Loudeac)-F010 | west fused spans in Loudeac | fiber (Loudeac → Lorient_KMA)-F054 | west edfa in Lorient_KMA to Loudeac | roadm Lorient_KMA | east edfa in Lorient_KMA to Vannes_KBE | fiber (Lorient_KMA → Vannes_KBE)-F055 | west edfa in Vannes_KBE to Lorient_KMA | roadm Vannes_KBE | trx Vannes_KBE,"[-276], [4]",,,
3,trx Lannion_CAS,trx Rennes_STA,60.0,True,1,1,vendorA_trx-type1,mode 1,28.29,25.85,21.77,32.0,1.0,trx Lannion_CAS | roadm Lannion_CAS | east edfa in Lannion_CAS to Stbrieuc | fiber (Lannion_CAS → Stbrieuc)-F056 | east edfa in Stbrieuc to Rennes_STA | fiber (Stbrieuc → Rennes_STA)-F057 | west edfa in Rennes_STA to Stbrieuc | roadm Rennes_STA | trx Rennes_STA,"-284, 4",,, 3,trx Lannion_CAS,trx Rennes_STA,60.0,True,1,1,vendorA_trx-type1,mode 1,28.29,25.85,21.77,32.0,1.0,trx Lannion_CAS | roadm Lannion_CAS | east edfa in Lannion_CAS to Stbrieuc | fiber (Lannion_CAS → Stbrieuc)-F056 | east edfa in Stbrieuc to Rennes_STA | fiber (Stbrieuc → Rennes_STA)-F057 | west edfa in Rennes_STA to Stbrieuc | roadm Rennes_STA | trx Rennes_STA,"[-284], [4]",,,
4,trx Rennes_STA,trx Lannion_CAS,150.0,True,1,1,vendorA_trx-type1,mode 2,22.27,22.15,15.05,64.0,0.0,trx Rennes_STA | roadm Rennes_STA | east edfa in Rennes_STA to Ploermel | fiber (Rennes_STA → Ploermel)- | east edfa in Ploermel to Vannes_KBE | fiber (Ploermel → Vannes_KBE)- | west edfa in Vannes_KBE to Ploermel | roadm Vannes_KBE | east edfa in Vannes_KBE to Lorient_KMA | fiber (Vannes_KBE → Lorient_KMA)-F055 | west edfa in Lorient_KMA to Vannes_KBE | roadm Lorient_KMA | east edfa in Lorient_KMA to Loudeac | fiber (Lorient_KMA → Loudeac)-F054 | east fused spans in Loudeac | fiber (Loudeac → Corlay)-F010 | east fused spans in Corlay | fiber (Corlay → Lannion_CAS)-F061 | west edfa in Lannion_CAS to Corlay | roadm Lannion_CAS | trx Lannion_CAS,"-266, 6",,, 4,trx Rennes_STA,trx Lannion_CAS,150.0,True,1,1,vendorA_trx-type1,mode 2,22.27,22.14,15.05,64.0,0.0,trx Rennes_STA | roadm Rennes_STA | east edfa in Rennes_STA to Ploermel | fiber (Rennes_STA → Ploermel)- | east edfa in Ploermel to Vannes_KBE | fiber (Ploermel → Vannes_KBE)- | west edfa in Vannes_KBE to Ploermel | roadm Vannes_KBE | east edfa in Vannes_KBE to Lorient_KMA | fiber (Vannes_KBE → Lorient_KMA)-F055 | west edfa in Lorient_KMA to Vannes_KBE | roadm Lorient_KMA | east edfa in Lorient_KMA to Loudeac | fiber (Lorient_KMA → Loudeac)-F054 | east fused spans in Loudeac | fiber (Loudeac → Corlay)-F010 | east fused spans in Corlay | fiber (Corlay → Lannion_CAS)-F061 | west edfa in Lannion_CAS to Corlay | roadm Lannion_CAS | trx Lannion_CAS,"[-266], [6]",,,
5,trx Rennes_STA,trx Lannion_CAS,20.0,True,1,1,vendorA_trx-type1,mode 2,30.79,28.77,21.68,64.0,3.0,trx Rennes_STA | roadm Rennes_STA | east edfa in Rennes_STA to Stbrieuc | fiber (Rennes_STA → Stbrieuc)-F057 | west edfa in Stbrieuc to Rennes_STA | fiber (Stbrieuc → Lannion_CAS)-F056 | west edfa in Lannion_CAS to Stbrieuc | roadm Lannion_CAS | trx Lannion_CAS,"-274, 6",,, 5,trx Rennes_STA,trx Lannion_CAS,20.0,True,1,1,vendorA_trx-type1,mode 2,30.79,28.76,21.67,64.0,3.0,trx Rennes_STA | roadm Rennes_STA | east edfa in Rennes_STA to Stbrieuc | fiber (Rennes_STA → Stbrieuc)-F057 | west edfa in Stbrieuc to Rennes_STA | fiber (Stbrieuc → Lannion_CAS)-F056 | west edfa in Lannion_CAS to Stbrieuc | roadm Lannion_CAS | trx Lannion_CAS,"[-274], [6]",,,
6,,,,NO_PATH,,,,,,,,,,,,,, 6,,,,NO_PATH,,,,,,,,,,,,,,
1 response-id source destination path_bandwidth Pass? nb of tsp pairs total cost transponder-type transponder-mode OSNR-0.1nm SNR-0.1nm SNR-bandwidth baud rate (Gbaud) input power (dBm) path spectrum (N,M) reversed path OSNR-0.1nm reversed path SNR-0.1nm reversed path SNR-bandwidth
2 0 trx Lorient_KMA trx Vannes_KBE 100.0 True 1 1 Voyager mode 1 30.84 30.84 26.75 32.0 0.0 trx Lorient_KMA | roadm Lorient_KMA | east edfa in Lorient_KMA to Vannes_KBE | fiber (Lorient_KMA → Vannes_KBE)-F055 | west edfa in Vannes_KBE to Lorient_KMA | roadm Vannes_KBE | trx Vannes_KBE -284, 4 [-284], [4]
3 1 trx Brest_KLA trx Vannes_KBE 10.0 True 1 1 Voyager mode 1 22.65 22.11 18.03 32.0 1.0 trx Brest_KLA | roadm Brest_KLA | east edfa in Brest_KLA to Morlaix | fiber (Brest_KLA → Morlaix)-F060 | east fused spans in Morlaix | fiber (Morlaix → Lannion_CAS)-F059 | west edfa in Lannion_CAS to Morlaix | roadm Lannion_CAS | east edfa in Lannion_CAS to Corlay | fiber (Lannion_CAS → Corlay)-F061 | west fused spans in Corlay | fiber (Corlay → Loudeac)-F010 | west fused spans in Loudeac | fiber (Loudeac → Lorient_KMA)-F054 | west edfa in Lorient_KMA to Loudeac | roadm Lorient_KMA | east edfa in Lorient_KMA to Vannes_KBE | fiber (Lorient_KMA → Vannes_KBE)-F055 | west edfa in Vannes_KBE to Lorient_KMA | roadm Vannes_KBE | trx Vannes_KBE -276, 4 [-276], [4]
4 3 trx Lannion_CAS trx Rennes_STA 60.0 True 1 1 vendorA_trx-type1 mode 1 28.29 25.85 21.77 32.0 1.0 trx Lannion_CAS | roadm Lannion_CAS | east edfa in Lannion_CAS to Stbrieuc | fiber (Lannion_CAS → Stbrieuc)-F056 | east edfa in Stbrieuc to Rennes_STA | fiber (Stbrieuc → Rennes_STA)-F057 | west edfa in Rennes_STA to Stbrieuc | roadm Rennes_STA | trx Rennes_STA -284, 4 [-284], [4]
5 4 trx Rennes_STA trx Lannion_CAS 150.0 True 1 1 vendorA_trx-type1 mode 2 22.27 22.15 22.14 15.05 64.0 0.0 trx Rennes_STA | roadm Rennes_STA | east edfa in Rennes_STA to Ploermel | fiber (Rennes_STA → Ploermel)- | east edfa in Ploermel to Vannes_KBE | fiber (Ploermel → Vannes_KBE)- | west edfa in Vannes_KBE to Ploermel | roadm Vannes_KBE | east edfa in Vannes_KBE to Lorient_KMA | fiber (Vannes_KBE → Lorient_KMA)-F055 | west edfa in Lorient_KMA to Vannes_KBE | roadm Lorient_KMA | east edfa in Lorient_KMA to Loudeac | fiber (Lorient_KMA → Loudeac)-F054 | east fused spans in Loudeac | fiber (Loudeac → Corlay)-F010 | east fused spans in Corlay | fiber (Corlay → Lannion_CAS)-F061 | west edfa in Lannion_CAS to Corlay | roadm Lannion_CAS | trx Lannion_CAS -266, 6 [-266], [6]
6 5 trx Rennes_STA trx Lannion_CAS 20.0 True 1 1 vendorA_trx-type1 mode 2 30.79 28.77 28.76 21.68 21.67 64.0 3.0 trx Rennes_STA | roadm Rennes_STA | east edfa in Rennes_STA to Stbrieuc | fiber (Rennes_STA → Stbrieuc)-F057 | west edfa in Stbrieuc to Rennes_STA | fiber (Stbrieuc → Lannion_CAS)-F056 | west edfa in Lannion_CAS to Stbrieuc | roadm Lannion_CAS | trx Lannion_CAS -274, 6 [-274], [6]
7 6 NO_PATH

View File

@@ -14,8 +14,8 @@
"trx_mode": "mode 1", "trx_mode": "mode 1",
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 50000000000.0, "spacing": 50000000000.0,
@@ -39,8 +39,8 @@
"trx_mode": "mode 1", "trx_mode": "mode 1",
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 50000000000.0, "spacing": 50000000000.0,
@@ -104,8 +104,8 @@
"trx_mode": "mode 1", "trx_mode": "mode 1",
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 50000000000.0, "spacing": 50000000000.0,
@@ -129,8 +129,8 @@
"trx_mode": "mode 2", "trx_mode": "mode 2",
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 75000000000.0, "spacing": 75000000000.0,
@@ -154,8 +154,8 @@
"trx_mode": "mode 2", "trx_mode": "mode 2",
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 75000000000.0, "spacing": 75000000000.0,
@@ -179,8 +179,8 @@
"trx_mode": "mode 2", "trx_mode": "mode 2",
"effective-freq-slot": [ "effective-freq-slot": [
{ {
"N": "null", "N": null,
"M": "null" "M": null
} }
], ],
"spacing": 75000000000.0, "spacing": 75000000000.0,

View File

@@ -12,12 +12,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "Voyager_16QAM", "trx_type": "Voyager_16QAM",
"trx_mode": "16QAM", "trx_mode": "16QAM",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 50000000000.0, "spacing": 50000000000.0,
"max-nb-of-channel": 80, "max-nb-of-channel": 80,
"output-power": 0.001, "output-power": 0.001,
@@ -37,12 +31,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "vendorA_trx-type1", "trx_type": "vendorA_trx-type1",
"trx_mode": "PS_SP64_1", "trx_mode": "PS_SP64_1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 50000000000.0, "spacing": 50000000000.0,
"max-nb-of-channel": 80, "max-nb-of-channel": 80,
"output-power": 0.001, "output-power": 0.001,
@@ -62,12 +50,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "vendorA_trx-type1", "trx_type": "vendorA_trx-type1",
"trx_mode": "PS_SP64_1", "trx_mode": "PS_SP64_1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 50000000000.0, "spacing": 50000000000.0,
"max-nb-of-channel": 80, "max-nb-of-channel": 80,
"output-power": 0.001, "output-power": 0.001,
@@ -87,12 +69,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "vendorA_trx-type1", "trx_type": "vendorA_trx-type1",
"trx_mode": "PS_SP64_1", "trx_mode": "PS_SP64_1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 50000000000.0, "spacing": 50000000000.0,
"max-nb-of-channel": 80, "max-nb-of-channel": 80,
"output-power": 0.001, "output-power": 0.001,
@@ -133,12 +109,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "Voyager", "trx_type": "Voyager",
"trx_mode": "mode 2 - fake", "trx_mode": "mode 2 - fake",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 75000000000.0, "spacing": 75000000000.0,
"max-nb-of-channel": 63, "max-nb-of-channel": 63,
"output-power": 0.001, "output-power": 0.001,
@@ -158,12 +128,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "Voyager", "trx_type": "Voyager",
"trx_mode": "mode 2", "trx_mode": "mode 2",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 75000000000.0, "spacing": 75000000000.0,
"max-nb-of-channel": 63, "max-nb-of-channel": 63,
"output-power": 0.001, "output-power": 0.001,
@@ -183,12 +147,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "vendorA_trx-type1", "trx_type": "vendorA_trx-type1",
"trx_mode": "PS_SP64_1", "trx_mode": "PS_SP64_1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 50000000000.0, "spacing": 50000000000.0,
"max-nb-of-channel": 80, "max-nb-of-channel": 80,
"output-power": 0.001, "output-power": 0.001,
@@ -221,12 +179,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "Voyager", "trx_type": "Voyager",
"trx_mode": "mode 3", "trx_mode": "mode 3",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 62500000000.0, "spacing": 62500000000.0,
"max-nb-of-channel": 76, "max-nb-of-channel": 76,
"output-power": 0.001, "output-power": 0.001,
@@ -259,12 +211,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "vendorA_trx-type1", "trx_type": "vendorA_trx-type1",
"trx_mode": "PS_SP64_1", "trx_mode": "PS_SP64_1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 50000000000.0, "spacing": 50000000000.0,
"max-nb-of-channel": 80, "max-nb-of-channel": 80,
"output-power": 0.001, "output-power": 0.001,
@@ -284,12 +230,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "vendorA_trx-type1", "trx_type": "vendorA_trx-type1",
"trx_mode": "PS_SP64_1", "trx_mode": "PS_SP64_1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 50000000000.0, "spacing": 50000000000.0,
"max-nb-of-channel": 80, "max-nb-of-channel": 80,
"output-power": 0.001, "output-power": 0.001,
@@ -330,12 +270,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "Voyager_16QAM", "trx_type": "Voyager_16QAM",
"trx_mode": "16QAM", "trx_mode": "16QAM",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 50000000000.0, "spacing": 50000000000.0,
"max-nb-of-channel": 80, "max-nb-of-channel": 80,
"output-power": null, "output-power": null,
@@ -355,12 +289,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "Voyager", "trx_type": "Voyager",
"trx_mode": "mode 1", "trx_mode": "mode 1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 50000000000.0, "spacing": 50000000000.0,
"max-nb-of-channel": null, "max-nb-of-channel": null,
"output-power": 0.001, "output-power": 0.001,
@@ -380,12 +308,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "vendorA_trx-type1", "trx_type": "vendorA_trx-type1",
"trx_mode": "PS_SP64_1", "trx_mode": "PS_SP64_1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 50000000000.0, "spacing": 50000000000.0,
"max-nb-of-channel": null, "max-nb-of-channel": null,
"output-power": null, "output-power": null,
@@ -405,12 +327,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "vendorA_trx-type1", "trx_type": "vendorA_trx-type1",
"trx_mode": null, "trx_mode": null,
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 50000000000.0, "spacing": 50000000000.0,
"max-nb-of-channel": 80, "max-nb-of-channel": 80,
"output-power": 0.001, "output-power": 0.001,
@@ -451,12 +367,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "Voyager", "trx_type": "Voyager",
"trx_mode": null, "trx_mode": null,
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 50000000000.0, "spacing": 50000000000.0,
"max-nb-of-channel": null, "max-nb-of-channel": null,
"output-power": 0.001, "output-power": 0.001,
@@ -476,12 +386,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "Voyager", "trx_type": "Voyager",
"trx_mode": null, "trx_mode": null,
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 75000000000.0, "spacing": 75000000000.0,
"max-nb-of-channel": 63, "max-nb-of-channel": 63,
"output-power": null, "output-power": null,
@@ -501,12 +405,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "Voyager", "trx_type": "Voyager",
"trx_mode": null, "trx_mode": null,
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 50000000000.0, "spacing": 50000000000.0,
"max-nb-of-channel": null, "max-nb-of-channel": null,
"output-power": null, "output-power": null,
@@ -526,12 +424,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "Voyager", "trx_type": "Voyager",
"trx_mode": null, "trx_mode": null,
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 75000000000.0, "spacing": 75000000000.0,
"max-nb-of-channel": null, "max-nb-of-channel": null,
"output-power": null, "output-power": null,
@@ -551,12 +443,6 @@
"technology": "flexi-grid", "technology": "flexi-grid",
"trx_type": "Voyager", "trx_type": "Voyager",
"trx_mode": null, "trx_mode": null,
"effective-freq-slot": [
{
"n": "null",
"m": "null"
}
],
"spacing": 30000000000.0, "spacing": 30000000000.0,
"max-nb-of-channel": null, "max-nb-of-channel": null,
"output-power": null, "output-power": null,

View File

@@ -0,0 +1,97 @@
signal,nli
1.9952623149688796e-05,1.0570305869494063e-08
1.9952623149688796e-05,1.1989102199581664e-08
1.9952623149688796e-05,1.2687787891259665e-08
1.9952623149688796e-05,1.3153676763101585e-08
1.9952623149688796e-05,1.3504001312414315e-08
1.9952623149688796e-05,1.378517965356758e-08
1.9952623149688796e-05,1.4020312829929705e-08
1.9952623149688796e-05,1.4222564206194578e-08
1.9952623149688796e-05,1.440014394542033e-08
1.9952623149688796e-05,1.4558516068269932e-08
1.9952623149688796e-05,1.4701499315172012e-08
1.9952623149688796e-05,1.4831866587815758e-08
1.9952623149688796e-05,1.4951694168451522e-08
1.9952623149688796e-05,1.506257639956634e-08
1.9952623149688796e-05,1.5165763570833366e-08
1.9952623149688796e-05,1.5262253772723937e-08
1.9952623149688796e-05,1.535285600134073e-08
1.9952623149688796e-05,1.543823467328411e-08
1.9952623149688796e-05,1.551894175425445e-08
1.9952623149688796e-05,1.5595440417063968e-08
1.9952623149688796e-05,1.5668122772822936e-08
1.9952623149688796e-05,1.5737323370281063e-08
1.9952623149688796e-05,1.5803329618444796e-08
1.9952623149688796e-05,1.5866389935670908e-08
1.9952623149688796e-05,1.592672019391794e-08
1.9952623149688796e-05,1.598450886742589e-08
1.9952623149688796e-05,1.6039921184766554e-08
1.9952623149688796e-05,1.609310250559421e-08
1.9952623149688796e-05,1.61441810880001e-08
1.9952623149688796e-05,1.6193270372246937e-08
1.9952623149688796e-05,1.6240470877236143e-08
1.9952623149688796e-05,1.6285871784230113e-08
1.9952623149688796e-05,1.6329552265978812e-08
1.9952623149688796e-05,1.6371582606990462e-08
1.9952623149688796e-05,1.641202515119326e-08
1.9952623149688796e-05,1.6450935105904177e-08
1.9952623149688796e-05,1.6488361225310858e-08
1.9952623149688796e-05,1.6524346392188574e-08
1.9952623149688796e-05,1.6558928113022246e-08
1.9952623149688796e-05,1.6592138938867027e-08
1.9952623149688796e-05,1.6624006821997905e-08
1.9952623149688796e-05,1.665455541654349e-08
1.9952623149688796e-05,1.668380432977811e-08
1.9952623149688796e-05,1.6711769329485368e-08
1.9952623149688796e-05,1.6738462511750264e-08
1.9952623149688796e-05,1.6763892432637406e-08
1.9952623149688796e-05,1.6788064206436675e-08
1.9952623149688796e-05,1.681097957247311e-08
1.9952623149688796e-05,1.6832636931862217e-08
1.9952623149688796e-05,1.6853031355021186e-08
1.9952623149688796e-05,1.687215456020574e-08
1.9952623149688796e-05,1.688999486281053e-08
1.9952623149688796e-05,1.690653709463382e-08
1.9952623149688796e-05,1.6921762491746848e-08
1.9952623149688796e-05,1.6935648549006222e-08
1.9952623149688796e-05,1.6948168838584662e-08
1.9952623149688796e-05,1.695929278914847e-08
1.9952623149688796e-05,1.696898542145055e-08
1.9952623149688796e-05,1.6977207035104874e-08
1.9952623149688796e-05,1.6983912840119302e-08
1.9952623149688796e-05,1.6989052525338295e-08
1.9952623149688796e-05,1.699256975421989e-08
1.9952623149688796e-05,1.6994401576260005e-08
1.9952623149688796e-05,1.6994477739772384e-08
1.9952623149688796e-05,1.699271988849318e-08
1.9952623149688796e-05,1.6989040620420942e-08
1.9952623149688796e-05,1.6983342382173362e-08
1.9952623149688796e-05,1.6975516165613388e-08
1.9952623149688796e-05,1.6965439965112504e-08
1.9952623149688796e-05,1.6952976942961452e-08
1.9952623149688796e-05,1.693797323625088e-08
1.9952623149688796e-05,1.6920255319826076e-08
1.9952623149688796e-05,1.6899626814970685e-08
1.9952623149688796e-05,1.6875864599859146e-08
1.9952623149688796e-05,1.6848714031984708e-08
1.9952623149688796e-05,1.6817883029489423e-08
1.9952623149688796e-05,1.6783034669737056e-08
1.9952623149688796e-05,1.674377783759437e-08
1.9952623149688796e-05,1.669965527407164e-08
1.9952623149688796e-05,1.6650128108596616e-08
1.9952623149688796e-05,1.6594555557112625e-08
1.9952623149688796e-05,1.6532167853144137e-08
1.9952623149688796e-05,1.6462029512327026e-08
1.9952623149688796e-05,1.6382988469074245e-08
1.9952623149688796e-05,1.6293604020234886e-08
1.9952623149688796e-05,1.619204201130206e-08
1.9952623149688796e-05,1.607591759753518e-08
1.9952623149688796e-05,1.5942050594874486e-08
1.9952623149688796e-05,1.578606776895102e-08
1.9952623149688796e-05,1.5601720601345105e-08
1.9952623149688796e-05,1.5379633197361104e-08
1.9952623149688796e-05,1.5104793671989548e-08
1.9952623149688796e-05,1.4750892345803154e-08
1.9952623149688796e-05,1.4265134295425351e-08
1.9952623149688796e-05,1.3514359541675816e-08
1.9952623149688796e-05,1.194579997186553e-08
1 signal nli
2 1.9952623149688796e-05 1.0570305869494063e-08
3 1.9952623149688796e-05 1.1989102199581664e-08
4 1.9952623149688796e-05 1.2687787891259665e-08
5 1.9952623149688796e-05 1.3153676763101585e-08
6 1.9952623149688796e-05 1.3504001312414315e-08
7 1.9952623149688796e-05 1.378517965356758e-08
8 1.9952623149688796e-05 1.4020312829929705e-08
9 1.9952623149688796e-05 1.4222564206194578e-08
10 1.9952623149688796e-05 1.440014394542033e-08
11 1.9952623149688796e-05 1.4558516068269932e-08
12 1.9952623149688796e-05 1.4701499315172012e-08
13 1.9952623149688796e-05 1.4831866587815758e-08
14 1.9952623149688796e-05 1.4951694168451522e-08
15 1.9952623149688796e-05 1.506257639956634e-08
16 1.9952623149688796e-05 1.5165763570833366e-08
17 1.9952623149688796e-05 1.5262253772723937e-08
18 1.9952623149688796e-05 1.535285600134073e-08
19 1.9952623149688796e-05 1.543823467328411e-08
20 1.9952623149688796e-05 1.551894175425445e-08
21 1.9952623149688796e-05 1.5595440417063968e-08
22 1.9952623149688796e-05 1.5668122772822936e-08
23 1.9952623149688796e-05 1.5737323370281063e-08
24 1.9952623149688796e-05 1.5803329618444796e-08
25 1.9952623149688796e-05 1.5866389935670908e-08
26 1.9952623149688796e-05 1.592672019391794e-08
27 1.9952623149688796e-05 1.598450886742589e-08
28 1.9952623149688796e-05 1.6039921184766554e-08
29 1.9952623149688796e-05 1.609310250559421e-08
30 1.9952623149688796e-05 1.61441810880001e-08
31 1.9952623149688796e-05 1.6193270372246937e-08
32 1.9952623149688796e-05 1.6240470877236143e-08
33 1.9952623149688796e-05 1.6285871784230113e-08
34 1.9952623149688796e-05 1.6329552265978812e-08
35 1.9952623149688796e-05 1.6371582606990462e-08
36 1.9952623149688796e-05 1.641202515119326e-08
37 1.9952623149688796e-05 1.6450935105904177e-08
38 1.9952623149688796e-05 1.6488361225310858e-08
39 1.9952623149688796e-05 1.6524346392188574e-08
40 1.9952623149688796e-05 1.6558928113022246e-08
41 1.9952623149688796e-05 1.6592138938867027e-08
42 1.9952623149688796e-05 1.6624006821997905e-08
43 1.9952623149688796e-05 1.665455541654349e-08
44 1.9952623149688796e-05 1.668380432977811e-08
45 1.9952623149688796e-05 1.6711769329485368e-08
46 1.9952623149688796e-05 1.6738462511750264e-08
47 1.9952623149688796e-05 1.6763892432637406e-08
48 1.9952623149688796e-05 1.6788064206436675e-08
49 1.9952623149688796e-05 1.681097957247311e-08
50 1.9952623149688796e-05 1.6832636931862217e-08
51 1.9952623149688796e-05 1.6853031355021186e-08
52 1.9952623149688796e-05 1.687215456020574e-08
53 1.9952623149688796e-05 1.688999486281053e-08
54 1.9952623149688796e-05 1.690653709463382e-08
55 1.9952623149688796e-05 1.6921762491746848e-08
56 1.9952623149688796e-05 1.6935648549006222e-08
57 1.9952623149688796e-05 1.6948168838584662e-08
58 1.9952623149688796e-05 1.695929278914847e-08
59 1.9952623149688796e-05 1.696898542145055e-08
60 1.9952623149688796e-05 1.6977207035104874e-08
61 1.9952623149688796e-05 1.6983912840119302e-08
62 1.9952623149688796e-05 1.6989052525338295e-08
63 1.9952623149688796e-05 1.699256975421989e-08
64 1.9952623149688796e-05 1.6994401576260005e-08
65 1.9952623149688796e-05 1.6994477739772384e-08
66 1.9952623149688796e-05 1.699271988849318e-08
67 1.9952623149688796e-05 1.6989040620420942e-08
68 1.9952623149688796e-05 1.6983342382173362e-08
69 1.9952623149688796e-05 1.6975516165613388e-08
70 1.9952623149688796e-05 1.6965439965112504e-08
71 1.9952623149688796e-05 1.6952976942961452e-08
72 1.9952623149688796e-05 1.693797323625088e-08
73 1.9952623149688796e-05 1.6920255319826076e-08
74 1.9952623149688796e-05 1.6899626814970685e-08
75 1.9952623149688796e-05 1.6875864599859146e-08
76 1.9952623149688796e-05 1.6848714031984708e-08
77 1.9952623149688796e-05 1.6817883029489423e-08
78 1.9952623149688796e-05 1.6783034669737056e-08
79 1.9952623149688796e-05 1.674377783759437e-08
80 1.9952623149688796e-05 1.669965527407164e-08
81 1.9952623149688796e-05 1.6650128108596616e-08
82 1.9952623149688796e-05 1.6594555557112625e-08
83 1.9952623149688796e-05 1.6532167853144137e-08
84 1.9952623149688796e-05 1.6462029512327026e-08
85 1.9952623149688796e-05 1.6382988469074245e-08
86 1.9952623149688796e-05 1.6293604020234886e-08
87 1.9952623149688796e-05 1.619204201130206e-08
88 1.9952623149688796e-05 1.607591759753518e-08
89 1.9952623149688796e-05 1.5942050594874486e-08
90 1.9952623149688796e-05 1.578606776895102e-08
91 1.9952623149688796e-05 1.5601720601345105e-08
92 1.9952623149688796e-05 1.5379633197361104e-08
93 1.9952623149688796e-05 1.5104793671989548e-08
94 1.9952623149688796e-05 1.4750892345803154e-08
95 1.9952623149688796e-05 1.4265134295425351e-08
96 1.9952623149688796e-05 1.3514359541675816e-08
97 1.9952623149688796e-05 1.194579997186553e-08

View File

@@ -0,0 +1,6 @@
signal,nli
1.9952623149688796e-05,5.183134799604202e-09
1.7957360834719913e-05,4.286200408629989e-09
2.593841009459543e-05,6.2510001955285065e-09
1.5962098519751036e-05,4.082332034495425e-09
2.3943147779626556e-05,7.857762167195498e-09
1 signal nli
2 1.9952623149688796e-05 5.183134799604202e-09
3 1.7957360834719913e-05 4.286200408629989e-09
4 2.593841009459543e-05 6.2510001955285065e-09
5 1.5962098519751036e-05 4.082332034495425e-09
6 2.3943147779626556e-05 7.857762167195498e-09

Some files were not shown because too many files have changed in this diff Show More