402 Commits

Author SHA1 Message Date
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
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
Jan Kundrát
24e7f4a5a1 refactoring: OpenROADM: store the NF model of a premp/booster
All other noise models set the `nf_def` variable, so let's make the YANG
code simpler by remembering the amplifier NF model like that.

Change-Id: I341e4ac296c25bf9f27a98a7e4e92e0fd1546021
2021-06-04 23:10:30 +02:00
Jan Kundrát
225cafa8b7 Floating point formatting of elements' operational parameters
The current JSON data loader preserves (some) integers as integers. When
printed, the value might not contain any decimal points. The YANG patch
series, however, forces floats when floats are expected (while still
allowing None). This makes the output subtly different.

Change-Id: I0e0c013eb3abddb4aeac1ba43bf0d473fed731d4
2021-06-04 23:10:09 +02:00
Jan Kundrát
ad9cbb8a93 Merge "Use the term GSNR in result outputs" 2021-06-04 20:54:58 +00:00
Jonas Mårtensson
581b4a726f Use the term GSNR in result outputs
The term "GSNR" is well established by now. I think it's time we start
using it in our own result outputs instead of alternatives like "total
SNR" or just "SNR".

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I1fc65f6db1e3b2d7cfe974875174132fe5b28d3b
2021-06-04 12:48:50 +02:00
Jan Kundrát
ce92d4e1b8 JSON: don't sleep when there are warnings in the input topology
Well, sleeping ain't fun. A red warning is plenty. In case of API
access, there's none, but that's just the APIs are.

Change-Id: I2fb0c051a9c3bb7f2ef2264083686e929c27ec2c
Fixes: 6a6591e4 (Add a warning message when attributes are missing in eqpt_config.json)
2021-06-04 00:04:16 +02:00
Jan Kundrát
eb17b74ea4 Merge "Fix behavior when there's no EDFA DGT" 2021-06-03 21:58:52 +00:00
Jan Kundrát
051359ad77 Merge changes I7596efae,I3633a59d
* changes:
  Remove unused property Fiber.fiber_loss
  Remove unused property
2021-06-03 21:49:58 +00:00
Jan Kundrát
912eb712c3 Fix behavior when there's no EDFA DGT
We ship "some" DGT values which effectively mask this, but it's possible
to provide a "trivial" DGT vector such as [0, 0]. When that happens, the
code was failing with a numpy-level warning related to a division by
zero.

The code tried to be ready for this by trying to catch an exception, but
this relied on a particular numpy behavior upon zero division which was
not set up properly. For the added fun, there are two possible cases of
division by zero:

- on a zero tilt, it's a case of `0.0 / np.float64(0.)`, which is
controlled via "invalid",
- on a non-zero tilt, it's a case of `<float> / np.float64(0.)`, which
leads to "divide".

Let's just check for zero instead of wrestling exceptions.

Change-Id: I7a3c0b6b9b85e90a8a7201b7c7a56a5a44c27d69
2021-06-03 10:57:25 +02:00
Jan Kundrát
ce4ea9d6e3 README: shuffle elements around a wee bit
Let's start with some brief introduction, and only then push the picture
with the schematics.

Change-Id: I7e8662c3afe5076c73411def97d0fcb1f1a03e7f
2021-06-03 02:22:46 +02:00
Jan Kundrát
39c894bb6a Remove unused property Fiber.fiber_loss
Change-Id: I7596efaeaa1c6cdef15118521662e54db34fd9e6
2021-06-03 01:54:06 +02:00
Jan Kundrát
be95496f85 Remove unused property
I dig through the git log, and it looks like something which was never
used after some refactorings 3+ years ago.

Change-Id: I3633a59d8f2720932fa32c885ee5be643e640a46
2021-06-03 01:52:44 +02:00
Jan Kundrát
d38dabc824 Merge changes I8523fb93,Ia832cd8f,Id92bda62
* changes:
  Remove unused variables
  remove unused variable
  remove unused import
2021-06-02 21:55:34 +00:00
Jan Kundrát
5ad6336fda Docker: use a newer Python
Change-Id: I2e6725f7a009690f1a49a77d9ec4ff696141d5b5
2021-06-02 23:22:32 +02:00
Jan Kundrát
8ec9aca559 equipment: remember NF_min, NF_max when using the operator model
...so that these original values are accessible to the YANG conversion
later on, if needed.

Change-Id: I254127f9641a39a0478d0ef27b9f2e524bf60bda
2021-06-02 23:18:54 +02:00
Jan Kundrát
9abec6c9b7 docs: convert something into MarkDown
Change-Id: I5880e6675657dfc52d01cce529aba6563f9ca75f
2021-06-02 23:18:54 +02:00
Jan Kundrát
e3b904fb06 docs: allow writing Sphinx docs in Markdown
...because it's less annoying than the ReStructuredText.

Change-Id: Ic3db1fe1834580b0f31190b3bf96d6ab95c3b40e
2021-06-02 23:18:54 +02:00
Jan Kundrát
5d13b9bfb6 docs: remove a dependency on PBR
In the past, we had some manual version info in this file, but
apparently it never got updated past the 0.1 release (yay). The, in
4d5d10935 I changed this to retrieve the version information via PBR's
existing magic. That worked well, showing the abbreviated commit hash at
the footer on ReadTheDocs, but I don't think it was terribly useful:

- it was just a hash, nothing like the output of `git describe` (i.e.,
no tag info was there),
- it wasn't showing up in local docs builds which use the Alabaster
default theme.

Now, in 1b2eb9a5 I split the dependency handling. The idea was to make
sure that a "regular installation" would not pull the docs in
needlessly. Unfortunately, the way I set up the RTD builds, the common
dependencies were skipped, and as a result, the `pbr` package was not
available to RTD, and stuff failed.

I could have configured RTD to *also* install from the main deps, but I
don't think this is actually needed. Given that the version info is
rather subtle, let's skip it altogether. Instead, it's better to use
RTD's built-in features for switching between docs for a particular
release (via a git tag) and for the generic "master branch".

Change-Id: Iab0cc9608fa6c24eba93d772370ecd379cf65b1e
Fixes: 1b2eb9a5 (docs: separate out dependencies)
2021-06-02 23:10:05 +02:00
Jan Kundrát
0c26fd24b5 Travis: fix the build
Builds on Travis-CI do not use Tox, and I forgot to fix the dependencies
there.

Change-Id: I8a75e82dd0832d6a1e55257722e7e98b5ed57655
2021-06-02 22:36:48 +02: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
1b2eb9a5a8 docs: separate out dependencies
The intention is to separate "stuff that's needed by this package" from
"stuff that's needed to build the docs".

Change-Id: I7baf27e6f814880ea241ccbd3de4b87a6e7c4d1b
2021-06-01 01:26:53 +02:00
Jan Kundrát
219204e320 docs: use a newer Python on readthedocs
They do not seem [1] to support 3.9 yet, so let's stick with the 3.8
version for now.

[1] https://docs.readthedocs.io/en/stable/config-file/v1.html#python-version

Change-Id: I581bde3057a2a33b1ac419cdca3d110d196bdcfe
2021-05-31 20:28:29 +02:00
Jan Kundrát
38fc1fdc6d Remove unused variables
Change-Id: I8523fb938123ec9767eb9f540b229ddca23b9da5
2021-05-31 20:15:25 +02:00
Jan Kundrát
e25e1fbe50 remove unused variable
It was only assigned to in `read_service_sheet`, and it's safe to remove
it also in `convert_service_sheet`.

Change-Id: Ia832cd8fea2d864e920907e455e834a3c3a724dd
2021-05-31 20:13:38 +02:00
Jan Kundrát
8a96ff563e remove unused import
Change-Id: Id92bda6263a1b8db70d9142970522c504c10126d
2021-05-31 20:11:28 +02:00
Jan Kundrát
27d4fb0811 docs: add a blurb about OpenROADM
The discoverability can be improved, but hey, I guess this is better
than nothing.

Change-Id: I57792b50adb93321eff7bf301107f67cb9a8747d
2021-05-31 20:01:46 +02:00
Jan Kundrát
b5a8ae3f06 badges: link to Gerrit
Change-Id: I7e9e72bdae304f5b96567c98d12aa9564f126fb5
2021-05-31 19:58:58 +02:00
Jan Kundrát
e45a54c2b5 README: rewrite in Markdown
I find this about 666x more readable than the ReStructuredText.

Change-Id: Ic07cf129e0052b0083fff6899650e54b627efde7
2021-05-31 19:50:14 +02:00
Jan Kundrát
172697a2aa docs: move the introduction from the README to an extra docs chapter
Change-Id: Ifefbcb35e0e53f5d664b5720943f90fab34e1380
2021-05-31 19:37:36 +02:00
Jan Kundrát
9762b6e610 docs: move the project meta-stuff into the docs
The README should be just that, a README. Ours was about four pages
long, that's a bit too much I'm afraid.

Change-Id: I646e9d09e512384898271e10ff628906299b75a9
2021-05-31 19:37:36 +02:00
Jan Kundrát
4675a74e02 docs: tweak the shields on the landing page
Change-Id: I8cb320c0c10390af4f7d209ded5f3acee34b9790
2021-05-31 19:37:36 +02:00
Jan Kundrát
a268c219ed docs: show the Python versions used on the latest release
Change-Id: I028166bf830d3229066cb04f7c6ed9d4028b9644
2021-05-31 19:05:49 +02:00
Jan Kundrát
a47f069d97 PyPI: Python 3.9 is supported as well
We've been testing with Python 3.9 via Travis since this February, so
let's mark the project accordingly. Once a release which contains this
commit gets uploaded to PyPI, it will be propagated to the pip's web
interface.

Change-Id: I84635157565f1dbe9936231fa32ef3f6a0605e2f
2021-05-31 19:02:19 +02:00
Jan Kundrát
8fcead4294 docs: add some latex fanciness
Change-Id: I39348e0994fe576ce33d8b241ab9cedd0fd184c8
2021-05-31 18:56:17 +02:00
Jan Kundrát
b6daa15356 tests: include the OpenROADM amplifiers
Change-Id: I26d6ad422917fa6fd5943ffaa5da933c2acec80e
2021-05-31 16:41:13 +02:00
Jan Kundrát
469c0f5218 Merge changes If2545b5d,I3b182f1a,I77494833
* changes:
  Add OpenROADM example network
  Add OpenROADM equipment config file
  Update gain_min of OpenROADM ILAs in eqpt_config.json to match MSA spec
2021-05-31 14:38:15 +00:00
Jonas Mårtensson
830ed22690 Add OpenROADM example network
Change-Id: If2545b5d102778404b44a11576054284eb52bea6
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2021-05-31 15:59:48 +02:00
Jonas Mårtensson
a386262bfd Add OpenROADM equipment config file
The config file has the following properties:

* Includes only OpenROADM EDFA types (ILA / booster / preamp) and sets
allowed_for_design to true for ILAs.

* Sets ROADM restrictions to OpenROADM booster and preamp.

* Adds an OpenROADM transceiver type_variety with mode parameters
following the MSA spec.

* Sets other parameters (power_dbm, padding, add_drop_osnr, etc.)
according to OpenROADM MSA spec.

Using this config file together with auto-design should result in an
"OpenROADM compliant" network. Specifically, compliant fiber input
power levels for 50 GHz spacing are obtained by setting power_dbm = 2
and padding = 11. For other spacings than 50 GHz, power_dbm should be
changed accordingly.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I3b182f1abcc22fd77d7ec073a6c87fad320957ae
2021-05-31 15:59:48 +02:00
Jan Kundrát
ede3c1a943 Merge changes I58d7e13a,Icf9a33db
* changes:
  CI: produce a HTML report with all linter warnings
  tox: do not pass envlist
2021-05-28 19:50:18 +00:00
Jan Kundrát
af767dd38a CI: produce a HTML report with all linter warnings
Change-Id: I58d7e13a20bd114277c6267653d44897c221526b
Depends-on: https://review.gerrithub.io/c/Telecominfraproject/oopt-zuul-jobs/+/517864
2021-05-28 20:36:39 +02:00
Jan Kundrát
6dcc5a8524 docs: document the f_min/f_max idiosyncrasies
Thanks to Jonas for pointing this out during the review of the OpenROADM
patches.

Change-Id: I4e3f973c9a4d4d01565210dd22bfec84794f0a26
2021-05-28 19:21:59 +02:00
Jan Kundrát
7b5878e2f2 tox: do not pass envlist
...because it's ignored by the CI, and it's kind of up to the invoker to
specify which env they want. We have not relied upon the multiple
environment feature of tox.

Change-Id: Icf9a33dbb5e3df641bd85e3aa7482dfb85f13e5f
2021-05-27 19:41:55 +02:00
Jan Kundrát
8c0eac1bdc Merge changes If02e7f18,I9eec3a10
* changes:
  tests: activate mccabe the complexity checker
  CI: flake8: check invalid "noqa" comments
2021-05-26 23:56:52 +00:00
Jan Kundrát
d09938c1b8 Merge "Fix wrong parameter name in error message raised in compute_nli" 2021-05-26 23:56:41 +00:00
Jonas Mårtensson
dba4da0169 Fix wrong parameter name in error message raised in compute_nli
The error message was refering to method_nli which does not exist. The
correct parameter name is nli_method_name.

Change-Id: I24f24a2c5251317e1a80dda60aa27ec151628172
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2021-05-26 22:29:34 +02:00
Jan Kundrát
998249be61 Merge "Round fiber length to two decimal places in Fiber string representation" 2021-05-26 09:32:53 +00:00
Jonas Mårtensson
ebdba47660 Round fiber length to two decimal places in Fiber string representation
The __str__ method of the Fiber element class rounded the fiber length
to an integer but then formatted it with two decimal places, which
doesn't make sense. With this patch, two decimal places are kept.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I03b886dff7ba624929ccc85b4d77d8d6a7cbcfb4
2021-05-26 10:47:21 +02:00
AndreaDAmico
6072203afb Fix z position array creation within Raman solver
The Raman solver gave the wrong loss profile when the fiber length was
not a multiple of the simulation parameter space_resolution as,
in this case, the fiber termination position was not included within
the considered z position array and the output loss profile was
evaluated at the wrong position.

Additionally, the Raman solver failed when the simulation parameter
space_resolution was greater than the fiber length as the z position
array contained only one element.

With this version the fiber termination position is always included
in the z position array and is composed of multiple uniformly spaced
positions and the final position (in general, the latter is not
uniformly spaced).

Example:
* fiber_length = 5, space_resolution = 4 => old version z = [0, 4]
                                          => new version z = [0, 4, 5]

* fiber_length = 5, space_resolution = 6 => old version z = [0]
                                          => new version z = [0, 5]

Alternative:

z = linespace(0, fiber_length, int(ceil(fiber_length/space_resolution)))

PROS
* Solves the first issue
* Returns a uniformly spaced z position array (there is not a
  straightforward advantage in the simulation)

CONS
* Does not solve the second issue
* It is slightly more involved

Change-Id: I8886c3563ac7305c49cb5915712777ef561c5d4f
Bug: https://github.com/Telecominfraproject/oopt-gnpy/discussions/400
2021-05-25 16:16:39 +00:00
EstherLerouzic
b867c57bee bugfix: don't check a reversed path that has no baudrate
without this change if a request was blocked on main path due to 'NO_FEASIBLE_BAUDRATE_WITH_SPACING'
and if the request was bidir, there was a propagation tentative on the reversed path
despite the fact that no baudrate was selected, which ended up with a program crash.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ie4e578aab944e534d8b2d99fb02c4e28a242e717
2021-05-20 16:53:16 +02:00
Jan Kundrát
4396a4efe9 docs: link to patches-in-progress in GerritHub from the README
Change-Id: Ie0e302c96973e26fafe61d4160e135fee23e7bc1
2021-05-20 16:24:55 +02:00
Jan Kundrát
afe686c666 GitHub: link to a faster landing page in GerritHub
The dashboards have changed in Gerrit 3.4 which got deployed to
GerritHub earlier today. Now there's no pagination and no is:mergeable
operator. Let's just link to a (paginated) list of all open changes,
that's probably better value for now.

Change-Id: I22ad68becfc33d3829bcd8aaccb5203e76faeded
2021-05-20 15:31:49 +02:00
Jonas Mårtensson
e0faf6107d Fix order of except clauses in cli_examples.py
ParametersError is a sub class of ConfigurationError so the
corresponding except clause should be placed before. Otherwise the
ConfigurationError will be matched first instead of the more specific
ParametersError.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I67156dd7321101693fdf061d77937d4e75462593
2021-05-20 11:43:56 +02:00
Jan Kundrát
441f566964 tests: activate mccabe the complexity checker
It turns out that my text editor has been warning about "excessive code
complexity" via the python-language-server plugin that uses pycodestyle
underneath. As the test suite is configured to use `flake8` instead,
let's make sure that the results are consistent, and configure the code
complexity threshold for the CI linter run as well.

I'm not sure if this is usable when run incrementally, though (such as
in the CI). I *think* that `flake8 --diff` disregards warnings issued on
lines which have not been changed in the corresponding diff, while
mccabe reports its warnings for the line with the function signature --
and that one is not included in the output of `git diff -U0`. As a
result, I suspect that if a patch increases the code complexity of a
function over the threshold, our CI setup might not report that.

However, a non-diff run of the linters via `flake8` will now start
reporting C901 warnings. For those using `python-language-server` to
integrate with their IDEs and text editors, install `pyls-flake8` to
preserve the existing functionality.

Change-Id: If02e7f189514077aca3e865e1882a4a3fabb4dba
2021-05-16 18:21:22 +02:00
Jan Kundrát
2ca92f1aaa CI: flake8: check invalid "noqa" comments
There are some (unmerged) patches which selectively mute some linter
warnings via the `# noqa: something` stanza. It makes sense to configure
the linter in such a way that it checks the format of that `something`
to make sure that the linter override is valid and selective.

Change-Id: I9eec3a1024d3df88f4f44d4df746ff514670f71f
2021-05-16 18:21:22 +02:00
EstherLerouzic
c4235fa61c Add a test case for the case of 2 adjacent fibers
make sure that their loss is not concatenated

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I63678dd5b72f7f6984101c4367320f3f981cb573
2021-05-12 13:07:47 +02:00
Jonas Mårtensson
41c53fbc5a Update gain_min of OpenROADM ILAs in eqpt_config.json to match MSA spec
Change-Id: I77494833d76cad6759e791ee1de6de025024f114
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2021-05-12 09:40:29 +02:00
Jonas Mårtensson
44f8cdbf20 Fix span loss calculation for auto-design
I believe a previous commit, 3ac08f5, changed the behaviour of span
loss calculation in an unintended way, since now it adds the loss of
consecutive fiber elements even when there is no fused element in
between. This means for example that no padding is added when the loss
of consecutive fibers is higher than the padding specified in the
equipment file even though inline amplifiers will be added between the
fibers in a later step. This patch changes the conditions in the next_
and prev_node_generator so that they stop when two consecutive fibers
are found.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I42c9188c789a98a9b3d7e51d5aae15774d40dde7
2021-05-12 09:19:05 +02:00
Jan Kundrát
07ef8e4e10 Fix (some) pep8 warnings
Change-Id: I3fd709b0b12617d534ce9edd5b287539ee036b38
2021-05-11 11:48:36 +02:00
Jonas Mårtensson
bf0e435542 Include ILA nodes when converting from xls to json
Fix GitHub issue #217

Currently, if a user specifies an ILA node in an xls file, including
city name and coordinates, but does not specify type of amplifier,
etc., in the Eqpt sheet, the ILA node is not preserved when converting
to json. This patch proposes to include all ILA nodes to prevent loss
of information.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: Id169348cce185e4d33d5b80068270b36043e3353
2021-05-11 11:35:00 +02:00
Jan Kundrát
b688493e98 Merge "Specifying a list of EDFA type varieties for auto-design" 2021-05-11 09:27:58 +00:00
Jan Kundrát
1ad01963c8 Merge "Better positioning of fibers after splitting" 2021-05-07 14:56:15 +00:00
Jan Kundrát
493de58e65 Merge changes Ifc3ec5f9,I7344ff53,I2971b975,Ia2ee3fdd,Ie57340d4, ...
* changes:
  equipment: make sure all OpenROADM EDFAs have "openroadm" in their names
  Introduce OpenROADM preamp and booster models
  The "openroadm" NF model only applies to inline amps
  docs: OpenROADM-ILA parameters in the JSON equipment library
  docs: JSON: do not claim that the default NF implementation is "advanced_model"
  docs: line wrapping
2021-05-07 14:03:51 +00:00
Jonas Mårtensson
7e97547774 Better positioning of fibers after splitting
Currently, calculated new fiber coordinates, after splitting a fiber by
auto-design, are evenly distibuted. Since coordinates for added inline
edfas are the midpoint between neighboring nodes, this makes the edfas
"look" non-equidistant even if all spans have the same length. I think
it would make more sense to have the fiber coordinates represent the
midpoint of the fiber. That way, the edfa positions will look more
"natural". Here is an attempt to illustrate the difference for a link
with three fiber spans:

Before this patch:

r-----f--e--f--e--f-----r

After this patch:
r---f---e---f---e---f---r

r = roadm
e = edfa
f = fiber

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I6eafe3fcd4c718b0b995a046dbff0fd04bdc42d7
2021-05-07 08:53:16 +02:00
Jan Kundrát
0f73a8f810 equipment: make sure all OpenROADM EDFAs have "openroadm" in their names
This could be (potentially) annoying to those users who rely on the
default equipment library. However, it brings at least some order into
the current state -- which was rather disorganized.

Suggested-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: Ifc3ec5f9e0e2526b8621d905160fc82af6a469f2
2021-05-06 21:10:43 +02:00
Jonas Mårtensson
fa834338ab Introduce OpenROADM preamp and booster models
The NF calculated by the preamp model is compliant with the MW-MW noise
mask in the OpenROADM MSA spec. The booster is noise-free, which is
modeled by setting the NF to zero (-inf in dB units). This is obviously
unphysical but it is the simplest way to model the total noise
contribution from a ROADM, including preamp and booster, that is
compliant with the the OpenROADM MSA.

This also introduces two new EDFA type varieties,
"openroadm_mw_mw_preamp" and "openroadm_mw_mw_booster" in the equipment
library. I would prefer to also change the names of the existing
"openroadm" type_def and "standard"/"low_noise" type_variety,
representing an OpenROADM inline-amplifier, for better consistency but
this probably needs to be discussed first.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I7344ff53623f166649efb389c95c04ff92718e35
Signed-off-by: Jan Kundrát <jan.kundrat@telecominfraproject.com>
Co-authored-by: Jan Kundrát <jan.kundrat@telecominfraproject.com>
2021-05-06 19:54:59 +02:00
Jan Kundrát
fc82f43b89 The "openroadm" NF model only applies to inline amps
Change-Id: I2971b97506c8d0a778b3007fd5bd092c3ba83601
2021-05-06 19:54:19 +02:00
Jan Kundrát
3d9d5d7a8d docs: OpenROADM-ILA parameters in the JSON equipment library
Change-Id: Ia2ee3fdde1c4eed0b7ef1de77153959230ba1c01
2021-05-06 19:54:17 +02:00
Jan Kundrát
eef2cdc81c docs: JSON: do not claim that the default NF implementation is "advanced_model"
As Jonas pointed out in Ia2ee3fdde1c4eed0b7ef1de77153959230ba1c01, the
documentation was wrong and the default NF implementation was not the
advanced_model. Let's not describe what the backwards-compatible
behavior is (we do not want our users to rely on that), and instead just
say that "adavnced_model" activates, well, the advanced model.

Change-Id: Ie57340d491a6f73d696d77c07091f85952cb4a08
2021-05-06 19:53:32 +02:00
Jan Kundrát
487ca8c2d6 docs: line wrapping
The numbered list was very hard to read; split that into one sentence
per line. Also do that everywhere else but in the tables (in RST I'm
afraid this would be super-painful).

Change-Id: Ib80c05b66cbc98af2d0dda612943f91d923425b0
2021-05-06 19:51:45 +02:00
Jan Kundrát
ec66d628f0 CI: do not "fail" branches upon coverage decrease
Right now the project homepage on GitHub shows an red failure due to
[1]. While I agree that coverage reports are a useful metric, they are
most relevant when considering patches (or changes) on their own. I do
not think it makes any sense to show them on branches. When we merged
that commit, the reviewer had the info about the coverage change, and we
just do not have 100% coverage. Let's not pretend that that is a
blocker.

[1] 924c56850d

Change-Id: I652ada3357e521c9a3351faac2f9c2e8e4aa5773
2021-05-06 17:02:33 +02:00
Jan Kundrát
924c56850d Merge "Properly remove duplicate links in XLS conversion" 2021-05-04 09:32:55 +00:00
Jan Kundrát
21385cbf03 Merge "docs: Fix a typo" 2021-05-04 08:14:20 +00:00
Jan Kundrát
87c617b602 Merge "Better naming and location of Roadm preamp/booster Edfas in auto-design" 2021-05-04 08:13:40 +00:00
Jonas Mårtensson
4fce4ea7d8 Simplify plotting code
Remove unused code.

Change-Id: I2d3dc62d578912b69d3b5d45634ba5d3f271538d
2021-05-02 23:11:44 +02:00
Jonas Mårtensson
2ddbd961ff Fix plotting bug
See GitHub issue #391

There has been a change in the networkx drawing API, which means
'figure' is no longer an accepted keyword argument.

Change-Id: I8600e8cd5eb2cb378a529c7857f664c1ebed8337
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2021-05-02 11:28:18 +02:00
Jan Kundrát
05a044dc2c Merge changes I6962b9f1,I41a65e89
* changes:
  Detect unconnected nodes via span generators
  Fix bug on iteration within a span
2021-04-30 15:01:32 +00:00
Jan Kundrát
340840840f Detect unconnected nodes via span generators
Change-Id: I6962b9f193723dc7856b324d5da94d2f46b21c06
2021-04-30 16:33:44 +02:00
EstherLerouzic
3ac08f59e2 Fix bug on iteration within a span
The old code assumed that the Fused node only connects Fiber nodes. In a
sequence of Fused - Amplifier - Fused - Fiber, the Amplifier would be
included by a mistake. In addition, the code was not that easy to read,
and it just instantiated StopIteration without raising that (which would
be an error in a generator context). It was also rather strict, failing
if the iterator was requested for an "edge node" (a transponder), and
one of the exceptions was not actually an f-string.

Finally, the span_loss function would occasionally report wrong values
(for example in the provided test case, span_loss("fused7") would say 1
instead of 17).

Fix this by making sure that prev_node_generator() and
next_node_generator() never return anything but Fiber and Fused nodes,
which made it possible to simplify the span_loss() function. This should
now properly account for the accumulated losses of an arbitrary sequence
of Fiber and Fused elements.

I went over this a few times because set_egress_amplifier() calls
span_loss() on a *ROADM* node type. That does not make any sense, and
the code deals with this "properly" by returning a loss of 0 dB. It was
a bit confusing for me to see that it's actually OK to ask for a "span
loss" that's identified by a ROADM.

A side effect of this code is that Fused instances that are isolated
from the rest of the topology no longer raise an exception. I was
thinking about preserving this (because for GNPy, the only element with
no previous or no next nodes are the transceivers, but then Esther's
test topology contains an isolated `fused4` element. If we want to make
this strict, we can do that easily like this:

 --- a/gnpy/core/network.py
 +++ b/gnpy/core/network.py
 @@ -162,10 +162,12 @@ _fiber_fused_types = (elements.Fused, elements.Fiber)
  def prev_node_generator(network, node):
      """fused spans interest:
      iterate over all predecessors while they are Fused or Fiber type"""
      try:
          prev_node = next(network.predecessors(node))
      except StopIteration:
 -        return
 +        if isinstance(node, elements.Transceiver):
 +            return
 +        raise NetworkTopologyError(f'Node {node.uid} is not properly connected, please check network topology')
      if isinstance(prev_node, _fiber_fused_types) and isinstance(node, _fiber_fused_types):
          yield prev_node
          yield from prev_node_generator(network, prev_node)

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Co-authored-by: Jan Kundrát <jan.kundrat@telecominfraproject.com>
Change-Id: I41a65e89eef763f82e41e52bc325ed2d488cb601
2021-04-30 16:03:21 +02:00
Jonas Mårtensson
3bcafc2345 Add some documentation about amplifier tilt setting
It is added to the Excel input files documentation.
(We should also have a topology json input file documentation but it
currently does not exist.)

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I3184e36090388155e826d1b09bc9c6bf28d623d0
2021-04-22 15:19:48 +02:00
Jonas Mårtensson
b58c089945 Change sign convention for amplifier tilt
As pointed out in GitHub issue #390, the normal convention for the sign
of amplifier tilt is to define it with regard to wavelength, i.e.
negative tilt means lower gain for longer wavelengths (lower
frequencies). Currently GNPy uses the opposite convention, which this
patch proposes to change.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I8f7829a3b0b0b710f7da013c525630a60b22a2b5
2021-04-21 13:19:56 +02:00
Jonas Mårtensson
a211e305c3 Define tilt target over the full amplifier bandwidth
Currently the tilt_target defined by a user is applied over the band of
propagating channels. This means for example that if only two channels
are propagated, the difference in gain between the two channels will be
equal to the tilt_target, independently of how close the two channels
are in frequency. I think it makes more sense to always define the
tilt_target over the full operational bandwidth of the amplifier.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I4f29de2edc4d0de239b34e0d8d678d964b6a0af3
2021-04-21 12:59:24 +02:00
Jonas Mårtensson
3a72ce84d0 Reverse order of values in example amplifier config files
As identified in GitHub issue #390, the dgt values (as well as gain and
nf ripple values) in example config json files are listed in order of
increasing wavelength (decreasing frequency) while the code assumes
values listed in the opposite order. This patch reverses the order of
values in affected files so that they are consistent with existing use
in the code.

Also, the f_min value in the Juniper-BoosterHG.json file is updated to
match measurement data so that interpolation is performed correctly.

Change-Id: I97a9d2f9be81380d1658bee5fa1ef4def3e1c537
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2021-04-20 21:17:26 +02:00
Jan Kundrát
de09b4f8ce docs: Fix a typo
Change-Id: I370f6887872c268416623e93141fca3eb902499a
2021-04-20 17:58:18 +02:00
Jonas Mårtensson
be5519455f Change example file meshTopologyExampleV2.json
Following the change of the corresponding xls file in 60b9256 we should
change also this json file for consistency and to avoid unnecessary
confusion.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: Iab12002544ad6b8489d8dfa0511fdce762cc1d7a
2021-04-06 16:06:56 +02:00
Jan Kundrát
f98eb2c10c Merge changes from topic "warning on gain"
* changes:
  Change example file meshTopologyExampleV2.xls
  add warning in case gain over max_flat_gain + extended_gain_range
2021-04-06 10:29:11 +00:00
EstherLerouzic
60b9256f22 Change example file meshTopologyExampleV2.xls
Previous file imposed equipment types inconsistantly with required gain

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: If19c5a0a710edf280fde146bca94c431bb5fe836
2021-04-06 11:53:07 +02:00
EstherLerouzic
94b9c16d67 add warning in case gain over max_flat_gain + extended_gain_range
This is an old pull request rebased and restricted to only raising warning.
The initial work also limited gain, which is finally not a desired behaviour:
an advanced user might want to have this high gain.

the only impact on test is that it raises warnings on almost all amplifiers
on the mesh_topology_exampleV2.xls: indeed all of them are set to low_gain
but without gain specified and the result of autodesign results in higher gains
than supported by this amplifier variety.

This may be confusing for users to see these warnings on an example from gnpy
so I will push a new commit changing the amp types to avoid this.
The alternative would be to push the warnings into the logger, so they
remain invisible, but I think that the example change makes more sense.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Idf0c67137b5b466b07ddc7817f53a82f92a21a5b
2021-04-06 11:52:36 +02:00
Jan Kundrát
eaccd63739 Update minimal required version of Pygments for CVE-2021-27291
Change-Id: I2e3a7f2bf2ef05b318e787909e6e7ec33e72717e
2021-03-30 11:31:24 +02:00
Jan Kundrát
8fcb61f12c docs: fix link to an example file
Relative links within the source tree work, but they were not being
turned into nice usable hyperlink that go back to GitHub under the
generated documentation.

Reported-by: Melin, Stefan M. <Stefan.Melin@teliacompany.com>
Change-Id: I137ad95fa75a6ee5e1b03a252782e6357d36a3af
2021-02-16 20:00:19 +00: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
Jan Kundrát
8e62955bb0 docs: Remove '%3' from graphviz mouse hover tooltips
In the generated SVG files, there was a '%3' shown as a mouseover
tooltip on mouse hover. Apparently that's what ended up in the <title>
of the whole generated SVG document.

This turned out to be an internal thingy in graphviz/dot. In Sphinx
there's already some support for catching element IDs with this '%3'
madness, but apparently titles are not handled like that.

Solve this simply by providing a title for the whole `graph {}` stanza
in the embedded dot graphs.

Reported-by: Melin, Stefan M. <Stefan.Melin@teliacompany.com>
Bug: https://gitlab.com/graphviz/graphviz/-/issues/1327
See-also: https://github.com/sphinx-doc/sphinx/commit/8494638e45
Change-Id: Ia9f39d13748cdffe74f2cb5032f92c77babb20d8
2021-02-16 18:04:21 +01:00
Jan Kundrát
561fd76a20 Upgrade Sphinx
I wanted to make sure that that funny SVG rendering is not a problem of
an outdated Sphinx. It is not, see the next commit. However, there's no
need to stay on a release from 2017, there's 3.5.0 already.

Change-Id: I181cc5d96eae3fadbf93711063e8f969a2f451cb
2021-02-16 00:26:32 +01:00
Jan Kundrát
ccb6653f50 docs: improve Raman description for vendors
Suggested-by: Melin, Stefan M. <Stefan.Melin@teliacompany.com>
Change-Id: Ib8a1b66cc28bddc512ac529f9326490b1092be79
2021-02-16 00:10: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
Jan Kundrát
3a31d458ee Merge "Minor refactor in disjunctions_from_json" 2021-02-03 15:48:35 +00:00
Jan Kundrát
22d55ae881 CI: travis: also test on Python 3.9
Change-Id: I9ee55daacc8ebba1736ad1b6fa5d8888c9daf90d
2021-02-03 16:11:59 +01:00
Jan Kundrát
3324645f78 CI: travis: upgrade to a newer base image
Change-Id: I4c6ee2e894c58a07eea89dd20414b87436e977c2
2021-02-03 16:11:46 +01:00
Jan Kundrát
3014a881f5 CI: Unbreak Travis
...because that one obviously doesn't read bindep.txt, but it has its
own way of installing these dependencies.

Change-Id: I6c2a70095c57686228ae697a06bb82b8c6d47459
2021-02-03 16:10:44 +01:00
Jan Kundrát
35877022ec docs: explain general concepts and the equipment library
Change-Id: I3b18b9695f417a3e9b747fc5ce6fe41946fa55f3
2021-02-03 15:23:04 +01:00
Jan Kundrát
9b985d1fc5 docs: fix copyright string
Change-Id: Ie74216e78861e6f32772177cffd8d51e5029152f
2021-02-03 13:56:43 +01:00
Jan Kundrát
cd95c83bbf sphinx: fix coding style
Change-Id: I01c3b81d9fd5eee5942509fc9e462f0495cab6f1
2021-01-25 11:43:50 +01:00
Jonas Mårtensson
f9dbf7d132 Fix github bug #380
Patch 503833 (Fix calculation of gain for first Edfa after Roadm)
introduced a bug when it was rebased on top of the per degree power
feature since per_degree_pch_out_db used for propagation was actually
updated based on calculated prev_dp value.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I39e8f5945d5035b99c70ef577011bba79bb89a72
2021-01-21 18:28:23 +01:00
Jonas Mårtensson
b37248077c Specifying a list of EDFA type varieties for auto-design
This allows users to limit the choice of type_variety by auto-design
for an EDFA node by setting a "variety_list" attribute in the input
topology json file. One use-case is switchable gain EDFAs where the
two gain ranges can be modeled by two separate type varieties in the
equipment library. A user may know that such an EDFA will be used in
a node but not which gain range is optimal. The choice of gain range
can then be left to auto-design while not allowing any other
type_variety by specifying the node e.g. like this in the topology:

{
  "uid": "Edfa1",
  "type": "Edfa",
  "variety_list": ["foo_gain_range_1", "foo_gain_range_2"],
  ...
}

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: Ia69ef78f885e3a61310530b6b80da64e43758211
2021-01-21 13:09:48 +01:00
Jan Kundrát
d2a8d8e887 Merge "Support Trx non-Roadm nodes in topology with Roadms" 2021-01-14 17:39:56 +00:00
Jonas Mårtensson
35c4073292 Minor refactor in disjunctions_from_json
Improve code clarity and readability.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I73f0b8c15769b689f35d9e8dc6fee4ee07f7cade
2021-01-13 17:46:14 +01:00
Jonas Mårtensson
b8e72511de Properly remove duplicate links in XLS conversion
The sanity_check function in convert.py removes duplicate links in the
input XLS file. However, it doesn't remove them from the links_by_city
dict. This has two consequences. Firstly, the output json will still
have duplicate connections. Since networkx.DiGraph.add_edge will only
add one edge anyway, this has no major impact. Secondly, sanity_check
will think that a degree-2 ILA node with duplicate links is higher
degree and change it to a ROADM node.

With this patch, duplicate links are removed also from links_by_city.

Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
Change-Id: I4be9ab225bc89277aec467a5bd60216b4aa31993
2021-01-09 17:39:33 +01:00
Jonas Mårtensson
120c326e77 Merge "Bump all dependencies" 2021-01-08 11:16:48 +00:00
Jan Kundrát
01115f9852 Merge "Raise error when type_def for Edfa is not recognized" 2021-01-01 19:52:45 +00:00
Jan Kundrát
b91ea1828f Bump all dependencies
I think it's "healthy" to sync up the list of minimal versions of our
dependencies with what the CI is actually checking, at least every now
and then. We're getting some variance via the list of different Python
interpreters, but still -- let's make sure that our dependencies match
what we're testing.

The only interesting case is scipy where the 1.6 release started
requiring Python 3.7. Let's stick with 1.5.x, which is the last version
supported on Python 3.6.

Change-Id: I4aa0a89e7c615f2095a951ff522a4838ef50178e
2021-01-01 20:16:51 +01:00
Jan Kundrát
d2a294ac5a Bumpy numpy for Travis CI
On Travis, builds with Python 3.7 started failing recently:

```
 ERROR: pandas 1.2.0 has requirement numpy>=1.16.5, but you'll have numpy 1.16.4 which is incompatible.
 ERROR: scipy 1.6.0 has requirement numpy>=1.16.5, but you'll have numpy 1.16.4 which is incompatible.
```

Apparently the version of numpy that gets used when under Travis is
rather different among their Python versions, and we get:

- numpy-1.19.4 in their Python 3.8.6 env,
- numpy-1.19.0 on 3.6.7,
- numpy-1.16.4 on 3.7.1

That's a bit insane to my liking, perhaps they're preloading popular
libs such as numpy into their images for speed ups? For comparison, on
Zuul CI we have this:

- numpy-1.19.4 with Python 3.8.6 on Fedora 32,
- numpy-1.19.4 with Python 3.6.8 on CentOS 8

Let's start requiring numpy 1.19.4 everywhere. This is the cost we're
paying for https://github.com/Telecominfraproject/oopt-gnpy/pull/268 .

Change-Id: I6f1167096cfadc415cf456ad09ff0a08b535fc08
2021-01-01 19:55:13 +01:00
Jan Kundrát
f41acf31f6 Merge "Don't call sys.exit() from library functions" 2021-01-01 18:41:41 +00:00
Jan Kundrát
8cef09158f Merge "Error out on conflicting Fiber and RamanFiber definitions" 2021-01-01 18:16:30 +00:00
Jonas Mårtensson
549e04e925 Better naming and location of Roadm preamp/booster Edfas in auto-design
Currently when an Edfa is inserted by auto-design after a Roadm (i.e.
booster) it gets the same city attribute as the Roadm while an Edfa
inserted before a Roadm (preamp) gets the city attribute from the
preceding fiber. This is illogical and confusing. Also both the Edfa
preamp and booster get coordinates different from the Roadm (halfway
between the Roadm and the neighbor node). Since in practice the preamp
and booster are always colocated with the Roadm I think it makes more
sense to give them the same coordinates.

Also change how uid is assigned to an Edfa connected to a Roadm so that
it indicates whether it is a booster or a preamp.

Change-Id: I98718fe1e2914b5628e7cfd23fc28fb5708a8889
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-12-28 21:00:44 +01:00
Jonas Mårtensson
918c19b1bc Raise error when type_def for Edfa is not recognized
When loading the equipment file in json_io.py we should raise an error
if an Edfa type_variety specifies a NF type_def that is not
implemented. This should also allow to remove the assert statement in
the _nf method in the Edfa class.

Change-Id: Ida0bb19829c0ee54ecbe3e2f74ea7c22eb24f6a2
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-12-21 22:25:44 +01:00
Jonas Mårtensson
6820a3fc36 Don't call sys.exit() from library functions
I think it's better to raise an exception instead like we do for other
errors.

Change-Id: If6dd795bd76471b2534d873772e991d9ae0a4271
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-12-21 14:38:50 +01:00
Jonas Mårtensson
7e8ed590eb Support Trx non-Roadm nodes in topology with Roadms
Currently, auto-design does not work for an OMS starting from a Trx
that is not connected through a Roadm if the topology also contains one
or more Roadms. I don't see a good reason not to support this case,
since a Trx not connected through a Roadm can make sense, e.g. for a
degree-1 node. This is a proposal to remove the restriction.

Change-Id: I14686521a838b30249126b9bd403fa26c848875a
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-12-21 14:10:35 +01:00
Esther Le Rouzic
4218b7ef44 Merge "correction of the excel to json conversion of per degree" 2020-12-17 13:57:45 +00:00
Jan Kundrát
87af343b38 Merge "Include operational parameters in RamanFiber to_json method" 2020-12-17 10:44:59 +00:00
Jonas Mårtensson
26cd33b4dc Include operational parameters in RamanFiber to_json method
Currently, the RamanFiber class does not implement its own to_json
method but inherits it from the parent Fiber class. This means that
operational parameters (temperature and raman_pumps) are not included
(and therefore not picked up by the network_to_json method in
json_io.py). So if a user saves the topology, e.g. using the
--save-network option, and later uses that saved topology as input,
the result will be wrong.

This patch includes the operational parameters in to_json.

Change-Id: I07c09a4d122858ff412373623d8c0a087a3e11ec
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-12-14 09:28:29 +01:00
Jonas Mårtensson
861724ef4f Detect loops in network topology
Currently, if an input topology contains a loop in an OMS, the
set_egress_amplifier method in network.py will go into an infinite
loop. With this patch, such a loop is detected and an error is raised.

Change-Id: Id7cd0cc6d7eaff3af1d9e0309b5c2eb90aeb7454
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-12-11 11:08:54 +01:00
Jonas Mårtensson
86492cff60 Check that nodes are properly connected in network topology
In network.py we are already checking that fibers are properly
connected. Let's check Edfas and other node types as well.

Change-Id: I224b9046729197fef3eb172e9631969a2da13ab5
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-12-11 09:12:34 +01:00
Jan Kundrát
27fd5cdad6 Error out on conflicting Fiber and RamanFiber definitions
The equipment library has, so far, used completely different namespaces
for `Fiber` and `RamanFiber` definitions. It was possible to define a
"SSMF" fiber whose properties, such as CD or PMD, would differ when used
as a Fiber vs. the RamanFiber object in the networking topology. That is
likely a bug of the equipment library, so this patch makes sure that a
configuration like that cannot be used.

I came across this when working on the YANG support where both fiber
types are defined in a common namespace, and the difference between them
is determined by lack or presence of a sub-container with the Raman
properties.

Change-Id: I8e86bed80eb1794b8abd4e1ecd96398d395f81f2
2020-12-03 15:08:55 +01:00
EstherLerouzic
2fc444be4b correction of the excel to json conversion of per degree
The patch correction changing the params
from
per_degree_params: { to_node: xx , target_pch_out_db: yy}
to
per_degree_pch_out_db: {xx: yy}

had not been updated on convert.py for reading the excel input.

the commit also fixes the automatic tests with the correct version

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I17a5cfad18e1b570a7aaa218e932368fa80f2d37
2020-12-03 14:49:53 +01:00
Jonas Mårtensson
d3490ae30c Raise error if specified Edfa type_variety is missing in eqpt config
A recent patch introduced the possibility to define an Edfa in the
topology file without specifying its type_variety:

https://review.gerrithub.io/c/Telecominfraproject/oopt-gnpy/+/488967

But with the current implementation, if a user specifies a type_variety
that is missing from the equipment configuration file (e.g. because of
a spelling mistake) this is silently ignored and the type_variety is
instead selected by auto design. I think this is not desired since it
can lead to confusing results. This patch proposes to raise an error
when the specified type_variety is missing while still allowing the user
to not specify type_variety or set type_variety = '' for selection by
auto design.

A recently introduced test actually does exactly this, i.e. it defines
Edfas with type_variety = 'std_high_gain' even though this type variety
does not exist in the equipment config file used by the test. Therefore
this patch also modifies the topology file used by that test.

Change-Id: I7d2a1aa6d633b62d51a99b07e8270cafcbad505f
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-12-01 21:58:56 +01:00
Jan Kundrát
59c3895a51 Merge "Remove while loop to avoid infinite loop" 2020-11-24 16:27:22 +00:00
Jan Kundrát
11bc41b941 Merge "cleaning: minor changes and specific numpy imports in utils and science_utils." 2020-11-19 15:22:02 +00:00
AndreaDAmico
9a7f94a391 cleaning: minor changes and specific numpy imports in utils and science_utils.
Change-Id: I57cd9075dd0a523a90131fbd8747519cf6554900
2020-11-19 14:57:57 +00:00
Jan Kundrát
6487b98136 Merge changes Idc473762,I004de102
* changes:
  Fix calculation of power target for Edfa in gain mode
  Fix calculation of gain for first Edfa after Roadm
2020-11-17 13:35:26 +00:00
Jan Kundrát
15df99510f plot: Show something useful for missing city names
This makes it possible to render at least something on tiny topologies
now that YANG doesn't support city labels.

Change-Id: I1431b557b2eecd34bf24557fdee0da0f2c2c0487
2020-11-10 14:43:52 +00:00
Jonas Mårtensson
3d5b1fcf64 Fix calculation of power target for Edfa in gain mode
See GitHub issue #368

The out_voa attenuation of the previous Edfa is currently not taken
into account when calculating power target for an Edfa in gain mode.
This makes the calculated gain target (in case of autodesign) for
the following Edfa in the chain incorrect and also impacts automatic
amplifier selection.

Change-Id: Idc473762ccf7b021a0885c7ce20de1abb66eb075
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-11-03 21:40:38 +01:00
Jonas Mårtensson
928bc42cb9 Fix calculation of gain for first Edfa after Roadm
See issue reported in #360

In autodesign, currently the calculation of gain for the first Edfa
after a Roadm is incorrect when the reference channel power is
different from 0 dBm. The bug is somewhat hidden by the fact that the
gain is anyway updated during propagation in power mode, taking the
reference channel power into account. But the gain reported by the
to_json function of Edfa elements before propagation will be wrong.
And more seriously, the incorrect gain will impact the Edfa selection
in autodesign.

Change-Id: I004de102832c3a0786435e21e71b0444d8901604
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-11-03 21:34:45 +01:00
EstherLerouzic
643680ec47 add a set of test to check power equalization of roadm
Check that egress power of roadm is equal to target power if input power
is greater than target power else, that it is equal to input power.
Use a simple two hops A-B-C topology for the test where the prev_node
in ROADM B is either an amplifier or a fused. If it is a fused, the target
power can not be met.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I3388f060a4f364055d58c8ca7c2b59143f784fa8
2020-11-03 16:44:28 +01:00
EstherLerouzic
a4a144a319 Add tests for the per degree feature
- add a test on the json conversion in case of a ROADM sheet
- test the per per_degree_pch_out_db is correctly created with specified
  values or default.
- also tests that the convert correctly creates the correct equipment
  only if the entry exists in eqpt sheet.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ic006d9c741404b185d15678953d2801cd17bab97
2020-11-03 16:44:28 +01:00
EstherLerouzic
093085fba8 adding a roadm sheet to handle per degree info in roadms
This part only targets conversion from an xls input topology file.
In order to define per degree power, the convert function needs to know
the booster final name.
However before this change, the booster name may not be known if there
is no defined amplifier in eqpt sheet at this stage.
In order to solve this ambiguity, the final name are defined in the convert
function provided that the direction is defined in eqpt sheet and
even if the amp type is not defined.

Then the per degrre target power is defined in a new roadm sheet using
the same direction naming as for Eqpt sheet.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I8f279aa880aa0a8ceb60e48e43bddb0f6ff06147
2020-11-03 16:44:28 +01:00
EstherLerouzic
c56ea898a6 Add per degree channel power target out
- add the per degree info using the EXACT next node uid as identifier
  of the degree when a node is a roadm
- add the degree identifier on the propagate and on the call functions
- use the per degree target_pch_out_db defined in json entry for the
  target power in network build
- verifies existence of the per degree power target in order to support
  partial per degree target power definition
- correct test data files for expected auto design results that now
  should include the per degree information, even if it is the same
  for all degree.
- in order to enable per degree power definition on direction where
  booster is not defined, enable the declaration of edfas in json without
  specifying type variety

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I5004cbb250ca5fd6b6498ac9d4b9c4f28a65efee
2020-11-03 16:44:21 +01:00
Jan Kundrát
a5398a5c57 docs: fix a broken link
Reported-by: Melin, Stefan M. <Stefan.Melin@teliacompany.com>
Change-Id: I8b9b6939c340b1856f1304f56880526b0f578914
2020-11-02 19:54:56 +01:00
EstherLerouzic
6dd40935b7 Remove while loop to avoid infinite loop
In the previous implementation, when a constraint was added on top of
the disjunction, there could be an infinite loop because the test was
not correctly performed on the request_id but on a request class.
Moreover, there the loop was not needed since the first feasible
candidate is selected if it exist.
This patch removes the loop and adds some comments to explain the code.

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Icdbee9032f28214b3e03cb62d55ea353477d94bf
2020-10-21 15:17:08 +02:00
Jan Kundrát
e6ee512001 Explicitly consider "system margins" instead of stashing them to transponder modes
Since 30234f913c, the code just added the
"system margins" to each transponder's minimal required OSNR. That's
simple and straightforward, but I think that mutating "equipment
library" in response to a "global simulation parameter" is not really
the right way to go.

Make this explicit; always check the resulting OSNR against the
transponder's minimum plus the margin.

I got into this when checking the fixups that are performed within the
JSON loader. I don't think that the JSON loader is an appropriate place
for this.

Change-Id: Ic8f8385cbbb29dc0d769462f17caad47c2b36940
2020-09-15 10:04:15 +02:00
Jan Kundrát
e13d27c1f5 Merge "docs: link to a Gerrit tutorial for contributors" 2020-09-08 18:32:13 +00:00
Jan Kundrát
bb552fbdd6 Merge "XLS: improve consistency checks" 2020-09-08 18:22:36 +00:00
Jan Kundrát
ed8a3dd933 Merge "docs: distinguish "equipment parameters" from "simulation parameters"" 2020-09-08 14:51:30 +00:00
Jan Kundrát
3204077a6c XLS: improve consistency checks
- do not talk about just "Nodes" and "Links" when also checking "Eqpt"
- don't check for non-existing "Nodes" from "Links" when that's already
  done elsewhere (and improve that check's error message)
- when checking "Eqpt", verify not just "from_city", but also "to_city"

Change-Id: I2e926049fa5e3c4dcb08cc29f18970a3b3b077d8
2020-09-08 16:48:26 +02:00
Jan Kundrát
85d1bf4e1e docs: distinguish "equipment parameters" from "simulation parameters"
This was identified during today's coders call where Andrea asked what
the best place for documentation of `sim_params.json` is. Let's split
docs about tangible equipment from those of global "simulation options".
Of course these options are still done in `eqpt_config.json`, which
might get super-confusing to the user -- so please make sure that this
is correctly explained when adding docs for `sim_params.json` in future.

Change-Id: If43894e8f562ca8a768b0efb6cc6c1afeb4aa514
2020-09-02 00:52:20 +02:00
Jan Kundrát
42edb2e6b9 docs: XLS: improve description of the "Eqpt" sheet
Change-Id: I57288798298563cfcc7d014a716bf549721db035
2020-09-02 00:45:42 +02:00
Jan Kundrát
21174a4190 Merge "Fix bug when calculating new coordinates in split_fiber" 2020-08-31 21:21:42 +00:00
Jan Kundrát
f6c2da24cd docs: link to a Gerrit tutorial for contributors
I've already updated the wiki pages, so the onboarding guide is
"correct", but a bit verbose. Let's just try to make the contributor
experience streamlined a wee bit.

Let's also use a GitHub PR template so that people are aware of Gerrit.

Change-Id: I44fb095f14396c7ba49494c32b6215f6a29ade2b
2020-08-31 23:08:59 +02:00
Jonas Mårtensson
9f16aaac61 Fix bug when calculating new coordinates in split_fiber
The current implementation based on scipy interp1d fails when
predecessor and successor of the fiber to be split have the same
longitude. In this case the new latitudes become NaN. This patch
fixes the bug.

Change-Id: I7c5dc4d410630a6b4b773d36cc192db8271a4346
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-08-24 21:55:33 +02:00
Jonas Mårtensson
29fc9d7dac Handle exceptions in cli_examples when calling build_network
Since build_network can raise NetworkTopologyError and
ConfigurationError we should handle these in cli_examples instead of
crashing.

Change-Id: I4b84831c74be7f1c88253c938f3f67b2d204630e
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-07-28 23:06:18 +02:00
Jonas Mårtensson
be3af5c2e5 Handle network topology error in add_connector_loss function
Other functions used be build_network raises NetworkTopologyError when
a fiber is not properly connected but this handling was missing from
add_connector_loss.

Change-Id: Id08cd4a9bad15f755d364a31ff3d38993d034447
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-07-28 22:56:21 +02:00
Jan Kundrát
e0e9ebde28 CI: Prefer Python 3.8 and Fedora 32 over EL8
The first build was much faster, there was about 50s in the actual
pytest invocation time. Overall, the total time is about a minute
quicker, which is nice. I cannot qualify whether the improvement is due
to a 1g-1c -> 2g-2c VM flavor underneath, or if it's that newer Python
*and* updated compile options in Fedora -- but it's a nice improvement.
The downside is twice the cost of the VM hour.

Depends-on: https://review.gerrithub.io/c/Telecominfraproject/oopt-zuul-jobs/+/497370
See-also: https://fedoraproject.org/wiki/Changes/PythonNoSemanticInterpositionSpeedup
Change-Id: I6f5db61e9481170515b2f7ff4f3358fae5daa68e
2020-07-09 13:39:55 +02:00
Jan Kundrát
5dc16a39c5 CI: Build with Python 3.8
Now that we're using new enough Pandas, it is actually safe to start
building on Python 3.8 as well.

Depends-on: https://review.gerrithub.io/c/Telecominfraproject/oopt-zuul-jobs/+/497368
Change-Id: I9b0371e9fc0a094f064c7e978bbcce984facfb30
2020-07-09 13:24:59 +02:00
Jan Kundrát
416da5c60b Update Pandas for better Python 3.8 compatibility
I kept wondering why installations on Python 3.8+ kept failing in the
CI, and I assumed that it was because the Pandas project somehow got
their homework wrong and did not provide a "wheel", which is a Python
thingy for prebuilt binaries. That's however not the case, and the
reason was simply that our Pandas version pin was too low, predating the
time when Python 3.8 was released.

Solve this by updating the Pandas requirement to the most recent one,
and now there's no need to mess with anything. Yay!

Thanks-to: https://github.com/mars-project/mars/pull/1332
Change-Id: Iceb31ae16aa911ebef67a938a3d2a524faad164f
2020-07-08 12:18:33 +02:00
Jan Kundrát
f8047f9afe docs: remind users that the pip command ends with a dot
Thanks to Tomáš Horváth from CESNET for volunteering for a test install.

Change-Id: Ieaa4baa92df19b8e39e2d33176a5a2af9685d40f
2020-07-08 11:56:55 +02:00
Jan Kundrát
9e91933106 Merge "network.py code cleaning and minor optimizations" 2020-07-06 23:42:26 +00:00
Jan Kundrát
7407e6809b docs: Fix a non-existing link
Thanks to Stefan for reporting this.

Reported-by: Melin, Stefan M. <Stefan.Melin@teliacompany.com>
Change-Id: I9ab3aa5f829ffe3ef722df2d46f1393f748a52dc
2020-07-02 17:29:28 +02:00
Jan Kundrát
56f66779f9 docs: fix a typo
Change-Id: I75448f00dcd1b77e30860dc3fee9524b100a8cb8
2020-07-02 17:27:10 +02:00
Jonas Mårtensson
2704c56e50 network.py code cleaning and minor optimizations
Change-Id: I9228c661df97552af3fbdbd6f58b2432ada7b6ef
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-06-26 17:29:17 +00:00
Jan Kundrát
ba4cc1ceef trove: let's stop calling us "alpha" when we consider ourselves as stable
Change-Id: I2c497e4804e3c521e23a0501876a61dbbef27fe6
2020-06-26 18:58:55 +02:00
Jan Kundrát
8396cea652 trove: add more target categories for this SW
Change-Id: Ic565196a6272190845d58dab3b6c06d128bea192
2020-06-26 18:58:25 +02:00
Jan Kundrát
2b1029f3b6 trove: indicate all Python versions that we're currently testing for
Change-Id: Ifeb6690c46e036f902137bd1e63e49a94f4ac3f2
2020-06-26 18:57:31 +02:00
Jan Kundrát
8e2709490f CI: Travis: test on Python 3.8 as well
Change-Id: I57841e200628410f9edcea481548eee25d550543
2020-06-26 18:19:06 +02:00
Jan Kundrát
ebf8249154 CI: travis: fix some warnings
sudo is a no-op these days, and let's put in that default linux OS
thingy to silence an info message, too.

Change-Id: I95d5b842932345025adbe20bd8775fb6dd83030e
2020-06-26 18:19:06 +02:00
Jan Kundrát
aaddffcb2e Install via PIP
There are several differences between `python setup.py develop` and `pip
install --editable .` ; one which became relevant a few days ago is the
fact that `python setup.py develop` is apparently happy to pull in
pre-releases of our dependencies -- perhaps due to the fact that this
package, when installed from git, is also considered a pre-release. This
has led to CI failures in Travis, and for some reason just on Python
3.7, not on Python 3.6.

This is of course rather ugly, and there's no need to start pulling in
pre-releases of various pieces of software that we're using, anyway. Fix
this by asking our users to use PIP, and adjusting the CI accordingly.
Zuul CI uses tox which is documented to call PIP behind the scenes, so
there's no change in there.

Fixes: https://travis-ci.com/github/Telecominfraproject/oopt-gnpy/jobs/353680894
Change-Id: I254066b8dc345e23c061a69d55546d48bac6761d
2020-06-26 18:18:55 +02:00
Jan Kundrát
29d1f8c666 Merge "Remove unused "carrier probing"" 2020-06-25 19:01:09 +00:00
Jan Kundrát
0126645c4d Merge "docs: badges: link to PyPI" 2020-06-25 18:58:37 +00:00
Jonas Mårtensson
1d657d6819 Return spectral info from propagate function
There are two propagation functions in request.py, propagate and
propagate2. The propagate2 function saves and returns spectral infos
from before and after every element of the path. In most cases only the
final spectral info after propagation is needed (there is currently
nothing in the code using anything more). On the other hand, the
propagate function just returns the path which is unnecessary since that
is modified in place. This patch proposes to instead return the final
spectral info from the propagate function.

Since propagate2 is now redundant, it can be removed.

Change-Id: I3a0f7310a7d5f9c93dc5f257e051c7d45e20c6fe
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-06-25 19:41:03 +02:00
Jonas Mårtensson
0b965d931c Don't include add_drop_osnr when there is no ROADM in path
The parameter add_drop_osnr is specified in the Roadm section of
eqpt_config.json and represents noise added by amplifiers within the
add/drop block of ROADMs. Currently this noise is added to the signal
whether the propagation path includes any ROADMs or not, which does not
make sense to me. This patch proposes to only add the add_drop_osnr when
a path actually includes ROADMs.

See GitHub issue #274.

Change-Id: I58961772c049578eff8879dfb2e53265866d12c4
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-06-22 10:06:04 +02:00
Jonas Mårtensson
d3eaa4d7ba Include tx_osnr when printing first transceiver
(Migrated from GitHub PR #311)

Currently the output of transmission_main_example shows the first
transceiver having ONSR = +inf even when a tx_osnr has been specified,
which is confusing.

This patch proposes to update the SNR of the first transceiver with
tx_osnr.

Change-Id: Idab7c92c2f5a12cc92ce5c1c551e5710f30e6a02
Signed-off-by: Jonas Mårtensson <jonas.martensson@ri.se>
2020-06-18 21:39:44 +02:00
Jan Kundrát
1dbbc6273b Point the gerrit-review to master
Change-Id: I03d53a69ea529f3b8534a0752b5ee7cabb240c89
2020-06-18 20:30:34 +02:00
Jan Kundrát
efa8b83249 docs: badges: link to PyPI
Change-Id: I3479d0b60a1e411793b81d0c263a51c1db35905b
2020-06-18 16:11:36 +02:00
Jan Kundrát
30599bf63a Merge "coding style: remove dead code, fix up sphinx" 2020-06-18 13:19:55 +00:00
Jan Kundrát
7c14fe02ab docs: badges: Use the stable DOI for the GNPy project
Zenodo offers two kinds of DOIs for GitHub projects:

- a per-release one, always assigned automatically upon tagging a new
release in GitHub,
- a stable one which [always resolves to the latest version at time of
access](https://help.zenodo.org/#versioning).

I think there's value in people citing us as "GNPy", not "GNPy 2.2", for
example, so let's make sure we include this magic-always-newest-but-stable
DOI for this software.

Change-Id: Ia57a94882c74d605e21f55c87b0a74b1d734b5ed
2020-06-18 13:09:44 +02:00
Jan Kundrát
cd0415e523 coding style: remove dead code, fix up sphinx
Change-Id: I80479959d551915ddb12d14ef94ed77042b000ef
2020-06-12 09:08:49 +02:00
Jan Kundrát
33dcdde422 Remove unused "carrier probing"
This is effectively a revert of commit 771af49 which added a
commented-out feature for printing out carrier info of the first hop.

On one hand, I'm reluctant to remove this, because apparently this was
not added by an accident, the PR #193 explicitly speaks about a
suggestion from Dave Boertjes for this feature -- and the git history
with merges looks like this one was actually pulled in as a single
commit. On the other hand, it is apparently not used anywhere, and all
of the required information is already available in some other manner --
for example, one can easily follow the path and add these prints to the
propagation, or just walk the path manually.

Digging further, I removed some of similar print() statements in
acafc78, and then restored some commented-out print()s via ec9eb8d (also
see the discussion in #299), which were then removed by Esther in
8107dde. So my TL;DR version is that this is dead code, and that
apparently the *real* use case is having total insight into the spectrum
info along the path (e.g., #246). That should, IMHO, be handled by
proper processing of the resulting data in a nice UI.

Change-Id: I366d33f98e230f4cb60a6d4b791707f7604f8d65
2020-06-12 06:54:07 +00:00
174 changed files with 82942 additions and 62578 deletions

View File

@@ -1 +1,9 @@
comment: off
coverage:
status:
project:
default:
threshold: 5%
patch:
default:
only_pulls: true

View File

@@ -1,3 +1,3 @@
#!/bin/bash
cp -nr /opt/application/oopt-gnpy/gnpy/example-data /shared
cp -nr /oopt-gnpy/gnpy/example-data /shared
exec "$@"

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

View File

@@ -1 +0,0 @@
venv/

7
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,7 @@
# Thanks for contributing to GNPy
If it isn't much trouble, please send your contribution as patches to our Gerrit.
Here's [how to submit patches](https://review.gerrithub.io/Documentation/intro-gerrit-walkthrough-github.html), and here's a [list of stuff we are currently working on](https://review.gerrithub.io/q/project:Telecominfraproject/oopt-gnpy+status:open).
Just sign in via your existing GitHub account.
However, if you feel more comfortable with filing GitHub PRs, we can work with that too.

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

@@ -0,0 +1,147 @@
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@v0.4
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 -r tests/requirements.txt
pip install --editable .
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-12
python_version: "3.11"
- os: macos-13
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 -r tests/requirements.txt
pip install --editable .
pytest -vv
strategy:
fail-fast: false
matrix:
include:
- os: macos-13-xlarge # Apple M1 CPU
python_version: "3.12"

2
.gitignore vendored
View File

@@ -65,5 +65,3 @@ target/
# MacOS DS_store
.DS_Store
venv/

View File

@@ -2,4 +2,3 @@
host=review.gerrithub.io
project=Telecominfraproject/oopt-gnpy
defaultrebase=0
defaultbranch=develop

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,4 +1,5 @@
build:
image: latest
python:
version: 3.6
version: 3.8
requirements_file: docs/requirements.txt

View File

@@ -1,24 +0,0 @@
dist: xenial
sudo: false
language: python
services: docker
python:
- "3.6"
- "3.7"
install: skip
script:
- python setup.py develop
- pip install pytest-cov rstcheck
- pytest --cov-report=xml --cov=gnpy -v
- 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,23 +2,33 @@
- project:
check:
jobs:
- tox-py36-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:
voting: false
dependencies:
- tox-py36-cover-previous
- tox-py36-cover
- tox-py310-cover-previous
- tox-py310-cover
vars:
coverage_job_name_previous: tox-py36-cover-previous
coverage_job_name_current: tox-py36-cover
- tox-linters-diff:
coverage_job_name_previous: tox-py310-cover-previous
coverage_job_name_current: tox-py310-cover
- tox-linters-diff-n-report:
voting: false
- tox-docs-el8
- tox-py36-cover-previous
gate:
jobs:
- tox-py36-el8
- tox-docs-el8
vars:
ensure_tox_version: '<4'
- tox-py310-cover-previous:
vars:
ensure_tox_version: '<4'
tag:
jobs:
- oopt-release-python:

View File

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

View File

@@ -1,18 +1,8 @@
FROM python:3.7-slim
WORKDIR /opt/application/oopt-gnpy
RUN mkdir -p /shared/example-data \
&& groupadd gnpy \
&& useradd -u 1000 -g gnpy -m gnpy \
&& apt-get update \
&& apt-get install git -y \
&& rm -rf /var/lib/apt/lists/*
COPY . /opt/application/oopt-gnpy
WORKDIR /opt/application/oopt-gnpy
RUN mkdir topology \
&& mkdir equipment \
&& mkdir autodesign \
&& pip install . \
&& chown -Rc gnpy:gnpy /opt/application/oopt-gnpy /shared/example-data
USER gnpy
ENTRYPOINT ["/opt/application/oopt-gnpy/.docker-entry.sh"]
FROM python:3.9-slim
COPY . /oopt-gnpy
WORKDIR /oopt-gnpy
RUN apt update; apt install -y git
RUN pip install .
WORKDIR /shared/example-data
ENTRYPOINT ["/oopt-gnpy/.docker-entry.sh"]
CMD ["/bin/bash"]

31
README.md Normal file
View File

@@ -0,0 +1,31 @@
# GNPy: Optical Route Planning and DWDM Network Optimization
[![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/)
[![Documentation status](https://readthedocs.org/projects/gnpy/badge/?version=master)](http://gnpy.readthedocs.io/en/master/?badge=master)
[![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)
[![Contributors](https://img.shields.io/github/contributors-anon/Telecominfraproject/oopt-gnpy)](https://github.com/Telecominfraproject/oopt-gnpy/graphs/contributors)
[![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)
[![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.
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.
Together, we are building this tool for rapid development of production-grade route planning tools which is easily extensible to include custom network elements and performant to the scale of real-world mesh optical networks.
![GNPy with an OLS system](docs/images/GNPy-banner.png)
## Quick Start
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).
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)
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/), 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

@@ -1,271 +0,0 @@
.. image:: docs/images/GNPy-banner.png
:width: 100%
:align: left
:alt: GNPy with an OLS system
====================================================================
`gnpy`: mesh optical network route planning and optimization library
====================================================================
|docs| |travis| |doi| |contributors| |codacy-quality| |codecov|
**`gnpy` is an open-source, community-developed library for building route
planning and optimization tools in real-world mesh optical networks.**
`gnpy <http://github.com/telecominfraproject/oopt-gnpy>`__ is:
--------------------------------------------------------------
- a sponsored project of the `OOPT/PSE <https://telecominfraproject.com/open-optical-packet-transport/>`_ working group of the `Telecom Infra Project <http://telecominfraproject.com>`_
- fully community-driven, fully open source library
- driven by a consortium of operators, vendors, and academic researchers
- intended for rapid development of production-grade route planning tools
- easily extensible to include custom network elements
- performant to the scale of real-world mesh optical networks
Documentation: https://gnpy.readthedocs.io
Get In Touch
~~~~~~~~~~~~
There are `weekly calls <https://telecominfraproject.workplace.com/events/702894886867547/>`__ about our progress.
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/>`__.
How to Install
--------------
Install either via `Docker <docs/install.rst#install-docker>`__, or as a `Python package <docs/install.rst#install-pip>`__.
Instructions for First Use
--------------------------
``gnpy`` is a library for building route planning and optimization tools.
It ships with a number of example programs. Release versions will ship with
fully-functional programs.
**Note**: *If you are a network operator or involved in route planning and
optimization for your organization, please contact project maintainer Jan
Kundrát <jan.kundrat@telecominfraproject.com>. gnpy is looking for users with
specific, delineated use cases to drive requirements for future
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:
.. image:: https://telecominfraproject.github.io/oopt-gnpy/docs/images/transmission_main_example.svg
:width: 100%
:align: left
:alt: Running a simple simulation example
:target: https://asciinema.org/a/252295
By default, this script operates on a single span network defined in
`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
example, to use the CORONET Global network defined in
`gnpy/example-data/CORONET_Global_Topology.json <gnpy/example-data/CORONET_Global_Topology.json>`_:
.. code-block:: shell-session
$ gnpy-transmission-example $(gnpy-example-data)/CORONET_Global_Topology.json
It is also possible to use an Excel file input (for example
`gnpy/example-data/CORONET_Global_Topology.xlsx <gnpy/example-data/CORONET_Global_Topology.xlsx>`_).
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>`__.
The main transmission example will calculate the average signal OSNR and SNR
across network elements (transceiver, ROADMs, fibers, and amplifiers)
between two transceivers selected by the user. Additional details are provided by doing ``gnpy-transmission-example -h``. (By default, for the CORONET Global
network, it will show the transmission of spectral information between Abilene and Albany)
This script calculates the average signal OSNR = |OSNR| and SNR = |SNR|.
.. |OSNR| replace:: P\ :sub:`ch`\ /P\ :sub:`ase`
.. |SNR| replace:: P\ :sub:`ch`\ /(P\ :sub:`nli`\ +\ P\ :sub:`ase`)
|Pase| is the amplified spontaneous emission noise, and |Pnli| the non-linear
interference noise.
.. |Pase| replace:: P\ :sub:`ase`
.. |Pnli| replace:: P\ :sub:`nli`
Further Instructions for Use
----------------------------
Simulations are driven by a set of `JSON <docs/json.rst>`__ or `XLS <docs/excel.rst>`__ files.
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.
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.
An experimental support for Raman amplification is available:
.. code-block:: shell-session
$ gnpy-transmission-example \
$(gnpy-example-data)/raman_edfa_example_network.json \
--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>`_.
General numeric parameters for simulaiton 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:
.. code-block:: shell-session
$ cd $(gnpy-example-data)
$ gnpy-path-request -o output_file.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).
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`.
Important note: ``gnpy-path-request`` is not a network dimensionning tool: each service does not reserve spectrum, or occupy ressources such as transponders. It only computes path feasibility assuming the spectrum (between defined frequencies) is loaded with "nb of channels" spaced by "spacing" values as specified in the system parameters input in the service file, each cannel having the same characteristics in terms of baudrate, format,... as the service transponder. The transceiver element acts as a "logical starting/stopping point" for the spectral information propagation. At that point it is not meant to represent the capacity of add drop ports.
As a result transponder type is not part of the network info. it is related to the list of services requests.
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.
REST API (experimental)
-----------------------
``gnpy`` provides an experimental api for requesting several paths at once. It is based on Flask server.
You can run it through command line or Docker.
.. code-block:: shell-session
$ gnpy-rest
.. code-block:: shell-session
$ docker run -p 8080:8080 -it emmanuelledelfour/gnpy-experimental:candi-1.0 gnpy-rest
When starting the api server will aks for an encryption/decryption key. This key i used to encrypt equipment file when using /api/v1/equipments endpoint.
This key is a Fernet key and can be generated this way:
.. code-block:: python
from cryptography.fernet import Fernet
Fernet.generate_key()
After typing the key, you can detach the container by typing ^P^Q.
After starting the api server, you can launch a request
.. code-block:: shell-session
$ curl -v -X POST -H "Content-Type: application/json" -d @<PATH_TO_JSON_REQUEST_FILE> https://localhost:8080/api/v1/path-computation -k
TODO: api documentation, unit tests, real WSGI server with trusted certificates
Contributing
------------
``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
<jan.kundrat@telecominfraproject.com> or Gert Grammel <ggrammel@juniper.net>.
``gnpy`` contributions are currently limited to members of `TIP
<http://telecominfraproject.com>`_. Membership is free and open to all.
See the `Onboarding Guide
<https://github.com/Telecominfraproject/gnpy/wiki/Onboarding-Guide>`_ for
specific details on code contributions.
See `AUTHORS.rst <AUTHORS.rst>`_ for past and present contributors.
Project Background
------------------
Data Centers are built upon interchangeable, highly standardized node and
network architectures rather than a sum of isolated solutions. This also
translates to optical networking. It leads to a push in enabling multi-vendor
optical network by disaggregating HW and SW functions and focusing on
interoperability. In this paradigm, the burden of responsibility for ensuring
the performance of such disaggregated open optical systems falls on the
operators. Consequently, operators and vendors are collaborating in defining
control models that can be readily used by off-the-shelf controllers. However,
node and network models are only part of the answer. To take reasonable
decisions, controllers need to incorporate logic to simulate and assess optical
performance. Hence, a vendor-independent optical quality estimator is required.
Given its vendor-agnostic nature, such an estimator needs to be driven by a
consortium of operators, system and component suppliers.
Founded in February 2016, the Telecom Infra Project (TIP) is an
engineering-focused initiative which is operator driven, but features
collaboration across operators, suppliers, developers, integrators, and
startups with the goal of disaggregating the traditional network deployment
approach. The groups ultimate goal is to help provide better connectivity for
communities all over the world as more people come on-line and demand more
bandwidth- intensive experiences like video, virtual reality and augmented
reality.
Within TIP, the Open Optical Packet Transport (OOPT) project group is chartered
with unbundling monolithic packet-optical network technologies in order to
unlock innovation and support new, more flexible connectivity paradigms.
The key to unbundling is the ability to accurately plan and predict the
performance of optical line systems based on an accurate simulation of optical
parameters. Under that OOPT umbrella, the Physical Simulation Environment (PSE)
working group set out to disrupt the planning landscape by providing an open
source simulation model which can be used freely across multiple vendor
implementations.
.. |docs| image:: https://readthedocs.org/projects/gnpy/badge/?version=master
:target: http://gnpy.readthedocs.io/en/master/?badge=master
:alt: Documentation Status
:scale: 100%
.. |travis| image:: https://travis-ci.com/Telecominfraproject/oopt-gnpy.svg?branch=master
:target: https://travis-ci.com/Telecominfraproject/oopt-gnpy
:alt: Build Status via Travis CI
:scale: 100%
.. |doi| image:: https://zenodo.org/badge/96894149.svg
:target: https://zenodo.org/badge/latestdoi/96894149
:alt: DOI
:scale: 100%
.. |contributors| image:: https://img.shields.io/github/contributors-anon/Telecominfraproject/oopt-gnpy
:target: https://github.com/Telecominfraproject/oopt-gnpy/graphs/contributors
:alt: Code Contributors via GitHub
:scale: 100%
.. |codacy-quality| image:: https://img.shields.io/lgtm/grade/python/github/Telecominfraproject/oopt-gnpy
:target: https://lgtm.com/projects/g/Telecominfraproject/oopt-gnpy/
:alt: Code Quality via LGTM.com
:scale: 100%
.. |codecov| image:: https://img.shields.io/codecov/c/github/Telecominfraproject/oopt-gnpy
:target: https://codecov.io/gh/Telecominfraproject/oopt-gnpy
:alt: Code Coverage via codecov
:scale: 100%
TIP OOPT/PSE & PSE WG Charter
-----------------------------
We believe that openly sharing ideas, specifications, and other intellectual
property is the key to maximizing innovation and reducing complexity
TIP OOPT/PSE's goal is to build an end-to-end simulation environment which
defines the network models of the optical device transfer functions and their
parameters. This environment will provide validation of the optical
performance requirements for the TIP OLS building blocks.
- The model may be approximate or complete depending on the network complexity.
Each model shall be validated against the proposed network scenario.
- The environment must be able to process network models from multiple vendors,
and also allow users to pick any implementation in an open source framework.
- The PSE will influence and benefit from the innovation of the DTC, API, and
OLS working groups.
- The PSE represents a step along the journey towards multi-layer optimization.
License
-------
``gnpy`` is distributed under a standard BSD 3-Clause License.
See `LICENSE <LICENSE>`__ for more details.

1
bindep.txt Normal file
View File

@@ -0,0 +1 @@
graphviz

59
docs/about-project.md Normal file
View File

@@ -0,0 +1,59 @@
(about-gnpy)=
# About the project
GNPy is a sponsored project of the [OOPT/PSE](https://telecominfraproject.com/open-optical-packet-transport/) working group of the [Telecom Infra Project](http://telecominfraproject.com).
There are weekly calls about our progress.
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).
(contributing)=
## Contributing
`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:jkt@jankundrat.com) or [Gert Grammel](mailto:ggrammel@juniper.net).
`gnpy` contributions are currently limited to members of [TIP](http://telecominfraproject.com).
Membership is free and open to all.
See the [Onboarding Guide](https://github.com/Telecominfraproject/gnpy/wiki/Onboarding-Guide) for specific details on code contributions, or just [upload patches to our Gerrit](https://review.gerrithub.io/Documentation/intro-gerrit-walkthrough-github.html).
Here is [what we are currently working on](https://review.gerrithub.io/q/project:Telecominfraproject/oopt-gnpy+status:open).
## Project Background
Data Centers are built upon interchangeable, highly standardized node and network architectures rather than a sum of isolated solutions.
This also translates to optical networking.
It leads to a push in enabling multi-vendor optical network by disaggregating HW and SW functions and focusing on interoperability.
In this paradigm, the burden of responsibility for ensuring the performance of such disaggregated open optical systems falls on the operators.
Consequently, operators and vendors are collaborating in defining control models that can be readily used by off-the-shelf controllers.
However, node and network models are only part of the answer.
To take reasonable decisions, controllers need to incorporate logic to simulate and assess optical performance.
Hence, a vendor-independent optical quality estimator is required.
Given its vendor-agnostic nature, such an estimator needs to be driven by a consortium of operators, system and component suppliers.
Founded in February 2016, the Telecom Infra Project (TIP) is an engineering-focused initiative which is operator driven, but features collaboration across operators, suppliers, developers, integrators, and startups with the goal of disaggregating the traditional network deployment approach.
The groups ultimate goal is to help provide better connectivity for communities all over the world as more people come on-line and demand more bandwidth-intensive experiences like video, virtual reality and augmented reality.
Within TIP, the Open Optical Packet Transport (OOPT) project group is chartered with unbundling monolithic packet-optical network technologies in order to unlock innovation and support new, more flexible connectivity paradigms.
The key to unbundling is the ability to accurately plan and predict the performance of optical line systems based on an accurate simulation of optical parameters.
Under that OOPT umbrella, the Physical Simulation Environment (PSE) working group set out to disrupt the planning landscape by providing an open source simulation model which can be used freely across multiple vendor implementations.
## TIP OOPT/PSE & PSE WG Charter
We believe that openly sharing ideas, specifications, and other intellectual property is the key to maximizing innovation and reducing complexity
TIP OOPT/PSE's goal is to build an end-to-end simulation environment which defines the network models of the optical device transfer functions and their parameters.
This environment will provide validation of the optical performance requirements for the TIP OLS building blocks.
- The model may be approximate or complete depending on the network complexity.
Each model shall be validated against the proposed network scenario.
- The environment must be able to process network models from multiple vendors, and also allow users to pick any implementation in an open source framework.
- The PSE will influence and benefit from the innovation of the DTC, API, and OLS working groups.
- The PSE represents a step along the journey towards multi-layer optimization.
License
-------
GNPy is distributed under a standard BSD 3-Clause License.

View File

@@ -1848,3 +1848,15 @@ month={Sept},}
title = {Telecom Infra Project},
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}
}

270
docs/concepts.rst Normal file
View File

@@ -0,0 +1,270 @@
.. _concepts:
Simulating networks with GNPy
=============================
Running simulations with GNPy requires three pieces of information:
- the :ref:`network topology<concepts-topology>`, which describes how the network looks like, what are the fiber lengths, what amplifiers are used, etc.,
- the :ref:`equipment library<concepts-equipment>`, which holds machine-readable datasheets of the equipment used in the network,
- the :ref:`simulation options<concepts-simulation>` holding instructions about what to simulate, and under which conditions.
.. _concepts-topology:
Network Topology
----------------
The *topology* acts as a "digital self" of the simulated network.
When given a network topology, GNPy can either run a specific simulation as-is, or it can *optimize* the topology before performing the simulation.
A network topology for GNPy is often a generic, mesh network.
This enables GNPy to take into consideration the current spectrum allocation as well as availability and resiliency considerations.
When the time comes to run a particular *propagation* of a signal and its impairments are computed, though, a linear path through the network is used.
For this purpose, the *path* through the network refers to an ordered, acyclic sequence of *nodes* that are processed.
This path is directional, and all "GNPy elements" along the path match the unidirectional part of a real-world network equipment.
.. note::
In practical terms, an amplifier in GNPy refers to an entity with a single input port and a single output port.
A real-world inline EDFA enclosed in a single chassis will be therefore represented as two GNPy-level amplifiers.
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**.
The topology is specified via :ref:`XLS files<excel>` or via :ref:`JSON<legacy-json>`.
.. _complete-vs-incomplete:
Fully Specified vs. Partially Designed Networks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Let's consider a simple triangle topology with three :abbr:`PoPs (Points of Presence)` covering three cities:
.. graphviz::
:layout: neato
:align: center
graph "High-level topology with three PoPs" {
A -- B
B -- C
C -- A
}
In the real world, each city would probably host a ROADM and some transponders:
.. graphviz::
:layout: neato
:align: center
graph "Simplified topology with transponders" {
"ROADM A" [pos="2,2!"]
"ROADM B" [pos="4,2!"]
"ROADM C" [pos="3,1!"]
"Transponder A" [shape=box, pos="0,2!"]
"Transponder B" [shape=box, pos="6,2!"]
"Transponder C" [shape=box, pos="3,0!"]
"ROADM A" -- "ROADM B"
"ROADM B" -- "ROADM C"
"ROADM C" -- "ROADM A"
"Transponder A" -- "ROADM A"
"Transponder B" -- "ROADM B"
"Transponder C" -- "ROADM C"
}
GNPy simulation works by propagating the optical signal over a sequence of elements, which means that one has to add some preamplifiers and boosters.
The amplifiers are, by definition, unidirectional, so the graph becomes quite complex:
.. _topo-roadm-preamp-booster:
.. graphviz::
:layout: neato
:align: center
digraph "Preamps and boosters are explicitly modeled in GNPy" {
"ROADM A" [pos="2,4!"]
"ROADM B" [pos="6,4!"]
"ROADM C" [pos="4,0!"]
"Transponder A" [shape=box, pos="1,5!"]
"Transponder B" [shape=box, pos="7,5!"]
"Transponder C" [shape=box, pos="4,-1!"]
"Transponder A" -> "ROADM A"
"Transponder B" -> "ROADM B"
"Transponder C" -> "ROADM C"
"ROADM A" -> "Transponder A"
"ROADM B" -> "Transponder B"
"ROADM C" -> "Transponder C"
"Booster A C" [shape=triangle, orientation=-150, fixedsize=true, width=0.5, height=0.5, pos="2.2,3.2!", color=red, label=""]
"Preamp A C" [shape=triangle, orientation=0, fixedsize=true, width=0.5, height=0.5, pos="1.5,3.0!", color=red, label=""]
"ROADM A" -> "Booster A C"
"Preamp A C" -> "ROADM A"
"Booster A B" [shape=triangle, orientation=-90, fixedsize=true, width=0.5, height=0.5, pos="3,4.3!", color=red, fontcolor=red, labelloc=b, label="\N\n\n"]
"Preamp A B" [shape=triangle, orientation=90, fixedsize=true, width=0.5, height=0.5, pos="3,3.6!", color=red, fontcolor=red, labelloc=t, label="\n \N"]
"ROADM A" -> "Booster A B"
"Preamp A B" -> "ROADM A"
"Booster C B" [shape=triangle, orientation=-30, fixedsize=true, width=0.5, height=0.5, pos="4.7,0.9!", color=red, label=""]
"Preamp C B" [shape=triangle, orientation=120, fixedsize=true, width=0.5, height=0.5, pos="5.4,0.7!", color=red, label=""]
"ROADM C" -> "Booster C B"
"Preamp C B" -> "ROADM C"
"Booster C A" [shape=triangle, orientation=30, fixedsize=true, width=0.5, height=0.5, pos="2.6,0.7!", color=red, label=""]
"Preamp C A" [shape=triangle, orientation=-30, fixedsize=true, width=0.5, height=0.5, pos="3.3,0.9!", color=red, label=""]
"ROADM C" -> "Booster C A"
"Preamp C A" -> "ROADM C"
"Booster B A" [shape=triangle, orientation=90, fixedsize=true, width=0.5, height=0.5, pos="5,3.6!", labelloc=t, color=red, fontcolor=red, label="\n\N "]
"Preamp B A" [shape=triangle, orientation=-90, fixedsize=true, width=0.5, height=0.5, pos="5,4.3!", labelloc=b, color=red, fontcolor=red, label="\N\n\n"]
"ROADM B" -> "Booster B A"
"Preamp B A" -> "ROADM B"
"Booster B C" [shape=triangle, orientation=-180, fixedsize=true, width=0.5, height=0.5, pos="6.5,3.0!", color=red, label=""]
"Preamp B C" [shape=triangle, orientation=-20, fixedsize=true, width=0.5, height=0.5, pos="5.8,3.2!", color=red, label=""]
"ROADM B" -> "Booster B C"
"Preamp B C" -> "ROADM B"
"Booster A C" -> "Preamp C A"
"Booster A B" -> "Preamp B A"
"Booster C A" -> "Preamp A C"
"Booster C B" -> "Preamp B C"
"Booster B C" -> "Preamp C B"
"Booster B A" -> "Preamp A B"
}
In many regions, the ROADMs are not placed physically close to each other, so the long-haul fiber links (:abbr:`OMS (Optical Multiplex Section)`) are split into individual spans (:abbr:`OTS (Optical Transport Section)`) by in-line amplifiers, resulting in an even more complicated topology graphs:
.. graphviz::
:layout: neato
:align: center
digraph "A subset of a real topology with inline amplifiers" {
"ROADM A" [pos="2,4!"]
"ROADM B" [pos="6,4!"]
"ROADM C" [pos="4,-3!"]
"Transponder A" [shape=box, pos="1,5!"]
"Transponder B" [shape=box, pos="7,5!"]
"Transponder C" [shape=box, pos="4,-4!"]
"Transponder A" -> "ROADM A"
"Transponder B" -> "ROADM B"
"Transponder C" -> "ROADM C"
"ROADM A" -> "Transponder A"
"ROADM B" -> "Transponder B"
"ROADM C" -> "Transponder C"
"Booster A C" [shape=triangle, orientation=-166, fixedsize=true, width=0.5, height=0.5, pos="2.2,3.2!", label=""]
"Preamp A C" [shape=triangle, orientation=0, fixedsize=true, width=0.5, height=0.5, pos="1.5,3.0!", label=""]
"ROADM A" -> "Booster A C"
"Preamp A C" -> "ROADM A"
"Booster A B" [shape=triangle, orientation=-90, fixedsize=true, width=0.5, height=0.5, pos="3,4.3!", label=""]
"Preamp A B" [shape=triangle, orientation=90, fixedsize=true, width=0.5, height=0.5, pos="3,3.6!", label=""]
"ROADM A" -> "Booster A B"
"Preamp A B" -> "ROADM A"
"Booster C B" [shape=triangle, orientation=-30, fixedsize=true, width=0.5, height=0.5, pos="4.7,-2.1!", label=""]
"Preamp C B" [shape=triangle, orientation=10, fixedsize=true, width=0.5, height=0.5, pos="5.4,-2.3!", label=""]
"ROADM C" -> "Booster C B"
"Preamp C B" -> "ROADM C"
"Booster C A" [shape=triangle, orientation=20, fixedsize=true, width=0.5, height=0.5, pos="2.6,-2.3!", label=""]
"Preamp C A" [shape=triangle, orientation=-30, fixedsize=true, width=0.5, height=0.5, pos="3.3,-2.1!", label=""]
"ROADM C" -> "Booster C A"
"Preamp C A" -> "ROADM C"
"Booster B A" [shape=triangle, orientation=90, fixedsize=true, width=0.5, height=0.5, pos="5,3.6!", label=""]
"Preamp B A" [shape=triangle, orientation=-90, fixedsize=true, width=0.5, height=0.5, pos="5,4.3!", label=""]
"ROADM B" -> "Booster B A"
"Preamp B A" -> "ROADM B"
"Booster B C" [shape=triangle, orientation=-180, fixedsize=true, width=0.5, height=0.5, pos="6.5,3.0!", label=""]
"Preamp B C" [shape=triangle, orientation=-20, fixedsize=true, width=0.5, height=0.5, pos="5.8,3.2!", label=""]
"ROADM B" -> "Booster B C"
"Preamp B C" -> "ROADM B"
"Inline A C 1" [shape=triangle, orientation=-166, fixedsize=true, width=0.5, pos="2.4,2.2!", label=" \N", color=red, fontcolor=red]
"Inline A C 2" [shape=triangle, orientation=-166, fixedsize=true, width=0.5, pos="2.6,1.2!", label=" \N", color=red, fontcolor=red]
"Inline A C 3" [shape=triangle, orientation=-166, fixedsize=true, width=0.5, pos="2.8,0.2!", label=" \N", color=red, fontcolor=red]
"Inline A C n" [shape=triangle, orientation=-166, fixedsize=true, width=0.5, pos="3.0,-1.1!", label=" \N", color=red, fontcolor=red]
"Booster A C" -> "Inline A C 1"
"Inline A C 1" -> "Inline A C 2"
"Inline A C 2" -> "Inline A C 3"
"Inline A C 3" -> "Inline A C n" [style=dotted]
"Inline A C n" -> "Preamp C A"
"Booster A B" -> "Preamp B A" [style=dotted]
"Booster C A" -> "Preamp A C" [style=dotted]
"Booster C B" -> "Preamp B C" [style=dotted]
"Booster B C" -> "Preamp C B" [style=dotted]
"Booster B A" -> "Preamp A B" [style=dotted]
}
In such networks, GNPy's autodesign features becomes very useful.
It is possible to connect ROADMs via "tentative links" which will be replaced by a sequence of actual fibers and specific amplifiers.
In other cases where the location of amplifier huts is already known, but the specific EDFA models have not yet been decided, one can put in amplifier placeholders and let GNPy assign the best amplifier.
.. _concepts-equipment:
The Equipment Library
---------------------
In order to produce an accurate simulation, GNPy needs to know the physical properties of each entity which affects the optical signal.
Entries in the equipment library correspond to actual real-world, tangible entities.
Unlike a typical :abbr:`NMS (Network Management System)`, GNPy considers not just the active :abbr:`NEs (Network Elements)` such as amplifiers and :abbr:`ROADMs (Reconfigurable Optical Add/Drop Multiplexers)`, but also the passive ones, such as the optical fiber.
As the signal propagates through the network, the largest source of optical impairments is the noise introduced from amplifiers.
An accurate description of the :abbr:`EDFA (Erbium-Doped Fiber Amplifier)` and especially its noise characteristics is required.
GNPy describes this property in terms of the **Noise Figure (NF)** of an amplifier model as a function of its operating point.
The amplifiers compensate power losses induced on the signal in the optical fiber.
The linear losses, however, are just one phenomenon of a multitude of effects that affect the signals in a long fiber run.
While a more detailed description is available :ref:`in the literature<physical-model>`, for the purpose of the equipment library, the description of the *optical fiber* comprises its **linear attenutation coefficient**, a set of parameters for the **Raman effect**, optical **dispersion**, etc.
Signals are introduced into the network via *transponders*.
The set of parameters that are required describe the physical properties of each supported *mode* of the transponder, including its **symbol rate**, spectral **width**, etc.
In the junctions of the network, *ROADMs* are used for spectrum routing.
GNPy currently does not take into consideration the spectrum filtering penalties of the :abbr:`WSSes (Wavelength Selective Switches)`, but the equipment library nonetheless contains a list of required parameters, such as the attenuation options, so that the network can be properly simulated.
.. _concepts-nf-model:
Amplifier Noise Figure Models
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
One of the key parameters of an amplifier is the method to use for computing the Noise Figure (NF).
GNPy supports several different noise models with varying level of accuracy.
When in doubt, contact your vendor's technical support and ask them to :ref:`contribute their equipment descriptions<extending-edfa>` to GNPy.
The most accurate noise models describe the resulting NF of an EDFA as a third-degree polynomial.
GNPy understands polynomials as a NF-yielding function of the :ref:`gain difference from the optimal gain<ext-nf-model-polynomial-NF>`, or as a function of the input power resulting in an incremental OSNR as used in :ref:`OpenROADM inline amplifiers<ext-nf-model-polynomial-OSNR-OpenROADM>` and :ref:`OpenROADM booster/preamps in the ROADMs<ext-nf-model-noise-mask-OpenROADM>`.
For scenarios where the vendor has not yet contributed an accurate EDFA NF description to GNPy, it is possible to approximate the characteristics via an operator-focused, min-max NF model.
.. _nf-model-min-max-NF:
Min-max NF
**********
This is an operator-focused model where performance is defined by the *minimal* and *maximal NF*.
These are especially suited to model a dual-coil EDFA with a VOA in between.
In these amplifiers, the minimal NF is achieved when the EDFA operates at its maximal (and usually optimal, in terms of flatness) gain.
The worst (maximal) NF applies when the EDFA operates at its minimal gain.
This model is suitable for use when the vendor has not provided a more accurate performance description of the EDFA.
Raman Approximation
*******************
While GNPy is fully Raman-aware, under certain scenarios it is useful to be able to run a simulation without an accurate Raman description.
For these purposes the :ref:`polynomial NF<ext-nf-model-polynomial-NF>` model with :math:`\text{a} = \text{b} = \text{c} = 0`, and :math:`\text{d} = NF` can be used.
.. _concepts-simulation:
Simulation
----------
When the network model has been instantiated and the physical properties and operational settings of the actual physical devices are known, GNPy can start simulating how the signal propagate through the optical fiber.
This set of input parameters include options such as the *spectrum allocation*, i.e., the number of channels and their spacing.
Various strategies for network optimization can be provided as well.

View File

@@ -31,10 +31,17 @@ sys.path.insert(0, os.path.abspath('../'))
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.mathjax',
'sphinx.ext.githubpages',
'sphinxcontrib.bibtex',
'pbr.sphinxext',]
'sphinx.ext.mathjax',
'sphinx.ext.githubpages',
'sphinxcontrib.bibtex',
'sphinx.ext.graphviz',
'myst_parser',
]
myst_enable_extensions = [
"deflist",
"dollarmath",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -50,8 +57,8 @@ master_doc = 'index'
# General information about the project.
project = 'gnpy'
copyright = '2018, Telecom InfraProject - OOPT PSE Group'
author = 'Telecom InfraProject - OOPT PSE Group'
copyright = '2018 - 2021, Telecom Infra Project - OOPT PSE Group'
author = 'Telecom Infra Project - OOPT PSE Group'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -150,7 +157,7 @@ latex_elements = {
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'gnpy.tex', 'gnpy Documentation',
'Telecom InfraProject - OOPT PSE Group', 'manual'),
'Telecom Infra Project - OOPT PSE Group', 'manual'),
]
@@ -181,3 +188,7 @@ autodoc_default_options = {
'private-members': True,
'show-inheritance': True,
}
graphviz_output_format = 'svg'
bibtex_bibfiles = ['biblio.bib']

View File

@@ -1,3 +1,5 @@
.. _excel:
Excel (XLS, XLSX) input files
=============================
@@ -121,15 +123,17 @@ and a fiber span from node3 to node6::
Eqpt sheet
----------
Eqt sheet is optional. It lists the amplifiers types and characteristics on each degree of the *Node A* line.
Eqpt sheet must contain twelve columns::
The equipment sheet (named "Eqpt") is optional.
If provided, it specifies types of boosters and preamplifiers for all ROADM degrees of all ROADM nodes, and for all ILA nodes.
This sheet contains twelve columns::
<-- east cable from a to z --> <-- west from z to a -->
Node A ; Node Z ; amp type ; att_in ; amp gain ; tilt ; att_out ; delta_p ; amp type ; att_in ; amp gain ; tilt ; att_out ; delta_p
If the sheet is present, it MUST have as many lines as egress directions of ROADMs defined in Links Sheet.
If the sheet is present, it MUST have as many lines as there are egress directions of ROADMs defined in Links Sheet, and all ILAs.
For example, consider the following list of links (A,B and C being a ROADM and amp# ILAs)
For example, consider the following list of links (A, B and C being a ROADM and amp# ILAs):
::
@@ -141,8 +145,8 @@ For example, consider the following list of links (A,B and C being a ROADM and a
then Eqpt sheet should contain:
- one line for each ILAs: amp1, amp2, amp3
- one line for each degree 1 ROADMs B and C
- two lines for ROADM A which is a degree 2 ROADM
- one line for each one-degree ROADM (B and C in this example)
- two lines for each two-degree ROADM (just the ROADM A)
::
@@ -183,7 +187,8 @@ This generates a text file meshTopologyExampleV2_eqt_sheet.txt whose content ca
- *att_in* and *att_out* are not mandatory and are not used yet. They are the value of the attenuator at input and output of amplifier (in dB).
If filled they must contain positive numbers.
- *tilt* --TODO--
- **tilt**, in dB, is not mandatory. It is the target gain tilt over the full amplfifier bandwidth and is defined with regard to wavelength, i.e. negative tilt means lower gain
for higher wavelengths (lower frequencies). If not filled, the default value is 0.
- **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.
@@ -196,7 +201,7 @@ This generates a text file meshTopologyExampleV2_eqt_sheet.txt whose content ca
Service sheet
-------------
Service sheet is optional. It lists the services for which path and feasibility must be computed with ``gnpy-path_request``.
Service sheet is optional. It lists the services for which path and feasibility must be computed with ``gnpy-path-request``.
Service sheet must contain 11 columns::

176
docs/extending.rst Normal file
View File

@@ -0,0 +1,176 @@
.. _extending:
Extending GNPy with vendor-specific data
========================================
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>`__ -- 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>`.
.. _extending-edfa:
EDFAs
-----
An accurate description of the :abbr:`EDFA (Erbium-Doped Fiber Amplifier)` and especially its noise characteristics is required.
GNPy describes this property in terms of the **Noise Figure (NF)** of an amplifier model as a function of its operating point.
GNPy supports several different :ref:`noise models<concepts-nf-model>`, and vendors are encouraged to pick one which describes performance of their equipment most accurately.
.. _ext-nf-model-polynomial-NF:
Polynomial NF
*************
This model computes the NF as a function of the difference between the optimal gain and the current gain.
The NF is expressed as a third-degree polynomial:
.. math::
f(x) &= \text{a}x^3 + \text{b}x^2 + \text{c}x + \text{d}
\text{NF} &= f(G - G_\text{max})
This model can be also used for fixed-gain fixed-NF amplifiers.
In that case, use:
.. math::
a = b = c &= 0
d &= \text{NF}
.. _ext-nf-model-polynomial-OSNR-OpenROADM:
Polynomial OSNR (OpenROADM-style for inline amplifier)
******************************************************
This model is useful for amplifiers compliant to the OpenROADM specification for ILA (an in-line amplifier).
The amplifier performance is evaluated via its incremental OSNR, which is a function of the input power.
.. math::
\text{OSNR}_\text{inc}(P_\text{in}) = \text{a}P_\text{in}^3 + \text{b}P_\text{in}^2 + \text{c}P_\text{in} + \text{d}
.. _ext-nf-model-noise-mask-OpenROADM:
Noise mask (OpenROADM-style for combined preamp and booster)
************************************************************
Unlike GNPy which simluates the preamplifier and the booster separately as two amplifiers for best accuracy, the OpenROADM specification mandates a certain performance level for a combination of these two amplifiers.
For the express path, the effective noise mask comprises the preamplifier and the booster.
When terminating a channel, the same effective noise mask is mandated for a combination of the preamplifier and the drop stage.
GNPy emulates this specification via two special NF models:
- The ``openroadm_preamp`` NF model for preamplifiers.
This NF model provides all of the linear impairments to the signal, including those which are incured by the booster in a real network.
- The ``openroadm_booster`` NF model is a special "zero noise" faux amplifier in place of the booster.
.. _ext-nf-model-min-max-NF:
Min-max NF
**********
When the vendor prefers not to share the amplifier description in full detail, GNPy also supports describing the NF characteristics via the *minimal* and *maximal NF*.
This approximates a more accurate polynomial description reasonably well for some models of a dual-coil EDFA with a VOA in between.
In these amplifiers, the minimal NF is achieved when the EDFA operates at its maximal (and usually optimal, in terms of flatness) gain.
The worst (maximal) NF applies when the EDFA operates at the minimal gain.
.. _ext-nf-model-dual-stage-amplifier:
Dual-stage
**********
Dual-stage amplifier combines two distinct amplifiers.
Vendors which provide an accurate description of their preamp and booster stages separately can use the dual-stage model for an aggregate description of the whole amplifier.
.. _ext-nf-model-advanced:
Advanced Specification
**********************
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.
.. _extending-raman:
Raman Amplifiers
----------------
An accurate simulation of Raman amplification requires knowledge of:
* the *power* and *wavelength* of all Raman pumping lasers,
* the *direction*, whether it is co-propagating or counter-propagating,
* the Raman efficiency of the fiber,
* the fiber temperature.
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`.
This is also useful to quickly approximate a hybrid EDFA+Raman amplifier.
.. _extending-transponder:
Transponders
------------
Since transponders are usually capable of operating in a variety of modes, these are described separately.
A *mode* usually refers to a particular performance point that is defined by a combination of the symbol rate, modulation format, and :abbr:`FEC (Forward Error Correction)`.
The following data are required for each mode:
``bit-rate``
Data bit rate, in :math:`\text{Gbits}\times s^{-1}`.
``baud-rate``
Symbol modulation rate, in :math:`\text{Gbaud}`.
``required-osnr``
Minimal allowed OSNR for the receiver.
``tx-osnr``
Initial OSNR at the transmitter's output.
``grid-spacing``
Minimal grid spacing, i.e., an effective channel spectral bandwidth.
In :math:`\text{Hz}`.
``tx-roll-off``
Roll-off parameter (:math:`\beta`) of the TX pulse shaping filter.
This assumes a raised-cosine filter.
``rx-power-min`` and ``rx-power-max``
The allowed range of power at the receiver.
In :math:`\text{dBm}`.
``cd-max``
Maximal allowed Chromatic Dispersion (CD).
In :math:`\text{ps}/\text{nm}`.
``pmd-max``
Maximal allowed Polarization Mode Dispersion (PMD).
In :math:`\text{ps}`.
``cd-penalty``
*Work-in-progress.*
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.
.. _extending-roadm:
ROADMs
------
In a :abbr:`ROADM (Reconfigurable Add/Drop Multiplexer)`, GNPy simulates the impairments of the preamplifiers and boosters of line degrees :ref:`separately<topo-roadm-preamp-booster>`.
The set of parameters for each ROADM model therefore includes:
``add-drop-osnr``
OSNR penalty introduced by the Add and Drop stages of this ROADM type.
``target-channel-out-power``
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.
This can be overridden on a per-link in the network topology.
``pmd``
Polarization mode dispersion (PMD) penalty of the express path.
In :math:`\text{ps}`.
Provisions are in place to define the list of all allowed booster and preamplifier types.
This is useful for specifying constraints on what amplifier modules fit into ROADM chassis, and when using fully disaggregated ROADM topologies as well.

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

View File

@@ -8,9 +8,13 @@ in real-world mesh optical networks. It is based on the Gaussian Noise Model.
.. toctree::
:maxdepth: 4
intro
concepts
install
json
excel
extending
about-project
model
gnpy-api

View File

@@ -38,7 +38,7 @@ Using Python on your computer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**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
managing dependencies.
@@ -84,7 +84,7 @@ exact version of Python you are using.
$ which python # check which Python executable is used
/path/to/anaconda/bin/python
$ python -V # check your Python version
Python 3.6.5 :: Anaconda, Inc.
Python 3.8.0 :: Anaconda, Inc.
.. _install-pip:
@@ -98,7 +98,7 @@ of the `gnpy` repo and install it with:
$ git clone https://github.com/Telecominfraproject/oopt-gnpy # clone the repo
$ cd oopt-gnpy
$ python setup.py develop
$ pip install --editable . # note the trailing dot
To test that `gnpy` was successfully installed, you can run this command. If it
executes without a ``ModuleNotFoundError``, you have successfully installed

94
docs/intro.rst Normal file
View File

@@ -0,0 +1,94 @@
.. _intro:
Introduction
============
``gnpy`` is a library for building route planning and optimization tools.
It ships with a number of example programs. Release versions will ship with
fully-functional programs.
**Note**: *If you are a network operator or involved in route planning and
optimization for your organization, please contact project maintainer Jan
Kundrát <jkt@jankundrat.com>. gnpy is looking for users with
specific, delineated use cases to drive requirements for future
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:
.. image:: https://telecominfraproject.github.io/oopt-gnpy/docs/images/transmission_main_example.svg
:width: 100%
:align: left
:alt: Running a simple simulation example
By default, this script operates on a single span network defined in
`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
example, to use the CORONET Global network defined in
`gnpy/example-data/CORONET_Global_Topology.json <gnpy/example-data/CORONET_Global_Topology.json>`_:
.. code-block:: shell-session
$ gnpy-transmission-example $(gnpy-example-data)/CORONET_Global_Topology.json
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>`_).
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>`__.
The main transmission example will calculate the average signal OSNR and SNR
across network elements (transceiver, ROADMs, fibers, and amplifiers)
between two transceivers selected by the user. Additional details are provided by doing ``gnpy-transmission-example -h``. (By default, for the CORONET Global
network, it will show the transmission of spectral information between Abilene and Albany)
This script calculates the average signal OSNR = |OSNR| and SNR = |SNR|.
.. |OSNR| replace:: P\ :sub:`ch`\ /P\ :sub:`ase`
.. |SNR| replace:: P\ :sub:`ch`\ /(P\ :sub:`nli`\ +\ P\ :sub:`ase`)
|Pase| is the amplified spontaneous emission noise, and |Pnli| the non-linear
interference noise.
.. |Pase| replace:: P\ :sub:`ase`
.. |Pnli| replace:: P\ :sub:`nli`
Further Instructions for Use
----------------------------
Simulations are driven by a set of `JSON <docs/json.rst>`__ or `XLS <docs/excel.rst>`__ files.
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.
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.
An experimental support for Raman amplification is available:
.. code-block:: shell-session
$ gnpy-transmission-example \
$(gnpy-example-data)/raman_edfa_example_network.json \
--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>`_.
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:
.. code-block:: shell-session
$ cd $(gnpy-example-data)
$ gnpy-path-request -o output_file.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).
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`.
Important note: ``gnpy-path-request`` is not a network dimensionning tool: each service does not reserve spectrum, or occupy ressources such as transponders. It only computes path feasibility assuming the spectrum (between defined frequencies) is loaded with "nb of channels" spaced by "spacing" values as specified in the system parameters input in the service file, each cannel having the same characteristics in terms of baudrate, format,... as the service transponder. The transceiver element acts as a "logical starting/stopping point" for the spectral information propagation. At that point it is not meant to represent the capacity of add drop ports.
As a result transponder type is not part of the network info. it is related to the list of services requests.
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.

View File

@@ -1,3 +1,5 @@
.. _legacy-json:
JSON Input Files
================
@@ -7,13 +9,11 @@ Some data (such as network topology or the service requests) can be also passed
Equipment Library
-----------------
Design and transmission parameters are defined in a dedicated json file. By
default, this information is read from `gnpy/example-data/eqpt_config.json
<gnpy/example-data/eqpt_config.json>`_. This file defines the equipment libraries that
can be customized (EDFAs, fibers, and transceivers).
Design and transmission parameters are defined in a dedicated json file.
By default, this information is read from `gnpy/example-data/eqpt_config.json <https://github.com/Telecominfraproject/oopt-gnpy/blob/master/gnpy/example-data/eqpt_config.json>`_.
This file defines the equipment libraries that can be customized (EDFAs, fibers, and transceivers).
It also defines the simulation parameters (spans, ROADMs, and the spectral
information to transmit.)
It also defines the simulation parameters (spans, ROADMs, and the spectral information to transmit.)
EDFA
~~~~
@@ -21,9 +21,20 @@ EDFA
The EDFA equipment library is a list of supported amplifiers. New amplifiers
can be added and existing ones removed. Three different noise models are available:
1. ``'type_def': 'variable_gain'`` is a simplified model simulating a 2-coil EDFA with internal, input and output VOAs. The NF vs gain response is calculated accordingly based on the input parameters: ``nf_min``, ``nf_max``, and ``gain_flatmax``. It is not a simple interpolation but a 2-stage NF calculation.
2. ``'type_def': 'fixed_gain'`` is a fixed gain model. `NF == Cte == nf0` if `gain_min < gain < gain_flatmax`
3. ``'type_def': None`` is an advanced model. A detailed JSON configuration file is required (by default `gnpy/example-data/std_medium_gain_advanced_config.json <gnpy/example-data/std_medium_gain_advanced_config.json>`_). It uses a 3rd order polynomial where NF = f(gain), NF_ripple = f(frequency), gain_ripple = f(frequency), N-array dgt = f(frequency). Compared to the previous models, NF ripple and gain ripple are modelled.
1. ``'type_def': 'variable_gain'`` is a simplified model simulating a 2-coil EDFA with internal, input and output VOAs.
The NF vs gain response is calculated accordingly based on the input parameters: ``nf_min``, ``nf_max``, and ``gain_flatmax``.
It is not a simple interpolation but a 2-stage NF calculation.
2. ``'type_def': 'fixed_gain'`` is a fixed gain model.
`NF == Cte == nf0` if `gain_min < gain < gain_flatmax`
3. ``'type_def': 'openroadm'`` models the incremental OSNR contribution as a function of input power.
It is suitable for inline amplifiers that conform to the OpenROADM specification.
The input parameters are coefficients of the :ref:`third-degree polynomial<ext-nf-model-polynomial-OSNR-OpenROADM>`.
4. ``'type_def': 'openroadm_preamp'`` and ``openroadm_booster`` approximate the :ref:`preamp and booster within an OpenROADM network<ext-nf-model-noise-mask-OpenROADM>`.
No extra parameters specific to the NF model are accepted.
5. ``'type_def': 'advanced_model'`` is an advanced model.
A detailed JSON configuration file is required (by default `gnpy/example-data/std_medium_gain_advanced_config.json <https://github.com/Telecominfraproject/oopt-gnpy/blob/master/gnpy/example-data/std_medium_gain_advanced_config.json>`_).
It uses a 3rd order polynomial where NF = f(gain), NF_ripple = f(frequency), gain_ripple = f(frequency), N-array dgt = f(frequency).
Compared to the previous models, NF ripple and gain ripple are modelled.
For all amplifier models:
@@ -33,7 +44,7 @@ For all amplifier models:
| ``type_variety`` | (string) | a unique name to ID the amplifier in the|
| | | JSON/Excel template topology input file |
+------------------------+-----------+-----------------------------------------+
| ``out_voa_auto`` | (boolean) | auto_design feature to optimize the |
| ``out_voa_auto`` | (boolean) | auto-design feature to optimize the |
| | | amplifier output VOA. If true, output |
| | | VOA is present and will be used to push |
| | | amplifier gain to its maximum, within |
@@ -50,22 +61,98 @@ Fiber
The fiber library currently describes SSMF and NZDF but additional fiber types can be entered by the user following the same model:
+----------------------+-----------+-----------------------------------------+
| field | type | description |
+======================+===========+=========================================+
| ``type_variety`` | (string) | a unique name to ID the fiber in the |
| | | JSON or Excel template topology input |
| | | file |
+----------------------+-----------+-----------------------------------------+
| ``dispersion`` | (number) | (s.m-1.m-1) |
+----------------------+-----------+-----------------------------------------+
| ``dispersion_slope`` | (number) | (s.m-1.m-1.m-1) |
+----------------------+-----------+-----------------------------------------+
| ``gamma`` | (number) | 2pi.n2/(lambda*Aeff) (w-1.m-1) |
+----------------------+-----------+-----------------------------------------+
| ``pmd_coef`` | (number) | Polarization mode dispersion (PMD) |
| | | coefficient. (s.sqrt(m)-1) |
+----------------------+-----------+-----------------------------------------+
+------------------------------+-----------------+------------------------------------------------+
| field | type | description |
+==============================+=================+================================================+
| ``type_variety`` | (string) | a unique name to ID the fiber in the |
| | | JSON or Excel template topology input |
| | | file |
+------------------------------+-----------------+------------------------------------------------+
| ``dispersion`` | (number) | In :math:`s \times m^{-1} \times m^{-1}`. |
+------------------------------+-----------------+------------------------------------------------+
| ``dispersion_slope`` | (number) | In :math:`s \times m^{-1} \times m^{-1} |
| | | \times m^{-1}` |
+------------------------------+-----------------+------------------------------------------------+
| ``dispersion_per_frequency`` | (dict) | Dictionary of dispersion values evaluated at |
| | | various frequencies, as follows: |
| | | ``{"value": [], "frequency": []}``. |
| | | ``value`` in |
| | | :math:`s \times m^{-1} \times m^{-1}` and |
| | | ``frequency`` in Hz. |
+------------------------------+-----------------+------------------------------------------------+
| ``effective_area`` | (number) | Effective area of the fiber (not just |
| | | the MFD circle). This is the |
| | | :math:`A_{eff}`, see e.g., the |
| | | `Corning whitepaper on MFD/EA`_. |
| | | Specified in :math:`m^{2}`. |
+------------------------------+-----------------+------------------------------------------------+
| ``gamma`` | (number) | Coefficient :math:`\gamma = 2\pi\times |
| | | n^2/(\lambda*A_{eff})`. |
| | | If not provided, this will be derived |
| | | from the ``effective_area`` |
| | | :math:`A_{eff}`. |
| | | In :math:`w^{-1} \times m^{-1}`. |
| | | This quantity is evaluated at the |
| | | reference frequency and it is scaled |
| | | along frequency accordingly to the |
| | | effective area scaling. |
+------------------------------+-----------------+------------------------------------------------+
| ``pmd_coef`` | (number) | Polarization mode dispersion (PMD) |
| | | coefficient. In |
| | | :math:`s\times\sqrt{m}^{-1}`. |
+------------------------------+-----------------+------------------------------------------------+
| ``lumped_losses`` | (array) | Places along the fiber length with extra |
| | | losses. Specified as a loss in dB at |
| | | each relevant position (in km): |
| | | ``{"position": 10, "loss": 1.5}``) |
+------------------------------+-----------------+------------------------------------------------+
| ``raman_coefficient`` | (dict) | The fundamental parameter that describes |
| | | the regulation of the power transfer |
| | | between channels during fiber propagation |
| | | is the Raman gain coefficient (see |
| | | :cite:`DAmicoJLT2022` for further |
| | | details); :math:`f_{ref}` represents the |
| | | pump reference frequency used for the |
| | | Raman gain coefficient profile |
| | | measurement ("reference_frequency"), |
| | | :math:`\Delta f` is the frequency shift |
| | | between the pump and the specific Stokes |
| | | wave, the Raman gain coefficient |
| | | in terms of optical power |
| | | :math:`g_0`, expressed in |
| | | :math:`1/(m\;W)`. |
| | | Default values measured for a SSMF are |
| | | considered when not specified. |
+------------------------------+-----------------+------------------------------------------------+
.. _Corning whitepaper on MFD/EA: https://www.corning.com/microsites/coc/oem/documents/specialty-fiber/WP7071-Mode-Field-Diam-and-Eff-Area.pdf
RamanFiber
~~~~~~~~~~
The RamanFiber can be used to simulate Raman amplification through dedicated Raman pumps. The Raman pumps must be listed
in the key ``raman_pumps`` within the RamanFiber ``operational`` dictionary. The description of each Raman pump must
contain the following:
+---------------------------+-----------+------------------------------------------------------------+
| field | type | description |
+===========================+===========+============================================================+
| ``power`` | (number) | Total pump power in :math:`W` |
| | | considering a depolarized pump |
+---------------------------+-----------+------------------------------------------------------------+
| ``frequency`` | (number) | Pump central frequency in :math:`Hz` |
+---------------------------+-----------+------------------------------------------------------------+
| ``propagation_direction`` | (number) | The pumps can propagate in the same or opposite direction |
| | | with respect the signal. Valid choices are ``coprop`` and |
| | | ``counterprop``, respectively |
+---------------------------+-----------+------------------------------------------------------------+
Beside the list of Raman pumps, the RamanFiber ``operational`` dictionary must include the ``temperature`` that affects
the amplified spontaneous emission noise generated by the Raman amplification.
As the loss coefficient significantly varies outside the C-band, where the Raman pumps are usually placed,
it is suggested to include an estimation of the loss coefficient for the Raman pump central frequencies within
a dictionary-like definition of the ``RamanFiber.params.loss_coef``
(e.g. ``loss_coef = {"value": [0.18, 0.18, 0.20, 0.20], "frequency": [191e12, 196e12, 200e12, 210e12]}``).
Transceiver
~~~~~~~~~~~
@@ -73,7 +160,7 @@ Transceiver
The transceiver equipment library is a list of supported transceivers. New
transceivers can be added and existing ones removed at will by the user. It is
used to determine the service list path feasibility when running the
`path_request_run.py routine <gnpy/example-data/path_request_run.py>`_.
``gnpy-path-request`` script.
+----------------------+-----------+-----------------------------------------+
| field | type | description |
@@ -82,7 +169,7 @@ used to determine the service list path feasibility when running the
| | | the JSON or Excel template topology |
| | | input file |
+----------------------+-----------+-----------------------------------------+
| ``frequency`` | (number) | Min/max as below. |
| ``frequency`` | (number) | Min/max central channel frequency. |
+----------------------+-----------+-----------------------------------------+
| ``mode`` | (number) | A list of modes supported by the |
| | | transponder. New modes can be added at |
@@ -113,48 +200,142 @@ The modes are defined as follows:
| ``cost`` | (number) | Arbitrary unit |
+----------------------+-----------+-----------------------------------------+
Simulation parameters
~~~~~~~~~~~~~~~~~~~~~
ROADM
~~~~~
Auto-design automatically creates EDFA amplifier network elements when they are
missing, after a fiber, or between a ROADM and a fiber. This auto-design
functionality can be manually and locally deactivated by introducing a ``Fused``
network element after a ``Fiber`` or a ``Roadm`` that doesn't need amplification.
The amplifier is chosen in the EDFA list of the equipment library based on
gain, power, and NF criteria. Only the EDFA that are marked
``'allowed_for_design': true`` are considered.
The user can only modify the value of existing parameters:
For amplifiers defined in the topology JSON input but whose ``gain = 0``
(placeholder), auto-design will set its gain automatically: see ``power_mode`` in
the ``Spans`` library to find out how the gain is calculated.
+-------------------------------+-----------+----------------------------------------------------+
| field | type | description |
+===============================+===========+====================================================+
| ``target_pch_out_db`` | (number) | Default :ref:`equalization strategy<equalization>` |
| or | | for this ROADM type. |
| ``target_psd_out_mWperGHz`` | | |
| or | | Auto-design sets the ROADM egress channel |
| ``target_out_mWperSlotWidth`` | | power. This reflects typical control loop |
| (mutually exclusive) | | algorithms that adjust ROADM losses to |
| | | equalize channels (e.g., coming from |
| | | different ingress direction or add ports). |
| | | |
| | | These values are used as defaults when no |
| | | overrides are set per each ``Roadm`` |
| | | element in the network topology. |
+-------------------------------+-----------+----------------------------------------------------+
| ``add_drop_osnr`` | (number) | OSNR contribution from the add/drop ports |
+-------------------------------+-----------+----------------------------------------------------+
| ``pmd`` | (number) | Polarization mode dispersion (PMD). (s) |
+-------------------------------+-----------+----------------------------------------------------+
| ``restrictions`` | (dict of | If non-empty, keys ``preamp_variety_list`` |
| | strings) | and ``booster_variety_list`` represent |
| | | list of ``type_variety`` amplifiers which |
| | | are allowed for auto-design within ROADM's |
| | | line degrees. |
| | | |
| | | If no booster should be placed on a degree, |
| | | insert a ``Fused`` node on the degree |
| | | output. |
+-------------------------------+-----------+----------------------------------------------------+
Global parameters
-----------------
The following options are still defined in ``eqpt_config.json`` for legacy reasons, but
they do not correspond to tangible network devices.
Auto-design automatically creates EDFA amplifier network elements when they are missing, after a fiber, or between a ROADM and a fiber.
This auto-design functionality can be manually and locally deactivated by introducing a ``Fused`` network element after a ``Fiber`` or a ``Roadm`` that doesn't need amplification.
The amplifier is chosen in the EDFA list of the equipment library based on gain, power, and NF criteria.
Only the EDFA that are marked ``'allowed_for_design': true`` are considered.
For amplifiers defined in the topology JSON input but whose ``gain = 0`` (placeholder), auto-design will set its gain automatically: see ``power_mode`` in the ``Spans`` library to find out how the gain is calculated.
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.
If amplifiers don't have settings, auto-design also sets amplifiers gain, output VOA and target powers according to [J. -L. Auge, V. Curri and E. Le Rouzic, Open Design for Multi-Vendor Optical Networks, OFC 2019](https://ieeexplore.ieee.org/document/8696699), equation 4.
See ``delta_power_range_db`` for more explaination.
+---------------------------------------------+-----------+---------------------------------------------+
| field | type | description |
+=============================================+===========+=============================================+
| ``raman_params.flag`` | (boolean) | Enable/Disable the Raman effect that |
| | | produces a power transfer from higher to |
| | | lower frequencies. |
| | | In general, considering the Raman effect |
| | | provides more accurate results. It is |
| | | mandatory when Raman amplification is |
| | | included in the simulation |
+---------------------------------------------+-----------+---------------------------------------------+
| ``raman_params.result_spatial_resolution`` | (number) | Spatial resolution of the output |
| | | Raman profile along the entire fiber span. |
| | | This affects the accuracy and the |
| | | computational time of the NLI |
| | | calculation when the GGN method is used: |
| | | smaller the spatial resolution higher both |
| | | the accuracy and the computational time. |
| | | In C-band simulations, with input power per |
| | | channel around 0 dBm, a suggested value of |
| | | spatial resolution is 10e3 m |
+---------------------------------------------+-----------+---------------------------------------------+
| ``raman_params.solver_spatial_resolution`` | (number) | Spatial step for the iterative solution |
| | | of the first order differential equation |
| | | used to calculate the Raman profile |
| | | along the entire fiber span. |
| | | This affects the accuracy and the |
| | | computational time of the evaluated |
| | | Raman profile: |
| | | smaller the spatial resolution higher both |
| | | the accuracy and the computational time. |
| | | In C-band simulations, with input power per |
| | | channel around 0 dBm, a suggested value of |
| | | spatial resolution is 100 m |
+---------------------------------------------+-----------+---------------------------------------------+
| ``nli_params.method`` | (string) | Model used for the NLI evaluation. Valid |
| | | choices are ``gn_model_analytic`` (see |
| | | eq. 120 from `arXiv:1209.0394 |
| | | <https://arxiv.org/abs/1209.0394>`_) and |
| | | ``ggn_spectrally_separated`` (see eq. 21 |
| | | from `arXiv:1710.02225 |
| | | <https://arxiv.org/abs/1710.02225>`_). |
+---------------------------------------------+-----------+---------------------------------------------+
| ``nli_params.computed_channels`` | (number) | The channels on which the NLI is |
| | | explicitly evaluated. |
| | | The NLI of the other channels is |
| | | interpolated using ``numpy.interp``. |
| | | In a C-band simulation with 96 channels in |
| | | a 50 GHz spacing fix-grid we recommend at |
| | | one computed channel every 20 channels. |
+---------------------------------------------+-----------+---------------------------------------------+
Span
~~~~
Span configuration is not a list (which may change
in later releases) and the user can only modify the value of existing
parameters:
Span configuration is not a list (which may change in later releases) and the user can only modify the value of existing parameters:
+-------------------------------------+-----------+---------------------------------------------+
| field | type | description |
+=====================================+===========+=============================================+
| ``power_mode`` | (boolean) | If false, gain mode. Auto-design sets |
| | | amplifier gain = preceding span loss, |
| | | unless the amplifier exists and its |
| | | gain > 0 in the topology input JSON. |
| | | If true, power mode (recommended for |
| | | auto-design and power sweep.) |
| | | Auto-design sets amplifier power |
| | | according to delta_power_range. If the |
| | | amplifier exists with gain > 0 in the |
| | | topology JSON input, then its gain is |
| | | translated into a power target/channel. |
| | | Moreover, when performing a power sweep |
| | | (see ``power_range_db`` in the SI |
| | | configuration library) the power sweep |
| | | is performed w/r/t this power target, |
| | | regardless of preceding amplifiers |
| | | power saturation/limitations. |
| ``power_mode`` | (boolean) | If false, **gain mode**. In the gain mode, |
| | | only gain settings are used for |
| | | propagation, and ``delta_p`` is ignored. |
| | | If no ``gain_target`` is set in an |
| | | amplifier, auto-design computes one |
| | | according to the ``delta_power_range`` |
| | | optimisation range. |
| | | The gain mode |
| | | is recommended if all the amplifiers |
| | | have already consistent gain settings in |
| | | the topology input file. |
| | | |
| | | If true, **power mode**. In the power mode, |
| | | only the ``delta_p`` is used for |
| | | propagation, and ``gain_target`` is |
| | | ignored. |
| | | The power mode is recommended for |
| | | auto-design and power sweep. |
| | | If no ``delta_p`` is set, |
| | | auto-design sets an amplifier power target |
| | | according to delta_power_range_db. |
+-------------------------------------+-----------+---------------------------------------------+
| ``delta_power_range_db`` | (number) | Auto-design only, power-mode |
| | | only. Specifies the [min, max, step] |
@@ -256,55 +437,21 @@ parameters:
}
}
ROADM
~~~~~
The user can only modify the value of existing parameters:
+--------------------------+-----------+---------------------------------------------+
| field | type | description |
+==========================+===========+=============================================+
| ``target_pch_out_db`` | (number) | Auto-design sets the ROADM egress channel |
| | | power. This reflects typical control loop |
| | | algorithms that adjust ROADM losses to |
| | | equalize channels (eg coming from different |
| | | ingress direction or add ports) |
| | | This is the default value |
| | | Roadm/params/target_pch_out_db if no value |
| | | is given in the ``Roadm`` element in the |
| | | topology input description. |
| | | This default value is ignored if a |
| | | params/target_pch_out_db value is input in |
| | | the topology for a given ROADM. |
+--------------------------+-----------+---------------------------------------------+
| ``add_drop_osnr`` | (number) | OSNR contribution from the add/drop ports |
+--------------------------+-----------+---------------------------------------------+
| ``pmd`` | (number) | Polarization mode dispersion (PMD). (s) |
+--------------------------+-----------+---------------------------------------------+
| ``restrictions`` | (dict of | If non-empty, keys ``preamp_variety_list`` |
| | strings) | and ``booster_variety_list`` represent |
| | | list of ``type_variety`` amplifiers which |
| | | are allowed for auto-design within ROADM's |
| | | line degrees. |
| | | |
| | | If no booster should be placed on a degree, |
| | | insert a ``Fused`` node on the degree |
| | | output. |
+--------------------------+-----------+---------------------------------------------+
SpectralInformation
~~~~~~~~~~~~~~~~~~~
The user can only modify the value of existing parameters. It defines a spectrum of N
identical carriers. While the code libraries allow for different carriers and
power levels, the current user parametrization only allows one carrier type and
one power/channel definition.
GNPy requires a description of all channels that are propagated through the network.
Flexgrid channel partitioning is available since the 2.7 release via the extra ``--spectrum`` option.
In the simplest case, homogeneous channel allocation can be defined via the ``SpectralInformation`` construct which defines a spectrum of N identical carriers:
+----------------------+-----------+-------------------------------------------+
| field | type | description |
+======================+===========+===========================================+
| ``f_min``, | (number) | In Hz. Carrier min max excursion. |
| ``f_max`` | | |
| ``f_min``, | (number) | In Hz. Define spectrum boundaries. Note |
| ``f_max`` | | that due to backward compatibility, the |
| | | first channel central frequency is placed |
| | | at :math:`f_{min} + spacing` and the last |
| | | one at :math:`f_{max}`. |
+----------------------+-----------+-------------------------------------------+
| ``baud_rate`` | (number) | In Hz. Simulated baud rate. |
+----------------------+-----------+-------------------------------------------+
@@ -316,11 +463,20 @@ one power/channel definition.
+----------------------+-----------+-------------------------------------------+
| ``tx_osnr`` | (number) | In dB. OSNR out from transponder. |
+----------------------+-----------+-------------------------------------------+
| ``power_dbm`` | (number) | Reference channel power. In gain mode |
| | | (see spans/power_mode = false), all gain |
| | | settings are offset w/r/t this reference |
| | | power. In power mode, it is the |
| | | reference power for |
| ``power_dbm`` | (number) | Reference channel power, in dBm. |
| | | In gain mode |
| | | (see spans/power_mode = false), if no |
| | | gain is set in an amplifier, auto-design |
| | | sets gain to meet this reference |
| | | power. If amplifiers gain is set, |
| | | ``power_dbm`` is |
| | | ignored. |
| | | |
| | | In power mode, the ``power_dbm`` |
| | | is the reference power for |
| | | the ``delta_p`` settings in amplifiers. |
| | | It is also the reference power for |
| | | auto-design power optimisation range |
| | | Spans/delta_power_range_db. For example, |
| | | if delta_power_range_db = `[0,0,0]`, the |
| | | same power=power_dbm is launched in every |
@@ -328,12 +484,166 @@ one power/channel definition.
| | | with the power_dbm value: even if a |
| | | power sweep is defined (see after) the |
| | | design is not repeated. |
| | | |
| | | If the ``--power`` CLI option is used, |
| | | its value replaces this parameter. |
+----------------------+-----------+-------------------------------------------+
| ``power_range_db`` | (number) | Power sweep excursion around power_dbm. |
| | | It is not the min and max channel power |
| | | values! The reference power becomes: |
| ``power_range_db`` | (number) | Power sweep excursion around |
| | | ``power_dbm``. |
| | | This defines a list of reference powers |
| | | to run the propagation, in the range |
| | | power_range_db + power_dbm. |
| | | Power sweep uses the ``delta_p`` targets |
| | | or, if they have not been set, the ones |
| | | computed by auto-design, regardless of |
| | | of preceding amplifiers' power |
| | | saturation. |
| | | |
| | | Power sweep is an easy way to find the |
| | | optimal reference power. |
| | | |
| | | Power sweep excursion is ignored in case |
| | | of gain mode. |
+----------------------+-----------+-------------------------------------------+
| ``sys_margins`` | (number) | In dB. Added margin on min required |
| | | transceiver OSNR. |
+----------------------+-----------+-------------------------------------------+
.. _mixed-rate:
Arbitrary channel definition
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Non-uniform channels are defined via a list of spectrum "partitions" which are defined in an extra JSON file via the ``--spectrum`` option.
In this approach, each partition is internally homogeneous, but different partitions might use different channel widths, power targets, modulation rates, etc.
+----------------------+-----------+-------------------------------------------+
| field | type | description |
+======================+===========+===========================================+
| ``f_min``, | (number) | In Hz. Mandatory. |
| ``f_max`` | | Define partition :math:`f_{min}` is |
| | | the first carrier central frequency |
| | | :math:`f_{max}` is the last one. |
| | | :math:`f_{min}` -:math:`f_{max}` |
| | | partitions must not overlap. |
| | | |
| | | Note that the meaning of ``f_min`` and |
| | | ``f_max`` is different than the one in |
| | | ``SpectralInformation``. |
+----------------------+-----------+-------------------------------------------+
| ``baud_rate`` | (number) | In Hz. Mandatory. Simulated baud rate. |
+----------------------+-----------+-------------------------------------------+
| ``slot_width`` | (number) | In Hz. Carrier spectrum occupation. |
| | | Carriers of this partition are spaced at |
| | | ``slot_width`` offsets. |
+----------------------+-----------+-------------------------------------------+
| ``roll_off`` | (number) | Pure number between 0 and 1. Mandatory |
| | | TX signal roll-off shape. Used by |
| | | Raman-aware simulation code. |
+----------------------+-----------+-------------------------------------------+
| ``tx_osnr`` | (number) | In dB. Optional. OSNR out from |
| | | transponder. Default value is 40 dB. |
+----------------------+-----------+-------------------------------------------+
| ``delta_pdb`` | (number) | In dB. Optional. Power offset compared to |
| | | the reference power used for design |
| | | (SI block in equipment library) to be |
| | | applied by ROADM to equalize the carriers |
| | | in this partition. Default value is 0 dB. |
+----------------------+-----------+-------------------------------------------+
For example this example:
.. code-block:: json
{
"SI":[
{
"f_min": 191.4e12,
"f_max":193.1e12,
"baud_rate": 32e9,
"slot_width": 50e9,
"roll_off": 0.15,
"tx_osnr": 40
},
{
"f_min": 193.1625e12,
"f_max":195e12,
"baud_rate": 64e9,
"delta_pdb": 3,
"slot_width": 75e9,
"roll_off": 0.15,
"tx_osnr": 40
}
]
}
...defines a spectrum split into two parts.
Carriers with central frequencies ranging from 191.4 THz to 193.1 THz will have 32 GBaud rate and will be spaced by 50 Ghz.
Carriers with central frequencies ranging from 193.1625 THz to 195 THz will have 64 GBaud rate and will be spaced by 75 GHz with 3 dB power offset.
If the SI reference carrier is set to ``power_dbm`` = 0dBm, and the ROADM has ``target_pch_out_db`` set to -20 dBm, then all channels ranging from 191.4 THz to 193.1 THz will have their power equalized to -20 + 0 dBm (due to the 0 dB power offset).
All channels ranging from 193.1625 THz to 195 THz will have their power equalized to -20 + 3 = -17 dBm (total power signal + noise).
Note that first carrier of the second partition has center frequency 193.1625 THz (its spectrum occupation ranges from 193.125 THz to 193.2 THz).
The last carrier of the second partition has center frequency 193.1 THz and spectrum occupation ranges from 193.075 THz to 193.125 THz.
There is no overlap of the occupation and both share the same boundary.
.. _equalization:
Equalization choices
~~~~~~~~~~~~~~~~~~~~
ROADMs typically equalize the optical power across multiple channels using one of the available equalization strategies — either targeting a specific output power, or a specific power spectral density (PSD), or a spectfic power spectral density using slot_width as spectrum width reference (PSW).
All of these strategies can be adjusted by a per-channel power offset.
The equalization strategy can be defined globally per a ROADM model, or per each ROADM instance in the topology, and within a ROADM also on a per-degree basis.
Let's consider some example for the equalization. Suppose that the types of signal to be propagated are the following:
.. code-block:: json
{
"baud_rate": 32e9,
"f_min":191.3e12,
"f_max":192.3e12,
"spacing": 50e9,
"label": 1
},
{
"baud_rate": 64e9,
"f_min":193.3e12,
"f_max":194.3e12,
"spacing": 75e9,
"label": 2
}
with the PSD equalization in a ROADM:
.. code-block:: json
{
"uid": "roadm A",
"type": "Roadm",
"params": {
"target_psd_out_mWperGHz": 3.125e-4,
}
},
This means that power out of the ROADM will be computed as 3.125e-4 * 32 = 0.01 mW ie -20 dBm for label 1 types of carriers
and 3.125e4 * 64 = 0.02 mW ie -16.99 dBm for label2 channels. So a ratio of ~ 3 dB between target powers for these carriers.
With the PSW equalization:
.. code-block:: json
{
"uid": "roadm A",
"type": "Roadm",
"params": {
"target_out_mWperSlotWidth": 2.0e-4,
}
},
the power out of the ROADM will be computed as 2.0e-4 * 50 = 0.01 mW ie -20 dBm for label 1 types of carriers
and 2.0e4 * 75 = 0.015 mW ie -18.24 dBm for label2 channels. So a ratio of ~ 1.76 dB between target powers for these carriers.

View File

@@ -1,3 +1,5 @@
.. _physical-model:
Physical Model used in GNPy
===========================
@@ -124,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
:cite:`Innoue-FWM`- to a continuity of tones, possibly spectrally shaped.
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
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.
Note that the GN-model has not been derived with the aim of an *exact*
performance estimation, but to pursue a conservative performance prediction.
@@ -143,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
PSE framework.
.. bibliography:: biblio.bib
.. bibliography::

7
docs/requirements.txt Normal file
View File

@@ -0,0 +1,7 @@
alabaster>=0.7.12,<1
docutils>=0.17.1,<1
myst-parser>=0.16.1,<1
Pygments>=2.11.2,<3
rstcheck
Sphinx>=4.4.0,<5
sphinxcontrib-bibtex>=2.4.1,<3

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.
Signal propagation is implemented in :py:mod:`.core`.
Path finding and spectrum assignment is in :py:mod:`.topology`.
Various tools and auxiliary code, including the JSON I/O handling, is in
:py:mod:`.tools`.
'''
"""

View File

@@ -1,9 +0,0 @@
# coding: utf-8
from flask import Flask
app = Flask(__name__)
import gnpy.api.route.path_request_route
import gnpy.api.route.status_route
import gnpy.api.route.topology_route
import gnpy.api.route.equipments_route

View File

@@ -1 +0,0 @@
# coding: utf-8

View File

@@ -1,14 +0,0 @@
# coding: utf-8
class ConfigError(Exception):
""" Exception raise for configuration file error
Attributes:
message -- explanation of the error
"""
def __init__(self, message):
self.message = message
def __str__(self):
return self.message

View File

@@ -1,14 +0,0 @@
# coding: utf-8
class EquipmentError(Exception):
""" Exception raise for equipment error
Attributes:
message -- explanation of the error
"""
def __init__(self, message):
self.message = message
def __str__(self):
return self.message

View File

@@ -1,33 +0,0 @@
# coding: utf-8
import json
import re
import werkzeug
from gnpy.api.model.error import Error
_reaesc = re.compile(r'\x1b[^m]*m')
def common_error_handler(exception):
"""
:type exception: Exception
"""
status_code = 500
if not isinstance(exception, werkzeug.exceptions.HTTPException):
exception = werkzeug.exceptions.InternalServerError()
exception.description = "Something went wrong on our side."
else:
status_code = exception.code
response = Error(message=exception.name, description=exception.description,
code=status_code)
return werkzeug.Response(response=json.dumps(response.__dict__), status=status_code, mimetype='application/json')
def bad_request_handler(exception):
response = Error(message='bad request', description=_reaesc.sub('', str(exception)),
code=400)
return werkzeug.Response(response=json.dumps(response.__dict__), status=400, mimetype='application/json')

View File

@@ -1,14 +0,0 @@
# coding: utf-8
class PathComputationError(Exception):
""" Exception raise for path computation error error
Attributes:
message -- explanation of the error
"""
def __init__(self, message):
self.message = message
def __str__(self):
return self.message

View File

@@ -1,14 +0,0 @@
# coding: utf-8
class TopologyError(Exception):
""" Exception raise for topology error
Attributes:
message -- explanation of the error
"""
def __init__(self, message):
self.message = message
def __str__(self):
return self.message

View File

@@ -1 +0,0 @@
# coding: utf-8

View File

@@ -1,17 +0,0 @@
# coding: utf-8
class Error:
def __init__(self, code: int = None, message: str = None, description: str = None):
"""Error
:param code: The code of this Error.
:type code: int
:param message: The message of this Error.
:type message: str
:param description: The description of this Error.
:type description: str
"""
self.code = code
self.message = message
self.description = description

View File

@@ -1,8 +0,0 @@
# coding: utf-8
class Result:
def __init__(self, message: str = None, description: str = None):
self.message = message
self.description = description

View File

@@ -1,83 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
gnpy.tools.rest_example
=======================
GNPy as a rest API example
'''
import logging
from logging.handlers import RotatingFileHandler
import werkzeug
from flask_injector import FlaskInjector
from injector import singleton
from werkzeug.exceptions import InternalServerError
import gnpy.core.exceptions as exceptions
from gnpy.api import app
from gnpy.api.exception.exception_handler import bad_request_handler, common_error_handler
from gnpy.api.exception.path_computation_error import PathComputationError
from gnpy.api.exception.topology_error import TopologyError
from gnpy.api.service import config_service
from gnpy.api.service.encryption_service import EncryptionService
from gnpy.api.service.equipment_service import EquipmentService
from gnpy.api.service.path_request_service import PathRequestService
_logger = logging.getLogger(__name__)
def _init_logger():
handler = RotatingFileHandler('api.log', maxBytes=1024 * 1024, backupCount=5, encoding='utf-8')
ch = logging.StreamHandler()
logging.basicConfig(level=logging.INFO, handlers=[handler, ch],
format="%(asctime)s %(levelname)s %(name)s(%(lineno)s) [%(threadName)s - %(thread)d] - %("
"message)s")
def _init_app(key):
app.register_error_handler(KeyError, bad_request_handler)
app.register_error_handler(TypeError, bad_request_handler)
app.register_error_handler(ValueError, bad_request_handler)
app.register_error_handler(exceptions.ConfigurationError, bad_request_handler)
app.register_error_handler(exceptions.DisjunctionError, bad_request_handler)
app.register_error_handler(exceptions.EquipmentConfigError, bad_request_handler)
app.register_error_handler(exceptions.NetworkTopologyError, bad_request_handler)
app.register_error_handler(exceptions.ServiceError, bad_request_handler)
app.register_error_handler(exceptions.SpectrumError, bad_request_handler)
app.register_error_handler(exceptions.ParametersError, bad_request_handler)
app.register_error_handler(AssertionError, bad_request_handler)
app.register_error_handler(InternalServerError, common_error_handler)
app.register_error_handler(TopologyError, bad_request_handler)
app.register_error_handler(PathComputationError, bad_request_handler)
for error_code in werkzeug.exceptions.default_exceptions:
app.register_error_handler(error_code, common_error_handler)
config = config_service.init_config()
config.add_section('SECRET')
config.set('SECRET', 'equipment', key)
app.config['properties'] = config
def _configure(binder):
binder.bind(EquipmentService,
to=EquipmentService(EncryptionService(app.config['properties'].get('SECRET', 'equipment'))),
scope=singleton)
binder.bind(PathRequestService,
to=PathRequestService(EncryptionService(app.config['properties'].get('SECRET', 'equipment'))),
scope=singleton)
app.config['properties'].pop('SECRET', None)
def main():
key = input('Enter encryption/decryption key: ')
_init_logger()
_init_app(key)
FlaskInjector(app=app, modules=[_configure])
app.run(host='0.0.0.0', port=8080, ssl_context='adhoc')
if __name__ == '__main__':
main()

View File

@@ -1,2 +0,0 @@
# coding: utf-8

View File

@@ -1,38 +0,0 @@
# coding: utf-8
import http
import json
from flask import request
from gnpy.api import app
from gnpy.api.exception.equipment_error import EquipmentError
from gnpy.api.model.result import Result
from gnpy.api.service.equipment_service import EquipmentService
EQUIPMENT_BASE_PATH = '/api/v1/equipments'
EQUIPMENT_ID_PATH = EQUIPMENT_BASE_PATH + '/<equipment_id>'
@app.route(EQUIPMENT_BASE_PATH, methods=['POST'])
def create_equipment(equipment_service: EquipmentService):
if not request.is_json:
raise EquipmentError('Request body is not json')
equipment_identifier = equipment_service.save_equipment(request.json)
response = Result(message='Equipment creation ok', description=equipment_identifier)
return json.dumps(response.__dict__), 201, {'location': EQUIPMENT_BASE_PATH + '/' + equipment_identifier}
@app.route(EQUIPMENT_ID_PATH, methods=['PUT'])
def update_equipment(equipment_id, equipment_service: EquipmentService):
if not request.is_json:
raise EquipmentError('Request body is not json')
equipment_identifier = equipment_service.update_equipment(request.json, equipment_id)
response = Result(message='Equipment update ok', description=equipment_identifier)
return json.dumps(response.__dict__), http.HTTPStatus.OK, {
'location': EQUIPMENT_BASE_PATH + '/' + equipment_identifier}
@app.route(EQUIPMENT_ID_PATH, methods=['DELETE'])
def delete_equipment(equipment_id, equipment_service: EquipmentService):
equipment_service.delete_equipment(equipment_id)
return '', http.HTTPStatus.NO_CONTENT

View File

@@ -1,63 +0,0 @@
# coding: utf-8
import http
import os
from pathlib import Path
from flask import request
from gnpy.api import app
from gnpy.api.exception.equipment_error import EquipmentError
from gnpy.api.exception.topology_error import TopologyError
from gnpy.api.service import topology_service
from gnpy.api.service.equipment_service import EquipmentService
from gnpy.api.service.path_request_service import PathRequestService
from gnpy.tools.json_io import _equipment_from_json, network_from_json
from gnpy.topology.request import ResultElement
PATH_COMPUTATION_BASE_PATH = '/api/v1/path-computation'
AUTODESIGN_PATH = PATH_COMPUTATION_BASE_PATH + '/<path_computation_id>/autodesign'
_examples_dir = Path(__file__).parent.parent.parent / 'example-data'
@app.route(PATH_COMPUTATION_BASE_PATH, methods=['POST'])
def compute_path(equipment_service: EquipmentService, path_request_service: PathRequestService):
data = request.json
service = data['gnpy-api:service']
if 'gnpy-api:topology' in data:
topology = data['gnpy-api:topology']
elif 'gnpy-api:topology_id' in data:
topology = topology_service.get_topology(data['gnpy-api:topology_id'])
else:
raise TopologyError('No topology found in request')
if 'gnpy-api:equipment' in data:
equipment = data['gnpy-api:equipment']
elif 'gnpy-api:equipment_id' in data:
equipment = equipment_service.get_equipment(data['gnpy-api:equipment_id'])
else:
raise EquipmentError('No equipment found in request')
equipment = _equipment_from_json(equipment,
os.path.join(_examples_dir, 'std_medium_gain_advanced_config.json'))
network = network_from_json(topology, equipment)
propagatedpths, reversed_propagatedpths, rqs, path_computation_id = path_request_service.path_requests_run(service,
network,
equipment)
# Generate the output
result = []
# assumes that list of rqs and list of propgatedpths have same order
for i, pth in enumerate(propagatedpths):
result.append(ResultElement(rqs[i], pth, reversed_propagatedpths[i]))
return {"result": {"response": [n.json for n in result]}}, 201, {
'location': AUTODESIGN_PATH.replace('<path_computation_id>', path_computation_id)}
@app.route(AUTODESIGN_PATH, methods=['GET'])
def get_autodesign(path_computation_id, path_request_service: PathRequestService):
return path_request_service.get_autodesign(path_computation_id), http.HTTPStatus.OK
@app.route(AUTODESIGN_PATH, methods=['DELETE'])
def delete_autodesign(path_computation_id, path_request_service: PathRequestService):
path_request_service.delete_autodesign(path_computation_id)
return '', http.HTTPStatus.NO_CONTENT

View File

@@ -1,7 +0,0 @@
# coding: utf-8
from gnpy.api import app
@app.route('/api/v1/status', methods=['GET'])
def api_status():
return {"version": "v1", "status": "ok"}, 200

View File

@@ -1,43 +0,0 @@
# coding: utf-8
import http
import json
from flask import request
from gnpy.api import app
from gnpy.api.exception.topology_error import TopologyError
from gnpy.api.model.result import Result
from gnpy.api.service import topology_service
TOPOLOGY_BASE_PATH = '/api/v1/topologies'
TOPOLOGY_ID_PATH = TOPOLOGY_BASE_PATH + '/<topology_id>'
@app.route(TOPOLOGY_BASE_PATH, methods=['POST'])
def create_topology():
if not request.is_json:
raise TopologyError('Request body is not json')
topology_identifier = topology_service.save_topology(request.json)
response = Result(message='Topology creation ok', description=topology_identifier)
return json.dumps(response.__dict__), 201, {'location': TOPOLOGY_BASE_PATH + '/' + topology_identifier}
@app.route(TOPOLOGY_ID_PATH, methods=['PUT'])
def update_topology(topology_id):
if not request.is_json:
raise TopologyError('Request body is not json')
topology_identifier = topology_service.update_topology(request.json, topology_id)
response = Result(message='Topology update ok', description=topology_identifier)
return json.dumps(response.__dict__), http.HTTPStatus.OK, {
'location': TOPOLOGY_BASE_PATH + '/' + topology_identifier}
@app.route(TOPOLOGY_ID_PATH, methods=['GET'])
def get_topology(topology_id):
return topology_service.get_topology(topology_id), http.HTTPStatus.OK
@app.route(TOPOLOGY_ID_PATH, methods=['DELETE'])
def delete_topology(topology_id):
topology_service.delete_topology(topology_id)
return '', http.HTTPStatus.NO_CONTENT

View File

@@ -1 +0,0 @@
# coding: utf-8

View File

@@ -1,45 +0,0 @@
# coding: utf-8
import configparser
import os
from flask import current_app
from gnpy.api.exception.config_error import ConfigError
def init_config(properties_file_path: str = os.path.join(os.path.dirname(__file__),
'properties.ini')) -> configparser.ConfigParser:
"""
Read config from properties_file_path
@param properties_file_path: the properties file to read
@return: config parser
"""
if not os.path.exists(properties_file_path):
raise ConfigError('Properties file does not exist ' + properties_file_path)
config = configparser.ConfigParser()
config.read(properties_file_path)
return config
def get_topology_dir() -> str:
"""
Get the base dir where topologies are saved
@return: the directory of topologies
"""
return current_app.config['properties'].get('DIRECTORY', 'topology')
def get_equipment_dir() -> str:
"""
Get the base dir where equipments are saved
@return: the directory of equipments
"""
return current_app.config['properties'].get('DIRECTORY', 'equipment')
def get_autodesign_dir() -> str:
"""
Get the base dir where autodesign are saved
@return: the directory of equipments
"""
return current_app.config['properties'].get('DIRECTORY', 'autodesign')

View File

@@ -1,13 +0,0 @@
# coding: utf-8
from cryptography.fernet import Fernet
class EncryptionService:
def __init__(self, key):
self._fernet = Fernet(key)
def encrypt(self, data):
return self._fernet.encrypt(data)
def decrypt(self, data):
return self._fernet.decrypt(data)

View File

@@ -1,66 +0,0 @@
# coding: utf-
import json
import os
import uuid
from injector import Inject
from gnpy.api.exception.equipment_error import EquipmentError
from gnpy.api.service import config_service
from gnpy.api.service.encryption_service import EncryptionService
class EquipmentService:
def __init__(self, encryption_service: EncryptionService):
self.encryption = encryption_service
def save_equipment(self, equipment):
"""
Save equipment to file.
@param equipment: json content
@return: a UUID identifier to identify the equipment
"""
equipment_identifier = str(uuid.uuid4())
# TODO: validate json content
self._write_equipment(equipment, equipment_identifier)
return equipment_identifier
def update_equipment(self, equipment, equipment_identifier):
"""
Update equipment with identifier equipment_identifier.
@param equipment_identifier: the identifier of the equipment to be updated
@param equipment: json content
@return: a UUID identifier to identify the equipment
"""
# TODO: validate json content
self._write_equipment(equipment, equipment_identifier)
return equipment_identifier
def _write_equipment(self, equipment, equipment_identifier):
equipment_dir = config_service.get_equipment_dir()
with(open(os.path.join(equipment_dir, '.'.join([equipment_identifier, 'json'])), 'wb')) as file:
file.write(self.encryption.encrypt(json.dumps(equipment).encode()))
def get_equipment(self, equipment_id: str) -> dict:
"""
Get the equipment with id equipment_id
@param equipment_id:
@return: the equipment in json format
"""
equipment_dir = config_service.get_equipment_dir()
equipment_file = os.path.join(equipment_dir, '.'.join([equipment_id, 'json']))
if not os.path.exists(equipment_file):
raise EquipmentError('Equipment with id {} does not exist '.format(equipment_id))
with(open(equipment_file, 'rb')) as file:
return json.loads(self.encryption.decrypt(file.read()))
def delete_equipment(self, equipment_id: str):
"""
Delete equipment with id equipment_id
@param equipment_id:
"""
equipment_dir = config_service.get_equipment_dir()
equipment_file = os.path.join(equipment_dir, '.'.join([equipment_id, 'json']))
if os.path.exists(equipment_file):
os.remove(equipment_file)

View File

@@ -1,100 +0,0 @@
# -*- coding: utf-8 -*-
import json
import logging
import os
import uuid
import gnpy.core.ansi_escapes as ansi_escapes
from gnpy.api.exception.path_computation_error import PathComputationError
from gnpy.api.service import config_service
from gnpy.api.service.encryption_service import EncryptionService
from gnpy.core.network import build_network
from gnpy.core.utils import lin2db, automatic_nch
from gnpy.tools.json_io import requests_from_json, disjunctions_from_json, network_to_json
from gnpy.topology.request import (compute_path_dsjctn, requests_aggregation,
correct_json_route_list,
deduplicate_disjunctions, compute_path_with_disjunction)
from gnpy.topology.spectrum_assignment import build_oms_list, pth_assign_spectrum
_logger = logging.getLogger(__name__)
class PathRequestService:
def __init__(self, encryption_service: EncryptionService):
self.encryption = encryption_service
def path_requests_run(self, service, network, equipment):
# 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))
build_network(network, equipment, p_db, p_total_db)
path_computation_identifier = str(uuid.uuid4())
autodesign_dir = config_service.get_autodesign_dir()
with(open(os.path.join(autodesign_dir, '.'.join([path_computation_identifier, 'json'])), 'wb')) as file:
file.write(self.encryption.encrypt(json.dumps(network_to_json(network)).encode()))
oms_list = build_oms_list(network, equipment)
rqs = requests_from_json(service, equipment)
# 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)
raise ValueError('Requests id ' + all_ids + ' are not unique')
rqs = correct_json_route_list(network, rqs)
# pths = compute_path(network, equipment, rqs)
dsjn = disjunctions_from_json(service)
# 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)
rqs, dsjn = requests_aggregation(rqs, dsjn)
# TODO export novel set of aggregated demands in a json file
_logger.info(f'{ansi_escapes.blue}The following services have been requested:{ansi_escapes.reset}' + str(rqs))
_logger.info(f'{ansi_escapes.blue}Computing all paths with constraints{ansi_escapes.reset}')
pths = compute_path_dsjctn(network, equipment, rqs, dsjn)
_logger.info(f'{ansi_escapes.blue}Propagating on selected path{ansi_escapes.reset}')
propagatedpths, reversed_pths, reversed_propagatedpths = compute_path_with_disjunction(network, equipment, rqs,
pths)
# 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.
pth_assign_spectrum(pths, rqs, oms_list, reversed_pths)
return propagatedpths, reversed_propagatedpths, rqs, path_computation_identifier
def get_autodesign(self, path_computation_id):
"""
Get the autodesign with id topology_id
@param path_computation_id:
@return: the autodesign in json format
"""
autodesign_dir = config_service.get_autodesign_dir()
autodesign_file = os.path.join(autodesign_dir, '.'.join([path_computation_id, 'json']))
if not os.path.exists(autodesign_file):
raise PathComputationError('Autodesign with id {} does not exist '.format(path_computation_id))
with(open(autodesign_file, 'rb')) as file:
return json.loads(self.encryption.decrypt(file.read()))
def delete_autodesign(self, path_computation_id: str):
"""
Delete autodesign with id equipment_id
@param path_computation_id:
"""
autodesign_dir = config_service.get_autodesign_dir()
autodesign_file = os.path.join(autodesign_dir, '.'.join([path_computation_id, 'json']))
if os.path.exists(autodesign_file):
os.remove(autodesign_file)

View File

@@ -1,4 +0,0 @@
[DIRECTORY]
topology: /opt/application/oopt-gnpy/topology
equipment: /opt/application/oopt-gnpy/equipment
autodesign: /opt/application/oopt-gnpy/autodesign

View File

@@ -1,62 +0,0 @@
# coding: utf-
import json
import os
import uuid
from gnpy.api.exception.topology_error import TopologyError
from gnpy.api.service import config_service
def save_topology(topology):
"""
Save topology to file.
@param topology: json content
@return: a UUID identifier to identify the topology
"""
topology_identifier = str(uuid.uuid4())
# TODO: validate json content
_write_topology(topology, topology_identifier)
return topology_identifier
def update_topology(topology, topology_identifier):
"""
Update topology with identifier topology_identifier.
@param topology_identifier: the identifier of the topology to be updated
@param topology: json content
@return: a UUID identifier to identify the topology
"""
# TODO: validate json content
_write_topology(topology, topology_identifier)
return topology_identifier
def _write_topology(topology, topology_identifier):
topology_dir = config_service.get_topology_dir()
with(open(os.path.join(topology_dir, '.'.join([topology_identifier, 'json'])), 'w')) as file:
json.dump(topology, file)
def get_topology(topology_id: str) -> dict:
"""
Get the topology with id topology_id
@param topology_id:
@return: the topology in json format
"""
topology_dir = config_service.get_topology_dir()
topology_file = os.path.join(topology_dir, '.'.join([topology_id, 'json']))
if not os.path.exists(topology_file):
raise TopologyError('Topology with id {} does not exist '.format(topology_id))
with(open(topology_file, 'r')) as file:
return json.load(file)
def delete_topology(topology_id: str):
"""
Delete topology with id topology_id
@param topology_id:
"""
topology_dir = config_service.get_topology_dir()
topology_file = os.path.join(topology_dir, '.'.join([topology_id, 'json']))
if os.path.exists(topology_file):
os.remove(topology_file)

View File

@@ -1,4 +1,4 @@
'''
"""
Simulation of signal propagation in the DWDM network
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`.
The simulation is controlled via :py:mod:`.parameters` and implemented mainly
via :py:mod:`.science_utils`.
'''
"""

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,12 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
"""
gnpy.core.equipment
===================
This module contains functionality for specifying equipment.
'''
"""
from gnpy.core.utils import automatic_nch, db2lin
from gnpy.core.exceptions import EquipmentConfigError
@@ -29,17 +29,22 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F
trx_params = {**mode_params}
# sanity check: spacing baudrate must be smaller than min spacing
if trx_params['baud_rate'] > trx_params['min_spacing']:
raise EquipmentConfigError(f'Inconsistency in equipment library:\n Transpoder "{trx_type_variety}" mode "{trx_params["format"]}" ' +
f'has baud rate {trx_params["baud_rate"]*1e-9} GHz greater than min_spacing {trx_params["min_spacing"]*1e-9}.')
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:.3f} GHz greater than min_spacing'
+ f' {trx_params["min_spacing"] * 1e-9:.3f}.')
trx_params['equalization_offset_db'] = trx_params.get('equalization_offset_db', 0)
else:
mode_params = {"format": "undetermined",
"baud_rate": None,
"OSNR": None,
"penalties": None,
"bit_rate": None,
"roll_off": None,
"tx_osnr": None,
"min_spacing": None,
"cost": None}
"cost": None,
"equalization_offset_db": 0}
trx_params = {**mode_params}
trx_params['f_min'] = equipment['Transceiver'][trx_type_variety].frequency['min']
trx_params['f_max'] = equipment['Transceiver'][trx_type_variety].frequency['max']
@@ -59,14 +64,13 @@ def trx_mode_params(equipment, trx_type_variety='', trx_mode='', error_message=F
trx_params['baud_rate'] = default_si_data.baud_rate
trx_params['spacing'] = default_si_data.spacing
trx_params['OSNR'] = None
trx_params['penalties'] = {}
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['equalization_offset_db'] = 0
trx_params['power'] = db2lin(default_si_data.power_dbm) * 1e-3

View File

@@ -1,37 +1,37 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
"""
gnpy.core.exceptions
====================
Exceptions thrown by other gnpy modules
'''
"""
class ConfigurationError(Exception):
'''User-provided configuration contains an error'''
"""User-provided configuration contains an error"""
class EquipmentConfigError(ConfigurationError):
'''Incomplete or wrong configuration within the equipment library'''
"""Incomplete or wrong configuration within the equipment library"""
class NetworkTopologyError(ConfigurationError):
'''Topology of user-provided network is wrong'''
"""Topology of user-provided network is wrong"""
class ServiceError(Exception):
'''Service of user-provided request is wrong'''
"""Service of user-provided request is wrong"""
class DisjunctionError(ServiceError):
'''Disjunction of user-provided request can not be satisfied'''
"""Disjunction of user-provided request can not be satisfied"""
class SpectrumError(Exception):
'''Spectrum errors of the program'''
"""Spectrum errors of the program"""
class ParametersError(ConfigurationError):
'''Incomplete or wrong configurations within parameters json'''
"""Incomplete or wrong configurations within parameters json"""

View File

@@ -1,57 +1,404 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
"""
gnpy.core.info
==============
This module contains classes for modelling :class:`SpectralInformation`.
'''
"""
from __future__ import annotations
from collections import namedtuple
from gnpy.core.utils import automatic_nch, lin2db
from collections.abc import Iterable
from typing import Union
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')):
"""carriers power in W"""
class Channel(namedtuple('Channel', 'channel_number frequency baud_rate roll_off power chromatic_dispersion pmd')):
""" Class containing the parameters of a WDM signal.
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.
:param channel_number: channel number in the WDM grid
:param frequency: central frequency of the signal (Hz)
:param baud_rate: the symbol rate of the signal (Baud)
: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 chromatic_dispersion: chromatic dispersion (s/m)
:param pmd: polarization mode dispersion (s)
:param channel_number: channel number in the WDM grid
:param frequency: central frequency of the signal (Hz)
: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 power (gnpy.core.info.Power): power of signal, ASE noise and NLI (W)
:param chromatic_dispersion: chromatic dispersion (s/m)
: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 Pref(namedtuple('Pref', 'p_span0, p_spani, ref_carrier')):
"""noiseless reference power in dBm:
p_span0: inital target carrier power
p_spani: carrier power after element i
neq_ch: equivalent channel count in dB"""
p_span0: inital target carrier power for a reference channel defined by user
p_spani: carrier power after element i for a reference channel defined by user
ref_carrier records the baud rate of the reference channel
"""
class SpectralInformation(namedtuple('SpectralInformation', 'pref carriers')):
class SpectralInformation(object):
"""Class containing the parameters of the entire WDM comb.
def __new__(cls, pref, carriers):
return super().__new__(cls, pref, carriers)
delta_pdb_per_channel: (per frequency) per channel delta power in dbm for the actual mix of channels"""
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, ref_power: Pref, 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._pref = ref_power
self._label = label[indices]
@property
def pref(self):
"""Instance of gnpy.info.Pref"""
return self._pref
@pref.setter
def pref(self, pref: Pref):
self._pref = pref
@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 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:
# Note that pref.p_spanx from "self" and "other" must be identical for a given simulation (correspond to the
# the simulation setup):
# - for a given simulation there is only one design (one p_span0),
# - and p_spani is the propagation result of p_span0 so there should not be different p_spani either.
if (self.pref.p_span0 != other.pref.p_span0) or (self.pref.p_spani != other.pref.p_spani):
raise SpectrumError('reference powers of the spectrum are not identical')
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),
ref_power=Pref(self.pref.p_span0, self.pref.p_spani, self.pref.ref_carrier),
label=append(self.label, other.label))
except SpectrumError:
raise SpectrumError('Spectra cannot be summed: channels overlapping.')
def create_input_spectral_information(f_min, f_max, roll_off, baud_rate, power, spacing):
# pref in dB : convert power lin into power in dB
pref = lin2db(power * 1e3)
nb_channel = automatic_nch(f_min, f_max, spacing)
si = SpectralInformation(
pref=Pref(pref, pref, lin2db(nb_channel)),
carriers=[
Channel(f, (f_min + spacing * f),
baud_rate, roll_off, Power(power, 0, 0), 0, 0) for f in range(1, nb_channel + 1)
]
)
return si
def _replace(self, carriers, pref):
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])
self.pref = pref
return self
def create_arbitrary_spectral_information(frequency: Union[ndarray, Iterable, float],
signal: Union[float, ndarray, Iterable],
baud_rate: Union[float, ndarray, Iterable],
tx_osnr: Union[float, ndarray, Iterable],
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.,
ref_power: Pref = None,
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)
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,
ref_power=ref_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, tx_osnr, delta_pdb=0,
ref_carrier=None):
"""Creates a fixed slot width spectral information with flat power.
all arguments are scalar values"""
number_of_channels = automatic_nch(f_min, f_max, spacing)
frequency = [(f_min + spacing * i) for i in range(1, number_of_channels + 1)]
p_span0 = watt2dbm(power)
p_spani = watt2dbm(power)
delta_pdb_per_channel = delta_pdb * ones(number_of_channels)
label = [f'{baud_rate * 1e-9 :.2f}G' for i in range(number_of_channels)]
return create_arbitrary_spectral_information(frequency, slot_width=spacing, signal=power, baud_rate=baud_rate,
roll_off=roll_off, delta_pdb_per_channel=delta_pdb_per_channel,
tx_osnr=tx_osnr,
ref_power=Pref(p_span0=p_span0, p_spani=p_spani,
ref_carrier=ref_carrier),
label=label)
def carriers_to_spectral_information(initial_spectrum: dict[float, Carrier], power: float,
ref_carrier: ReferenceCarrier) -> 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 and roll off.
:param power: power of the request
:param ref_carrier: reference carrier (baudrate) used for the reference channel
"""
frequency = list(initial_spectrum.keys())
signal = [power * db2lin(c.delta_pdb) 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()]
label = [c.label for c in initial_spectrum.values()]
p_span0 = watt2dbm(power)
p_spani = watt2dbm(power)
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,
ref_power=Pref(p_span0=p_span0, p_spani=p_spani,
ref_carrier=ref_carrier),
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
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
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 p_span_i 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 slot_width or roll-off) may be added there for future equalization purpose.
"""
baud_rate: float
slot_width: float

View File

@@ -1,19 +1,25 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
"""
gnpy.core.network
=================
Working with networks which consist of network elements
'''
"""
from scipy.interpolate import interp1d
from operator import attrgetter
from gnpy.core import ansi_escapes, elements
from collections import namedtuple
from logging import getLogger
from gnpy.core import elements
from gnpy.core.exceptions import ConfigurationError, NetworkTopologyError
from gnpy.core.utils import round2float, convert_length
from collections import namedtuple
from gnpy.core.info import ReferenceCarrier
from gnpy.tools.json_io import Amp
logger = getLogger(__name__)
def edfa_nf(gain_target, variety_type, equipment):
@@ -28,6 +34,7 @@ def edfa_nf(gain_target, variety_type, equipment):
)
amp.pin_db = 0
amp.nch = 88
amp.slot_width = 50e9
return amp._calc_nf(True)
@@ -103,10 +110,9 @@ def select_edfa(raman_allowed, gain_target, power_target, equipment, uid, restri
please increase span fiber padding')
else:
# TODO: convert to logging
print(
f'{ansi_escapes.red}WARNING:{ansi_escapes.reset} target gain in node {uid} is below all available amplifiers min gain: \
amplifier input padding will be assumed, consider increase span fiber padding instead'
)
logger.warning(f'\n\tWARNING: target gain in node {uid} is below all available amplifiers min gain: '
+ '\n\tamplifier input padding will be assumed, consider increase span fiber padding '
+ 'instead.\n')
acceptable_gain_min_list = edfa_list
# filter on gain+power limitation:
@@ -128,16 +134,16 @@ def select_edfa(raman_allowed, gain_target, power_target, equipment, uid, restri
# check what are the gain and power limitations of this amp
power_reduction = round(min(selected_edfa.power, 0), 2)
if power_reduction < -0.5:
print(
f'{ansi_escapes.red}WARNING:{ansi_escapes.reset} target gain and power in node {uid}\n \
is beyond all available amplifiers capabilities and/or extended_gain_range:\n\
a power reduction of {power_reduction} is applied\n'
)
logger.warning(f'\n\tWARNING: target gain and power in node {uid}\n'
+ '\tis beyond all available amplifiers capabilities and/or extended_gain_range:\n'
+ f'\ta power reduction of {power_reduction} is applied\n')
return selected_edfa.variety, power_reduction
def target_power(network, node, equipment): # get_fiber_dp
if isinstance(node, elements.Roadm):
return 0
SPAN_LOSS_REF = 20
POWER_SLOPE = 0.3
dp_range = list(equipment['Span']['default'].delta_power_range_db)
@@ -147,62 +153,52 @@ def target_power(network, node, equipment): # get_fiber_dp
dp = round2float((node_loss - SPAN_LOSS_REF) * POWER_SLOPE, dp_range[2])
dp = max(dp_range[0], dp)
dp = min(dp_range[1], dp)
except KeyError:
except IndexError:
raise ConfigurationError(f'invalid delta_power_range_db definition in eqpt_config[Span]'
f'delta_power_range_db: [lower_bound, upper_bound, step]')
if isinstance(node, elements.Roadm):
dp = 0
return dp
_fiber_fused_types = (elements.Fused, elements.Fiber)
def prev_node_generator(network, node):
"""fused spans interest:
iterate over all predecessors while they are Fused or Fiber type"""
iterate over all predecessors while they are either Fused or Fibers succeeded by Fused"""
try:
prev_node = next(n for n in network.predecessors(node))
prev_node = next(network.predecessors(node))
except StopIteration:
if isinstance(node, elements.Transceiver):
return
raise NetworkTopologyError(f'Node {node.uid} is not properly connected, please check network topology')
# yield and re-iterate
if isinstance(prev_node, elements.Fused) or isinstance(node, elements.Fused):
if ((isinstance(prev_node, elements.Fused) and isinstance(node, _fiber_fused_types)) or
(isinstance(prev_node, _fiber_fused_types) and isinstance(node, elements.Fused))):
yield prev_node
yield from prev_node_generator(network, prev_node)
else:
StopIteration
def next_node_generator(network, node):
"""fused spans interest:
iterate over all successors while they are Fused or Fiber type"""
iterate over all predecessors while they are either Fused or Fibers preceded by Fused"""
try:
next_node = next(n for n in network.successors(node))
next_node = next(network.successors(node))
except StopIteration:
raise NetworkTopologyError('Node {node.uid} is not properly connected, please check network topology')
# yield and re-iterate
if isinstance(next_node, elements.Fused) or isinstance(node, elements.Fused):
if isinstance(node, elements.Transceiver):
return
raise NetworkTopologyError(f'Node {node.uid} is not properly connected, please check network topology')
if ((isinstance(next_node, elements.Fused) and isinstance(node, _fiber_fused_types)) or
(isinstance(next_node, _fiber_fused_types) and isinstance(node, elements.Fused))):
yield next_node
yield from next_node_generator(network, next_node)
else:
StopIteration
def span_loss(network, node):
"""Fused span interest:
return the total span loss of all the fibers spliced by a Fused node"""
"""Total loss of a span (Fiber and Fused nodes) which contains the given node"""
loss = node.loss if node.passive else 0
try:
prev_node = next(n for n in network.predecessors(node))
if isinstance(prev_node, elements.Fused):
loss += sum(n.loss for n in prev_node_generator(network, node))
except StopIteration:
pass
try:
next_node = next(n for n in network.successors(node))
if isinstance(next_node, elements.Fused):
loss += sum(n.loss for n in next_node_generator(network, node))
except StopIteration:
pass
loss += sum(n.loss for n in prev_node_generator(network, node))
loss += sum(n.loss for n in next_node_generator(network, node))
return loss
@@ -229,10 +225,10 @@ def find_last_node(network, node):
def set_amplifier_voa(amp, power_target, power_mode):
VOA_MARGIN = 1 # do not maximize the VOA optimization
if amp.out_voa is None:
if power_mode:
if power_mode and amp.params.out_voa_auto:
voa = min(amp.params.p_max - power_target,
amp.params.gain_flatmax - amp.effective_gain)
voa = max(round2float(max(voa, 0), 0.5) - VOA_MARGIN, 0) if amp.params.out_voa_auto else 0
voa = max(round2float(voa, 0.5) - VOA_MARGIN, 0)
amp.delta_p = amp.delta_p + voa
amp.effective_gain = amp.effective_gain + voa
else:
@@ -240,98 +236,140 @@ def set_amplifier_voa(amp, power_target, power_mode):
amp.out_voa = voa
def set_egress_amplifier(network, roadm, equipment, pref_total_db):
def set_egress_amplifier(network, this_node, equipment, pref_ch_db, pref_total_db):
"""this node can be a transceiver or a ROADM (same function called in both cases)"""
power_mode = equipment['Span']['default'].power_mode
next_oms = (n for n in network.successors(roadm) if not isinstance(n, elements.Transceiver))
ref_carrier = ReferenceCarrier(baud_rate=equipment['SI']['default'].baud_rate,
slot_width=equipment['SI']['default'].spacing)
next_oms = (n for n in network.successors(this_node) if not isinstance(n, elements.Transceiver))
for oms in next_oms:
# go through all the OMS departing from the Roadm
node = roadm
prev_node = roadm
next_node = oms
# if isinstance(next_node, elements.Fused): #support ROADM wo egress amp for metro applications
# node = find_last_node(next_node)
# next_node = next(n for n in network.successors(node))
# next_node = find_last_node(next_node)
prev_dp = getattr(node.params, 'target_pch_out_db', 0)
# go through all the OMS departing from the ROADM
prev_node = this_node
node = oms
if isinstance(this_node, elements.Transceiver):
this_node_out_power = 0.0 # default value if this_node is a transceiver
if isinstance(this_node, elements.Roadm):
# get target power out from ROADM for the reference carrier based on equalization settings
this_node_out_power = this_node.get_per_degree_ref_power(degree=node.uid, ref_carrier=ref_carrier)
# use the target power on this degree
prev_dp = this_node_out_power - pref_ch_db
dp = prev_dp
prev_voa = 0
voa = 0
while True:
visited_nodes = []
while not (isinstance(node, elements.Roadm) or isinstance(node, elements.Transceiver)):
# go through all nodes in the OMS (loop until next Roadm instance)
try:
next_node = next(network.successors(node))
except StopIteration:
raise NetworkTopologyError(f'{type(node).__name__} {node.uid} is not properly connected, please check network topology')
visited_nodes.append(node)
if next_node in visited_nodes:
raise NetworkTopologyError(f'Loop detected for {type(node).__name__} {node.uid}, please check network topology')
if isinstance(node, elements.Edfa):
node_loss = span_loss(network, prev_node)
voa = node.out_voa if node.out_voa else 0
if node.delta_p is None:
dp = target_power(network, next_node, equipment)
dp = target_power(network, next_node, equipment) + voa
else:
dp = node.delta_p
gain_from_dp = node_loss + dp - prev_dp + prev_voa
if node.effective_gain is None or power_mode:
gain_target = gain_from_dp
gain_target = node_loss + dp - prev_dp + prev_voa
else: # gain mode with effective_gain
gain_target = node.effective_gain
dp = prev_dp - node_loss + gain_target
dp = prev_dp - node_loss - prev_voa + gain_target
power_target = pref_total_db + dp
raman_allowed = False
if isinstance(prev_node, elements.Fiber):
max_fiber_lineic_loss_for_raman = \
equipment['Span']['default'].max_fiber_lineic_loss_for_raman
raman_allowed = prev_node.params.loss_coef < max_fiber_lineic_loss_for_raman
equipment['Span']['default'].max_fiber_lineic_loss_for_raman * 1e-3 # dB/m
raman_allowed = (prev_node.params.loss_coef < max_fiber_lineic_loss_for_raman).all()
else:
raman_allowed = False
# implementation of restrictions on roadm boosters
if isinstance(prev_node, elements.Roadm):
if prev_node.restrictions['booster_variety_list']:
if node.params.type_variety == '':
if node.variety_list and isinstance(node.variety_list, list):
restrictions = node.variety_list
elif isinstance(prev_node, elements.Roadm) and prev_node.restrictions['booster_variety_list']:
# implementation of restrictions on roadm boosters
restrictions = prev_node.restrictions['booster_variety_list']
else:
restrictions = None
elif isinstance(next_node, elements.Roadm):
# implementation of restrictions on roadm preamp
if next_node.restrictions['preamp_variety_list']:
elif isinstance(next_node, elements.Roadm) and next_node.restrictions['preamp_variety_list']:
# implementation of restrictions on roadm preamp
restrictions = next_node.restrictions['preamp_variety_list']
else:
restrictions = None
else:
restrictions = None
if node.params.type_variety == '':
edfa_variety, power_reduction = select_edfa(raman_allowed, gain_target, power_target, equipment, node.uid, restrictions)
extra_params = equipment['Edfa'][edfa_variety]
node.params.update_params(extra_params.__dict__)
dp += power_reduction
gain_target += power_reduction
elif node.params.raman and not raman_allowed:
print(f'{ansi_escapes.red}WARNING{ansi_escapes.reset}: raman is used in node {node.uid}\n but fiber lineic loss is above threshold\n')
else:
if node.params.raman and not raman_allowed:
if isinstance(prev_node, elements.Fiber):
logger.warning(f'\n\tWARNING: raman is used in node {node.uid}\n '
+ '\tbut fiber lineic loss is above threshold\n')
else:
logger.critical(f'\n\tWARNING: raman is used in node {node.uid}\n '
+ '\tbut previous node is not a fiber\n')
# if variety is imposed by user, and if the gain_target (computed or imposed) is also above
# variety max gain + extended range, then warn that gain > max_gain + extended range
if gain_target - equipment['Edfa'][node.params.type_variety].gain_flatmax - \
equipment['Span']['default'].target_extended_gain > 1e-2:
# 1e-2 to allow a small margin according to round2float min step
logger.warning(f'\n\tWARNING: effective gain in Node {node.uid}\n'
+ f'\tis above user specified amplifier {node.params.type_variety}\n'
+ '\tmax flat gain: '
+ f'{equipment["Edfa"][node.params.type_variety].gain_flatmax}dB ; '
+ f'required gain: {gain_target}dB. Please check amplifier type.\n')
node.delta_p = dp if power_mode else None
node.effective_gain = gain_target
set_amplifier_voa(node, power_target, power_mode)
if isinstance(next_node, elements.Roadm) or isinstance(next_node, elements.Transceiver):
break
prev_dp = dp
prev_voa = voa
prev_node = node
node = next_node
# print(f'{node.uid}')
next_node = next(n for n in network.successors(node))
def add_egress_amplifier(network, node):
next_nodes = [n for n in network.successors(node)
def set_roadm_per_degree_targets(roadm, network):
"""Set target powers/PSD on all degrees
This is needed to populate per_degree_pch_out_dbm or per_degree_pch_psd or per_degree_pch_psw dicts when
they are not initialized by users.
"""
next_oms = (n for n in network.successors(roadm) if not isinstance(n, elements.Transceiver))
for node in next_oms:
# go through all the OMS departing from the ROADM
if node.uid not in roadm.per_degree_pch_out_dbm and node.uid not in roadm.per_degree_pch_psd and \
node.uid not in roadm.per_degree_pch_psw:
# if no target power is defined on this degree or no per degree target power is given use the global one
if roadm.params.target_pch_out_db:
roadm.per_degree_pch_out_dbm[node.uid] = roadm.params.target_pch_out_db
elif roadm.params.target_psd_out_mWperGHz:
roadm.per_degree_pch_psd[node.uid] = roadm.params.target_psd_out_mWperGHz
elif roadm.params.target_out_mWperSlotWidth:
roadm.per_degree_pch_psw[node.uid] = roadm.params.target_out_mWperSlotWidth
else:
raise ConfigurationError(roadm.uid, 'needs an equalization target')
def add_roadm_booster(network, roadm):
next_nodes = [n for n in network.successors(roadm)
if not (isinstance(n, elements.Transceiver) or isinstance(n, elements.Fused) or isinstance(n, elements.Edfa))]
# no amplification for fused spans or TRX
for i, next_node in enumerate(next_nodes):
network.remove_edge(node, next_node)
for next_node in next_nodes:
network.remove_edge(roadm, next_node)
amp = elements.Edfa(
uid=f'Edfa{i}_{node.uid}',
params={},
uid=f'Edfa_booster_{roadm.uid}_to_{next_node.uid}',
params=Amp.default_values,
metadata={
'location': {
'latitude': (node.lat * 2 + next_node.lat * 2) / 4,
'longitude': (node.lng * 2 + next_node.lng * 2) / 4,
'city': node.loc.city,
'region': node.loc.region,
'latitude': roadm.lat,
'longitude': roadm.lng,
'city': roadm.loc.city,
'region': roadm.loc.region,
}
},
operational={
@@ -339,11 +377,62 @@ def add_egress_amplifier(network, node):
'tilt_target': 0,
})
network.add_node(amp)
if isinstance(node, elements.Fiber):
edgeweight = node.params.length
network.add_edge(roadm, amp, weight=0.01)
network.add_edge(amp, next_node, weight=0.01)
def add_roadm_preamp(network, roadm):
prev_nodes = [n for n in network.predecessors(roadm)
if not (isinstance(n, elements.Transceiver) or isinstance(n, elements.Fused) or isinstance(n, elements.Edfa))]
# no amplification for fused spans or TRX
for prev_node in prev_nodes:
network.remove_edge(prev_node, roadm)
amp = elements.Edfa(
uid=f'Edfa_preamp_{roadm.uid}_from_{prev_node.uid}',
params=Amp.default_values,
metadata={
'location': {
'latitude': roadm.lat,
'longitude': roadm.lng,
'city': roadm.loc.city,
'region': roadm.loc.region,
}
},
operational={
'gain_target': None,
'tilt_target': 0,
})
network.add_node(amp)
if isinstance(prev_node, elements.Fiber):
edgeweight = prev_node.params.length
else:
edgeweight = 0.01
network.add_edge(node, amp, weight=edgeweight)
network.add_edge(prev_node, amp, weight=edgeweight)
network.add_edge(amp, roadm, weight=0.01)
def add_inline_amplifier(network, fiber):
next_node = next(network.successors(fiber))
if isinstance(next_node, elements.Fiber) or isinstance(next_node, elements.RamanFiber):
# no amplification for fused spans or TRX
network.remove_edge(fiber, next_node)
amp = elements.Edfa(
uid=f'Edfa_{fiber.uid}',
params=Amp.default_values,
metadata={
'location': {
'latitude': (fiber.lat + next_node.lat) / 2,
'longitude': (fiber.lng + next_node.lng) / 2,
'city': fiber.loc.city,
'region': fiber.loc.region,
}
},
operational={
'gain_target': None,
'tilt_target': 0,
})
network.add_node(amp)
network.add_edge(fiber, amp, weight=fiber.params.length)
network.add_edge(amp, next_node, weight=0.01)
@@ -351,24 +440,20 @@ def calculate_new_length(fiber_length, bounds, target_length):
if fiber_length < bounds.stop:
return fiber_length, 1
n_spans = int(fiber_length // target_length)
n_spans2 = int(fiber_length // target_length)
n_spans1 = n_spans2 + 1
length1 = fiber_length / (n_spans + 1)
delta1 = target_length - length1
result1 = (length1, n_spans + 1)
length2 = fiber_length / n_spans
delta2 = length2 - target_length
result2 = (length2, n_spans)
length1 = fiber_length / n_spans1
length2 = fiber_length / n_spans2
if (bounds.start <= length1 <= bounds.stop) and not(bounds.start <= length2 <= bounds.stop):
result = result1
return (length1, n_spans1)
elif (bounds.start <= length2 <= bounds.stop) and not(bounds.start <= length1 <= bounds.stop):
result = result2
return (length2, n_spans2)
elif length2 - target_length <= target_length - length1 and length2 <= bounds.stop:
return (length2, n_spans2)
else:
result = result1 if delta1 < delta2 else result2
return result
return (length1, n_spans1)
def split_fiber(network, fiber, bounds, target_length, equipment):
@@ -386,9 +471,8 @@ def split_fiber(network, fiber, bounds, target_length, equipment):
fiber.params.length = new_length
f = interp1d([prev_node.lng, next_node.lng], [prev_node.lat, next_node.lat])
xpos = [prev_node.lng + (next_node.lng - prev_node.lng) * (n + 1) / (n_spans + 1) for n in range(n_spans)]
ypos = f(xpos)
xpos = [prev_node.lng + (next_node.lng - prev_node.lng) * (n + 0.5) / n_spans for n in range(n_spans)]
ypos = [prev_node.lat + (next_node.lat - prev_node.lat) * (n + 0.5) / n_spans for n in range(n_spans)]
for span, lng, lat in zip(range(n_spans), xpos, ypos):
new_span = elements.Fiber(uid=f'{fiber.uid}_({span+1}/{n_spans})',
type_variety=fiber.type_variety,
@@ -416,11 +500,14 @@ def split_fiber(network, fiber, bounds, target_length, equipment):
def add_connector_loss(network, fibers, default_con_in, default_con_out, EOL):
for fiber in fibers:
try:
next_node = next(network.successors(fiber))
except StopIteration:
raise NetworkTopologyError(f'Fiber {fiber.uid} is not properly connected, please check network topology')
if fiber.params.con_in is None:
fiber.params.con_in = default_con_in
if fiber.params.con_out is None:
fiber.params.con_out = default_con_out
next_node = next(n for n in network.successors(fiber))
if not isinstance(next_node, elements.Fused):
fiber.params.con_out += EOL
@@ -431,54 +518,58 @@ def add_fiber_padding(network, fibers, padding):
for fiber in network.predecessors(n)
if isinstance(fiber, elements.Fiber))"""
for fiber in fibers:
this_span_loss = span_loss(network, fiber)
try:
next_node = next(network.successors(fiber))
except StopIteration:
raise NetworkTopologyError(f'Fiber {fiber.uid} is not properly connected, please check network topology')
if this_span_loss < padding and not (isinstance(next_node, elements.Fused)):
if isinstance(next_node, elements.Fused):
continue
this_span_loss = span_loss(network, fiber)
if this_span_loss < padding:
# add a padding att_in at the input of the 1st fiber:
# address the case when several fibers are spliced together
first_fiber = find_first_node(network, fiber)
# in order to support no booster , fused might be placed
# just after a roadm: need to check that first_fiber is really a fiber
if isinstance(first_fiber, elements.Fiber):
if first_fiber.params.att_in is None:
first_fiber.params.att_in = padding - this_span_loss
else:
first_fiber.params.att_in = first_fiber.params.att_in + padding - this_span_loss
first_fiber.params.att_in = first_fiber.params.att_in + padding - this_span_loss
def build_network(network, equipment, pref_ch_db, pref_total_db):
def build_network(network, equipment, pref_ch_db, pref_total_db, no_insert_edfas=False):
default_span_data = equipment['Span']['default']
max_length = int(convert_length(default_span_data.max_length, default_span_data.length_units))
min_length = max(int(default_span_data.padding / 0.2 * 1e3), 50_000)
bounds = range(min_length, max_length)
target_length = max(min_length, 90_000)
default_con_in = default_span_data.con_in
default_con_out = default_span_data.con_out
padding = default_span_data.padding
target_length = max(min_length, min(max_length, 90_000))
# set roadm loss for gain_mode before to build network
fibers = [f for f in network.nodes() if isinstance(f, elements.Fiber)]
add_connector_loss(network, fibers, default_con_in, default_con_out, default_span_data.EOL)
add_fiber_padding(network, fibers, padding)
add_connector_loss(network, fibers, default_span_data.con_in, default_span_data.con_out, default_span_data.EOL)
# don't group split fiber and add amp in the same loop
# =>for code clarity (at the expense of speed):
for fiber in fibers:
split_fiber(network, fiber, bounds, target_length, equipment)
amplified_nodes = [n for n in network.nodes() if isinstance(n, elements.Fiber) or isinstance(n, elements.Roadm)]
for node in amplified_nodes:
add_egress_amplifier(network, node)
roadms = [r for r in network.nodes() if isinstance(r, elements.Roadm)]
for roadm in roadms:
set_egress_amplifier(network, roadm, equipment, pref_total_db)
# support older json input topology wo Roadms:
if len(roadms) == 0:
trx = [t for t in network.nodes() if isinstance(t, elements.Transceiver)]
for t in trx:
set_egress_amplifier(network, t, equipment, pref_total_db)
if not no_insert_edfas:
for fiber in fibers:
split_fiber(network, fiber, bounds, target_length, equipment)
for roadm in roadms:
add_roadm_preamp(network, roadm)
add_roadm_booster(network, roadm)
fibers = [f for f in network.nodes() if isinstance(f, elements.Fiber)]
for fiber in fibers:
add_inline_amplifier(network, fiber)
add_fiber_padding(network, fibers, default_span_data.padding)
for roadm in roadms:
set_roadm_per_degree_targets(roadm, network)
set_egress_amplifier(network, roadm, equipment, pref_ch_db, pref_total_db)
trx = [t for t in network.nodes() if isinstance(t, elements.Transceiver)]
for t in trx:
next_node = next(network.successors(t), None)
if next_node and not isinstance(next_node, elements.Roadm):
set_egress_amplifier(network, t, equipment, 0, pref_total_db)

View File

@@ -6,14 +6,13 @@ gnpy.core.parameters
====================
This module contains all parameters to configure standard network elements.
"""
from collections import namedtuple
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
@@ -30,110 +29,128 @@ class Parameters:
class PumpParams(Parameters):
def __init__(self, power, frequency, propagation_direction):
self._power = power
self._frequency = frequency
self._propagation_direction = propagation_direction
@property
def power(self):
return self._power
@property
def frequency(self):
return self._frequency
@property
def propagation_direction(self):
return self._propagation_direction
self.power = power
self.frequency = frequency
self.propagation_direction = propagation_direction.lower()
class RamanParams(Parameters):
def __init__(self, **kwargs):
self._flag_raman = kwargs['flag_raman']
self._space_resolution = kwargs['space_resolution'] if 'space_resolution' in kwargs else None
self._tolerance = kwargs['tolerance'] if 'tolerance' in kwargs else None
def __init__(self, flag=False, result_spatial_resolution=10e3, solver_spatial_resolution=50):
"""Simulation parameters used within the Raman Solver
@property
def flag_raman(self):
return self._flag_raman
@property
def space_resolution(self):
return self._space_resolution
@property
def tolerance(self):
return self._tolerance
:params flag: boolean for enabling/disable the evaluation of the Raman power profile in frequency and position
: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.result_spatial_resolution = result_spatial_resolution # [m]
self.solver_spatial_resolution = solver_spatial_resolution # [m]
class NLIParams(Parameters):
def __init__(self, **kwargs):
self._nli_method_name = kwargs['nli_method_name']
self._wdm_grid_size = kwargs['wdm_grid_size']
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
def __init__(self, method='gn_model_analytic', dispersion_tolerance=1, phase_shift_tolerance=0.1,
computed_channels=None):
"""Simulation parameters used within the Nli Solver
@property
def nli_method_name(self):
return self._nli_method_name
@property
def wdm_grid_size(self):
return self._wdm_grid_size
@property
def dispersion_tolerance(self):
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
:params method: formula for NLI calculation
:params dispersion_tolerance: tuning parameter for ggn model solution
: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
"""
self.method = method.lower()
self.dispersion_tolerance = dispersion_tolerance
self.phase_shift_tolerance = phase_shift_tolerance
self.computed_channels = computed_channels
class SimParams(Parameters):
def __init__(self, **kwargs):
try:
if 'nli_parameters' in kwargs:
self._nli_params = NLIParams(**kwargs['nli_parameters'])
else:
self._nli_params = None
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}')
_shared_dict = {'nli_params': NLIParams(), 'raman_params': RamanParams()}
@classmethod
def set_params(cls, sim_params):
cls._shared_dict['nli_params'] = NLIParams(**sim_params.get('nli_params', {}))
cls._shared_dict['raman_params'] = RamanParams(**sim_params.get('raman_params', {}))
@property
def nli_params(self):
return self._nli_params
return self._shared_dict['nli_params']
@property
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']
except KeyError as e:
raise ParametersError(f'ROADM configurations must include {e}. Configuration: {kwargs}')
class FusedParams(Parameters):
def __init__(self, **kwargs):
self.loss = kwargs['loss'] if 'loss' in kwargs else 1
DEFAULT_RAMAN_COEFFICIENT = {
# SSMF Raman coefficient profile normalized with respect to the effective area overlap (g0 * A_eff(f_probe, f_pump))
'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]
), # [m/W]
# 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': 206184634112792 # [Hz] (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):
@@ -141,45 +158,87 @@ class FiberParams(Parameters):
try:
self._length = convert_length(kwargs['length'], kwargs['length_units'])
# 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
# the None value will be updated in network.py[build_network]
# with default values from eqpt_config.json[Spans]
self._con_in = kwargs['con_in'] if 'con_in' in kwargs else None
self._con_out = kwargs['con_out'] if 'con_out' in kwargs else None
self._con_in = kwargs.get('con_in')
self._con_out = kwargs.get('con_out')
# Reference frequency (unique for all parameters: beta2, beta3, gamma, effective_area)
if 'ref_wavelength' in kwargs:
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:
self._ref_frequency = kwargs['ref_frequency']
self._ref_wavelength = c / self.ref_frequency
self._ref_wavelength = c / self._ref_frequency
else:
self._ref_wavelength = 1550e-9
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 \
-2 * self._dispersion/self.ref_wavelength # s/m/m/m
self._beta2 = -(self.ref_wavelength ** 2) * self.dispersion / (2 * pi * c) # 1/(m * Hz^2)
# Eq. (3.23) in Abramczyk, Halina. "Dispersion phenomena in optical fibers." Virtual European University
# on Lasers. Available online: http://mitr.p.lodz.pl/evu/lectures/Abramczyk3.pdf
# (accessed on 25 March 2018) (2005).
self._beta3 = ((self.dispersion_slope - (4*pi*c/self.ref_wavelength**3) * self.beta2) /
(2*pi*c/self.ref_wavelength**2)**2)
self._gamma = kwargs['gamma'] # 1/W/m
self._ref_wavelength = 1550e-9 # conventional central C band wavelength [m]
self._ref_frequency = c / self._ref_wavelength
# Chromatic Dispersion
if 'dispersion_per_frequency' in kwargs:
# Frequency-dependent dispersion
self._dispersion = asarray(kwargs['dispersion']['value']) # s/m/m
self._f_dispersion_ref = asarray(kwargs['dispersion']['frequency']) # Hz
self._dispersion_slope = None
elif 'dispersion' in kwargs:
# 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._effective_area = 2 * pi * self._n2 / (self._ref_wavelength * self._gamma) # m^2
else:
self._effective_area = 83e-12 # m^2
self._gamma = 2 * pi * self._n2 / (self._ref_wavelength * self._effective_area) # 1/W/m
self._contrast = 0.5 * (c / (2 * pi * self._ref_frequency * self._core_radius * self._n1) * exp(
pi * self._core_radius ** 2 / self._effective_area)) ** 2
# Raman Gain Coefficient
raman_coefficient = kwargs.get('raman_coefficient', DEFAULT_RAMAN_COEFFICIENT)
self._g0 = asarray(raman_coefficient['g0'])
raman_reference_frequency = raman_coefficient['reference_frequency']
frequency_offset = asarray(raman_coefficient['frequency_offset'])
stokes_wave = raman_reference_frequency - frequency_offset
gamma_raman = self._g0 * self.effective_area_overlap(stokes_wave, raman_reference_frequency)
normalized_gamma_raman = gamma_raman / raman_reference_frequency # 1 / m / W / Hz
self._raman_reference_frequency = raman_reference_frequency
# 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)
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
# 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 = kwargs['loss_coef'] * 1e-3 # lineic loss dB/m
self._f_loss_ref = 193.5e12 # Hz
self._lin_attenuation = db2lin(self.length * self.loss_coef)
self._lin_loss_exp = self.loss_coef / (10 * log10(exp(1))) # linear power exponent loss Neper/m
self._effective_length = (1 - exp(- self.lin_loss_exp * self.length)) / self.lin_loss_exp
self._asymptotic_length = 1 / self.lin_loss_exp
# raman parameters (not compulsory)
self._raman_efficiency = kwargs['raman_efficiency'] if 'raman_efficiency' in kwargs else None
self._pumps_loss_coef = kwargs['pumps_loss_coef'] if 'pumps_loss_coef' in kwargs else None
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:
raise ParametersError(f'Fiber configurations json must include {e}. Configuration: {kwargs}')
@@ -212,6 +271,10 @@ class FiberParams(Parameters):
def con_out(self):
return self._con_out
@property
def lumped_losses(self):
return self._lumped_losses
@con_out.setter
def con_out(self, con_out):
self._con_out = con_out
@@ -220,6 +283,10 @@ class FiberParams(Parameters):
def dispersion(self):
return self._dispersion
@property
def f_dispersion_ref(self):
return self._f_dispersion_ref
@property
def dispersion_slope(self):
return self._dispersion_slope
@@ -228,6 +295,20 @@ class FiberParams(Parameters):
def gamma(self):
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
def pmd_coef(self):
return self._pmd_coef
@@ -240,14 +321,6 @@ class FiberParams(Parameters):
def ref_frequency(self):
return self._ref_frequency
@property
def beta2(self):
return self._beta2
@property
def beta3(self):
return self._beta3
@property
def loss_coef(self):
return self._loss_coef
@@ -257,31 +330,148 @@ class FiberParams(Parameters):
return self._f_loss_ref
@property
def lin_loss_exp(self):
return self._lin_loss_exp
def raman_coefficient(self):
return self._raman_coefficient
@property
def lin_attenuation(self):
return self._lin_attenuation
@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 latency(self):
return self._latency
def asdict(self):
dictionary = super().asdict()
dictionary['loss_coef'] = self.loss_coef * 1e3
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
class EdfaParams:
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
self.f_cent = (self.f_max + self.f_min) / 2
self.f_ripple_ref = params['f_ripple_ref']
# 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.")
# 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, self.update_params(**v) if isinstance(v, dict) else v)
class EdfaOperational:
default_values = {
'gain_target': None,
'delta_p': None,
'out_voa': None,
'tilt_target': 0
}
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})')

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,18 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
"""
gnpy.core.utils
===============
This module contains utility functions that are used with gnpy.
'''
"""
from csv import writer
import numpy as np
from numpy import pi, cos, sqrt, log10
from numpy import pi, cos, sqrt, log10, linspace, zeros, shape, where, logical_and, mean
from scipy import constants
from copy import deepcopy
from gnpy.core.exceptions import ConfigurationError
@@ -70,7 +70,7 @@ def arrange_frequencies(length, start, stop):
:return: an array of frequencies determined by the spacing parameter
:rtype: numpy.ndarray
"""
return np.linspace(start, stop, length)
return linspace(start, stop, length)
def lin2db(value):
@@ -107,7 +107,99 @@ def db2lin(value):
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):
"""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)
if step >= 0.01:
number = round(number / step, 0)
@@ -122,7 +214,7 @@ freq2wavelength = constants.nu2lambda
def freq2wavelength(value):
""" Converts frequency units to wavelength units.
"""Converts frequency units to wavelength units.
>>> round(freq2wavelength(191.35e12) * 1e9, 3)
1566.723
@@ -138,8 +230,33 @@ def snr_sum(snr, bw, snr_added, bw_added=12.5e9):
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):
""" deltawl2deltaf(delta_wl, wavelength):
"""deltawl2deltaf(delta_wl, wavelength):
delta_wl is BW in wavelength units
wavelength is the center wl
units for delta_wl and wavelength must be same
@@ -157,9 +274,9 @@ def deltawl2deltaf(delta_wl, wavelength):
def deltaf2deltawl(delta_f, frequency):
""" deltawl2deltaf(delta_f, frequency):
converts delta frequency to delta wavelength
units for delta_wl and wavelength must be same
"""convert delta frequency to delta wavelength
Units for delta_wl and wavelength must be same.
:param delta_f: delta frequency in same units as frequency
:param frequency: frequency BW is relevant for
@@ -174,8 +291,7 @@ def deltaf2deltawl(delta_f, frequency):
def rrc(ffs, baud_rate, alpha):
""" rrc(ffs, baud_rate, alpha): computes the root-raised cosine filter
function.
"""compute the root-raised cosine filter function
:param ffs: A numpy array of frequencies
:param baud_rate: The Baud Rate of the System
@@ -190,18 +306,18 @@ def rrc(ffs, baud_rate, alpha):
Ts = 1 / baud_rate
l_lim = (1 - alpha) / (2 * Ts)
r_lim = (1 + alpha) / (2 * Ts)
hf = np.zeros(np.shape(ffs))
slope_inds = np.where(
np.logical_and(np.abs(ffs) > l_lim, np.abs(ffs) < r_lim))
hf = zeros(shape(ffs))
slope_inds = where(
logical_and(abs(ffs) > l_lim, abs(ffs) < r_lim))
hf[slope_inds] = 0.5 * (1 + cos((pi * Ts / alpha) *
(np.abs(ffs[slope_inds]) - l_lim)))
p_inds = np.where(np.logical_and(np.abs(ffs) > 0, np.abs(ffs) < l_lim))
(abs(ffs[slope_inds]) - l_lim)))
p_inds = where(logical_and(abs(ffs) > 0, abs(ffs) < l_lim))
hf[p_inds] = 1
return sqrt(hf)
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': []}}}
>>> d2 = {'params': {'target_pch_out_db': -20}}
@@ -296,3 +412,43 @@ def convert_length(value, units):
return value * 1e3
else:
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]

View File

@@ -1,13 +1,13 @@
{
"nf_fit_coeff": [
"nf_fit_coeff": [
0.0008,
0.0272,
-0.2249,
6.4902
],
"f_min": 191.35e12,
"f_max": 196.1e12,
"nf_ripple": [
],
"f_min": 191.4e12,
"f_max": 196.1e12,
"nf_ripple": [
0.0,
0.0,
0.0,
@@ -58,103 +58,103 @@
0.0
],
"gain_ripple": [
0.15017064489112,
0.14157768006701,
0.00223094639866,
-0.06701528475711,
-0.05982935510889,
-0.01028161641541,
0.02740682579566,
0.02795958961474,
0.00107516750419,
-0.02199015912898,
-0.00877407872698,
0.0453465242881,
0.1204721524288,
0.18936662479061,
0.23826109715241,
0.26956762981574,
0.27836159966498,
0.26941687604691,
0.23579878559464,
0.18147717755444,
0.1191656197655,
0.05921587102177,
0.01509526800668,
-0.01053287269681,
-0.02475397822447,
-0.01847257118928,
-0.00420121440538,
0.01584903685091,
0.0399193886097,
0.04494451423784,
0.04961788107202,
0.03378873534338,
0.01027114740367,
-0.01319618927973,
-0.04962835008375,
-0.0765630234506,
-0.10606051088777,
-0.13550774706866,
-0.15460322445561,
-0.17113588777219,
-0.18053287269681,
-0.18324644053602,
-0.19440221943049,
-0.20897508375209,
-0.23575900335007,
-0.25188965661642,
-0.15656302345061,
-0.22244242043552,
-0.15656302345061
-0.25188965661642,
-0.23575900335007,
-0.20897508375209,
-0.19440221943049,
-0.18324644053602,
-0.18053287269681,
-0.17113588777219,
-0.15460322445561,
-0.13550774706866,
-0.10606051088777,
-0.0765630234506,
-0.04962835008375,
-0.01319618927973,
0.01027114740367,
0.03378873534338,
0.04961788107202,
0.04494451423784,
0.0399193886097,
0.01584903685091,
-0.00420121440538,
-0.01847257118928,
-0.02475397822447,
-0.01053287269681,
0.01509526800668,
0.05921587102177,
0.1191656197655,
0.18147717755444,
0.23579878559464,
0.26941687604691,
0.27836159966498,
0.26956762981574,
0.23826109715241,
0.18936662479061,
0.1204721524288,
0.0453465242881,
-0.00877407872698,
-0.02199015912898,
0.00107516750419,
0.02795958961474,
0.02740682579566,
-0.01028161641541,
-0.05982935510889,
-0.06701528475711,
0.00223094639866,
0.14157768006701,
0.15017064489112
],
"dgt": [
2.4553191172498,
2.44342862248888,
2.41879254989742,
2.38192717604575,
2.33147727493671,
2.26678136721453,
2.19013043016015,
2.10336369905543,
2.01414465424155,
1.92915262384742,
1.85543800978691,
1.79748596476494,
1.75428006928365,
1.72461030013125,
1.70379790088896,
1.68845480656382,
1.6761448370895,
1.66286684904577,
1.64799163036252,
1.63068023161292,
1.61073904908309,
1.58973304612691,
1.56750088631614,
1.54578500307573,
1.5242627235492,
1.50335352244996,
1.48420288841848,
1.46637521309853,
1.44977369463316,
1.43476940680732,
1.42089447397912,
1.40864903907609,
1.3966294751726,
1.38430337205545,
1.3710092503689,
1.35690844654118,
1.3405812000038,
1.32210817897091,
1.30069883494415,
1.27657903892303,
1.24931318255134,
1.21911100318577,
1.18632744096844,
1.15209185089701,
1.11575888725852,
1.07773189112355,
1.0,
1.03941448941778,
1.0
1.07773189112355,
1.11575888725852,
1.15209185089701,
1.18632744096844,
1.21911100318577,
1.24931318255134,
1.27657903892303,
1.30069883494415,
1.32210817897091,
1.3405812000038,
1.35690844654118,
1.3710092503689,
1.38430337205545,
1.3966294751726,
1.40864903907609,
1.42089447397912,
1.43476940680732,
1.44977369463316,
1.46637521309853,
1.48420288841848,
1.50335352244996,
1.5242627235492,
1.54578500307573,
1.56750088631614,
1.58973304612691,
1.61073904908309,
1.63068023161292,
1.64799163036252,
1.66286684904577,
1.6761448370895,
1.68845480656382,
1.70379790088896,
1.72461030013125,
1.75428006928365,
1.79748596476494,
1.85543800978691,
1.92915262384742,
2.01414465424155,
2.10336369905543,
2.19013043016015,
2.26678136721453,
2.33147727493671,
2.38192717604575,
2.41879254989742,
2.44342862248888,
2.4553191172498
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -6,101 +6,101 @@
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.0,
1.017807767853702,
1.0
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

@@ -227,7 +227,7 @@ In an opensource and multi-vendor environnement, it is needed to support differe
.. code-block:: json-object
"Edfa":[{
"type_variety": "low_noise",
"type_variety": "openroadm_ila_low_noise",
"type_def": "openroadm",
"gain_flatmax": 27,
"gain_min": 12,

View File

@@ -29,24 +29,58 @@
"allowed_for_design": false
},
{
"type_variety": "low_noise",
"type_variety": "openroadm_ila_low_noise",
"type_def": "openroadm",
"gain_flatmax": 27,
"gain_min": 12,
"gain_min": 0,
"p_max": 22,
"nf_coef": [-8.104e-4,-6.221e-2,-5.889e-1,37.62],
"allowed_for_design": false
},
{
"type_variety": "standard",
"type_variety": "openroadm_ila_standard",
"type_def": "openroadm",
"gain_flatmax": 27,
"gain_min": 12,
"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",
"type_def": "openroadm_preamp",
"gain_flatmax": 27,
"gain_min": 0,
"p_max": 22,
"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_def": "openroadm_booster",
"gain_flatmax": 32,
"gain_min": 0,
"p_max": 22,
"allowed_for_design": false
},
{
"type_variety": "std_high_gain",
"type_def": "variable_gain",
"gain_flatmax": 35,
@@ -146,51 +180,27 @@
"Fiber":[{
"type_variety": "SSMF",
"dispersion": 1.67e-05,
"gamma": 0.00127,
"effective_area": 83e-12,
"pmd_coef": 1.265e-15
},
{
"type_variety": "NZDF",
"dispersion": 0.5e-05,
"gamma": 0.00146,
"effective_area": 72e-12,
"pmd_coef": 1.265e-15
},
{
"type_variety": "LOF",
"dispersion": 2.2e-05,
"gamma": 0.000843,
"effective_area": 125e-12,
"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
]
}
"effective_area": 83e-12,
"pmd_coef": 1.265e-15
}
],
"Span":[{
@@ -211,6 +221,7 @@
"target_pch_out_db": -20,
"add_drop_osnr": 38,
"pmd": 0,
"pdl": 0,
"restrictions": {
"preamp_variety_list":[],
"booster_variety_list":[]

View File

@@ -0,0 +1,349 @@
{
"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,409 @@
{
"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,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

@@ -624,6 +624,70 @@
"con_out": null
}
},
{
"uid": "west edfa in Quimper",
"metadata": {
"location": {
"city": "Quimper",
"region": "RLD",
"latitude": 1.0,
"longitude": 1.0
}
},
"type": "Edfa",
"operational": {
"gain_target": null,
"tilt_target": 0
}
},
{
"uid": "west edfa in Ploermel",
"metadata": {
"location": {
"city": "Ploermel",
"region": "RLD",
"latitude": 1.0,
"longitude": 2.0
}
},
"type": "Edfa",
"operational": {
"gain_target": null,
"tilt_target": 0
}
},
{
"uid": "east edfa in Quimper",
"metadata": {
"location": {
"city": "Quimper",
"region": "RLD",
"latitude": 1.0,
"longitude": 1.0
}
},
"type": "Edfa",
"operational": {
"gain_target": null,
"tilt_target": 0
}
},
{
"uid": "east edfa in Ploermel",
"metadata": {
"location": {
"city": "Ploermel",
"region": "RLD",
"latitude": 1.0,
"longitude": 2.0
}
},
"type": "Edfa",
"operational": {
"gain_target": null,
"tilt_target": 0
}
},
{
"uid": "east edfa in Lannion_CAS to Corlay",
"metadata": {
@@ -635,7 +699,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -643,6 +707,21 @@
"out_voa": null
}
},
{
"uid": "east edfa in Lorient_KMA to Vannes_KBE",
"metadata": {
"location": {
"city": "Lorient_KMA",
"region": "RLD",
"latitude": 2.0,
"longitude": 3.0
}
},
"type": "Fused",
"params": {
"loss": 0
}
},
{
"uid": "east edfa in Lannion_CAS to Stbrieuc",
"metadata": {
@@ -654,7 +733,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -692,7 +771,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -711,7 +790,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -730,7 +809,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -749,7 +828,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -768,7 +847,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -787,7 +866,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_high_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -882,7 +961,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_high_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -901,7 +980,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -946,21 +1025,6 @@
"tilt_target": 0,
"out_voa": null
}
},
{
"uid": "east edfa in Lorient_KMA to Vannes_KBE",
"metadata": {
"location": {
"city": "Lorient_KMA",
"region": "RLD",
"latitude": 2.0,
"longitude": 3.0
}
},
"type": "Fused",
"params": {
"loss": 0
}
}
],
"connections": [
@@ -1190,18 +1254,34 @@
},
{
"from_node": "fiber (Brest_KLA → Quimper)-",
"to_node": "west edfa in Quimper"
},
{
"from_node": "west edfa in Quimper",
"to_node": "fiber (Quimper → Lorient_KMA)-"
},
{
"from_node": "fiber (Lorient_KMA → Quimper)-",
"to_node": "east edfa in Quimper"
},
{
"from_node": "east edfa in Quimper",
"to_node": "fiber (Quimper → Brest_KLA)-"
},
{
"from_node": "fiber (Vannes_KBE → Ploermel)-",
"to_node": "west edfa in Ploermel"
},
{
"from_node": "west edfa in Ploermel",
"to_node": "fiber (Ploermel → Rennes_STA)-"
},
{
"from_node": "fiber (Rennes_STA → Ploermel)-",
"to_node": "east edfa in Ploermel"
},
{
"from_node": "east edfa in Ploermel",
"to_node": "fiber (Ploermel → Vannes_KBE)-"
},
{
@@ -1245,4 +1325,4 @@
"to_node": "trx Brest_KLA"
}
]
}
}

View File

@@ -20,12 +20,12 @@
"temperature": 283,
"raman_pumps": [
{
"power": 200e-3,
"power": 224.403e-3,
"frequency": 205e12,
"propagation_direction": "counterprop"
},
{
"power": 206e-3,
"power": 231.135e-3,
"frequency": 201e12,
"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",
"type": "Edfa",
@@ -88,6 +103,10 @@
},
{
"from_node": "Span1",
"to_node": "Fused1"
},
{
"from_node": "Fused1",
"to_node": "Edfa1"
},
{

View File

@@ -1,14 +1,13 @@
{
"raman_parameters": {
"flag_raman": true,
"space_resolution": 10e3,
"tolerance": 1e-8
"raman_params": {
"flag": true,
"result_spatial_resolution": 10e3,
"solver_spatial_resolution": 50
},
"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]
"nli_params": {
"method": "ggn_spectrally_separated",
"dispersion_tolerance": 1,
"phase_shift_tolerance": 0.1,
"computed_channels": [1, 18, 37, 56, 75]
}
}
}

View File

@@ -1,303 +1,304 @@
{ "nf_fit_coeff": [
0.000168241,
0.0469961,
0.0359549,
5.82851
],
"f_min": 191.35e12,
"f_max": 196.1e12,
"nf_ripple": [
-0.3110761646066259,
-0.3110761646066259,
-0.31110274831665313,
-0.31419329378173544,
-0.3172854168606314,
-0.32037911876162584,
-0.3233255190215882,
-0.31624321721895354,
-0.30915729645781326,
-0.30206775396360075,
-0.2949045115165272,
-0.26632156113294336,
-0.23772399031437283,
-0.20911178784023846,
-0.18048410390821285,
-0.14379944379052215,
-0.10709599992470213,
-0.07037375788020579,
-0.03372858157230583,
-0.015660302006048,
0.0024172385953583004,
0.020504047353947653,
0.03860013139908377,
0.05670549786742816,
0.07482015390297145,
0.0838762040768461,
0.09284481475528361,
0.1018180306253394,
0.11079585523492333,
0.1020395478432815,
0.09310160456603413,
0.08415906712621996,
0.07521193198077789,
0.0676340601339394,
0.06005437964543287,
0.052470799141237305,
0.044883315610536455,
0.037679759069084225,
0.03047647598902483,
0.02326948274513522,
0.01605877647020772,
0.021248462316134083,
0.02657315875107553,
0.03190060058247842,
0.03723078993416436,
0.04256372893215024,
0.047899419704645264,
0.03915515813685565,
0.030289222542492025,
0.021418708618354456,
0.012573926129294415,
0.006240488799898697,
-9.622162373026585e-05,
-0.006436207679519103,
-0.012779471908040341,
-0.02038153550619876,
-0.027999803010447587,
-0.035622012697103154,
-0.043236398934156144,
-0.04493583574805963,
-0.04663615264317309,
-0.048337350303318156,
-0.050039429413028365,
-0.051742390657545205,
-0.05342028484370278,
-0.05254242298580185,
-0.05166410580536087,
-0.05078533294804249,
-0.04990610405914272,
-0.05409792133358102,
-0.05832916277634124,
-0.06256260169582961,
-0.06660356886269536,
-0.04779792991567815,
-0.028982516728038848,
-0.010157321677553965,
0.00861320615127981,
0.01913736978785662,
0.029667009055877668,
0.04020212822983975,
0.050742731588695494,
0.061288823415841555,
0.07184040799914815,
0.1043252636301016,
0.13687829834471027,
0.1694483010211072,
0.202035284929368,
0.23624619427167134,
0.27048596623174515,
0.30474360397422756,
0.3390191214858807,
0.36358851509924695,
0.38814205928193013,
0.41270842850729195,
0.4372876328262819,
0.4372876328262819
],
"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
],
"gain_ripple": [
0.1359703369791596,
0.11822862697916037,
0.09542181697916163,
0.06245819697916133,
0.02602813697916062,
-0.0036199830208403228,
-0.018326963020840026,
-0.0246928330208398,
-0.016792253020838643,
-0.0028138630208403015,
0.017572956979162058,
0.038328296979159404,
0.054956336979159914,
0.0670723869791594,
0.07091459697916136,
0.07094413697916124,
0.07114372697916238,
0.07533675697916209,
0.08731066697916035,
0.10313984697916112,
0.12276252697916235,
0.14239527697916188,
0.15945681697916214,
0.1739275269791598,
0.1767381569791624,
0.17037189697916233,
0.15216302697916007,
0.13114358697916018,
0.10802383697916085,
0.08548825697916129,
0.06916723697916183,
0.05848224697916038,
0.05447361697916264,
0.05154489697916276,
0.04946107697915991,
0.04717897697916129,
0.04551704697916037,
0.04467697697916151,
0.04072968697916224,
0.03285456697916089,
0.023488786979161347,
0.01659282697915998,
0.013321846979160057,
0.011234826979162449,
0.01030063697916006,
0.00936596697916059,
0.00874012697916271,
0.00842583697916055,
0.006965146979162284,
0.0040435869791615175,
0.0007104669791608842,
-0.0015763130208377163,
-0.006936193020838033,
-0.016475303020840215,
-0.028748483020837767,
-0.039618433020837784,
-0.051112303020840244,
-0.06468462302083822,
-0.07868024302083754,
-0.09101254302083817,
-0.10103437302083762,
-0.11041488302083735,
-0.11916081302083725,
-0.12789859302083784,
-0.1353792530208402,
-0.14160178302083892,
-0.1455411330208385,
-0.1484450830208388,
-0.14823350302084037,
-0.14591937302083835,
-0.1409032730208395,
-0.13525493302083902,
-0.1279646530208396,
-0.11963431302083904,
-0.11089282302084058,
-0.1027863830208382,
-0.09717347302083823,
-0.09343261302083761,
-0.0913487130208388,
-0.08906007302083907,
-0.0865687230208394,
-0.08407607302083875,
-0.07844600302084004,
-0.06968090302083851,
-0.05947139302083926,
-0.05095282302083959,
-0.042428283020839785,
-0.03218106302083967,
-0.01819858302084043,
-0.0021726530208390216,
0.01393231697916164,
0.028098946979159933,
0.040326236979161934,
0.05257029697916238,
0.06479749697916048,
0.07704745697916238
]
}
{
"nf_fit_coeff": [
0.000168241,
0.0469961,
0.0359549,
5.82851
],
"f_min": 191.35e12,
"f_max": 196.1e12,
"nf_ripple": [
0.4372876328262819,
0.4372876328262819,
0.41270842850729195,
0.38814205928193013,
0.36358851509924695,
0.3390191214858807,
0.30474360397422756,
0.27048596623174515,
0.23624619427167134,
0.202035284929368,
0.1694483010211072,
0.13687829834471027,
0.1043252636301016,
0.07184040799914815,
0.061288823415841555,
0.050742731588695494,
0.04020212822983975,
0.029667009055877668,
0.01913736978785662,
0.00861320615127981,
-0.010157321677553965,
-0.028982516728038848,
-0.04779792991567815,
-0.06660356886269536,
-0.06256260169582961,
-0.05832916277634124,
-0.05409792133358102,
-0.04990610405914272,
-0.05078533294804249,
-0.05166410580536087,
-0.05254242298580185,
-0.05342028484370278,
-0.051742390657545205,
-0.050039429413028365,
-0.048337350303318156,
-0.04663615264317309,
-0.04493583574805963,
-0.043236398934156144,
-0.035622012697103154,
-0.027999803010447587,
-0.02038153550619876,
-0.012779471908040341,
-0.006436207679519103,
-9.622162373026585e-05,
0.006240488799898697,
0.012573926129294415,
0.021418708618354456,
0.030289222542492025,
0.03915515813685565,
0.047899419704645264,
0.04256372893215024,
0.03723078993416436,
0.03190060058247842,
0.02657315875107553,
0.021248462316134083,
0.01605877647020772,
0.02326948274513522,
0.03047647598902483,
0.037679759069084225,
0.044883315610536455,
0.052470799141237305,
0.06005437964543287,
0.0676340601339394,
0.07521193198077789,
0.08415906712621996,
0.09310160456603413,
0.1020395478432815,
0.11079585523492333,
0.1018180306253394,
0.09284481475528361,
0.0838762040768461,
0.07482015390297145,
0.05670549786742816,
0.03860013139908377,
0.020504047353947653,
0.0024172385953583004,
-0.015660302006048,
-0.03372858157230583,
-0.07037375788020579,
-0.10709599992470213,
-0.14379944379052215,
-0.18048410390821285,
-0.20911178784023846,
-0.23772399031437283,
-0.26632156113294336,
-0.2949045115165272,
-0.30206775396360075,
-0.30915729645781326,
-0.31624321721895354,
-0.3233255190215882,
-0.32037911876162584,
-0.3172854168606314,
-0.31419329378173544,
-0.31110274831665313,
-0.3110761646066259,
-0.3110761646066259
],
"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
],
"gain_ripple": [
0.07704745697916238,
0.06479749697916048,
0.05257029697916238,
0.040326236979161934,
0.028098946979159933,
0.01393231697916164,
-0.0021726530208390216,
-0.01819858302084043,
-0.03218106302083967,
-0.042428283020839785,
-0.05095282302083959,
-0.05947139302083926,
-0.06968090302083851,
-0.07844600302084004,
-0.08407607302083875,
-0.0865687230208394,
-0.08906007302083907,
-0.0913487130208388,
-0.09343261302083761,
-0.09717347302083823,
-0.1027863830208382,
-0.11089282302084058,
-0.11963431302083904,
-0.1279646530208396,
-0.13525493302083902,
-0.1409032730208395,
-0.14591937302083835,
-0.14823350302084037,
-0.1484450830208388,
-0.1455411330208385,
-0.14160178302083892,
-0.1353792530208402,
-0.12789859302083784,
-0.11916081302083725,
-0.11041488302083735,
-0.10103437302083762,
-0.09101254302083817,
-0.07868024302083754,
-0.06468462302083822,
-0.051112303020840244,
-0.039618433020837784,
-0.028748483020837767,
-0.016475303020840215,
-0.006936193020838033,
-0.0015763130208377163,
0.0007104669791608842,
0.0040435869791615175,
0.006965146979162284,
0.00842583697916055,
0.00874012697916271,
0.00936596697916059,
0.01030063697916006,
0.011234826979162449,
0.013321846979160057,
0.01659282697915998,
0.023488786979161347,
0.03285456697916089,
0.04072968697916224,
0.04467697697916151,
0.04551704697916037,
0.04717897697916129,
0.04946107697915991,
0.05154489697916276,
0.05447361697916264,
0.05848224697916038,
0.06916723697916183,
0.08548825697916129,
0.10802383697916085,
0.13114358697916018,
0.15216302697916007,
0.17037189697916233,
0.1767381569791624,
0.1739275269791598,
0.15945681697916214,
0.14239527697916188,
0.12276252697916235,
0.10313984697916112,
0.08731066697916035,
0.07533675697916209,
0.07114372697916238,
0.07094413697916124,
0.07091459697916136,
0.0670723869791594,
0.054956336979159914,
0.038328296979159404,
0.017572956979162058,
-0.0028138630208403015,
-0.016792253020838643,
-0.0246928330208398,
-0.018326963020840026,
-0.0036199830208403228,
0.02602813697916062,
0.06245819697916133,
0.09542181697916163,
0.11822862697916037,
0.1359703369791596
]
}

View File

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

View File

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

View File

@@ -1,36 +1,34 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
"""
gnpy.tools.cli_examples
=======================
Common code for CLI examples
'''
"""
import argparse
from json import dumps
import logging
import os.path
import sys
from math import ceil
from numpy import linspace, mean
from pathlib import Path
import gnpy.core.ansi_escapes as ansi_escapes
from gnpy.core.elements import Transceiver, Fiber, RamanFiber
from gnpy.core.equipment import trx_mode_params
import gnpy.core.exceptions as exceptions
from gnpy.core.network import build_network
from gnpy.core.parameters import SimParams
from gnpy.core.science_utils import Simulation
from gnpy.core.utils import db2lin, lin2db, automatic_nch
from gnpy.topology.request import (ResultElement, jsontocsv, compute_path_dsjctn, requests_aggregation,
BLOCKING_NOPATH, correct_json_route_list,
deduplicate_disjunctions, compute_path_with_disjunction,
PathRequest, compute_constrained_path, propagate2)
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.json_io import (load_equipment, load_network, load_json, load_requests, save_network,
requests_from_json, disjunctions_from_json, save_json, load_initial_spectrum)
from gnpy.tools.plots import plot_baseline, plot_results
_logger = logging.getLogger(__name__)
@@ -50,7 +48,7 @@ def show_example_data_dir():
def load_common_data(equipment_filename, topology_filename, simulation_filename, save_raw_network_filename):
'''Load common configuration from JSON files'''
"""Load common configuration from JSON files"""
try:
equipment = load_equipment(equipment_filename)
@@ -58,26 +56,27 @@ def load_common_data(equipment_filename, topology_filename, simulation_filename,
if save_raw_network_filename is not None:
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}')
sim_params = SimParams(**load_json(simulation_filename)) if simulation_filename is not None else None
if not sim_params:
if not simulation_filename:
sim_params = {}
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} '
f'RamanFiber requires passing simulation params via --sim-params')
sys.exit(1)
else:
Simulation.set_params(sim_params)
sim_params = load_json(simulation_filename)
SimParams.set_params(sim_params)
except exceptions.EquipmentConfigError as e:
print(f'{ansi_escapes.red}Configuration error in the equipment library:{ansi_escapes.reset} {e}')
sys.exit(1)
except exceptions.NetworkTopologyError as e:
print(f'{ansi_escapes.red}Invalid network definition:{ansi_escapes.reset} {e}')
sys.exit(1)
except exceptions.ConfigurationError as e:
print(f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}')
sys.exit(1)
except exceptions.ParametersError as e:
print(f'{ansi_escapes.red}Simulation parameters error:{ansi_escapes.reset} {e}')
sys.exit(1)
except exceptions.ConfigurationError as e:
print(f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}')
sys.exit(1)
except exceptions.ServiceError as e:
print(f'{ansi_escapes.red}Service error:{ansi_escapes.reset} {e}')
sys.exit(1)
@@ -86,7 +85,7 @@ def load_common_data(equipment_filename, topology_filename, simulation_filename,
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):
@@ -104,6 +103,9 @@ def _add_common_options(parser: argparse.ArgumentParser, network_default: Path):
help='Save the final network as a JSON file')
parser.add_argument('--save-network-before-autodesign', type=Path, metavar=_help_fname_json,
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.')
def transmission_main_example(args=None):
@@ -113,10 +115,11 @@ def transmission_main_example(args=None):
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
_add_common_options(parser, network_default=_examples_dir / 'edfa_example_network.json')
parser.add_argument('--show-channels', action='store_true', help='Show final per-channel OSNR summary')
parser.add_argument('--show-channels', action='store_true', help='Show final per-channel OSNR and GSNR summary')
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('-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('destination', nargs='?', help='destination node')
@@ -193,15 +196,31 @@ def transmission_main_example(args=None):
if args.power:
trx_params['power'] = db2lin(float(args.power)) * 1e-3
params.update(trx_params)
initial_spectrum = None
nb_channels = automatic_nch(trx_params['f_min'], trx_params['f_max'], trx_params['spacing'])
if args.spectrum:
initial_spectrum = load_initial_spectrum(args.spectrum)
nb_channels = len(initial_spectrum)
print('User input for spectrum used for propagation instead of SI')
params['nb_channel'] = nb_channels
req = PathRequest(**params)
req.initial_spectrum = initial_spectrum
print(f'There are {nb_channels} channels propagating')
power_mode = equipment['Span']['default'].power_mode
print('\n'.join([f'Power mode is set to {power_mode}',
f'=> it can be modified in eqpt_config.json - Span']))
# Keep the reference channel for design: the one from SI, with full load same channels
pref_ch_db = lin2db(req.power * 1e3) # reference channel power / span (SL=20dB)
pref_total_db = pref_ch_db + lin2db(req.nb_channel) # reference total power / span (SL=20dB)
build_network(network, equipment, pref_ch_db, pref_total_db)
try:
build_network(network, equipment, pref_ch_db, pref_total_db, args.no_insert_edfas)
except exceptions.NetworkTopologyError as e:
print(f'{ansi_escapes.red}Invalid network definition:{ansi_escapes.reset} {e}')
sys.exit(1)
except exceptions.ConfigurationError as e:
print(f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}')
sys.exit(1)
path = compute_constrained_path(network, req)
spans = [s.params.length for s in path if isinstance(s, RamanFiber) or isinstance(s, Fiber)]
@@ -209,24 +228,27 @@ def transmission_main_example(args=None):
f'and {destination.uid}')
print(f'\nNow propagating between {source.uid} and {destination.uid}:')
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:
print('invalid power range definition in eqpt_config, should be power_range_db: [lower, upper, step]')
power_range = [0]
if not power_mode:
power_range = [0]
if power_mode:
# power cannot be changed in gain mode
power_range = [0]
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:
print('invalid power range definition in eqpt_config, should be power_range_db: [lower, upper, step]')
for dp_db in power_range:
req.power = db2lin(pref_ch_db + dp_db) * 1e-3
# if initial spectrum did not contain any power, now we need to use this one.
# note the initial power defines a differential wrt req.power so that if req.power is set to 2mW (3dBm)
# and initial spectrum was set to 0, this sets a initial per channel delta power to -3dB, so that
# whatever the equalization, -3 dB is applied on all channels (ie initial power in initial spectrum pre-empts
# "--power" option)
if power_mode:
print(f'\nPropagating with input power = {ansi_escapes.cyan}{lin2db(req.power*1e3):.2f} dBm{ansi_escapes.reset}:')
else:
print(f'\nPropagating in {ansi_escapes.cyan}gain mode{ansi_escapes.reset}: power cannot be set manually')
infos = propagate2(path, req, equipment)
infos = propagate(path, req, equipment)
if len(power_range) == 1:
for elem in path:
print(elem)
@@ -234,21 +256,16 @@ def transmission_main_example(args=None):
print(f'\nTransmission result for input power = {lin2db(req.power*1e3):.2f} dBm:')
else:
print(f'\nTransmission results:')
print(f' Final SNR total (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:
print(path[-1])
# print(f'\n !!!!!!!!!!!!!!!!! TEST POINT !!!!!!!!!!!!!!!!!!!!!')
# print(f'carriers ase output of {path[1]} =\n {list(path[1].carriers("out", "nli"))}')
# => use "in" or "out" parameter
# => use "nli" or "ase" or "signal" or "total" parameter
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}')
if args.show_channels:
print('\nThe total SNR per channel at the end of the line is:')
print('\nThe GSNR per channel at the end of the line is:')
print(
'{:>5}{:>26}{:>26}{:>28}{:>28}{:>28}' .format(
'Ch. #',
@@ -256,15 +273,15 @@ def transmission_main_example(args=None):
'Channel power (dBm)',
'OSNR ASE (signal bw, dB)',
'SNR NLI (signal bw, dB)',
'SNR total (signal bw, dB)'))
'GSNR (signal bw, dB)'))
for final_carrier, ch_osnr, ch_snr_nl, ch_snr in zip(
infos[path[-1]][1].carriers, path[-1].osnr_ase, path[-1].osnr_nli, path[-1].snr):
infos.carriers, path[-1].osnr_ase, path[-1].osnr_nli, path[-1].snr):
ch_freq = final_carrier.frequency * 1e-12
ch_power = lin2db(final_carrier.power.signal * 1e3)
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(
ch_freq, 2), round(
ch_freq, 5), round(
ch_power, 2), round(
ch_osnr, 2), round(
ch_snr_nl, 2), round(
@@ -281,7 +298,7 @@ def transmission_main_example(args=None):
print(f'\n(Invalid destination node {args.destination!r} replaced with {destination.uid})')
if args.plot:
plot_results(network, path, source, destination, infos)
plot_results(network, path, source, destination)
def _path_result_json(pathresult):
@@ -306,8 +323,7 @@ def path_requests_run(args=None):
args = parser.parse_args(args if args is not None else sys.argv[1:])
_setup_logging(args)
_logger.info(f'Computing path requests {args.service_filename} into JSON format')
print(f'{ansi_escapes.blue}Computing path requests {os.path.relpath(args.service_filename)} into JSON format{ansi_escapes.reset}')
_logger.info(f'Computing path requests {args.service_filename.name} into JSON format')
(equipment, network) = load_common_data(args.equipment, args.topology, args.sim_params, args.save_network_before_autodesign)
@@ -315,10 +331,16 @@ def path_requests_run(args=None):
# 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))
build_network(network, equipment, p_db, p_total_db)
try:
build_network(network, equipment, p_db, p_total_db, args.no_insert_edfas)
except exceptions.NetworkTopologyError as e:
print(f'{ansi_escapes.red}Invalid network definition:{ansi_escapes.reset} {e}')
sys.exit(1)
except exceptions.ConfigurationError as e:
print(f'{ansi_escapes.red}Configuration error:{ansi_escapes.reset} {e}')
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}')
@@ -376,7 +398,7 @@ def path_requests_run(args=None):
pth_assign_spectrum(pths, rqs, oms_list, reversed_pths)
print(f'{ansi_escapes.blue}Result summary{ansi_escapes.reset}')
header = ['req id', ' demand', ' snr@bandwidth A-Z (Z-A)', ' snr@0.1nm A-Z (Z-A)',
header = ['req id', ' demand', ' GSNR@bandwidth A-Z (Z-A)', ' GSNR@0.1nm A-Z (Z-A)',
' Receiver minOSNR', ' mode', ' Gbit/s', ' nb of tsp pairs',
'N,M or blocking reason']
data = []
@@ -402,7 +424,8 @@ def path_requests_run(args=None):
f'-', f'{rqs[i].blocking_reason}']
except AttributeError:
line = [f'{rqs[i].request_id}', f' {rqs[i].source} to {rqs[i].destination} : ', psnrb,
psnr, f'{rqs[i].OSNR}', f'{rqs[i].tsp_mode}', f'{round(rqs[i].path_bandwidth * 1e-9,2)}',
psnr, f'{rqs[i].OSNR + equipment["SI"]["default"].sys_margins}',
f'{rqs[i].tsp_mode}', f'{round(rqs[i].path_bandwidth * 1e-9,2)}',
f'{ceil(rqs[i].path_bandwidth / rqs[i].bit_rate) }', f'({rqs[i].N},{rqs[i].M})']
data.append(line)
@@ -414,7 +437,7 @@ def path_requests_run(args=None):
secondcol = ''.join(row[1].ljust(secondcol_width))
remainingcols = ''.join(word.center(col_width, ' ') for word in row[2:])
print(f'{firstcol} {secondcol} {remainingcols}')
print(f'{ansi_escapes.yellow}Result summary shows mean SNR and OSNR (average over all channels){ansi_escapes.reset}')
print(f'{ansi_escapes.yellow}Result summary shows mean GSNR and OSNR (average over all channels){ansi_escapes.reset}')
if args.output:
result = []

View File

@@ -20,20 +20,23 @@ 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.
"""
from sys import exit
from xlrd import open_workbook
from logging import getLogger
from argparse import ArgumentParser
from collections import namedtuple, Counter, defaultdict
from itertools import chain
from json import dumps
from pathlib import Path
from copy import copy
from gnpy.core import ansi_escapes
from gnpy.core.utils import silent_remove
from gnpy.core.exceptions import NetworkTopologyError
from gnpy.core.elements import Edfa, Fused, Fiber
_logger = getLogger(__name__)
def all_rows(sh, start=0):
return (sh.row(x) for x in range(start, sh.nrows))
@@ -124,6 +127,23 @@ class Eqpt(object):
}
class Roadm(object):
def __init__(self, **kwargs):
super(Roadm, self).__init__()
self.update_attr(kwargs)
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():
v = clean_kwargs.get(k, v)
setattr(self, k, v)
default_values = {'from_node': '',
'to_node': '',
'target_pch_out_db': None
}
def read_header(my_sheet, line, slice_):
""" return the list of headers !:= ''
header_i = [(header, header_column_index), ...]
@@ -167,18 +187,18 @@ 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)
iteration += 1
if slice_out == (-1, -1):
msg = f'missing header {h0}'
if h0 in ('east', 'Node A', 'Node Z', 'City'):
print(f'{ansi_escapes.red}CRITICAL{ansi_escapes.reset}: missing _{h0}_ header: EXECUTION ENDS')
exit()
raise NetworkTopologyError(msg)
else:
print(f'missing header {h0}')
_logger.warning(msg)
elif not isinstance(input_headers_dict[h0], dict):
headers[slice_out[0]] = input_headers_dict[h0]
else:
headers = parse_headers(my_sheet, input_headers_dict[h0], headers, start_line + 1, slice_out)
if headers == {}:
print(f'{ansi_escapes.red}CRITICAL ERROR{ansi_escapes.reset}: could not find any header to read _ ABORT')
exit()
msg = 'CRITICAL ERROR: could not find any header to read _ ABORT'
raise NetworkTopologyError(msg)
return headers
@@ -193,40 +213,86 @@ def parse_sheet(my_sheet, input_headers_dict, header_line, start_line, column):
yield parse_row(row[0: column], headers)
def _format_items(items):
return '\n'.join(f' - {item}' for item in items)
def sanity_check(nodes, links, nodes_by_city, links_by_city, eqpts_by_city):
duplicate_links = []
for l1 in links:
for l2 in 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 \
\nthe 1st duplicate link will be removed but you should check Links sheet input')
duplicate_links.append(l1)
for l in duplicate_links:
links.remove(l)
try:
test_nodes = [n for n in nodes_by_city if n not in links_by_city]
test_links = [n for n in links_by_city if n not in nodes_by_city]
test_eqpts = [n for n in eqpts_by_city if n not in nodes_by_city]
assert (test_nodes == [] or test_nodes == [''])\
and (test_links == [] or test_links == [''])\
and (test_eqpts == [] or test_eqpts == [''])
except AssertionError:
msg = f'CRITICAL error in excel input: Names in Nodes and Links sheets do no match, check:\
\n{test_nodes} in Nodes sheet\
\n{test_links} in Links sheet\
\n{test_eqpts} in Eqpt sheet'
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]
if unreferenced_nodes:
msg = 'XLS error: The following nodes are not ' \
+ 'referenced from the Links sheet. ' \
+ 'If unused, remove them from the Nodes sheet:\n' \
+ _format_items(unreferenced_nodes)
raise NetworkTopologyError(msg)
# 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_to = [n.to_city for destinations in eqpts_by_city.values()
for n in destinations if n.to_city not in nodes_by_city]
wrong_eqpt = wrong_eqpt_from + wrong_eqpt_to
if wrong_eqpt:
msg = 'XLS error: ' \
+ 'The Eqpt sheet refers to nodes that ' \
+ 'are not defined in the Nodes sheet:\n'\
+ _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():
if nodes_by_city[city].node_type.lower() == 'ila' and len(link) != 2:
# wrong input: ILA sites can only be Degree 2
# => correct to make it a ROADM and remove entry in links_by_city
# TODO: put in log rather than print
print(f'invalid node type ({nodes_by_city[city].node_type})\
specified in {city}, replaced by ROADM')
_logger.warning(f'invalid node type ({nodes_by_city[city].node_type}) '
+ f'specified in {city}, replaced by ROADM')
nodes_by_city[city].node_type = 'ROADM'
for n in nodes:
if n.city == city:
@@ -234,8 +300,98 @@ def sanity_check(nodes, links, nodes_by_city, links_by_city, eqpts_by_city):
return nodes, links
def create_roadm_element(node, roadms_by_city):
""" create the json element for a roadm node, including the different cases:
- if there are restrictions
- if there are per degree target power defined on a direction
direction is defined by the booster name, so that booster must also be created in eqpt sheet
if the direction is defined in roadm
"""
roadm = {'uid': f'roadm {node.city}'}
if node.preamp_restriction != '' or node.booster_restriction != '':
roadm['params'] = {
'restrictions': {
'preamp_variety_list': silent_remove(node.preamp_restriction.split(' | '), ''),
'booster_variety_list': silent_remove(node.booster_restriction.split(' | '), '')}
}
if node.city in roadms_by_city.keys():
if 'params' not in roadm.keys():
roadm['params'] = {}
roadm['params']['per_degree_pch_out_db'] = {}
for elem in roadms_by_city[node.city]:
to_node = f'east edfa in {node.city} to {elem.to_node}'
if elem.target_pch_out_db is not None:
roadm['params']['per_degree_pch_out_db'][to_node] = elem.target_pch_out_db
roadm['metadata'] = {'location': {'city': node.city,
'region': node.region,
'latitude': node.latitude,
'longitude': node.longitude}}
roadm['type'] = 'Roadm'
return roadm
def create_east_eqpt_element(node):
""" create amplifiers json elements for the east direction.
this includes the case where the case of a fused element defined instead of an
ILA in eqpt sheet
"""
eqpt = {'uid': f'east edfa in {node.from_city} to {node.to_city}',
'metadata': {'location': {'city': nodes_by_city[node.from_city].city,
'region': nodes_by_city[node.from_city].region,
'latitude': nodes_by_city[node.from_city].latitude,
'longitude': nodes_by_city[node.from_city].longitude}}}
if node.east_amp_type.lower() != '' and node.east_amp_type.lower() != 'fused':
eqpt['type'] = 'Edfa'
eqpt['type_variety'] = f'{node.east_amp_type}'
eqpt['operational'] = {'gain_target': node.east_amp_gain,
'delta_p': node.east_amp_dp,
'tilt_target': node.east_tilt,
'out_voa': node.east_att_out}
elif node.east_amp_type.lower() == '':
eqpt['type'] = 'Edfa'
eqpt['operational'] = {'gain_target': node.east_amp_gain,
'delta_p': node.east_amp_dp,
'tilt_target': node.east_tilt,
'out_voa': node.east_att_out}
elif node.east_amp_type.lower() == 'fused':
# fused edfa variety is a hack to indicate that there should not be
# booster amplifier out the roadm.
# If user specifies ILA in Nodes sheet and fused in Eqpt sheet, then assumes that
# this is a fused nodes.
eqpt['type'] = 'Fused'
eqpt['params'] = {'loss': 0}
return eqpt
def create_west_eqpt_element(node):
""" create amplifiers json elements for the west direction.
this includes the case where the case of a fused element defined instead of an
ILA in eqpt sheet
"""
eqpt = {'uid': f'west edfa in {node.from_city} to {node.to_city}',
'metadata': {'location': {'city': nodes_by_city[node.from_city].city,
'region': nodes_by_city[node.from_city].region,
'latitude': nodes_by_city[node.from_city].latitude,
'longitude': nodes_by_city[node.from_city].longitude}},
'type': 'Edfa'}
if node.west_amp_type.lower() != '' and node.west_amp_type.lower() != 'fused':
eqpt['type_variety'] = f'{node.west_amp_type}'
eqpt['operational'] = {'gain_target': node.west_amp_gain,
'delta_p': node.west_amp_dp,
'tilt_target': node.west_tilt,
'out_voa': node.west_att_out}
elif node.west_amp_type.lower() == '':
eqpt['operational'] = {'gain_target': node.west_amp_gain,
'delta_p': node.west_amp_dp,
'tilt_target': node.west_tilt,
'out_voa': node.west_att_out}
elif node.west_amp_type.lower() == 'fused':
eqpt['type'] = 'Fused'
eqpt['params'] = {'loss': 0}
return eqpt
def xls_to_json_data(input_filename, filter_region=[]):
nodes, links, eqpts = parse_excel(input_filename)
nodes, links, eqpts, roadms = parse_excel(input_filename)
if filter_region:
nodes = [n for n in nodes if n.region.lower() in filter_region]
cities = {n.city for n in nodes}
@@ -258,6 +414,10 @@ def xls_to_json_data(input_filename, filter_region=[]):
for eqpt in eqpts:
eqpts_by_city[eqpt.from_city].append(eqpt)
roadms_by_city = defaultdict(list)
for roadm in roadms:
roadms_by_city[roadm.from_node].append(roadm)
nodes, links = sanity_check(nodes, links, nodes_by_city, links_by_city, eqpts_by_city)
return {
@@ -269,28 +429,8 @@ def xls_to_json_data(input_filename, filter_region=[]):
'longitude': x.longitude}},
'type': 'Transceiver'}
for x in nodes_by_city.values() if x.node_type.lower() == 'roadm'] +
[{'uid': f'roadm {x.city}',
'metadata': {'location': {'city': x.city,
'region': x.region,
'latitude': x.latitude,
'longitude': x.longitude}},
'type': 'Roadm'}
for x in nodes_by_city.values() if x.node_type.lower() == 'roadm'
and x.booster_restriction == '' and x.preamp_restriction == ''] +
[{'uid': f'roadm {x.city}',
'params': {
'restrictions': {
'preamp_variety_list': silent_remove(x.preamp_restriction.split(' | '), ''),
'booster_variety_list': silent_remove(x.booster_restriction.split(' | '), '')
}
},
'metadata': {'location': {'city': x.city,
'region': x.region,
'latitude': x.latitude,
'longitude': x.longitude}},
'type': 'Roadm'}
for x in nodes_by_city.values() if x.node_type.lower() == 'roadm' and
(x.booster_restriction != '' or x.preamp_restriction != '')] +
[create_roadm_element(x, roadms_by_city)
for x in nodes_by_city.values() if x.node_type.lower() == 'roadm'] +
[{'uid': f'west fused spans in {x.city}',
'metadata': {'location': {'city': x.city,
'region': x.region,
@@ -327,58 +467,27 @@ def xls_to_json_data(input_filename, filter_region=[]):
'loss_coef': x.west_lineic,
'con_in': x.west_con_in,
'con_out': x.west_con_out}
} # missing ILA construction
for x in links] +
[{'uid': f'east edfa in {e.from_city} to {e.to_city}',
'metadata': {'location': {'city': nodes_by_city[e.from_city].city,
'region': nodes_by_city[e.from_city].region,
'latitude': nodes_by_city[e.from_city].latitude,
'longitude': nodes_by_city[e.from_city].longitude}},
} for x in links] +
[{'uid': f'west edfa in {x.city}',
'metadata': {'location': {'city': x.city,
'region': x.region,
'latitude': x.latitude,
'longitude': x.longitude}},
'type': 'Edfa',
'type_variety': e.east_amp_type,
'operational': {'gain_target': e.east_amp_gain,
'delta_p': e.east_amp_dp,
'tilt_target': e.east_tilt,
'out_voa': e.east_att_out}
}
for e in eqpts if (e.east_amp_type.lower() != '' and \
e.east_amp_type.lower() != 'fused')] +
[{'uid': f'west edfa in {e.from_city} to {e.to_city}',
'metadata': {'location': {'city': nodes_by_city[e.from_city].city,
'region': nodes_by_city[e.from_city].region,
'latitude': nodes_by_city[e.from_city].latitude,
'longitude': nodes_by_city[e.from_city].longitude}},
'operational': {'gain_target': None,
'tilt_target': 0}
} 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}',
'metadata': {'location': {'city': x.city,
'region': x.region,
'latitude': x.latitude,
'longitude': x.longitude}},
'type': 'Edfa',
'type_variety': e.west_amp_type,
'operational': {'gain_target': e.west_amp_gain,
'delta_p': e.west_amp_dp,
'tilt_target': e.west_tilt,
'out_voa': e.west_att_out}
}
for e in eqpts if (e.west_amp_type.lower() != '' and \
e.west_amp_type.lower() != 'fused')] +
# fused edfa variety is a hack to indicate that there should not be
# booster amplifier out the roadm.
# If user specifies ILA in Nodes sheet and fused in Eqpt sheet, then assumes that
# this is a fused nodes.
[{'uid': f'east edfa in {e.from_city} to {e.to_city}',
'metadata': {'location': {'city': nodes_by_city[e.from_city].city,
'region': nodes_by_city[e.from_city].region,
'latitude': nodes_by_city[e.from_city].latitude,
'longitude': nodes_by_city[e.from_city].longitude}},
'type': 'Fused',
'params': {'loss': 0}
}
for e in eqpts if e.east_amp_type.lower() == 'fused'] +
[{'uid': f'west edfa in {e.from_city} to {e.to_city}',
'metadata': {'location': {'city': nodes_by_city[e.from_city].city,
'region': nodes_by_city[e.from_city].region,
'latitude': nodes_by_city[e.from_city].latitude,
'longitude': nodes_by_city[e.from_city].longitude}},
'type': 'Fused',
'params': {'loss': 0}
}
for e in eqpts if e.west_amp_type.lower() == 'fused'],
'operational': {'gain_target': None,
'tilt_target': 0}
} 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_west_eqpt_element(e) for e in eqpts],
'connections':
list(chain.from_iterable([eqpt_connection_by_city(n.city)
for n in nodes]))
@@ -399,6 +508,7 @@ def convert_file(input_filename, filter_region=[], output_json_file_name=None):
output_json_file_name = input_filename.with_suffix('.json')
with open(output_json_file_name, 'w', encoding='utf-8') as edfa_json_file:
edfa_json_file.write(dumps(data, indent=2, ensure_ascii=False))
edfa_json_file.write('\n') # add end of file newline because json dumps does not.
return output_json_file_name
@@ -407,7 +517,7 @@ def corresp_names(input_filename, network):
and names used in the json, and created by the autodesign.
All names are listed
"""
nodes, links, eqpts = parse_excel(input_filename)
nodes, links, eqpts, roadms = parse_excel(input_filename)
fused = [n.uid for n in network.nodes() if isinstance(n, Fused)]
ila = [n.uid for n in network.nodes() if isinstance(n, Edfa)]
@@ -435,17 +545,15 @@ def corresp_names(input_filename, network):
# build corresp ila based on eqpt sheet
# 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 e.east_amp_type.lower() != '' and
f'east edfa in {e.from_city} to {e.to_city}' in ila}
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:
if my_e.west_amp_type.lower() != '':
name = f'west edfa in {my_e.from_city} to {my_e.to_city}'
if name in ila:
if my_e.from_city in corresp_ila.keys():
corresp_ila[my_e.from_city].append(name)
else:
corresp_ila[my_e.from_city] = [name]
name = f'west edfa in {my_e.from_city} to {my_e.to_city}'
if name in ila:
if my_e.from_city in corresp_ila.keys():
corresp_ila[my_e.from_city].append(name)
else:
corresp_ila[my_e.from_city] = [name]
# complete with potential autodesign names: amplifiers
for my_l in links:
name = f'Edfa0_fiber ({my_l.to_city} \u2192 {my_l.from_city})-{my_l.west_cable}'
@@ -466,7 +574,6 @@ def corresp_names(input_filename, network):
corresp_ila[my_l.to_city].append(name)
else:
corresp_ila[my_l.to_city] = [name]
# merge fused with ila:
for key, val in corresp_fused.items():
if key in corresp_ila.keys():
@@ -531,6 +638,10 @@ def parse_excel(input_filename):
'att_out': 'west_att_out'
}
}
roadm_headers = {'Node A': 'from_node',
'Node Z': 'to_node',
'per degree target power (dBm)': 'target_pch_out_db'
}
with open_workbook(input_filename) as wb:
nodes_sheet = wb.sheet_by_name('Nodes')
@@ -540,6 +651,11 @@ def parse_excel(input_filename):
except Exception:
# eqpt_sheet is optional
eqpt_sheet = None
try:
roadm_sheet = wb.sheet_by_name('Roadms')
except Exception:
# roadm_sheet is optional
roadm_sheet = None
nodes = []
for node in parse_sheet(nodes_sheet, node_headers, NODES_LINE, NODES_LINE + 1, NODES_COLUMN):
@@ -558,18 +674,29 @@ def parse_excel(input_filename):
for eqpt in parse_sheet(eqpt_sheet, eqpt_headers, EQPTS_LINE, EQPTS_LINE + 2, EQPTS_COLUMN):
eqpts.append(Eqpt(**eqpt))
roadms = []
if roadm_sheet is not None:
for roadm in parse_sheet(roadm_sheet, roadm_headers, ROADMS_LINE, ROADMS_LINE+2, ROADMS_COLUMN):
roadms.append(Roadm(**roadm))
# sanity check
all_cities = Counter(n.city for n in 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 = []
for lnk in links:
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])
if bad_links:
raise NetworkTopologyError(f'Bad link(s): {bad_links}.')
return nodes, links, eqpts
if bad_links:
msg = 'XLS error: ' \
+ 'The Links sheet references nodes that ' \
+ 'are not defined in the Nodes sheet:\n' \
+ _format_items(f'{item[0]} -> {item[1]}' for item in bad_links)
raise NetworkTopologyError(msg)
return nodes, links, eqpts, roadms
def eqpt_connection_by_city(city_name):
@@ -609,20 +736,18 @@ def connect_eqpt(from_, in_, to_):
def eqpt_in_city_to_city(in_city, to_city, direction='east'):
rev_direction = 'west' if direction == 'east' else 'east'
amp_direction = f'{direction}_amp_type'
amp_rev_direction = f'{rev_direction}_amp_type'
return_eqpt = ''
if in_city in eqpts_by_city:
for e in eqpts_by_city[in_city]:
if nodes_by_city[in_city].node_type.lower() == 'roadm':
if e.to_city == to_city and getattr(e, amp_direction) != '':
if e.to_city == to_city:
return_eqpt = f'{direction} edfa in {e.from_city} to {e.to_city}'
elif nodes_by_city[in_city].node_type.lower() == 'ila':
if e.to_city != to_city:
direction = rev_direction
amp_direction = amp_rev_direction
if getattr(e, amp_direction) != '':
return_eqpt = f'{direction} edfa in {e.from_city} to {e.to_city}'
return_eqpt = f'{direction} edfa in {e.from_city} to {e.to_city}'
elif nodes_by_city[in_city].node_type.lower() == 'ila':
return_eqpt = f'{direction} edfa in {in_city}'
if nodes_by_city[in_city].node_type.lower() == 'fused':
return_eqpt = f'{direction} fused spans in {in_city}'
return return_eqpt
@@ -729,6 +854,8 @@ LINKS_COLUMN = 16
LINKS_LINE = 3
EQPTS_LINE = 3
EQPTS_COLUMN = 14
ROADMS_LINE = 3
ROADMS_COLUMN = 3
def _do_convert():

View File

@@ -1,50 +1,60 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
"""
gnpy.tools.json_io
==================
Loading and saving data from JSON files in GNPy's internal data format
'''
"""
from networkx import DiGraph
from logging import getLogger
from pathlib import Path
import json
from collections import namedtuple
from gnpy.core import ansi_escapes, elements
from numpy import arange
from gnpy.core import elements
from gnpy.core.equipment import trx_mode_params
from gnpy.core.exceptions import ConfigurationError, EquipmentConfigError, NetworkTopologyError, ServiceError
from gnpy.core.science_utils import estimate_nf_model
from gnpy.core.info import Carrier
from gnpy.core.utils import automatic_nch, automatic_fmax, merge_amplifier_restrictions
from gnpy.topology.request import PathRequest, Disjunction
from gnpy.core.parameters import DEFAULT_RAMAN_COEFFICIENT
from gnpy.topology.request import PathRequest, Disjunction, compute_spectrum_slot_vs_bandwidth
from gnpy.topology.spectrum_assignment import mvalue_to_slots
from gnpy.tools.convert import xls_to_json_data
from gnpy.tools.service_sheet import read_service_sheet
import time
_logger = getLogger(__name__)
Model_vg = namedtuple('Model_vg', 'nf1 nf2 delta_p')
Model_vg = namedtuple('Model_vg', 'nf1 nf2 delta_p orig_nf_min orig_nf_max')
Model_fg = namedtuple('Model_fg', 'nf0')
Model_openroadm = namedtuple('Model_openroadm', 'nf_coef')
Model_openroadm_ila = namedtuple('Model_openroadm_ila', 'nf_coef')
Model_hybrid = namedtuple('Model_hybrid', 'nf_ram gain_ram edfa_variety')
Model_dual_stage = namedtuple('Model_dual_stage', 'preamp_variety booster_variety')
class Model_openroadm_preamp:
pass
class Model_openroadm_booster:
pass
class _JsonThing:
def update_attr(self, default_values, kwargs, name):
clean_kwargs = {k: v for k, v in kwargs.items() if v != ''}
for k, v in default_values.items():
setattr(self, k, clean_kwargs.get(k, v))
if k not in clean_kwargs and name != 'Amp':
print(ansi_escapes.red +
f'\n WARNING missing {k} attribute in eqpt_config.json[{name}]' +
f'\n default value is {k} = {v}' +
ansi_escapes.reset)
time.sleep(1)
msg = f'\n WARNING missing {k} attribute in eqpt_config.json[{name}]' \
+ f'\n default value is {k} = {v}'
_logger.warning(msg)
class SI(_JsonThing):
@@ -85,9 +95,9 @@ class Span(_JsonThing):
class Roadm(_JsonThing):
default_values = {
'target_pch_out_db': -17,
'add_drop_osnr': 100,
'pmd': 0,
'pdl': 0,
'restrictions': {
'preamp_variety_list': [],
'booster_variety_list': []
@@ -95,6 +105,21 @@ class Roadm(_JsonThing):
}
def __init__(self, **kwargs):
# If equalization is not defined in equipment, then raise an error.
# Only one type of equalization must be defined.
allowed_equalisations = ['target_pch_out_db', 'target_psd_out_mWperGHz', 'target_out_mWperSlotWidth']
requested_eq_mask = [eq in kwargs for eq in allowed_equalisations]
if sum(requested_eq_mask) > 1:
msg = 'Only one equalization type should be set in ROADM, found: ' \
+ ', '.join(eq for eq in allowed_equalisations if eq in kwargs)
raise EquipmentConfigError(msg)
if not any(requested_eq_mask):
msg = 'No equalization type set in ROADM'
raise EquipmentConfigError(msg)
for key in allowed_equalisations:
if key in kwargs:
setattr(self, key, kwargs[key])
break
self.update_attr(self.default_values, kwargs, 'Roadm')
@@ -107,36 +132,51 @@ class Transceiver(_JsonThing):
def __init__(self, **kwargs):
self.update_attr(self.default_values, kwargs, 'Transceiver')
for mode_params in self.mode:
penalties = mode_params.get('penalties')
mode_params['penalties'] = {}
mode_params['equalization_offset_db'] = mode_params.get('equalization_offset_db', 0)
if not penalties:
continue
for impairment in ('chromatic_dispersion', 'pmd', 'pdl'):
imp_penalties = [p for p in penalties if impairment in p]
if not imp_penalties:
continue
if all(p[impairment] > 0 for p in imp_penalties):
# make sure the list of penalty values include a proper lower boundary
# (we assume 0 penalty for 0 impairment)
imp_penalties.insert(0, {impairment: 0, 'penalty_value': 0})
# make sure the list of penalty values are sorted by impairment value
imp_penalties.sort(key=lambda i: i[impairment])
# rearrange as dict of lists instead of list of dicts
mode_params['penalties'][impairment] = {
'up_to_boundary': [p[impairment] for p in imp_penalties],
'penalty_value': [p['penalty_value'] for p in imp_penalties]
}
class Fiber(_JsonThing):
default_values = {
'type_variety': '',
'dispersion': None,
'gamma': 0,
'effective_area': None,
'pmd_coef': 0
}
def __init__(self, **kwargs):
self.update_attr(self.default_values, kwargs, 'Fiber')
self.update_attr(self.default_values, kwargs, self.__class__.__name__)
if 'gamma' in kwargs:
setattr(self, 'gamma', kwargs['gamma'])
if 'raman_efficiency' in kwargs:
raman_coefficient = kwargs['raman_efficiency']
cr = raman_coefficient.pop('cr')
raman_coefficient['g0'] = cr
raman_coefficient['reference_frequency'] = DEFAULT_RAMAN_COEFFICIENT['reference_frequency']
setattr(self, 'raman_coefficient', raman_coefficient)
class RamanFiber(_JsonThing):
default_values = {
'type_variety': '',
'dispersion': None,
'gamma': 0,
'pmd_coef': 0,
'raman_efficiency': None
}
def __init__(self, **kwargs):
self.update_attr(self.default_values, kwargs, 'RamanFiber')
for param in ('cr', 'frequency_offset'):
if param not in self.raman_efficiency:
raise EquipmentConfigError(f'RamanFiber.raman_efficiency: missing "{param}" parameter')
if self.raman_efficiency['frequency_offset'] != sorted(self.raman_efficiency['frequency_offset']):
raise EquipmentConfigError(f'RamanFiber.raman_efficiency.frequency_offset is not sorted')
class RamanFiber(Fiber):
pass
class Amp(_JsonThing):
@@ -150,13 +190,24 @@ class Amp(_JsonThing):
'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': None,
'nf_ripple': 0,
'dgt': None,
'gain_ripple': None,
'gain_ripple': 0,
'tilt_ripple': 0,
'f_ripple_ref': None,
'out_voa_auto': False,
'allowed_for_design': False,
'raman': False
'raman': False,
'pmd': 0,
'pdl': 0,
'advance_configurations_from_json': None
}
def __init__(self, **kwargs):
@@ -175,7 +226,8 @@ class Amp(_JsonThing):
try:
nf0 = kwargs.pop('nf0')
except KeyError: # nf0 is expected for a fixed gain amp
raise EquipmentConfigError(f'missing nf0 value input for amplifier: {type_variety} in equipment config')
msg = f'missing nf0 value input for amplifier: {type_variety} in equipment config'
raise EquipmentConfigError(msg)
for k in ('nf_min', 'nf_max'):
try:
del kwargs[k]
@@ -190,26 +242,34 @@ class Amp(_JsonThing):
nf_min = kwargs.pop('nf_min')
nf_max = kwargs.pop('nf_max')
except KeyError:
raise EquipmentConfigError(f'missing nf_min or nf_max value input for amplifier: {type_variety} in equipment config')
msg = f'missing nf_min or nf_max value input for amplifier: {type_variety} in equipment config'
raise EquipmentConfigError(msg)
try: # remove all remaining nf inputs
del kwargs['nf0']
except KeyError:
pass # nf0 is not needed for variable gain amp
nf1, nf2, delta_p = estimate_nf_model(type_variety, gain_min, gain_max, nf_min, nf_max)
nf_def = Model_vg(nf1, nf2, delta_p)
nf_def = Model_vg(nf1, nf2, delta_p, nf_min, nf_max)
elif type_def == 'openroadm':
try:
nf_coef = kwargs.pop('nf_coef')
except KeyError: # nf_coef is expected for openroadm amp
raise EquipmentConfigError(f'missing nf_coef input for amplifier: {type_variety} in equipment config')
nf_def = Model_openroadm(nf_coef)
nf_def = Model_openroadm_ila(nf_coef)
elif type_def == 'openroadm_preamp':
nf_def = Model_openroadm_preamp()
elif type_def == 'openroadm_booster':
nf_def = Model_openroadm_booster()
elif type_def == 'dual_stage':
try: # nf_ram and gain_ram are expected for a hybrid amp
preamp_variety = kwargs.pop('preamp_variety')
booster_variety = kwargs.pop('booster_variety')
except KeyError:
raise EquipmentConfigError(f'missing preamp/booster variety input for amplifier: {type_variety} in equipment config')
msg = f'missing preamp/booster variety input for amplifier: {type_variety} in equipment config'
raise EquipmentConfigError(msg)
dual_stage_def = Model_dual_stage(preamp_variety, booster_variety)
else:
raise EquipmentConfigError(f'Edfa type_def {type_def} does not exist')
json_data = load_json(config)
@@ -225,17 +285,87 @@ def _automatic_spacing(baud_rate):
return min((s[1] for s in spacing_list if s[0] > baud_rate), default=baud_rate * 1.2)
def _spectrum_from_json(json_data):
"""JSON_data is a list of spectrum partitions each with
{f_min, f_max, baud_rate, roll_off, delta_pdb, slot_width, tx_osnr, label}
Creates the per freq Carrier's dict.
f_min, f_max, baud_rate, slot_width and roll_off are mandatory
label, tx_osnr and delta_pdb are created if not present
label should be different for each partition
>>> json_data = {'spectrum': \
[{'f_min': 193.2e12, 'f_max': 193.4e12, 'slot_width': 50e9, 'baud_rate': 32e9, 'roll_off': 0.15, \
'delta_pdb': 1, 'tx_osnr': 45},\
{'f_min': 193.4625e12, 'f_max': 193.9875e12, 'slot_width': 75e9, 'baud_rate': 64e9, 'roll_off': 0.15},\
{'f_min': 194.075e12, 'f_max': 194.075e12, 'slot_width': 100e9, 'baud_rate': 90e9, 'roll_off': 0.15},\
{'f_min': 194.2e12, 'f_max': 194.35e12, 'slot_width': 50e9, 'baud_rate': 32e9, 'roll_off': 0.15}]}
>>> spectrum = _spectrum_from_json(json_data['spectrum'])
>>> for k, v in spectrum.items():
... print(f'{k}: {v}')
...
193200000000000.0: Carrier(delta_pdb=1, baud_rate=32000000000.0, slot_width=50000000000.0, roll_off=0.15, tx_osnr=45, label='0-32.00G')
193250000000000.0: Carrier(delta_pdb=1, baud_rate=32000000000.0, slot_width=50000000000.0, roll_off=0.15, tx_osnr=45, label='0-32.00G')
193300000000000.0: Carrier(delta_pdb=1, baud_rate=32000000000.0, slot_width=50000000000.0, roll_off=0.15, tx_osnr=45, label='0-32.00G')
193350000000000.0: Carrier(delta_pdb=1, baud_rate=32000000000.0, slot_width=50000000000.0, roll_off=0.15, tx_osnr=45, label='0-32.00G')
193400000000000.0: Carrier(delta_pdb=1, baud_rate=32000000000.0, slot_width=50000000000.0, roll_off=0.15, tx_osnr=45, label='0-32.00G')
193462500000000.0: Carrier(delta_pdb=0, baud_rate=64000000000.0, slot_width=75000000000.0, roll_off=0.15, tx_osnr=40, label='1-64.00G')
193537500000000.0: Carrier(delta_pdb=0, baud_rate=64000000000.0, slot_width=75000000000.0, roll_off=0.15, tx_osnr=40, label='1-64.00G')
193612500000000.0: Carrier(delta_pdb=0, baud_rate=64000000000.0, slot_width=75000000000.0, roll_off=0.15, tx_osnr=40, label='1-64.00G')
193687500000000.0: Carrier(delta_pdb=0, baud_rate=64000000000.0, slot_width=75000000000.0, roll_off=0.15, tx_osnr=40, label='1-64.00G')
193762500000000.0: Carrier(delta_pdb=0, baud_rate=64000000000.0, slot_width=75000000000.0, roll_off=0.15, tx_osnr=40, label='1-64.00G')
193837500000000.0: Carrier(delta_pdb=0, baud_rate=64000000000.0, slot_width=75000000000.0, roll_off=0.15, tx_osnr=40, label='1-64.00G')
193912500000000.0: Carrier(delta_pdb=0, baud_rate=64000000000.0, slot_width=75000000000.0, roll_off=0.15, tx_osnr=40, label='1-64.00G')
193987500000000.0: Carrier(delta_pdb=0, baud_rate=64000000000.0, slot_width=75000000000.0, roll_off=0.15, tx_osnr=40, label='1-64.00G')
194075000000000.0: Carrier(delta_pdb=0, baud_rate=90000000000.0, slot_width=100000000000.0, roll_off=0.15, tx_osnr=40, label='2-90.00G')
194200000000000.0: Carrier(delta_pdb=0, baud_rate=32000000000.0, slot_width=50000000000.0, roll_off=0.15, tx_osnr=40, label='3-32.00G')
194250000000000.0: Carrier(delta_pdb=0, baud_rate=32000000000.0, slot_width=50000000000.0, roll_off=0.15, tx_osnr=40, label='3-32.00G')
194300000000000.0: Carrier(delta_pdb=0, baud_rate=32000000000.0, slot_width=50000000000.0, roll_off=0.15, tx_osnr=40, label='3-32.00G')
194350000000000.0: Carrier(delta_pdb=0, baud_rate=32000000000.0, slot_width=50000000000.0, roll_off=0.15, tx_osnr=40, label='3-32.00G')
"""
spectrum = {}
json_data = sorted(json_data, key=lambda x: x['f_min'])
# min freq of occupation is f_min - slot_width/2 (numbering starts at 0)
previous_part_max_freq = 0.0
for index, part in enumerate(json_data):
# default delta_pdb is 0 dB
if 'delta_pdb' not in part:
part['delta_pdb'] = 0
# add a label to the partition for the printings
if 'label' not in part:
part['label'] = f'{index}-{part["baud_rate"] * 1e-9 :.2f}G'
# default tx_osnr is set to 40 dB
if 'tx_osnr' not in part:
part['tx_osnr'] = 40
# starting freq is exactly f_min to be consistent with utils.automatic_nch
# first partition min occupation is f_min - slot_width / 2 (central_frequency is f_min)
# supposes that carriers are centered on frequency
if previous_part_max_freq > (part['f_min'] - part['slot_width'] / 2):
# check that previous part last channel does not overlap on next part first channel
# max center of the part should be below part['f_max'] and aligned on the slot_width
msg = 'Not a valid initial spectrum definition:\nprevious spectrum last carrier max occupation ' +\
f'{previous_part_max_freq * 1e-12 :.5f}GHz ' +\
'overlaps on next spectrum first carrier occupation ' +\
f'{(part["f_min"] - part["slot_width"] / 2) * 1e-12 :.5f}GHz'
raise ValueError(msg)
max_range = ((part['f_max'] - part['f_min']) // part['slot_width'] + 1) * part['slot_width']
for current_freq in arange(part['f_min'],
part['f_min'] + max_range,
part['slot_width']):
spectrum[current_freq] = Carrier(delta_pdb=part['delta_pdb'], baud_rate=part['baud_rate'],
slot_width=part['slot_width'], roll_off=part['roll_off'],
tx_osnr=part['tx_osnr'], label=part['label'])
previous_part_max_freq = current_freq + part['slot_width'] / 2
return spectrum
def load_equipment(filename):
json_data = load_json(filename)
return _equipment_from_json(json_data, filename)
def _update_trx_osnr(equipment):
"""add sys_margins to all Transceivers OSNR values"""
for trx in equipment['Transceiver'].values():
for m in trx.mode:
m['OSNR'] = m['OSNR'] + equipment['SI']['default'].sys_margins
return equipment
def load_initial_spectrum(filename):
json_data = load_json(filename)
return _spectrum_from_json(json_data['spectrum'])
def _update_dual_stage(equipment):
@@ -258,9 +388,7 @@ def _update_dual_stage(equipment):
def _roadm_restrictions_sanity_check(equipment):
""" verifies that booster and preamp restrictions specified in roadm equipment are listed
in the edfa.
"""
"""verifies that booster and preamp restrictions specified in roadm equipment are listed in the edfa."""
restrictions = equipment['Roadm']['default'].restrictions['booster_variety_list'] + \
equipment['Roadm']['default'].restrictions['preamp_variety_list']
for amp_name in restrictions:
@@ -268,6 +396,21 @@ def _roadm_restrictions_sanity_check(equipment):
raise EquipmentConfigError(f'ROADM restriction {amp_name} does not refer to a defined EDFA name')
def _check_fiber_vs_raman_fiber(equipment):
"""Ensure that Fiber and RamanFiber with the same name define common properties equally"""
if 'RamanFiber' not in equipment:
return
for fiber_type in set(equipment['Fiber'].keys()) & set(equipment['RamanFiber'].keys()):
for attr in ('dispersion', 'dispersion-slope', 'effective_area', 'gamma', 'pmd-coefficient'):
fiber = equipment['Fiber'][fiber_type]
raman = equipment['RamanFiber'][fiber_type]
a = getattr(fiber, attr, None)
b = getattr(raman, attr, None)
if a != b:
raise EquipmentConfigError(f'WARNING: Fiber and RamanFiber definition of "{fiber_type}" '
f'disagrees for "{attr}": {a} != {b}')
def _equipment_from_json(json_data, filename):
"""build global dictionnary eqpt_library that stores all eqpt characteristics:
edfa type type_variety, fiber type_variety
@@ -298,7 +441,7 @@ def _equipment_from_json(json_data, filename):
equipment[key][subkey] = RamanFiber(**entry)
else:
raise EquipmentConfigError(f'Unrecognized network element type "{key}"')
equipment = _update_trx_osnr(equipment)
_check_fiber_vs_raman_fiber(equipment)
equipment = _update_dual_stage(equipment)
_roadm_restrictions_sanity_check(equipment)
return equipment
@@ -315,11 +458,11 @@ def load_network(filename, equipment):
def save_network(network: DiGraph, filename: str):
'''Dump the network into a JSON file
"""Dump the network into a JSON file
:param network: network to work on
:param filename: file to write to
'''
"""
save_json(network_to_json(network), filename)
@@ -353,14 +496,28 @@ def network_from_json(json_data, equipment):
# well, there's no variety for the 'Fused' node type
pass
elif variety in equipment[typ]:
extra_params = equipment[typ][variety]
extra_params = equipment[typ][variety].__dict__
temp = el_config.setdefault('params', {})
temp = merge_amplifier_restrictions(temp, extra_params.__dict__)
if typ == 'Roadm':
# if equalization is defined, remove default equalization from the extra_params
# If equalisation is not defined in the element config, then use the default one from equipment
# if more than one equalization was defined in element config, then raise an error
extra_params = merge_equalization(temp, extra_params)
if not extra_params:
msg = f'ROADM {el_config["uid"]}: invalid equalization settings'
raise ConfigurationError(msg)
temp = merge_amplifier_restrictions(temp, extra_params)
el_config['params'] = temp
el_config['type_variety'] = variety
elif typ in ['Edfa', 'Fiber', 'RamanFiber']: # catch it now because the code will crash later!
elif (typ in ['Fiber', 'RamanFiber']):
raise ConfigurationError(f'The {typ} of variety type {variety} was not recognized:'
'\nplease check it is properly defined in the eqpt_config json file')
elif typ == 'Edfa':
if variety in ['default', '']:
el_config['params'] = Amp.default_values
else:
raise ConfigurationError(f'The Edfa of variety type {variety} was not recognized:'
'\nplease check it is properly defined in the eqpt_config json file')
el = cls(**el_config)
g.add_node(el)
@@ -375,7 +532,8 @@ def network_from_json(json_data, equipment):
edge_length = 0.01
g.add_edge(nodes[from_node], nodes[to_node], weight=edge_length)
except KeyError:
raise NetworkTopologyError(f'can not find {from_node} or {to_node} defined in {cx}')
msg = f'can not find {from_node} or {to_node} defined in {cx}'
raise NetworkTopologyError(msg)
return g
@@ -406,15 +564,13 @@ def save_json(obj, filename):
def load_requests(filename, eqpt, bidir, network, network_filename):
""" loads the requests from a json or an excel file into a data string
"""
"""loads the requests from a json or an excel file into a data string"""
if filename.suffix.lower() in ('.xls', '.xlsx'):
_logger.info('Automatically converting requests from XLS to JSON')
try:
return convert_service_sheet(filename, eqpt, network, network_filename=network_filename, bidir=bidir)
except ServiceError as this_e:
print(f'{ansi_escapes.red}Service error:{ansi_escapes.reset} {this_e}')
exit(1)
raise ServiceError(f'Service error: {this_e}')
else:
return load_json(filename)
@@ -426,19 +582,19 @@ def requests_from_json(json_data, equipment):
for req in json_data['path-request']:
# init all params from request
params = {}
params['request_id'] = req['request-id']
params['request_id'] = f'{req["request-id"]}'
params['source'] = req['source']
params['bidir'] = req['bidirectional']
params['destination'] = req['destination']
params['trx_type'] = req['path-constraints']['te-bandwidth']['trx_type']
if 'trx_mode' in req['path-constraints']['te-bandwidth'].keys():
params['trx_mode'] = req['path-constraints']['te-bandwidth']['trx_mode']
else:
params['trx_mode'] = None
if params['trx_type'] is None:
msg = f'Request {req["request-id"]} has no transceiver type defined.'
raise ServiceError(msg)
params['trx_mode'] = req['path-constraints']['te-bandwidth'].get('trx_mode', None)
params['format'] = params['trx_mode']
params['spacing'] = req['path-constraints']['te-bandwidth']['spacing']
try:
nd_list = req['explicit-route-objects']['route-object-include-exclude']
nd_list = sorted(req['explicit-route-objects']['route-object-include-exclude'], key=lambda x: x['index'])
except KeyError:
nd_list = []
params['nodes_list'] = [n['num-unnum-hop']['node-id'] for n in nd_list]
@@ -446,9 +602,12 @@ def requests_from_json(json_data, equipment):
# recover trx physical param (baudrate, ...) from type and mode
# in trx_mode_params optical power is read from equipment['SI']['default'] and
# nb_channel is computed based on min max frequency and spacing
trx_params = trx_mode_params(equipment, params['trx_type'], params['trx_mode'], True)
try:
trx_params = trx_mode_params(equipment, params['trx_type'], params['trx_mode'], True)
except EquipmentConfigError as e:
msg = f'Equipment Config error in {req["request-id"]}: {e}'
raise EquipmentConfigError(msg) from e
params.update(trx_params)
# print(trx_params['min_spacing'])
# optical power might be set differently in the request. if it is indicated then the
# params['power'] is updated
try:
@@ -469,17 +628,13 @@ def requests_from_json(json_data, equipment):
params['nb_channel'] = automatic_nch(f_min, f_max_from_si, params['spacing'])
except KeyError:
params['nb_channel'] = automatic_nch(f_min, f_max_from_si, params['spacing'])
if 'effective-freq-slot' in req['path-constraints']['te-bandwidth']:
# temporarily reads only the first slot
params['effective_freq_slot'] = req['path-constraints']['te-bandwidth']['effective-freq-slot'][0]
else:
params['effective_freq_slot'] = None
_check_one_request(params, f_max_from_si)
params['effective_freq_slot'] = \
req['path-constraints']['te-bandwidth'].get('effective-freq-slot', [{'N': None, 'M': None}])
try:
params['path_bandwidth'] = req['path-constraints']['te-bandwidth']['path_bandwidth']
except KeyError:
pass
_check_one_request(params, f_max_from_si)
requests_list.append(PathRequest(**params))
return requests_list
@@ -488,35 +643,69 @@ def _check_one_request(params, f_max_from_si):
"""Checks that the requested parameters are consistant (spacing vs nb channel vs transponder mode...)"""
f_min = params['f_min']
f_max = params['f_max']
max_recommanded_nb_channels = automatic_nch(f_min, f_max, params['spacing'])
max_recommanded_nb_channels = automatic_nch(f_min, f_max_from_si, params['spacing'])
if params['baud_rate'] is not None:
# implicitly means that a mode is defined with min_spacing
if params['min_spacing'] > params['spacing']:
msg = f'Request {params["request_id"]} has spacing below transponder ' +\
f'{params["trx_type"]} {params["trx_mode"]} min spacing value ' +\
f'{params["min_spacing"]*1e-9}GHz.\nComputation stopped'
print(msg)
_logger.critical(msg)
raise ServiceError(msg)
if f_max > f_max_from_si:
msg = f'''Requested channel number {params["nb_channel"]}, baud rate {params["baud_rate"]} GHz
and requested spacing {params["spacing"]*1e-9}GHz is not consistent with frequency range
{f_min*1e-12} THz, {f_max*1e-12} THz, min recommanded spacing {params["min_spacing"]*1e-9}GHz.
max recommanded nb of channels is {max_recommanded_nb_channels}.'''
_logger.critical(msg)
msg = f'Requested channel number {params["nb_channel"]}, baud rate {params["baud_rate"] * 1e-9} GHz' \
+ f' and requested spacing {params["spacing"]*1e-9}GHz is not consistent with frequency range' \
+ f' {f_min*1e-12} THz, {f_max_from_si*1e-12} THz.' \
+ f' Max recommanded nb of channels is {max_recommanded_nb_channels}.'
raise ServiceError(msg)
# Transponder mode already selected; will it fit to the requested bandwidth?
if params['trx_mode'] is not None and params['effective_freq_slot'] is not None:
required_nb_of_channels, requested_m = compute_spectrum_slot_vs_bandwidth(params['path_bandwidth'],
params['spacing'],
params['bit_rate'])
_, per_channel_m = compute_spectrum_slot_vs_bandwidth(params['bit_rate'],
params['spacing'],
params['bit_rate'])
# each M should fit one or more channels if it is not None
# spectrum slots should not overlap
# resulting nb of channels should be bigger than the nb computed with path_bandwidth
# without being splitted
# TODO: elaborate a more accurate estimate with nb_wl * tx_osnr + possibly guardbands in case of
# superchannel closed packing.
nb_of_channels = 0
# order slots
slots = sorted(params['effective_freq_slot'], key=lambda x: float('inf') if x['N'] is None else x['N'])
for slot in slots:
nb_of_channels = nb_of_channels + slot['M'] // per_channel_m if slot['M'] is not None \
and nb_of_channels is not None else None
if slot['M'] is not None and slot['M'] < per_channel_m:
msg = f'Requested M {slot} number of slots for request' +\
f' {params["request_id"]} should be greater than {per_channel_m} to support request' +\
f'with {params["trx_type"]} {params["trx_mode"]}'
_logger.critical(msg)
if nb_of_channels is not None and nb_of_channels < required_nb_of_channels:
msg = f'Requested M {slots} number of slots for request {params["request_id"]} support {nb_of_channels}' +\
f' nb of channels while {required_nb_of_channels} are required to support request' +\
f' {params["path_bandwidth"] * 1e-9} Gbit/s with {params["trx_type"]} {params["trx_mode"]}'
raise ServiceError(msg)
if nb_of_channels is not None:
_, stop0n = mvalue_to_slots(slots[0]['N'], slots[0]['M'])
i = 1
while i < len(slots):
slot = slots[i]
startn, stopn = mvalue_to_slots(slot['N'], slot['M'])
if startn <= stop0n:
msg = f'Requested M {slots} for request {params["request_id"]} overlap'
raise ServiceError(msg)
_, stop0n = startn, stopn
i += 1
def disjunctions_from_json(json_data):
""" reads the disjunction requests from the json dict and create the list
of requested disjunctions for this set of requests
"""reads the disjunction requests from the json dict and create the list
of requested disjunctions for this set of requests
"""
disjunctions_list = []
try:
temp_test = json_data['synchronization']
except KeyError:
temp_test = []
if temp_test:
if 'synchronization' in json_data:
for snc in json_data['synchronization']:
params = {}
params['disjunction_id'] = snc['synchronization-id']
@@ -535,10 +724,48 @@ def convert_service_sheet(
network,
network_filename=None,
output_filename='',
bidir=False,
filter_region=None):
bidir=False):
if output_filename == '':
output_filename = f'{str(input_filename)[0:len(str(input_filename))-len(str(input_filename.suffixes[0]))]}_services.json'
data = read_service_sheet(input_filename, eqpt, network, network_filename, bidir, filter_region)
data = read_service_sheet(input_filename, eqpt, network, network_filename, bidir)
save_json(data, output_filename)
return data
def find_equalisation(params, equalization_types):
"""Find the equalization(s) defined in params. params can be a dict or a Roadm object.
>>> roadm = {'add_drop_osnr': 100, 'pmd': 1, 'pdl': 0.5,
... 'restrictions': {'preamp_variety_list': ['a'], 'booster_variety_list': ['b']},
... 'target_psd_out_mWperGHz': 4e-4}
>>> equalization_types = ['target_pch_out_db', 'target_psd_out_mWperGHz']
>>> find_equalisation(roadm, equalization_types)
{'target_pch_out_db': False, 'target_psd_out_mWperGHz': True}
"""
equalization = {e: False for e in equalization_types}
for equ in equalization_types:
if equ in params:
equalization[equ] = True
return equalization
def merge_equalization(params, extra_params):
"""params contains ROADM element config and extra_params default values from equipment library.
If equalization is not defined in ROADM element use the one defined in equipment library.
Only one type of equalization must be defined: power (target_pch_out_db) or PSD (target_psd_out_mWperGHz)
or PSW (target_out_mWperSlotWidth)
params and extra_params are dict
"""
equalization_types = ['target_pch_out_db', 'target_psd_out_mWperGHz', 'target_out_mWperSlotWidth']
roadm_equalizations = find_equalisation(params, equalization_types)
if sum(roadm_equalizations.values()) > 1:
# if ROADM config contains more than one equalization type then this is an error
return None
if sum(roadm_equalizations.values()) == 1:
# if ROADM config contains one equalization
# don't use the default equalization
return {k: v for k, v in extra_params.items() if k not in equalization_types}
if sum(roadm_equalizations.values()) == 0:
# If ROADM config doesn't contain any equalization type, keep the default one
return extra_params
return None

View File

@@ -1,62 +1,50 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
"""
gnpy.tools.plots
================
Graphs and plots usable form a CLI application
'''
Graphs and plots usable from a CLI application
"""
from matplotlib.pyplot import show, axis, figure, title, text
from networkx import draw_networkx_nodes, draw_networkx_edges, draw_networkx_labels
from networkx import draw_networkx
from gnpy.core.elements import Transceiver
def plot_baseline(network):
edges = set(network.edges())
pos = {n: (n.lng, n.lat) for n in network.nodes()}
labels = {n: n.location.city for n in network.nodes() if isinstance(n, Transceiver)}
city_labels = set(labels.values())
for n in network.nodes():
if n.location.city and n.location.city not in city_labels:
labels[n] = n.location.city
city_labels.add(n.location.city)
label_pos = pos
def _try_city(node):
return node.location.city if node.location.city else node.uid
fig = figure()
kwargs = {'figure': fig, 'pos': pos}
plot = draw_networkx_nodes(network, nodelist=network.nodes(), node_color='#ababab', **kwargs)
draw_networkx_edges(network, edgelist=edges, edge_color='#ababab', **kwargs)
draw_networkx_labels(network, labels=labels, font_size=14, **{**kwargs, 'pos': label_pos})
def plot_baseline(network):
pos = {n: (n.lng, n.lat) for n in network.nodes()}
labels = {n: _try_city(n) for n in network.nodes() if isinstance(n, Transceiver)}
draw_networkx(network, pos=pos, node_size=50, node_color='#ababab', edge_color='#ababab',
labels=labels, font_size=14)
axis('off')
show()
def plot_results(network, path, source, destination, infos):
def plot_results(network, path, source, destination):
path_edges = set(zip(path[:-1], path[1:]))
edges = set(network.edges()) - path_edges
nodes = [n for n in network.nodes() if n not in path]
pos = {n: (n.lng, n.lat) for n in network.nodes()}
nodes = {}
nodes_by_pos = {}
for k, (x, y) in pos.items():
nodes.setdefault((round(x, 1), round(y, 1)), []).append(k)
labels = {n: n.location.city for n in network.nodes() if isinstance(n, Transceiver)}
city_labels = set(labels.values())
for n in network.nodes():
if n.location.city and n.location.city not in city_labels:
labels[n] = n.location.city
city_labels.add(n.location.city)
label_pos = pos
nodes_by_pos.setdefault((round(x, 1), round(y, 1)), []).append(k)
labels = {n: _try_city(n) for n in network.nodes() if isinstance(n, Transceiver)}
fig = figure()
kwargs = {'figure': fig, 'pos': pos}
all_nodes = [n for n in network.nodes() if n not in path]
plot = draw_networkx_nodes(network, nodelist=all_nodes, node_color='#ababab', node_size=50, **kwargs)
draw_networkx_nodes(network, nodelist=path, node_color='#ff0000', node_size=55, **kwargs)
draw_networkx_edges(network, edgelist=edges, edge_color='#ababab', **kwargs)
draw_networkx_edges(network, edgelist=path_edges, edge_color='#ff0000', **kwargs)
draw_networkx_labels(network, labels=labels, font_size=14, **{**kwargs, 'pos': label_pos})
title(f'Propagating from {source.loc.city} to {destination.loc.city}')
draw_networkx(network, pos=pos, labels=labels, font_size=14,
nodelist=nodes, node_color='#ababab', node_size=50,
edgelist=edges, edge_color='#ababab')
draw_networkx(network, pos=pos, with_labels=False,
nodelist=path, node_color='#ff0000', node_size=55,
edgelist=path_edges, edge_color='#ff0000')
title(f'Propagating from {_try_city(source)} to {_try_city(destination)}')
axis('off')
heading = 'Spectral Information\n\n'
@@ -65,7 +53,7 @@ def plot_results(network, path, source, destination, infos):
bbox={'boxstyle': 'round', 'facecolor': 'wheat', 'alpha': 0.5})
msgs = {(x, y): heading + '\n\n'.join(str(n) for n in ns if n in path)
for (x, y), ns in nodes.items()}
for (x, y), ns in nodes_by_pos.items()}
def hover(event):
if event.xdata is None or event.ydata is None:

View File

@@ -18,7 +18,6 @@ from copy import deepcopy
from gnpy.core.utils import db2lin
from gnpy.core.exceptions import ServiceError
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
SERVICES_COLUMN = 12
@@ -68,24 +67,21 @@ class Request_element(Element):
if [mode for mode in equipment['Transceiver'][Request.trx_type].mode if mode['format'] == Requestmode]:
self.mode = Requestmode
else:
msg = f'Request Id: {self.request_id} - could not find tsp : \'{Request.trx_type}\' with mode: \'{Requestmode}\' in eqpt library \nComputation stopped.'
# print(msg)
logger.critical(msg)
msg = f'Request Id: {self.request_id} - could not find tsp : \'{Request.trx_type}\' ' \
+ f'with mode: \'{Requestmode}\' in eqpt library \nComputation stopped.'
raise ServiceError(msg)
else:
Requestmode = None
self.mode = Request.mode
except KeyError:
msg = f'Request Id: {self.request_id} - could not find tsp : \'{Request.trx_type}\' with mode: \'{Request.mode}\' in eqpt library \nComputation stopped.'
# print(msg)
logger.critical(msg)
msg = f'Request Id: {self.request_id} - could not find tsp : \'{Request.trx_type}\' ' \
+ f'with mode: \'{Request.mode}\' in eqpt library \nComputation stopped.'
raise ServiceError(msg)
# excel input are in GHz and dBm
if Request.spacing is not None:
self.spacing = Request.spacing * 1e9
else:
msg = f'Request {self.request_id} missing spacing: spacing is mandatory.\ncomputation stopped'
logger.critical(msg)
raise ServiceError(msg)
if Request.power is not None:
self.power = db2lin(Request.power) * 1e-3
@@ -178,12 +174,9 @@ def read_service_sheet(
eqpt,
network,
network_filename=None,
bidir=False,
filter_region=None):
bidir=False):
""" converts a service sheet into a json structure
"""
if filter_region is None:
filter_region = []
if network_filename is None:
network_filename = input_filename
service = parse_excel(input_filename)
@@ -228,7 +221,7 @@ def parse_excel(input_filename):
def parse_service_sheet(service_sheet):
""" reads each column according to authorized fieldnames. order is not important.
"""
logger.info(f'Validating headers on {service_sheet.name!r}')
logger.debug(f'Validating headers on {service_sheet.name!r}')
# 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
header = [x.value.strip() for x in service_sheet.row(4)[0:SERVICES_COLUMN]
@@ -248,7 +241,6 @@ def parse_service_sheet(service_sheet):
service_fieldnames = [authorized_fieldnames[e] for e in header]
except KeyError:
msg = f'Malformed header on Service sheet: {header} field not in {authorized_fieldnames}'
logger.critical(msg)
raise ValueError(msg)
for row in all_rows(service_sheet, start=5):
yield Request(**parse_row(row[0:SERVICES_COLUMN], service_fieldnames))
@@ -276,15 +268,13 @@ def correct_xls_route_list(network_filename, network, pathreqlist):
for pathreq in pathreqlist:
# first check that source and dest are transceivers
if pathreq.source not in transponders:
msg = f'{ansi_escapes.red}Request: {pathreq.request_id}: could not find' +\
f' transponder source : {pathreq.source}.{ansi_escapes.reset}'
logger.critical(msg)
msg = f'Request: {pathreq.request_id}: could not find' +\
f' transponder source : {pathreq.source}.'
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)
msg = f'Request: {pathreq.request_id}: could not find' +\
f' transponder destination: {pathreq.destination}.'
raise ServiceError(msg)
# 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
@@ -336,17 +326,16 @@ def correct_xls_route_list(network_filename, network, pathreqlist):
# too much ambiguity, 'b' is an ila, its name can be:
# Edfa0_fiber (a → b)-xx if next node is c or
# Edfa0_fiber (c → b)-xx if next node is a
msg = f'{ansi_escapes.yellow}Invalid route node specified:' +\
f'\n\t\'{n_id}\', replaced with \'{new_n}\'{ansi_escapes.reset}'
logger.info(msg)
msg = f'Request {pathreq.request_id}: Invalid route node specified:' \
+ f'\n\t\'{n_id}\', replaced with \'{new_n}\''
logger.warning(msg)
pathreq.nodes_list[pathreq.nodes_list.index(n_id)] = new_n
except StopIteration:
# shall not come in this case, unless requested direction does not exist
msg = f'{ansi_escapes.yellow}Invalid route specified {n_id}: could' +\
f' not decide on direction, skipped!.\nPlease add a valid' +\
f' direction in constraints (next neighbour node){ansi_escapes.reset}'
print(msg)
logger.info(msg)
msg = f'Request {pathreq.request_id}: Invalid route specified {n_id}: could' \
+ ' not decide on direction, skipped!.\nPlease add a valid' \
+ ' direction in constraints (next neighbour node)'
logger.warning(msg)
pathreq.loose_list.pop(pathreq.nodes_list.index(n_id))
pathreq.nodes_list.remove(n_id)
else:
@@ -354,28 +343,24 @@ def correct_xls_route_list(network_filename, network, pathreqlist):
# 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'{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)
msg = f'Request {pathreq.request_id}: Invalid node specified:\n\t\'{n_id}\'' \
+ ', could not use it as constraint, skipped!'
logger.warning(msg)
pathreq.loose_list.pop(pathreq.nodes_list.index(n_id))
pathreq.nodes_list.remove(n_id)
else:
msg = f'{ansi_escapes.red}Could not find node:\n\t\'{n_id}\' in network' +\
f' topology. Strict constraint can not be applied.{ansi_escapes.reset}'
logger.critical(msg)
msg = f'Request {pathreq.request_id}: Could not find node:\n\t\'{n_id}\' in network' \
+ ' topology. 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}')
logger.warning(f'Request {pathreq.request_id}: Invalid route node specified:\n\t\'{n_id}\''
+ ' type is not supported as constraint with xls network input, skipped!')
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)
msg = f'Invalid route node specified \n\t\'{n_id}\'' \
+ ' type is not supported as constraint with xls network input,' \
+ ', Strict constraint can not be applied.'
raise ServiceError(msg)
return pathreqlist

View File

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

View File

@@ -20,30 +20,28 @@ from logging import getLogger
from networkx import (dijkstra_path, NetworkXNoPath,
all_simple_paths, shortest_simple_paths)
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.info import create_input_spectral_information
from gnpy.core.info import create_input_spectral_information, carriers_to_spectral_information, ReferenceCarrier
from gnpy.core.exceptions import ServiceError, DisjunctionError
import gnpy.core.ansi_escapes as ansi_escapes
from copy import deepcopy
from csv import writer
from math import ceil
LOGGER = getLogger(__name__)
RequestParams = namedtuple('RequestParams', 'request_id source destination bidir trx_type' +
' trx_mode nodes_list loose_list spacing power nb_channel f_min' +
' f_max format baud_rate OSNR bit_rate roll_off tx_osnr' +
' min_spacing cost path_bandwidth effective_freq_slot')
DisjunctionParams = namedtuple('DisjunctionParams', 'disjunction_id relaxable link' +
'_diverse node_diverse disjunctions_req')
RequestParams = namedtuple('RequestParams', 'request_id source destination bidir trx_type'
' trx_mode nodes_list loose_list spacing power nb_channel f_min'
' f_max format baud_rate OSNR penalties bit_rate'
' roll_off tx_osnr min_spacing cost path_bandwidth effective_freq_slot'
' equalization_offset_db')
DisjunctionParams = namedtuple('DisjunctionParams', 'disjunction_id relaxable link_diverse'
' node_diverse disjunctions_req')
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):
params = RequestParams(**params)
self.request_id = params.request_id
@@ -62,6 +60,7 @@ class PathRequest:
self.f_max = params.f_max
self.format = params.format
self.OSNR = params.OSNR
self.penalties = params.penalties
self.bit_rate = params.bit_rate
self.roll_off = params.roll_off
self.tx_osnr = params.tx_osnr
@@ -69,8 +68,10 @@ class PathRequest:
self.cost = params.cost
self.path_bandwidth = params.path_bandwidth
if params.effective_freq_slot is not None:
self.N = params.effective_freq_slot['N']
self.M = params.effective_freq_slot['M']
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):
return '\n\t'.join([f'{type(self).__name__} {self.request_id}',
@@ -78,7 +79,7 @@ class PathRequest:
f'destination: {self.destination}'])
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
temp2 = self.bit_rate * 1e-9
else:
@@ -102,8 +103,7 @@ class PathRequest:
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):
params = DisjunctionParams(**params)
@@ -132,7 +132,7 @@ BLOCKING_NOPATH = ['NO_PATH', 'NO_PATH_WITH_CONSTRAINT',
'NO_FEASIBLE_BAUDRATE_WITH_SPACING',
'NO_COMPUTED_SNR']
BLOCKING_NOMODE = ['NO_FEASIBLE_MODE', 'MODE_NOT_FEASIBLE']
BLOCKING_NOSPECTRUM = 'NO_SPECTRUM'
BLOCKING_NOSPECTRUM = ['NO_SPECTRUM', 'NOT_ENOUGH_RESERVED_SPECTRUM']
class ResultElement:
@@ -148,8 +148,7 @@ class ResultElement:
@property
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
pro_list = []
for element in self.computed_path:
@@ -165,24 +164,30 @@ class ResultElement:
}
pro_list.append(temp)
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 = {
'path-route-object': {
'index': index,
"label-hop": {
"N": self.path_request.N,
"M": self.path_request.M
},
"label-hop": [{
"N": n,
"M": m
} for n, m in zip(self.path_request.N, self.path_request.M)],
}
}
pro_list.append(temp)
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:
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):
temp = {
'path-route-object': {
@@ -199,11 +204,9 @@ class ResultElement:
@property
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):
""" creates the metrics dictionary
"""
"""creates the metrics dictionary"""
return [
{
'metric-type': 'SNR-bandwidth',
@@ -245,8 +248,7 @@ class ResultElement:
@property
def pathresult(self):
""" create the result dictionnary (response for a request)
"""
"""create the result dictionnary (response for a request)"""
try:
if self.path_request.blocking_reason in BLOCKING_NOPATH:
response = {
@@ -284,7 +286,6 @@ def compute_constrained_path(network, req):
# been corrected and harmonized before
msg = (f'Request {req.request_id} malformed list of nodes: last node should '
'be destination trx')
LOGGER.critical(msg)
raise ValueError()
trx = [n for n in network if isinstance(n, Transceiver)]
@@ -299,10 +300,9 @@ def compute_constrained_path(network, req):
path_generator = shortest_simple_paths(network, source, destination, weight='weight')
total_path = next(path for path in path_generator if ispart(nodes_list, path))
except NetworkXNoPath:
msg = (f'{ansi_escapes.yellow}Request {req.request_id} could not find a path from'
f' {source.uid} to node: {destination.uid} in network topology{ansi_escapes.reset}')
msg = (f'Request {req.request_id} could not find a path from'
f' {source.uid} to node: {destination.uid} in network topology')
LOGGER.critical(msg)
print(msg)
req.blocking_reason = 'NO_PATH'
total_path = []
except StopIteration:
@@ -311,79 +311,104 @@ def compute_constrained_path(network, req):
# last node which is the transceiver)
# 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
print(f'{ansi_escapes.yellow}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}')
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')
if 'STRICT' not in req.loose_list[:-1]:
msg = (f'{ansi_escapes.yellow}Request {req.request_id} could not find a path with user_'
f'include node constraints{ansi_escapes.reset}')
LOGGER.info(msg)
print(f'constraint ignored')
msg = (f'Request {req.request_id} could not find a path with user_'
f'include node constraints. Constraint ignored')
LOGGER.warning(msg)
total_path = dijkstra_path(network, source, destination, weight='weight')
else:
# one STRICT makes the whole list STRICT
msg = (f'{ansi_escapes.yellow}Request {req.request_id} could not find a path with user '
f'include node constraints.\nNo path computed{ansi_escapes.reset}')
msg = (f'Request {req.request_id} could not find a path with user '
f'include node constraints.\nNo path computed')
LOGGER.critical(msg)
print(msg)
req.blocking_reason = 'NO_PATH_WITH_CONSTRAINT'
total_path = []
return total_path
def ref_carrier(equipment):
"""Create a reference carier based SI information with the specified request's power:
req_power records the power in W that the user has defined for a given request
(which might be different from the one used for the design).
"""
return ReferenceCarrier(baud_rate=equipment['SI']['default'].baud_rate,
slot_width=equipment['SI']['default'].spacing)
def propagate(path, req, equipment):
si = create_input_spectral_information(
req.f_min, req.f_max, req.roll_off, req.baud_rate,
req.power, req.spacing)
for el in path:
si = el(si)
path[-1].update_snr(req.tx_osnr, equipment['Roadm']['default'].add_drop_osnr)
return path
def propagate2(path, req, equipment):
si = create_input_spectral_information(
req.f_min, req.f_max, req.roll_off, req.baud_rate,
req.power, req.spacing)
infos = {}
for el in path:
before_si = si
after_si = si = el(si)
infos[el] = before_si, after_si
path[-1].update_snr(req.tx_osnr, equipment['Roadm']['default'].add_drop_osnr)
return infos
"""propagates signals in each element according to initial spectrum set by user"""
if req.initial_spectrum is not None:
si = carriers_to_spectral_information(initial_spectrum=req.initial_spectrum,
power=req.power, ref_carrier=ref_carrier(equipment))
else:
si = create_input_spectral_information(
f_min=req.f_min, f_max=req.f_max, roll_off=req.roll_off, baud_rate=req.baud_rate,
power=req.power, spacing=req.spacing, tx_osnr=req.tx_osnr, delta_pdb=req.offset_db,
ref_carrier=ref_carrier(equipment))
for i, el in enumerate(path):
if isinstance(el, Roadm):
si = el(si, degree=path[i+1].uid)
else:
si = el(si)
path[0].update_snr(si.tx_osnr)
path[0].calc_penalties(req.penalties)
if any(isinstance(el, Roadm) for el in path):
path[-1].update_snr(si.tx_osnr, equipment['Roadm']['default'].add_drop_osnr)
else:
path[-1].update_snr(si.tx_osnr)
path[-1].calc_penalties(req.penalties)
return si
def propagate_and_optimize_mode(path, req, equipment):
# 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
baudrate_to_explore = list(set([this_mode['baud_rate']
for this_mode in equipment['Transceiver'][req.tsp].mode
if float(this_mode['min_spacing']) <= req.spacing]))
# step 1: create an ordered list of modes based on baudrate and power offset
# 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
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
baudrate_to_explore = sorted(baudrate_to_explore, reverse=True)
if baudrate_to_explore:
baudrate_offset_to_explore = sorted(baudrate_offset_to_explore, reverse=True)
if baudrate_offset_to_explore:
# 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
if this_mode['baud_rate'] == this_br and
float(this_mode['min_spacing']) <= req.spacing]
if this_mode['baud_rate'] == this_br
and float(this_mode['min_spacing']) <= req.spacing]
modes_to_explore = sorted(modes_to_explore,
key=lambda x: x['bit_rate'], reverse=True)
# print(modes_to_explore)
key=lambda x: (x['bit_rate'], x['equalization_offset_db']), reverse=True)
# 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
spc_info = create_input_spectral_information(req.f_min, req.f_max,
equipment['SI']['default'].roll_off,
this_br, req.power, req.spacing)
for el in path:
spc_info = el(spc_info)
if req.initial_spectrum is not None:
# this case is not yet handled: spectrum can not be defined for the path-request-run function
# 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, power=req.power, spacing=req.spacing,
delta_pdb=this_offset,
tx_osnr=req.tx_osnr, ref_carrier=ref_carrier(equipment))
for i, el in enumerate(path):
if isinstance(el, Roadm):
spc_info = el(spc_info, degree=path[i+1].uid)
else:
spc_info = el(spc_info)
for this_mode in modes_to_explore:
if path[-1].snr is not None:
path[-1].update_snr(this_mode['tx_osnr'], equipment['Roadm']['default'].add_drop_osnr)
if round(min(path[-1].snr + lin2db(this_br / (12.5e9))), 2) > this_mode['OSNR']:
path[0].update_snr(this_mode['tx_osnr'])
path[0].calc_penalties(this_mode['penalties'])
if any(isinstance(el, Roadm) for el in path):
path[-1].update_snr(this_mode['tx_osnr'], equipment['Roadm']['default'].add_drop_osnr)
else:
path[-1].update_snr(this_mode['tx_osnr'])
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:
return path, this_mode
else:
last_explored_mode = this_mode
@@ -394,22 +419,19 @@ def propagate_and_optimize_mode(path, req, equipment):
# returns the last propagated path and mode
msg = f'\tWarning! Request {req.request_id}: no mode satisfies path SNR requirement.\n'
print(msg)
LOGGER.info(msg)
LOGGER.warning(msg)
req.blocking_reason = 'NO_FEASIBLE_MODE'
return path, last_explored_mode
else:
# no baudrate satisfying spacing
msg = f'\tWarning! Request {req.request_id}: no baudrate satisfies spacing requirement.\n'
print(msg)
LOGGER.info(msg)
LOGGER.warning(msg)
req.blocking_reason = 'NO_FEASIBLE_BAUDRATE_WITH_SPACING'
return [], None
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']
for e in path_metric if e['metric-type'] == 'SNR-0.1nm')
output_snrbandwidth = next(e['accumulative-value']
@@ -427,9 +449,7 @@ def jsontopath_metric(path_metric):
def jsontoparams(my_p, tsp, mode, equipment):
""" a function that derives optical params from transponder type and mode
supports the no mode case
"""
"""a function that derives optical params from transponder type and mode supports the no mode case"""
temp = []
for elem in my_p['path-properties']['path-route-objects']:
if 'num-unnum-hop' in elem['path-route-object']:
@@ -439,8 +459,8 @@ def jsontoparams(my_p, tsp, mode, equipment):
temp2 = []
for elem in my_p['path-properties']['path-route-objects']:
if 'label-hop' in elem['path-route-object'].keys():
temp2.append(f'{elem["path-route-object"]["label-hop"]["N"]}, ' +
f'{elem["path-route-object"]["label-hop"]["M"]}')
temp2.append(f'{[e["N"] for e in elem["path-route-object"]["label-hop"]]}, '
+ f'{[e["M"] for e in elem["path-route-object"]["label-hop"]]}')
# OrderedDict.fromkeys returns the unique set of strings.
# TODO: if spectrum changes along the path, we should be able to give the segments
# eg for regeneration case
@@ -464,10 +484,10 @@ def jsontoparams(my_p, tsp, mode, equipment):
def jsontocsv(json_data, equipment, fileout):
""" reads json path result file in accordance with:
Yang model for requesting Path Computation
draft-ietf-teas-yang-path-computation-01.txt.
and write results in an CSV file
"""reads json path result file in accordance with:
Yang model for requesting Path Computation
draft-ietf-teas-yang-path-computation-01.txt.
and write results in an CSV file
"""
mywriter = writer(fileout)
mywriter.writerow(('response-id', 'source', 'destination', 'path_bandwidth', 'Pass?',
@@ -696,8 +716,8 @@ def compute_path_dsjctn(network, equipment, pathreqlist, disjunctions_list):
# in each loop, dpath is updated with a path for rq that satisfies
# disjunction with each path in dpath
# for example, assume set of requests in the vector (disjunction_list) is {rq1,rq2, rq3}
# rq1 p1: abfhg
# p2: aefhg
# rq1 p1: aefhg
# p2: abfhg
# p3: abcg
# rq2 p8: bf
# rq3 p4: abcgh
@@ -714,6 +734,7 @@ def compute_path_dsjctn(network, equipment, pathreqlist, disjunctions_list):
# after second loop:
# dpath = [ p3 p8 p6 ]
# since p1 and p4 are not disjoint
# p1 and p6 are not disjoint
# p1 and p7 are not disjoint
# p3 and p4 are not disjoint
# p3 and p7 are not disjoint
@@ -737,7 +758,6 @@ def compute_path_dsjctn(network, equipment, pathreqlist, disjunctions_list):
temp.append(temp2)
# print(f' coucou {elem1}: \t{temp}')
dpath = temp
# print(dpath)
candidates[dis.disjunction_id] = dpath
# for i in disjunctions_list:
@@ -778,9 +798,9 @@ def compute_path_dsjctn(network, equipment, pathreqlist, disjunctions_list):
if pth in cndt:
candidates[this_id].remove(cndt)
# for i in disjunctions_list:
# print(i.disjunction_id)
# print(f'\n{candidates[i.disjunction_id]}')
# for i in disjunctions_list:
# print(i.disjunction_id)
# print(f'\n{candidates[i.disjunction_id]}')
# step 4 apply route constraints: remove candidate path that do not satisfy
# the constraint only in the case of disjounction: the simple path is processed in
@@ -788,54 +808,51 @@ def compute_path_dsjctn(network, equipment, pathreqlist, disjunctions_list):
# TODO: keep a version without the loose constraint
for this_d in disjunctions_list:
temp = []
alternatetemp = []
for j, sol in enumerate(candidates[this_d.disjunction_id]):
testispartok = True
testispartnokloose = True
for pth in sol:
# print(f'test {allpaths[id(pth)].req.request_id}')
# print(f'length of route {len(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
# except if this was the last solution: then check if the constraint is loose
# or not
# if any pth from sol does not contain the ordered list node,
# remove sol from the candidate, except if constraint was loose:
# then keep sol as an alternate solution
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:
temp.append(sol)
candidates[this_d.disjunction_id] = temp
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
elif alternatetemp:
candidates[this_d.disjunction_id] = alternatetemp
else:
candidates[this_d.disjunction_id] = []
# step 5 select the first combination that works
pathreslist_disjoint = {}
for dis in disjunctions_list:
test_sol = True
while test_sol:
# print('coucou')
if candidates[dis.disjunction_id]:
for pth in candidates[dis.disjunction_id][0]:
if allpaths[id(pth)].req in pathreqlist_disjt:
# print(f'selected path:{pth} for req {allpaths[id(pth)].req.request_id}')
pathreslist_disjoint[allpaths[id(pth)].req] = allpaths[id(pth)].pth
pathreqlist_disjt.remove(allpaths[id(pth)].req)
candidates = remove_candidate(candidates, allpaths, allpaths[id(pth)].req, pth)
test_sol = False
else:
msg = f'No disjoint path found with added constraint'
LOGGER.critical(msg)
print(f'{msg}\nComputation stopped.')
# TODO in this case: replay step 5 with the candidate without constraints
raise DisjunctionError(msg)
if candidates[dis.disjunction_id]:
for pth in candidates[dis.disjunction_id][0]:
if allpaths[id(pth)].req in pathreqlist_disjt:
# print(f'selected path:{pth} for req {allpaths[id(pth)].req.request_id}')
pathreslist_disjoint[allpaths[id(pth)].req] = allpaths[id(pth)].pth
# remove request from list of requests (in case of duplicate)
pathreqlist_disjt.remove(allpaths[id(pth)].req)
# remove duplicated candidates
candidates = remove_candidate(candidates, allpaths, allpaths[id(pth)].req, pth)
else:
msg = 'No disjoint path found with added constraint\nComputation stopped.'
# TODO in this case: replay step 5 with the candidate without constraints
raise DisjunctionError(msg)
# for i in disjunctions_list:
# print(i.disjunction_id)
@@ -854,8 +871,7 @@ def compute_path_dsjctn(network, equipment, pathreqlist, disjunctions_list):
def isdisjoint(pth1, pth2):
""" returns 0 if disjoint
"""
"""returns 0 if disjoint"""
edge1 = list(pairwise(pth1))
edge2 = list(pairwise(pth2))
for edge in edge1:
@@ -865,9 +881,9 @@ def isdisjoint(pth1, pth2):
def find_reversed_path(pth):
""" select of intermediate roadms and find the path between them
note that this function may not give an exact result in case of multiple
links between two adjacent nodes.
"""select of intermediate roadms and find the path between them
note that this function may not give an exact result in case of multiple
links between two adjacent nodes.
"""
# TODO add some indication on elements to indicate from which other they
# are the reversed direction. This is partly done with oms indication
@@ -890,9 +906,8 @@ def find_reversed_path(pth):
# concatenation should be [roadma el1 el2 roadmb el3 el4 roadmc]
reversed_path = list(OrderedDict.fromkeys(reversed_path))
else:
msg = f'Error while handling reversed path {pth[-1].uid} to {pth[0].uid}:' +\
' can not handle unidir topology. TO DO.'
LOGGER.critical(msg)
msg = f'Error while handling reversed path {pth[-1].uid} to {pth[0].uid}:' \
+ ' can not handle unidir topology. TO DO.'
raise ValueError(msg)
reversed_path.append(pth[0])
@@ -900,9 +915,7 @@ def find_reversed_path(pth):
def ispart(ptha, pthb):
""" the functions takes two paths a and b and retrns True
if all a elements are part of b and in the same order
"""
"""the functions takes two paths a and b and retrns True if all a elements are part of b and in the same order"""
j = 0
for elem in ptha:
if elem in pthb:
@@ -916,8 +929,7 @@ def ispart(ptha, pthb):
def remove_candidate(candidates, allpaths, rqst, pth):
""" filter duplicate candidates
"""
"""filter duplicate candidates"""
# print(f'coucou {rqst.request_id}')
for key, candidate in candidates.items():
temp = candidate.copy()
@@ -932,8 +944,7 @@ def remove_candidate(candidates, allpaths, rqst, pth):
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]
dis2 = [d for d in disjlist if req2.request_id in d.disjunctions_req]
same_disj = False
@@ -973,19 +984,24 @@ def compare_reqs(req1, req2, disjlist):
def requests_aggregation(pathreqlist, disjlist):
""" this function aggregates requests so that if several requests
exist between same source and destination and with same transponder type
"""this function aggregates requests so that if several requests
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 ...
# currently if undefined takes the default values
local_list = pathreqlist.copy()
for req in pathreqlist:
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
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
this_r.request_id = ' | '.join((this_r.request_id, req.request_id))
# remove request from list
local_list.remove(req)
# todo change also disjunction req with new demand
@@ -1002,23 +1018,22 @@ def requests_aggregation(pathreqlist, disjlist):
def correct_json_route_list(network, pathreqlist):
""" 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
suppresses the constraint it it is loose or raises an error if it is strict
"""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
suppresses the constraint it it is loose or raises an error if it is strict
"""
all_uid = [n.uid for n in network.nodes()]
transponders = [n.uid for n in network.nodes() if isinstance(n, Transceiver)]
for pathreq in pathreqlist:
if pathreq.source not in transponders:
msg = f'{ansi_escapes.red}Request: {pathreq.request_id}: could not find transponder' +\
f' source : {pathreq.source}.{ansi_escapes.reset}'
LOGGER.critical(msg)
msg = f'Request: {pathreq.request_id}: could not find transponder' \
+ f' source : {pathreq.source}.'
raise ServiceError(msg)
if pathreq.destination not in transponders:
msg = f'{ansi_escapes.red}Request: {pathreq.request_id}: could not find transponder' +\
f' destination : {pathreq.destination}.{ansi_escapes.reset}'
LOGGER.critical(msg)
msg = f'Request: {pathreq.request_id}: could not find transponder' \
+ f' destination : {pathreq.destination}.'
raise ServiceError(msg)
# silently remove source and dest nodes from the list
@@ -1037,24 +1052,21 @@ def correct_json_route_list(network, pathreqlist):
# 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'{ansi_escapes.yellow}invalid route node specified:\n\t\'{n_id}\',' +\
f' could not use it as constraint, skipped!{ansi_escapes.reset}'
print(msg)
LOGGER.info(msg)
msg = f'invalid route node specified:\n\t\'{n_id}\',' \
+ ' could not use it as constraint, skipped!'
LOGGER.warning(msg)
pathreq.loose_list.pop(pathreq.nodes_list.index(n_id))
pathreq.nodes_list.remove(n_id)
else:
msg = f'{ansi_escapes.red}could not find node:\n\t \'{n_id}\' in network' +\
f' topology. Strict constraint can not be applied.{ansi_escapes.reset}'
LOGGER.critical(msg)
msg = f'could not find node:\n\t \'{n_id}\' in network' \
+ ' topology. Strict constraint can not be applied.'
raise ServiceError(msg)
return pathreqlist
def deduplicate_disjunctions(disjn):
""" clean disjunctions to remove possible repetition
"""
"""clean disjunctions to remove possible repetition"""
local_disjn = disjn.copy()
for elem in local_disjn:
for dis_elem in local_disjn:
@@ -1065,8 +1077,9 @@ def deduplicate_disjunctions(disjn):
def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
""" 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 !
"""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 !
"""
path_res_list = []
reversed_path_res_list = []
@@ -1077,10 +1090,10 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
# use the power specified in requests but might be different from the one
# specified for design the power is an optional parameter for requests
# definition if optional, use the one defines in eqt_config.json
print(f'request {pathreq.request_id}')
print(f'Computing path from {pathreq.source} to {pathreq.destination}')
# adding first node to be clearer on the output
print(f'with path constraint: {[pathreq.source] + pathreq.nodes_list}')
msg = f'\n\trequest {pathreq.request_id}\n' \
+ f'\tComputing path from {pathreq.source} to {pathreq.destination}\n' \
+ f'\twith 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
# last element is a transciver and where the result of the propagation is
@@ -1090,21 +1103,24 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
# 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
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
# print(f'{pathreq.baud_rate} {pathreq.power} {pathreq.spacing} {pathreq.nb_channel}')
if total_path:
if pathreq.baud_rate is not None:
# means that at this point the mode was entered/forced by user and thus a
# baud_rate was defined
total_path = propagate(total_path, pathreq, equipment)
temp_snr01nm = round(mean(total_path[-1].snr+lin2db(pathreq.baud_rate/(12.5e9))), 2)
if temp_snr01nm < pathreq.OSNR:
msg = f'\tWarning! Request {pathreq.request_id} computed path from' +\
f' {pathreq.source} to {pathreq.destination} does not pass with' +\
f' {pathreq.tsp_mode}\n\tcomputedSNR in 0.1nm = {temp_snr01nm} ' +\
f'- required osnr {pathreq.OSNR}'
print(msg)
propagate(total_path, pathreq, equipment)
snr01nm_with_penalty = total_path[-1].snr_01nm - total_path[-1].total_penalty
min_ind = argmin(snr01nm_with_penalty)
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' \
+ f' {pathreq.source} to {pathreq.destination} does not pass with {pathreq.tsp_mode}' \
+ f'\n\tcomputed SNR in 0.1nm = {round(total_path[-1].snr_01nm[min_ind], 2)}'
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)
pathreq.blocking_reason = 'MODE_NOT_FEASIBLE'
else:
@@ -1124,6 +1140,7 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
pathreq.OSNR = mode['OSNR']
pathreq.tx_osnr = mode['tx_osnr']
pathreq.bit_rate = mode['bit_rate']
pathreq.penalties = mode['penalties']
# other blocking reason should not appear at this point
except AttributeError:
pathreq.baud_rate = mode['baud_rate']
@@ -1132,25 +1149,27 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
pathreq.OSNR = mode['OSNR']
pathreq.tx_osnr = mode['tx_osnr']
pathreq.bit_rate = mode['bit_rate']
pathreq.penalties = mode['penalties']
# reversed path is needed for correct spectrum assignment
reversed_path = find_reversed_path(pathlist[i])
if pathreq.bidir:
# only propagate if bidir is true, but needs the reversed path anyway for
# correct spectrum assignment
if pathreq.bidir and pathreq.baud_rate is not None:
# Both directions requested, and a feasible mode was found
rev_p = deepcopy(reversed_path)
print(f'\n\tPropagating Z to A direction {pathreq.destination} to {pathreq.source}')
print(f'\tPath (roadsm) {[r.uid for r in rev_p if isinstance(r,Roadm)]}\n')
propagated_reversed_path = propagate(rev_p, pathreq, equipment)
temp_snr01nm = round(mean(propagated_reversed_path[-1].snr +\
lin2db(pathreq.baud_rate/(12.5e9))), 2)
if temp_snr01nm < pathreq.OSNR:
msg = f'\tWarning! Request {pathreq.request_id} computed path from' +\
f' {pathreq.source} to {pathreq.destination} does not pass with' +\
f' {pathreq.tsp_mode}\n' +\
f'\tcomputedSNR in 0.1nm = {temp_snr01nm} - required osnr {pathreq.OSNR}'
print(msg)
msg = f'\n\tPropagating Z to A direction {pathreq.destination} to {pathreq.source}\n' \
+ f'\tPath (roadms) {[r.uid for r in rev_p if isinstance(r,Roadm)]}\n'
LOGGER.info(msg)
propagate(rev_p, pathreq, equipment)
propagated_reversed_path = rev_p
snr01nm_with_penalty = rev_p[-1].snr_01nm - rev_p[-1].total_penalty
min_ind = argmin(snr01nm_with_penalty)
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' \
+ f' {pathreq.destination} to {pathreq.source} does not pass with {pathreq.tsp_mode}' \
+ f'\n\tcomputed SNR in 0.1nm = {round(rev_p[-1].snr_01nm[min_ind], 2)}'
msg = _penalty_msg(rev_p, msg, min_ind) \
+ f'\n\trequired osnr = {pathreq.OSNR}' \
+ f'\n\tsystem margin = {equipment["SI"]["default"].sys_margins}'
LOGGER.warning(msg)
# TODO selection of mode should also be on reversed direction !!
if not hasattr(pathreq, 'blocking_reason'):
@@ -1158,9 +1177,8 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
else:
propagated_reversed_path = []
else:
msg = 'Total path is empty. No propagation'
print(msg)
LOGGER.info(msg)
msg = f'Request {pathreq.request_id}: Total path is empty. No propagation'
LOGGER.warning(msg)
reversed_path = []
propagated_reversed_path = []
@@ -1168,5 +1186,33 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
reversed_path_res_list.append(reversed_path)
propagated_reversed_path_res_list.append(propagated_reversed_path)
# print to have a nice output
print('')
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

View File

@@ -15,16 +15,16 @@ element/oms correspondace
from collections import namedtuple
from logging import getLogger
from math import ceil
from gnpy.core.elements import Roadm, Transceiver
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
LOGGER = getLogger(__name__)
class Bitmap:
""" records the spectrum occupation
"""
"""records the spectrum occupation"""
def __init__(self, f_min, f_max, grid, guardband=0.15e12, bitmap=None):
# n is the min index including guardband. Guardband is require to be sure
@@ -45,26 +45,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}')
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]
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)
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
temp = list(range(self.n_min - len(newbitmap), self.n_min))
self.freq_index = temp + self.freq_index
self.n_min = self.freq_index[0]
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.freq_index = self.freq_index + list(range(self.n_max, self.n_max + len(newbitmap)))
self.n_max = self.freq_index[-1]
@@ -75,8 +71,8 @@ OMSParams = namedtuple('OMSParams', 'oms_id el_id_list el_list')
class OMS:
""" OMS class is the logical container that represent a link between two adjacent ROADMs and
records the crossed elements and the occupied spectrum
"""OMS class is the logical container that represent a link between two adjacent ROADMs and
records the crossed elements and the occupied spectrum
"""
def __init__(self, *args, **params):
@@ -98,36 +94,28 @@ class OMS:
f'{self.el_id_list[0]} - {self.el_id_list[-1]}', '\n'])
def add_element(self, elem):
""" records oms elements
"""
"""records oms elements"""
self.el_id_list.append(elem.uid)
self.el_list.append(elem)
def update_spectrum(self, f_min, f_max, guardband=0.15e12, existing_spectrum=None,
grid=0.00625e12):
""" frequencies expressed in Hz
def update_spectrum(self, f_min, f_max, guardband=0.15e12, existing_spectrum=None, grid=0.00625e12):
"""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:
# add some 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.
# TODO : add explaination on that / parametrize ....
self.spectrum_bitmap = Bitmap(f_min, f_max, grid, guardband)
# print(len(self.spectrum_bitmap.bitmap))
self.spectrum_bitmap = Bitmap(f_min=f_min, f_max=f_max, grid=grid, guardband=guardband,
bitmap=existing_spectrum)
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):
raise SpectrumError(f'N must be a signed integer, got {nvalue}')
if not isinstance(mvalue, int):
@@ -146,16 +134,16 @@ class OMS:
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):
""" record service and mark spectrum as occupied
"""
"""record service and mark spectrum as occupied"""
self.service_list.append(service_id)
self.nb_channels += nb_wl
def frequency_to_n(freq, grid=0.00625e12):
""" converts frequency into the n value (ITU grid)
reference to Recommendation G.694.1 (02/12), Figure I.3
https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
"""converts frequency into the n value (ITU grid)
reference to Recommendation G.694.1 (02/12), Figure I.3
https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
>>> frequency_to_n(193.1375e12)
6
@@ -167,9 +155,10 @@ def frequency_to_n(freq, grid=0.00625e12):
def nvalue_to_frequency(nvalue, grid=0.00625e12):
""" converts n value into a frequency
reference to Recommendation G.694.1 (02/12), Table 1
https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
"""converts n value into a frequency
reference to Recommendation G.694.1 (02/12), Table 1
https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
>>> nvalue_to_frequency(6)
193137500000000.0
@@ -181,17 +170,17 @@ def nvalue_to_frequency(nvalue, grid=0.00625e12):
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
stopn = nvalue + mvalue - 1
return startn, stopn
def slots_to_m(startn, stopn):
""" 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
https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
"""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
https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
>>> nval, mval = slots_to_m(6, 20)
>>> nval
@@ -206,10 +195,11 @@ def slots_to_m(startn, stopn):
def m_to_freq(nvalue, mvalue, grid=0.00625e12):
""" converts m into frequency range
spectrum(13,7) is (193137500000000.0, 193225000000000.0)
reference to Recommendation G.694.1 (02/12), Figure I.3
https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
"""converts m into frequency range
spectrum(13,7) is (193137500000000.0, 193225000000000.0)
reference to Recommendation G.694.1 (02/12), Figure I.3
https://www.itu.int/rec/T-REC-G.694.1-201202-I/en
>>> fstart, fstop = m_to_freq(13, 7)
>>> fstart
@@ -225,9 +215,7 @@ def m_to_freq(nvalue, mvalue, grid=0.00625e12):
def align_grids(oms_list):
""" used to apply same grid to all oms : same starting n, stop n and slot size
out of grid slots are set to 0
"""
"""Used to apply same grid to all oms : same starting n, stop n and slot size. Out of grid slots are set to 0."""
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])
for this_o in oms_list:
@@ -239,12 +227,13 @@ def align_grids(oms_list):
def build_oms_list(network, equipment):
""" initialization of OMS list in the network
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
oms it belongs to.
the function supports different spectrum width and supposes that the whole network
works with the min range among OMSs
"""initialization of OMS list in the network
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
oms it belongs to.
the function supports different spectrum width and supposes that the whole network
works with the min range among OMSs
"""
oms_id = 0
oms_list = []
@@ -296,8 +285,9 @@ def build_oms_list(network, equipment):
def reversed_oms(oms_list):
""" identifies reversed OMS
only applicable for non parallel OMS
"""identifies reversed OMS
only applicable for non parallel OMS
"""
for oms in oms_list:
has_reversed = False
@@ -322,28 +312,41 @@ def bitmap_sum(band1, band2):
return res
def spectrum_selection(pth, oms_list, requested_m, requested_n=None):
"""Collects spectrum availability and call the select_candidate function"""
# use indexes instead of ITU-T n values
def build_path_oms_id_list(pth):
path_oms = []
for elem in pth:
if not isinstance(elem, Roadm) and not isinstance(elem, Transceiver):
# only edfa, fused and fibers have oms_id attribute
path_oms.append(elem.oms_id)
# remove duplicate oms_id, order is not important
path_oms = 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
return list(set(path_oms))
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:]:
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.freq_index_min)
freq_max = nvalue_to_frequency(spectrum.freq_index_max)
aggregate_oms = OMS(**params)
aggregate_oms.update_spectrum(freq_min, freq_max, grid=0.00625e12, 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:
# 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)
@@ -354,29 +357,36 @@ def spectrum_selection(pth, oms_list, requested_m, requested_n=None):
candidate = select_candidate(candidates, policy='first_fit')
else:
i = oms_list[path_oms[0]].spectrum_bitmap.geti(requested_n)
# print(f'N {requested_n} i {i}')
# print(freq_availability[i-m:i+m] )
# 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
i = test_oms.spectrum_bitmap.geti(requested_n)
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):
# candidate is the triplet center_n, startn and stopn
candidate = (requested_n, requested_n - requested_m, requested_n + requested_m - 1)
else:
candidate = (None, None, None)
# print("coucou11")
# print(candidate)
# print(freq_availability[321:321+2*m])
# a = [i+321 for i in range(2*m)]
# print(a)
# print(candidate)
return candidate, path_oms
return candidate
def determine_slot_numbers(test_oms, requested_n, required_m, per_channel_m):
"""determines max availability around requested_n. requested_n should not be None"""
bitmap = test_oms.spectrum_bitmap
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):
""" selects a candidate among all available spectrum
"""
"""selects a candidate among all available spectrum"""
if policy == 'first_fit':
if candidates:
return candidates[0]
@@ -386,62 +396,112 @@ def select_candidate(candidates, policy):
raise ServiceError('Only first_fit spectrum assignment policy is implemented.')
def pth_assign_spectrum(pths, rqs, oms_list, rpths):
""" basic first fit assignment
if reversed path are provided, means that occupation is bidir
"""
for i, pth in enumerate(pths):
# computes the number of channels required
try:
if rqs[i].blocking_reason:
rqs[i].blocked = True
rqs[i].N = None
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
if hasattr(rqs[i], 'M') and rqs[i].M is not None:
# Consistency check between the requested M and path_bandwidth
# M value should be bigger than the computed requested_m (simple estimate)
# TODO: elaborate a more accurate estimate with nb_wl * tx_osnr + possibly guardbands in case of
# superchannel closed packing.
if requested_m <= rqs[i].M:
requested_m = rqs[i].M
else:
# TODO : create a specific blocking reason and following process for this case instead of an exception
raise SpectrumError(f'requested M {rqs[i].M} number of slots for request {rqs[i].request_id} ' +
f'should be greater than {requested_m} to support request ' +
f'{rqs[i].path_bandwidth * 1e-9} Gbit/s with {rqs[i].tsp} {rqs[i].tsp_mode}')
# else: there is no M value so the programs uses the requested_m one
if hasattr(rqs[i], 'N'):
requested_n = rqs[i].N
else:
requested_n = None
(center_n, startn, stopn), path_oms = spectrum_selection(pth + rpths[i], oms_list, requested_m,
requested_n)
# 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)
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
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
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:
rqs[i].blocked = True
rqs[i].N = None
rqs[i].M = 0
rqs[i].blocking_reason = 'NO_SPECTRUM'
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):
"""basic first fit assignment
if reversed path are provided, means that occupation is bidir
"""
for pth, rq, rpth in zip(pths, rqs, rpths):
if hasattr(rq, 'blocking_reason'):
rq.N = None
rq.M = None
else:
# computes the number of channels required for path_bandwidth and the min required nb of slots
# for one channel (corresponds to the spacing)
nb_wl, required_m = compute_spectrum_slot_vs_bandwidth(rq.path_bandwidth,
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,310 +0,0 @@
{
"Edfa":[{
"type_variety": "high_detail_model_example",
"type_def": "advanced_model",
"gain_flatmax": 25.0,
"gain_min": 15.0,
"p_max": 21.0,
"advanced_config_from_json": "std_medium_gain_advanced_config.json",
"out_voa_auto": false,
"allowed_for_design": false
}, {
"type_variety": "Juniper_BoosterHG",
"type_def": "advanced_model",
"gain_flatmax": 25.0,
"gain_min": 10.0,
"p_max": 21.0,
"advanced_config_from_json": "Juniper-BoosterHG.json",
"out_voa_auto": false,
"allowed_for_design": false
},
{
"type_variety": "operator_model_example",
"type_def": "variable_gain",
"gain_flatmax": 26.0,
"gain_min": 15.0,
"p_max": 23.0,
"nf_min": 6.0,
"nf_max": 10.0,
"out_voa_auto": false,
"allowed_for_design": false
},
{
"type_variety": "low_noise",
"type_def": "openroadm",
"gain_flatmax": 27.0,
"gain_min": 12.0,
"p_max": 22.0,
"nf_coef": [-8.104e-4,-6.221e-2,-5.889e-1,37.62],
"allowed_for_design": false
},
{
"type_variety": "standard",
"type_def": "openroadm",
"gain_flatmax": 27.0,
"gain_min": 12.0,
"p_max": 22.0,
"nf_coef": [-5.952e-4,-6.250e-2,-1.071,28.99],
"allowed_for_design": false
},
{
"type_variety": "std_high_gain",
"type_def": "variable_gain",
"gain_flatmax": 35.0,
"gain_min": 25.0,
"p_max": 21.0,
"nf_min": 5.5,
"nf_max": 7.0,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_medium_gain",
"type_def": "variable_gain",
"gain_flatmax": 26.0,
"gain_min": 15.0,
"p_max": 23.0,
"nf_min": 6.0,
"nf_max": 10.0,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "std_low_gain",
"type_def": "variable_gain",
"gain_flatmax": 16.0,
"gain_min": 8.0,
"p_max": 23.0,
"nf_min": 6.5,
"nf_max": 11.0,
"out_voa_auto": false,
"allowed_for_design": true
},
{
"type_variety": "high_power",
"type_def": "variable_gain",
"gain_flatmax": 16.0,
"gain_min": 8.0,
"p_max": 25.0,
"nf_min": 9.0,
"nf_max": 15.0,
"out_voa_auto": false,
"allowed_for_design": false
},
{
"type_variety": "std_fixed_gain",
"type_def": "fixed_gain",
"gain_flatmax": 21.0,
"gain_min": 20.0,
"p_max": 21.0,
"nf0": 5.5,
"allowed_for_design": false
},
{
"type_variety": "4pumps_raman",
"type_def": "fixed_gain",
"gain_flatmax": 12.0,
"gain_min": 12.0,
"p_max": 21.0,
"nf0": -1.0,
"allowed_for_design": false
},
{
"type_variety": "hybrid_4pumps_lowgain",
"type_def": "dual_stage",
"raman": true,
"gain_min": 25.0,
"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.0,
"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.0,
"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.0,
"preamp_variety": "std_medium_gain",
"booster_variety": "high_power",
"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": [-2.0, 3.0, 0.5],
"max_fiber_lineic_loss_for_raman": 0.25,
"target_extended_gain": 2.5,
"max_length": 150.0,
"length_units": "km",
"max_loss": 28.0,
"padding": 10.0,
"EOL": 0.0,
"con_in": 0.0,
"con_out": 0.0
}
],
"Roadm":[{
"target_pch_out_db": -20.0,
"add_drop_osnr": 38.0,
"pmd": 0.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.0,
"power_range_db": [0.0,0.0,1.0],
"roll_off": 0.15,
"tx_osnr": 40.0,
"sys_margins": 2.0
}],
"Transceiver":[
{
"type_variety": "vendorA_trx-type1",
"frequency":{
"min": 191.35e12,
"max": 196.1e12
},
"mode":[{
"format": "mode 1",
"baud_rate": 32e9,
"OSNR": 11.0,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 40.0,
"min_spacing": 37.5e9,
"cost":1.0
},
{
"format": "mode 2",
"baud_rate": 66e9,
"OSNR": 15.0,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 40.0,
"min_spacing": 75e9,
"cost":1.0
}
]
},
{
"type_variety": "Voyager",
"frequency":{
"min": 191.35e12,
"max": 196.1e12
},
"mode":[
{
"format": "mode 1",
"baud_rate": 32e9,
"OSNR": 12.0,
"bit_rate": 100e9,
"roll_off": 0.15,
"tx_osnr": 40.0,
"min_spacing": 37.5e9,
"cost":1.0
},
{
"format": "mode 3",
"baud_rate": 44e9,
"OSNR": 18.0,
"bit_rate": 300e9,
"roll_off": 0.15,
"tx_osnr": 40.0,
"min_spacing": 62.5e9,
"cost":1.0
},
{
"format": "mode 2",
"baud_rate": 66e9,
"OSNR": 21.0,
"bit_rate": 400e9,
"roll_off": 0.15,
"tx_osnr": 40.0,
"min_spacing": 75e9,
"cost":1.0
},
{
"format": "mode 4",
"baud_rate": 66e9,
"OSNR": 16.0,
"bit_rate": 200e9,
"roll_off": 0.15,
"tx_osnr": 40.0,
"min_spacing": 75e9,
"cost":1.0
}
]
}
]
}

View File

@@ -1,180 +0,0 @@
{
"gnpy-api:service":{
"path-request": [
{
"request-id": "0",
"source": "trx Alice",
"destination": "trx Bob",
"src-tp-id": "trx Alice",
"dst-tp-id": "trx Bob",
"bidirectional": false,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"trx_mode": "mode 1",
"effective-freq-slot": [
{
"N": 0,
"M": 12
}
],
"spacing": 50000000000.0,
"path_bandwidth": 100000000000.0
}
}
},
{
"request-id": "1",
"source": "trx Alice",
"destination": "trx Bob",
"src-tp-id": "trx Alice",
"dst-tp-id": "trx Bob",
"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": "2",
"source": "trx Alice",
"destination": "trx Bob",
"src-tp-id": "trx Alice",
"dst-tp-id": "trx Bob",
"bidirectional": false,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"trx_mode": "mode 2",
"spacing": 100000000000.0,
"path_bandwidth": 100000000000.0
}
}
},
{
"request-id": "3",
"source": "trx Alice",
"destination": "trx Bob",
"src-tp-id": "trx Alice",
"dst-tp-id": "trx Bob",
"bidirectional": true,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"spacing": 50000000000.0,
"path_bandwidth": 100000000000.0
}
},
"explicit-route-objects": {
"route-object-include-exclude": [
{
"explicit-route-usage": "route-include-ero",
"index": 0,
"num-unnum-hop": {
"node-id": "roadm Carol",
"link-tp-id": "link-tp-id is not used",
"hop-type": "LOOSE"
}
}
]
}
},
{
"request-id": "4",
"source": "trx Alice",
"destination": "trx Bob",
"src-tp-id": "trx Alice",
"dst-tp-id": "trx Bob",
"bidirectional": true,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"effective-freq-slot": [
{
"N": -284,
"M": 12
}
],
"spacing": 50000000000.0,
"path_bandwidth": 100000000000.0
}
}
},
{
"request-id": "5",
"source": "trx Bob1",
"destination": "trx Carol1",
"src-tp-id": "trx Bob1",
"dst-tp-id": "trx Carol1",
"bidirectional": true,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "vendorA_trx-type1",
"spacing": 100000000000.0,
"path_bandwidth": 100000000000.0
}
}
},
{
"request-id": "6",
"source": "trx Bob1",
"destination": "trx Carol1",
"src-tp-id": "trx Bob1",
"dst-tp-id": "trx Carol1",
"bidirectional": true,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"trx_mode": "mode 1",
"spacing": 50000000000.0,
"path_bandwidth": 100000000000.0
}
}
},
{
"request-id": "7",
"source": "trx Bob1",
"destination": "trx Carol",
"src-tp-id": "trx Bob1",
"dst-tp-id": "trx Carol",
"bidirectional": true,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"trx_mode": "mode 1",
"spacing": 50000000000.0,
"path_bandwidth": 100000000000.0
}
}
}
],
"synchronization": [
{
"synchronization-id": "1",
"svec": {
"relaxable": false,
"disjointness": "node link",
"request-id-number": [
"1",
"0"
]
}
}
]
},
"gnpy-api:topology_id": "5cf39d4b-be10-4ee9-b38b-7f4db7403db7",
"gnpy-api:equipment_id": "9ed86e34-9d41-41b2-b8e4-984ca0901d47"
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,901 +0,0 @@
{
"elements": [
{
"uid": "trx Alice",
"type": "Transceiver",
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Alice",
"region": ""
}
}
},
{
"uid": "trx Bob",
"type": "Transceiver",
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "trx Carol",
"type": "Transceiver",
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "trx Bob1",
"type": "Transceiver",
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "trx Carol1",
"type": "Transceiver",
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "roadm Alice",
"type": "Roadm",
"params": {
"target_pch_out_db": -20.0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
}
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Alice",
"region": ""
}
}
},
{
"uid": "roadm Bob",
"type": "Roadm",
"params": {
"target_pch_out_db": -20.0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
}
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "roadm Carol",
"type": "Roadm",
"params": {
"target_pch_out_db": -20.0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
}
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "roadm Bob1",
"type": "Roadm",
"params": {
"target_pch_out_db": -20.0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
}
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "roadm Carol1",
"type": "Roadm",
"params": {
"target_pch_out_db": -20.0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
}
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "fiber (Alice → Bob)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 75.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Bob → Carol)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Bob1 → Carol1)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.5,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Carol → Dan)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 83.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Dan → Alice)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 60.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Bob → Alice)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 75.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Carol → Bob)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Carol1 → Bob1)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.5,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Dan → Carol)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 83.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "fiber (Alice → Dan)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 60.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0.0,
"con_in": 0.0,
"con_out": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "null",
"region": "null"
}
}
},
{
"uid": "east edfa in Alice to Bob",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 18.5,
"delta_p": -1.5,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Alice",
"region": ""
}
}
},
{
"uid": "east edfa in Bob to Carol",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 19.0,
"delta_p": -1.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "east edfa in Bob1 to Carol1",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 19.0,
"delta_p": -1.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "east edfa in Carol to Dan",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 19.0,
"delta_p": -1.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "east edfa in Dan to Alice",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 15.600000000000001,
"delta_p": -2.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Dan",
"region": ""
}
}
},
{
"uid": "east edfa in Bob to Alice",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 18.5,
"delta_p": -1.5,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "east edfa in Alice to Dan",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 18.0,
"delta_p": -2.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Alice",
"region": ""
}
}
},
{
"uid": "east edfa in Carol to Bob",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 19.0,
"delta_p": -1.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "east edfa in Carol1 to Bob1",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 19.0,
"delta_p": -1.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "west edfa in Alice to Bob",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 16.5,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Alice",
"region": ""
}
}
},
{
"uid": "west edfa in Bob to Carol",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 17.0,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "west edfa in Bob1 to Carol1",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 17.0,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "west edfa in Carol to Dan",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 17.6,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "west edfa in Dan to Alice",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 13.0,
"delta_p": -1.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Dan",
"region": ""
}
}
},
{
"uid": "west edfa in Bob to Alice",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 16.5,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Bob",
"region": ""
}
}
},
{
"uid": "west edfa in Alice to Dan",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 14.0,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Alice",
"region": ""
}
}
},
{
"uid": "west edfa in Carol to Bob",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 17.0,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
},
{
"uid": "west edfa in Carol1 to Bob1",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 17.0,
"delta_p": 0.0,
"tilt_target": 0.0,
"out_voa": 0.0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "Carol",
"region": ""
}
}
}
],
"connections": [
{
"from_node": "trx Alice",
"to_node": "roadm Alice"
},
{
"from_node": "trx Bob",
"to_node": "roadm Bob"
},
{
"from_node": "trx Bob1",
"to_node": "roadm Bob1"
},
{
"from_node": "trx Carol",
"to_node": "roadm Carol"
},
{
"from_node": "trx Carol1",
"to_node": "roadm Carol1"
},
{
"from_node": "roadm Alice",
"to_node": "east edfa in Alice to Bob"
},
{
"from_node": "roadm Alice",
"to_node": "east edfa in Alice to Dan"
},
{
"from_node": "roadm Alice",
"to_node": "trx Alice"
},
{
"from_node": "roadm Bob",
"to_node": "east edfa in Bob to Alice"
},
{
"from_node": "roadm Bob1",
"to_node": "east edfa in Bob1 to Carol1"
},
{
"from_node": "roadm Bob",
"to_node": "east edfa in Bob to Carol"
},
{
"from_node": "roadm Bob",
"to_node": "trx Bob"
},
{
"from_node": "roadm Bob1",
"to_node": "trx Bob1"
},
{
"from_node": "roadm Carol",
"to_node": "east edfa in Carol to Bob"
},
{
"from_node": "roadm Carol1",
"to_node": "east edfa in Carol1 to Bob1"
},
{
"from_node": "roadm Carol",
"to_node": "east edfa in Carol to Dan"
},
{
"from_node": "roadm Carol",
"to_node": "trx Carol"
},
{
"from_node": "roadm Carol1",
"to_node": "trx Carol1"
},
{
"from_node": "fiber (Alice → Bob)-",
"to_node": "west edfa in Bob to Alice"
},
{
"from_node": "fiber (Bob → Carol)-",
"to_node": "west edfa in Carol to Bob"
},
{
"from_node": "fiber (Bob1 → Carol1)-",
"to_node": "west edfa in Carol1 to Bob1"
},
{
"from_node": "fiber (Carol → Dan)-",
"to_node": "east edfa in Dan to Alice"
},
{
"from_node": "fiber (Dan → Alice)-",
"to_node": "west edfa in Alice to Dan"
},
{
"from_node": "fiber (Bob → Alice)-",
"to_node": "west edfa in Alice to Bob"
},
{
"from_node": "fiber (Carol → Bob)-",
"to_node": "west edfa in Bob to Carol"
},
{
"from_node": "fiber (Carol1 → Bob1)-",
"to_node": "west edfa in Bob1 to Carol1"
},
{
"from_node": "fiber (Dan → Carol)-",
"to_node": "west edfa in Carol to Dan"
},
{
"from_node": "fiber (Alice → Dan)-",
"to_node": "west edfa in Dan to Alice"
},
{
"from_node": "east edfa in Alice to Bob",
"to_node": "fiber (Alice → Bob)-"
},
{
"from_node": "east edfa in Bob to Carol",
"to_node": "fiber (Bob → Carol)-"
},
{
"from_node": "east edfa in Bob1 to Carol1",
"to_node": "fiber (Bob1 → Carol1)-"
},
{
"from_node": "east edfa in Carol to Dan",
"to_node": "fiber (Carol → Dan)-"
},
{
"from_node": "east edfa in Dan to Alice",
"to_node": "fiber (Dan → Alice)-"
},
{
"from_node": "east edfa in Bob to Alice",
"to_node": "fiber (Bob → Alice)-"
},
{
"from_node": "east edfa in Alice to Dan",
"to_node": "fiber (Alice → Dan)-"
},
{
"from_node": "east edfa in Carol to Bob",
"to_node": "fiber (Carol → Bob)-"
},
{
"from_node": "east edfa in Carol1 to Bob1",
"to_node": "fiber (Carol1 → Bob1)-"
},
{
"from_node": "west edfa in Alice to Bob",
"to_node": "roadm Alice"
},
{
"from_node": "west edfa in Bob to Carol",
"to_node": "roadm Bob"
},
{
"from_node": "west edfa in Bob1 to Carol1",
"to_node": "roadm Bob1"
},
{
"from_node": "west edfa in Bob1 to Carol1",
"to_node": "roadm Bob1"
},
{
"from_node": "west edfa in Carol to Dan",
"to_node": "roadm Carol"
},
{
"from_node": "west edfa in Dan to Alice",
"to_node": "fiber (Dan → Carol)-"
},
{
"from_node": "west edfa in Bob to Alice",
"to_node": "roadm Bob"
},
{
"from_node": "west edfa in Alice to Dan",
"to_node": "roadm Alice"
},
{
"from_node": "west edfa in Carol to Bob",
"to_node": "roadm Carol"
},
{
"from_node": "west edfa in Carol1 to Bob1",
"to_node": "roadm Carol1"
}
]
}

View File

@@ -1,53 +0,0 @@
module gnpy-api {
yang-version 1.1;
namespace "gnpy:gnpy-api";
prefix gnpyapi;
import gnpy-network-topology {
prefix gnpynt;
}
import gnpy-path-computation-simplified {
prefix gnpypc;
}
import gnpy-eqpt-config {
prefix gnpyeqpt;
}
organization
"Telecom Infra Project OOPT PSE Working Group";
contact
"WG Web: <https://github.com/Telecominfraproject/oopt-gnpy>
contact: <mailto:ahmed.triki@orange.com>
contact: <mailto:esther.lerouzic@orange.com>
";
description
"YANG model for gnpy api input for path computation - TransportPCE preversion";
revision 2020-10-22 {
description
"draft for experimental/2020-candi";
reference
"YANG model for api input for path computation with gnpy";
}
container service {
description
"Describe the service file to connect to gnpy";
uses gnpypc:service;
}
container result {
uses gnpypc:result;
description
"Describe the response object to gnpy";
}
container topology {
description
"Describe the topology file to connect to gnpy";
uses gnpynt:topo;
}
container equipment {
description
"Describe the equipment library to connect to gnpy";
uses gnpyeqpt:eqpt;
}
}

View File

@@ -1,78 +0,0 @@
module gnpy-api {
yang-version 1.1;
namespace "gnpy:gnpy-api";
prefix gnpyapi;
import gnpy-network-topology {
prefix gnpynt;
}
import gnpy-path-computation-simplified {
prefix gnpypc;
}
import gnpy-eqpt-config {
prefix gnpyeqpt;
}
import ietf-yang-types {
prefix ietftypes;
}
organization
"Telecom Infra Project OOPT PSE Working Group";
contact
"WG Web: <https://github.com/Telecominfraproject/oopt-gnpy>
contact: <mailto:ahmed.triki@orange.com>
contact: <mailto:esther.lerouzic@orange.com>
";
description
"YANG model for gnpy api input for path computation - TransportPCE preversion";
revision 2021-01-06 {
description
"draft for experimental/2020-candi.
Add the possibility to use a topology_id or an equipment_id
";
reference
"YANG model for api input for path computation with gnpy";
}
container service {
description
"Describe the service file to connect to gnpy";
uses gnpypc:service;
}
container result {
uses gnpypc:result;
description
"Describe the response object to gnpy";
}
choice topo {
case explicit {
container topology {
description
"Describe the topology file to connect to gnpy";
uses gnpynt:topo;
}
}
case id {
leaf topology_id {
type ietftypes:uuid;
mandatory true;
}
}
}
choice eqpt {
case explicit {
container equipment {
description
"Describe the equipment library to connect to gnpy";
uses gnpyeqpt:eqpt;
}
}
case id {
leaf equipment_id {
type ietftypes:uuid;
mandatory true;
}
}
}
}

View File

@@ -1,442 +0,0 @@
module gnpy-eqpt-config {
yang-version 1;
namespace "gnpy:gnpy-eqpt-config";
prefix "gnpyeqpt";
organization
"Telecom Infra Project OOPT PSE
Working Group";
contact
"WG Web: <https://github.com/Telecominfraproject/oopt-gnpy>
contact: <mailto:ahmed.triki@orange.com>
contact: <mailto:esther.lerouzic@orange.com>
";
description "Base YANG model for gnpy equipment library input for path computation - 2020 - candi preversion";
revision "2020-10-22" {
description "draft for experimental/2020-candi";
reference "Base YANG model for equipment library input for path computation with gnpy";
}
/*
* Identities
identity edfa-type-def {
description "base identity for variable gain and fixed gain";
}
identity variable-gain{
base edfa-type-def ;
description "'variable_gain' is a simplified model simulating a 2-coil
EDFA with internal, input and output VOAs. The NF vs gain response is calculated
accordingly based on the input parameters: nf_min, nf_max, and gain_flatmax. It
is not a simple interpolation but a 2-stage NF calculation.";
}
identity fixed-gain{
base edfa-type-def ;
description "'fixed_gain' is a fixed gain model. NF == Cte == nf0 if gain_min < gain < gain_flatmax";
}
identity fiber-variety {
description "base identity for fiber variety";
}
identity transceiver-variety {
description "base identity for transceiver variety";
}
*/
grouping variable-gain {
leaf nf_min {
type decimal64 {
fraction-digits 2;
}
units dB;
}
leaf nf_max {
type decimal64 {
fraction-digits 2;
}
units dB;
}
leaf out_voa_auto{
type boolean ;
description "auto_design feature to optimize the amplifier output VOA. If true, output VOA is present
and will be used to push amplifier gain to its maximum, within EOL power margins.";
}
}
grouping fixed-gain{
leaf nf0 {
type decimal64 {
fraction-digits 2;
}
units dB;
}
}
grouping no-type-def{
leaf advanced_config_from_json {
type string ;
description " filename with json edfa";
}
}
grouping openroadm{
leaf-list nf_coef {
type decimal64 {
fraction-digits 5;
}
//default [8.1e-4,6.142e-2,1.558,19.97] ;
}
}
grouping dual-stage {
leaf raman {
type boolean;
}
leaf preamp_variety {
type leafref {
path "../../Edfa/type_variety";
}
}
leaf booster_variety {
type leafref {
path "../../Edfa/type_variety";
}
}
}
grouping edfa-common {
leaf allowed_for_design{
type boolean ;
description "If false, the amplifier will not be picked by auto-design but it can still be used as a
manual input (from JSON or Excel template topology files.)";
}
leaf gain_flatmax {
type decimal64 {
fraction-digits 2;
}
units dB;
}
leaf gain_min {
type decimal64 {
fraction-digits 2;
}
units dB;
}
leaf p_max {
type decimal64 {
fraction-digits 2;
}
units dBm;
}
leaf type_def {
type identityref{
base edfa-type-def ;
}
}
choice type_of_model {
case variable-gain {
when "type_def = 'variable-gain'";
uses variable-gain ;
}
case fixed-gain{
when "type_def = 'fixed-gain'";
uses fixed-gain;
}
case no-type-def{
when "type_def = 'no-type-def'";
uses no-type-def;
}
case openroadm{
when "type_def = 'openroadm'";
uses openroadm;
}
case dual_stage {
when "type_def = 'dual_stage'";
uses dual-stage ;
}
}
}
grouping common-fiber {
description "common parameters for fiber and raman fiber";
leaf type_variety {
type string ;
}
description "a unique name to ID the fiber in the JSON or Excel template topology input file";
leaf dispersion{
type decimal64 {
fraction-digits 8;
}
units s.m-1.m-1;
}
leaf gamma{
type decimal64 {
fraction-digits 8;
}
units w-1.m-1 ;
description "2pi.n2/(lambda*Aeff) (w-2.m-1)";
}
leaf pmd_coef{
type decimal64 {
fraction-digits 16;
}
units s.sqrt(m)-1;
}
}
grouping eqpt{
list Edfa {
key type_variety;
leaf type_variety {
type string;
description "a unique name to ID the amplifier in the JSON/Excel template topology input file";
}
uses edfa-common;
}
list Fiber {
key type_variety;
uses common-fiber;
}
list RamanFiber {
uses common-fiber;
container raman_efficiency {
leaf-list cr {
type decimal64 {
fraction-digits 8;
}
}
leaf-list frequency_offset {
type decimal64 {
fraction-digits 8;
}
}
}
}
list Span {
leaf power_mode {
type boolean ;
}
leaf-list delta_power_range_db {
type decimal64 {
fraction-digits 2;
}
}
leaf max_length {
type decimal64 {
fraction-digits 2;
}
units km;
default 150.0 ;
}
leaf max_loss {
type decimal64 {
fraction-digits 2;
}
units dB;
}
leaf max_fiber_lineic_loss_for_raman {
type decimal64 {
fraction-digits 2;
}
units dB.km-1;
}
leaf target_extended_gain {
type decimal64 {
fraction-digits 2;
}
units dB;
}
leaf length_units{
type string ;
default "km";
}
leaf padding{
type decimal64 {
fraction-digits 2;
}
default 10.0 ;
}
leaf EOL{
type decimal64 {
fraction-digits 2;
}
default 0.0 ;
}
leaf con_in{
type decimal64 {
fraction-digits 2;
}
default 0.0 ;
}
leaf con_out{
type decimal64 {
fraction-digits 2;
}
default 0.0 ;
}
}
list Roadm {
leaf target_pch_out_db {
type decimal64 {
fraction-digits 2;
}
}
leaf add_drop_osnr {
type decimal64 {
fraction-digits 2;
}
}
leaf pmd {
type decimal64 {
fraction-digits 2;
}
}
container restrictions {
leaf-list preamp_variety_list {
type string;
}
leaf-list booster_variety_list {
type string;
}
}
}
list SI {
leaf f_min {
type decimal64 {
fraction-digits 2;
}
}
leaf f_max {
type decimal64 {
fraction-digits 2;
}
}
leaf baud_rate {
type decimal64 {
fraction-digits 2;
}
}
leaf spacing {
type decimal64 {
fraction-digits 2;
}
}
leaf power_dbm {
type decimal64 {
fraction-digits 2;
}
}
leaf-list power_range_db {
type decimal64 {
fraction-digits 2;
}
}
leaf roll_off {
type decimal64 {
fraction-digits 2;
}
}
leaf tx_osnr {
type decimal64 {
fraction-digits 2;
}
}
leaf sys_margins {
type decimal64 {
fraction-digits 2;
}
}
}
list Transceiver {
leaf type_variety {
type string ;
description "a unique name to ID the transceiver in the JSON or Excel template topology input file";
}
container frequency {
leaf min {
type decimal64 {
fraction-digits 2;
}
units Hz ;
}
leaf max {
type decimal64 {
fraction-digits 2;
}
units Hz ;
}
description "Min/max frequency of transponder eg 191.35e12 and 196.1e12";
}
list mode {
leaf format {
type string ;
description "unique name of the mode";
}
leaf baud_rate {
type decimal64 {
fraction-digits 2;
}
units baud ;
description "baud_rate";
}
leaf OSNR {
type decimal64 {
fraction-digits 2;
}
units dB ;
description "min required OSNR in 0.1nm (dB)";
}
leaf tx_osnr {
type decimal64 {
fraction-digits 2;
}
units dB ;
description "min required OSNR in 0.1nm (dB)";
}
leaf min_spacing {
type decimal64 {
fraction-digits 2;
}
units GHz ;
description "...";
}
leaf bit_rate {
type decimal64 {
fraction-digits 2;
}
units bit/s ;
description "bit rate";
}
leaf roll_off {
type decimal64 {
fraction-digits 2;
}
description "...";
}
leaf cost {
type decimal64 {
fraction-digits 2;
}
description "arbitrary unit";
}
}
}
}
}

View File

@@ -1,300 +0,0 @@
module gnpy-network-topology {
yang-version 1.1;
namespace "gnpy:gnpy-network-topology";
prefix gnpynt;
organization
"Telecom Infra Project OOPT PSE Working Group";
contact
"WG Web: <https://github.com/Telecominfraproject/oopt-gnpy>
contact: <mailto:ahmed.triki@orange.com>
contact: <mailto:esther.lerouzic@orange.com>
";
description
"YANG model for gnpy network input for path computation - 2020 - candi preversion";
revision 2020-10-22 {
description
"draft for experimental/2020-candi";
reference
"YANG model for network input for path computation with gnpy";
}
identity type-element {
description
"Base identity for element type";
}
identity Transceiver {
base type-element;
description
" Transceiver element";
}
identity Fiber {
base type-element;
description
"Fiber element (unidirectional)";
}
identity Roadm {
base type-element;
description
"Roadm element";
}
identity Edfa {
base type-element;
description
"Edfa element";
}
identity Fused {
base type-element;
description
"Fused element ; non amplified connection between two fiber spans ;
can be used to model optical distribution frame, or losses due to
connectors or fused in a span";
}
identity length-unit {
description
"length unit";
}
identity km {
base length-unit;
description
"kilometers";
}
identity m {
base length-unit;
description
"meter";
}
typedef Coordinate {
type decimal64 {
fraction-digits 6;
}
}
typedef Coef {
type decimal64 {
fraction-digits 2;
}
}
grouping location-attributes {
container location {
leaf city {
type string;
mandatory true;
}
leaf region {
type string;
mandatory true;
}
leaf latitude {
type Coordinate;
mandatory true;
}
leaf longitude {
type Coordinate;
mandatory true;
}
}
}
grouping fiber-params {
description
".....";
leaf length {
type decimal64 {
fraction-digits 2;
}
mandatory true;
}
leaf loss_coef {
type decimal64 {
fraction-digits 2;
}
mandatory true;
units db/km;
description "Loss coefficient of the fiber span (dB/km)";
}
leaf length_units {
type identityref {
base length-unit;
}
mandatory true;
}
leaf att_in {
type decimal64 {
fraction-digits 2;
}
units "dB";
mandatory true;
}
leaf con_in {
type decimal64 {
fraction-digits 2;
}
units "dB";
mandatory true;
}
leaf con_out {
type decimal64 {
fraction-digits 2;
}
units "dB";
mandatory true;
}
}
grouping edfa-params {
container operational {
description
"Operational values for the Edfa ";
leaf gain_target {
type decimal64 {
fraction-digits 2;
}
units "dB";
mandatory true;
description
"gain target of the amplifier (before VOA and after att_in)";
}
leaf tilt_target {
type decimal64 {
fraction-digits 2;
}
mandatory true;
description
"..";
}
leaf out_voa {
type decimal64 {
fraction-digits 2;
}
units "dB";
mandatory true;
description
"..";
}
leaf delta_p {
type decimal64 {
fraction-digits 2;
}
units "dB";
mandatory true;
description
"per channel target output power delta with respect to power setting in SI";
}
}
}
grouping roadm-params {
leaf target_pch_out_db {
type decimal64 {
fraction-digits 2;
}
units "dB";
description
"..";
}
container restrictions {
leaf-list preamp_variety_list {
type string;
description
"List of authorized preamp type-variety";
}
leaf-list booster_variety_list {
type string;
description
"List of authorized booster type-variety";
}
}
}
grouping transceiver-params;
grouping fused-params{
leaf loss {
type decimal64 {
fraction-digits 2;
}
units "dB";
description
"Concentrated loss of the fused element";
}
}
grouping element-type-choice {
choice element-type {
case Edfa {
when "type = 'Edfa'";
uses edfa-params;
}
case FiberRoadm {
container params {
choice fiberroadmfused {
case Fiber {
when "type = 'Fiber'";
uses fiber-params;
}
case Roadm {
when "type = 'Roadm'";
uses roadm-params;
}
case Fused {
when "type = 'Fused'";
uses fused-params;
}
}
}
}
case Transceiver {
when "type = 'Transceiver'";
}
}
}
grouping topo {
list elements {
key "uid";
leaf uid {
type string;
}
leaf type {
type identityref {
base type-element;
}
mandatory true;
}
leaf type_variety {
type string;
mandatory false;
}
container metadata {
uses location-attributes;
}
uses element-type-choice;
}
list connections {
config false;
leaf from_node {
type leafref {
path "../../elements/uid";
}
}
leaf to_node {
type leafref {
path "../../elements/uid";
}
}
}
}
}

View File

@@ -1,559 +0,0 @@
module gnpy-path-computation-simplified {
yang-version 1.1;
namespace "gnpy:path";
prefix "gnpypc";
organization
"Telecom Infra Project OOPT PSE Working Group";
contact
"WG Web: <https://github.com/Telecominfraproject/oopt-gnpy>
contact: <mailto:ahmed.triki@orange.com>
contact: <mailto:esther.lerouzic@orange.com>
";
description "YANG model for gnpy path computation simplified for - 2020 - candi preversion";
revision "2020-10-22" {
description
"draft for experimental/2020-candi";
reference
"YANG model for path computation with gnpy inputs";
}
grouping effective-freq-slot{
/* content copied from ietf-flexi-grid-media-channel, because only M and N are needed
from the initial grouping.
*/
description "The effective frequency slot is an attribute
of a media channel and, being a frequency slot, it is
described by its nominal central frequency and slot
width";
reference "rfc7698";
leaf N {
type uint32;
description
"Is used to determine the Nominal Central
Frequency. The set of nominal central frequencies
can be built using the following expression:
f = 193.1 THz + n x 0.00625 THz,
where 193.1 THz is ITU-T ''anchor frequency'' for
transmission over the C band, n is a positive or
negative integer including 0.";
reference "rfc7698";
}
leaf M {
type int32;
description
"Is used to determine the slot width. A slot width
is constrained to be M x SWG (that is, M x 12.5 GHz),
where M is an integer greater than or equal to 1.";
reference "rfc7698";
}
}
grouping gnpy-specific-parameters{
description
"This grouping defines the gnpy specific parameters for requests.";
leaf technology {
type string;
default "flexi-grid";
description
"Data plane technology type.";
}
leaf trx_type {
type string ;
mandatory true;
description "name of the transponder type (to be read from equipment library";
}
leaf trx_mode {
type string;
description "name of the transponder mode (to be read from equipment library";
}
list effective-freq-slot {
key "N";
uses effective-freq-slot ;
}
leaf spacing {
mandatory true;
type decimal64 {
fraction-digits 1;
}
units Hz;
description
"It is the spacing between channels assuming full load with
same channels as the requested one. multiple of 12.5 GHz";
}
leaf max-nb-of-channel{
type int32;
description "Nb of channel to take into account for the full load case.
";
}
leaf output-power{
type decimal64 {
fraction-digits 5;
}
units W;
description "optical power setting to be used for the propagation";
}
leaf path_bandwidth{
type decimal64 {
fraction-digits 5;
}
mandatory true;
units bit/s;
description "Capacity required";
}
}
identity SNR-bandwidth {
base path-metric-type;
description
"A metric that records SNR in signal bandwidth";
}
identity OSNR-bandwidth {
base path-metric-type;
description
"A metric that records OSNR in signal bandwidth";
}
identity SNR-0.1nm {
base path-metric-type;
description
"A metric that records SNR in 0.1nm";
}
identity OSNR-0.1nm {
base path-metric-type;
description
"A metric that records OSNR in 0.1nm";
}
identity reference_power {
base path-metric-type;
description
"to be revised";
}
identity path_bandwidth {
base path-metric-type;
description
"to be revised";
}
grouping transponder{
leaf transponder-type {
type string ;
description
"transponder type.";
}
leaf transponder-mode {
type string ;
description
"transponder mode.";
}
}
grouping hop-attribute{
description
"This grouping defines the hop attribute parameters for request or response";
choice hop-type{
case tsp {
container transponder{
uses transponder ;
}
}
case regen {
container regenerator{
leaf regenerator-id{
type string ;
}
uses transponder ;
}
}
case pow {
container optical-power{
leaf optical-power{
type decimal64 {
fraction-digits 5;
}
units W;
description "not used yet. hop output (input??) power";
}
}
}
}
}
identity path-metric-type {
description
"Base identity for path metric type";
}
identity route-usage-type {
description
"Base identity for route usage";
}
identity route-include-ero {
base route-usage-type;
description
"Include ERO from route";
}
identity route-exclude-ero {
base route-usage-type;
description
"Exclude ERO from route";
}
identity route-exclude-srlg {
base route-usage-type;
description
"Exclude SRLG from route";
}
typedef te-hop-type {
type enumeration {
enum LOOSE {
description
"loose hop in an explicit path";
}
enum STRICT {
description
"strict hop in an explicit path";
}
}
description
"enumerated type for specifying loose or strict
paths";
reference "RFC3209: section-4.3.2";
}
typedef te-path-disjointness {
type bits {
bit node {
position 0;
description "Node disjoint.";
}
bit link {
position 1;
description "Link disjoint.";
}
bit srlg {
position 2;
description "SRLG (Shared Risk Link Group) disjoint.";
}
}
description
"Type of the resource disjointness for a TE tunnel path.";
reference
"RFC4872: RSVP-TE Extensions in Support of End-to-End
Generalized Multi-Protocol Label Switching (GMPLS)
Recovery";
} // te-path-disjointness
typedef accumulated-metric-type {
type union {
type uint64;
type decimal64 {
fraction-digits 2;
}
}
description
"type useable for accumulative-value";
}
grouping path-route-objects {
description
"List of EROs to be included or excluded when performing
the path computation.";
container explicit-route-objects {
description
"Container for the route object list";
list route-object-include-exclude {
description
"List of explicit route objects to include or
exclude in path computation";
leaf explicit-route-usage {
type identityref {
base route-usage-type;
}
description "Explicit-route usage.";
}
key "index";
uses explicit-route-hop ;
}
}
}
grouping generic-path-disjointness {
description "Path disjointness grouping";
leaf disjointness {
type te-path-disjointness;
description
"The type of resource disjointness.
Under primary path, disjointness level applies to
all secondary LSPs. Under secondary, disjointness
level overrides the one under primary";
}
}
grouping common-path-constraints-attributes {
description
"Common path constraints configuration grouping";
uses common-constraints_config;
}
grouping generic-path-constraints {
description
"Global named path constraints configuration
grouping";
container path-constraints {
description "TE named path constraints container";
uses common-path-constraints-attributes;
}
}
grouping explicit-route-hop {
description
"The explicit route subobject grouping";
leaf index {
type uint32;
description "ERO subobject index";
}
choice type {
description
"The explicit route subobject type";
case num-unnum-hop {
container num-unnum-hop {
leaf node-id {
//type te-node-id;
type string;
description
"The identifier of a node in the TE topology.";
}
leaf link-tp-id {
//type te-tp-id;
type string;
description
"TE link termination point identifier. The combination
of TE link ID and the TE node ID is used to identify an
unnumbered TE link.";
}
leaf hop-type {
type te-hop-type;
description "strict or loose hop";
}
description
"Numbered and Unnumbered link/node explicit route
subobject";
}
}
case label {
container label-hop {
description "Label hop type";
uses effective-freq-slot;
}
description
"The Label ERO subobject";
}
case hop-attribute{
uses gnpypc:hop-attribute ;
}
}
}
grouping common-constraints_config {
description
"Common constraints grouping that can be set on
a constraint set or directly on the tunnel";
container te-bandwidth {
uses gnpy-specific-parameters ;
description
"A requested bandwidth to use for path computation";
}
}
grouping end-points {
description
"Common grouping to define the TE tunnel end-points";
leaf source {
type string;
description "TE tunnel source address.";
}
leaf destination {
type string;
description "P2P tunnel destination address";
}
leaf src-tp-id {
type string;
description "TE tunnel source termination point identifier.";
}
leaf dst-tp-id {
type string;
description "TE tunnel destination termination point
identifier.";
}
}
grouping synchronization-info {
description "Information for sync";
list synchronization {
key "synchronization-id";
description "sync list";
leaf synchronization-id {
type string;
description "index";
}
container svec {
description
"Synchronization VECtor";
leaf relaxable {
type boolean;
default true;
description
"If this leaf is true, path computation process is free
to ignore svec content.
otherwise it must take into account this svec.";
}
uses generic-path-disjointness;
leaf-list request-id-number {
type string;
description "This list reports the set of M path computation requests that must be synchronized.";
}
}
}
}
grouping path-metric {
description "TE path metric type";
leaf metric-type {
type identityref {
base path-metric-type;
}
description "TE path metric type";
}
leaf accumulative-value {
type decimal64 {
fraction-digits 2;
}
description "TE path metric accumulative value";
}
}
grouping generic-path-properties {
description "TE generic path properties grouping";
container path-properties {
config false;
description "The TE path properties";
list path-metric {
key metric-type;
uses path-metric;
}
list z-a-path-metric {
key metric-type;
uses path-metric;
}
list path-route-objects {
description
"Container for the list of route objects either returned by
the computation engine or actually used by an LSP";
container path-route-object {
description
"List of route objects either returned by the computation
engine or actually used by an LSP";
uses explicit-route-hop;
}
}
}
}
grouping path-info {
uses generic-path-properties;
description "Path computation output information";
}
// adding some blocking reasons and info on path in case of blocking
grouping no-path-info {
description "no-path-info";
container no-path {
presence "Response without path information, due to failure
performing the path computation";
leaf no-path {
type string;
mandatory true ;
description
"returned blocking reasons:
NO_PATH
NO_COMPUTED_SNR
NO_FEASIBLE_BAUDRATE_WITH_SPACING
NO_PATH_WITH_CONSTRAINT
NO_FEASIBLE_MODE
MODE_NOT_FEASIBLE
NO_SPECTRUM
";
}
uses generic-path-properties ;
description "if path computation cannot identify a path,
rpc returns no path.";
}
}
grouping service {
list path-request {
key "request-id";
description "request-list";
leaf request-id {
type string;
mandatory true;
description "Each path computation request is uniquely identified by the request-id-number.";
}
leaf bidirectional {
type boolean;
mandatory true;
description "Specify the bidirectionality of the path";
}
uses end-points;
uses path-route-objects;
uses generic-path-constraints;
}
uses synchronization-info;
}
grouping result {
list response {
key response-id;
config false;
description "response";
leaf response-id {
type string;
description
"The list key that has to reuse request-id-number.";
}
choice response-type {
config false;
description "response-type";
case no-path-case {
uses no-path-info;
}
case path-case {
uses path-info;
description "Path computation service.";
}
}
}
}
}

View File

@@ -141,7 +141,7 @@ location is in **gnpy-transmission-example** folder:
.. code-block:: json-object
"Edfa":[{
"type_variety": "low_noise",
"type_variety": "openroadm_ila_low_noise",
"type_def": "openroadm",
"gain_flatmax": 27,
"gain_min": 12,

View File

@@ -1,18 +1,11 @@
alabaster>=0.7.12,<1
docutils==0.15.2
matplotlib>=3.1.0,<4
networkx>=2.3,<3
numpy>=1.16.1,<2
pandas==0.24.2
pbr>=5.4.4,<6
Pygments>=2.4.2,<3
scipy>=1.3.0,<2
Sphinx>=2.4.4,<3
sphinxcontrib-bibtex>=0.4.2,<1
# 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
flask>=1.1.2
gnpy~=2.2.0
cryptography~=3.3.1
Werkzeug~=1.0.1
setuptools~=50.3.1
Flask-Injector

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