106 Commits

Author SHA1 Message Date
EstherLerouzic
c80cf84ac6 Change N to int32 and M to uint32
Correction, because N can be negative, and M must be positive

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I0819f23fa14dd774b7b0c760b5071d08d14e300b
2021-05-03 14:22:26 +02:00
manuedelf
40bbb9b553 Update readme
update docker tag in readme

Signed-off-by: manuedelf <59697943+edelfour@users.noreply.github.com>
2021-04-12 21:13:40 +02:00
EstherLerouzic
c5a29d7b81 change behaviour on wrong user input: return a error code instead of raising an exception
When user submits a request having a spacing smaller than the mode minimum spacing,
the consistent check raises an exception for bad service. This behaviour assumes that
the user knows the modes characteristics.
Instead it makes more sense to return an error code in the case when the user doesn't know
about the library details but only on modes names.
this is usefull for CANDI-optical POC use case

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ic7e02566a0cd5750a07752db7c318c29c62b3e1f
2021-04-12 17:16:27 +02:00
EstherLerouzic
2fa0dac2f1 correct test with missing effective_freq_slot params
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ie66d2a6a0d0e3f3d7e1441880ea8d851d337dcd2
2021-04-12 17:16:15 +02:00
EstherLerouzic
02916691c7 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-04-12 16:37:48 +02:00
EstherLerouzic
5d3526a74c Add the possibilty to input id instead of explicit topo or eqpt
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I81c9fd56773e6f998bc2bdf87fc2aef817e252a4
2021-04-12 15:45:13 +02:00
manuedelf
459a82150b add autodesign routes
Signed-off-by: manuedelf <59697943+edelfour@users.noreply.github.com>
2021-04-12 15:45:13 +02:00
manuedelf
f67e9c4914 fix error in if
Signed-off-by: manuedelf <59697943+edelfour@users.noreply.github.com>
2021-04-12 15:45:13 +02:00
manuedelf
0c2bf58080 Add equipments and topolgies endpoints
- add POST, PUT, DELETE on equipments
- add POST, PUT, GET, DELETE on topogies
- path-computation request body can now have equipment id and/or
topology id instead of full data
- activate embedded https of Flask while waiting for real trusted
certificate
- update readme
- add request payload samples in yang directory
- equipment data are encrypted with Fernet

Signed-off-by: manuedelf <59697943+edelfour@users.noreply.github.com>
2021-04-12 15:45:13 +02:00
manuedelf
396020eeb5 Put api in a dedicated python package
Signed-off-by: manuedelf <59697943+edelfour@users.noreply.github.com>
2021-04-12 15:45:13 +02:00
manuedelf
84fd574df9 remove obsolete example 2021-04-12 15:45:13 +02:00
EstherLerouzic
e97d20c3fe add an example request (with answers)
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Id75fff88cd3b03fcf965c22763075ac3dbea41c6
2021-04-12 15:45:13 +02:00
EstherLerouzic
f52ff92918 add 'gnpy-api:' context when reading the content of the request
in order to be compliant with yang

Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: Ifa6ab93025b18a5a678b625e42e3d351499c69d7
2021-04-12 15:45:13 +02:00
EstherLerouzic
4707abd2bd adding yang corresponding to the json inputs
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I75b0cc3c3ce84dc724e588f918bddf0a5a97225d
2021-04-12 15:45:13 +02:00
EstherLerouzic
5b824a7a2c support missing trx_mode in request instead of null value
Signed-off-by: EstherLerouzic <esther.lerouzic@orange.com>
Change-Id: I5c05b17b0b134c7782a08e86015dc30c7c9b3713
2021-04-12 15:45:13 +02:00
EstherLerouzic
3784627bc9 Change N values from 0 to None in case of NO_SPECTRUM
in case spectrum can not be assigned default values for N is 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-04-12 15:45:13 +02:00
EstherLerouzic
1b265a3d27 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-04-12 15:45:13 +02:00
EstherLerouzic
927d73c720 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-04-12 15:45:13 +02:00
manuedelf
1cf90a019b docker image update + readme 2021-04-12 15:45:13 +02:00
manuedelf
e2fac13219 Rest api for GNPy 2021-04-12 15:45:13 +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
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
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
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
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
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
94 changed files with 17154 additions and 4551 deletions

View File

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

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
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/p/Telecominfraproject/oopt-gnpy/+/dashboard/main:main).
Just sign in via your existing GitHub account.
However, if you feel more comfortable with filing GitHub PRs, we can work with that too.

2
.gitignore vendored
View File

@@ -65,3 +65,5 @@ 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

View File

@@ -1,13 +1,17 @@
dist: xenial
sudo: false
dist: focal
os: linux
language: python
services: docker
python:
- "3.6"
- "3.7"
- "3.8"
- "3.9"
before_install:
- sudo apt-get -y install graphviz
install: skip
script:
- python setup.py develop
- pip install --editable .
- pip install pytest-cov rstcheck
- pytest --cov-report=xml --cov=gnpy -v
- rstcheck --ignore-roles cite *.rst

View File

@@ -2,23 +2,24 @@
- project:
check:
jobs:
- tox-py36-cover
- tox-py38-cover
- coverage-diff:
voting: false
dependencies:
- tox-py36-cover-previous
- tox-py36-cover
- tox-py38-cover-previous
- tox-py38-cover
vars:
coverage_job_name_previous: tox-py36-cover-previous
coverage_job_name_current: tox-py36-cover
coverage_job_name_previous: tox-py38-cover-previous
coverage_job_name_current: tox-py38-cover
- tox-linters-diff:
voting: false
- tox-docs-el8
- tox-py36-cover-previous
- tox-py36-el8
- tox-docs-f32
- tox-py38-cover-previous
gate:
jobs:
- tox-py36-el8
- tox-docs-el8
- tox-py38-f32
- tox-docs-f32
tag:
jobs:
- oopt-release-python:

View File

@@ -1,8 +1,18 @@
FROM python:3.7-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"]
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"]
CMD ["/bin/bash"]

View File

@@ -7,7 +7,7 @@
`gnpy`: mesh optical network route planning and optimization library
====================================================================
|docs| |travis| |doi| |contributors| |codacy-quality| |codecov|
|pypi| |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.**
@@ -70,7 +70,7 @@ example, to use the CORONET Global network defined in
$ 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>`_).
`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>`__.
@@ -128,6 +128,36 @@ As a result transponder type is not part of the network info. it is related to t
The current version includes a spectrum assigment features that enables to compute a candidate spectrum assignment for each service based on a first fit policy. Spectrum is assigned based on service specified spacing value, path_bandwidth value and selected mode for the transceiver. This spectrum assignment includes a basic capacity planning capability so that the spectrum resource is limited by the frequency min and max values defined for the links. If the requested services reach the link spectrum capacity, additional services feasibility are computed but marked as blocked due to spectrum reason.
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.1 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
------------
@@ -143,7 +173,8 @@ To get involved, please contact Jan Kundrát
See the `Onboarding Guide
<https://github.com/Telecominfraproject/gnpy/wiki/Onboarding-Guide>`_ for
specific details on code contributions.
specific details on code contributions, or just `upload patches to our Gerrit
<https://review.gerrithub.io/Documentation/intro-gerrit-walkthrough-github.html>`_.
See `AUTHORS.rst <AUTHORS.rst>`_ for past and present contributors.
@@ -194,8 +225,8 @@ implementations.
:alt: Build Status via Travis CI
:scale: 100%
.. |doi| image:: https://zenodo.org/badge/96894149.svg
:target: https://zenodo.org/badge/latestdoi/96894149
.. |doi| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.3458319.svg
:target: https://doi.org/10.5281/zenodo.3458319
:alt: DOI
:scale: 100%
@@ -214,6 +245,12 @@ implementations.
:alt: Code Coverage via codecov
:scale: 100%
.. |pypi| image:: https://img.shields.io/pypi/v/gnpy
:target: https://pypi.org/project/gnpy/
:alt: Install via PyPI
:scale: 100%
TIP OOPT/PSE & PSE WG Charter
-----------------------------

1
bindep.txt Normal file
View File

@@ -0,0 +1 @@
graphviz

269
docs/concepts.rst Normal file
View File

@@ -0,0 +1,269 @@
.. _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**.
.. _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 :ref:`incremental OSNR as used in OpenROADM<ext-nf-model-polynomial-OSNR-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,12 @@ 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',
'pbr.sphinxext',
'sphinx.ext.graphviz',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -50,8 +52,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 +152,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 +183,5 @@ autodoc_default_options = {
'private-members': True,
'show-inheritance': True,
}
graphviz_output_format = 'svg'

View File

@@ -121,15 +121,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 +143,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)
::
@@ -196,7 +198,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::

161
docs/extending.rst Normal file
View File

@@ -0,0 +1,161 @@
.. _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>`__.
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_\text{max} - G)
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)
*********************************
This model is useful for amplifiers compliant to the OpenROADM specification for ILA.
In OpenROADM, 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-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.

View File

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

View File

@@ -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

View File

@@ -7,10 +7,9 @@ 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.)
@@ -23,7 +22,7 @@ can be added and existing ones removed. Three different noise models are availab
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.
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 <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:
@@ -73,7 +72,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 |
@@ -113,8 +112,47 @@ The modes are defined as follows:
| ``cost`` | (number) | Arbitrary unit |
+----------------------+-----------+-----------------------------------------+
Simulation 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. |
+--------------------------+-----------+---------------------------------------------+
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
@@ -256,42 +294,6 @@ 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
~~~~~~~~~~~~~~~~~~~

View File

@@ -1,3 +1,5 @@
.. _physical-model:
Physical Model used in GNPy
===========================

9
gnpy/api/__init__.py Normal file
View File

@@ -0,0 +1,9 @@
# 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

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

View File

@@ -0,0 +1,14 @@
# 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

@@ -0,0 +1,14 @@
# 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

@@ -0,0 +1,33 @@
# 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

@@ -0,0 +1,14 @@
# 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

@@ -0,0 +1,14 @@
# 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

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

17
gnpy/api/model/error.py Normal file
View File

@@ -0,0 +1,17 @@
# 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

8
gnpy/api/model/result.py Normal file
View File

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

83
gnpy/api/rest_example.py Normal file
View File

@@ -0,0 +1,83 @@
#!/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

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

View File

@@ -0,0 +1,38 @@
# 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

@@ -0,0 +1,63 @@
# 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

@@ -0,0 +1,7 @@
# 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

@@ -0,0 +1,43 @@
# 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

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

View File

@@ -0,0 +1,45 @@
# 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

@@ -0,0 +1,13 @@
# 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

@@ -0,0 +1,66 @@
# 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

@@ -0,0 +1,100 @@
# -*- 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

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

View File

