# -*- coding: utf-8 -*-
"""
Created on Wed Jun 03 15:11:20 2015
This file is part of pyNLO.
pyNLO is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
pyNLO is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with pyNLO. If not, see <http://www.gnu.org/licenses/>.
@author: ycasg
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import numpy as np
from scipy import misc, optimize
from scipy.constants import speed_of_light
[docs]class Crystal:
""" Container for chi-2 nonlinear crystals. Actual crystal refractive index,
dispersion, and nonlinearity information is stored in modular files. Read these
in by calling <crystal>.load(crystal_instance, params). """
_c_nm_ps = speed_of_light*1e9/1e12 # c in nm/ps
# Cache results
_wavelength_axes = {}
_enable_caching = False
_cached_ns = {}
_crystal_properties = {'damage_threshold_GW_per_sqcm': 1.0,
'damage_threshold_info' : ''}
def __init__(self, params):
if 'length' in params.keys():
self._length = params['length']
else:
self._length = 1.0
if 'enable_caching' in params.keys():
self._enable_caching = params['enable_caching']
else:
self._enable_caching = False
def set_pp_chirp(self, start, stop):
self.pp = lambda x: start + (stop-start) * x /self.length
[docs] def get_pulse_k(self, pulse_instance, axis = None):
""" Return vector of angular wavenumbers (m^-1) for the pulse_instance's
frequency grid inside the crystal """
if axis is None:
ks = 2.0 * np.pi * self.n(pulse_instance.wl_nm) / pulse_instance.wl_mks
else:
ks = 2.0 * np.pi * self.n(pulse_instance.wl_nm, axis) / pulse_instance.wl_mks
return ks
[docs] def get_pulse_n(self, pulse_instance, axis = None):
""" Return vector of indices of refraction for the pulse_instance's
frequency grid inside the crystal """
if self._enable_caching:
if pulse_instance.cache_hash + str(axis) in self._cached_ns.keys():
return self._cached_ns[pulse_instance.cache_hash + str(axis)]
else:
if axis is None:
ns = self.n(pulse_instance.wl_nm)
else:
ns = self.n(pulse_instance.wl_nm, axis)
self._cached_ns[pulse_instance.cache_hash + str(axis)]= ns
else:
if axis is None:
ns = self.n(pulse_instance.wl_nm)
else:
ns = self.n(pulse_instance.wl_nm, axis)
return ns
[docs] def calculate_group_velocity_nm_ps(self, wavelengths_nm, axis = None):
""" Calculate group velocity vg at 'wavelengths_nm' [nm] along 'axis'
in units of nm/ps """
# Equation 4.7.7b in Verdeyen
fn = lambda x: self.n(x, axis)
dn_dl = misc.derivative(fn, wavelengths_nm, dx = 0.1, n = 1, order = 11)
vg_inverse = (1.0 / self._c_nm_ps) * (fn(wavelengths_nm) - wavelengths_nm * dn_dl)
return 1.0 / vg_inverse
[docs] def calculate_pulse_delay_ps(self, wl1_nm, wl2_nm, crystal_length_mks = None, axis = None):
""" Calculate the pulse delay between pulses at wl1 and wl2 after
crystal. Be default, crystal instance's length is used. """
if crystal_length_mks is None:
crystal_length = self.length_nm
else:
crystal_length = 1.0e9 * crystal_length_mks
vg1 = self.calculate_group_velocity_nm_ps(wl1_nm, axis)
vg2 = self.calculate_group_velocity_nm_ps(wl2_nm, axis)
delta_t = crystal_length/vg1 - crystal_length/vg2
return delta_t
[docs] def calculate_D_ps_nm_km(self, wavelengths_nm, axis = None):
""" Calculate crystal dispersion at 'wavelengths_nm' [nm] along 'axis' in
standard photonic engineering units ps/nm/km"""
fn = lambda x: self.n(x, axis)
d2n_dl2 = misc.derivative(fn, wavelengths_nm, dx = 0.1, n = 2, order = 11)
D1 = (wavelengths_nm / self._c_nm_ps) * d2n_dl2 # units are ps/nm/nm
D = D1 * 1.0e12
return D
[docs] def calculate_D_fs_um_mm(self, wavelengths_nm, axis = None):
""" Calculate crystal dispersion at 'wavelengths_nm' along 'axis' in
short crystal, broad bandwidth units of fs/um/mm """
D = self.calculate_D_ps_nm_km(wavelengths_nm, axis)
scale = 1.0
return D * scale
[docs] def calculate_mix_phasematching_bw(self, pump_wl_nm, signal_wl_nm, axis = None ):
r"""Calculate the phase matching bandwidth in the case of mixing
between narrowband pump (highest photon energy) with a signal field.
The bandwidths of mixing between pump-signal and pump-idler are
calculated, and the smaller of the two is returned.
Parameters
----------
pump_wl_nm : float
Wavelength of pump field, bandwidth assumed to be 0 [nm]
signal_wl_nm : array-like
Wavelength of signal field [nm]
Returns
-------
acceptance bandwidth : float
Phasematching bandwidth [m^-1 * m]
References
----------
Peter E Powers, "Fundamentals of Nonlinear Optics", pp 106
"""
idler_wl_nm = 1.0/(1.0/pump_wl_nm - 1.0/signal_wl_nm)
vg_s = self.calculate_group_velocity_nm_ps(signal_wl_nm, axis)*1e3 # m/s
vg_i = self.calculate_group_velocity_nm_ps(idler_wl_nm, axis)*1e3 # m/s
vg_p = self.calculate_group_velocity_nm_ps(pump_wl_nm, axis)*1e3 # m/s
try:
vg_p = np.ones((len(vg_s),)) * vg_p
except TypeError:
pass
deltaOmega_deltaL = np.minimum(np.abs( 0.886 * np.pi / (1.0/vg_s - 1.0/vg_p) ),
np.abs( 0.886 * np.pi / (1.0/vg_i - 1.0/vg_p) ) )
deltak_deltaL = deltaOmega_deltaL * 1.0/speed_of_light
return deltak_deltaL
[docs] def invert_dfg_qpm_to_signal_wl(self, pump_wl_nm, poling_period_mks,
max_signal_wl_nm = 2000 ):
r"""Calculate the signal wavelength phasematched in QPM by the given
poing period for the specified pump wavelength.
Parameters
----------
pump_wl_nm : float
Wavelength of pump field, bandwidth assumed to be 0 [nm]
poling_period_mks : float
Period length of the QPM grating
Returns
-------
Signal wavelength [nm] : float
"""
def err_fn(wl_s):
return (self.calculate_poling_period(pump_wl_nm, wl_s, None, silent = True)[0] - poling_period_mks )**2
res = optimize.minimize_scalar(err_fn, bounds = [pump_wl_nm*1.001, max_signal_wl_nm],
method = 'bounded')
return res.x
[docs] def set_caching(self, cache_enable = True):
r""" Enable or disable caching of refractive indices. Enabling this uses
more memory, but can save costly recomputations
Parameters
----------
cache_enable : bool
"""
assert(cache_enable == True or cache_enable == False)
self._enable_caching = cache_enable
def _get_length_mks(self):
return self._length
def _get_length_nm(self):
return self._length * 1.0e9
length_mks = property(_get_length_mks)
length_nm = property(_get_length_nm)
def _damage_threshold_mks(self):
return self._crystal_properties['damage_threshold_GW_per_sqcm'] * 1.0e13
damage_threshold_mks = property(_damage_threshold_mks)