Skip to content

Commit

Permalink
updated docs
Browse files Browse the repository at this point in the history
  • Loading branch information
flaport committed Jun 23, 2020
1 parent a417af0 commit 1c71d05
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 155 deletions.
2 changes: 1 addition & 1 deletion photontorch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"""

__version__ = "0.0.4"
__version__ = "0.1.0"

# Test pytorch version
import torch
Expand Down
29 changes: 0 additions & 29 deletions photontorch/components/readme.md

This file was deleted.

43 changes: 43 additions & 0 deletions photontorch/components/readme.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
components
==========

Each Component is generally defined by several key attributes defining the
behavior of the component in a network.

* `num_ports`: The number of ports of the components.

* `S`: The scattering matrix of the component.

* `C`: The connection matrix for the component (usually all zero for base
components)

* `sources_at`: The location of the sources in the component (usually all zero
for base components)

* `detectors_at`: The location of the detectors in the component (usually all
zero for base components)

* `actions_at`: The location of the active nodes in the component (usually all
zero for passive components)

* `delays`: delays introduced by the nodes of the component.

Defining your own Component comes down to subclassing `Component` and
redefining the relevant setters `set_*` for these attributes. For example::

class Waveguide(pt.Component):
num_ports = 2
def __init__(self, length=1e-5, neff=2.34, ng=3.40, name=None):
super(Waveguide, self).__init__(self, name=name)
self.neff = float(neff)
self.wl0 = float(wl0)
self.ng = float(ng)
self.length = float(length)
def set_delays(self, delays):
delays[:] = self.ng * self.length / self.env.c
def set_S(self, S):
wls = torch.tensor(self.env.wl, dtype=torch.float64, device=self.device)
phase = (2 * np.pi * neff * self.length / wls) % (2 * np.pi)
S[0, :, 0, 1] = S[0, :, 1, 0] = torch.cos(phase).to(torch.float32) # real part
S[1, :, 0, 1] = S[1, :, 1, 0] = torch.sin(phase).to(torch.float32) # imag part

8 changes: 0 additions & 8 deletions photontorch/detectors/readme.md

This file was deleted.

119 changes: 119 additions & 0 deletions photontorch/detectors/readme.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
detectors
=========

lowpassdetector
---------------

responsivity
^^^^^^^^^^^^

The detected photo-current :math:`I_p~[C/s]` can be described in terms of the
optical power :math:`P_o~[W]` through the quantum efficiency :math:`\eta` and
the carrier frequency :math:`f=c/\lambda~[1/s]` of the light:

.. math::
\begin{align*}
I_p = \eta \frac{q}{h f}P_o = \eta \frac{q \lambda}{h c}P_o
\end{align*}
However, it's often more useful to describe it in terms of a single quantity
:math:`r~[A/W]`:

.. math::
\begin{align*}
I_p &= rP_o,
\end{align*}
where we defined :math:`r~[A/W]` as the responsivity of the detector. At a
wavelength of :math:`1550nm` and for a quantum efficiency of :math:`\eta=1`, we
have:

.. math::
\begin{align*}
r &= \eta \frac{q \lambda}{h c} = 1.25 A/W,
\end{align*}
hence a default value of 1 for the responsivity is appropriate.


photodetector
-------------

responsivity
^^^^^^^^^^^^

As mentioned above, a responsivity of :math:`r=1 A/W` is a valid value, but almost
always it will be lower. If the detected signal is not as noisy as expected,
you probably used a too high responsivity.

thermal noise
^^^^^^^^^^^^^
On top of the detected current, thermal noise should be added. The thermal
noise variance :math:`\sigma_t^2` [:math:`C^2/s^2`] is given by:

.. math::
\begin{align*}
\sigma_t^2 &= N_t^2 f_c,
\end{align*}
with :math:`f_c~[1/s]` the cutoff frequency of the signal and
:math:`N_t~[C/\sqrt s]` the thermal noise spectral density. This represenation
(which is the representation used in VPI for example) requires you to use a
quantity with :math:`N_t~[C/\sqrt s]` with weird units. We therefore prefer
another representation (see for example
`wikipedia <https://en.wikipedia.org/wiki/Johnson%E2%80%93Nyquist_noise>`_):

.. math::
\begin{align*}
\sigma_t^2 = \frac{4kT}{R_L} f_c
\end{align*}
To find a good default value for the load resistance :math:`R_L`, we can equate
this equation with the previous one using the default VPI value for
:math:`N_t=10^{-11}C/\sqrt s`:

.. math::
\begin{align}
R_L = 166 \Omega
\end{align}
where we assumed for the room temparture :math:`T=300K`.

shot noise
^^^^^^^^^^
The shot noise variance :math:`\sigma_t^2` [:math:`C^2/s^2`] for a PIN
photodetector is given by:

.. math::
\begin{align*}
\sigma_s^2 = 2 q (I_p + I_d)f_c
\end{align*}
with :math:`q~[C]` the elementary charge and :math:`I_d` the dark current of
the photodetector. Notice that this power is dependent on the photocurrent
:math:`I_p~[C/s]` itself.

In some representations, :math:`\mu_p=\left< I_p \right>~[C/s]` is used
instead. We choose not to take this average to have a more accurate
reprentation. Moreover, low-pass filtering already takes a kind of moving
average into account.

total noise
^^^^^^^^^^^
The total post-detection noise variance :math:`\sigma_p^2` [:math:`C^2/s^2`] now depends on
the thermal noise variance :math:`\sigma_t^2` and the shot noise variance
:math:`\sigma_s^2`:

.. math::
\begin{align*}
\sigma_p^2 &= \sigma_t^2 + \sigma_s^2
\end{align*}
10 changes: 0 additions & 10 deletions photontorch/environment/readme.md

This file was deleted.

5 changes: 5 additions & 0 deletions photontorch/environment/readme.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
environment
===========

The `Environment` class contains all the necessary parameters to initialize a
network for a simulation.
74 changes: 0 additions & 74 deletions photontorch/networks/readme.md

This file was deleted.

92 changes: 92 additions & 0 deletions photontorch/networks/readme.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
networks
========

The Network is the core of Photontorch.

This is where everything comes together. The Network is a special kind of
torch.nn.Module, where all subcomponents are automatically initialized and
connected in the right way.

reduction of S-matrix
---------------------

Each component can be described in terms of it's S matrix. For such a
component, we have that the output fields :math:`\bf x_{\rm out}` are connected to
the input fields :math:`x_{\rm in}` through a scattering matrix:

.. math::
x_{\rm out} = S \cdot x_{\rm in}
For a network of components, the field vectors :math:`x` will just be stacked on top of each other,
and the S-matrix will just be the block-diagonal matrix of the S-matrices of the
individual components. However, to connect the output fields to each other, we need
a connection matrix, which connects the output fields of the individual components
to input fields of other components in the fields vector :math:`x`:

.. math::
x_{\rm in} = C \cdot x_{\rm out}
a simulation (without delays) can thus simply be described by:

.. math::
x(t+1) = C\cdot S\cdot x(t)
However, when delays come in the picture, the situation is a bit more complex.
We then split the fields vector :math:`x` in a memory_containing part (mc) and a
memory-less part (ml):

.. math::
\begin{pmatrix}x^{\rm mc} \\x^{\rm ml} \end{pmatrix}(t+1) =
\begin{pmatrix} C^{\rm mcmc} & C^{\rm mcml} \\ C^{\rm mlmc} & C^{\rm mlml} \end{pmatrix}
\begin{pmatrix} S^{\rm mcmc} & S^{\rm mcml} \\ S^{\rm mlmc} & S^{\rm mlml} \end{pmatrix}
\cdot\begin{pmatrix}x^{\rm mc} \\x^{\rm ml} \end{pmatrix}(t)
Usually, we are only interested in the memory-containing nodes, as memory-less nodes
should be connected together and act all at once. After some matrix algebra we arrive at

.. math::
\begin{align}
x^{\rm mc}(t+1) &= \left( C^{\rm mcmc} + C^{\rm mcml}\cdot S^{\rm mlml}\cdot
\left(1-C^{\rm mlml}S^{\rm mlml}\right)^{-1} C^{\rm mlmc}\right)S^{\rm mcmc} x^{\rm mc}(t) \\
&= C^{\rm red} x^{\rm mc}(t),
\end{align}
Which defines the reduced connection matrix used in the simulations.

complex matrix inverse
----------------------

PyTorch still does not allow complex valued Tensors. Therefore, the above
equation was completely rewritten with matrices containing the real and
imaginary parts. This would be fairly straightforward if it were not for the
matrix inverse in the reduced connection matrix:

.. math::
\begin{align}
P^{-1} = \left(1-C^{\rm mlml}S^{\rm mlml}\right)^{-1}
\end{align}
unfortunately for complex matrices :math:`P^{-1} \neq {\rm real}(P)^{-1} + i{\rm
imag}(P)^{-1}`, the actual case is a bit more complicated.

It is however, pretty clear from the equations that the :math:`{\rm real}(P)^{-1}`
will always exist, and thus we can write for the real and imaginary part of
:math:`P^{-1}`:


.. math::
\begin{align}
{\rm real}(P^{-1}) &= \left({\rm real}(P) + {\rm imag}(P)\cdot {\rm real}(P)^{-1}
\cdot {\rm imag}(P)\right)^{-1}\\
{\rm real}(P^{-1}) &= -{\rm real}(P^{-1})\cdot {\rm imag}(P) \cdot {\rm real}(P)^{-1}
\end{align}
This equation is valid, even if :math:`{\rm imag}(P)^{-1}` does not exist.

Loading

0 comments on commit 1c71d05

Please sign in to comment.