@@ -0,0 +1,62 @@
# 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,7 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
"""
gnpy.core.elements
==================
@@ -18,10 +18,9 @@ Network elements MUST implement two attributes :py:attr:`uid` and :py:attr:`name
unique identifier and a printable name, and provide the :py:meth:`__call__` method taking a
:class:`SpectralInformation` as an input and returning another :class:`SpectralInformation`
instance as a result.
'''
"""
from numpy import abs, arange, array, divide, errstate, ones
from numpy import interp, mean, pi, polyfit, polyval, sum, sqrt
from numpy import abs, arange, array, divide, errstate, ones, interp, mean, pi, polyfit, polyval, sum, sqrt
from scipy.constants import h, c
from collections import namedtuple
@@ -184,17 +183,20 @@ class Transceiver(_Node):
return spectral_info
RoadmParams = namedtuple('RoadmParams', 'target_pch_out_db add_drop_osnr pmd restrictions')
RoadmParams = namedtuple('RoadmParams', 'target_pch_out_db add_drop_osnr pmd restrictions per_degree_pch_out_db')
class Roadm(_Node):
def __init__(self, *args, params, **kwargs):
if 'per_degree_pch_out_db' not in params.keys():
params['per_degree_pch_out_db'] = {}
super().__init__(*args, params=RoadmParams(**params), **kwargs)
self.loss = 0 # auto-design interest
self.effective_loss = None
self.effective_pch_out_db = self.params.target_pch_out_db
self.passive = True
self.restrictions = self.params.restrictions
self.per_degree_pch_out_db = self.params.per_degree_pch_out_db
@property
def to_json(self):
@@ -202,8 +204,9 @@ class Roadm(_Node):
'type': type(self).__name__,
'params': {
'target_pch_out_db': self.effective_pch_out_db,
'restrictions': self.restrictions
},
'restrictions': self.restrictions,
'per_degree_pch_out_db': self.per_degree_pch_out_db
},
'metadata': {
'location': self.metadata['location']._asdict()
}
@@ -220,15 +223,21 @@ class Roadm(_Node):
f' effective loss (dB): {self.effective_loss:.2f}',
f' pch out (dBm): {self.effective_pch_out_db!r}'])
def propagate(self, pref, *carriers):
def propagate(self, pref, *carriers, degree):
# pin_target and loss are read from eqpt_config.json['Roadm']
# all ingress channels in xpress are set to this power level
# but add channels are not, so we define an effective loss
# in the case of add channels
self.effective_pch_out_db = min(pref.p_spani, self.params.target_pch_out_db)
# find the target power on this degree:
# if a target power has been defined for this degree use it else use the global one.
# if the input power is lower than the target one, use the input power instead because
# a ROADM doesn't amplify, it can only attenuate
# TODO maybe add a minimum loss for the ROADM
per_degree_pch = self.per_degree_pch_out_db[degree] if degree in self.per_degree_pch_out_db.keys() else self.params.target_pch_out_db
self.effective_pch_out_db = min(pref.p_spani, per_degree_pch)
self.effective_loss = pref.p_spani - self.effective_pch_out_db
carriers_power = array([c.power.signal + c.power.nli + c.power.ase for c in carriers])
carriers_att = list(map(lambda x: lin2db(x * 1e3) - self.params.target_pch_out_db, carriers_power))
carriers_att = list(map(lambda x: lin2db(x * 1e3) - per_degree_pch, carriers_power))
exceeding_att = -min(list(filter(lambda x: x < 0, carriers_att)), default=0)
carriers_att = list(map(lambda x: db2lin(x + exceeding_att), carriers_att))
for carrier_att, carrier in zip(carriers_att, carriers):
@@ -242,8 +251,8 @@ class Roadm(_Node):
def update_pref(self, pref):
return pref._replace(p_span0=pref.p_span0, p_spani=self.effective_pch_out_db)
def __call__(self, spectral_info):
carriers = tuple(self.propagate(spectral_info.pref, *spectral_info.carriers))
def __call__(self, spectral_info, degree):
carriers = tuple(self.propagate(spectral_info.pref, *spectral_info.carriers, degree=degree))
pref = self.update_pref(spectral_info.pref)
return spectral_info._replace(carriers=carriers, pref=pref)
@@ -303,8 +312,6 @@ class Fiber(_Node):
if not params:
params = {}
super().__init__(*args, params=FiberParams(**params), **kwargs)
self.carriers_in = None
self.carriers_out = None
self.pch_out_db = None
self.nli_solver = NliSolver(self)
@@ -360,22 +367,6 @@ class Fiber(_Node):
def passive(self):
return True
def carriers(self, loc, attr):
"""retrieve carriers information
:param loc: (in, out) of the class element
:param attr: (ase, nli, signal, total) power information
"""
if not (loc in ('in', 'out') and attr in ('nli', 'signal', 'total', 'ase')):
yield None
return
loc_attr = 'carriers_' + loc
for c in getattr(self, loc_attr):
if attr == 'total':
yield c.power.ase + c.power.nli + c.power.signal
else:
yield c.power._asdict().get(attr, None)
def alpha(self, frequencies):
"""It returns the values of the series expansion of attenuation coefficient alpha(f) for all f in frequencies
@@ -418,11 +409,11 @@ class Fiber(_Node):
return self.params.pmd_coef * sqrt(self.params.length)
def _gn_analytic(self, carrier, *carriers):
"""Computes the nonlinear interference power on a single carrier.
r"""Computes the nonlinear interference power on a single carrier.
The method uses eq. 120 from `arXiv:1209.0394 <https://arxiv.org/abs/1209.0394>`__.
:param carrier: the signal under analysis
:param carriers: the full WDM comb
:param \*carriers: the full WDM comb
:return: carrier_nli: the amount of nonlinear interference in W on the under analysis
"""
@@ -478,10 +469,8 @@ class Fiber(_Node):
return pref._replace(p_span0=pref.p_span0, p_spani=self.pch_out_db)
def __call__(self, spectral_info):
self.carriers_in = spectral_info.carriers
carriers = tuple(self.propagate(*spectral_info.carriers))
pref = self.update_pref(spectral_info.pref)
self.carriers_out = carriers
return spectral_info._replace(carriers=carriers, pref=pref)
@@ -495,16 +484,18 @@ class RamanFiber(Fiber):
self.raman_pumps = None
self.raman_solver = RamanSolver(self)
@property
def to_json(self):
return dict(super().to_json, operational=self.operational)
def update_pref(self, pref, *carriers):
pch_out_db = lin2db(mean([carrier.power.signal for carrier in carriers])) + 30
self.pch_out_db = round(pch_out_db, 2)
return pref._replace(p_span0=pref.p_span0, p_spani=self.pch_out_db)
def __call__(self, spectral_info):
self.carriers_in = spectral_info.carriers
carriers = tuple(self.propagate(*spectral_info.carriers))
pref = self.update_pref(spectral_info.pref, *carriers)
self.carriers_out = carriers
return spectral_info._replace(carriers=carriers, pref=pref)
def propagate(self, *carriers):
@@ -586,8 +577,6 @@ class Edfa(_Node):
self.effective_pch_out_db = None
self.passive = False
self.att_in = None
self.carriers_in = None
self.carriers_out = None
self.effective_gain = self.operational.gain_target
self.delta_p = self.operational.delta_p # delta P with Pref (power swwep) in power mode
self.tilt_target = self.operational.tilt_target
@@ -639,22 +628,6 @@ class Edfa(_Node):
f' effective pch (dBm): {self.effective_pch_out_db!r}',
f' output VOA (dB): {self.out_voa:.2f}'])
def carriers(self, loc, attr):
"""retrieve carriers information
:param loc: (in, out) of the class element
:param attr: (ase, nli, signal, total) power information
"""
if not (loc in ('in', 'out') and attr in ('nli', 'signal', 'total', 'ase')):
yield None
return
loc_attr = 'carriers_' + loc
for c in getattr(self, loc_attr):
if attr == 'total':
yield c.power.ase + c.power.nli + c.power.signal
else:
yield c.power._asdict().get(attr, None)
def interpol_params(self, frequencies, pin, baud_rates, pref):
"""interpolate SI channel frequencies with the edfa dgt and gain_ripple frquencies from JSON
"""
@@ -714,8 +687,6 @@ class Edfa(_Node):
nf_avg = pin_ch - polyval(nf_model.nf_coef, pin_ch) + 58
elif type_def == 'advanced_model':
nf_avg = polyval(nf_fit_coeff, -dg)
else:
assert False, "Unrecognized amplifier type, this should have been checked by the JSON loader"
return nf_avg + pad, pad
def _calc_nf(self, avg=False):
@@ -934,8 +905,6 @@ class Edfa(_Node):
p_spani=pref.p_spani + self.effective_gain - self.out_voa)
def __call__(self, spectral_info):
self.carriers_in = spectral_info.carriers
carriers = tuple(self.propagate(spectral_info.pref, *spectral_info.carriers))
pref = self.update_pref(spectral_info.pref)
self.carriers_out = carriers
return spectral_info._replace(carriers=carriers, pref=pref)

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,13 +1,12 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
"""
gnpy.core.info
==============
This module contains classes for modelling :class:`SpectralInformation`.
'''
"""
from collections import namedtuple
from gnpy.core.utils import automatic_nch, lin2db

View File

@@ -8,7 +8,6 @@ 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 gnpy.core.exceptions import ConfigurationError, NetworkTopologyError
@@ -138,6 +137,9 @@ def select_edfa(raman_allowed, gain_target, power_target, equipment, uid, restri
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,13 +149,10 @@ 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
@@ -161,7 +160,7 @@ def prev_node_generator(network, node):
"""fused spans interest:
iterate over all predecessors while they are Fused or Fiber type"""
try:
prev_node = next(n for n in network.predecessors(node))
prev_node = next(network.predecessors(node))
except StopIteration:
raise NetworkTopologyError(f'Node {node.uid} is not properly connected, please check network topology')
# yield and re-iterate
@@ -176,7 +175,7 @@ def next_node_generator(network, node):
"""fused spans interest:
iterate over all successors while they are Fused or Fiber type"""
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
@@ -192,13 +191,13 @@ def span_loss(network, node):
return the total span loss of all the fibers spliced by a Fused node"""
loss = node.loss if node.passive else 0
try:
prev_node = next(n for n in network.predecessors(node))
prev_node = next(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))
next_node = next(network.successors(node))
if isinstance(next_node, elements.Fused):
loss += sum(n.loss for n in next_node_generator(network, node))
except StopIteration:
@@ -229,10 +228,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,24 +239,39 @@ 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))
next_oms = (n for n in network.successors(this_node) if not isinstance(n, elements.Transceiver))
this_node_degree = {k: v for k, v in this_node.per_degree_pch_out_db.items()} if hasattr(this_node, 'per_degree_pch_out_db') else {}
for oms in next_oms:
# go through all the OMS departing from the Roadm
node = roadm
prev_node = roadm
next_node = oms
# go through all the OMS departing from the ROADM
prev_node = this_node
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)
if node.uid not in this_node_degree:
# if no target power is defined on this degree or no per degree target power is given use the global one
# if target_pch_out_db is not an attribute, then the element must be a transceiver
this_node_degree[node.uid] = getattr(this_node.params, 'target_pch_out_db', 0)
# use the target power on this degree
prev_dp = this_node_degree[node.uid] - 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
@@ -265,20 +279,20 @@ def set_egress_amplifier(network, roadm, equipment, pref_total_db):
dp = target_power(network, next_node, equipment)
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
else:
raman_allowed = False
# implementation of restrictions on roadm boosters
if isinstance(prev_node, elements.Roadm):
@@ -303,19 +317,30 @@ def set_egress_amplifier(network, roadm, equipment, pref_total_db):
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 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
print(f'{ansi_escapes.red}WARNING{ansi_escapes.reset}: '
f'WARNING: effective gain in Node {node.uid} is above user '
f'specified amplifier {node.params.type_variety}\n'
f'max flat gain: {equipment["Edfa"][node.params.type_variety].gain_flatmax}dB ; '
f'required gain: {gain_target}dB. Please check amplifier type.')
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))
if isinstance(this_node, elements.Roadm):
this_node.per_degree_pch_out_db = {k: v for k, v in this_node_degree.items()}
def add_egress_amplifier(network, node):
next_nodes = [n for n in network.successors(node)
@@ -328,8 +353,8 @@ def add_egress_amplifier(network, node):
params={},
metadata={
'location': {
'latitude': (node.lat * 2 + next_node.lat * 2) / 4,
'longitude': (node.lng * 2 + next_node.lng * 2) / 4,
'latitude': (node.lat + next_node.lat) / 2,
'longitude': (node.lng + next_node.lng) / 2,
'city': node.loc.city,
'region': node.loc.region,
}
@@ -351,24 +376,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 target_length - length1 < length2 - target_length:
return (length1, n_spans1)
else:
result = result1 if delta1 < delta2 else result2
return result
return (length2, n_spans2)
def split_fiber(network, fiber, bounds, target_length, equipment):
@@ -386,9 +407,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)
ypos = [prev_node.lat + (next_node.lat - prev_node.lat) * (n + 1) / (n_spans + 1) 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 +436,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,22 +454,21 @@ 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):
@@ -455,14 +477,11 @@ def build_network(network, equipment, pref_ch_db, pref_total_db):
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
# 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)
add_fiber_padding(network, fibers, default_span_data.padding)
# don't group split fiber and add amp in the same loop
# =>for code clarity (at the expense of speed):
for fiber in fibers:
@@ -473,12 +492,12 @@ def build_network(network, equipment, pref_ch_db, pref_total_db):
for node in amplified_nodes:
add_egress_amplifier(network, node)
roadms = [r for r in network.nodes() if isinstance(r, elements.Roadm)]
roadms = [r for r in amplified_nodes if isinstance(r, elements.Roadm)]
for roadm in roadms:
set_egress_amplifier(network, roadm, equipment, pref_total_db)
set_egress_amplifier(network, roadm, equipment, pref_ch_db, 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)
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,13 +6,11 @@ gnpy.core.parameters
====================
This module contains all parameters to configure standard network elements.
"""
from scipy.constants import c, pi
from numpy import squeeze, log10, exp
from gnpy.core.utils import db2lin, convert_length
from gnpy.core.exceptions import ParametersError

View File

@@ -1,4 +1,17 @@
import numpy as np
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
gnpy.core.science_utils
=======================
Solver definitions to calculate the Raman effect and the nonlinear interference noise
The solvers take as input instances of the spectral information, the fiber and the simulation parameters
"""
from numpy import interp, pi, zeros, shape, where, cos, reshape, array, append, ones, argsort, nan, exp, arange, sqrt, \
empty, vstack, trapz, arcsinh, clip, abs, sum
from operator import attrgetter
from logging import getLogger
import scipy.constants as ph
@@ -11,7 +24,6 @@ from math import isclose
from gnpy.core.utils import db2lin, lin2db
from gnpy.core.exceptions import EquipmentConfigError
logger = getLogger(__name__)
@@ -65,7 +77,7 @@ def propagate_raman_fiber(fiber, *carriers):
new_carriers = []
for carrier, attenuation, rmn_ase in zip(carriers, fiber_attenuation, raman_ase):
carrier_nli = np.interp(carrier.frequency, nli_frequencies, computed_nli)
carrier_nli = interp(carrier.frequency, nli_frequencies, computed_nli)
pwr = carrier.power
pwr = pwr._replace(signal=pwr.signal / attenuation / attenuation_out,
nli=(pwr.nli + carrier_nli) / attenuation / attenuation_out,
@@ -83,10 +95,10 @@ def frequency_resolution(carrier, carriers, sim_params, fiber):
return res_dict[method], method, res_dict
def _get_freq_res_dispersion_attenuation(delta_count, grid_size, alpha0, beta2, k_tol):
return k_tol * abs(alpha0) / abs(beta2) / (1 + delta_count) / (4 * np.pi ** 2 * grid_size)
return k_tol * abs(alpha0) / abs(beta2) / (1 + delta_count) / (4 * pi ** 2 * grid_size)
def _get_freq_res_phase_rotation(delta_count, grid_size, delta_z, beta2, phi_tol):
return phi_tol / abs(beta2) / (1 + delta_count) / delta_z / (4 * np.pi ** 2 * grid_size)
return phi_tol / abs(beta2) / (1 + delta_count) / delta_z / (4 * pi ** 2 * grid_size)
grid_size = sim_params.nli_params.wdm_grid_size
delta_z = sim_params.raman_params.space_resolution
@@ -117,20 +129,20 @@ def raised_cosine_comb(f, *carriers):
:param carriers: namedtuple describing the WDM comb
:return: PSD of the WDM comb evaluated over f
"""
psd = np.zeros(np.shape(f))
psd = zeros(shape(f))
for carrier in carriers:
f_nch = carrier.frequency
g_ch = carrier.power.signal / carrier.baud_rate
ts = 1 / carrier.baud_rate
passband = (1 - carrier.roll_off) / (2 / carrier.baud_rate)
stopband = (1 + carrier.roll_off) / (2 / carrier.baud_rate)
ff = np.abs(f - f_nch)
tf = ff - passband
pass_band = (1 - carrier.roll_off) / (2 / carrier.baud_rate)
stop_band = (1 + carrier.roll_off) / (2 / carrier.baud_rate)
ff = abs(f - f_nch)
tf = ff - pass_band
if carrier.roll_off == 0:
psd = np.where(tf <= 0, g_ch, 0.) + psd
psd = where(tf <= 0, g_ch, 0.) + psd
else:
psd = g_ch * (np.where(tf <= 0, 1., 0.) + 1 / 2 * (1 + np.cos(np.pi * ts / carrier.roll_off * tf)) *
np.where(tf > 0, 1., 0.) * np.where(np.abs(ff) <= stopband, 1., 0.)) + psd
psd = g_ch * (where(tf <= 0, 1., 0.) + 1 / 2 * (1 + cos(pi * ts / carrier.roll_off * tf)) *
where(tf > 0, 1., 0.) * where(abs(ff) <= stop_band, 1., 0.)) + psd
return psd
@@ -227,13 +239,13 @@ class RamanSolver:
alphap_fiber = self.fiber.alpha(freq_array)
freq_diff = abs(freq_array - np.reshape(freq_array, (len(freq_array), 1)))
freq_diff = abs(freq_array - reshape(freq_array, (len(freq_array), 1)))
interp_cr = interp1d(raman_efficiency['frequency_offset'], raman_efficiency['cr'])
cr = interp_cr(freq_diff)
# z propagation axis
z_array = self.stimulated_raman_scattering.z
ase_bc = np.zeros(freq_array.shape)
ase_bc = zeros(freq_array.shape)
# calculate ase power
int_spontaneous_raman = self._int_spontaneous_raman(z_array, self._stimulated_raman_scattering.power,
@@ -254,28 +266,28 @@ class RamanSolver:
"""
# Signal power spectrum
pow_array = np.array([])
f_array = np.array([])
noise_bandwidth_array = np.array([])
pow_array = array([])
f_array = array([])
noise_bandwidth_array = array([])
for carrier in sorted(carriers, key=attrgetter('frequency')):
f_array = np.append(f_array, carrier.frequency)
pow_array = np.append(pow_array, carrier.power.signal)
f_array = append(f_array, carrier.frequency)
pow_array = append(pow_array, carrier.power.signal)
ref_bw = carrier.baud_rate
noise_bandwidth_array = np.append(noise_bandwidth_array, ref_bw)
noise_bandwidth_array = append(noise_bandwidth_array, ref_bw)
propagation_direction = np.ones(len(f_array))
propagation_direction = ones(len(f_array))
# Raman pump power spectrum
if raman_pumps:
for pump in raman_pumps:
pow_array = np.append(pow_array, pump.power)
f_array = np.append(f_array, pump.frequency)
pow_array = append(pow_array, pump.power)
f_array = append(f_array, pump.frequency)
direction = +1 if pump.propagation_direction.lower() == 'coprop' else -1
propagation_direction = np.append(propagation_direction, direction)
noise_bandwidth_array = np.append(noise_bandwidth_array, ref_bw)
propagation_direction = append(propagation_direction, direction)
noise_bandwidth_array = append(noise_bandwidth_array, ref_bw)
# Final sorting
ind = np.argsort(f_array)
ind = argsort(f_array)
f_array = f_array[ind]
pow_array = pow_array[ind]
propagation_direction = propagation_direction[ind]
@@ -293,27 +305,26 @@ class RamanSolver:
h = ph.value('Planck constant')
kb = ph.value('Boltzmann constant')
power_ase = np.nan * np.ones(raman_matrix.shape)
power_ase = nan * ones(raman_matrix.shape)
int_pump = cumtrapz(raman_matrix, z_array, dx=dx, axis=1, initial=0)
for f_ind, f_ase in enumerate(freq_array):
cr_raman = cr_raman_matrix[f_ind, :]
vibrational_loss = f_ase / freq_array[:f_ind]
eta = 1 / (np.exp((h * freq_diff[f_ind, f_ind + 1:]) / (kb * temperature)) - 1)
eta = 1 / (exp((h * freq_diff[f_ind, f_ind + 1:]) / (kb * temperature)) - 1)
int_fiber_loss = -alphap_fiber[f_ind] * z_array
int_raman_loss = np.sum((cr_raman[:f_ind] * vibrational_loss * int_pump[:f_ind, :].transpose()).transpose(),
int_raman_loss = sum((cr_raman[:f_ind] * vibrational_loss * int_pump[:f_ind, :].transpose()).transpose(),
axis=0)
int_raman_gain = np.sum((cr_raman[f_ind + 1:] * int_pump[f_ind + 1:, :].transpose()).transpose(), axis=0)
int_raman_gain = sum((cr_raman[f_ind + 1:] * int_pump[f_ind + 1:, :].transpose()).transpose(), axis=0)
int_gain_loss = int_fiber_loss + int_raman_gain + int_raman_loss
new_ase = np.sum((cr_raman[f_ind + 1:] * (1 + eta) * raman_matrix[f_ind + 1:, :].transpose()).transpose()
new_ase = sum((cr_raman[f_ind + 1:] * (1 + eta) * raman_matrix[f_ind + 1:, :].transpose()).transpose()
* h * f_ase * bn_array[f_ind], axis=0)
bc_evolution = ase_bc[f_ind] * np.exp(int_gain_loss)
ase_evolution = np.exp(int_gain_loss) * cumtrapz(new_ase *
np.exp(-int_gain_loss), z_array, dx=dx, initial=0)
bc_evolution = ase_bc[f_ind] * exp(int_gain_loss)
ase_evolution = exp(int_gain_loss) * cumtrapz(new_ase * exp(-int_gain_loss), z_array, dx=dx, initial=0)
power_ase[f_ind, :] = bc_evolution + ase_evolution
@@ -332,7 +343,7 @@ class RamanSolver:
sim_params = simulation.sim_params
if not sim_params.raman_params.flag_raman:
raman_efficiency['cr'] = np.zeros(len(raman_efficiency['cr']))
raman_efficiency['cr'] = zeros(len(raman_efficiency['cr']))
# raman solver parameters
z_resolution = sim_params.raman_params.space_resolution
tolerance = sim_params.raman_params.tolerance
@@ -343,12 +354,12 @@ class RamanSolver:
alphap_fiber = self.fiber.alpha(freq_array)
freq_diff = abs(freq_array - np.reshape(freq_array, (len(freq_array), 1)))
freq_diff = abs(freq_array - reshape(freq_array, (len(freq_array), 1)))
interp_cr = interp1d(raman_efficiency['frequency_offset'], raman_efficiency['cr'])
cr = interp_cr(freq_diff)
# z propagation axis
z = np.arange(0, fiber_length + 1, z_resolution)
z = arange(0, fiber_length + 1, z_resolution)
def ode_function(z, p):
return self._ode_stimulated_raman(z, p, alphap_fiber, freq_array, cr, prop_direct)
@@ -362,14 +373,14 @@ class RamanSolver:
bvp_solution = solve_bvp(ode_function, boundary_residual, z, initial_guess_conditions, tol=tolerance)
rho = (bvp_solution.y.transpose() / power_spectrum).transpose()
rho = np.sqrt(rho) # From power attenuation to field attenuation
rho = sqrt(rho) # From power attenuation to field attenuation
stimulated_raman_scattering = StimulatedRamanScattering(freq_array, bvp_solution.x, rho, bvp_solution.y)
self._stimulated_raman_scattering = stimulated_raman_scattering
def _residuals_stimulated_raman(self, ya, yb, power_spectrum, prop_direct):
computed_boundary_value = np.zeros(ya.size)
computed_boundary_value = zeros(ya.size)
for index, direction in enumerate(prop_direct):
if direction == +1:
@@ -393,12 +404,12 @@ class RamanSolver:
the second ndarray index identifies the step in z. ndarray
"""
power_guess = np.empty((power_spectrum.size, z.size))
power_guess = empty((power_spectrum.size, z.size))
for f_index, power_slice in enumerate(power_spectrum):
if prop_direct[f_index] == +1:
power_guess[f_index, :] = np.exp(-alphap_fiber[f_index] * z) * power_slice
power_guess[f_index, :] = exp(-alphap_fiber[f_index] * z) * power_slice
else:
power_guess[f_index, :] = np.exp(-alphap_fiber[f_index] * z[::-1]) * power_slice
power_guess[f_index, :] = exp(-alphap_fiber[f_index] * z[::-1]) * power_slice
return power_guess
@@ -419,26 +430,26 @@ class RamanSolver:
:return: dP/dz: the power variation in dz [W/m]. numpy array. Size n
"""
dpdz = np.nan * np.ones(power_spectrum.shape)
dpdz = nan * ones(power_spectrum.shape)
for f_ind, power in enumerate(power_spectrum):
cr_raman = cr_raman_matrix[f_ind, :]
vibrational_loss = freq_array[f_ind] / freq_array[:f_ind]
for z_ind, power_sample in enumerate(power):
raman_gain = np.sum(cr_raman[f_ind + 1:] * power_spectrum[f_ind + 1:, z_ind])
raman_loss = np.sum(vibrational_loss * cr_raman[:f_ind] * power_spectrum[:f_ind, z_ind])
raman_gain = sum(cr_raman[f_ind + 1:] * power_spectrum[f_ind + 1:, z_ind])
raman_loss = sum(vibrational_loss * cr_raman[:f_ind] * power_spectrum[:f_ind, z_ind])
dpdz_element = prop_direct[f_ind] * (-alphap_fiber[f_ind] + raman_gain - raman_loss) * power_sample
dpdz[f_ind][z_ind] = dpdz_element
return np.vstack(dpdz)
return vstack(dpdz)
class NliSolver:
""" This class implements the NLI models.
Model and method can be specified in `sim_params.nli_params.method`.
List of implemented methods:
'gn_model_analytic': brute force triple integral solution
'gn_model_analytic': eq. 120 from arXiv:1209.0394
'ggn_spectrally_separated_xpm_spm': XPM plus SPM
"""
@@ -488,38 +499,38 @@ class NliSolver:
return carrier_nli
def _compute_eta_matrix(self, carrier_cut, *carriers):
cut_index = carrier_cut.channel_number - 1
def _compute_eta_matrix(self, cut_carrier, *carriers):
cut_index = cut_carrier.channel_number - 1
simulation = Simulation.get_simulation()
sim_params = simulation.sim_params
# Matrix initialization
matrix_size = max(carriers, key=lambda x: getattr(x, 'channel_number')).channel_number
eta_matrix = np.zeros(shape=(matrix_size, matrix_size))
eta_matrix = zeros(shape=(matrix_size, matrix_size))
# SPM
logger.debug(f'Start computing SPM on channel #{carrier_cut.channel_number}')
logger.debug(f'Start computing SPM on channel #{cut_carrier.channel_number}')
# SPM GGN
if 'ggn' in sim_params.nli_params.nli_method_name.lower():
partial_nli = self._generalized_spectrally_separated_spm(carrier_cut)
partial_nli = self._generalized_spectrally_separated_spm(cut_carrier)
# SPM GN
elif 'gn' in sim_params.nli_params.nli_method_name.lower():
partial_nli = self._gn_analytic(carrier_cut, *[carrier_cut])
eta_matrix[cut_index, cut_index] = partial_nli / (carrier_cut.power.signal**3)
partial_nli = self._gn_analytic(cut_carrier, *[cut_carrier])
eta_matrix[cut_index, cut_index] = partial_nli / (cut_carrier.power.signal**3)
# XPM
for pump_carrier in carriers:
pump_index = pump_carrier.channel_number - 1
if not (cut_index == pump_index):
logger.debug(f'Start computing XPM on channel #{carrier_cut.channel_number} '
logger.debug(f'Start computing XPM on channel #{cut_carrier.channel_number} '
f'from channel #{pump_carrier.channel_number}')
# XPM GGN
if 'ggn' in sim_params.nli_params.nli_method_name.lower():
partial_nli = self._generalized_spectrally_separated_xpm(carrier_cut, pump_carrier)
partial_nli = self._generalized_spectrally_separated_xpm(cut_carrier, pump_carrier)
# XPM GGN
elif 'gn' in sim_params.nli_params.nli_method_name.lower():
partial_nli = self._gn_analytic(carrier_cut, *[pump_carrier])
eta_matrix[pump_index, pump_index] = partial_nli /\
(carrier_cut.power.signal * pump_carrier.power.signal**2)
partial_nli = self._gn_analytic(cut_carrier, *[pump_carrier])
eta_matrix[pump_index, pump_index] = \
partial_nli / (cut_carrier.power.signal * pump_carrier.power.signal**2)
return eta_matrix
# Methods for computing GN-model
@@ -537,12 +548,12 @@ class NliSolver:
g_nli = 0
for interfering_carrier in carriers:
g_interfearing = interfering_carrier.power.signal / interfering_carrier.baud_rate
g_interfering = interfering_carrier.power.signal / interfering_carrier.baud_rate
g_signal = carrier.power.signal / carrier.baud_rate
g_nli += g_interfearing**2 * g_signal \
g_nli += g_interfering**2 * g_signal \
* _psi(carrier, interfering_carrier, beta2=beta2, asymptotic_length=asymptotic_length)
g_nli *= (16.0 / 27.0) * (gamma * effective_length) ** 2 /\
(2 * np.pi * abs(beta2) * asymptotic_length)
(2 * pi * abs(beta2) * asymptotic_length)
carrier_nli = carrier.baud_rate * g_nli
return carrier_nli
@@ -559,26 +570,26 @@ class NliSolver:
self._generalized_psi(carrier, carrier, f_eval, f_cut_resolution, f_cut_resolution)
return spm_nli
def _generalized_spectrally_separated_xpm(self, carrier_cut, pump_carrier):
def _generalized_spectrally_separated_xpm(self, cut_carrier, pump_carrier):
gamma = self.fiber.params.gamma
simulation = Simulation.get_simulation()
sim_params = simulation.sim_params
delta_index = pump_carrier.channel_number - carrier_cut.channel_number
delta_index = pump_carrier.channel_number - cut_carrier.channel_number
f_cut_resolution = sim_params.nli_params.f_cut_resolution[f'delta_{delta_index}']
f_pump_resolution = sim_params.nli_params.f_pump_resolution
f_eval = carrier_cut.frequency
f_eval = cut_carrier.frequency
g_pump = (pump_carrier.power.signal / pump_carrier.baud_rate)
g_cut = (carrier_cut.power.signal / carrier_cut.baud_rate)
g_cut = (cut_carrier.power.signal / cut_carrier.baud_rate)
frequency_offset_threshold = self._frequency_offset_threshold(pump_carrier.baud_rate)
if abs(carrier_cut.frequency - pump_carrier.frequency) <= frequency_offset_threshold:
xpm_nli = carrier_cut.baud_rate * (16.0 / 27.0) * gamma ** 2 * g_pump**2 * g_cut * \
2 * self._generalized_psi(carrier_cut, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution)
if abs(cut_carrier.frequency - pump_carrier.frequency) <= frequency_offset_threshold:
xpm_nli = cut_carrier.baud_rate * (16.0 / 27.0) * gamma ** 2 * g_pump**2 * g_cut * \
2 * self._generalized_psi(cut_carrier, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution)
else:
xpm_nli = carrier_cut.baud_rate * (16.0 / 27.0) * gamma ** 2 * g_pump**2 * g_cut * \
2 * self._fast_generalized_psi(carrier_cut, pump_carrier, f_eval, f_cut_resolution)
xpm_nli = cut_carrier.baud_rate * (16.0 / 27.0) * gamma ** 2 * g_pump**2 * g_cut * \
2 * self._fast_generalized_psi(cut_carrier, pump_carrier, f_eval, f_cut_resolution)
return xpm_nli
def _fast_generalized_psi(self, carrier_cut, pump_carrier, f_eval, f_cut_resolution):
def _fast_generalized_psi(self, cut_carrier, pump_carrier, f_eval, f_cut_resolution):
""" It computes the generalized psi function similarly to the one used in the GN model
:return: generalized_psi
"""
@@ -589,29 +600,29 @@ class NliSolver:
f_ref_beta = self.fiber.params.ref_frequency
z = self.stimulated_raman_scattering.z
frequency_rho = self.stimulated_raman_scattering.frequency
rho_norm = self.stimulated_raman_scattering.rho * np.exp(np.abs(alpha0) * z / 2)
rho_norm = self.stimulated_raman_scattering.rho * exp(abs(alpha0) * z / 2)
if len(frequency_rho) == 1:
def rho_function(f): return rho_norm[0, :]
else:
rho_function = interp1d(frequency_rho, rho_norm, axis=0, fill_value='extrapolate')
rho_norm_pump = rho_function(pump_carrier.frequency)
f1_array = np.array([pump_carrier.frequency - (pump_carrier.baud_rate * (1 + pump_carrier.roll_off) / 2),
pump_carrier.frequency + (pump_carrier.baud_rate * (1 + pump_carrier.roll_off) / 2)])
f2_array = np.arange(carrier_cut.frequency,
carrier_cut.frequency + (carrier_cut.baud_rate * (1 + carrier_cut.roll_off) / 2),
f_cut_resolution) # Only positive f2 is used since integrand_f2 is symmetric
f1_array = array([pump_carrier.frequency - (pump_carrier.baud_rate * (1 + pump_carrier.roll_off) / 2),
pump_carrier.frequency + (pump_carrier.baud_rate * (1 + pump_carrier.roll_off) / 2)])
f2_array = arange(cut_carrier.frequency,
cut_carrier.frequency + (cut_carrier.baud_rate * (1 + cut_carrier.roll_off) / 2),
f_cut_resolution) # Only positive f2 is used since integrand_f2 is symmetric
integrand_f1 = np.zeros(len(f1_array))
integrand_f1 = zeros(len(f1_array))
for f1_index, f1 in enumerate(f1_array):
delta_beta = 4 * np.pi**2 * (f1 - f_eval) * (f2_array - f_eval) * \
(beta2 + np.pi * beta3 * (f1 + f2_array - 2 * f_ref_beta))
delta_beta = 4 * pi**2 * (f1 - f_eval) * (f2_array - f_eval) * \
(beta2 + pi * beta3 * (f1 + f2_array - 2 * f_ref_beta))
integrand_f2 = self._generalized_rho_nli(delta_beta, rho_norm_pump, z, alpha0)
integrand_f1[f1_index] = 2 * np.trapz(integrand_f2, f2_array) # 2x since integrand_f2 is symmetric in f2
integrand_f1[f1_index] = 2 * trapz(integrand_f2, f2_array) # 2x since integrand_f2 is symmetric in f2
generalized_psi = 0.5 * sum(integrand_f1) * pump_carrier.baud_rate
return generalized_psi
def _generalized_psi(self, carrier_cut, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution):
def _generalized_psi(self, cut_carrier, pump_carrier, f_eval, f_cut_resolution, f_pump_resolution):
""" It computes the generalized psi function similarly to the one used in the GN model
:return: generalized_psi
"""
@@ -622,44 +633,44 @@ class NliSolver:
f_ref_beta = self.fiber.params.ref_frequency
z = self.stimulated_raman_scattering.z
frequency_rho = self.stimulated_raman_scattering.frequency
rho_norm = self.stimulated_raman_scattering.rho * np.exp(np.abs(alpha0) * z / 2)
rho_norm = self.stimulated_raman_scattering.rho * exp(abs(alpha0) * z / 2)
if len(frequency_rho) == 1:
def rho_function(f): return rho_norm[0, :]
else:
rho_function = interp1d(frequency_rho, rho_norm, axis=0, fill_value='extrapolate')
rho_norm_pump = rho_function(pump_carrier.frequency)
f1_array = np.arange(pump_carrier.frequency - (pump_carrier.baud_rate * (1 + pump_carrier.roll_off) / 2),
pump_carrier.frequency + (pump_carrier.baud_rate * (1 + pump_carrier.roll_off) / 2),
f_pump_resolution)
f2_array = np.arange(carrier_cut.frequency - (carrier_cut.baud_rate * (1 + carrier_cut.roll_off) / 2),
carrier_cut.frequency + (carrier_cut.baud_rate * (1 + carrier_cut.roll_off) / 2),
f_cut_resolution)
f1_array = arange(pump_carrier.frequency - (pump_carrier.baud_rate * (1 + pump_carrier.roll_off) / 2),
pump_carrier.frequency + (pump_carrier.baud_rate * (1 + pump_carrier.roll_off) / 2),
f_pump_resolution)
f2_array = arange(cut_carrier.frequency - (cut_carrier.baud_rate * (1 + cut_carrier.roll_off) / 2),
cut_carrier.frequency + (cut_carrier.baud_rate * (1 + cut_carrier.roll_off) / 2),
f_cut_resolution)
psd1 = raised_cosine_comb(f1_array, pump_carrier) * (pump_carrier.baud_rate / pump_carrier.power.signal)
integrand_f1 = np.zeros(len(f1_array))
integrand_f1 = zeros(len(f1_array))
for f1_index, (f1, psd1_sample) in enumerate(zip(f1_array, psd1)):
f3_array = f1 + f2_array - f_eval
psd2 = raised_cosine_comb(f2_array, carrier_cut) * (carrier_cut.baud_rate / carrier_cut.power.signal)
psd2 = raised_cosine_comb(f2_array, cut_carrier) * (cut_carrier.baud_rate / cut_carrier.power.signal)
psd3 = raised_cosine_comb(f3_array, pump_carrier) * (pump_carrier.baud_rate / pump_carrier.power.signal)
ggg = psd1_sample * psd2 * psd3
delta_beta = 4 * np.pi**2 * (f1 - f_eval) * (f2_array - f_eval) * \
(beta2 + np.pi * beta3 * (f1 + f2_array - 2 * f_ref_beta))
delta_beta = 4 * pi**2 * (f1 - f_eval) * (f2_array - f_eval) * \
(beta2 + pi * beta3 * (f1 + f2_array - 2 * f_ref_beta))
integrand_f2 = ggg * self._generalized_rho_nli(delta_beta, rho_norm_pump, z, alpha0)
integrand_f1[f1_index] = np.trapz(integrand_f2, f2_array)
generalized_psi = np.trapz(integrand_f1, f1_array)
integrand_f1[f1_index] = trapz(integrand_f2, f2_array)
generalized_psi = trapz(integrand_f1, f1_array)
return generalized_psi
@staticmethod
def _generalized_rho_nli(delta_beta, rho_norm_pump, z, alpha0):
w = 1j * delta_beta - alpha0
generalized_rho_nli = (rho_norm_pump[-1]**2 * np.exp(w * z[-1]) - rho_norm_pump[0]**2 * np.exp(w * z[0])) / w
generalized_rho_nli = (rho_norm_pump[-1]**2 * exp(w * z[-1]) - rho_norm_pump[0]**2 * exp(w * z[0])) / w
for z_ind in range(0, len(z) - 1):
derivative_rho = (rho_norm_pump[z_ind + 1]**2 - rho_norm_pump[z_ind]**2) / (z[z_ind + 1] - z[z_ind])
generalized_rho_nli -= derivative_rho * (np.exp(w * z[z_ind + 1]) - np.exp(w * z[z_ind])) / (w**2)
generalized_rho_nli = np.abs(generalized_rho_nli)**2
generalized_rho_nli -= derivative_rho * (exp(w * z[z_ind + 1]) - exp(w * z[z_ind])) / (w**2)
generalized_rho_nli = abs(generalized_rho_nli)**2
return generalized_rho_nli
def _frequency_offset_threshold(self, symbol_rate):
@@ -674,15 +685,14 @@ class NliSolver:
def _psi(carrier, interfering_carrier, beta2, asymptotic_length):
"""Calculates eq. 123 from `arXiv:1209.0394 <https://arxiv.org/abs/1209.0394>`__"""
if carrier.channel_number == interfering_carrier.channel_number: # SCI, SPM
psi = np.arcsinh(0.5 * np.pi**2 * asymptotic_length * abs(beta2) * carrier.baud_rate**2)
psi = arcsinh(0.5 * pi**2 * asymptotic_length * abs(beta2) * carrier.baud_rate**2)
else: # XCI, XPM
delta_f = carrier.frequency - interfering_carrier.frequency
psi = np.arcsinh(np.pi**2 * asymptotic_length * abs(beta2) *
carrier.baud_rate * (delta_f + 0.5 * interfering_carrier.baud_rate))
psi -= np.arcsinh(np.pi**2 * asymptotic_length * abs(beta2) *
carrier.baud_rate * (delta_f - 0.5 * interfering_carrier.baud_rate))
psi = arcsinh(pi**2 * asymptotic_length * abs(beta2) *
carrier.baud_rate * (delta_f + 0.5 * interfering_carrier.baud_rate))
psi -= arcsinh(pi**2 * asymptotic_length * abs(beta2) *
carrier.baud_rate * (delta_f - 0.5 * interfering_carrier.baud_rate))
return psi
@@ -712,7 +722,7 @@ def estimate_nf_model(type_variety, gain_min, gain_max, nf_min, nf_max):
# nf2 should be nf1 + 0.3 < nf2 < nf1 + 2
# If not, recompute and check delta_p
if not nf1 + 0.3 < nf2 < nf1 + 2:
nf2 = np.clip(nf2, nf1 + 0.3, nf1 + 2)
nf2 = clip(nf2, nf1 + 0.3, nf1 + 2)
g1a_max = lin2db(db2lin(nf2) / (db2lin(nf_min) - db2lin(nf1)))
delta_p = gain_max - g1a_max
g1a_min = gain_min - (gain_max - gain_min) - delta_p

View File

@@ -1,18 +1,17 @@
#!/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
from scipy import constants
from gnpy.core.exceptions import ConfigurationError
@@ -70,7 +69,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):
@@ -190,12 +189,12 @@ 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)

View File

@@ -635,7 +635,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -643,6 +643,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 +669,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -692,7 +707,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -711,7 +726,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -730,7 +745,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -749,7 +764,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -768,7 +783,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -787,7 +802,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_high_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -882,7 +897,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_high_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -901,7 +916,7 @@
}
},
"type": "Edfa",
"type_variety": "std_low_gain",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": null,
"delta_p": 1.0,
@@ -946,21 +961,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": [
@@ -1245,4 +1245,4 @@
"to_node": "trx Brest_KLA"
}
]
}
}

View File

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

View File

@@ -27,7 +27,7 @@ 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
@@ -188,6 +188,8 @@ def transmission_main_example(args=None):
params['loose_list'] = ['strict']
params['format'] = ''
params['path_bandwidth'] = 0
params['effective_freq_slot'] = None
params['blocking_reason'] = None
trx_params = trx_mode_params(equipment)
if args.power:
trx_params['power'] = db2lin(float(args.power)) * 1e-3
@@ -200,7 +202,14 @@ def transmission_main_example(args=None):
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)
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)]
@@ -225,7 +234,7 @@ def transmission_main_example(args=None):
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)
@@ -237,11 +246,6 @@ def transmission_main_example(args=None):
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}')
@@ -257,7 +261,7 @@ def transmission_main_example(args=None):
'SNR NLI (signal bw, dB)',
'SNR total (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(
@@ -280,7 +284,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):
@@ -317,7 +321,14 @@ def path_requests_run(args=None):
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)
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}')
@@ -401,7 +412,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)

View File

@@ -20,7 +20,6 @@ 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 argparse import ArgumentParser
from collections import namedtuple, Counter, defaultdict
@@ -124,6 +123,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), ...]
@@ -169,7 +185,7 @@ def parse_headers(my_sheet, input_headers_dict, headers, start_line, slice_in):
if slice_out == (-1, -1):
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(f'Missing _{h0}_ header')
else:
print(f'missing header {h0}')
elif not isinstance(input_headers_dict[h0], dict):
@@ -178,7 +194,7 @@ def parse_headers(my_sheet, input_headers_dict, headers, start_line, slice_in):
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()
raise NetworkTopologyError('Could not find any header to read')
return headers
@@ -193,6 +209,10 @@ 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 = []
@@ -206,19 +226,23 @@ def sanity_check(nodes, links, nodes_by_city, links_by_city, eqpts_by_city):
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'
raise NetworkTopologyError(msg)
unreferenced_nodes = [n for n in nodes_by_city if n not in links_by_city]
if unreferenced_nodes:
raise NetworkTopologyError(f'{ansi_escapes.red}XLS error:{ansi_escapes.reset} The following nodes are not '
f'referenced from the {ansi_escapes.cyan}Links{ansi_escapes.reset} sheet. '
f'If unused, remove them from the {ansi_escapes.cyan}Nodes{ansi_escapes.reset} '
f'sheet:\n'
+ _format_items(unreferenced_nodes))
# 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:
raise NetworkTopologyError(f'{ansi_escapes.red}XLS error:{ansi_escapes.reset} '
f'The {ansi_escapes.cyan}Eqpt{ansi_escapes.reset} sheet refers to nodes that '
f'are not defined in the {ansi_escapes.cyan}Nodes{ansi_escapes.reset} sheet:\n'
+ _format_items(wrong_eqpt))
for city, link in links_by_city.items():
if nodes_by_city[city].node_type.lower() == 'ila' and len(link) != 2:
@@ -226,7 +250,7 @@ def sanity_check(nodes, links, nodes_by_city, links_by_city, eqpts_by_city):
# => 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')
specified in {city}, replaced by ROADM')
nodes_by_city[city].node_type = 'ROADM'
for n in nodes:
if n.city == city:
@@ -234,8 +258,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 +372,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 +387,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,
@@ -325,60 +423,12 @@ def xls_to_json_data(input_filename, filter_region=[]):
'params': {'length': round(x.west_distance, 3),
'length_units': x.distance_units,
'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}},
'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}},
'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'],
'con_in':x.west_con_in,
'con_out':x.west_con_out}
} # missing ILA construction
for x in links] +
[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 +449,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 +458,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 +486,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 +515,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 +579,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 +592,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,6 +615,11 @@ 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):
@@ -566,10 +628,14 @@ def parse_excel(input_filename):
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:
raise NetworkTopologyError(f'{ansi_escapes.red}XLS error:{ansi_escapes.reset} '
f'The {ansi_escapes.cyan}Links{ansi_escapes.reset} sheet references nodes that '
f'are not defined in the {ansi_escapes.cyan}Nodes{ansi_escapes.reset} sheet:\n'
+ _format_items(f'{item[0]} -> {item[1]}' for item in bad_links))
return nodes, links, eqpts, roadms
def eqpt_connection_by_city(city_name):
@@ -615,14 +681,13 @@ def eqpt_in_city_to_city(in_city, to_city, direction='east'):
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}'
if nodes_by_city[in_city].node_type.lower() == 'fused':
return_eqpt = f'{direction} fused spans in {in_city}'
return return_eqpt
@@ -729,6 +794,8 @@ LINKS_COLUMN = 16
LINKS_LINE = 3
EQPTS_LINE = 3
EQPTS_COLUMN = 14
ROADMS_LINE = 3
ROADMS_COLUMN = 3
def _do_convert():

View File

@@ -210,6 +210,8 @@ class Amp(_JsonThing):
except KeyError:
raise EquipmentConfigError(f'missing preamp/booster variety input for amplifier: {type_variety} in equipment config')
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)
@@ -230,14 +232,6 @@ def load_equipment(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 _update_dual_stage(equipment):
edfa_dict = equipment['Edfa']
for edfa in edfa_dict.values():
@@ -268,6 +262,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', '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 +307,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
@@ -358,7 +367,7 @@ def network_from_json(json_data, equipment):
temp = merge_amplifier_restrictions(temp, extra_params.__dict__)
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']) or (typ == 'Edfa' and variety not in ['default', '']):
raise ConfigurationError(f'The {typ} of variety type {variety} was not recognized:'
'\nplease check it is properly defined in the eqpt_config json file')
el = cls(**el_config)
@@ -431,7 +440,10 @@ def requests_from_json(json_data, equipment):
params['bidir'] = req['bidirectional']
params['destination'] = req['destination']
params['trx_type'] = req['path-constraints']['te-bandwidth']['trx_type']
params['trx_mode'] = req['path-constraints']['te-bandwidth']['trx_mode']
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
params['format'] = params['trx_mode']
params['spacing'] = req['path-constraints']['te-bandwidth']['spacing']
try:
@@ -466,8 +478,12 @@ 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'])
_check_one_request(params, f_max_from_si)
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
params['blocking_reason'] = _check_one_request(params, f_max_from_si)
try:
params['path_bandwidth'] = req['path-constraints']['te-bandwidth']['path_bandwidth']
except KeyError:
@@ -489,7 +505,7 @@ def _check_one_request(params, f_max_from_si):
f'{params["min_spacing"]*1e-9}GHz.\nComputation stopped'
print(msg)
_logger.critical(msg)
raise ServiceError(msg)
return 'MODE_BAUDRATE_NOT_CONSISTENT_WITH_SPACING'
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
@@ -497,6 +513,7 @@ def _check_one_request(params, f_max_from_si):
max recommanded nb of channels is {max_recommanded_nb_channels}.'''
_logger.critical(msg)
raise ServiceError(msg)
return None
def disjunctions_from_json(json_data):
@@ -504,11 +521,7 @@ def disjunctions_from_json(json_data):
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']

View File

@@ -13,10 +13,14 @@ from networkx import draw_networkx_nodes, draw_networkx_edges, draw_networkx_lab
from gnpy.core.elements import Transceiver
def _try_city(node):
return node.location.city if node.location.city else node.uid
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)}
labels = {n: _try_city(n) 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:
@@ -33,14 +37,15 @@ def plot_baseline(network):
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
pos = {n: (n.lng, n.lat) for n in network.nodes()}
nodes = {}
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)}
labels = {n: _try_city(n) 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:
@@ -56,7 +61,7 @@ def plot_results(network, path, source, destination, infos):
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}')
title(f'Propagating from {_try_city(source)} to {_try_city(destination)}')
axis('off')
heading = 'Spectral Information\n\n'

View File

@@ -127,7 +127,7 @@ class Request_element(Element):
'technology': 'flexi-grid',
'trx_type': self.trx_type,
'trx_mode': self.mode,
'effective-freq-slot': [{'N': 'null', 'M': 'null'}],
'effective-freq-slot': [{'N': None, 'M': None}],
'spacing': self.spacing,
'max-nb-of-channel': self.nb_channel,
'output-power': self.power

View File

@@ -35,7 +35,7 @@ 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')
' min_spacing cost path_bandwidth effective_freq_slot blocking_reason')
DisjunctionParams = namedtuple('DisjunctionParams', 'disjunction_id relaxable link' +
'_diverse node_diverse disjunctions_req')
@@ -68,6 +68,11 @@ class PathRequest:
self.min_spacing = params.min_spacing
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']
if params.blocking_reason is not None:
self.blocking_reason = params.blocking_reason
def __str__(self):
return '\n\t'.join([f'{type(self).__name__} {self.request_id}',
@@ -127,7 +132,7 @@ class Disjunction:
BLOCKING_NOPATH = ['NO_PATH', 'NO_PATH_WITH_CONSTRAINT',
'NO_FEASIBLE_BAUDRATE_WITH_SPACING',
'NO_COMPUTED_SNR']
'NO_COMPUTED_SNR', 'MODE_BAUDRATE_NOT_CONSISTENT_WITH_SPACING']
BLOCKING_NOMODE = ['NO_FEASIBLE_MODE', 'MODE_NOT_FEASIBLE']
BLOCKING_NOSPECTRUM = 'NO_SPECTRUM'
@@ -333,23 +338,17 @@ 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
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(req.tx_osnr)
if any(isinstance(el, Roadm) for el in path):
path[-1].update_snr(req.tx_osnr, equipment['Roadm']['default'].add_drop_osnr)
else:
path[-1].update_snr(req.tx_osnr)
return si
def propagate_and_optimize_mode(path, req, equipment):
@@ -375,19 +374,26 @@ def propagate_and_optimize_mode(path, req, equipment):
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)
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'])
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'])
if round(min(path[-1].snr + lin2db(this_br / (12.5e9))), 2) \
> this_mode['OSNR'] + equipment['SI']['default'].sys_margins:
return path, this_mode
else:
last_explored_mode = this_mode
else:
req.blocking_reason = 'NO_COMPUTED_SNR'
return path, None
# only get to this point if no baudrate/mode satisfies OSNR requirement
# returns the last propagated path and mode
@@ -817,23 +823,21 @@ def compute_path_dsjctn(network, equipment, pathreqlist, disjunctions_list):
# 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 = 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)
# for i in disjunctions_list:
# print(i.disjunction_id)
@@ -1095,13 +1099,13 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
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)
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:
if temp_snr01nm < 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' +\
f' {pathreq.tsp_mode}\n\tcomputedSNR in 0.1nm = {temp_snr01nm} ' +\
f'- required osnr {pathreq.OSNR}'
f'- required osnr {pathreq.OSNR} + {equipment["SI"]["default"].sys_margins} margin'
print(msg)
LOGGER.warning(msg)
pathreq.blocking_reason = 'MODE_NOT_FEASIBLE'
@@ -1133,25 +1137,28 @@ def compute_path_with_disjunction(network, equipment, pathreqlist, pathlist):
# reversed path is needed for correct spectrum assignment
reversed_path = find_reversed_path(pathlist[i])
if pathreq.bidir:
if pathreq.bidir and pathreq.baud_rate is not None:
# only propagate if bidir is true, but needs the reversed path anyway for
# correct spectrum assignment
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)
propagate(rev_p, pathreq, equipment)
propagated_reversed_path = rev_p
temp_snr01nm = round(mean(propagated_reversed_path[-1].snr +\
lin2db(pathreq.baud_rate/(12.5e9))), 2)
if temp_snr01nm < pathreq.OSNR:
if temp_snr01nm < 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' +\
f' {pathreq.tsp_mode}\n' +\
f'\tcomputedSNR in 0.1nm = {temp_snr01nm} - required osnr {pathreq.OSNR}'
f'\tcomputedSNR in 0.1nm = {temp_snr01nm} -' \
f' required osnr {pathreq.OSNR} + {equipment["SI"]["default"].sys_margins} margin'
print(msg)
LOGGER.warning(msg)
# TODO selection of mode should also be on reversed direction !!
pathreq.blocking_reason = 'MODE_NOT_FEASIBLE'
if not hasattr(pathreq, 'blocking_reason'):
pathreq.blocking_reason = 'MODE_NOT_FEASIBLE'
else:
propagated_reversed_path = []
else:

View File

@@ -395,7 +395,7 @@ def pth_assign_spectrum(pths, rqs, oms_list, rpths):
try:
if rqs[i].blocking_reason:
rqs[i].blocked = True
rqs[i].N = 0
rqs[i].N = None
rqs[i].M = 0
except AttributeError:
nb_wl = ceil(rqs[i].path_bandwidth / rqs[i].bit_rate)
@@ -404,9 +404,25 @@ def pth_assign_spectrum(pths, rqs, oms_list, rpths):
# assumes that all channels must be grouped
# TODO : enables non contiguous reservation in case of blocking
requested_m = ceil(rqs[i].spacing / 0.0125e12) * nb_wl
# concatenate all path and reversed path elements to derive slots availability
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=None)
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
@@ -426,6 +442,6 @@ def pth_assign_spectrum(pths, rqs, oms_list, rpths):
rqs[i].M = requested_m
else:
rqs[i].blocked = True
rqs[i].N = 0
rqs[i].N = None
rqs[i].M = 0
rqs[i].blocking_reason = 'NO_SPECTRUM'

View File

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

@@ -0,0 +1,180 @@
{
"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"
}

2777
gnpy/yang/api-request.json Normal file

File diff suppressed because it is too large Load Diff

901
gnpy/yang/api-topology.json Normal file
View File

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

@@ -0,0 +1,53 @@
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

@@ -0,0 +1,78 @@
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

@@ -0,0 +1,442 @@
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

@@ -0,0 +1,300 @@
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

@@ -0,0 +1,559 @@
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 int32;
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 uint32;
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

@@ -1,12 +1,18 @@
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
matplotlib>=3.3.3,<4
networkx>=2.5,<3
numpy>=1.19.4,<2
pandas>=1.1.5,<2
pbr>=5.5.1,<6
Pygments>=2.7.4,<3
scipy>=1.5.4,<2
Sphinx>=3.5.0,<4
sphinxcontrib-bibtex>=0.4.2,<1
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

View File

@@ -12,7 +12,7 @@ project_urls =
Documentation = https://gnpy.readthedocs.io/
python-requires = >=3.6
classifier =
Development Status :: 3 - Alpha
Development Status :: 5 - Production/Stable
Intended Audience :: Developers
Intended Audience :: Science/Research
Intended Audience :: Telecommunications Industry
@@ -21,9 +21,12 @@ classifier =
Programming Language :: Python
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: Implementation :: CPython
Topic :: Scientific/Engineering
Topic :: Scientific/Engineering :: Physics
Topic :: System :: Networking
keywords =
optics
network
@@ -48,3 +51,4 @@ console_scripts =
gnpy-transmission-example = gnpy.tools.cli_examples:transmission_main_example
gnpy-path-request = gnpy.tools.cli_examples:path_requests_run
gnpy-convert-xls = gnpy.tools.convert:_do_convert
gnpy-rest = gnpy.api.rest_example:main

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -79,8 +79,8 @@
"path-route-object": {
"index": 5,
"num-unnum-hop": {
"node-id": "Edfa1_roadm Lorient_KMA",
"link-tp-id": "Edfa1_roadm Lorient_KMA"
"node-id": "east edfa in Lorient_KMA to Vannes_KBE",
"link-tp-id": "east edfa in Lorient_KMA to Vannes_KBE"
}
}
},
@@ -115,8 +115,8 @@
"path-route-object": {
"index": 9,
"num-unnum-hop": {
"node-id": "Edfa0_fiber (Lorient_KMA → Vannes_KBE)-F055",
"link-tp-id": "Edfa0_fiber (Lorient_KMA → Vannes_KBE)-F055"
"node-id": "west edfa in Vannes_KBE to Lorient_KMA",
"link-tp-id": "west edfa in Vannes_KBE to Lorient_KMA"
}
}
},
@@ -256,8 +256,8 @@
"path-route-object": {
"index": 5,
"num-unnum-hop": {
"node-id": "Edfa0_roadm Brest_KLA",
"link-tp-id": "Edfa0_roadm Brest_KLA"
"node-id": "east edfa in Brest_KLA to Morlaix",
"link-tp-id": "east edfa in Brest_KLA to Morlaix"
}
}
},
@@ -472,8 +472,8 @@
"path-route-object": {
"index": 29,
"num-unnum-hop": {
"node-id": "Edfa0_fiber (Loudeac → Lorient_KMA)-F054",
"link-tp-id": "Edfa0_fiber (Loudeac → Lorient_KMA)-F054"
"node-id": "west edfa in Lorient_KMA to Loudeac",
"link-tp-id": "west edfa in Lorient_KMA to Loudeac"
}
}
},
@@ -508,8 +508,8 @@
"path-route-object": {
"index": 33,
"num-unnum-hop": {
"node-id": "Edfa1_roadm Lorient_KMA",
"link-tp-id": "Edfa1_roadm Lorient_KMA"
"node-id": "east edfa in Lorient_KMA to Vannes_KBE",
"link-tp-id": "east edfa in Lorient_KMA to Vannes_KBE"
}
}
},
@@ -544,8 +544,8 @@
"path-route-object": {
"index": 37,
"num-unnum-hop": {
"node-id": "Edfa0_fiber (Lorient_KMA → Vannes_KBE)-F055",
"link-tp-id": "Edfa0_fiber (Lorient_KMA → Vannes_KBE)-F055"
"node-id": "west edfa in Vannes_KBE to Lorient_KMA",
"link-tp-id": "west edfa in Vannes_KBE to Lorient_KMA"
}
}
},
@@ -757,8 +757,8 @@
"path-route-object": {
"index": 13,
"num-unnum-hop": {
"node-id": "Edfa0_fiber (Stbrieuc → Rennes_STA)-F057",
"link-tp-id": "Edfa0_fiber (Stbrieuc → Rennes_STA)-F057"
"node-id": "west edfa in Rennes_STA to Stbrieuc",
"link-tp-id": "west edfa in Rennes_STA to Stbrieuc"
}
}
},
@@ -898,8 +898,8 @@
"path-route-object": {
"index": 5,
"num-unnum-hop": {
"node-id": "Edfa1_roadm Rennes_STA",
"link-tp-id": "Edfa1_roadm Rennes_STA"
"node-id": "east edfa in Rennes_STA to Ploermel",
"link-tp-id": "east edfa in Rennes_STA to Ploermel"
}
}
},
@@ -970,8 +970,8 @@
"path-route-object": {
"index": 13,
"num-unnum-hop": {
"node-id": "Edfa0_fiber (Ploermel → Vannes_KBE)-",
"link-tp-id": "Edfa0_fiber (Ploermel → Vannes_KBE)-"
"node-id": "west edfa in Vannes_KBE to Ploermel",
"link-tp-id": "west edfa in Vannes_KBE to Ploermel"
}
}
},
@@ -1006,8 +1006,8 @@
"path-route-object": {
"index": 17,
"num-unnum-hop": {
"node-id": "Edfa0_roadm Vannes_KBE",
"link-tp-id": "Edfa0_roadm Vannes_KBE"
"node-id": "east edfa in Vannes_KBE to Lorient_KMA",
"link-tp-id": "east edfa in Vannes_KBE to Lorient_KMA"
}
}
},
@@ -1042,8 +1042,8 @@
"path-route-object": {
"index": 21,
"num-unnum-hop": {
"node-id": "Edfa0_fiber (Vannes_KBE → Lorient_KMA)-F055",
"link-tp-id": "Edfa0_fiber (Vannes_KBE → Lorient_KMA)-F055"
"node-id": "west edfa in Lorient_KMA to Vannes_KBE",
"link-tp-id": "west edfa in Lorient_KMA to Vannes_KBE"
}
}
},
@@ -1078,8 +1078,8 @@
"path-route-object": {
"index": 25,
"num-unnum-hop": {
"node-id": "Edfa0_roadm Lorient_KMA",
"link-tp-id": "Edfa0_roadm Lorient_KMA"
"node-id": "east edfa in Lorient_KMA to Loudeac",
"link-tp-id": "east edfa in Lorient_KMA to Loudeac"
}
}
},
@@ -1327,8 +1327,8 @@
"path-route-object": {
"index": 5,
"num-unnum-hop": {
"node-id": "Edfa0_roadm Rennes_STA",
"link-tp-id": "Edfa0_roadm Rennes_STA"
"node-id": "east edfa in Rennes_STA to Stbrieuc",
"link-tp-id": "east edfa in Rennes_STA to Stbrieuc"
}
}
},
@@ -1363,8 +1363,8 @@
"path-route-object": {
"index": 9,
"num-unnum-hop": {
"node-id": "Edfa0_fiber (Rennes_STA → Stbrieuc)-F057",
"link-tp-id": "Edfa0_fiber (Rennes_STA → Stbrieuc)-F057"
"node-id": "west edfa in Stbrieuc to Rennes_STA",
"link-tp-id": "west edfa in Stbrieuc to Rennes_STA"
}
}
},
@@ -1399,8 +1399,8 @@
"path-route-object": {
"index": 13,
"num-unnum-hop": {
"node-id": "Edfa0_fiber (Stbrieuc → Lannion_CAS)-F056",
"link-tp-id": "Edfa0_fiber (Stbrieuc → Lannion_CAS)-F056"
"node-id": "west edfa in Lannion_CAS to Stbrieuc",
"link-tp-id": "west edfa in Lannion_CAS to Stbrieuc"
}
}
},

View File

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

View File

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

View File

@@ -14,8 +14,8 @@
"trx_mode": "16QAM",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 50000000000.0,
@@ -39,8 +39,8 @@
"trx_mode": "PS_SP64_1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 50000000000.0,
@@ -64,8 +64,8 @@
"trx_mode": "PS_SP64_1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 50000000000.0,
@@ -89,8 +89,8 @@
"trx_mode": "PS_SP64_1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 50000000000.0,
@@ -135,8 +135,8 @@
"trx_mode": "mode 2 - fake",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 75000000000.0,
@@ -160,8 +160,8 @@
"trx_mode": "mode 2",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 75000000000.0,
@@ -185,8 +185,8 @@
"trx_mode": "PS_SP64_1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 50000000000.0,
@@ -223,8 +223,8 @@
"trx_mode": "mode 3",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 62500000000.0,
@@ -261,8 +261,8 @@
"trx_mode": "PS_SP64_1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 50000000000.0,
@@ -286,8 +286,8 @@
"trx_mode": "PS_SP64_1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 50000000000.0,
@@ -332,8 +332,8 @@
"trx_mode": "16QAM",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 50000000000.0,
@@ -357,8 +357,8 @@
"trx_mode": "mode 1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 50000000000.0,
@@ -382,8 +382,8 @@
"trx_mode": "PS_SP64_1",
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 50000000000.0,
@@ -407,8 +407,8 @@
"trx_mode": null,
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 50000000000.0,
@@ -453,8 +453,8 @@
"trx_mode": null,
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 50000000000.0,
@@ -478,8 +478,8 @@
"trx_mode": null,
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 75000000000.0,
@@ -503,8 +503,8 @@
"trx_mode": null,
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 50000000000.0,
@@ -528,8 +528,8 @@
"trx_mode": null,
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 75000000000.0,
@@ -553,8 +553,8 @@
"trx_mode": null,
"effective-freq-slot": [
{
"n": "null",
"m": "null"
"N":"null",
"M":"null"
}
],
"spacing": 30000000000.0,

Binary file not shown.

View File

@@ -1,14 +0,0 @@
{
"raman_parameters": {
"flag_raman": true,
"space_resolution": 10e3,
"tolerance": 1e-8
},
"nli_parameters": {
"nli_method_name": "ggn_spectrally_separated",
"wdm_grid_size": 50e9,
"dispersion_tolerance": 1,
"phase_shift_tolerance": 0.1,
"raman_computed_channels": [1, 18, 37, 56, 75]
}
}

View File

@@ -0,0 +1,813 @@
{
"elements": [
{
"uid": "trx node A",
"type": "Transceiver",
"metadata": {
"location": {
"latitude": 0,
"longitude": 0,
"city": "node A",
"region": ""
}
}
},
{
"uid": "trx node B",
"type": "Transceiver",
"metadata": {
"location": {
"latitude": 0,
"longitude": 0,
"city": "node B",
"region": ""
}
}
},
{
"uid": "trx node C",
"type": "Transceiver",
"metadata": {
"location": {
"latitude": 0,
"longitude": 0,
"city": "node C",
"region": ""
}
}
},
{
"uid": "roadm node A",
"type": "Roadm",
"params": {
"target_pch_out_db": -15,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
},
"per_degree_pch_out_db": {
"Edfa0_roadm node A": -15
}
},
"metadata": {
"location": {
"latitude": 0,
"longitude": 0,
"city": "node A",
"region": ""
}
}
},
{
"uid": "roadm node B",
"type": "Roadm",
"params": {
"target_pch_out_db": -20.0,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
},
"per_degree_pch_out_db": {
"east edfa in node B to ila2": -20,
"Edfa0_roadm node B": -20
}
},
"metadata": {
"location": {
"latitude": 0,
"longitude": 0,
"city": "node B",
"region": ""
}
}
},
{
"uid": "roadm node C",
"type": "Roadm",
"params": {
"target_pch_out_db": -15,
"restrictions": {
"preamp_variety_list": [],
"booster_variety_list": []
},
"per_degree_pch_out_db": {
"Edfa0_roadm node C": -15
}
},
"metadata": {
"location": {
"latitude": 0,
"longitude": 0,
"city": "node C",
"region": ""
}
}
},
{
"uid": "fiber (node A → ila1)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0,
"con_in": 0,
"con_out": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "fiber (ila1 → ila2)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0,
"con_in": 0,
"con_out": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "fiber (ila2 → node B)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 100.0,
"loss_coef": 0.22,
"length_units": "km",
"att_in": 0,
"con_in": 0,
"con_out": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "fiber (node B → ila3)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0,
"con_in": 0,
"con_out": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "fiber (ila3 → ila4)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0,
"con_in": 0,
"con_out": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "fiber (ila4 → node C)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0,
"con_in": 0,
"con_out": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "fiber (ila1 → node A)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0,
"con_in": 0,
"con_out": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "fiber (ila2 → ila1)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0,
"con_in": 0,
"con_out": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "fiber (node B → ila2)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 100.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0,
"con_in": 0,
"con_out": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "fiber (ila3 → node B)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0,
"con_in": 0,
"con_out": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "fiber (ila4 → ila3)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0,
"con_in": 0,
"con_out": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "fiber (node C → ila4)-",
"type": "Fiber",
"type_variety": "SSMF",
"params": {
"length": 80.0,
"loss_coef": 0.2,
"length_units": "km",
"att_in": 0,
"con_in": 0,
"con_out": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "east edfa in node B to ila2",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 35.0,
"delta_p": 0.0,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"latitude": 0,
"longitude": 0,
"city": "node B",
"region": ""
}
}
},
{
"uid": "west edfa in node B to ila2",
"type": "Fused",
"params": {
"loss": 0
},
"metadata": {
"location": {
"latitude": 0,
"longitude": 0,
"city": "node B",
"region": ""
}
}
},
{
"uid": "Edfa0_roadm node A",
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": 14.0,
"delta_p": -1.0,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "node A",
"region": ""
}
}
},
{
"uid": "Edfa0_roadm node B",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 19.0,
"delta_p": -1.0,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "node B",
"region": ""
}
}
},
{
"uid": "Edfa0_roadm node C",
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": 14.0,
"delta_p": -1.0,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": "node C",
"region": ""
}
}
},
{
"uid": "Edfa0_fiber (node A → ila1)-",
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": 16.0,
"delta_p": -1.0,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "Edfa0_fiber (ila1 → ila2)-",
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": 17.0,
"delta_p": 0.0,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "Edfa0_fiber (node B → ila3)-",
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": 16.0,
"delta_p": -1.0,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "Edfa0_fiber (ila3 → ila4)-",
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": 16.0,
"delta_p": -1.0,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "Edfa0_fiber (ila4 → node C)-",
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": 17.0,
"delta_p": 0,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "Edfa0_fiber (ila1 → node A)-",
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": 17.0,
"delta_p": 0,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "Edfa0_fiber (ila2 → ila1)-",
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": 16.0,
"delta_p": -1.0,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "Edfa0_fiber (node B → ila2)-",
"type": "Edfa",
"type_variety": "std_medium_gain",
"operational": {
"gain_target": 19.0,
"delta_p": -1.0,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "Edfa0_fiber (ila3 → node B)-",
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": 17.0,
"delta_p": 0,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "Edfa0_fiber (ila4 → ila3)-",
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": 16.0,
"delta_p": -1.0,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
},
{
"uid": "Edfa0_fiber (node C → ila4)-",
"type": "Edfa",
"type_variety": "std_low_gain",
"operational": {
"gain_target": 16.0,
"delta_p": -1.0,
"tilt_target": 0,
"out_voa": 0
},
"metadata": {
"location": {
"latitude": 0.0,
"longitude": 0.0,
"city": null,
"region": null
}
}
}
],
"connections": [
{
"from_node": "trx node A",
"to_node": "roadm node A"
},
{
"from_node": "trx node B",
"to_node": "roadm node B"
},
{
"from_node": "trx node C",
"to_node": "roadm node C"
},
{
"from_node": "roadm node A",
"to_node": "trx node A"
},
{
"from_node": "roadm node A",
"to_node": "Edfa0_roadm node A"
},
{
"from_node": "roadm node B",
"to_node": "east edfa in node B to ila2"
},
{
"from_node": "roadm node B",
"to_node": "trx node B"
},
{
"from_node": "roadm node B",
"to_node": "Edfa0_roadm node B"
},
{
"from_node": "roadm node C",
"to_node": "trx node C"
},
{
"from_node": "roadm node C",
"to_node": "Edfa0_roadm node C"
},
{
"from_node": "fiber (node A → ila1)-",
"to_node": "Edfa0_fiber (node A → ila1)-"
},
{
"from_node": "fiber (ila1 → ila2)-",
"to_node": "Edfa0_fiber (ila1 → ila2)-"
},
{
"from_node": "fiber (ila2 → node B)-",
"to_node": "west edfa in node B to ila2"
},
{
"from_node": "fiber (node B → ila3)-",
"to_node": "Edfa0_fiber (node B → ila3)-"
},
{
"from_node": "fiber (ila3 → ila4)-",
"to_node": "Edfa0_fiber (ila3 → ila4)-"
},
{
"from_node": "fiber (ila4 → node C)-",
"to_node": "Edfa0_fiber (ila4 → node C)-"
},
{
"from_node": "fiber (ila1 → node A)-",
"to_node": "Edfa0_fiber (ila1 → node A)-"
},
{
"from_node": "fiber (ila2 → ila1)-",
"to_node": "Edfa0_fiber (ila2 → ila1)-"
},
{
"from_node": "fiber (node B → ila2)-",
"to_node": "Edfa0_fiber (node B → ila2)-"
},
{
"from_node": "fiber (ila3 → node B)-",
"to_node": "Edfa0_fiber (ila3 → node B)-"
},
{
"from_node": "fiber (ila4 → ila3)-",
"to_node": "Edfa0_fiber (ila4 → ila3)-"
},
{
"from_node": "fiber (node C → ila4)-",
"to_node": "Edfa0_fiber (node C → ila4)-"
},
{
"from_node": "east edfa in node B to ila2",
"to_node": "fiber (node B → ila2)-"
},
{
"from_node": "west edfa in node B to ila2",
"to_node": "roadm node B"
},
{
"from_node": "Edfa0_roadm node A",
"to_node": "fiber (node A → ila1)-"
},
{
"from_node": "Edfa0_roadm node B",
"to_node": "fiber (node B → ila3)-"
},
{
"from_node": "Edfa0_roadm node C",
"to_node": "fiber (node C → ila4)-"
},
{
"from_node": "Edfa0_fiber (node A → ila1)-",
"to_node": "fiber (ila1 → ila2)-"
},
{
"from_node": "Edfa0_fiber (ila1 → ila2)-",
"to_node": "fiber (ila2 → node B)-"
},
{
"from_node": "Edfa0_fiber (node B → ila3)-",
"to_node": "fiber (ila3 → ila4)-"
},
{
"from_node": "Edfa0_fiber (ila3 → ila4)-",
"to_node": "fiber (ila4 → node C)-"
},
{
"from_node": "Edfa0_fiber (ila4 → node C)-",
"to_node": "roadm node C"
},
{
"from_node": "Edfa0_fiber (ila1 → node A)-",
"to_node": "roadm node A"
},
{
"from_node": "Edfa0_fiber (ila2 → ila1)-",
"to_node": "fiber (ila1 → node A)-"
},
{
"from_node": "Edfa0_fiber (node B → ila2)-",
"to_node": "fiber (ila2 → ila1)-"
},
{
"from_node": "Edfa0_fiber (ila3 → node B)-",
"to_node": "roadm node B"
},
{
"from_node": "Edfa0_fiber (ila4 → ila3)-",
"to_node": "fiber (ila3 → node B)-"
},
{
"from_node": "Edfa0_fiber (node C → ila4)-",
"to_node": "fiber (ila4 → ila3)-"
}
]
}

View File

@@ -144,11 +144,11 @@ Computed path (roadms):['roadm Lannion_CAS', 'roadm Lorient_KMA']
Result summary
req id demand snr@bandwidth A-Z (Z-A) snr@0.1nm A-Z (Z-A) Receiver minOSNR mode Gbit/s nb of tsp pairs N,M or blocking reason
0 trx Lorient_KMA to trx Vannes_KBE : 24.62 28.7 14 mode 1 100.0 1 (-284,4)
1 trx Brest_KLA to trx Vannes_KBE : 17.59 21.68 14 mode 1 200.0 2 (-272,8)
0 trx Lorient_KMA to trx Vannes_KBE : 24.83 28.92 14 mode 1 100.0 1 (-284,4)
1 trx Brest_KLA to trx Vannes_KBE : 17.75 21.83 14 mode 1 200.0 2 (-272,8)
3 trx Lannion_CAS to trx Rennes_STA : 22.21 26.29 13 mode 1 60.0 1 (-284,4)
4 trx Rennes_STA to trx Lannion_CAS : 15.94 23.16 17 mode 2 150.0 1 (-258,6)
5 trx Rennes_STA to trx Lannion_CAS : 20.22 27.44 17 mode 2 20.0 1 (-274,6)
7 | 6 trx Lannion_CAS to trx Lorient_KMA : 19.24 23.32 14 mode 1 700.0 7 (-224,28)
7b trx Lannion_CAS to trx Lorient_KMA : 19.32 23.4 14 mode 1 400.0 4 (-172,24)
4 trx Rennes_STA to trx Lannion_CAS : 16.07 23.29 17 mode 2 150.0 1 (-258,6)
5 trx Rennes_STA to trx Lannion_CAS : 20.31 27.54 17 mode 2 20.0 1 (-274,6)
7 | 6 trx Lannion_CAS to trx Lorient_KMA : 19.52 23.61 14 mode 1 700.0 7 (-224,28)
7b trx Lannion_CAS to trx Lorient_KMA : 19.61 23.69 14 mode 1 400.0 4 (-172,24)
Result summary shows mean SNR and OSNR (average over all channels)

View File

@@ -8,10 +8,10 @@ Now propagating between Site_A and Site_B:
Propagating with input power = 0.00 dBm:
Transceiver Site_A
OSNR ASE (0.1nm, dB): inf
OSNR ASE (signal bw, dB): inf
SNR total (signal bw, dB): inf
SNR total (0.1nm, dB): inf
OSNR ASE (0.1nm, dB): 40.00
OSNR ASE (signal bw, dB): 35.92
SNR total (signal bw, dB): 35.92
SNR total (0.1nm, dB): 40.00
CD (ps/nm): 0.00
PMD (ps): 0.00
Fiber Span1
@@ -36,15 +36,15 @@ Edfa Edfa1
effective pch (dBm): -2.0
output VOA (dB): 0.00
Transceiver Site_B
OSNR ASE (0.1nm, dB): 32.03
OSNR ASE (signal bw, dB): 27.95
SNR total (signal bw, dB): 26.27
SNR total (0.1nm, dB): 30.35
OSNR ASE (0.1nm, dB): 33.30
OSNR ASE (signal bw, dB): 29.21
SNR total (signal bw, dB): 27.09
SNR total (0.1nm, dB): 31.17
CD (ps/nm): 1336.00
PMD (ps): 0.36
Transmission result for input power = 0.00 dBm:
Final SNR total (0.1 nm): 30.35 dB
Final SNR total (0.1 nm): 31.17 dB
(No source node specified: picked Site_A)

View File

@@ -8,10 +8,10 @@ Now propagating between Site_A and Site_B:
Propagating with input power = 0.00 dBm:
Transceiver Site_A
OSNR ASE (0.1nm, dB): inf
OSNR ASE (signal bw, dB): inf
SNR total (signal bw, dB): inf
SNR total (0.1nm, dB): inf
OSNR ASE (0.1nm, dB): 40.00
OSNR ASE (signal bw, dB): 35.92
SNR total (signal bw, dB): 35.92
SNR total (0.1nm, dB): 40.00
CD (ps/nm): 0.00
PMD (ps): 0.00
RamanFiber Span1
@@ -36,94 +36,94 @@ Edfa Edfa1
effective pch (dBm): -2.0
output VOA (dB): 0.00
Transceiver Site_B
OSNR ASE (0.1nm, dB): 32.65
OSNR ASE (signal bw, dB): 28.57
SNR total (signal bw, dB): 26.48
SNR total (0.1nm, dB): 30.56
OSNR ASE (0.1nm, dB): 34.18
OSNR ASE (signal bw, dB): 30.10
SNR total (signal bw, dB): 27.35
SNR total (0.1nm, dB): 31.43
CD (ps/nm): 1336.00
PMD (ps): 0.36
Transmission result for input power = 0.00 dBm:
Final SNR total (0.1 nm): 30.56 dB
Final SNR total (0.1 nm): 31.43 dB
The total SNR per channel at the end of the line is:
Ch. # Channel frequency (THz) Channel power (dBm) OSNR ASE (signal bw, dB) SNR NLI (signal bw, dB) SNR total (signal bw, dB)
1 191.35 0.21 29.57 31.47 27.41
2 191.40 0.17 29.56 31.38 27.36
3 191.45 0.14 29.54 31.30 27.32
4 191.50 0.10 29.53 31.22 27.28
5 191.55 0.04 29.51 31.14 27.24
6 191.60 -0.02 29.49 31.06 27.19
7 191.65 -0.08 29.47 30.98 27.15
8 191.70 -0.14 29.45 30.90 27.11
9 191.75 -0.20 29.43 30.83 27.06
10 191.80 -0.26 29.41 30.75 27.02
11 191.85 -0.33 29.39 30.68 26.97
12 191.90 -0.39 29.36 30.61 26.93
13 191.95 -0.46 29.34 30.54 26.89
14 192.00 -0.52 29.32 30.47 26.84
15 192.05 -0.59 29.30 30.40 26.80
16 192.10 -0.66 29.27 30.33 26.76
17 192.15 -0.72 29.25 30.26 26.72
18 192.20 -0.79 29.22 30.20 26.67
19 192.25 -0.86 29.20 30.21 26.66
20 192.30 -0.94 29.17 30.21 26.65
21 192.35 -1.01 29.14 30.22 26.64
22 192.40 -1.09 29.11 30.23 26.62
23 192.45 -1.16 29.08 30.23 26.61
24 192.50 -1.24 29.05 30.24 26.60
25 192.55 -1.31 29.03 30.25 26.58
26 192.60 -1.38 29.00 30.25 26.57
27 192.65 -1.45 28.97 30.26 26.56
28 192.70 -1.52 28.94 30.27 26.55
29 192.75 -1.59 28.91 30.28 26.53
30 192.80 -1.66 28.89 30.28 26.52
31 192.85 -1.73 28.86 30.29 26.50
32 192.90 -1.80 28.83 30.30 26.49
33 192.95 -1.87 28.80 30.30 26.48
34 193.00 -1.94 28.77 30.31 26.46
35 193.05 -2.01 28.74 30.32 26.45
36 193.10 -2.08 28.71 30.33 26.43
37 193.15 -2.15 28.68 30.33 26.42
38 193.20 -2.22 28.65 30.35 26.41
39 193.25 -2.29 28.62 30.37 26.40
40 193.30 -2.36 28.59 30.39 26.38
41 193.35 -2.43 28.56 30.40 26.37
42 193.40 -2.49 28.53 30.42 26.36
43 193.45 -2.56 28.49 30.44 26.35
44 193.50 -2.63 28.46 30.46 26.34
45 193.55 -2.70 28.43 30.47 26.32
46 193.60 -2.78 28.40 30.49 26.31
47 193.65 -2.85 28.36 30.51 26.29
48 193.70 -2.92 28.33 30.53 26.28
49 193.75 -2.99 28.29 30.54 26.26
50 193.80 -3.06 28.26 30.56 26.25
51 193.85 -3.14 28.22 30.58 26.23
52 193.90 -3.21 28.19 30.60 26.22
53 193.95 -3.28 28.15 30.62 26.20
54 194.00 -3.35 28.12 30.64 26.19
55 194.05 -3.42 28.08 30.65 26.17
56 194.10 -3.50 28.04 30.67 26.15
57 194.15 -3.57 28.01 30.73 26.15
58 194.20 -3.64 27.97 30.79 26.14
59 194.25 -3.72 27.93 30.85 26.14
60 194.30 -3.79 27.89 30.91 26.13
61 194.35 -3.86 27.86 30.97 26.13
62 194.40 -3.93 27.82 31.03 26.12
63 194.45 -4.01 27.78 31.09 26.12
64 194.50 -4.08 27.74 31.15 26.11
65 194.55 -4.14 27.71 31.22 26.11
66 194.60 -4.21 27.67 31.28 26.10
67 194.65 -4.28 27.63 31.35 26.09
68 194.70 -4.34 27.60 31.41 26.09
69 194.75 -4.41 27.56 31.48 26.08
70 194.80 -4.47 27.52 31.55 26.08
71 194.85 -4.54 27.49 31.62 26.07
72 194.90 -4.60 27.45 31.69 26.06
73 194.95 -4.67 27.41 31.77 26.06
74 195.00 -4.73 27.38 31.84 26.05
75 195.05 -4.80 27.34 31.91 26.04
76 195.10 -4.86 27.30 31.91 26.01
1 191.35 0.21 31.56 31.47 28.50
2 191.40 0.17 31.54 31.38 28.45
3 191.45 0.14 31.52 31.30 28.40
4 191.50 0.10 31.50 31.22 28.34
5 191.55 0.04 31.47 31.14 28.29
6 191.60 -0.02 31.44 31.06 28.23
7 191.65 -0.08 31.41 30.98 28.18
8 191.70 -0.14 31.37 30.90 28.12
9 191.75 -0.20 31.34 30.83 28.07
10 191.80 -0.26 31.31 30.75 28.01
11 191.85 -0.33 31.27 30.68 27.96
12 191.90 -0.39 31.24 30.61 27.90
13 191.95 -0.46 31.20 30.54 27.85
14 192.00 -0.52 31.17 30.47 27.79
15 192.05 -0.59 31.13 30.40 27.74
16 192.10 -0.66 31.10 30.33 27.69
17 192.15 -0.72 31.06 30.26 27.63
18 192.20 -0.79 31.02 30.20 27.58
19 192.25 -0.86 30.98 30.21 27.57
20 192.30 -0.94 30.94 30.21 27.55
21 192.35 -1.01 30.90 30.22 27.54
22 192.40 -1.09 30.86 30.23 27.52
23 192.45 -1.16 30.81 30.23 27.50
24 192.50 -1.24 30.77 30.24 27.49
25 192.55 -1.31 30.73 30.25 27.47
26 192.60 -1.38 30.69 30.25 27.46
27 192.65 -1.45 30.65 30.26 27.44
28 192.70 -1.52 30.61 30.27 27.42
29 192.75 -1.59 30.56 30.28 27.41
30 192.80 -1.66 30.52 30.28 27.39
31 192.85 -1.73 30.48 30.29 27.37
32 192.90 -1.80 30.44 30.30 27.36
33 192.95 -1.87 30.39 30.30 27.34
34 193.00 -1.94 30.35 30.31 27.32
35 193.05 -2.01 30.31 30.32 27.30
36 193.10 -2.08 30.27 30.33 27.29
37 193.15 -2.15 30.22 30.33 27.27
38 193.20 -2.22 30.18 30.35 27.25
39 193.25 -2.29 30.14 30.37 27.24
40 193.30 -2.36 30.09 30.39 27.23
41 193.35 -2.43 30.05 30.40 27.21
42 193.40 -2.49 30.01 30.42 27.20
43 193.45 -2.56 29.96 30.44 27.18
44 193.50 -2.63 29.92 30.46 27.17
45 193.55 -2.70 29.87 30.47 27.15
46 193.60 -2.78 29.83 30.49 27.13
47 193.65 -2.85 29.78 30.51 27.12
48 193.70 -2.92 29.73 30.53 27.10
49 193.75 -2.99 29.68 30.54 27.08
50 193.80 -3.06 29.64 30.56 27.06
51 193.85 -3.14 29.59 30.58 27.05
52 193.90 -3.21 29.54 30.60 27.03
53 193.95 -3.28 29.49 30.62 27.01
54 194.00 -3.35 29.44 30.64 26.99
55 194.05 -3.42 29.39 30.65 26.97
56 194.10 -3.50 29.34 30.67 26.95
57 194.15 -3.57 29.29 30.73 26.94
58 194.20 -3.64 29.24 30.79 26.94
59 194.25 -3.72 29.19 30.85 26.93
60 194.30 -3.79 29.14 30.91 26.93
61 194.35 -3.86 29.09 30.97 26.92
62 194.40 -3.93 29.04 31.03 26.91
63 194.45 -4.01 28.99 31.09 26.90
64 194.50 -4.08 28.94 31.15 26.90
65 194.55 -4.14 28.89 31.22 26.89
66 194.60 -4.21 28.85 31.28 26.88
67 194.65 -4.28 28.80 31.35 26.88
68 194.70 -4.34 28.75 31.41 26.87
69 194.75 -4.41 28.70 31.48 26.86
70 194.80 -4.47 28.66 31.55 26.86
71 194.85 -4.54 28.61 31.62 26.85
72 194.90 -4.60 28.56 31.69 26.84
73 194.95 -4.67 28.51 31.77 26.83
74 195.00 -4.73 28.47 31.84 26.82
75 195.05 -4.80 28.42 31.91 26.81
76 195.10 -4.86 28.37 31.91 26.78
(No source node specified: picked Site_A)

View File

@@ -129,6 +129,8 @@ def create_rq(equipment, srce, dest, bdir, nd_list, ls_list):
f_max_from_si = params['f_max']
params['nb_channel'] = automatic_nch(f_min, f_max_from_si, params['spacing'])
params['path_bandwidth'] = 100000000000.0
params['effective_freq_slot'] = None
params['blocking_reason'] = None
requests_list.append(PathRequest(**params))
return requests_list

View File

@@ -12,7 +12,7 @@ DATA_DIR = TEST_DIR / 'data'
def test_sim_parameters():
j = load_json(DATA_DIR / 'test_sim_params.json')
j = load_json(DATA_DIR / 'sim_params.json')
sim_params = SimParams(**j)
Simulation.set_params(sim_params)
s1 = Simulation.get_simulation()

View File

@@ -19,6 +19,7 @@ from pathlib import Path
from os import unlink
import shutil
from pandas import read_csv
from xlrd import open_workbook
import pytest
from tests.compare import compare_networks, compare_services
from copy import deepcopy
@@ -29,7 +30,8 @@ from gnpy.topology.request import (jsontocsv, requests_aggregation, compute_path
compute_path_with_disjunction, ResultElement, PathRequest)
from gnpy.topology.spectrum_assignment import build_oms_list, pth_assign_spectrum
from gnpy.tools.convert import convert_file
from gnpy.tools.json_io import load_json, load_network, save_network, load_equipment, requests_from_json, disjunctions_from_json
from gnpy.tools.json_io import (load_json, load_network, save_network, load_equipment, requests_from_json,
disjunctions_from_json, network_to_json, network_from_json)
from gnpy.tools.service_sheet import read_service_sheet, correct_xls_route_list
TEST_DIR = Path(__file__).parent
@@ -41,6 +43,8 @@ equipment = load_equipment(eqpt_filename)
@pytest.mark.parametrize('xls_input,expected_json_output', {
DATA_DIR / 'CORONET_Global_Topology.xlsx': DATA_DIR / 'CORONET_Global_Topology_expected.json',
DATA_DIR / 'testTopology.xls': DATA_DIR / 'testTopology_expected.json',
DATA_DIR / 'perdegreemeshTopologyExampleV2.xls': DATA_DIR / 'perdegreemeshTopologyExampleV2_expected.json'
}.items())
def test_excel_json_generation(tmpdir, xls_input, expected_json_output):
""" tests generation of topology json
@@ -104,20 +108,22 @@ def test_auto_design_generation_fromxlsgainmode(tmpdir, xls_input, expected_json
# test that autodesign creates same file as an input file already autodesigned
@pytest.mark.parametrize('json_input,expected_json_output',
@pytest.mark.parametrize('json_input, power_mode',
{DATA_DIR / 'CORONET_Global_Topology_auto_design_expected.json':
DATA_DIR / 'CORONET_Global_Topology_auto_design_expected.json',
False,
DATA_DIR / 'testTopology_auto_design_expected.json':
DATA_DIR / 'testTopology_auto_design_expected.json',
False,
DATA_DIR / 'perdegreemeshTopologyExampleV2_auto_design_expected.json':
True
}.items())
def test_auto_design_generation_fromjson(tmpdir, json_input, expected_json_output):
def test_auto_design_generation_fromjson(tmpdir, json_input, power_mode):
"""test that autodesign creates same file as an input file already autodesigned
"""
equipment = load_equipment(eqpt_filename)
network = load_network(json_input, equipment)
# in order to test the Eqpt sheet and load gain target,
# change the power-mode to False (to be in gain mode)
equipment['Span']['default'].power_mode = False
equipment['Span']['default'].power_mode = power_mode
# Build the network once using the default power defined in SI in eqpt config
p_db = equipment['SI']['default'].power_dbm
@@ -128,7 +134,7 @@ def test_auto_design_generation_fromjson(tmpdir, json_input, expected_json_outpu
save_network(network, actual_json_output)
actual = load_json(actual_json_output)
unlink(actual_json_output)
expected = load_json(expected_json_output)
expected = load_json(json_input)
results = compare_networks(expected, actual)
assert not results.elements.missing
@@ -361,9 +367,9 @@ def test_json_response_generation(xls_input, expected_response_file):
('trx Lannion_CAS', 'trx Lorient_KMA', 'Ploermel | Vannes_KBE', 'LOOSE',
['east edfa in Ploermel to Vannes_KBE', 'roadm Vannes_KBE']),
('trx Rennes_STA', 'trx Brest_KLA', 'Vannes_KBE | Quimper | Brest_KLA', 'LOOSE',
['roadm Vannes_KBE', 'Edfa0_fiber (Lorient_KMA → Quimper)-', 'roadm Brest_KLA']),
['roadm Vannes_KBE', 'west edfa in Quimper to Lorient_KMA', 'roadm Brest_KLA']),
('trx Brest_KLA', 'trx Rennes_STA', 'Brest_KLA | Quimper | Lorient_KMA', 'LOOSE',
['roadm Brest_KLA', 'Edfa0_fiber (Brest_KLA → Quimper)-', 'roadm Lorient_KMA']),
['roadm Brest_KLA', 'east edfa in Quimper to Lorient_KMA', 'roadm Lorient_KMA']),
('Brest_KLA', 'trx Rennes_STA', '', 'LOOSE', 'Fail'),
('trx Brest_KLA', 'Rennes_STA', '', 'LOOSE', 'Fail'),
('Brest_KLA', 'Rennes_STA', '', 'LOOSE', 'Fail'),
@@ -418,6 +424,8 @@ def test_excel_ila_constraints(source, destination, route_list, hoptype, expecte
'nb_channel': 0,
'power': 0,
'path_bandwidth': 0,
'effective_freq_slot': None,
'blocking_reason': None
}
request = PathRequest(**params)
@@ -427,3 +435,136 @@ def test_excel_ila_constraints(source, destination, route_list, hoptype, expecte
else:
with pytest.raises(ServiceError):
[request] = correct_xls_route_list(service_xls_input, network, [request])
def setup_per_degree(case):
""" common setup for degree: returns the dict network for different cases
"""
json_network = load_json(DATA_DIR / 'testTopology_expected.json')
json_network_auto = load_json(DATA_DIR / 'testTopology_auto_design_expected.json')
if case == 'no':
return json_network
elif case == 'all':
return json_network_auto
elif case == 'Lannion_CAS and all':
elem = next(e for e in json_network['elements'] if e['uid'] == 'roadm Lannion_CAS')
elem['params'] = {'per_degree_pch_out_db': {
"east edfa in Lannion_CAS to Corlay": -17,
"east edfa in Lannion_CAS to Stbrieuc": -18,
"east edfa in Lannion_CAS to Morlaix": -21}}
return json_network
elif case == 'Lannion_CAS and one':
elem = next(e for e in json_network['elements'] if e['uid'] == 'roadm Lannion_CAS')
elem['params'] = {'per_degree_pch_out_db': {
"east edfa in Lannion_CAS to Corlay": -17,
"east edfa in Lannion_CAS to Stbrieuc": -18}}
return json_network
@pytest.mark.parametrize('case', ['no', 'all', 'Lannion_CAS and all', 'Lannion_CAS and one'])
def test_target_pch_out_db_global(case):
""" check that per degree attributes are correctly created with global values if none are given
"""
json_network = setup_per_degree(case)
per_degree = {}
for elem in json_network['elements']:
if 'type' in elem.keys() and elem['type'] == 'Roadm' and 'params' in elem.keys() \
and 'per_degree_pch_out_db' in elem['params']:
# records roadms that have a per degree target
per_degree[elem['uid']] = {k: v for k, v in elem['params']['per_degree_pch_out_db'].items()}
network = network_from_json(json_network, equipment)
# Build the network once using the default power defined in SI in eqpt config
# 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)
data = network_to_json(network)
for elem in data['elements']:
if 'type' in elem.keys() and elem['type'] == 'Roadm':
# check that power target attributes exist and are filled with correct values
# first check that global 'target_pch_out_db' is correctly filled
assert elem['params']['target_pch_out_db'] == equipment['Roadm']['default'].target_pch_out_db
for degree, power in elem['params']['per_degree_pch_out_db'].items():
if elem['uid'] not in per_degree.keys():
# second: check that per degree 'target_pch_out_db' is correctly filled with global value
# when there was no per degree specification on network input
assert power == equipment['Roadm']['default'].target_pch_out_db
else:
if degree not in per_degree[elem['uid']].keys():
# third: check that per degree 'target_pch_out_db' is correctly filled with global value
# on degrees that had no specification when other degrees are filled
assert power == equipment['Roadm']['default'].target_pch_out_db
else:
# fourth: check that per degree 'target_pch_out_db' is correctly filled with specified values
assert power == per_degree[elem['uid']][degree]
def all_rows(sh, start=0):
""" reads excel sheet row per row
"""
return (sh.row(x) for x in range(start, sh.nrows))
class Amp:
""" Node element contains uid, list of connected nodes and eqpt type
"""
def __init__(self, uid, to_node, eqpt=None, west=None):
self.uid = uid
self.to_node = to_node
self.eqpt = eqpt
self.west = west
def test_eqpt_creation(tmpdir):
""" tests that convert correctly creates equipment according to equipment sheet
including all cominations in testTopologyconvert.xls: if a line exists the amplifier
should be created even if no values are provided.
"""
xls_input = DATA_DIR / 'testTopologyconvert.xls'
xls_copy = Path(tmpdir) / xls_input.name
shutil.copyfile(xls_input, xls_copy)
convert_file(xls_copy)
actual_json_output = xls_copy.with_suffix('.json')
actual = load_json(actual_json_output)
unlink(actual_json_output)
connections = {elem['from_node']: elem['to_node'] for elem in actual['connections']}
jsonconverted = {}
for elem in actual['elements']:
if 'type' in elem.keys() and elem['type'] == 'Edfa':
print(elem['uid'])
if 'type_variety' in elem.keys():
jsonconverted[elem['uid']] = Amp(elem['uid'], connections[elem['uid']], elem['type_variety'])
else:
jsonconverted[elem['uid']] = Amp(elem['uid'], connections[elem['uid']])
with open_workbook(xls_input) as wobo:
# reading Eqpt sheet assuming header is node A, Node Z, amp variety
# fused should not be recorded as an amp
eqpt_sheet = wobo.sheet_by_name('Eqpt')
raw_eqpts = {}
for row in all_rows(eqpt_sheet, start=5):
if row[0].value not in raw_eqpts.keys():
raw_eqpts[row[0].value] = Amp(row[0].value, [row[1].value], [row[2].value], [row[7].value])
else:
raw_eqpts[row[0].value].to_node.append(row[1].value)
raw_eqpts[row[0].value].eqpt.append(row[2].value)
raw_eqpts[row[0].value].west.append(row[7].value)
# create the possible names similarly to what convert should do
possiblename = [f'east edfa in {xlsname} to {node}' for xlsname, value in raw_eqpts.items()
for i, node in enumerate(value.to_node) if value.eqpt[i] != 'fused'] +\
[f'west edfa in {xlsname} to {node}' for xlsname, value in raw_eqpts.items()
for i, node in enumerate(value.to_node) if value.west[i] != 'fused']
# check that all lines in eqpt sheet correctly converts to an amp element
for name in possiblename:
assert name in jsonconverted.keys()
# check that all amp in the converted files corresponds to an eqpt line
for ampuid in jsonconverted.keys():
assert ampuid in possiblename

View File

@@ -13,11 +13,7 @@ from pathlib import Path
from networkx import dijkstra_path
from numpy import mean, sqrt, ones
#network_file_name = 'tests/test_network.json'
network_file_name = Path(__file__).parent.parent / 'tests/LinkforTest.json'
# TODO: note that this json entries has a weird topology since EDfa1 has a possible branch on a receiver B
# this might not pass future tests/ code updates
#network_file_name = Path(__file__).parent.parent / 'examples/edfa_example_network.json'
eqpt_library_name = Path(__file__).parent.parent / 'tests/data/eqpt_config.json'

View File

@@ -16,7 +16,10 @@ from gnpy.core.utils import lin2db, automatic_nch
from gnpy.core.elements import Fused, Roadm, Edfa
from gnpy.core.network import build_network
from gnpy.tools.json_io import network_from_json, load_equipment, load_json, Amp
from gnpy.core.equipment import trx_mode_params
from gnpy.topology.request import PathRequest, compute_constrained_path
from gnpy.core.info import create_input_spectral_information
from gnpy.core.utils import db2lin
TEST_DIR = Path(__file__).parent
EQPT_LIBRARY_NAME = TEST_DIR / 'data/eqpt_config.json'
@@ -202,3 +205,73 @@ def test_restrictions(restrictions, equipment):
not roadms[next(network.successors(amp)).uid].restrictions['preamp_variety_list']:
if amp.params.type_variety not in restrictions['preamp_variety_list']:
raise AssertionError()
@pytest.mark.parametrize('prev_node_type, effective_pch_out_db', [('edfa', -20.0), ('fused', -22.0)])
def test_roadm_target_power(prev_node_type, effective_pch_out_db):
''' 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, so that the target
power can not be met in this last case.
'''
equipment = load_equipment(EQPT_LIBRARY_NAME)
json_network = load_json(TEST_DIR / 'data/twohops_roadm_power_test.json')
prev_node = next(n for n in json_network['elements'] if n['uid'] == 'west edfa in node B to ila2')
json_network['elements'].remove(prev_node)
if prev_node_type == 'edfa':
prev_node = {'uid': 'west edfa in node B to ila2', 'type': 'Edfa'}
elif prev_node_type == 'fused':
prev_node = {'uid': 'west edfa in node B to ila2', 'type': 'Fused'}
prev_node['params'] = {'loss': 0}
json_network['elements'].append(prev_node)
network = network_from_json(json_network, equipment)
# Build the network once using the default power defined in SI in eqpt config
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)
params = {}
params['request_id'] = 0
params['trx_type'] = ''
params['trx_mode'] = ''
params['source'] = 'trx node A'
params['destination'] = 'trx node C'
params['bidir'] = False
params['nodes_list'] = ['trx node C']
params['loose_list'] = ['strict']
params['format'] = ''
params['path_bandwidth'] = 100e9
params['effective_freq_slot'] = None
params['blocking_reason'] = None
trx_params = trx_mode_params(equipment)
params.update(trx_params)
req = PathRequest(**params)
path = compute_constrained_path(network, req)
si = create_input_spectral_information(
req.f_min, req.f_max, req.roll_off, req.baud_rate,
req.power, req.spacing)
for i, el in enumerate(path):
if isinstance(el, Roadm):
carriers_power_in_roadm = min([c.power.signal + c.power.nli + c.power.ase for c in si.carriers])
si = el(si, degree=path[i+1].uid)
if el.uid == 'roadm node B':
print('input', carriers_power_in_roadm)
assert el.effective_pch_out_db == effective_pch_out_db
for carrier in si.carriers:
print(carrier.power.signal + carrier.power.nli + carrier.power.ase)
power = carrier.power.signal + carrier.power.nli + carrier.power.ase
if prev_node_type == 'edfa':
# edfa prev_node sets input power to roadm to a high enough value:
# Check that egress power of roadm is equal to target power
assert power == pytest.approx(db2lin(effective_pch_out_db - 30), rel=1e-3)
elif prev_node_type == 'fused':
# fused prev_node does reamplfy power after fiber propagation, so input power
# to roadm is low.
# Check that egress power of roadm is equalized to the min carrier input power.
assert power == pytest.approx(carriers_power_in_roadm, rel=1e-3)
else:
si = el(si)

View File

@@ -2,24 +2,25 @@
# -*- coding: utf-8 -*-
# @Author: Alessio Ferrari
"""
checks that RamanFiber propagates properly the spectral information. In this way, also the RamanSolver and the NliSolver
are tested.
Checks that RamanFiber propagates properly the spectral information. In this way, also the RamanSolver and the NliSolver
are tested.
"""
from pathlib import Path
from pandas import read_csv
from numpy.testing import assert_allclose
from gnpy.core.info import create_input_spectral_information
from gnpy.core.elements import RamanFiber
from gnpy.core.parameters import SimParams
from gnpy.core.science_utils import Simulation
from gnpy.tools.json_io import load_json
from pathlib import Path
TEST_DIR = Path(__file__).parent
def test_raman_fiber():
""" Test the accuracy of propagating the RamanFiber.
"""
""" Test the accuracy of propagating the RamanFiber."""
# spectral information generation
power = 1e-3
eqpt_params = load_json(TEST_DIR / 'data' / 'eqpt_config.json')
@@ -41,7 +42,7 @@ def test_raman_fiber():
p_ase = [carrier.power.ase for carrier in spectral_info_out.carriers]
p_nli = [carrier.power.nli for carrier in spectral_info_out.carriers]
expected_results = read_csv(TEST_DIR / 'data' / 'expected_results_science_utils.csv')
expected_results = read_csv(TEST_DIR / 'data' / 'test_science_utils_expected_results.csv')
assert_allclose(p_signal, expected_results['signal'], rtol=1e-3)
assert_allclose(p_ase, expected_results['ase'], rtol=1e-3)
assert_allclose(p_nli, expected_results['nli'], rtol=1e-3)